1 package org.opendaylight.controller.packetcable.provider;
3 import java.net.InetAddress;
4 import java.net.UnknownHostException;
5 import java.util.ArrayList;
6 import java.util.HashMap;
7 import java.util.HashSet;
11 import java.util.concurrent.ConcurrentHashMap;
12 import java.util.concurrent.ExecutionException;
13 import java.util.concurrent.ExecutorService;
14 import java.util.concurrent.Executors;
16 import javax.annotation.concurrent.ThreadSafe;
18 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
19 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
20 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
21 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
22 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
23 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
26 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.Ccap;
27 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.Qos;
28 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceClassName;
29 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ServiceFlowDirection;
30 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.Ccaps;
31 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.ccap.CcapsKey;
32 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.Apps;
33 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.AppsKey;
34 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.Subs;
35 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.SubsKey;
36 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.Gates;
37 import org.opendaylight.yang.gen.v1.urn.packetcable.rev150327.pcmm.qos.gates.apps.subs.GatesKey;
38 import org.opendaylight.yangtools.concepts.ListenerRegistration;
39 import org.opendaylight.yangtools.yang.binding.DataObject;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.pcmm.rcd.IPCMMClient;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * Called by ODL framework to start this bundle.
48 * This class is responsible for processing messages received from ODL's restconf interface.
49 * TODO - Remove some of these state maps and move some of this into the PCMMService
52 public class PacketcableProvider implements BindingAwareProvider, DataChangeListener, AutoCloseable {
54 private static final Logger logger = LoggerFactory.getLogger(PacketcableProvider.class);
56 // keys to the /restconf/config/packetcable:ccap and /restconf/config/packetcable:qos config datastore
57 public static final InstanceIdentifier<Ccap> ccapIID = InstanceIdentifier.builder(Ccap.class).build();
58 public static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
61 * The ODL object used to broker messages throughout the framework
63 private DataBroker dataBroker;
65 private ListenerRegistration<DataChangeListener> ccapDataChangeListenerRegistration;
66 private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
69 * The thread pool executor
71 private final ExecutorService executor;
73 // TODO - Revisit these maps and remove the ones no longer necessary
74 private final Map<String, Ccaps> ccapMap = new ConcurrentHashMap<>();
75 private final Map<String, Gates> gateMap = new ConcurrentHashMap<>();
76 private final Map<String, String> gateCcapMap = new ConcurrentHashMap<>();
77 private final Map<Subnet, Ccaps> subscriberSubnetsMap = new ConcurrentHashMap<>();
78 private final Map<ServiceClassName, List<Ccaps>> downstreamScnMap = new ConcurrentHashMap<>();
79 private final Map<ServiceClassName, List<Ccaps>> upstreamScnMap = new ConcurrentHashMap<>();
82 * Holds a PCMMService object for each CCAP being managed.
84 private final Map<String, PCMMService> pcmmServiceMap = new ConcurrentHashMap<>();
89 public PacketcableProvider() {
90 logger.info("Starting provider");
91 executor = Executors.newCachedThreadPool();
95 public void onSessionInitiated(ProviderContext session) {
96 logger.info("Packetcable Session Initiated");
98 dataBroker = session.getSALService(DataBroker.class);
100 ccapDataChangeListenerRegistration =
101 dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
102 PacketcableProvider.ccapIID, this, DataBroker.DataChangeScope.SUBTREE );
104 qosDataChangeListenerRegistration =
105 dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
106 PacketcableProvider.qosIID, this, DataBroker.DataChangeScope.SUBTREE );
109 * Implemented from the AutoCloseable interface.
112 public void close() throws ExecutionException, InterruptedException {
114 if (ccapDataChangeListenerRegistration != null) {
115 ccapDataChangeListenerRegistration.close();
118 if (qosDataChangeListenerRegistration != null) {
119 qosDataChangeListenerRegistration.close();
123 public InetAddress getInetAddress(final String subId){
125 return InetAddress.getByName(subId);
126 } catch (UnknownHostException e) {
127 logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
132 private String getIpPrefixStr(final IpPrefix ipPrefix) {
133 final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
135 return ipv4.getValue();
137 return ipPrefix.getIpv6Prefix().getValue();
141 private void updateCcapMaps(final Ccaps ccap) {
142 // add ccap to the subscriberSubnets map
143 for (final IpPrefix ipPrefix : ccap.getSubscriberSubnets()) {
145 subscriberSubnetsMap.put(Subnet.createInstance(getIpPrefixStr(ipPrefix)), ccap);
146 } catch (UnknownHostException e) {
147 logger.error("updateSubscriberSubnets: {}:{} FAILED: {}", ipPrefix, ccap, e.getMessage());
150 // ccap to upstream SCN map
151 for (final ServiceClassName scn : ccap.getUpstreamScns()) {
152 if (upstreamScnMap.containsKey(scn)) {
153 upstreamScnMap.get(scn).add(ccap);
155 final List<Ccaps> ccapList = new ArrayList<>();
157 upstreamScnMap.put(scn, ccapList);
160 // ccap to downstream SCN map
161 for (final ServiceClassName scn : ccap.getDownstreamScns()) {
162 if (downstreamScnMap.containsKey(scn)) {
163 downstreamScnMap.get(scn).add(ccap);
165 final List<Ccaps> ccapList = new ArrayList<>();
167 downstreamScnMap.put(scn, ccapList);
172 private void removeCcapFromAllMaps(final Ccaps ccap) {
173 // remove the ccap from all maps
174 // subscriberSubnets map
175 for (final Map.Entry<Subnet, Ccaps> entry : subscriberSubnetsMap.entrySet()) {
176 if (entry.getValue() == ccap) {
177 subscriberSubnetsMap.remove(entry.getKey());
180 // ccap to upstream SCN map
181 for (final Map.Entry<ServiceClassName, List<Ccaps>> entry : upstreamScnMap.entrySet()) {
182 final List<Ccaps> ccapList = entry.getValue();
183 ccapList.remove(ccap);
184 if (ccapList.isEmpty()) {
185 upstreamScnMap.remove(entry.getKey());
188 // ccap to downstream SCN map
189 for (final Map.Entry<ServiceClassName, List<Ccaps>> entry : downstreamScnMap.entrySet()) {
190 final List<Ccaps> ccapList = entry.getValue();
191 ccapList.remove(ccap);
192 if (ccapList.isEmpty()) {
193 downstreamScnMap.remove(entry.getKey());
197 final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
198 if (service != null) service.disconect();
201 private Ccaps findCcapForSubscriberId(final InetAddress inetAddr) {
202 Ccaps matchedCcap = null;
203 int longestPrefixLen = -1;
204 for (final Map.Entry<Subnet, Ccaps> entry : subscriberSubnetsMap.entrySet()) {
205 final Subnet subnet = entry.getKey();
206 if (subnet.isInNet(inetAddr)) {
207 int prefixLen = subnet.getPrefixLen();
208 if (prefixLen > longestPrefixLen) {
209 matchedCcap = entry.getValue();
210 longestPrefixLen = prefixLen;
217 private ServiceFlowDirection findScnOnCcap(final ServiceClassName scn, final Ccaps ccap) {
218 if (upstreamScnMap.containsKey(scn)) {
219 final List<Ccaps> ccapList = upstreamScnMap.get(scn);
220 if (ccapList.contains(ccap)) {
221 return ServiceFlowDirection.Us;
223 } else if (downstreamScnMap.containsKey(scn)) {
224 final List<Ccaps> ccapList = downstreamScnMap.get(scn);
225 if (ccapList.contains(ccap)) {
226 return ServiceFlowDirection.Ds;
233 * Implemented from the DataChangeListener interface.
236 private class InstanceData {
238 public final Map<InstanceIdentifier<Ccaps>, Ccaps> ccapIidMap = new HashMap<>();
241 public final Map<String, String> gatePathMap = new HashMap<>();
242 public String gatePath;
243 public final Map<InstanceIdentifier<Gates>, Gates> gateIidMap = new HashMap<>();
244 // remove path for either CCAP or Gates
245 public final Set<String> removePathList = new HashSet<>();
247 public InstanceData(final Map<InstanceIdentifier<?>, DataObject> thisData) {
248 // only used to parse createdData or updatedData
250 if (ccapIidMap.isEmpty()) {
252 if (! gateIidMap.isEmpty()){
253 gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId");
258 public InstanceData(final Set<InstanceIdentifier<?>> thisData) {
259 // only used to parse the removedData paths
260 for (final InstanceIdentifier<?> removeThis : thisData) {
261 getGatePathMap(removeThis);
262 if (gatePathMap.containsKey("ccapId")) {
263 gatePath = gatePathMap.get("ccapId");
264 removePathList.add(gatePath);
265 } else if (gatePathMap.containsKey("gateId")) {
266 gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId") + "/" + gatePathMap.get("gateId");
267 removePathList.add(gatePath);
271 private void getGatePathMap(final InstanceIdentifier<?> thisInstance) {
272 logger.info("onDataChanged().getGatePathMap(): " + thisInstance);
274 final InstanceIdentifier<Ccaps> ccapInstance = thisInstance.firstIdentifierOf(Ccaps.class);
275 if (ccapInstance != null) {
276 final CcapsKey ccapKey = InstanceIdentifier.keyOf(ccapInstance);
277 if (ccapKey != null) {
278 gatePathMap.put("ccapId", ccapKey.getCcapId());
281 // get the gate path keys from the InstanceIdentifier Map key set if they are there
282 final InstanceIdentifier<Apps> appsInstance = thisInstance.firstIdentifierOf(Apps.class);
283 if (appsInstance != null) {
284 final AppsKey appKey = InstanceIdentifier.keyOf(appsInstance);
285 if (appKey != null) {
286 gatePathMap.put("appId", appKey.getAppId());
289 final InstanceIdentifier<Subs> subsInstance = thisInstance.firstIdentifierOf(Subs.class);
290 if (subsInstance != null) {
291 final SubsKey subKey = InstanceIdentifier.keyOf(subsInstance);
292 if (subKey != null) {
293 subId = subKey.getSubId();
294 gatePathMap.put("subId", subId);
297 final InstanceIdentifier<Gates> gatesInstance = thisInstance.firstIdentifierOf(Gates.class);
298 if (gatesInstance != null) {
299 final GatesKey gateKey = InstanceIdentifier.keyOf(gatesInstance);
300 if (gateKey != null) {
301 gatePathMap.put("gateId", gateKey.getGateId());
305 } catch (ClassCastException err) {
306 logger.warn("Unexpected exception", err);
310 private void getCcaps(final Map<InstanceIdentifier<?>, DataObject> thisData) {
311 logger.info("onDataChanged().getCcaps(): " + thisData);
312 for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
313 if (entry.getValue() instanceof Ccaps) {
314 // TODO FIXME - Potential ClassCastException thrown here!!!
315 ccapIidMap.put((InstanceIdentifier<Ccaps>)entry.getKey(), (Ccaps)entry.getValue());
320 private void getGates(final Map<InstanceIdentifier<?>, DataObject> thisData) {
321 logger.info("onDataChanged().getGates(): " + thisData);
322 for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
323 if (entry.getValue() instanceof Gates) {
324 final Gates gate = (Gates)entry.getValue();
326 // TODO FIXME - Potential ClassCastException thrown here!!!
327 final InstanceIdentifier<Gates> gateIID = (InstanceIdentifier<Gates>)entry.getKey();
328 getGatePathMap(gateIID);
329 gateIidMap.put(gateIID, gate);
336 public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
337 logger.info("onDataChanged");
338 // Determine what change action took place by looking at the change object's InstanceIdentifier sets
339 // and validate all instance data
340 if (!change.getCreatedData().isEmpty()) {
341 if (!new ValidateInstanceData(dataBroker, change.getCreatedData()).validateYang()) {
342 // leave now -- a bad yang object has been detected and a response object has been inserted
345 onCreate(new InstanceData(change.getCreatedData()));
346 } else if (!change.getRemovedPaths().isEmpty()) {
347 onRemove(new InstanceData(change.getRemovedPaths()));
348 } else if (!change.getUpdatedData().isEmpty()) {
349 if (new ValidateInstanceData(dataBroker, change.getUpdatedData()).isResponseEcho()) {
350 // leave now -- this is an echo of the inserted response object
353 onUpdate(new InstanceData(change.getUpdatedData()));
355 // we should not be here -- complain bitterly and return
356 logger.error("onDataChanged(): Unknown change action: " + change);
360 private void onCreate(final InstanceData thisData) {
361 logger.info("onCreate(): " + thisData);
363 // get the CCAP parameters
365 if (! thisData.ccapIidMap.isEmpty()) {
366 for (Map.Entry<InstanceIdentifier<Ccaps>, Ccaps> entry : thisData.ccapIidMap.entrySet()) {
367 final Ccaps thisCcap = entry.getValue();
368 // get the CCAP node identity from the Instance Data
369 final String ccapId = thisCcap.getCcapId();
371 if (pcmmServiceMap.get(thisCcap.getCcapId()) == null) {
372 final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, thisCcap);
373 // TODO - may want to use the AMID but for the client type but probably not???
375 final PCMMService pcmmService = new PCMMService(
376 thisCcap.getAmId().getAmType().shortValue(), thisCcap);
378 pcmmServiceMap.put(thisCcap.getCcapId(), pcmmService);
379 message = pcmmService.addCcap();
380 if (message.contains("200 OK")) {
381 ccapMap.put(ccapId, thisCcap);
382 updateCcapMaps(thisCcap);
383 logger.info("Created CCAP: {}/{} : {}", thisData.gatePath, thisCcap, message);
384 logger.info("Created CCAP: {} : {}", thisData.gatePath, message);
386 // TODO - when a connection cannot be made, need to remove CCAP from ODL cache.
387 logger.error("Create CCAP Failed: {} : {}", thisData.gatePath, message);
389 // set the response string in the config ccap object using a new thread
390 executor.execute(new Response(dataBroker, entry.getKey(), thisCcap, message));
392 logger.error("Already monitoring CCAP - " + thisCcap);
397 // get the PCMM gate parameters from the ccapId/appId/subId/gateId path in the Maps entry (if new gate)
398 for (final Map.Entry<InstanceIdentifier<Gates>, Gates> entry : thisData.gateIidMap.entrySet()) {
400 final Gates gate = entry.getValue();
401 final String gateId = gate.getGateId();
402 final String gatePathStr = thisData.gatePath + "/" + gateId ;
403 final InetAddress subId = getInetAddress(thisData.subId);
405 final Ccaps thisCcap = findCcapForSubscriberId(subId);
406 if (thisCcap != null) {
407 final String ccapId = thisCcap.getCcapId();
408 // verify SCN exists on CCAP and force gateSpec.Direction to align with SCN direction
409 final ServiceClassName scn = gate.getTrafficProfile().getServiceClassName();
411 final ServiceFlowDirection scnDir = findScnOnCcap(scn, thisCcap);
412 if (scnDir != null) {
413 if (pcmmServiceMap.get(thisCcap.getCcapId()) != null) {
414 message = pcmmServiceMap.get(thisCcap.getCcapId()).sendGateSet(gatePathStr, subId, gate, scnDir);
415 gateMap.put(gatePathStr, gate);
416 gateCcapMap.put(gatePathStr, thisCcap.getCcapId());
418 if (message.contains("200 OK")) {
419 logger.info("Created QoS gate {} for {}/{}/{} - {}",
420 gateId, ccapId, gatePathStr, gate, message);
421 logger.info("Created QoS gate {} for {}/{} - {}",
422 gateId, ccapId, gatePathStr, message);
424 logger.info("Unable to create QoS gate {} for {}/{}/{} - {}",
425 gateId, ccapId, gatePathStr, gate, message);
426 logger.error("Unable to create QoS gate {} for {}/{} - {}",
427 gateId, ccapId, gatePathStr, message);
430 logger.error("Unable to locate PCMM Service for CCAP - " + thisCcap);
434 logger.error("PCMMService: sendGateSet(): SCN {} not found on CCAP {} for {}/{}",
435 scn.getValue(), thisCcap, gatePathStr, gate);
436 message = String.format("404 Not Found - SCN %s not found on CCAP %s for %s",
437 scn.getValue(), thisCcap.getCcapId(), gatePathStr);
441 final String subIdStr = thisData.subId;
442 message = String.format("404 Not Found - no CCAP found for subscriber %s in %s",
443 subIdStr, gatePathStr);
444 logger.info("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}/{}",
445 gateId, subIdStr, gatePathStr, gate);
446 logger.error("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}",
447 gateId, subIdStr, gatePathStr);
450 final String subIdStr = thisData.subId;
451 message = String.format("400 Bad Request - subId must be a valid IP address for subscriber %s in %s",
452 subIdStr, gatePathStr);
453 logger.info("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}/{}",
454 gateId, subIdStr, gatePathStr, gate);
455 logger.error("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}",
456 gateId, subIdStr, gatePathStr);
458 // set the response message in the config gate object using a new thread
459 executor.execute(new Response(dataBroker, entry.getKey(), gate, message));
464 private void onRemove(final InstanceData thisData) {
465 logger.info("onRemove(): " + thisData);
466 for (final String gatePathStr: thisData.removePathList) {
467 if (gateMap.containsKey(gatePathStr)) {
468 final Gates thisGate = gateMap.remove(gatePathStr);
469 final String gateId = thisGate.getGateId();
470 final String ccapId = gateCcapMap.remove(gatePathStr);
471 final Ccaps thisCcap = ccapMap.get(ccapId);
472 final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
473 if (service != null) {
474 service.sendGateDelete(gatePathStr);
475 logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr, thisGate);
476 logger.info("onDataChanged(): removed QoS gate {} for {}/{}: ", gateId, ccapId, gatePathStr);
478 logger.warn("Unable to send to locate PCMMService to send gate delete message with CCAP - "
482 for (final String ccapIdStr: thisData.removePathList) {
483 if (ccapMap.containsKey(ccapIdStr)) {
484 final Ccaps thisCcap = ccapMap.remove(ccapIdStr);
485 removeCcapFromAllMaps(thisCcap);
490 private void onUpdate(final InstanceData oldData) {
491 logger.info("onUpdate(): " + oldData);
492 // update operation not allowed -- restore the original config object and complain
493 if (! oldData.ccapIidMap.isEmpty()) {
494 for (final Map.Entry<InstanceIdentifier<Ccaps>, Ccaps> entry : oldData.ccapIidMap.entrySet()) {
495 final Ccaps ccap = entry.getValue();
496 final String ccapId = ccap.getCcapId();
497 String message = String.format("405 Method Not Allowed - %s: CCAP update not permitted (use delete); ",
499 // push new error message onto existing response
500 message += ccap.getResponse();
501 // set the response message in the config object using a new thread -- also restores the original data
502 executor.execute(new Response(dataBroker, entry.getKey(), ccap, message));
503 logger.error("onDataChanged(): CCAP update not permitted {}/{}", ccapId, ccap);
506 for (final Map.Entry<InstanceIdentifier<Gates>, Gates> entry : oldData.gateIidMap.entrySet()) {
507 final Gates gate = entry.getValue();
508 final String gatePathStr = oldData.gatePath + "/" + gate.getGateId() ;
509 String message = String.format("405 Method Not Allowed - %s: QoS Gate update not permitted (use delete); ", gatePathStr);
510 // push new error message onto existing response
511 message += gate.getResponse();
512 // set the response message in the config object using a new thread -- also restores the original data
513 executor.execute(new Response(dataBroker, entry.getKey(), gate, message));
514 logger.error("onDataChanged(): QoS Gate update not permitted: {}/{}", gatePathStr, gate);