1 package org.opendaylight.controller.packetcable.provider;
3 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
4 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
5 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
6 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
7 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
8 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
9 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
10 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
11 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.Ccap;
12 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.Qos;
13 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceClassName;
14 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection;
15 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps;
16 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.CcapsKey;
17 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.attributes.Connection;
18 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.Apps;
19 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.AppsKey;
20 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.Subs;
21 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.SubsKey;
22 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates;
23 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.GatesKey;
24 import org.opendaylight.yangtools.concepts.ListenerRegistration;
25 import org.opendaylight.yangtools.yang.binding.DataObject;
26 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
27 import org.pcmm.rcd.IPCMMClient;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import javax.annotation.concurrent.ThreadSafe;
32 import java.net.InetAddress;
33 import java.net.UnknownHostException;
35 import java.util.concurrent.ConcurrentHashMap;
36 import java.util.concurrent.ExecutionException;
39 * Called by ODL framework to start this bundle.
41 * This class is responsible for processing messages received from ODL's restconf interface.
42 * TODO - Remove some of these state maps and move some of this into the PCMMService
45 public class PacketcableProvider implements BindingAwareProvider, DataChangeListener, AutoCloseable {
47 private static final Logger logger = LoggerFactory.getLogger(PacketcableProvider.class);
49 // keys to the /restconf/config/packetcable:ccap and /restconf/config/packetcable:qos config datastore
50 public static final InstanceIdentifier<Ccap> ccapIID = InstanceIdentifier.builder(Ccap.class).build();
51 public static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
54 * The ODL object used to broker messages throughout the framework
56 private DataBroker dataBroker;
58 private MdsalUtils mdsalUtils;
60 private ListenerRegistration<DataChangeListener> ccapDataChangeListenerRegistration;
61 private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
63 // TODO - Revisit these maps and remove the ones no longer necessary
64 private final Map<String, Ccaps> ccapMap = new ConcurrentHashMap<>();
65 private final Map<String, Gates> gateMap = new ConcurrentHashMap<>();
66 private final Map<String, String> gateCcapMap = new ConcurrentHashMap<>();
67 private final Map<Subnet, Ccaps> subscriberSubnetsMap = new ConcurrentHashMap<>();
68 private final Map<ServiceClassName, List<Ccaps>> downstreamScnMap = new ConcurrentHashMap<>();
69 private final Map<ServiceClassName, List<Ccaps>> upstreamScnMap = new ConcurrentHashMap<>();
72 * Holds a PCMMService object for each CCAP being managed.
74 private final Map<String, PCMMService> pcmmServiceMap = new ConcurrentHashMap<>();
79 public PacketcableProvider() {
80 logger.info("Starting provider");
84 public void onSessionInitiated(ProviderContext session) {
85 logger.info("Packetcable Session Initiated");
87 dataBroker = session.getSALService(DataBroker.class);
89 mdsalUtils = new MdsalUtils(dataBroker);
91 ccapDataChangeListenerRegistration =
92 dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
93 PacketcableProvider.ccapIID, this, DataBroker.DataChangeScope.SUBTREE );
95 qosDataChangeListenerRegistration =
96 dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
97 PacketcableProvider.qosIID, this, DataBroker.DataChangeScope.SUBTREE );
100 * Implemented from the AutoCloseable interface.
103 public void close() throws ExecutionException, InterruptedException {
104 if (ccapDataChangeListenerRegistration != null) {
105 ccapDataChangeListenerRegistration.close();
108 if (qosDataChangeListenerRegistration != null) {
109 qosDataChangeListenerRegistration.close();
113 public InetAddress getInetAddress(final String subId){
115 return InetAddress.getByName(subId);
116 } catch (UnknownHostException e) {
117 logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
122 private String getIpPrefixStr(final IpPrefix ipPrefix) {
123 final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
125 return ipv4.getValue();
127 return ipPrefix.getIpv6Prefix().getValue();
131 private void updateCcapMaps(final Ccaps ccap) {
132 // add ccap to the subscriberSubnets map
133 for (final IpPrefix ipPrefix : ccap.getSubscriberSubnets()) {
135 subscriberSubnetsMap.put(Subnet.createInstance(getIpPrefixStr(ipPrefix)), ccap);
136 } catch (UnknownHostException e) {
137 logger.error("updateSubscriberSubnets: {}:{} FAILED: {}", ipPrefix, ccap, e.getMessage());
140 // ccap to upstream SCN map
141 for (final ServiceClassName scn : ccap.getUpstreamScns()) {
142 if (upstreamScnMap.containsKey(scn)) {
143 upstreamScnMap.get(scn).add(ccap);
145 final List<Ccaps> ccapList = new ArrayList<>();
147 upstreamScnMap.put(scn, ccapList);
150 // ccap to downstream SCN map
151 for (final ServiceClassName scn : ccap.getDownstreamScns()) {
152 if (downstreamScnMap.containsKey(scn)) {
153 downstreamScnMap.get(scn).add(ccap);
155 final List<Ccaps> ccapList = new ArrayList<>();
157 downstreamScnMap.put(scn, ccapList);
162 private void removeCcapFromAllMaps(final Ccaps ccap) {
163 // remove the ccap from all maps
164 // subscriberSubnets map
165 for (final Map.Entry<Subnet, Ccaps> entry : subscriberSubnetsMap.entrySet()) {
166 if (entry.getValue() == ccap) {
167 subscriberSubnetsMap.remove(entry.getKey());
170 // ccap to upstream SCN map
171 for (final Map.Entry<ServiceClassName, List<Ccaps>> entry : upstreamScnMap.entrySet()) {
172 final List<Ccaps> ccapList = entry.getValue();
173 ccapList.remove(ccap);
174 if (ccapList.isEmpty()) {
175 upstreamScnMap.remove(entry.getKey());
178 // ccap to downstream SCN map
179 for (final Map.Entry<ServiceClassName, List<Ccaps>> entry : downstreamScnMap.entrySet()) {
180 final List<Ccaps> ccapList = entry.getValue();
181 ccapList.remove(ccap);
182 if (ccapList.isEmpty()) {
183 downstreamScnMap.remove(entry.getKey());
187 final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
188 if (service != null) service.disconect();
191 private Ccaps findCcapForSubscriberId(final InetAddress inetAddr) {
192 Ccaps matchedCcap = null;
193 int longestPrefixLen = -1;
194 for (final Map.Entry<Subnet, Ccaps> entry : subscriberSubnetsMap.entrySet()) {
195 final Subnet subnet = entry.getKey();
196 if (subnet.isInNet(inetAddr)) {
197 int prefixLen = subnet.getPrefixLen();
198 if (prefixLen > longestPrefixLen) {
199 matchedCcap = entry.getValue();
200 longestPrefixLen = prefixLen;
207 private ServiceFlowDirection findScnOnCcap(final ServiceClassName scn, final Ccaps ccap) {
208 if (upstreamScnMap.containsKey(scn)) {
209 final List<Ccaps> ccapList = upstreamScnMap.get(scn);
210 if (ccapList.contains(ccap)) {
211 return ServiceFlowDirection.Us;
213 } else if (downstreamScnMap.containsKey(scn)) {
214 final List<Ccaps> ccapList = downstreamScnMap.get(scn);
215 if (ccapList.contains(ccap)) {
216 return ServiceFlowDirection.Ds;
223 * Implemented from the DataChangeListener interface.
226 private class InstanceData {
228 public final Map<InstanceIdentifier<Ccaps>, Ccaps> ccapIidMap = new HashMap<>();
231 public final Map<String, String> gatePathMap = new HashMap<>();
232 public String gatePath;
233 public final Map<InstanceIdentifier<Gates>, Gates> gateIidMap = new HashMap<>();
234 // remove path for either CCAP or Gates
235 public final Set<String> removePathList = new HashSet<>();
237 public final Set<InstanceIdentifier<?>> reqCcapIds = new HashSet<>();
239 public InstanceData(final Map<InstanceIdentifier<?>, DataObject> thisData) {
240 // only used to parse createdData or updatedData
242 if (ccapIidMap.isEmpty()) {
244 if (! gateIidMap.isEmpty()){
245 gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId");
250 public InstanceData(final Set<InstanceIdentifier<?>> thisData) {
251 // only used to parse the removedData paths
252 for (final InstanceIdentifier<?> removeThis : thisData) {
253 getGatePathMap(removeThis);
254 if (gatePathMap.containsKey("ccapId")) {
255 gatePath = gatePathMap.get("ccapId");
256 removePathList.add(gatePath);
257 } else if (gatePathMap.containsKey("gateId")) {
258 gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId") + "/" + gatePathMap.get("gateId");
259 removePathList.add(gatePath);
263 private void getGatePathMap(final InstanceIdentifier<?> thisInstance) {
264 logger.info("onDataChanged().getGatePathMap(): " + thisInstance);
266 final InstanceIdentifier<Ccaps> ccapInstance = thisInstance.firstIdentifierOf(Ccaps.class);
267 if (ccapInstance != null) {
268 final CcapsKey ccapKey = InstanceIdentifier.keyOf(ccapInstance);
269 if (ccapKey != null) {
270 gatePathMap.put("ccapId", ccapKey.getCcapId());
273 // get the gate path keys from the InstanceIdentifier Map key set if they are there
274 final InstanceIdentifier<Apps> appsInstance = thisInstance.firstIdentifierOf(Apps.class);
275 if (appsInstance != null) {
276 final AppsKey appKey = InstanceIdentifier.keyOf(appsInstance);
277 if (appKey != null) {
278 gatePathMap.put("appId", appKey.getAppId());
281 final InstanceIdentifier<Subs> subsInstance = thisInstance.firstIdentifierOf(Subs.class);
282 if (subsInstance != null) {
283 final SubsKey subKey = InstanceIdentifier.keyOf(subsInstance);
284 if (subKey != null) {
285 subId = subKey.getSubId();
286 gatePathMap.put("subId", subId);
289 final InstanceIdentifier<Gates> gatesInstance = thisInstance.firstIdentifierOf(Gates.class);
290 if (gatesInstance != null) {
291 final GatesKey gateKey = InstanceIdentifier.keyOf(gatesInstance);
292 if (gateKey != null) {
293 gatePathMap.put("gateId", gateKey.getGateId());
297 } catch (ClassCastException err) {
298 logger.warn("Unexpected exception", err);
302 private void getCcaps(final Map<InstanceIdentifier<?>, DataObject> thisData) {
303 logger.info("onDataChanged().getCcaps(): " + thisData);
304 for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
306 if (entry.getKey().getTargetType().equals(Ccaps.class)) {
307 Ccaps ccaps = ((Ccaps) entry.getValue());
308 InstanceIdentifier<Ccaps> ccapsIid = InstanceIdentifier.builder(Ccap.class).child(Ccaps.class, new CcapsKey(ccaps.getCcapId())).build();
309 ccapIidMap.put(ccapsIid, ccaps);
312 if (entry.getKey().getTargetType().equals(Connection.class) ||
313 entry.getKey().getTargetType().equals(Ccaps.class)) {
314 reqCcapIds.add(entry.getKey());
319 private void getGates(final Map<InstanceIdentifier<?>, DataObject> thisData) {
320 logger.info("onDataChanged().getGates(): " + thisData);
321 for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
322 if (entry.getValue() instanceof Gates) {
323 final Gates gate = (Gates)entry.getValue();
325 // TODO FIXME - Potential ClassCastException thrown here!!!
326 final InstanceIdentifier<Gates> gateIID = (InstanceIdentifier<Gates>)entry.getKey();
327 getGatePathMap(gateIID);
328 if (!gateIidMap.containsKey(gateIID)){
329 gateIidMap.put(gateIID, gate);
332 // TODO reconciliate gates
333 // if (entry.getValue() instanceof Qos) {
334 // final Qos qos = (Qos) entry.getValue();
335 // if (qos.getApps() != null) {
336 // for (Apps apps : qos.getApps()) {
337 // if (apps.getSubs() != null) {
338 // for (Subs subs : apps.getSubs()) {
339 // if (subs.getGates() != null) {
340 // for (Gates gates : subs.getGates()) {
341 // final InstanceIdentifier<Gates> gateIID = (InstanceIdentifier<Gates>)entry.getKey();
342 // getGatePathMap(gateIID);
343 // if (!gateIidMap.containsKey(gateIID)){
344 // gateIidMap.put(gateIID, gates);
358 public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
359 logger.info("onDataChanged");
360 // Determine what change action took place by looking at the change object's InstanceIdentifier sets
361 // and validate all instance data
362 if (!change.getCreatedData().isEmpty()) {
363 if (!new ValidateInstanceData(mdsalUtils, change.getCreatedData()).validateYang()) {
364 // leave now -- a bad yang object has been detected and a response object has been inserted
367 onCreate(new InstanceData(change.getCreatedData()));
368 } else if (!change.getRemovedPaths().isEmpty()) {
369 onRemove(new InstanceData(change.getRemovedPaths()));
370 } else if (!change.getUpdatedData().isEmpty()) {
371 onUpdate(new InstanceData(change.getUpdatedData()));
373 // we should not be here -- complain bitterly and return
374 logger.error("onDataChanged(): Unknown change action: " + change);
378 private void onCreate(final InstanceData thisData) {
379 logger.info("onCreate(): " + thisData);
381 // get the CCAP parameters
383 if (! thisData.reqCcapIds.isEmpty()) {
384 for (Map.Entry<InstanceIdentifier<Ccaps>, Ccaps> entry : thisData.ccapIidMap.entrySet()) {
385 final Ccaps thisCcap = entry.getValue();
386 // get the CCAP node identity from the Instance Data
387 final String ccapId = thisCcap.getCcapId();
389 if (pcmmServiceMap.get(thisCcap.getCcapId()) == null) {
390 final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, thisCcap);
391 // TODO - may want to use the AMID but for the client type but probably not???
393 final PCMMService pcmmService = new PCMMService(
394 thisCcap.getAmId().getAmType().shortValue(), thisCcap);
396 message = pcmmService.addCcap();
397 if (message.contains("200 OK")) {
398 pcmmServiceMap.put(thisCcap.getCcapId(), pcmmService);
399 ccapMap.put(ccapId, thisCcap);
400 updateCcapMaps(thisCcap);
401 logger.info("Created CCAP: {}/{} : {}", thisData.gatePath, thisCcap, message);
402 logger.info("Created CCAP: {} : {}", thisData.gatePath, message);
404 logger.error("Create CCAP Failed: {} : {}", thisData.gatePath, message);
405 for (final InstanceIdentifier<?> instId : thisData.reqCcapIds) {
406 mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, instId);
408 ccapMap.remove(ccapId);
411 logger.error("Already monitoring CCAP - " + thisCcap);
416 // get the PCMM gate parameters from the ccapId/appId/subId/gateId path in the Maps entry (if new gate)
417 for (final Map.Entry<InstanceIdentifier<Gates>, Gates> entry : thisData.gateIidMap.entrySet()) {
419 final Gates gate = entry.getValue();
420 final String gateId = gate.getGateId();
421 final String gatePathStr = thisData.gatePath + "/" + gateId ;
422 final InetAddress subId = getInetAddress(thisData.subId);
424 final Ccaps thisCcap = findCcapForSubscriberId(subId);
425 if (thisCcap != null) {
426 final String ccapId = thisCcap.getCcapId();
427 // verify SCN exists on CCAP and force gateSpec.Direction to align with SCN direction
428 final ServiceClassName scn = gate.getTrafficProfile().getServiceClassName();
430 final ServiceFlowDirection scnDir = findScnOnCcap(scn, thisCcap);
431 if (scnDir != null) {
432 if (pcmmServiceMap.get(thisCcap.getCcapId()) != null) {
433 message = pcmmServiceMap.get(thisCcap.getCcapId()).sendGateSet(gatePathStr, subId, gate, scnDir);
434 gateMap.put(gatePathStr, gate);
435 gateCcapMap.put(gatePathStr, thisCcap.getCcapId());
437 if (message.contains("200 OK")) {
438 logger.info("Created QoS gate {} for {}/{}/{} - {}",
439 gateId, ccapId, gatePathStr, gate, message);
440 logger.info("Created QoS gate {} for {}/{} - {}",
441 gateId, ccapId, gatePathStr, message);
443 logger.info("Unable to create QoS gate {} for {}/{}/{} - {}",
444 gateId, ccapId, gatePathStr, gate, message);
445 logger.error("Unable to create QoS gate {} for {}/{} - {}",
446 gateId, ccapId, gatePathStr, message);
449 logger.error("Unable to locate PCMM Service for CCAP - " + thisCcap);
453 logger.error("PCMMService: sendGateSet(): SCN {} not found on CCAP {} for {}/{}",
454 scn.getValue(), thisCcap, gatePathStr, gate);
455 message = String.format("404 Not Found - SCN %s not found on CCAP %s for %s",
456 scn.getValue(), thisCcap.getCcapId(), gatePathStr);
460 final String subIdStr = thisData.subId;
461 message = String.format("404 Not Found - no CCAP found for subscriber %s in %s",
462 subIdStr, gatePathStr);
463 logger.info("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}/{}",
464 gateId, subIdStr, gatePathStr, gate);
465 logger.error("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}",
466 gateId, subIdStr, gatePathStr);
469 final String subIdStr = thisData.subId;
470 message = String.format("400 Bad Request - subId must be a valid IP address for subscriber %s in %s",
471 subIdStr, gatePathStr);
472 logger.info("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}/{}",
473 gateId, subIdStr, gatePathStr, gate);
474 logger.error("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}",
475 gateId, subIdStr, gatePathStr);
477 if (!message.contains("200 OK")) {
478 mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, entry.getKey());
484 private void onRemove(final InstanceData thisData) {
485 logger.info("onRemove(): " + thisData);
486 for (final String gatePathStr: thisData.removePathList) {
487 if (gateMap.containsKey(gatePathStr)) {
488 final Gates thisGate = gateMap.remove(gatePathStr);
489 final String gateId = thisGate.getGateId();
490 final String ccapId = gateCcapMap.remove(gatePathStr);
491 final Ccaps thisCcap = ccapMap.get(ccapId);
492 final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
493 if (service != null) {
494 service.sendGateDelete(gatePathStr);
495 logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr, thisGate);
496 logger.info("onDataChanged(): removed QoS gate {} for {}/{}: ", gateId, ccapId, gatePathStr);
498 logger.warn("Unable to send to locate PCMMService to send gate delete message with CCAP - "
502 for (final String ccapIdStr: thisData.removePathList) {
503 if (ccapMap.containsKey(ccapIdStr)) {
504 final Ccaps thisCcap = ccapMap.remove(ccapIdStr);
505 removeCcapFromAllMaps(thisCcap);
510 private void onUpdate(final InstanceData oldData) {
511 logger.info("onUpdate(): " + oldData);
512 // update operation not allowed -- restore the original config object and complain
513 if (! oldData.ccapIidMap.isEmpty()) {
514 for (final Map.Entry<InstanceIdentifier<Ccaps>, Ccaps> entry : oldData.ccapIidMap.entrySet()) {
515 final Ccaps ccap = entry.getValue();
516 final String ccapId = ccap.getCcapId();
517 // restores the original data - although I don't think this is what is done here! I think the update data is put into the DS/config
518 mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), ccap);
519 logger.error("onDataChanged(): CCAP update not permitted {}/{}", ccapId, ccap);
522 for (final Map.Entry<InstanceIdentifier<Gates>, Gates> entry : oldData.gateIidMap.entrySet()) {
523 final Gates gate = entry.getValue();
524 final String gatePathStr = oldData.gatePath + "/" + gate.getGateId() ;
525 // restores the original data - although I don't think this is what is done here! I think the update data is put into the DS/config
526 mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), gate);
527 logger.error("onDataChanged(): QoS Gate update not permitted: {}/{}", gatePathStr, gate);