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 javax.annotation.concurrent.ThreadSafe;
14 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
15 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
16 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
17 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
18 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
19 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
20 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
21 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
22 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Ccaps;
23 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Qos;
24 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceClassName;
25 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceFlowDirection;
26 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.Connection;
27 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.Ccap;
28 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.CcapKey;
29 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.App;
30 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.AppKey;
31 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.Subscriber;
32 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.SubscriberKey;
33 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
34 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateKey;
35 import org.opendaylight.yangtools.concepts.ListenerRegistration;
36 import org.opendaylight.yangtools.yang.binding.DataObject;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
38 import org.pcmm.rcd.IPCMMClient;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * Called by ODL framework to start this bundle.
45 * This class is responsible for processing messages received from ODL's restconf interface.
46 * TODO - Remove some of these state maps and move some of this into the PCMMService
49 public class PacketcableProvider implements BindingAwareProvider, DataChangeListener, AutoCloseable {
51 private static final Logger logger = LoggerFactory.getLogger(PacketcableProvider.class);
53 // keys to the /restconf/config/packetcable:ccap and /restconf/config/packetcable:qos config datastore
54 public static final InstanceIdentifier<Ccaps> ccapIID = InstanceIdentifier.builder(Ccaps.class).build();
55 public static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
58 * The ODL object used to broker messages throughout the framework
60 private DataBroker dataBroker;
62 private MdsalUtils mdsalUtils;
64 private ListenerRegistration<DataChangeListener> ccapDataChangeListenerRegistration;
65 private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
67 // TODO - Revisit these maps and remove the ones no longer necessary
68 private final Map<String, Ccap> ccapMap = new ConcurrentHashMap<>();
69 private final Map<String, Gate> gateMap = new ConcurrentHashMap<>();
70 private final Map<String, String> gateCcapMap = new ConcurrentHashMap<>();
71 private final Map<Subnet, Ccap> subscriberSubnetsMap = new ConcurrentHashMap<>();
72 private final Map<ServiceClassName, List<Ccap>> downstreamScnMap = new ConcurrentHashMap<>();
73 private final Map<ServiceClassName, List<Ccap>> upstreamScnMap = new ConcurrentHashMap<>();
76 * Holds a PCMMService object for each CCAP being managed.
78 private final Map<String, PCMMService> pcmmServiceMap = new ConcurrentHashMap<>();
83 public PacketcableProvider() {
84 logger.info("Starting provider");
88 public void onSessionInitiated(ProviderContext session) {
89 logger.info("Packetcable Session Initiated");
91 dataBroker = session.getSALService(DataBroker.class);
93 mdsalUtils = new MdsalUtils(dataBroker);
95 ccapDataChangeListenerRegistration =
96 dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
97 PacketcableProvider.ccapIID, this, DataBroker.DataChangeScope.SUBTREE );
99 qosDataChangeListenerRegistration =
100 dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
101 PacketcableProvider.qosIID, this, DataBroker.DataChangeScope.SUBTREE );
104 * Implemented from the AutoCloseable interface.
107 public void close() throws ExecutionException, InterruptedException {
108 if (ccapDataChangeListenerRegistration != null) {
109 ccapDataChangeListenerRegistration.close();
112 if (qosDataChangeListenerRegistration != null) {
113 qosDataChangeListenerRegistration.close();
117 public InetAddress getInetAddress(final String subId){
119 return InetAddress.getByName(subId);
120 } catch (UnknownHostException e) {
121 logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
126 private String getIpPrefixStr(final IpPrefix ipPrefix) {
127 final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
129 return ipv4.getValue();
131 return ipPrefix.getIpv6Prefix().getValue();
135 private void updateCcapMaps(final Ccap ccap) {
136 // add ccap to the subscriberSubnets map
137 for (final IpPrefix ipPrefix : ccap.getSubscriberSubnets()) {
139 subscriberSubnetsMap.put(Subnet.createInstance(getIpPrefixStr(ipPrefix)), ccap);
140 } catch (UnknownHostException e) {
141 logger.error("updateSubscriberSubnets: {}:{} FAILED: {}", ipPrefix, ccap, e.getMessage());
144 // ccap to upstream SCN map
145 for (final ServiceClassName scn : ccap.getUpstreamScns()) {
146 if (upstreamScnMap.containsKey(scn)) {
147 upstreamScnMap.get(scn).add(ccap);
149 final List<Ccap> ccapList = new ArrayList<>();
151 upstreamScnMap.put(scn, ccapList);
154 // ccap to downstream SCN map
155 for (final ServiceClassName scn : ccap.getDownstreamScns()) {
156 if (downstreamScnMap.containsKey(scn)) {
157 downstreamScnMap.get(scn).add(ccap);
159 final List<Ccap> ccapList = new ArrayList<>();
161 downstreamScnMap.put(scn, ccapList);
166 private void removeCcapFromAllMaps(final Ccap ccap) {
167 // remove the ccap from all maps
168 // subscriberSubnets map
169 for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
170 if (entry.getValue() == ccap) {
171 subscriberSubnetsMap.remove(entry.getKey());
174 // ccap to upstream SCN map
175 for (final Map.Entry<ServiceClassName, List<Ccap>> entry : upstreamScnMap.entrySet()) {
176 final List<Ccap> ccapList = entry.getValue();
177 ccapList.remove(ccap);
178 if (ccapList.isEmpty()) {
179 upstreamScnMap.remove(entry.getKey());
182 // ccap to downstream SCN map
183 for (final Map.Entry<ServiceClassName, List<Ccap>> entry : downstreamScnMap.entrySet()) {
184 final List<Ccap> ccapList = entry.getValue();
185 ccapList.remove(ccap);
186 if (ccapList.isEmpty()) {
187 downstreamScnMap.remove(entry.getKey());
191 final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
192 if (service != null) service.disconect();
195 private Ccap findCcapForSubscriberId(final InetAddress inetAddr) {
196 Ccap matchedCcap = null;
197 int longestPrefixLen = -1;
198 for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
199 final Subnet subnet = entry.getKey();
200 if (subnet.isInNet(inetAddr)) {
201 int prefixLen = subnet.getPrefixLen();
202 if (prefixLen > longestPrefixLen) {
203 matchedCcap = entry.getValue();
204 longestPrefixLen = prefixLen;
211 private ServiceFlowDirection findScnOnCcap(final ServiceClassName scn, final Ccap ccap) {
212 if (upstreamScnMap.containsKey(scn)) {
213 final List<Ccap> ccapList = upstreamScnMap.get(scn);
214 if (ccapList.contains(ccap)) {
215 return ServiceFlowDirection.Us;
217 } else if (downstreamScnMap.containsKey(scn)) {
218 final List<Ccap> ccapList = downstreamScnMap.get(scn);
219 if (ccapList.contains(ccap)) {
220 return ServiceFlowDirection.Ds;
227 * Implemented from the DataChangeListener interface.
230 private class InstanceData {
232 public final Map<InstanceIdentifier<Ccap>, Ccap> ccapIidMap = new HashMap<>();
235 public final Map<String, String> gatePathMap = new HashMap<>();
236 public String gatePath;
237 public final Map<InstanceIdentifier<Gate>, Gate> gateIidMap = new HashMap<>();
238 // remove path for either CCAP or Gate
239 public final Set<String> removePathList = new HashSet<>();
241 public final Set<InstanceIdentifier<?>> reqCcapIds = new HashSet<>();
243 public InstanceData(final Map<InstanceIdentifier<?>, DataObject> thisData) {
244 // only used to parse createdData or updatedData
246 if (ccapIidMap.isEmpty()) {
248 if (! gateIidMap.isEmpty()){
249 gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId");
254 public InstanceData(final Set<InstanceIdentifier<?>> thisData) {
255 // only used to parse the removedData paths
256 for (final InstanceIdentifier<?> removeThis : thisData) {
257 getGatePathMap(removeThis);
258 if (gatePathMap.containsKey("ccapId")) {
259 gatePath = gatePathMap.get("ccapId");
260 removePathList.add(gatePath);
261 } else if (gatePathMap.containsKey("gateId")) {
262 gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId") + "/" + gatePathMap.get("gateId");
263 removePathList.add(gatePath);
267 private void getGatePathMap(final InstanceIdentifier<?> thisInstance) {
268 logger.info("onDataChanged().getGatePathMap(): " + thisInstance);
270 final InstanceIdentifier<Ccap> ccapInstance = thisInstance.firstIdentifierOf(Ccap.class);
271 if (ccapInstance != null) {
272 final CcapKey ccapKey = InstanceIdentifier.keyOf(ccapInstance);
273 if (ccapKey != null) {
274 gatePathMap.put("ccapId", ccapKey.getCcapId());
277 // get the gate path keys from the InstanceIdentifier Map key set if they are there
278 final InstanceIdentifier<App> appInstance = thisInstance.firstIdentifierOf(App.class);
279 if (appInstance != null) {
280 final AppKey appKey = InstanceIdentifier.keyOf(appInstance);
281 if (appKey != null) {
282 gatePathMap.put("appId", appKey.getAppId());
285 final InstanceIdentifier<Subscriber> subInstance = thisInstance.firstIdentifierOf(Subscriber.class);
286 if (subInstance != null) {
287 final SubscriberKey subKey = InstanceIdentifier.keyOf(subInstance);
288 if (subKey != null) {
289 subId = subKey.getSubscriberId();
290 gatePathMap.put("subId", subId);
293 final InstanceIdentifier<Gate> gatesInstance = thisInstance.firstIdentifierOf(Gate.class);
294 if (gatesInstance != null) {
295 final GateKey gateKey = InstanceIdentifier.keyOf(gatesInstance);
296 if (gateKey != null) {
297 gatePathMap.put("gateId", gateKey.getGateId());
301 } catch (ClassCastException err) {
302 logger.warn("Unexpected exception", err);
306 private void getCcaps(final Map<InstanceIdentifier<?>, DataObject> thisData) {
307 logger.info("onDataChanged().getCcaps(): " + thisData);
308 for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
310 if (entry.getKey().getTargetType().equals(Ccap.class)) {
311 Ccap ccaps = ((Ccap) entry.getValue());
312 InstanceIdentifier<Ccap> ccapsIid = InstanceIdentifier.builder(Ccaps.class).child(Ccap.class, new CcapKey(ccaps.getCcapId())).build();
313 ccapIidMap.put(ccapsIid, ccaps);
316 if (entry.getKey().getTargetType().equals(Connection.class) ||
317 entry.getKey().getTargetType().equals(Ccap.class)) {
318 reqCcapIds.add(entry.getKey());
323 private void getGates(final Map<InstanceIdentifier<?>, DataObject> thisData) {
324 logger.info("onDataChanged().getGates(): " + thisData);
325 for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
326 if (entry.getValue() instanceof Gate) {
327 final Gate gate = (Gate)entry.getValue();
329 // TODO FIXME - Potential ClassCastException thrown here!!!
330 final InstanceIdentifier<Gate> gateIID = (InstanceIdentifier<Gate>)entry.getKey();
331 getGatePathMap(gateIID);
332 if (!gateIidMap.containsKey(gateIID)){
333 gateIidMap.put(gateIID, gate);
336 // TODO reconciliate gates
337 // if (entry.getValue() instanceof Qos) {
338 // final Qos qos = (Qos) entry.getValue();
339 // if (qos.getApps() != null) {
340 // for (Apps apps : qos.getApps()) {
341 // if (apps.getSubs() != null) {
342 // for (Subs subs : apps.getSubs()) {
343 // if (subs.getGates() != null) {
344 // for (Gate gates : subs.getGates()) {
345 // final InstanceIdentifier<Gate> gateIID = (InstanceIdentifier<Gate>)entry.getKey();
346 // getGatePathMap(gateIID);
347 // if (!gateIidMap.containsKey(gateIID)){
348 // gateIidMap.put(gateIID, gates);
362 public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
363 logger.info("onDataChanged");
364 // Determine what change action took place by looking at the change object's InstanceIdentifier sets
365 // and validate all instance data
366 if (!change.getCreatedData().isEmpty()) {
367 if (!new ValidateInstanceData(mdsalUtils, change.getCreatedData()).validateYang()) {
368 // leave now -- a bad yang object has been detected and a response object has been inserted
371 onCreate(new InstanceData(change.getCreatedData()));
372 } else if (!change.getRemovedPaths().isEmpty()) {
373 onRemove(new InstanceData(change.getRemovedPaths()));
374 } else if (!change.getUpdatedData().isEmpty()) {
375 onUpdate(new InstanceData(change.getUpdatedData()));
377 // we should not be here -- complain bitterly and return
378 logger.error("onDataChanged(): Unknown change action: " + change);
382 private void onCreate(final InstanceData thisData) {
383 logger.info("onCreate(): " + thisData);
385 // get the CCAP parameters
387 if (! thisData.reqCcapIds.isEmpty()) {
388 for (Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : thisData.ccapIidMap.entrySet()) {
389 final Ccap thisCcap = entry.getValue();
390 // get the CCAP node identity from the Instance Data
391 final String ccapId = thisCcap.getCcapId();
393 if (pcmmServiceMap.get(thisCcap.getCcapId()) == null) {
394 final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, thisCcap);
395 // TODO - may want to use the AMID but for the client type but probably not???
397 final PCMMService pcmmService = new PCMMService(
398 thisCcap.getAmId().getAmType().shortValue(), thisCcap);
400 message = pcmmService.addCcap();
401 if (message.contains("200 OK")) {
402 pcmmServiceMap.put(thisCcap.getCcapId(), pcmmService);
403 ccapMap.put(ccapId, thisCcap);
404 updateCcapMaps(thisCcap);
405 logger.info("Created CCAP: {}/{} : {}", thisData.gatePath, thisCcap, message);
406 logger.info("Created CCAP: {} : {}", thisData.gatePath, message);
408 logger.error("Create CCAP Failed: {} : {}", thisData.gatePath, message);
409 for (final InstanceIdentifier<?> instId : thisData.reqCcapIds) {
410 mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, instId);
412 ccapMap.remove(ccapId);
415 logger.error("Already monitoring CCAP - " + thisCcap);
420 // get the PCMM gate parameters from the ccapId/appId/subId/gateId path in the Maps entry (if new gate)
421 for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : thisData.gateIidMap.entrySet()) {
423 final Gate gate = entry.getValue();
424 final String gateId = gate.getGateId();
425 final String gatePathStr = thisData.gatePath + "/" + gateId ;
426 final InetAddress subId = getInetAddress(thisData.subId);
428 final Ccap thisCcap = findCcapForSubscriberId(subId);
429 if (thisCcap != null) {
430 final String ccapId = thisCcap.getCcapId();
431 // verify SCN exists on CCAP and force gateSpec.Direction to align with SCN direction
432 final ServiceClassName scn = gate.getTrafficProfile().getServiceClassName();
434 final ServiceFlowDirection scnDir = findScnOnCcap(scn, thisCcap);
435 if (scnDir != null) {
436 if (pcmmServiceMap.get(thisCcap.getCcapId()) != null) {
437 message = pcmmServiceMap.get(thisCcap.getCcapId()).sendGateSet(gatePathStr, subId, gate, scnDir);
438 gateMap.put(gatePathStr, gate);
439 gateCcapMap.put(gatePathStr, thisCcap.getCcapId());
441 if (message.contains("200 OK")) {
442 logger.info("Created QoS gate {} for {}/{}/{} - {}",
443 gateId, ccapId, gatePathStr, gate, message);
444 logger.info("Created QoS gate {} for {}/{} - {}",
445 gateId, ccapId, gatePathStr, message);
447 logger.info("Unable to create QoS gate {} for {}/{}/{} - {}",
448 gateId, ccapId, gatePathStr, gate, message);
449 logger.error("Unable to create QoS gate {} for {}/{} - {}",
450 gateId, ccapId, gatePathStr, message);
453 logger.error("Unable to locate PCMM Service for CCAP - " + thisCcap);
457 logger.error("PCMMService: sendGateSet(): SCN {} not found on CCAP {} for {}/{}",
458 scn.getValue(), thisCcap, gatePathStr, gate);
459 message = String.format("404 Not Found - SCN %s not found on CCAP %s for %s",
460 scn.getValue(), thisCcap.getCcapId(), gatePathStr);
464 final String subIdStr = thisData.subId;
465 message = String.format("404 Not Found - no CCAP found for subscriber %s in %s",
466 subIdStr, gatePathStr);
467 logger.info("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}/{}",
468 gateId, subIdStr, gatePathStr, gate);
469 logger.error("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}",
470 gateId, subIdStr, gatePathStr);
473 final String subIdStr = thisData.subId;
474 message = String.format("400 Bad Request - subId must be a valid IP address for subscriber %s in %s",
475 subIdStr, gatePathStr);
476 logger.info("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}/{}",
477 gateId, subIdStr, gatePathStr, gate);
478 logger.error("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}",
479 gateId, subIdStr, gatePathStr);
481 if (!message.contains("200 OK")) {
482 mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, entry.getKey());
488 private void onRemove(final InstanceData thisData) {
489 logger.info("onRemove(): " + thisData);
490 for (final String gatePathStr: thisData.removePathList) {
491 if (gateMap.containsKey(gatePathStr)) {
492 final Gate thisGate = gateMap.remove(gatePathStr);
493 final String gateId = thisGate.getGateId();
494 final String ccapId = gateCcapMap.remove(gatePathStr);
495 final Ccap thisCcap = ccapMap.get(ccapId);
496 final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
497 if (service != null) {
498 service.sendGateDelete(gatePathStr);
499 logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr, thisGate);
500 logger.info("onDataChanged(): removed QoS gate {} for {}/{}: ", gateId, ccapId, gatePathStr);
502 logger.warn("Unable to send to locate PCMMService to send gate delete message with CCAP - "
506 for (final String ccapIdStr: thisData.removePathList) {
507 if (ccapMap.containsKey(ccapIdStr)) {
508 final Ccap thisCcap = ccapMap.remove(ccapIdStr);
509 removeCcapFromAllMaps(thisCcap);
514 private void onUpdate(final InstanceData oldData) {
515 logger.info("onUpdate(): " + oldData);
516 // update operation not allowed -- restore the original config object and complain
517 if (! oldData.ccapIidMap.isEmpty()) {
518 for (final Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : oldData.ccapIidMap.entrySet()) {
519 final Ccap ccap = entry.getValue();
520 final String ccapId = ccap.getCcapId();
521 // 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
522 mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), ccap);
523 logger.error("onDataChanged(): CCAP update not permitted {}/{}", ccapId, ccap);
526 for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : oldData.gateIidMap.entrySet()) {
527 final Gate gate = entry.getValue();
528 final String gatePathStr = oldData.gatePath + "/" + gate.getGateId() ;
529 // 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
530 mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), gate);
531 logger.error("onDataChanged(): QoS Gate update not permitted: {}/{}", gatePathStr, gate);