From 0083a5a611239424af45805d8507699401a8d549 Mon Sep 17 00:00:00 2001 From: Steven Pisarski Date: Wed, 29 Apr 2015 14:07:19 -0600 Subject: [PATCH] The provider module for the Arris designed APIs. Change-Id: I14b087d72c4ff180007cda2c9fd9992cf54e9906 Signed-off-by: Steven Pisarski --- .../org/pcmm/rcd/impl/AbstractPCMMServer.java | 24 +- .../src/main/java/org/pcmm/rcd/impl/CMTS.java | 16 +- packetcable-policy-server/pom.xml | 124 +++ .../provider/PCMMGateReqBuilder.java | 337 ++++++ .../packetcable/provider/PCMMService.java | 264 +++++ .../provider/PacketcableProvider.java | 490 +++++++++ .../packetcable/provider/Response.java | 114 +++ .../packetcable/provider/Subnet.java | 114 +++ .../provider/ValidateInstanceData.java | 968 ++++++++++++++++++ .../rev140131/PacketcableProviderModule.java | 47 + .../PacketcableProviderModuleFactory.java | 13 + .../main/yang/packetcable-policy-server.yang | 51 + .../packetcable/provider/PCMMServiceTest.java | 123 +++ pom.xml | 1 + 14 files changed, 2669 insertions(+), 17 deletions(-) create mode 100644 packetcable-policy-server/pom.xml create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMGateReqBuilder.java create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMService.java create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PacketcableProvider.java create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Response.java create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Subnet.java create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/ValidateInstanceData.java create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModule.java create mode 100644 packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModuleFactory.java create mode 100644 packetcable-policy-server/src/main/yang/packetcable-policy-server.yang create mode 100644 packetcable-policy-server/src/test/java/org/opendaylight/controller/packetcable/provider/PCMMServiceTest.java diff --git a/packetcable-driver/src/main/java/org/pcmm/rcd/impl/AbstractPCMMServer.java b/packetcable-driver/src/main/java/org/pcmm/rcd/impl/AbstractPCMMServer.java index f8d519e..5bd01d4 100644 --- a/packetcable-driver/src/main/java/org/pcmm/rcd/impl/AbstractPCMMServer.java +++ b/packetcable-driver/src/main/java/org/pcmm/rcd/impl/AbstractPCMMServer.java @@ -44,10 +44,18 @@ public abstract class AbstractPCMMServer implements IPCMMServer { IWorkerPool pool; + /** + * Constructor to use the port number contained within the PCMMProperties static object + */ protected AbstractPCMMServer() { this(PCMMProperties.get(PCMMConstants.PCMM_PORT, Integer.class)); } + /** + * Constructor for starting the server to a pre-defined port number. When 0 is used, the server socket will + * assign one for you. To determine which port is being used, call getPort() after startServer() is called. + * @param port - the port number on which to start the server + */ protected AbstractPCMMServer(int port) { // XXX - Assert.assertTrue(port >= 0 && port <= 65535); this.port = port; @@ -67,6 +75,7 @@ public abstract class AbstractPCMMServer implements IPCMMServer { return; try { serverSocket = new ServerSocket(port); + port = serverSocket.getLocalPort(); logger.info("Server started and listening on port :" + port); } catch (IOException e) { logger.error(e.getMessage()); @@ -151,21 +160,6 @@ public abstract class AbstractPCMMServer implements IPCMMServer { return null; } - /** - * @return the serverSocket - */ - public ServerSocket getServerSocket() { - return serverSocket; - } - - /** - * @param serverSocket - * the serverSocket to set - */ - public void setServerSocket(ServerSocket serverSocket) { - this.serverSocket = serverSocket; - } - /** * @return the port */ diff --git a/packetcable-driver/src/main/java/org/pcmm/rcd/impl/CMTS.java b/packetcable-driver/src/main/java/org/pcmm/rcd/impl/CMTS.java index 9270631..8f17553 100644 --- a/packetcable-driver/src/main/java/org/pcmm/rcd/impl/CMTS.java +++ b/packetcable-driver/src/main/java/org/pcmm/rcd/impl/CMTS.java @@ -20,12 +20,24 @@ import java.util.*; import java.util.concurrent.Callable; /** - * + * This class starts a mock CMTS that can be used for testing. */ public class CMTS extends AbstractPCMMServer implements ICMTS { + /** + * Constructor for having the server port automatically assigned + * Call getPort() after startServer() is called to determine the port number of the server + */ public CMTS() { - super(); + this(0); + } + + /** + * Constructor for starting the server to a pre-defined port number + * @param port - the port number on which to start the server. + */ + public CMTS(final int port) { + super(port); } @Override diff --git a/packetcable-policy-server/pom.xml b/packetcable-policy-server/pom.xml new file mode 100644 index 0000000..2af7323 --- /dev/null +++ b/packetcable-policy-server/pom.xml @@ -0,0 +1,124 @@ + + + 4.0.0 + + org.opendaylight.packetcable + packetcable + 1.2.0-SNAPSHOT + + packetcable-policy-server + bundle + + + 1.2.0-SNAPSHOT + + + + + ${project.groupId} + packetcable-driver + ${project.version} + + + ${project.groupId} + packetcable-policy-model + ${project.version} + + + org.opendaylight.yangtools.model + ietf-inet-types + + + org.opendaylight.yangtools.model + ietf-yang-types + + + org.opendaylight.controller + config-api + + + org.opendaylight.controller + sal-binding-api + + + org.opendaylight.controller + sal-binding-config + + + org.opendaylight.controller + sal-common-util + + + org.osgi + org.osgi.core + + + junit + junit + test + + + org.mockito + mockito-all + 2.0.2-beta + test + + + + + + + maven-checkstyle-plugin + + true + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.config.yang.toaster_provider, + * + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + config + + generate-sources + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${jmxGeneratorPath} + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${salGeneratorPath} + + + true + + + + + + + + scm:git:ssh://git.opendaylight.org:29418/controller.git + scm:git:ssh://git.opendaylight.org:29418/controller.git + HEAD + https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL + + diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMGateReqBuilder.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMGateReqBuilder.java new file mode 100644 index 0000000..5257632 --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMGateReqBuilder.java @@ -0,0 +1,337 @@ +/** + * Build PCMM gate requests from API QoS Gate objects + */ +package org.opendaylight.controller.packetcable.provider; + +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.TosByte; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.AmId; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.classifier.Classifier; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.ext.classifier.ExtClassifier; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gate.spec.GateSpec; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.ipv6.classifier.Ipv6Classifier; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.traffic.profile.TrafficProfile; +import org.pcmm.gates.*; +import org.pcmm.gates.IGateSpec.DSCPTOS; +import org.pcmm.gates.IGateSpec.Direction; +import org.pcmm.gates.impl.DOCSISServiceClassNameTrafficProfile; +import org.pcmm.gates.impl.PCMMGateReq; +import org.pcmm.gates.impl.SubscriberID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * + * PacketCable data processor + * + */ +public class PCMMGateReqBuilder { + + private Logger logger = LoggerFactory.getLogger(PCMMGateReqBuilder.class); + + private PCMMGateReq gateReq = null; + + public PCMMGateReqBuilder() { + gateReq = new org.pcmm.gates.impl.PCMMGateReq(); + } + + public PCMMGateReq getGateReq() { + return gateReq; + } + + public void build(AmId qosAmId){ + IAMID amId = new org.pcmm.gates.impl.AMID(); + amId.setApplicationMgrTag(qosAmId.getAmTag().shortValue()); + amId.setApplicationType(qosAmId.getAmType().shortValue()); + gateReq.setAMID(amId); + } + + public void build(InetAddress qosSubId){ + ISubscriberID subId = new SubscriberID(); + subId.setSourceIPAddress(qosSubId); + gateReq.setSubscriberID(subId); + } + + public void build(GateSpec qosGateSpec, ServiceFlowDirection scnDirection) { + IGateSpec gateSpec = new org.pcmm.gates.impl.GateSpec(); + // service flow direction + ServiceFlowDirection qosDir = null; + Direction gateDir = null; + if (scnDirection != null) { + qosDir = scnDirection; + } else if (qosGateSpec.getDirection() != null) { + qosDir = qosGateSpec.getDirection(); + } + if (qosDir == ServiceFlowDirection.Ds) { + gateDir = Direction.DOWNSTREAM; + } else if (qosDir == ServiceFlowDirection.Us) { + gateDir = Direction.UPSTREAM; + } + gateSpec.setDirection(gateDir); + // DSCP/TOS Overwrite + TosByte tosOverwrite = qosGateSpec.getDscpTosOverwrite(); + if (tosOverwrite != null) { + byte gateTos = tosOverwrite.getValue().byteValue(); + gateSpec.setDSCP_TOSOverwrite(DSCPTOS.ENABLE); + gateSpec.setDSCP_TOSOverwrite(gateTos); + TosByte tosMask = qosGateSpec.getDscpTosMask(); + if (tosMask != null) { + byte gateTosMask = tosMask.getValue().byteValue(); + gateSpec.setDSCP_TOSMask(gateTosMask); + } else { + gateSpec.setDSCP_TOSMask((byte)0xff); + } + } + gateReq.setGateSpec(gateSpec); + } + + public void build(TrafficProfile qosTrafficProfile) { + if (qosTrafficProfile.getServiceClassName() != null) { + String scn = qosTrafficProfile.getServiceClassName().getValue(); + DOCSISServiceClassNameTrafficProfile trafficProfile = new DOCSISServiceClassNameTrafficProfile(); + if (scn.length() <= 16) { // NB.16 char SCN is max length per PCMM spec + trafficProfile.setServiceClassName(scn); + gateReq.setTrafficProfile(trafficProfile); + } + } + } + + private InetAddress getByName(String ipAddressStr){ + InetAddress ipAddress = null; + try { + ipAddress = InetAddress.getByName(ipAddressStr); + } catch (UnknownHostException e) { + logger.error(e.getMessage()); + } + return ipAddress; + } + + public void build(Classifier qosClassifier) { + // Legacy classifier + IClassifier classifier = new org.pcmm.gates.impl.Classifier(); + classifier.setPriority((byte) 64); + if (qosClassifier.getProtocol() != null){ + classifier.setProtocol(qosClassifier.getProtocol().getValue().shortValue()); + } + if (qosClassifier.getSrcIp() != null) { + InetAddress sip = getByName(qosClassifier.getSrcIp().getValue()); + if (sip != null) { + classifier.setSourceIPAddress(sip); + } + } + if (qosClassifier.getDstIp() != null) { + InetAddress dip = getByName(qosClassifier.getDstIp().getValue()); + if (dip != null) { + classifier.setDestinationIPAddress(dip); + } + } + if (qosClassifier.getSrcPort() != null) { + classifier.setSourcePort(qosClassifier.getSrcPort().getValue().shortValue()); + } + if (qosClassifier.getDstPort() != null) { + classifier.setDestinationPort(qosClassifier.getDstPort().getValue().shortValue()); + } + if (qosClassifier.getTosByte() != null) { + classifier.setDSCPTOS(qosClassifier.getTosByte().getValue().byteValue()); + if (qosClassifier.getTosMask() != null) { + classifier.setDSCPTOSMask(qosClassifier.getTosMask().getValue().byteValue()); + } else { + // set default TOS mask + classifier.setDSCPTOSMask((byte)0xff); + } + } + // push the classifier to the gate request + gateReq.setClassifier(classifier); + } + + public void build(ExtClassifier qosExtClassifier) { + // Extended classifier + IExtendedClassifier extClassifier = new org.pcmm.gates.impl.ExtendedClassifier(); + extClassifier.setPriority((byte) 64); + extClassifier.setActivationState((byte) 0x01); + // Protocol -- zero is match any + if (qosExtClassifier.getProtocol() != null){ + extClassifier.setProtocol(qosExtClassifier.getProtocol().getValue().shortValue()); + } else { + extClassifier.setProtocol((short)0); + } + // Source IP address & mask + if (qosExtClassifier.getSrcIp() != null) { + InetAddress sip = getByName(qosExtClassifier.getSrcIp().getValue()); + if (sip != null) { + extClassifier.setSourceIPAddress(sip); + if (qosExtClassifier.getSrcIpMask() != null) { + InetAddress sipMask = getByName(qosExtClassifier.getSrcIpMask().getValue()); + extClassifier.setIPSourceMask(sipMask); + } else { + // default mask is /32 + extClassifier.setIPSourceMask(getByName("255.255.255.255")); + } + } + } + // Destination IP address & mask + if (qosExtClassifier.getDstIp() != null) { + InetAddress dip = getByName(qosExtClassifier.getDstIp().getValue()); + if (dip != null) { + extClassifier.setDestinationIPAddress(dip); + if (qosExtClassifier.getDstIpMask() != null) { + InetAddress dipMask = getByName(qosExtClassifier.getDstIpMask().getValue()); + extClassifier.setIPDestinationMask(dipMask); + } else { + // default mask is /32 + extClassifier.setIPDestinationMask(getByName("255.255.255.255")); + } + } + } + // default source port range must be set to match any even if qosExtClassifier has no range + // match any port range is 0-65535, NOT 0-0 + short startPort = (short)0; + short endPort = (short)65535; + if (qosExtClassifier.getSrcPortStart() != null) { + startPort = qosExtClassifier.getSrcPortStart().getValue().shortValue(); + endPort = startPort; + if (qosExtClassifier.getSrcPortEnd() != null) { + endPort = qosExtClassifier.getSrcPortEnd().getValue().shortValue(); + } + if (startPort > endPort) { + logger.warn("Start port %d > End port %d in ext-classifier source port range -- forcing to same", startPort, endPort); + endPort = startPort; + } + } + extClassifier.setSourcePortStart(startPort); + extClassifier.setSourcePortEnd(endPort); + // default destination port range must be set to match any even if qosExtClassifier has no range + // match any port range is 0-65535, NOT 0-0 + startPort = (short)0; + endPort = (short)65535; + if (qosExtClassifier.getDstPortStart() != null) { + startPort = qosExtClassifier.getDstPortStart().getValue().shortValue(); + endPort = startPort; + if (qosExtClassifier.getDstPortEnd() != null) { + endPort = qosExtClassifier.getDstPortEnd().getValue().shortValue(); + } + if (startPort > endPort) { + logger.warn("Start port %d > End port %d in ext-classifier destination port range -- forcing to same", startPort, endPort); + endPort = startPort; + } + } + extClassifier.setDestinationPortStart(startPort); + extClassifier.setDestinationPortEnd(endPort); + // DSCP/TOP byte + if (qosExtClassifier.getTosByte() != null) { + // OR in the DSCP/TOS enable bit 0x01 + extClassifier.setDSCPTOS((byte) (qosExtClassifier.getTosByte().getValue().byteValue() | 0x01)); + if (qosExtClassifier.getTosMask() != null) { + extClassifier.setDSCPTOSMask(qosExtClassifier.getTosMask().getValue().byteValue()); + } else { + // set default TOS mask + extClassifier.setDSCPTOSMask((byte)0xff); + } + } + // push the extended classifier to the gate request + gateReq.setClassifier(extClassifier); + } + + public void build(Ipv6Classifier qosIpv6Classifier) { + // IPv6 classifier + IIPv6Classifier ipv6Classifier = new org.pcmm.gates.impl.IPv6Classifier(); + ipv6Classifier.setPriority((byte) 64); + ipv6Classifier.setActivationState((byte) 0x01); + // Flow Label + if (qosIpv6Classifier.getFlowLabel() != null){ + ipv6Classifier.setFlowLabel(qosIpv6Classifier.getFlowLabel()); + ipv6Classifier.setFlowLabelEnableFlag((byte)0x01); + } + // Next Header + if (qosIpv6Classifier.getNextHdr() != null){ + ipv6Classifier.setNextHdr(qosIpv6Classifier.getNextHdr().getValue().shortValue()); + } else { + // default: match any nextHdr is 256 because nextHdr 0 is Hop-by-Hop option + ipv6Classifier.setNextHdr((short)256); + } + // Source IPv6 address & prefix len + byte prefLen; + if (qosIpv6Classifier.getSrcIp6() != null) { + String[] parts = qosIpv6Classifier.getSrcIp6().getValue().split("/"); + String Ipv6AddressStr = parts[0]; + InetAddress sip6 = getByName(Ipv6AddressStr); + if (sip6 != null) { + ipv6Classifier.setSourceIPAddress(sip6); + } + prefLen = (byte)128; + if (parts.length > 1) { + prefLen = (byte)Integer.parseInt(parts[1]); + } + ipv6Classifier.setSourcePrefixLen(prefLen); + } + // Destination IPv6 address & prefix len + if (qosIpv6Classifier.getDstIp6() != null) { + String[] parts = qosIpv6Classifier.getDstIp6().getValue().split("/"); + String Ipv6AddressStr = parts[0]; + InetAddress dip6 = getByName(Ipv6AddressStr); + if (dip6 != null) { + ipv6Classifier.setDestinationIPAddress(dip6); + } + prefLen = (byte)128; + if (parts.length > 1) { + prefLen = (byte)Integer.parseInt(parts[1]); + } + ipv6Classifier.setDestinationPrefixLen(prefLen); + } + // default source port range must be set to match any -- even if qosExtClassifier has no range value + // match any port range is 0-65535, NOT 0-0 + short startPort = (short)0; + short endPort = (short)65535; + if (qosIpv6Classifier.getSrcPortStart() != null) { + startPort = qosIpv6Classifier.getSrcPortStart().getValue().shortValue(); + endPort = startPort; + if (qosIpv6Classifier.getSrcPortEnd() != null) { + endPort = qosIpv6Classifier.getSrcPortEnd().getValue().shortValue(); + } + if (startPort > endPort) { + logger.warn("Start port %d > End port %d in ipv6-classifier source port range -- forcing to same", startPort, endPort); + endPort = startPort; + } + } + ipv6Classifier.setSourcePortStart(startPort); + ipv6Classifier.setSourcePortEnd(endPort); + // default destination port range must be set to match any -- even if qosExtClassifier has no range value + // match any port range is 0-65535, NOT 0-0 + startPort = (short)0; + endPort = (short)65535; + if (qosIpv6Classifier.getDstPortStart() != null) { + startPort = qosIpv6Classifier.getDstPortStart().getValue().shortValue(); + endPort = startPort; + if (qosIpv6Classifier.getDstPortEnd() != null) { + endPort = qosIpv6Classifier.getDstPortEnd().getValue().shortValue(); + } + if (startPort > endPort) { + logger.warn("Start port %d > End port %d in ipv6-classifier destination port range -- forcing to same", startPort, endPort); + endPort = startPort; + } + } + ipv6Classifier.setDestinationPortStart(startPort); + ipv6Classifier.setDestinationPortEnd(endPort); + // TC low, high, mask + if (qosIpv6Classifier.getTcLow() != null) { + ipv6Classifier.setTcLow(qosIpv6Classifier.getTcLow().getValue().byteValue()); + if (qosIpv6Classifier.getTcHigh() != null) { + ipv6Classifier.setTcHigh(qosIpv6Classifier.getTcHigh().getValue().byteValue()); + } + if (qosIpv6Classifier.getTcMask() != null) { + ipv6Classifier.setTcMask(qosIpv6Classifier.getTcMask().getValue().byteValue()); + } else { + // set default TOS mask + ipv6Classifier.setTcMask((byte)0xff); + } + } else { + // mask 0x00 is match any + ipv6Classifier.setTcMask((byte)0x00); + } + // push the IPv6 classifier to the gate request + gateReq.setClassifier(ipv6Classifier); + } +} diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMService.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMService.java new file mode 100644 index 0000000..3ccabb1 --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PCMMService.java @@ -0,0 +1,264 @@ +package org.opendaylight.controller.packetcable.provider; + +import com.google.common.collect.Maps; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceClassName; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates; +import org.pcmm.PCMMPdpAgent; +import org.pcmm.PCMMPdpDataProcess; +import org.pcmm.PCMMPdpMsgSender; +import org.pcmm.gates.impl.PCMMGateReq; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.umu.cops.prpdp.COPSPdpException; +import org.umu.cops.stack.COPSError; +import org.umu.cops.stack.COPSError.ErrorTypes; +import org.umu.cops.stack.COPSException; + +import javax.annotation.concurrent.ThreadSafe; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Map; + +/** + * Class responsible for managing the gates for a single CCAP. + */ +@ThreadSafe +public class PCMMService { + private Logger logger = LoggerFactory.getLogger(PCMMService.class); + + private final Ccaps ccap; + private final IpAddress ipAddr; + private final PortNumber portNum; + protected final CcapClient ccapClient; + private Map gateRequests = Maps.newConcurrentMap(); + + private final short clientType; + + public PCMMService(final short clientType, final Ccaps ccap) { + this.clientType = clientType; + this.ccap = ccap; + ipAddr = ccap.getConnection().getIpAddress(); + portNum = ccap.getConnection().getPort(); + ccapClient = new CcapClient(ipAddr, portNum); + logger.info("Attempting to add CCAP with ID {} @ {}:{}", ccap.getCcapId(), ipAddr.getIpv4Address().getValue(), portNum.getValue()); + } + + public void disconect() { + ccapClient.disconnect(); + } + + // TODO - try and change the return to something other than a String to be parsed to determine success + public String addCcap() { + ccapClient.connect(); + if (ccapClient.isConnected) { + logger.info("Connected to CCAP with ID - " + ccap.getCcapId()); + return String.format("200 OK - CCAP %s connected @ %s:%d", ccap.getCcapId(), + ipAddr.getIpv4Address().getValue(), portNum.getValue()); + } else { + return String.format("404 Not Found - CCAP %s failed to connect @ %s:%d - %s", + ccap.getCcapId(), + ipAddr.getIpv4Address().getValue(), portNum.getValue(), ccapClient.errMessage); + } + } + + // TODO - Consider creating an object to return that contains a success flag, message, and gate ID or gate object + // TODO FIXME - the gate appears to be getting set as per restconf but I am not seeing the proper logging occurring + public String sendGateSet(final String gatePathStr, final InetAddress subId, final Gates qosGate, + final ServiceFlowDirection scnDir) { + logger.info("Sending gate to CCAP with ID - " + ccap.getCcapId()); + // assemble the gate request for this subId + final PCMMGateReqBuilder gateBuilder = new PCMMGateReqBuilder(); + gateBuilder.build(ccap.getAmId()); + gateBuilder.build(subId); + // force gateSpec.Direction to align with SCN direction + final ServiceClassName scn = qosGate.getTrafficProfile().getServiceClassName(); + if (scn != null) { + gateBuilder.build(qosGate.getGateSpec(), scnDir); + } else { + // not an SCN gate + gateBuilder.build(qosGate.getGateSpec(), null); + } + gateBuilder.build(qosGate.getTrafficProfile()); + + // pick a classifier type (only one for now) + if (qosGate.getClassifier() != null) { + gateBuilder.build(qosGate.getClassifier()); + } else if (qosGate.getExtClassifier() != null) { + gateBuilder.build(qosGate.getExtClassifier()); + } else if (qosGate.getIpv6Classifier() != null) { + gateBuilder.build(qosGate.getIpv6Classifier()); + } + // assemble the final gate request + final PCMMGateReq gateReq = gateBuilder.getGateReq(); + + // and remember it + gateRequests.put(gatePathStr, gateReq); + // and send it to the CCAP + ccapClient.sendGateSet(gateReq); + // and wait for the COPS response to complete processing gate request + try { + // TODO - see PCMMPdpReqStateMan#processReport() gate.notify(). Should determine a better means to + // TODO - handle this synchronization. + // TODO - if not changing this, may want to make this timeout configurable + synchronized(gateReq) { + logger.info("Waiting 1000ms for gate request to be updated"); + gateReq.wait(1000); + logger.debug("Gate request error - " + gateReq.getError()); + logger.debug("Gate request ID - " + gateReq.getGateID()); + } + } catch (Exception e) { + logger.error("PCMMService: sendGateSet(): gate response timeout exceeded for " + + gatePathStr + '/' + gateReq, e); + return String.format("408 Request Timeout - gate response timeout exceeded for %s/%s", + ccap.getCcapId(), gatePathStr); + } + if (gateReq.getError() != null) { + logger.error("PCMMService: sendGateSet(): returned error: {}", + gateReq.getError().toString()); + return String.format("404 Not Found - sendGateSet for %s/%s returned error - %s", + ccap.getCcapId(), gatePathStr, gateReq.getError().toString()); + } else { + if (gateReq.getGateID() != null) { + logger.info(String.format("PCMMService: sendGateSet(): returned GateId %08x: ", + gateReq.getGateID().getGateID())); + return String.format("200 OK - sendGateSet for %s/%s returned GateId %08x", + ccap.getCcapId(), gatePathStr, gateReq.getGateID().getGateID()); + } else { + logger.info("PCMMService: sendGateSet(): no gateId returned:"); + return String.format("404 Not Found - sendGateSet for %s/%s no gateId returned", + ccap.getCcapId(), gatePathStr); + } + } + } + + public Boolean sendGateDelete(final Ccaps ccap, final String gatePathStr) { + logger.info("sendGateDelete() - " + ccap); + // recover the original gate request + final PCMMGateReq gateReq = gateRequests.remove(gatePathStr); + if (gateReq != null) { + ccapClient.sendGateDelete(gateReq); + // and wait for the response to complete + try { + // TODO - see PCMMPdpReqStateMan#processReport() gate.notify(). Should determine a better means to + // TODO - handle this synchronization. + synchronized(gateReq) { + gateReq.wait(1000); + } + } catch (InterruptedException e) { + logger.error("PCMMService: sendGateDelete(): gate response timeout exceeded for {}/{}", + gatePathStr, gateReq); + } + if (gateReq.getError() != null) { + logger.warn("PCMMService: sendGateDelete(): returned error: {}", gateReq.getError().toString()); + return false; + } else { + if (gateReq.getGateID() != null) { + logger.info(String.format("PCMMService: sendGateDelete(): deleted GateId %08x: ", gateReq.getGateID().getGateID())); + } else { + logger.error("PCMMService: sendGateDelete(): deleted but no gateId returned"); + } + return true; + } + } else { + return false; + } + } + + private String getIpAddressStr(final IpAddress ipAddress) { + final Ipv4Address ipv4 = ipAddress.getIpv4Address(); + if (ipv4 != null) { + return ipv4.getValue(); + } else { + return ipAddress.getIpv6Address().getValue(); + } + } + + protected class CcapClient { + public final PCMMPdpDataProcess pcmmProcess; + public final PCMMPdpAgent pcmmPdp; + + // TODO - Needs to be initialized in connect() method or removed altogether as usage seems to be creating a new + // TODO - one each time is is used + protected transient PCMMPdpMsgSender pcmmSender; + + private final String ipv4; + private final Integer port; + + private transient Boolean isConnected = false; + private transient String errMessage = null; + + public CcapClient(final IpAddress ccapIp, final PortNumber portNum) { + ipv4 = ccapIp.getIpv4Address().getValue(); + if (portNum != null) port = portNum.getValue(); + else port = PCMMPdpAgent.WELL_KNOWN_PDP_PORT; + // TODO FIXME - if this object is not null, gate processing will not work correctly + // TODO see - PCMMPdpReqStateMan#processReport() where the report type is success and the process is null + // pcmmProcess = new PCMMPdpDataProcess(); + pcmmProcess = null; + pcmmPdp = new PCMMPdpAgent(clientType, ipv4, port, pcmmProcess); + } + + public void connect( ) { + logger.info("Attempting to connect to host: " + ipv4 + " port: " + port); + try { + pcmmPdp.connect(); + + // Cannot instantiate until after pcmmPdp.connect() is called as this is where the client handle is created + // pcmmSender = new PCMMPdpMsgSender(PCMMDef.C_PCMM, pcmmPdp.getClientHandle(), pcmmPdp.getSocket()); + + isConnected = true; + } catch (Exception e) { + isConnected = false; + logger.error("Failed to connect to host: " + ipv4 + " port: " + port, e); + errMessage = e.getMessage(); + } + } + + public void disconnect() { + logger.info("CcapClient: disconnect(): {}:{}", ipv4, port); + try { + pcmmPdp.disconnect(pcmmPdp.getPepIdString(), new COPSError(ErrorTypes.SHUTTING_DOWN, ErrorTypes.NA)); + isConnected = false; + } catch (COPSException | IOException e) { + logger.error("CcapClient: disconnect(): {}:{} FAILED: {}", ipv4, port, e.getMessage()); + } + } + + // TODO - consider returning a new PCMMGateReq object or a future here instead of setting the ID on the old + // TODO - request by reference which makes the code more convoluted thus making issues more difficult to track down. + public Boolean sendGateSet(final PCMMGateReq gateReq) { + logger.info("CcapClient: sendGateSet(): {}:{} => {}", ipv4, port, gateReq); + try { + pcmmSender = new PCMMPdpMsgSender(clientType, pcmmPdp.getClientHandle(), pcmmPdp.getSocket()); + pcmmSender.sendGateSet(gateReq); + // TODO - determine if this is the correct place to perform this operation as this currently is the + // TODO - place where the gate ID can be set on the gateReq object + // pcmmSender.handleGateReport(pcmmPdp.getSocket()); + } catch (COPSPdpException e) { + logger.error("CcapClient: sendGateSet(): {}:{} => {} FAILED:", ipv4, port, gateReq, e); + } + // and save it back to the gateRequest object for gate delete later + gateReq.setGateID(pcmmSender.getGateID()); + + // TODO - determine why this method is always returning true??? + return true; + } + + public Boolean sendGateDelete(final PCMMGateReq gateReq) { + logger.info("CcapClient: sendGateDelete(): {}:{} => {}", ipv4, port, gateReq); + try { + pcmmSender = new PCMMPdpMsgSender(clientType, pcmmPdp.getClientHandle(), pcmmPdp.getSocket()); + pcmmSender.sendGateDelete(gateReq); + } catch (COPSPdpException e) { + logger.error("CcapClient: sendGateDelete(): {}:{} => {} FAILED: {}", ipv4, port, gateReq, e.getMessage()); + } + return true; + } + } +} + diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PacketcableProvider.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PacketcableProvider.java new file mode 100644 index 0000000..dfe737d --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/PacketcableProvider.java @@ -0,0 +1,490 @@ +package org.opendaylight.controller.packetcable.provider; + +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.md.sal.common.api.data.AsyncReadWriteTransaction; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.Ccap; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.Qos; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceClassName; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.CcapsKey; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.Apps; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.AppsKey; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.Subs; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.SubsKey; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.GatesKey; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.pcmm.rcd.IPCMMClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.concurrent.ThreadSafe; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Called by ODL framework to start this bundle. + * + * This class is responsible for processing messages received from ODL's restconf interface. + * TODO - Remove some of these state maps and move some of this into the PCMMService + */ +@ThreadSafe +public class PacketcableProvider implements DataChangeListener, AutoCloseable { + + private static final Logger logger = LoggerFactory.getLogger(PacketcableProvider.class); + + // keys to the /restconf/config/packetcable:ccap and /restconf/config/packetcable:qos config datastore + public static final InstanceIdentifier ccapIID = InstanceIdentifier.builder(Ccap.class).build(); + public static final InstanceIdentifier qosIID = InstanceIdentifier.builder(Qos.class).build(); + + /** + * The ODL object used to broker messages throughout the framework + */ + private final DataBroker dataBroker; + + /** + * The thread pool executor + */ + private final ExecutorService executor; + + // TODO - Revisit these maps and remove the ones no longer necessary + private final Map ccapMap = new ConcurrentHashMap<>(); + private final Map gateMap = new ConcurrentHashMap<>(); + private final Map gateCcapMap = new ConcurrentHashMap<>(); + private final Map subscriberSubnetsMap = new ConcurrentHashMap<>(); + private final Map> downstreamScnMap = new ConcurrentHashMap<>(); + private final Map> upstreamScnMap = new ConcurrentHashMap<>(); + + /** + * Holds a PCMMService object for each CCAP being managed. + */ + private final Map pcmmServiceMap = new ConcurrentHashMap<>(); + + /** + * Constructor + */ + public PacketcableProvider(final DataBroker dataBroker) { + logger.info("Starting provider"); + this.dataBroker = dataBroker; + executor = Executors.newCachedThreadPool(); + } + + /** + * Implemented from the AutoCloseable interface. + */ + @Override + public void close() throws ExecutionException, InterruptedException { + executor.shutdown(); + if (dataBroker != null) { + // remove our config datastore instances + final AsyncReadWriteTransaction, ?> tx = dataBroker.newReadWriteTransaction(); + tx.delete(LogicalDatastoreType.CONFIGURATION, ccapIID); + tx.delete(LogicalDatastoreType.CONFIGURATION, qosIID); + tx.commit().get(); + } + } + + public InetAddress getInetAddress(final String subId){ + try { + return InetAddress.getByName(subId); + } catch (UnknownHostException e) { + logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage()); + return null; + } + } + + private String getIpPrefixStr(final IpPrefix ipPrefix) { + final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix(); + if (ipv4 != null) { + return ipv4.getValue(); + } else { + return ipPrefix.getIpv6Prefix().getValue(); + } + } + + private void updateCcapMaps(final Ccaps ccap) { + // add ccap to the subscriberSubnets map + for (final IpPrefix ipPrefix : ccap.getSubscriberSubnets()) { + try { + subscriberSubnetsMap.put(Subnet.createInstance(getIpPrefixStr(ipPrefix)), ccap); + } catch (UnknownHostException e) { + logger.error("updateSubscriberSubnets: {}:{} FAILED: {}", ipPrefix, ccap, e.getMessage()); + } + } + // ccap to upstream SCN map + for (final ServiceClassName scn : ccap.getUpstreamScns()) { + if (upstreamScnMap.containsKey(scn)) { + upstreamScnMap.get(scn).add(ccap); + } else { + final List ccapList = new ArrayList<>(); + ccapList.add(ccap); + upstreamScnMap.put(scn, ccapList); + } + } + // ccap to downstream SCN map + for (final ServiceClassName scn : ccap.getDownstreamScns()) { + if (downstreamScnMap.containsKey(scn)) { + downstreamScnMap.get(scn).add(ccap); + } else { + final List ccapList = new ArrayList<>(); + ccapList.add(ccap); + downstreamScnMap.put(scn, ccapList); + } + } + } + + private void removeCcapFromAllMaps(final Ccaps ccap) { + // remove the ccap from all maps + // subscriberSubnets map + for (final Map.Entry entry : subscriberSubnetsMap.entrySet()) { + if (entry.getValue() == ccap) { + subscriberSubnetsMap.remove(entry.getKey()); + } + } + // ccap to upstream SCN map + for (final Map.Entry> entry : upstreamScnMap.entrySet()) { + final List ccapList = entry.getValue(); + ccapList.remove(ccap); + if (ccapList.isEmpty()) { + upstreamScnMap.remove(entry.getKey()); + } + } + // ccap to downstream SCN map + for (final Map.Entry> entry : downstreamScnMap.entrySet()) { + final List ccapList = entry.getValue(); + ccapList.remove(ccap); + if (ccapList.isEmpty()) { + downstreamScnMap.remove(entry.getKey()); + } + } + + final PCMMService service = pcmmServiceMap.remove(ccap); + if (service != null) service.disconect(); + } + + private Ccaps findCcapForSubscriberId(final InetAddress inetAddr) { + Ccaps matchedCcap = null; + int longestPrefixLen = -1; + for (final Map.Entry entry : subscriberSubnetsMap.entrySet()) { + final Subnet subnet = entry.getKey(); + if (subnet.isInNet(inetAddr)) { + int prefixLen = subnet.getPrefixLen(); + if (prefixLen > longestPrefixLen) { + matchedCcap = entry.getValue(); + longestPrefixLen = prefixLen; + } + } + } + return matchedCcap; + } + + private ServiceFlowDirection findScnOnCcap(final ServiceClassName scn, final Ccaps ccap) { + if (upstreamScnMap.containsKey(scn)) { + final List ccapList = upstreamScnMap.get(scn); + if (ccapList.contains(ccap)) { + return ServiceFlowDirection.Us; + } + } else if (downstreamScnMap.containsKey(scn)) { + final List ccapList = downstreamScnMap.get(scn); + if (ccapList.contains(ccap)) { + return ServiceFlowDirection.Ds; + } + } + return null; + } + + /** + * Implemented from the DataChangeListener interface. + */ + + private class InstanceData { + // CCAP Identity + public final Map, Ccaps> ccapIidMap = new HashMap<>(); + // Gate Identity + public String subId; + public final Map gatePathMap = new HashMap<>(); + public String gatePath; + public final Map, Gates> gateIidMap = new HashMap<>(); + // remove path for either CCAP or Gates + public final Set removePathList = new HashSet<>(); + + public InstanceData(final Map, DataObject> thisData) { + // only used to parse createdData or updatedData + getCcaps(thisData); + if (ccapIidMap.isEmpty()) { + getGates(thisData); + if (! gateIidMap.isEmpty()){ + gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId"); + } + } + } + + public InstanceData(final Set> thisData) { + // only used to parse the removedData paths + for (final InstanceIdentifier removeThis : thisData) { + getGatePathMap(removeThis); + if (gatePathMap.containsKey("ccapId")) { + gatePath = gatePathMap.get("ccapId"); + removePathList.add(gatePath); + } else if (gatePathMap.containsKey("gateId")) { + gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId") + "/" + gatePathMap.get("gateId"); + removePathList.add(gatePath); + } + } + } + private void getGatePathMap(final InstanceIdentifier thisInstance) { + logger.info("onDataChanged().getGatePathMap(): " + thisInstance); + try { + final InstanceIdentifier ccapInstance = thisInstance.firstIdentifierOf(Ccaps.class); + if (ccapInstance != null) { + final CcapsKey ccapKey = InstanceIdentifier.keyOf(ccapInstance); + if (ccapKey != null) { + gatePathMap.put("ccapId", ccapKey.getCcapId()); + } + } else { + // get the gate path keys from the InstanceIdentifier Map key set if they are there + final InstanceIdentifier appsInstance = thisInstance.firstIdentifierOf(Apps.class); + if (appsInstance != null) { + final AppsKey appKey = InstanceIdentifier.keyOf(appsInstance); + if (appKey != null) { + gatePathMap.put("appId", appKey.getAppId()); + } + } + final InstanceIdentifier subsInstance = thisInstance.firstIdentifierOf(Subs.class); + if (subsInstance != null) { + final SubsKey subKey = InstanceIdentifier.keyOf(subsInstance); + if (subKey != null) { + subId = subKey.getSubId(); + gatePathMap.put("subId", subId); + } + } + final InstanceIdentifier gatesInstance = thisInstance.firstIdentifierOf(Gates.class); + if (gatesInstance != null) { + final GatesKey gateKey = InstanceIdentifier.keyOf(gatesInstance); + if (gateKey != null) { + gatePathMap.put("gateId", gateKey.getGateId()); + } + } + } + } catch (ClassCastException err) { + logger.warn("Unexpected exception", err); + } + } + + private void getCcaps(final Map, DataObject> thisData) { + logger.info("onDataChanged().getCcaps(): " + thisData); + for (final Map.Entry, DataObject> entry : thisData.entrySet()) { + if (entry.getValue() instanceof Ccaps) { + ccapIidMap.put((InstanceIdentifier)entry.getKey(), (Ccaps)entry.getValue()); + } + } + } + + private void getGates(final Map, DataObject> thisData) { + logger.info("onDataChanged().getGates(): " + thisData); + for (final Map.Entry, DataObject> entry : thisData.entrySet()) { + if (entry.getValue() instanceof Gates) { + final Gates gate = (Gates)entry.getValue(); + final InstanceIdentifier gateIID = (InstanceIdentifier)entry.getKey(); + getGatePathMap(gateIID); + gateIidMap.put(gateIID, gate); + } + } + } + } + + @Override + public void onDataChanged(final AsyncDataChangeEvent, DataObject> change) { + logger.info("onDataChanged"); + // Determine what change action took place by looking at the change object's InstanceIdentifier sets + // and validate all instance data + if (!change.getCreatedData().isEmpty()) { + if (!new ValidateInstanceData(dataBroker, change.getCreatedData()).validateYang()) { + // leave now -- a bad yang object has been detected and a response object has been inserted + return; + } + onCreate(new InstanceData(change.getCreatedData())); + } else if (!change.getRemovedPaths().isEmpty()) { + onRemove(new InstanceData(change.getRemovedPaths())); + } else if (!change.getUpdatedData().isEmpty()) { + if (new ValidateInstanceData(dataBroker, change.getUpdatedData()).isResponseEcho()) { + // leave now -- this is an echo of the inserted response object + return; + } + onUpdate(new InstanceData(change.getUpdatedData())); + } else { + // we should not be here -- complain bitterly and return + logger.error("onDataChanged(): Unknown change action: " + change); + } + } + + private void onCreate(final InstanceData thisData) { + logger.info("onCreate(): " + thisData); + + // get the CCAP parameters + String message; + if (! thisData.ccapIidMap.isEmpty()) { + for (Map.Entry, Ccaps> entry : thisData.ccapIidMap.entrySet()) { + final Ccaps thisCcap = entry.getValue(); + // get the CCAP node identity from the Instance Data + final String ccapId = thisCcap.getCcapId(); + + if (pcmmServiceMap.get(thisCcap) == null) { + final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, thisCcap); + // TODO - may want to use the AMID but for the client type but probably not??? +/* + final PCMMService pcmmService = new PCMMService( + thisCcap.getAmId().getAmType().shortValue(), thisCcap); +*/ + pcmmServiceMap.put(thisCcap, pcmmService); + message = pcmmService.addCcap(); + if (message.contains("200 OK")) { + ccapMap.put(ccapId, thisCcap); + updateCcapMaps(thisCcap); + logger.info("onDataChanged(): created CCAP: {}/{} : {}", thisData.gatePath, thisCcap, message); + logger.info("onDataChanged(): created CCAP: {} : {}", thisData.gatePath, message); + } else { + logger.error("onDataChanged(): create CCAP Failed: {} : {}", thisData.gatePath, message); + } + // set the response string in the config ccap object using a new thread + executor.execute(new Response(dataBroker, entry.getKey(), thisCcap, message)); + } else { + logger.error("Already monitoring CCAP - " + thisCcap); + break; + } + } + } else { + // get the PCMM gate parameters from the ccapId/appId/subId/gateId path in the Maps entry (if new gate) + for (final Map.Entry, Gates> entry : thisData.gateIidMap.entrySet()) { + message = null; + final Gates gate = entry.getValue(); + final String gateId = gate.getGateId(); + final String gatePathStr = thisData.gatePath + "/" + gateId ; + final InetAddress subId = getInetAddress(thisData.subId); + if (subId != null) { + final Ccaps thisCcap = findCcapForSubscriberId(subId); + if (thisCcap != null) { + final String ccapId = thisCcap.getCcapId(); + // verify SCN exists on CCAP and force gateSpec.Direction to align with SCN direction + final ServiceClassName scn = gate.getTrafficProfile().getServiceClassName(); + if (scn != null) { + final ServiceFlowDirection scnDir = findScnOnCcap(scn, thisCcap); + if (scnDir != null) { + if (pcmmServiceMap.get(thisCcap) != null) { + message = pcmmServiceMap.get(thisCcap).sendGateSet(gatePathStr, subId, gate, scnDir); + if (message.contains("200 OK")) { + gateMap.put(gatePathStr, gate); + gateCcapMap.put(gatePathStr, thisCcap.getCcapId()); + logger.info("onDataChanged(): created QoS gate {} for {}/{}/{} - {}", + gateId, ccapId, gatePathStr, gate, message); + logger.info("onDataChanged(): created QoS gate {} for {}/{} - {}", + gateId, ccapId, gatePathStr, message); + } else { + logger.info("onDataChanged(): Unable to create QoS gate {} for {}/{}/{} - {}", + gateId, ccapId, gatePathStr, gate, message); + logger.error("onDataChanged(): Unable to create QoS gate {} for {}/{} - {}", + gateId, ccapId, gatePathStr, message); + } + } else { + logger.error("Unable to locate PCMM Service for CCAP - " + thisCcap); + break; + } + } else { + logger.error("PCMMService: sendGateSet(): SCN {} not found on CCAP {} for {}/{}", + scn.getValue(), thisCcap, gatePathStr, gate); + message = String.format("404 Not Found - SCN %s not found on CCAP %s for %s", + scn.getValue(), thisCcap.getCcapId(), gatePathStr); + } + } + } else { + final String subIdStr = thisData.subId; + message = String.format("404 Not Found - no CCAP found for subscriber %s in %s", + subIdStr, gatePathStr); + logger.info("onDataChanged(): create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}/{}", + gateId, subIdStr, gatePathStr, gate); + logger.error("onDataChanged(): create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}", + gateId, subIdStr, gatePathStr); + } + } else { + final String subIdStr = thisData.subId; + message = String.format("400 Bad Request - subId must be a valid IP address for subscriber %s in %s", + subIdStr, gatePathStr); + logger.info("onDataChanged(): create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}/{}", + gateId, subIdStr, gatePathStr, gate); + logger.error("onDataChanged(): create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}", + gateId, subIdStr, gatePathStr); + } + // set the response message in the config gate object using a new thread + executor.execute(new Response(dataBroker, entry.getKey(), gate, message)); + } + } + } + + private void onRemove(final InstanceData thisData) { + logger.info("onRemove(): " + thisData); + for (final String gatePathStr: thisData.removePathList) { + if (gateMap.containsKey(gatePathStr)) { + final Gates thisGate = gateMap.remove(gatePathStr); + final String gateId = thisGate.getGateId(); + final String ccapId = gateCcapMap.remove(gatePathStr); + final Ccaps thisCcap = ccapMap.get(ccapId); + final PCMMService service = pcmmServiceMap.get(thisCcap); + if (service != null) { + service.sendGateDelete(thisCcap, gatePathStr); + logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr, thisGate); + logger.info("onDataChanged(): removed QoS gate {} for {}/{}: ", gateId, ccapId, gatePathStr); + } else + logger.warn("Unable to send to locate PCMMService to send gate delete message with CCAP - " + + thisCcap); + } + } + for (final String ccapIdStr: thisData.removePathList) { + if (ccapMap.containsKey(ccapIdStr)) { + final Ccaps thisCcap = ccapMap.remove(ccapIdStr); + removeCcapFromAllMaps(thisCcap); + } + } + } + + private void onUpdate(final InstanceData oldData) { + logger.info("onUpdate(): " + oldData); + // update operation not allowed -- restore the original config object and complain + if (! oldData.ccapIidMap.isEmpty()) { + for (final Map.Entry, Ccaps> entry : oldData.ccapIidMap.entrySet()) { + final Ccaps ccap = entry.getValue(); + final String ccapId = ccap.getCcapId(); + String message = String.format("405 Method Not Allowed - %s: CCAP update not permitted (use delete); ", + ccapId); + // push new error message onto existing response + message += ccap.getResponse(); + // set the response message in the config object using a new thread -- also restores the original data + executor.execute(new Response(dataBroker, entry.getKey(), ccap, message)); + logger.error("onDataChanged(): CCAP update not permitted {}/{}", ccapId, ccap); + } + } else { + for (final Map.Entry, Gates> entry : oldData.gateIidMap.entrySet()) { + final Gates gate = entry.getValue(); + final String gatePathStr = oldData.gatePath + "/" + gate.getGateId() ; + String message = String.format("405 Method Not Allowed - %s: QoS Gate update not permitted (use delete); ", gatePathStr); + // push new error message onto existing response + message += gate.getResponse(); + // set the response message in the config object using a new thread -- also restores the original data + executor.execute(new Response(dataBroker, entry.getKey(), gate, message)); + logger.error("onDataChanged(): QoS Gate update not permitted: {}/{}", gatePathStr, gate); + } + } + } + +} diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Response.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Response.java new file mode 100644 index 0000000..88132bb --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Response.java @@ -0,0 +1,114 @@ +/** + * The Response object inserts a "response" message object into the given CCAP or Gate object in the + * config data store. + * + * N.B. Updates to the config datastore must be run in a separate thread from the onDataChange() notification. + * Therefore, the Response object must always be invoked via executor.execute(resonse) after it is + * configured during new Response(dataBroker, ccapIID, ccapbase, message) for example. + * + * Also note well that when a CCAP or Gate object is updated with this "response" message, it will trigger + * another recursive onDataChange() notification seen in the change.getUpdatedData() -- this update must be ignored. + */ +package org.opendaylight.controller.packetcable.provider; + +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.CcapsBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.GatesBuilder; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Response implements Runnable { + + private Logger logger = LoggerFactory.getLogger(Response.class); + private DataBroker dataBroker; + private String message = null; + private InstanceIdentifier ccapIID = null; + private Ccaps ccapBase = null; + private InstanceIdentifier gateIID = null; + private Gates gateBase = null; + + public Response(DataBroker dataBroker, InstanceIdentifier ccapIID, Ccaps ccapBase, String message) { + this.dataBroker = dataBroker; + this.ccapIID = ccapIID; + this.ccapBase = ccapBase; + this.message = message; + } + public Response(DataBroker dataBroker, InstanceIdentifier gateIID, Gates gateBase, String message) { + this.dataBroker = dataBroker; + this.gateIID = gateIID; + this.gateBase = gateBase; + this.message = message; + } + + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + public void addMessage(String message) { + this.message += message; + } + public InstanceIdentifier getCcapIID() { + return ccapIID; + } + public void setCcapIID(InstanceIdentifier ccapIID) { + this.ccapIID = ccapIID; + } + public Ccaps getCcapBase() { + return ccapBase; + } + public void setCcapBase(Ccaps ccapBase) { + this.ccapBase = ccapBase; + } + public InstanceIdentifier getGateIID() { + return gateIID; + } + public void setGateIID(InstanceIdentifier gateIID) { + this.gateIID = gateIID; + } + public Gates getGateBase() { + return gateBase; + } + public void setGateBase(Gates gateBase) { + this.gateBase = gateBase; + } + + @SuppressWarnings("deprecation") + public void setResponse(InstanceIdentifier ccapIID, Ccaps ccapBase, String message) { + CcapsBuilder ccapBuilder = new CcapsBuilder(ccapBase); + ccapBuilder.setResponse(message); + Ccaps ccap = ccapBuilder.build(); + WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction(); + writeTx.merge(LogicalDatastoreType.CONFIGURATION, ccapIID, ccap, true); + writeTx.commit(); + logger.debug("Response.setResponse(ccap) complete {} {} {}", message, ccap, ccapIID); + } + @SuppressWarnings("deprecation") + public void setResponse(InstanceIdentifier gateIID, Gates gateBase, String message) { + GatesBuilder gateBuilder = new GatesBuilder(gateBase); + gateBuilder.setResponse(message); + Gates gate = gateBuilder.build(); + WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction(); + writeTx.merge(LogicalDatastoreType.CONFIGURATION, gateIID, gate, true); + writeTx.commit(); + logger.debug("Response.setResponse(gate) complete: {} {} {}", message, gate, gateIID); + } + + @Override + public void run() { + if (ccapIID != null) { + setResponse(ccapIID, ccapBase, message); + } else if (gateIID != null) { + setResponse(gateIID, gateBase, message); + } else { + logger.error("Unknown Response: must be for a CCAP or Gate instance"); + } + } +} + diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Subnet.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Subnet.java new file mode 100644 index 0000000..c0db407 --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/Subnet.java @@ -0,0 +1,114 @@ +package org.opendaylight.controller.packetcable.provider; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * @author c3oe.de, based on snippets from Scott Plante, John Kugelmann + */ +public class Subnet +{ + final private int bytesSubnetCount; + final private BigInteger bigMask; + final private BigInteger bigSubnetMasked; + final private int prefixLen; + + /** For use via format "192.168.0.0/24" or "2001:db8:85a3:880:0:0:0:0/57" */ + public Subnet( final InetAddress subnetAddress, final int bits ) + { + this.prefixLen = bits; + this.bytesSubnetCount = subnetAddress.getAddress().length; // 4 or 16 + this.bigMask = BigInteger.valueOf( -1 ).shiftLeft( this.bytesSubnetCount*8 - bits ); // mask = -1 << 32 - bits + this.bigSubnetMasked = new BigInteger( subnetAddress.getAddress() ).and( this.bigMask ); + } + + /** For use via format "192.168.0.0/255.255.255.0" or single address */ + public Subnet( final InetAddress subnetAddress, final InetAddress mask ) + { + this.bytesSubnetCount = subnetAddress.getAddress().length; + this.bigMask = null == mask ? BigInteger.valueOf( -1 ) : new BigInteger( mask.getAddress() ); // no mask given case is handled here. + this.bigSubnetMasked = new BigInteger( subnetAddress.getAddress() ).and( this.bigMask ); + int lowestSetBit = bigMask.getLowestSetBit(); + this.prefixLen = lowestSetBit == -1 ? 0 : 32 - lowestSetBit; + } + + /** + * Subnet factory method. + * @param subnetMask format: "192.168.0.0/24" or "192.168.0.0/255.255.255.0" + * or single address or "2001:db8:85a3:880:0:0:0:0/57" + * @return a new instance + * @throws UnknownHostException thrown if unsupported subnet mask. + */ + public static Subnet createInstance( final String subnetMask ) + throws UnknownHostException + { + final String[] stringArr = subnetMask.split("/"); + if ( 2 > stringArr.length ) + return new Subnet( InetAddress.getByName( stringArr[ 0 ] ), null); + else if ( stringArr[ 1 ].contains(".") || stringArr[ 1 ].contains(":") ) + return new Subnet( InetAddress.getByName( stringArr[ 0 ] ), InetAddress.getByName( stringArr[ 1 ] ) ); + else + return new Subnet( InetAddress.getByName( stringArr[ 0 ] ), Integer.parseInt( stringArr[ 1 ] ) ); + } + + public int getPrefixLen() { + return prefixLen; + } + + public boolean isInNet( final InetAddress address ) + { + final byte[] bytesAddress = address.getAddress(); + if ( this.bytesSubnetCount != bytesAddress.length ) + return false; + final BigInteger bigAddress = new BigInteger( bytesAddress ); + return bigAddress.and( this.bigMask ).equals( this.bigSubnetMasked ); + } + + @Override + final public boolean equals( Object obj ) + { + if ( ! (obj instanceof Subnet) ) + return false; + final Subnet other = (Subnet)obj; + return this.bigSubnetMasked.equals( other.bigSubnetMasked ) && + this.bigMask.equals( other.bigMask ) && + this.bytesSubnetCount == other.bytesSubnetCount; + } + + @Override + final public int hashCode() + { + return this.bytesSubnetCount; + } + + @Override + public String toString() + { + final StringBuilder buf = new StringBuilder(); + bigInteger2IpString( buf, this.bigSubnetMasked, this.bytesSubnetCount ); + buf.append( '/' ); + bigInteger2IpString( buf, this.bigMask, this.bytesSubnetCount ); + return buf.toString(); + } + + static private void bigInteger2IpString( final StringBuilder buf, final BigInteger bigInteger, final int displayBytes ) + { + final boolean isIPv4 = 4 == displayBytes; + byte[] bytes = bigInteger.toByteArray(); + int diffLen = displayBytes - bytes.length; + final byte fillByte = 0 > (int)bytes[ 0 ] ? (byte)0xFF : (byte)0x00; + + int integer; + for ( int i = 0; i < displayBytes; i++ ) + { + if ( 0 < i && ! isIPv4 && i % 2 == 0 ) + buf.append( ':' ); + else if ( 0 < i && isIPv4 ) + buf.append( '.' ); + integer = 0xFF & (i < diffLen ? fillByte : bytes[ i - diffLen ]); + if ( ! isIPv4 && 0x10 > integer ) + buf.append( '0' ); + buf.append( isIPv4 ? integer : Integer.toHexString( integer ) ); + } + } +} \ No newline at end of file diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/ValidateInstanceData.java b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/ValidateInstanceData.java new file mode 100644 index 0000000..24cd448 --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/controller/packetcable/provider/ValidateInstanceData.java @@ -0,0 +1,968 @@ +/** + * Validate all instance data received from the config datastore via the onDataChange() notification. + * + * N.B. that yang typedefs are not validated when a PUT operation places them into the config datastore. + * This means that they can arrive at onDataChange() with invalid values. + * + * In particular integer range values and string patterns (such as IP prefix/len) are not checked + * and accessing these values via any object.getValue() method call will cause an exception (as yang + * finally gets around to actually enforcing the typedef). + */ +package org.opendaylight.controller.packetcable.provider; + +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.*; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceClassName; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.TosByte; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.TpProtocol; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.CcapsBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.AmId; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.AmIdBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.Connection; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.ConnectionBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.classifier.Classifier; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.classifier.ClassifierBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.ext.classifier.ExtClassifier; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.ext.classifier.ExtClassifierBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gate.spec.GateSpec; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gate.spec.GateSpecBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.GatesBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.ipv6.classifier.Ipv6Classifier; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.ipv6.classifier.Ipv6ClassifierBuilder; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.traffic.profile.TrafficProfile; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.traffic.profile.TrafficProfileBuilder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ValidateInstanceData { + + private final static Logger logger = LoggerFactory.getLogger(ValidateInstanceData.class); + + // Final members + private final DataBroker dataBroker; + private final ExecutorService executor; + + // Gate Identities + private final Map, Gates> gateIidMap; + + // CCAP Identity + private transient Ccaps ccap; + private transient InstanceIdentifier ccapIID; + + public ValidateInstanceData(final DataBroker dataBroker, final Map, DataObject> thisData) { + executor = Executors.newCachedThreadPool(); + this.dataBroker = dataBroker; + getCcap(thisData); + // TODO FIXME - this value is always null??? + if (ccap == null) { + getGates(thisData); + } + gateIidMap = new ConcurrentHashMap<>(); + } + public boolean isResponseEcho() { + // see if there is a response object in the updated data + // if so this is an echo of the response message insertion so our caller can exit right away + if (ccap != null && ccap.getResponse() != null) { + return true; + } else if (! gateIidMap.isEmpty() && gateIidMap.values().iterator().next().getResponse() != null) { + return true; + } + return false; + } + public boolean validateYang() { + final String badText = "400 Bad Request - Invalid Element Values in json object - "; + if (isResponseEcho()) { + // don't validiate the echo again + return true; + } + if (ccap != null) { + Response response = new Response(dataBroker, ccapIID, ccap, badText); + if (! validateCcap(ccap, response)) { + logger.error("Validate CCAP {} failed - {}", ccap.getCcapId(), response.getMessage()); + executor.execute(response); + return false; + } + } else if (! gateIidMap.isEmpty()) { + for (Map.Entry, Gates> entry : gateIidMap.entrySet()) { + InstanceIdentifier gateIID = entry.getKey(); + Gates gate = entry.getValue(); + Response response = new Response(dataBroker, gateIID, gate, badText); + if (! validateGate(gate, response)) { + logger.error("Validate Gate {} failed - {}", gate.getGateId(), response.getMessage()); + executor.execute(response); + return false; + } + } + } + return true; + } + + private void getCcap(final Map, DataObject> thisData) { + for (final Map.Entry, DataObject> entry : thisData.entrySet()) { + if (entry.getValue() instanceof Ccaps) { + ccap = (Ccaps)entry.getValue(); + // TODO FIXME - ClassCastException waiting to occur here!!! + ccapIID = (InstanceIdentifier) entry.getKey(); + } + } + } + + private void getGates(final Map, DataObject> thisData) { + for (final Map.Entry, DataObject> entry : thisData.entrySet()) { + if (entry.getValue() instanceof Gates) { + final Gates gate = (Gates)entry.getValue(); + // TODO FIXME - ClassCastException waiting to occur here!!! + final InstanceIdentifier gateIID = (InstanceIdentifier)entry.getKey(); + gateIidMap.put(gateIID, gate); + } + } + } + private String validateMethod(final Class thisClass, final Object thisObj, final String methodName) { + try { + final Method method = thisClass.getMethod(methodName); + method.invoke(thisObj); + } catch (IllegalArgumentException e) { + return e.getMessage(); + } catch (Exception e) { + return " "; +// error = String.format("%s.%s(): Method failed: %s ", thisClass.getSimpleName(), methodName, e.getMessage()); + } + return null; + } + + private boolean validateGateSpec(final Gates gate, final GatesBuilder gateBuilder, final Response response) { + // gate-spec + String message = ""; + String error; + boolean valid = true; + GateSpec gateSpec = gate.getGateSpec(); + if (gateSpec != null) { + final ServiceFlowDirection dir; + error = validateMethod(GateSpec.class, gateSpec, "getDirection"); + if (error == null) { + dir = gateSpec.getDirection(); + if (dir != null) { + if (gate.getTrafficProfile().getServiceClassName() != null) { + message += " gate-spec.direction not allowed for traffic-profile.SCN;"; + valid = false; + } + } + } else { + message += " gate-spec.direction invalid: must be 'us' or 'ds' -" + error; + dir = null; + valid = false; + } + final TosByte tosByte; + error = validateMethod(GateSpec.class, gateSpec, "getDscpTosOverwrite"); + if (error == null) { + tosByte = gateSpec.getDscpTosOverwrite(); + } else { + message += " gate-spec.dscp-tos-overwrite invalid: " + error; + tosByte = null; + valid = false; + } + final TosByte tosMask; + error = validateMethod(GateSpec.class, gateSpec, "getDscpTosMask"); + if (error == null) { + tosMask = gateSpec.getDscpTosMask(); + if (tosByte != null && tosMask == null) { + message += " gate-spec.dscp-tos-mask missing;"; + valid = false; + } + } else { + message += " gate-spec.dscp-tos-mask invalid: " + error; + tosMask = null; + valid = false; + } + if (! valid) { + // rebuild the gateSpec with nulls replacing bad values + final GateSpecBuilder gateSpecBuilder = new GateSpecBuilder(); + gateSpecBuilder.setDirection(dir); + gateSpecBuilder.setDscpTosOverwrite(tosByte); + gateSpecBuilder.setDscpTosMask(tosMask); + gateSpec = gateSpecBuilder.build(); + // update the gate + gateBuilder.setGateSpec(gateSpec); + } + } + if (! valid) { + response.addMessage(message); + } + return valid; + } + + private boolean validateTrafficProfile(final Gates gate, final GatesBuilder gateBuilder, final Response response) { + // traffic-profile + String message = ""; + boolean valid = true; + TrafficProfile profile = gate.getTrafficProfile(); + if (profile == null) { + message += " traffic-profile is required;"; + valid = false; + } else { + final ServiceClassName scn; + final String error = validateMethod(TrafficProfile.class, profile, "getServiceClassName"); + if (error == null) { + scn = profile.getServiceClassName(); + if (scn == null) { + message += " traffic-profile.service-class-name missing;"; + valid = false; + } + } else { + message += " traffic-profile.service-class-name invalid: must be 2-16 characters " + error; + scn = null; + valid = false; + } + if (! valid) { + final TrafficProfileBuilder profileBuilder = new TrafficProfileBuilder(); + // TODO FIXME - scn is always null??? + profileBuilder.setServiceClassName(scn); + profile = profileBuilder.build(); + // update the gate + gateBuilder.setTrafficProfile(profile); + } + } + if (! valid) { + response.addMessage(message); + } + return valid; + } + + // TODO FIXME - Break this method apart + private boolean validateClassifier(final Gates gate, final GatesBuilder gateBuilder, final Response response) { + // validate classifier + String message = ""; + boolean valid = true; + int count = 0; + Classifier classifier = gate.getClassifier(); + // SIP + final Ipv4Address sip; + String error = validateMethod(Classifier.class, classifier, "getSrcIp"); + if (error == null) { + sip = classifier.getSrcIp(); + count++; + } else { + message += " classifier.srcIp invalid: - " + error; + sip = null; + valid = false; + } + // DIP + final Ipv4Address dip; + error = validateMethod(Classifier.class, classifier, "getDstIp"); + if (error == null) { + dip = classifier.getDstIp(); + count++; + } else { + message += " classifier.dstIp invalid: - " + error; + dip = null; + valid = false; + } + // Protocol + final TpProtocol proto; + error = validateMethod(Classifier.class, classifier, "getProtocol"); + if (error == null) { + proto = classifier.getProtocol(); + count++; + } else { + message += " classifier.protocol invalid: - " + error; + proto = null; + valid = false; + } + // Source Port + final PortNumber sport; + error = validateMethod(Classifier.class, classifier, "getSrcPort"); + if (error == null) { + sport = classifier.getSrcPort(); + count++; + } else { + message += " classifier.srcPort invalid: - " + error; + sport = null; + valid = false; + } + // Destination Port + final PortNumber dport; + error = validateMethod(Classifier.class, classifier, "getDstPort"); + if (error == null) { + dport = classifier.getDstPort(); + count++; + } else { + message += " classifier.dstPort invalid: - " + error; + dport = null; + valid = false; + } + // TOS + final TosByte tosByte; + error = validateMethod(Classifier.class, classifier, "getTosByte"); + if (error == null) { + tosByte = classifier.getTosByte(); + count++; + } else { + message += " classifier.tosByte invalid: " + error; + tosByte = null; + valid = false; + } + final TosByte tosMask; + error = validateMethod(Classifier.class, classifier, "getTosMask"); + if (error == null) { + tosMask = classifier.getTosMask(); + if (tosByte != null && tosMask == null) { + message += " classifier.tosMask missing;"; + valid = false; + } + } else { + message += " classifier.tosMask invalid: " + error; + tosMask = null; + valid = false; + } + if (count == 0) { + message += " classifer must have at least one match field"; + valid = false; + } + if (! valid) { + final ClassifierBuilder cBuilder = new ClassifierBuilder(); + cBuilder.setSrcIp(sip); + cBuilder.setDstIp(dip); + cBuilder.setProtocol(proto); + cBuilder.setSrcPort(sport); + cBuilder.setDstPort(dport); + cBuilder.setTosByte(tosByte); + cBuilder.setTosMask(tosMask); + classifier = cBuilder.build(); + gateBuilder.setClassifier(classifier); + response.addMessage(message); + } + return valid; + } + + // TODO FIXME - breakup this method + private boolean validateExtClassifier(final Gates gate, final GatesBuilder gateBuilder, final Response response) { + // validate ext-classifier + String message = ""; + String error; + boolean valid = true; + int count = 0; + ExtClassifier extClassifier = gate.getExtClassifier(); + // SIP & mask + final Ipv4Address sip; + error = validateMethod(ExtClassifier.class, extClassifier, "getSrcIp"); + if (error == null) { + sip = extClassifier.getSrcIp(); + count++; + } else { + message += " ext-classifier.srcIp invalid: - " + error; + sip = null; + valid = false; + } + final Ipv4Address sipMask; + error = validateMethod(ExtClassifier.class, extClassifier, "getSrcIpMask"); + if (error == null) { + sipMask = extClassifier.getSrcIpMask(); + count++; + } else { + message += " ext-classifier.srcIpMask invalid: - " + error; + sipMask = null; + valid = false; + } + if (sip != null && sipMask == null) { + message += " ext-classifier.srcIpMask missing"; + valid = false; + } + // DIP & mask + final Ipv4Address dip; + error = validateMethod(ExtClassifier.class, extClassifier, "getDstIp"); + if (error == null) { + dip = extClassifier.getDstIp(); + count++; + } else { + message += " ext-classifier.dstIp invalid: - " + error; + dip = null; + valid = false; + } + final Ipv4Address dipMask; + error = validateMethod(ExtClassifier.class, extClassifier, "getDstIpMask"); + if (error == null) { + dipMask = extClassifier.getDstIpMask(); + count++; + } else { + message += " ext-classifier.srcIpMask invalid: - " + error; + dipMask = null; + valid = false; + } + if (dip != null && dipMask == null) { + message += " ext-classifier.dstIpMask missing;"; + valid = false; + } + // Protocol + final TpProtocol proto; + error = validateMethod(ExtClassifier.class, extClassifier, "getProtocol"); + if (error == null) { + proto = extClassifier.getProtocol(); + count++; + } else { + message += " ext-classifier.protocol invalid: - " + error; + proto = null; + valid = false; + } + // Source port range + final PortNumber sportStart; + error = validateMethod(ExtClassifier.class, extClassifier, "getSrcPortStart"); + if (error == null) { + sportStart = extClassifier.getSrcPortStart(); + count++; + } else { + message += " ext-classifier.srcPortStart invalid: - " + error; + sportStart = null; + valid = false; + } + final PortNumber sportEnd; + error = validateMethod(ExtClassifier.class, extClassifier, "getSrcPortEnd"); + if (error == null) { + sportEnd = extClassifier.getSrcPortEnd(); + count++; + } else { + message += " ext-classifier.srcPortEnd invalid: - " + error; + sportEnd = null; + valid = false; + } + if (sportStart != null && sportEnd != null) { + if (sportStart.getValue() > sportEnd.getValue()) { + message += " ext-classifier.srcPortStart greater than srcPortEnd"; + valid = false; + } + } + // Destination port range + final PortNumber dportStart; + error = validateMethod(ExtClassifier.class, extClassifier, "getDstPortStart"); + if (error == null) { + dportStart = extClassifier.getDstPortStart(); + count++; + } else { + message += " ext-classifier.dstPortStart invalid: - " + error; + dportStart = null; + valid = false; + } + final PortNumber dportEnd; + error = validateMethod(ExtClassifier.class, extClassifier, "getDstPortEnd"); + if (error == null) { + dportEnd = extClassifier.getDstPortEnd(); + count++; + } else { + message += " ext-classifier.dstPortEnd invalid: - " + error; + dportEnd = null; + valid = false; + } + if (dportStart != null && dportEnd != null) { + if (dportStart.getValue() > dportEnd.getValue()) { + message += " ext-classifier.dstPortStart greater than dstPortEnd"; + valid = false; + } + } + // TOS byte + final TosByte tosByte; + error = validateMethod(ExtClassifier.class, extClassifier, "getTosByte"); + if (error == null) { + tosByte = extClassifier.getTosByte(); + count++; + } else { + message += " ext-classifier.tosByte invalid: " + error; + tosByte = null; + valid = false; + } + final TosByte tosMask; + error = validateMethod(ExtClassifier.class, extClassifier, "getTosMask"); + if (error == null) { + tosMask = extClassifier.getTosMask(); + if (tosByte != null && tosMask == null) { + message += " ext-classifier.tosMask missing;"; + valid = false; + } + } else { + message += " ext-classifier.tosMask invalid: " + error; + tosMask = null; + valid = false; + } + if (count == 0) { + message += " ext-classifer must have at least one match field"; + valid = false; + } + if (! valid) { + final ExtClassifierBuilder cBuilder = new ExtClassifierBuilder(); + cBuilder.setSrcIp(sip); + cBuilder.setSrcIpMask(sipMask); + cBuilder.setDstIp(dip); + cBuilder.setDstIpMask(dipMask); + cBuilder.setProtocol(proto); + cBuilder.setSrcPortStart(sportStart); + cBuilder.setSrcPortEnd(sportEnd); + cBuilder.setDstPortStart(dportStart); + cBuilder.setDstPortEnd(dportEnd); + cBuilder.setTosByte(tosByte); + cBuilder.setTosMask(tosMask); + extClassifier = cBuilder.build(); + gateBuilder.setExtClassifier(extClassifier); + response.addMessage(message); + } + return valid; + } + + // TODO FIXME - break apart this method. + private boolean validateIpv6Classifier(final Gates gate, final GatesBuilder gateBuilder, final Response response) { + // validate ipv6-classifier + String message = ""; + String error; + boolean valid = true; + int count = 0; + Ipv6Classifier ipv6Classifier = gate.getIpv6Classifier(); + // Source IPv6 prefix + final Ipv6Prefix sip6; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getSrcIp6"); + if (error == null) { + sip6 = ipv6Classifier.getSrcIp6(); + count++; + } else { + message += " ipv6-classifier.srcIp invalid: - " + error; + sip6 = null; + valid = false; + } + // Destination IPv6 prefix + final Ipv6Prefix dip6; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getDstIp6"); + if (error == null) { + dip6 = ipv6Classifier.getDstIp6(); + count++; + } else { + message += " ipv6-classifier.dstIp invalid: - " + error; + dip6 = null; + valid = false; + } + // Flow label + Long flowLabel; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getFlowLabel"); + if (error == null) { + flowLabel = ipv6Classifier.getFlowLabel(); + if (flowLabel > 1048575) { + message += " ipv6-classifier.flowLabel invalid: - must be 0..1048575"; + flowLabel = null; + valid = false; + } else { + count++; + } + } else { + message += " ipv6-classifier.flowLabel invalid: - " + error; + flowLabel = null; + valid = false; + } + // Next Hdr + final TpProtocol nxtHdr; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getNextHdr"); + if (error == null) { + nxtHdr = ipv6Classifier.getNextHdr(); + count++; + } else { + message += " ipv6-classifier.nextHdr invalid: - " + error; + nxtHdr = null; + valid = false; + } + // Source port range + final PortNumber sportStart; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getSrcPortStart"); + if (error == null) { + sportStart = ipv6Classifier.getSrcPortStart(); + count++; + } else { + message += " ipv6-classifier.srcPortStart invalid: - " + error; + sportStart = null; + valid = false; + } + final PortNumber sportEnd; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getSrcPortEnd"); + if (error == null) { + sportEnd = ipv6Classifier.getSrcPortEnd(); + count++; + } else { + message += " ipv6-classifier.srcPortEnd invalid: - " + error; + sportEnd = null; + valid = false; + } + if (sportStart != null && sportEnd != null) { + if (sportStart.getValue() > sportEnd.getValue()) { + message += " ipv6-classifier.srcPortStart greater than srcPortEnd"; + valid = false; + } + } + // Destination port range + final PortNumber dportStart; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getDstPortStart"); + if (error == null) { + dportStart = ipv6Classifier.getDstPortStart(); + count++; + } else { + message += " ipv6-classifier.dstPortStart invalid: - " + error; + dportStart = null; + valid = false; + } + final PortNumber dportEnd; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getDstPortEnd"); + if (error == null) { + dportEnd = ipv6Classifier.getDstPortEnd(); + count++; + } else { + message += " ipv6-classifier.dstPortEnd invalid: - " + error; + dportEnd = null; + valid = false; + } + if (dportStart != null && dportEnd != null) { + if (dportStart.getValue() > dportEnd.getValue()) { + message += " ipv6-classifier.dstPortStart greater than dstPortEnd"; + valid = false; + } + } + // TC byte + final TosByte tcLow; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getTcLow"); + if (error == null) { + tcLow = ipv6Classifier.getTcLow(); + count++; + } else { + message += " ipv6-classifier.tc-low invalid: " + error; + tcLow = null; + valid = false; + } + final TosByte tcHigh; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getTcHigh"); + if (error == null) { + tcHigh = ipv6Classifier.getTcHigh(); + count++; + } else { + message += " ipv6-classifier.tc-high invalid: " + error; + tcHigh = null; + valid = false; + } + if (tcLow != null && tcHigh != null) { + if (tcLow.getValue() > tcHigh.getValue()) { + message += " ipv6-classifier.tc-low is greater than tc-high"; + valid = false; + } + } + final TosByte tcMask; + error = validateMethod(Ipv6Classifier.class, ipv6Classifier, "getTcMask"); + if (error == null) { + tcMask = ipv6Classifier.getTcMask(); + } else { + message += " ipv6-classifier.tc-mask invalid: " + error; + tcMask = null; + valid = false; + } + if (tcLow != null && tcHigh != null && tcMask == null) { + message += " ipv6-classifier.tc-mask missing;"; + valid = false; + } + if (count == 0) { + message += " ipv6-classifer must have at least one match field"; + valid = false; + } + // rebuild ? + if (! valid) { + final Ipv6ClassifierBuilder cBuilder = new Ipv6ClassifierBuilder(); + cBuilder.setSrcIp6(sip6); + cBuilder.setDstIp6(dip6); + cBuilder.setFlowLabel(flowLabel); + cBuilder.setNextHdr(nxtHdr); + cBuilder.setSrcPortStart(sportStart); + cBuilder.setSrcPortEnd(sportEnd); + cBuilder.setDstPortStart(dportStart); + cBuilder.setDstPortEnd(dportEnd); + cBuilder.setTcLow(tcLow); + cBuilder.setTcHigh(tcHigh); + cBuilder.setTcMask(tcMask); + ipv6Classifier = cBuilder.build(); + gateBuilder.setIpv6Classifier(ipv6Classifier); + response.addMessage(message); + } + return valid; + } + + // TODO FIXME - Do we really want the gate parameter object to be muted by this method? + private boolean validateGate(Gates gate, final Response response) { + // validate gate elements and null out invalid elements as we go + final GatesBuilder gateBuilder = new GatesBuilder(); + String message = ""; + boolean rebuild = false; + // gate-spec + if (! validateGateSpec(gate, gateBuilder, response)) { + rebuild = true; + } + // traffic-profile + if (! validateTrafficProfile(gate, gateBuilder, response)) { + rebuild = true; + } + // classifiers (one of legacy classifier, ext-classifier, or ipv6 classifier + final Classifier classifier = gate.getClassifier(); + final ExtClassifier extClassifier = gate.getExtClassifier(); + final Ipv6Classifier ipv6Classifier = gate.getIpv6Classifier(); + int count = 0; + if (classifier != null) { count++; } + if (extClassifier != null) { count++; } + if (ipv6Classifier != null) { count++; } + if (count < 1){ + response.addMessage(" Missing classifer: must have only 1 of classifier, ext-classifier, or ipv6-classifier"); + rebuild = true; + } else if (count > 1) { + response.addMessage(" Multiple classifiers: must have only 1 of classifier, ext-classifier, or ipv6-classifier"); + rebuild = true; + } else if (count == 1) { + if (classifier != null) { + // validate classifier + if (! validateClassifier(gate, gateBuilder, response)) { + rebuild = true; + } + } else if (extClassifier != null) { + //validate ext-classifier + if (! validateExtClassifier(gate, gateBuilder, response)) { + rebuild = true; + } + } else if (ipv6Classifier != null) { + // TODO FIXME - ipv6Classifier is always null??? + // validate ipv6-classifier + if (! validateIpv6Classifier(gate, gateBuilder, response)) { + rebuild = true; + } + } + } + // rebuild the gate object with valid data and set the response + if (rebuild) { + gateBuilder.setGateId(gate.getGateId()); + gateBuilder.setKey(gate.getKey()); + // TODO FIXME - the input parameter "gate" is being muted here??? + gate = gateBuilder.build(); + response.setGateBase(gate); + response.addMessage(message); + } + return (! rebuild); + } + + private boolean validateAmId(final Ccaps ccap, final CcapsBuilder ccapBuilder, final Response response) { + // amId + String message = ""; + String error; + boolean valid = true; + AmId amId = ccap.getAmId(); + if (amId == null) { + message += " amId is required;"; + valid = false; + } else { + final Integer amTag; + error = validateMethod(AmId.class, amId, "getAmTag"); + if (error == null) { + amTag = amId.getAmTag(); + if (amTag == null) { + message += " amId.amTag missing;"; + valid = false; + } + } else { + message += " amId.amTag invalid: " + error; + amTag = null; + valid = false; + } + final Integer amType; + error = validateMethod(AmId.class, amId, "getAmType"); + if (error == null) { + amType = amId.getAmType(); + if (amType == null) { + message += " amId.amType missing;"; + valid = false; + } + } else { + message += " amId.amType invalid: " + error; + amType = null; + valid = false; + } + if (! valid) { + final AmIdBuilder amIdBuilder = new AmIdBuilder(); + amIdBuilder.setAmTag(amTag); + amIdBuilder.setAmType(amType); + amId = amIdBuilder.build(); + ccapBuilder.setAmId(amId); + } + } + if (! valid) { + response.addMessage(message); + } + return valid; + } + + private boolean validateConnection(final Ccaps ccap, final CcapsBuilder ccapBuilder, final Response response) { + // connection + String message = ""; + String error; + boolean valid = true; + Connection conn = ccap.getConnection(); + if (conn == null) { + message += " connection is required;"; + valid = false; + } else { + // IP address + final IpAddress ipAddress; + error = validateMethod(Connection.class, conn, "getIpAddress"); + if (error == null) { + ipAddress = conn.getIpAddress(); + if (ipAddress == null) { + message += " connection.ipAddress missing;"; + valid = false; + } + } else { + message += " connection.ipAddress invalid: " + error; + ipAddress = null; + valid = false; + } + // Port number + final PortNumber portNum; + error = validateMethod(Connection.class, conn, "getPort"); + if (error == null) { + portNum = conn.getPort(); + } else { + message += " connection.port invalid: " + error; + portNum = null; + valid = false; + } + if (! valid) { + final ConnectionBuilder connBuilder = new ConnectionBuilder(); + connBuilder.setIpAddress(ipAddress); + connBuilder.setPort(portNum); + conn = connBuilder.build(); + ccapBuilder.setConnection(conn); + } + } + if (! valid) { + response.addMessage(message); + } + return valid; + } + + private boolean validateSubscriberSubnets(final Ccaps ccap, final CcapsBuilder ccapBuilder, + final Response response) { + // subscriber-subnets + String message = ""; + String error; + boolean valid = true; + List subnets = null; + error = validateMethod(Ccaps.class, ccap, "getSubscriberSubnets"); + if (error == null) { + subnets = ccap.getSubscriberSubnets(); + if (subnets == null) { + message += " subscriber-subnets is required;"; + valid = false; + } + } else { + message += " subscriber-subnets contains invalid IpPrefix - must be / format;" + error; + valid = false; + } + if (! valid) { + // TODO FIXME - subnets is always null??? + ccapBuilder.setSubscriberSubnets(subnets); + response.addMessage(message); + } + return valid; + } + + private boolean validateUpstreamScns(final Ccaps ccap, final CcapsBuilder ccapBuilder, final Response response) { + // upstream-scns + String message = ""; + String error; + boolean valid = true; + List usScns = null; + error = validateMethod(Ccaps.class, ccap, "getUpstreamScns"); + if (error == null) { + usScns = ccap.getUpstreamScns(); + if (usScns == null) { + message += " upstream-scns is required;"; + valid = false; + } + } else { + message += " upstream-scns contains invalid SCN - must be 2-16 characters;" + error; + valid = false; + } + if (! valid) { + // TODO FIXME - usScns is always null??? + ccapBuilder.setUpstreamScns(usScns); + response.addMessage(message); + } + return valid; + } + + private boolean validateDownstreamScns(final Ccaps ccap, final CcapsBuilder ccapBuilder, final Response response) { + // downstream-scns + String message = ""; + boolean valid = true; + List dsScns = null; + final String error = validateMethod(Ccaps.class, ccap, "getDownstreamScns"); + if (error == null) { + dsScns = ccap.getDownstreamScns(); + if (dsScns == null) { + message += " downstream-scns is required;"; + valid = false; + } + } else { + message += " downstream-scns contains invalid SCN - must be 2-16 characters;" + error; + valid = false; + } + if (! valid) { + // TODO FIXME - dsScns is always null??? + ccapBuilder.setDownstreamScns(dsScns); + response.addMessage(message); + } + return valid; + } + + + // TODO FIXME - Do we really want the ccap parameter object to be muted by this method? + private boolean validateCcap(Ccaps ccap, final Response response) { + // validate ccap and null out invalid elements as we go + final CcapsBuilder ccapBuilder = new CcapsBuilder(); + String message = ""; + boolean rebuild = false; + // amId + if ( ! validateAmId(ccap, ccapBuilder, response)) { + rebuild = true; + } + // connection + if ( ! validateConnection(ccap, ccapBuilder, response)) { + rebuild = true; + } + // subscriber-subnets + if ( ! validateSubscriberSubnets(ccap, ccapBuilder, response)) { + rebuild = true; + } + // upstream-scns + if ( ! validateUpstreamScns(ccap, ccapBuilder, response)) { + rebuild = true; + } + // downstream-scns + if ( ! validateDownstreamScns(ccap, ccapBuilder, response)) { + rebuild = true; + } + // rebuild the ccap object with valid data and set the response + if (rebuild) { + ccapBuilder.setCcapId(ccap.getCcapId()); + ccapBuilder.setKey(ccap.getKey()); + // TODO FIXME - the input parameter "ccap" is being muted here??? + ccap = ccapBuilder.build(); + response.setCcapBase(ccap); + response.addMessage(message); + } + return (! rebuild); + } +} + diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModule.java b/packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModule.java new file mode 100644 index 0000000..51212ba --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModule.java @@ -0,0 +1,47 @@ +package org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.packetcable.packetcable.policy.server.impl.rev140131; + +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.packetcable.provider.PacketcableProvider; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PacketcableProviderModule extends org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.packetcable.packetcable.policy.server.impl.rev140131.AbstractPacketcableProviderModule { + private static final Logger logger = LoggerFactory.getLogger(PacketcableProviderModule.class); + + public PacketcableProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public PacketcableProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.packetcable.packetcable.policy.server.impl.rev140131.PacketcableProviderModule oldModule, java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void customValidation() { + // add custom validation form module attributes here. + } + + @Override + public java.lang.AutoCloseable createInstance() { + logger.info("Creating PacketcableProvider instance"); + + final DataBroker dataBrokerService = getDataBrokerDependency(); + final PacketcableProvider provider = new PacketcableProvider(dataBrokerService); + + final ListenerRegistration ccapDataChangeListenerRegistration = + dataBrokerService.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, + PacketcableProvider.ccapIID, provider, DataBroker.DataChangeScope.SUBTREE ); + + final ListenerRegistration qosDataChangeListenerRegistration = + dataBrokerService.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, + PacketcableProvider.qosIID, provider, DataBroker.DataChangeScope.SUBTREE ); + + + logger.info("PacketCableProvider Registered with DataBroker"); + return provider; + } + +} diff --git a/packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModuleFactory.java b/packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModuleFactory.java new file mode 100644 index 0000000..cdd8faa --- /dev/null +++ b/packetcable-policy-server/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/packetcable/packetcable/policy/server/impl/rev140131/PacketcableProviderModuleFactory.java @@ -0,0 +1,13 @@ +/* +* Generated file +* +* Generated from: yang module name: packetcable-policy-server-impl yang module local name: packetcable-policy-server-impl +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Tue Apr 21 12:24:46 MDT 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.packetcable.packetcable.policy.server.impl.rev140131; +public class PacketcableProviderModuleFactory extends org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.packetcable.packetcable.policy.server.impl.rev140131.AbstractPacketcableProviderModuleFactory { + +} diff --git a/packetcable-policy-server/src/main/yang/packetcable-policy-server.yang b/packetcable-policy-server/src/main/yang/packetcable-policy-server.yang new file mode 100644 index 0000000..d05911a --- /dev/null +++ b/packetcable-policy-server/src/main/yang/packetcable-policy-server.yang @@ -0,0 +1,51 @@ +module packetcable-policy-server-impl { + + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:packetcable:packetcable-policy-server:impl"; + prefix "packetcable-policy-server-impl"; + + import config { prefix config; revision-date 2013-04-05; } + import rpc-context { prefix rpcx; revision-date 2013-06-17; } + import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; } + import ietf-inet-types {prefix inet;revision-date 2010-09-24;} + + description + "This module contains the base YANG definitions for + packetcable-policy-server impl implementation."; + + revision "2014-01-31" { + description + "Initial revision."; + } + + // This is the definition of the service implementation as a module identity. + identity packetcable-policy-server-impl { + base config:module-type; + config:java-name-prefix PacketcableProvider; + } + + // Augments the 'configuration' choice node under modules/module. + augment "/config:modules/config:module/config:configuration" { + case packetcable-policy-server-impl { + when "/config:modules/config:module/config:type = 'packetcable-policy-server-impl'"; + + //wires in the data-broker and rpc services + container data-broker { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity mdsal:binding-async-data-broker; + } + } + } + container rpc-registry { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity mdsal:binding-rpc-registry; + } + } + } + } + } +} diff --git a/packetcable-policy-server/src/test/java/org/opendaylight/controller/packetcable/provider/PCMMServiceTest.java b/packetcable-policy-server/src/test/java/org/opendaylight/controller/packetcable/provider/PCMMServiceTest.java new file mode 100644 index 0000000..caf94f8 --- /dev/null +++ b/packetcable-policy-server/src/test/java/org/opendaylight/controller/packetcable/provider/PCMMServiceTest.java @@ -0,0 +1,123 @@ +package org.opendaylight.controller.packetcable.provider; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceClassName; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.AmId; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.Connection; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.classifier.Classifier; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gate.spec.GateSpec; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates; +import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.traffic.profile.TrafficProfile; +import org.pcmm.rcd.IPCMMClient; +import org.pcmm.rcd.impl.CMTS; + +import java.net.InetAddress; + +/** + * Tests the PCMMService's ability to connect to a CMTS. Gate additions will not properly work as there is currently + * not any other means to receive acknowledgements. This functionality must be tested by the PCMMService's client + * PacketcableProvider + */ +public class PCMMServiceTest { + + private Ccaps ccap; + private CMTS icmts; + + @Before + public void setup() { + icmts = new CMTS(); + icmts.startServer(); + + ccap = Mockito.mock(Ccaps.class); + final Connection conn = Mockito.mock(Connection.class); + Mockito.when(ccap.getConnection()).thenReturn(conn); + final PortNumber port = Mockito.mock(PortNumber.class); + Mockito.when(conn.getPort()).thenReturn(port); + Mockito.when(port.getValue()).thenReturn(icmts.getPort()); + final IpAddress addr = Mockito.mock(IpAddress.class); + Mockito.when(conn.getIpAddress()).thenReturn(addr); + final Ipv4Address ipv4 = Mockito.mock(Ipv4Address.class); + Mockito.when(addr.getIpv4Address()).thenReturn(ipv4); + + // Switch the value to a real CMTS here to test against it + // Ensure the code is checked in with 127.0.0.1 + Mockito.when(ipv4.getValue()).thenReturn("127.0.0.1"); + + // Following line has been commented out for testing against a real Arris CMTS in the Louisville development lab +// Mockito.when(ipv4.getValue()).thenReturn("10.32.10.3"); + + Mockito.when(ccap.getCcapId()).thenReturn("ccap-1"); + final AmId amid = Mockito.mock(AmId.class); + Mockito.when(ccap.getAmId()).thenReturn(amid); + Mockito.when(amid.getAmTag()).thenReturn(0xcada); + Mockito.when(amid.getAmType()).thenReturn(1); + } + + @After + public void tearDown() { + if (icmts != null) icmts.stopServer(); + } + + @Test + public void testAddCcap() { + final PCMMService service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap); + final String message = service.addCcap(); + Assert.assertTrue(message.startsWith("200")); + Assert.assertNotNull(service.ccapClient.pcmmPdp.getClientHandle()); + + // TODO - remove this sleep +/* + try { + Thread.sleep(100000); + } catch (InterruptedException e) { + e.printStackTrace(); + } +*/ + } + +// @Test + public void testAddGate() throws Exception { + final PCMMService service = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap); + service.addCcap(); + final byte[] addr = new byte[4]; + addr[0] = 10; + addr[1] = 32; + addr[2] = 110; + addr[3] = (byte)180; + + final Gates gate = Mockito.mock(Gates.class); + final GateSpec gateSpec = Mockito.mock(GateSpec.class); + Mockito.when(gate.getGateSpec()).thenReturn(gateSpec); + Mockito.when(gateSpec.getDirection()).thenReturn(ServiceFlowDirection.Us); + // TODO - make sure to write a test when this value is not null + Mockito.when(gateSpec.getDscpTosOverwrite()).thenReturn(null); + final TrafficProfile trafficProfile = Mockito.mock(TrafficProfile.class); + final ServiceClassName scn = Mockito.mock(ServiceClassName.class); + Mockito.when(scn.getValue()).thenReturn("extrm_up"); + Mockito.when(trafficProfile.getServiceClassName()).thenReturn(scn); + Mockito.when(gate.getTrafficProfile()).thenReturn(trafficProfile); + + // TODO - write tests when this is null and ExtClassifier or Ipv6Classifier objects are not null + final Classifier classifier = Mockito.mock(Classifier.class); + Mockito.when(gate.getClassifier()).thenReturn(classifier); + Mockito.when(gate.getExtClassifier()).thenReturn(null); + Mockito.when(gate.getIpv6Classifier()).thenReturn(null); + + // TODO - remove this sleep + Thread.sleep(1000); + + final String msg = service.sendGateSet("app1/10.32.110.180/gate2", InetAddress.getByAddress(addr), gate, + ServiceFlowDirection.Us); + Assert.fail(msg); + } + +} diff --git a/pom.xml b/pom.xml index 57c7d2a..6686825 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,7 @@ packetcable-policy-model + packetcable-policy-server scm:git:ssh://git.opendaylight.org:29418/packetcable.git -- 2.36.6