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