Expanded CMTS emulator to respond properly to gate add requests.
[packetcable.git] / packetcable-policy-server / src / test / java / org / opendaylight / controller / packetcable / provider / PCMMServiceTest.java
1 /*
2  * (c) 2015 Cable Television Laboratories, Inc.  All rights reserved.
3  */
4
5 package org.opendaylight.controller.packetcable.provider;
6
7 import org.junit.After;
8 import org.junit.Assert;
9 import org.junit.Before;
10 import org.junit.Test;
11 import org.mockito.Mockito;
12 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
13 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
14 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber;
15 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceClassName;
16 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection;
17 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.TosByte;
18 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.TpProtocol;
19 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps;
20 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.AmId;
21 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.Connection;
22 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.classifier.Classifier;
23 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gate.spec.GateSpec;
24 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates;
25 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.traffic.profile.TrafficProfile;
26 import org.pcmm.PCMMPdpAgent;
27 import org.pcmm.gates.IGateSpec.Direction;
28 import org.pcmm.gates.IPCMMGate;
29 import org.pcmm.rcd.IPCMMClient;
30 import org.pcmm.rcd.impl.CMTS;
31 import org.umu.cops.stack.*;
32 import org.umu.cops.stack.COPSContext.RType;
33 import org.umu.cops.stack.COPSDecision.Command;
34 import org.umu.cops.stack.COPSDecision.DecisionFlag;
35 import org.umu.cops.stack.COPSObjHeader.CNum;
36 import org.umu.cops.stack.COPSObjHeader.CType;
37
38 import java.io.*;
39 import java.net.InetAddress;
40 import java.net.Socket;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Map;
44 import java.util.Set;
45
46 /**
47  * Tests the PCMMService's ability to connect to a CMTS. Gate additions will not properly work as there is currently
48  * not any other means to receive acknowledgements. This functionality must be tested by the PCMMService's client
49  * PacketcableProvider.
50  */
51 public class PCMMServiceTest {
52
53     private final static String ccapId = "ccap-1";
54     private final static String gatePath = "testGatePath";
55
56     /**
57      * Denotes whether or not a real CMTS is being tested against.
58      * Ensure the checked-in value is always false else tests will most likely fail.
59      */
60     private final static boolean realCmts = false;
61
62
63     // The test objects/values to use that will be instantiated in @Before
64
65     /**
66      * The mock CMTS running on localhost with a dynamic port assigned.
67      */
68     private CMTS icmts;
69
70     /**
71      * The IP address object for the CMTS to test against
72      */
73     private Ipv4Address cmtsAddr;
74
75     /**
76      * The gate classifier's srcIp value, any valid IP should work.
77      */
78     private Ipv4Address srcAddr;
79
80     /**
81      * The gate classifier's dstIp value, any valid IP should work.
82      */
83     private Ipv4Address dstAddr;
84
85     /**
86      * Defines the CMTS to add to the PCMMService
87      */
88     private Ccaps ccap;
89
90     /**
91      * The class under test
92      */
93     private PCMMService service;
94
95     /**
96      * The cable modem IP address to which a gate should be set
97      */
98     private InetAddress cmAddrInet;
99     private InetAddress invalidCmAddrInet;
100
101     @Before
102     public void setup() throws IOException {
103         final Set<String> upGates = new HashSet<>();
104         upGates.add("extrm_up");
105         final Set<String> dnGates = new HashSet<>();
106         dnGates.add("extrm_dn");
107         final Map<Direction, Set<String>> gates = new HashMap<>();
108         gates.put(Direction.UPSTREAM, upGates);
109         gates.put(Direction.DOWNSTREAM, dnGates);
110
111         srcAddr = new Ipv4Address("10.10.10.0");
112         dstAddr = new Ipv4Address("10.32.99.99");
113
114         if (realCmts) {
115             cmAddrInet = InetAddress.getByAddress(new byte[] {10, 32, 110, (byte)180});
116             invalidCmAddrInet = InetAddress.getByAddress(new byte[] {99, 99, 99, 99});
117
118             // Use me when testing against a CMTS or emulator not running in the same JVM
119             cmtsAddr = new Ipv4Address("10.32.10.3");
120             ccap = makeCcapsObj(PCMMPdpAgent.WELL_KNOWN_PDP_PORT, cmtsAddr, ccapId);
121         } else {
122             cmAddrInet = InetAddress.getByAddress(new byte[] {10, 32, 110, (byte)180});
123             invalidCmAddrInet = InetAddress.getByAddress(new byte[] {99, 99, 99, 99});
124
125             // Use me for automated testing and the CMTS emulator running in the same JVM
126             cmtsAddr = new Ipv4Address("127.0.0.1");
127
128             final Map<String, Boolean> cmStatus = new HashMap<>();
129             cmStatus.put(cmAddrInet.getHostAddress(), true);
130             cmStatus.put(invalidCmAddrInet.getHostAddress(), false);
131             icmts = new CMTS(gates, cmStatus);
132             icmts.startServer();
133
134             ccap = makeCcapsObj(icmts.getPort(), cmtsAddr, ccapId);
135         }
136
137         service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
138     }
139
140     @After
141     public void tearDown() {
142         if (icmts != null) icmts.stopServer();
143         service.disconect();
144     }
145
146     @Test
147     public void testAddCcap() {
148         connectToCmts(service);
149     }
150
151     @Test
152     public void testAddInvalidCcapBadHost() {
153         cmtsAddr = new Ipv4Address("0.0.0.0");
154         ccap = makeCcapsObj(PCMMPdpAgent.WELL_KNOWN_PDP_PORT, cmtsAddr, ccapId);
155         service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
156         final String message = service.addCcap();
157         Assert.assertNotNull(message);
158         final String expectedMsg = "404 Not Found - CCAP " + ccapId + " failed to connect @ " + cmtsAddr.getValue()
159                 + ':' + PCMMPdpAgent.WELL_KNOWN_PDP_PORT + " - Connection refused";
160         Assert.assertEquals(expectedMsg, message);
161     }
162
163     @Test
164     public void testAddInvalidCcapBadPort() {
165         ccap = makeCcapsObj(1234, cmtsAddr, ccapId);
166         service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
167         final String message = service.addCcap();
168         Assert.assertNotNull(message);
169         final String expectedMsg = "404 Not Found - CCAP " + ccapId + " failed to connect @ " + cmtsAddr.getValue()
170                 + ":1234 - Connection refused";
171         Assert.assertEquals(expectedMsg, message);
172     }
173
174     @Test
175     public void testAddValidUpGateTwice() throws Exception {
176         connectToCmts(service);
177         final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
178         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
179                 expectedMsg1);
180
181         final String expectedMsg2 = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath + " already exists";
182         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
183                 expectedMsg2);
184
185         Assert.assertTrue(deleteGate(service, gatePath));
186     }
187
188     @Test
189     public void testAddTwoValidUpGates() throws Exception {
190         connectToCmts(service);
191
192         final String gatePath1 = "gatePath1";
193         final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath1 + " returned GateId";
194         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath1,
195                 expectedMsg1);
196
197         final String gatePath2 = "gatePath2";
198         final String expectedMsg2 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath2 + " returned GateId";
199         addAndValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath2,
200                 expectedMsg2);
201
202         Assert.assertTrue(deleteGate(service, gatePath1));
203         Assert.assertTrue(deleteGate(service, gatePath2));
204     }
205
206     @Test
207     public void testAddValidDownGateTwice() throws Exception {
208         connectToCmts(service);
209         final String expectedMsg1 = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
210         addAndValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
211                 expectedMsg1);
212
213         final String expectedMsg2 = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath + " already exists";
214         addAndValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
215                 expectedMsg2);
216
217         Assert.assertTrue(deleteGate(service, gatePath));
218     }
219
220     @Test
221     public void testDeleteNonExistentGate() throws Exception {
222         connectToCmts(service);
223         Assert.assertFalse(deleteGate(service, gatePath));
224     }
225
226     @Test
227     public void testAddAndRemoveValidUpGate() throws Exception {
228         final String expectedMsgStart = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
229         addRemoveValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet, gatePath,
230                 expectedMsgStart);
231     }
232
233     @Test
234     public void testAddAndRemoveValidDownGate() throws Exception {
235         final String expectedMsgStart = "200 OK - sendGateSet for " + ccapId + '/' + gatePath + " returned GateId";
236         addRemoveValidateGate(service, "extrm_dn", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet, gatePath,
237                 expectedMsgStart);
238     }
239
240     @Test
241     public void testAddAndRemoveInvalidCmAddrUpGate() throws Exception {
242         // TODO - fix cmts emulator
243         final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
244                 + " returned error - Error Code: 13 Error Subcode : 0  Invalid SubscriberID";
245         addRemoveValidateGate(service, "extrm_up", srcAddr, dstAddr, ServiceFlowDirection.Us, invalidCmAddrInet,
246                 gatePath, expectedMsgStart);
247     }
248
249     @Test
250     public void testAddInvalidScnUpGate() throws Exception {
251         final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
252                 + " returned error - Error Code: 11 Error Subcode : 0  Undefined Service Class Name";
253         addRemoveValidateGate(service, "extrm_up_invalid", srcAddr, dstAddr, ServiceFlowDirection.Us, cmAddrInet,
254                 gatePath, expectedMsgStart);
255     }
256
257     @Test
258     public void testAddInvalidScnDownGate() throws Exception {
259         final String expectedMsgStart = "404 Not Found - sendGateSet for " + ccapId + '/' + gatePath
260                 + " returned error - Error Code: 11 Error Subcode : 0  Undefined Service Class Name";
261         addRemoveValidateGate(service, "extrm_dn_invalid", srcAddr, dstAddr, ServiceFlowDirection.Ds, cmAddrInet,
262                 gatePath, expectedMsgStart);
263     }
264
265     /**
266      * This tests the instantiation of a COPSDecisionMsg object that is responsible for setting a gate request,
267      * streams it over a mock Socket object and parses the bytes into a new COPSDecisionMsg object which should
268      * be equivalent
269      * @throws Exception - test will fail should any exception be thrown during execution
270      */
271     @Test
272     public void testGateRequestDecisionMsg() throws Exception {
273         final Socket socket = new MockSocket();
274
275         final ServiceFlowDirection direction = ServiceFlowDirection.Us;
276         final Gates gate = makeGateObj("extrm_up", cmtsAddr, direction, new Ipv4Address("127.0.0.1"));
277         final IPCMMGate gateReq = makeGateRequest(ccap, gate, InetAddress.getByName("localhost"), direction);
278         final byte[] data = gateReq.getData();
279
280         final Set<COPSDecision> decisionSet = new HashSet<>();
281         decisionSet.add(new COPSDecision(CType.DEF, Command.INSTALL, DecisionFlag.REQERROR));
282         final Map<COPSContext, Set<COPSDecision>> decisionMap = new HashMap<>();
283         decisionMap.put(new COPSContext(RType.CONFIG, (short) 0), decisionSet);
284
285         final COPSClientSI clientSD = new COPSClientSI(CNum.DEC, CType.CSI, new COPSData(data, 0, data.length));
286         final COPSDecisionMsg decisionMsg = new COPSDecisionMsg(IPCMMClient.CLIENT_TYPE, new COPSHandle(new COPSData("123")),
287                 decisionMap, null, clientSD);
288         decisionMsg.writeData(socket);
289
290         final COPSMsg msg = COPSMsgParser.parseMessage(socket);
291         Assert.assertNotNull(msg);
292         Assert.assertEquals(decisionMsg, msg);
293     }
294
295     /**
296      * Attempts to create a gate against a CMTS, validates the results then attempts to delete it.
297      * @param service - the service used to connect to a CMTS for issuing requests
298      * @param scnName - the service class name (aka. gate name)
299      * @param srcAddr - the address to the CMTS subnet?
300      * @param dstAddr - the destination address
301      * @param direction - the gate direction
302      * @param cmAddrInet - the address to the cable modem to which the gate will be assigned
303      * @param gatePath - the path to the gate
304      * @param expGateSetMsgStart - the expected start of the gate set return message to be validated against
305      */
306     private void addRemoveValidateGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
307                                        final Ipv4Address dstAddr, final ServiceFlowDirection direction,
308                                        final InetAddress cmAddrInet, final String gatePath,
309                                        final String expGateSetMsgStart) {
310         connectToCmts(service);
311         addAndValidateGate(service, scnName, srcAddr, dstAddr, direction, cmAddrInet, gatePath, expGateSetMsgStart);
312         deleteGate(service, gatePath);
313     }
314
315     private void connectToCmts(final PCMMService service) {
316         final String message = service.addCcap();
317         Assert.assertNotNull(message);
318         final String expectedMsg = "200 OK - CCAP " + ccapId + " connected @ "
319                 + ccap.getConnection().getIpAddress().getIpv4Address().getValue()
320                 + ":" + ccap.getConnection().getPort().getValue();
321         Assert.assertEquals(expectedMsg, message);
322         Assert.assertNotNull(service.ccapClient.pcmmPdp.getClientHandle());
323     }
324
325     /**
326      * Attempts to create a gate against a CMTS and validates the results.
327      * @param service - the service used to connect to a CMTS for issuing requests
328      * @param scnName - the service class name (aka. gate name)
329      * @param srcAddr - the address to the CMTS subnet?
330      * @param dstAddr - the destination address
331      * @param direction - the gate direction
332      * @param cmAddrInet - the address to the cable modem to which the gate will be assigned
333      * @param gatePath - the path to the gate
334      * @param expGateSetMsgStart - the expected start of the gate set return message to be validated against
335      */
336     private void addAndValidateGate(final PCMMService service, final String scnName, final Ipv4Address srcAddr,
337                                     final Ipv4Address dstAddr, final ServiceFlowDirection direction,
338                                     final InetAddress cmAddrInet, final String gatePath,
339                                     final String expGateSetMsgStart) {
340         final Gates gate = makeGateObj(scnName, srcAddr, direction, dstAddr);
341
342         final String gateSetMsg = service.sendGateSet(gatePath, cmAddrInet, gate, direction);
343         Assert.assertNotNull(gateSetMsg);
344         Assert.assertTrue(gateSetMsg, gateSetMsg.startsWith(expGateSetMsgStart));
345
346         // TODO - add validation to the PCMMGateReq contained within the map
347         Assert.assertNotNull(service.gateRequests.get(gatePath));
348     }
349
350     /**
351      * Attempts to delete a gate
352      * @param service - the service used to connect to a CMTS for issuing requests
353      * @param gatePath - the path to the gate
354      */
355     private boolean deleteGate(final PCMMService service, final String gatePath) {
356         final boolean out = service.sendGateDelete(gatePath);
357
358         // Wait up to 1 sec for response to be processed
359         final long start = System.currentTimeMillis();
360         while (1000 < System.currentTimeMillis() - start) {
361             if (service.gateRequests.size() == 0) break;
362         }
363         Assert.assertNull(service.gateRequests.get(gatePath));
364         return out;
365     }
366
367     /**
368      * Creates a mock Ccaps object that can be used for connecting to a CMTS
369      * @param inPort - the CMTS port number
370      * @param ipAddr - the CMTS IPv4 address string
371      * @param ccapId - the ID of the CCAP
372      * @return - the mock Ccaps object
373      */
374     private Ccaps makeCcapsObj(final int inPort, final Ipv4Address ipAddr, final String ccapId) {
375         final Ccaps ccap = Mockito.mock(Ccaps.class);
376         final Connection conn = Mockito.mock(Connection.class);
377         Mockito.when(ccap.getConnection()).thenReturn(conn);
378         final PortNumber port = Mockito.mock(PortNumber.class);
379         Mockito.when(conn.getPort()).thenReturn(port);
380         Mockito.when(port.getValue()).thenReturn(inPort);
381
382         final IpAddress addr = Mockito.mock(IpAddress.class);
383         Mockito.when(conn.getIpAddress()).thenReturn(addr);
384         Mockito.when(addr.getIpv4Address()).thenReturn(ipAddr);
385
386         Mockito.when(ccap.getCcapId()).thenReturn(ccapId);
387         final AmId amid = Mockito.mock(AmId.class);
388         Mockito.when(ccap.getAmId()).thenReturn(amid);
389         Mockito.when(amid.getAmTag()).thenReturn(0xcada);
390         Mockito.when(amid.getAmType()).thenReturn(1);
391
392         return ccap;
393     }
394
395     /**
396      * Creates a mock Gates object
397      * @param scnValue - the service class name defined on the CMTS
398      * @param dstAddr - the CM address this gate should be set against
399      * @return - the gate request
400      */
401     private Gates makeGateObj(final String scnValue, final Ipv4Address srcAddr, final ServiceFlowDirection direction,
402                               final Ipv4Address dstAddr) {
403         final Gates gate = Mockito.mock(Gates.class);
404         final GateSpec gateSpec = Mockito.mock(GateSpec.class);
405         Mockito.when(gate.getGateSpec()).thenReturn(gateSpec);
406         Mockito.when(gateSpec.getDirection()).thenReturn(direction);
407         // TODO - make sure to write a test when this value is not null
408         Mockito.when(gateSpec.getDscpTosOverwrite()).thenReturn(null);
409         final TrafficProfile trafficProfile = Mockito.mock(TrafficProfile.class);
410         final ServiceClassName scn = Mockito.mock(ServiceClassName.class);
411         Mockito.when(scn.getValue()).thenReturn(scnValue);
412         Mockito.when(trafficProfile.getServiceClassName()).thenReturn(scn);
413         Mockito.when(gate.getTrafficProfile()).thenReturn(trafficProfile);
414
415         // TODO - write tests when this is null and ExtClassifier or Ipv6Classifier objects are not null
416         final Classifier classifier = Mockito.mock(Classifier.class);
417
418         // This is the address of the CM
419         Mockito.when(classifier.getDstIp()).thenReturn(dstAddr);
420
421         final PortNumber dstPort = new PortNumber(4321);
422         Mockito.when(classifier.getDstPort()).thenReturn(dstPort);
423         final TpProtocol protocol = new TpProtocol(0);
424         Mockito.when(classifier.getProtocol()).thenReturn(protocol);
425         Mockito.when(classifier.getSrcIp()).thenReturn(srcAddr);
426         final PortNumber srcPort = new PortNumber(1234);
427         Mockito.when(classifier.getSrcPort()).thenReturn(srcPort);
428         final TosByte tosByte = new TosByte((short)160);
429         Mockito.when(classifier.getTosByte()).thenReturn(tosByte);
430         final TosByte tosMask = new TosByte((short)224);
431         Mockito.when(classifier.getTosMask()).thenReturn(tosMask);
432
433         // TODO - enhance to test support of the other classifier types
434         Mockito.when(gate.getClassifier()).thenReturn(classifier);
435         Mockito.when(gate.getExtClassifier()).thenReturn(null);
436         Mockito.when(gate.getIpv6Classifier()).thenReturn(null);
437         return gate;
438     }
439
440     private IPCMMGate makeGateRequest(final Ccaps ccap, final Gates gateReq, final InetAddress addrSubId,
441                                      final ServiceFlowDirection direction) {
442         final PCMMGateReqBuilder gateBuilder = new PCMMGateReqBuilder();
443         gateBuilder.build(ccap.getAmId());
444         gateBuilder.build(addrSubId);
445         // force gateSpec.Direction to align with SCN direction
446         final ServiceClassName scn = gateReq.getTrafficProfile().getServiceClassName();
447         if (scn != null) {
448             gateBuilder.build(gateReq.getGateSpec(), direction);
449         } else {
450             // not an SCN gate
451             gateBuilder.build(gateReq.getGateSpec(), null);
452         }
453         gateBuilder.build(gateReq.getTrafficProfile());
454
455         // pick a classifier type (only one for now)
456         if (gateReq.getClassifier() != null) {
457             gateBuilder.build(gateReq.getClassifier());
458         } else if (gateReq.getExtClassifier() != null) {
459             gateBuilder.build(gateReq.getExtClassifier());
460         } else if (gateReq.getIpv6Classifier() != null) {
461             gateBuilder.build(gateReq.getIpv6Classifier());
462         }
463         // assemble the final gate request
464         return gateBuilder.getGateReq();
465     }
466
467     private class MockSocket extends Socket {
468
469         private ByteArrayOutputStream os = new ByteArrayOutputStream();
470         private ByteArrayInputStream is;
471
472         @Override
473         public OutputStream getOutputStream() {
474             return os;
475         }
476
477         @Override
478         public InputStream getInputStream() {
479             if (is == null) is = new ByteArrayInputStream(os.toByteArray());
480             return is;
481         }
482     }
483
484 }