Adding support for multiple classifiers per gate
[packetcable.git] / packetcable-policy-server / src / main / java / org / opendaylight / controller / packetcable / provider / PacketcableProvider.java
1 /*
2  * Copyright (c) 2015 CableLabs and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.packetcable.provider;
10
11 import static com.google.common.base.Preconditions.checkNotNull;
12
13 import com.google.common.base.Optional;
14 import com.google.common.collect.Maps;
15 import com.google.common.collect.Sets;
16 import com.google.common.util.concurrent.CheckedFuture;
17 import java.net.InetAddress;
18 import java.net.UnknownHostException;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.Executor;
27 import java.util.concurrent.Executors;
28 import javax.annotation.Nonnull;
29 import javax.annotation.concurrent.ThreadSafe;
30 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
31 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
32 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
33 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
34 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
35 import org.opendaylight.controller.packetcable.provider.validation.DataValidator;
36 import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
37 import org.opendaylight.controller.packetcable.provider.validation.Validator;
38 import org.opendaylight.controller.packetcable.provider.validation.impl.CcapsValidatorProviderFactory;
39 import org.opendaylight.controller.packetcable.provider.validation.impl.QosValidatorProviderFactory;
40 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
41 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
44 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.Ccaps;
45 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.Qos;
46 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ServiceClassName;
47 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ServiceFlowDirection;
48 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ccap.attributes.ConnectionBuilder;
49 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ccaps.Ccap;
50 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.ccaps.CcapBuilder;
51 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.Apps;
52 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.App;
53 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.AppBuilder;
54 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.AppKey;
55 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.Subscribers;
56 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.SubscribersBuilder;
57 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.Subscriber;
58 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.SubscriberBuilder;
59 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.SubscriberKey;
60 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.subscriber.Gates;
61 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.subscriber.GatesBuilder;
62 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
63 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateBuilder;
64 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151101.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateKey;
65 import org.opendaylight.yangtools.concepts.ListenerRegistration;
66 import org.opendaylight.yangtools.yang.binding.DataObject;
67 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
68 import org.pcmm.rcd.IPCMMClient;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72 /**
73  * Called by ODL framework to start this bundle.
74  * <p>
75  * This class is responsible for processing messages received from ODL's restconf interface.
76  * TODO - Remove some of these state maps and move some of this into the PCMMService
77  */
78 @ThreadSafe
79 public class PacketcableProvider implements BindingAwareProvider, AutoCloseable {
80
81     private static final Logger logger = LoggerFactory.getLogger(PacketcableProvider.class);
82
83     // keys to the /restconf/config/packetcable:ccaps and /restconf/config/packetcable:qos config datastore
84     private static final InstanceIdentifier<Ccaps> ccapsIID = InstanceIdentifier.builder(Ccaps.class).build();
85     private static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
86
87     // TODO - Revisit these maps and remove the ones no longer necessary
88     private final Map<String, Ccap> ccapMap = new ConcurrentHashMap<>();
89     private final Map<String, Gate> gateMap = new ConcurrentHashMap<>();
90     private final Map<String, String> gateCcapMap = new ConcurrentHashMap<>();
91     private final Map<Subnet, Ccap> subscriberSubnetsMap = new ConcurrentHashMap<>();
92     private final Map<ServiceClassName, List<Ccap>> downstreamScnMap = new ConcurrentHashMap<>();
93     private final Map<ServiceClassName, List<Ccap>> upstreamScnMap = new ConcurrentHashMap<>();
94
95     private final Executor executor = Executors.newSingleThreadExecutor();
96
97     /**
98      * Holds a PCMMService object for each CCAP being managed.
99      */
100     private final Map<String, PCMMService> pcmmServiceMap = new ConcurrentHashMap<>();
101
102     /**
103      * The ODL object used to broker messages throughout the framework
104      */
105     private DataBroker dataBroker;
106     private MdsalUtils mdsalUtils;
107
108     // Data change listeners/registrations
109     private final CcapsDataChangeListener ccapsDataChangeListener = new CcapsDataChangeListener();
110     private final QosDataChangeListener qosDataChangeListener = new QosDataChangeListener();
111
112     private ListenerRegistration<DataChangeListener> ccapsDataChangeListenerRegistration;
113     private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
114
115     /**
116      * Constructor
117      */
118     public PacketcableProvider() {
119         logger.info("Starting provider");
120     }
121
122     @Override
123     public void onSessionInitiated(ProviderContext session) {
124         logger.info("Packetcable Session Initiated");
125         logger.info("logging levels: error={}, warn={}, info={}, debug={}, trace={}", logger.isErrorEnabled(),
126                 logger.isWarnEnabled(), logger.isInfoEnabled(), logger.isDebugEnabled(), logger.isTraceEnabled());
127
128         dataBroker = session.getSALService(DataBroker.class);
129
130         mdsalUtils = new MdsalUtils(dataBroker);
131
132         ccapsDataChangeListenerRegistration = dataBroker
133                 .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, ccapsIID.child(Ccap.class),
134                         ccapsDataChangeListener, DataBroker.DataChangeScope.SUBTREE);
135
136         qosDataChangeListenerRegistration = dataBroker
137                 .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, PacketcableProvider.qosIID.child(Apps.class).child(App.class),
138                         qosDataChangeListener, DataBroker.DataChangeScope.SUBTREE);
139     }
140
141     /**
142      * Implemented from the AutoCloseable interface.
143      */
144     @Override
145     public void close() throws ExecutionException, InterruptedException {
146         if (ccapsDataChangeListenerRegistration != null) {
147             ccapsDataChangeListenerRegistration.close();
148         }
149
150         if (qosDataChangeListenerRegistration != null) {
151             qosDataChangeListenerRegistration.close();
152         }
153     }
154
155     private void updateCcapMaps(final Ccap ccap) {
156         // add ccap to the subscriberSubnets map
157         for (final IpPrefix ipPrefix : ccap.getSubscriberSubnets()) {
158             try {
159                 subscriberSubnetsMap.put(Subnet.createInstance(getIpPrefixStr(ipPrefix)), ccap);
160             } catch (UnknownHostException e) {
161                 logger.error("updateSubscriberSubnets: {}:{} FAILED: {}", ipPrefix, ccap, e.getMessage());
162             }
163         }
164         // ccap to upstream SCN map
165         for (final ServiceClassName scn : ccap.getUpstreamScns()) {
166             if (upstreamScnMap.containsKey(scn)) {
167                 upstreamScnMap.get(scn).add(ccap);
168             } else {
169                 final List<Ccap> ccapList = new ArrayList<>();
170                 ccapList.add(ccap);
171                 upstreamScnMap.put(scn, ccapList);
172             }
173         }
174         // ccap to downstream SCN map
175         for (final ServiceClassName scn : ccap.getDownstreamScns()) {
176             if (downstreamScnMap.containsKey(scn)) {
177                 downstreamScnMap.get(scn).add(ccap);
178             } else {
179                 final List<Ccap> ccapList = new ArrayList<>();
180                 ccapList.add(ccap);
181                 downstreamScnMap.put(scn, ccapList);
182             }
183         }
184     }
185
186     private String getIpPrefixStr(final IpPrefix ipPrefix) {
187         final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
188         if (ipv4 != null) {
189             return ipv4.getValue();
190         } else {
191             return ipPrefix.getIpv6Prefix().getValue();
192         }
193     }
194
195     public InetAddress getInetAddress(final String subId) {
196         try {
197             return InetAddress.getByName(subId);
198         } catch (UnknownHostException e) {
199             logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
200             return null;
201         }
202     }
203
204     private Ccap findCcapForSubscriberId(final InetAddress inetAddr) {
205         // TODO replace this with a loading cache, https://github.com/google/guava/wiki/CachesExplained
206         Ccap matchedCcap = null;
207         int longestPrefixLen = -1;
208         for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
209             final Subnet subnet = entry.getKey();
210             if (subnet.isInNet(inetAddr)) {
211                 int prefixLen = subnet.getPrefixLen();
212                 if (prefixLen > longestPrefixLen) {
213                     matchedCcap = entry.getValue();
214                     longestPrefixLen = prefixLen;
215                 }
216             }
217         }
218         return matchedCcap;
219     }
220
221     private ServiceFlowDirection findScnOnCcap(final ServiceClassName scn, final Ccap ccap) {
222         checkNotNull(scn);
223         checkNotNull(ccap);
224
225         if (upstreamScnMap.containsKey(scn)) {
226             final List<Ccap> ccapList = upstreamScnMap.get(scn);
227             if (ccapList.contains(ccap)) {
228                 return ServiceFlowDirection.Us;
229             }
230         } else if (downstreamScnMap.containsKey(scn)) {
231             final List<Ccap> ccapList = downstreamScnMap.get(scn);
232             if (ccapList.contains(ccap)) {
233                 return ServiceFlowDirection.Ds;
234             }
235         }
236         return null;
237     }
238
239     private void removeCcapFromAllMaps(final Ccap ccap) {
240         // remove the ccap from all maps
241         // subscriberSubnets map
242         for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
243             if (entry.getValue() == ccap) {
244                 subscriberSubnetsMap.remove(entry.getKey());
245             }
246         }
247         // ccap to upstream SCN map
248         for (final Map.Entry<ServiceClassName, List<Ccap>> entry : upstreamScnMap.entrySet()) {
249             final List<Ccap> ccapList = entry.getValue();
250             ccapList.remove(ccap);
251             if (ccapList.isEmpty()) {
252                 upstreamScnMap.remove(entry.getKey());
253             }
254         }
255         // ccap to downstream SCN map
256         for (final Map.Entry<ServiceClassName, List<Ccap>> entry : downstreamScnMap.entrySet()) {
257             final List<Ccap> ccapList = entry.getValue();
258             ccapList.remove(ccap);
259             if (ccapList.isEmpty()) {
260                 downstreamScnMap.remove(entry.getKey());
261             }
262         }
263
264         final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
265         if (service != null) {
266             service.disconect();
267         }
268     }
269
270     // ValidationException does not need to be thrown again
271     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
272     private <T extends DataObject> void saveErrors(@Nonnull Map<InstanceIdentifier<T>, ValidationException> errorMap,
273             @Nonnull Map<InstanceIdentifier<T>, T> dataMap) {
274
275         final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
276
277
278         for (InstanceIdentifier<T> iid : errorMap.keySet()) {
279
280             final ValidationException exception = errorMap.get(iid);
281             final T badData = dataMap.get(iid);
282
283             if (!badData.getImplementedInterface().isAssignableFrom(iid.getTargetType())) {
284                 // InstanceIdentifier<T> does not have the same type as the DataObject
285                 logger.error("Bad InstanceIdentifier to DataObject mapping, {} : {}", iid, badData);
286                 continue;
287             }
288
289             if (badData instanceof Ccap) {
290                 final Ccap ccap = (Ccap) badData;
291
292                 final Ccap opperationalCcap =
293                         new CcapBuilder().setCcapId(ccap.getCcapId()).setError(exception.getErrorMessages()).build();
294
295
296                 // type match between iid and badData is done at start of loop
297                 @SuppressWarnings("unchecked")
298                 final InstanceIdentifier<Ccap> ccapIID = (InstanceIdentifier<Ccap>) iid;
299                 writeTransaction.put(LogicalDatastoreType.OPERATIONAL, ccapIID, opperationalCcap);
300             }
301             else if (badData instanceof Gate) {
302                 final Gate gate = (Gate) badData;
303
304                 final Gate operationalGate =
305                         new GateBuilder()
306                         .setGateId(gate.getGateId())
307                         .setError(exception.getErrorMessages())
308                         .build();
309
310                 final Gates operationalGates = new GatesBuilder()
311                         .setGate(Collections.singletonList(operationalGate))
312                         .build();
313
314                 final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(iid.firstIdentifierOf(Subscriber.class));
315                 final Subscriber operationalSubscriber = new SubscriberBuilder()
316                         .setSubscriberId(subscriberKey.getSubscriberId())
317                         .setGates(operationalGates)
318                         .build();
319
320                 final Subscribers operationalSubscribers = new SubscribersBuilder()
321                         .setSubscriber(Collections.singletonList(operationalSubscriber))
322                         .build();
323
324                 final InstanceIdentifier<App> appIID = iid.firstIdentifierOf(App.class);
325                 final AppKey appKey = InstanceIdentifier.keyOf(appIID);
326                 final App operationalApp = new AppBuilder()
327                         .setAppId(appKey.getAppId())
328                         .setSubscribers(operationalSubscribers)
329                         .build();
330
331
332                 writeTransaction.put(LogicalDatastoreType.OPERATIONAL, appIID, operationalApp);
333             }
334             else {
335                 // If you get here a developer forgot to add a type above
336                 logger.error("Unexpected type requested for error saving: {}", badData);
337                 throw new IllegalStateException("Unsupported type for error saving");
338             }
339
340         }
341
342
343         CheckedFuture<Void, TransactionCommitFailedException> future = writeTransaction.submit();
344
345         try {
346             future.checkedGet();
347         } catch (TransactionCommitFailedException e) {
348             logger.error("Failed to write errors to operational datastore", e);
349         }
350     }
351
352     /**
353      * Removes Ccaps if all Ccap instances are removed
354      */
355     private class CcapsCleaner extends AbstractCleaner<Ccaps> {
356
357         public CcapsCleaner(final InstanceIdentifier<?> removedIID) {
358             super(removedIID, Ccaps.class, LogicalDatastoreType.OPERATIONAL);
359         }
360
361         @Override
362         protected boolean shouldClean(final Ccaps ccaps) {
363             return ccaps.getCcap().isEmpty();
364         }
365     }
366
367     /**
368      * Removes Subscriber if all Gate instances are removed
369      */
370     private class SubscriberCleaner extends AbstractCleaner<Subscriber> {
371
372         public SubscriberCleaner(InstanceIdentifier<Gate> removedGateIID) {
373             super(removedGateIID, Subscriber.class, LogicalDatastoreType.OPERATIONAL);
374         }
375
376         @Override
377         protected boolean shouldClean(final Subscriber subscriber) {
378             return subscriber.getGates().getGate().isEmpty();
379         }
380
381         @Override
382         protected void postRemove(InstanceIdentifier<Subscriber> subscriberIID) {
383             executor.execute(new AppCleaner(subscriberIID));
384         }
385     }
386
387
388     /**
389      * Removes App if all Subscribers are removed.
390      */
391     private class AppCleaner extends AbstractCleaner<App> {
392
393         public AppCleaner(InstanceIdentifier<Subscriber> removedSubscriberIID) {
394             super(removedSubscriberIID, App.class, LogicalDatastoreType.OPERATIONAL);
395         }
396
397         @Override
398         boolean shouldClean(final App app) {
399             return app.getSubscribers().getSubscriber().isEmpty();
400         }
401
402         @Override
403         void postRemove(final InstanceIdentifier<App> appIID) {
404             executor.execute(new AppsCleaner(appIID));
405         }
406     }
407
408
409     /**
410      * Removes Apps if all App instances are removed.
411      */
412     private class AppsCleaner extends  AbstractCleaner<Apps> {
413
414         public AppsCleaner(InstanceIdentifier<App> removedAppIID) {
415             super(removedAppIID, Apps.class, LogicalDatastoreType.OPERATIONAL);
416         }
417
418         @Override
419         protected boolean shouldClean(final Apps apps) {
420             return apps.getApp().isEmpty();
421         }
422     }
423
424
425     /**
426      * Helper class to do the heavy lifting in removing object. Lets subclasses decide with
427      *  {@link #shouldClean(DataObject)}. <br>
428      *
429      * Subclasses can react after an instance is removed by overriding {@link #postRemove(InstanceIdentifier)}
430      * @param <T> The type that will be removed
431      */
432     private abstract class AbstractCleaner <T extends DataObject> implements Runnable {
433         final InstanceIdentifier<?> removedIID;
434         final Class<T> tClass;
435         final LogicalDatastoreType datastoreType;
436
437         public AbstractCleaner(InstanceIdentifier<?> removedIID, Class<T> tClass, LogicalDatastoreType datastoreType) {
438             this.removedIID = checkNotNull(removedIID);
439             this.tClass = checkNotNull(tClass);
440             this.datastoreType = checkNotNull(datastoreType);
441         }
442
443         @Override
444         public void run() {
445             InstanceIdentifier<T> tIID = removedIID.firstIdentifierOf(tClass);
446             if (tIID != null) {
447                 Optional<T> optional = mdsalUtils.read(datastoreType, tIID);
448                 if (optional.isPresent()) {
449
450                     if (shouldClean(optional.get())) {
451                         if (mdsalUtils.delete(datastoreType, tIID)) {
452                             postRemove(tIID);
453                         }
454                         else {
455                             removeFailed(tIID);
456                         }
457                     }
458
459                 }
460             }
461             else {
462                 logger.error("Expected to find InstanceIdentifier<{}> but was not found: {}",
463                         tClass.getSimpleName(), removedIID);
464             }
465         }
466
467         /**
468          * If returns true the object will be removed from the datastore
469          * @param object The object that might be removed.
470          * @return true if it should be removed.
471          */
472         abstract boolean shouldClean(final T object);
473
474         /**
475          * Called after an instance is removed.
476          * @param tIID the InstanceIdentifier of the removed object
477          */
478         void postRemove(InstanceIdentifier<T> tIID) {
479
480         }
481
482         void removeFailed(InstanceIdentifier<T> tIID) {
483             logger.error("Failed to remove {}", tIID);
484         }
485     }
486
487
488     /**
489      * Listener for the packetcable:ccaps tree
490      */
491     private class CcapsDataChangeListener extends AbstractDataChangeListener<Ccap> {
492
493         private final DataValidator ccapsDataValidator = new DataValidator(new CcapsValidatorProviderFactory().build());
494
495         private final Set<InstanceIdentifier<Ccap>> updateQueue = Sets.newConcurrentHashSet();
496
497         public CcapsDataChangeListener() {
498             super(Ccap.class);
499         }
500
501         @Override
502         protected void handleCreatedData(final Map<InstanceIdentifier<Ccap>, Ccap> createdCcaps) {
503             if (createdCcaps.isEmpty()) {
504                 return;
505             }
506
507             final Map<InstanceIdentifier<Ccap>, ValidationException> errorMap =
508                     ccapsDataValidator.validateOneType(createdCcaps, Validator.Extent.NODE_AND_SUBTREE);
509
510             // validate all new objects an update operational datastore
511             if (!errorMap.isEmpty()) {
512                 // bad data write errors to operational datastore
513                 saveErrors(errorMap, createdCcaps);
514             }
515
516             if (createdCcaps.size() > errorMap.size()) {
517                 final Map<InstanceIdentifier<Ccap>, Ccap> goodData =
518                         Maps.newHashMapWithExpectedSize(createdCcaps.size() - errorMap.size());
519                 for (InstanceIdentifier<Ccap> iid : createdCcaps.keySet()) {
520                     if (!errorMap.containsKey(iid)) {
521                         goodData.put(iid, createdCcaps.get(iid));
522                     }
523                 }
524                 addNewCcaps(goodData);
525             }
526         }
527
528         private void addNewCcaps(final Map<InstanceIdentifier<Ccap>, Ccap> goodData) {
529             for (InstanceIdentifier<Ccap> iid : goodData.keySet()) {
530                 final Ccap ccap = goodData.get(iid);
531
532                 // add service
533                 if (pcmmServiceMap.containsKey(ccap.getCcapId())) {
534                     logger.error("Already monitoring CCAP - " + ccap);
535                     continue;
536                 }
537                 final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
538                 // TODO - may want to use the AMID but for the client type but probably not???
539 /*
540                             final PCMMService pcmmService = new PCMMService(
541                                     thisCcap.getAmId().getAmType().shortValue(), thisCcap);
542 */
543                 ConnectionBuilder connectionBuilder = new ConnectionBuilder();
544                 String message = pcmmService.addCcap();
545                 if (message.contains("200 OK")) {
546                     pcmmServiceMap.put(ccap.getCcapId(), pcmmService);
547                     ccapMap.put(ccap.getCcapId(), ccap);
548                     updateCcapMaps(ccap);
549                     logger.info("Created CCAP: {}/{} : {}", iid, ccap, message);
550                     logger.info("Created CCAP: {} : {}", iid, message);
551                     connectionBuilder.setConnected(true).setError(Collections.<String>emptyList());
552                 } else {
553                     logger.error("Create CCAP Failed: {} : {}", iid, message);
554
555                     connectionBuilder.setConnected(false).setError(Collections.singletonList(message));
556                 }
557
558                 Optional<Ccap> optionalCcap = mdsalUtils.read(LogicalDatastoreType.OPERATIONAL, iid);
559
560                 final CcapBuilder responseCcapBuilder;
561                 if (optionalCcap.isPresent()) {
562                     responseCcapBuilder = new CcapBuilder(optionalCcap.get());
563                 } else {
564                     responseCcapBuilder = new CcapBuilder();
565                     responseCcapBuilder.setCcapId(ccap.getCcapId());
566                 }
567
568                 responseCcapBuilder.setConnection(connectionBuilder.build());
569
570                 mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, iid, responseCcapBuilder.build());
571             }
572
573         }
574
575         @Override
576         protected void handleUpdatedData(final Map<InstanceIdentifier<Ccap>, Ccap> updatedCcaps,
577                 final Map<InstanceIdentifier<Ccap>, Ccap> originalCcaps) {
578
579             // TODO actually support updates
580
581             // update operation not allowed -- restore the original config object and complain
582             for (final Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : updatedCcaps.entrySet()) {
583                 if (!originalCcaps.containsKey(entry.getKey())) {
584                     logger.error("No original data found for supposedly updated data: {}", entry.getValue());
585                     continue;
586                 }
587
588                 // If this notification is coming from our modification ignore it.
589                 if (updateQueue.contains(entry.getKey())) {
590                     updateQueue.remove(entry.getKey());
591                     continue;
592                 }
593
594                 final Ccap originalCcap = originalCcaps.get(entry.getKey());
595                 //final Ccap updatedCcap = entry.getValue();
596
597                 // restore the original data
598                 updateQueue.add(entry.getKey());
599                 mdsalUtils.put(LogicalDatastoreType.CONFIGURATION, entry.getKey(), originalCcap);
600                 logger.error("CCAP update not permitted {}", entry.getKey());
601             }
602         }
603
604         @Override
605         protected void handleRemovedData(final Set<InstanceIdentifier<Ccap>> removedCcapPaths,
606                 final Map<InstanceIdentifier<Ccap>, Ccap> originalCcaps) {
607
608             for (InstanceIdentifier<Ccap> iid : removedCcapPaths) {
609                 final Ccap nukedCcap = originalCcaps.get(iid);
610                 removeCcapFromAllMaps(nukedCcap);
611
612                 mdsalUtils.delete(LogicalDatastoreType.OPERATIONAL, iid);
613
614                 // clean up ccaps level if it is now empty
615                 executor.execute(new CcapsCleaner(iid));
616             }
617
618         }
619     }
620
621
622     private class QosDataChangeListener extends AbstractDataChangeListener<Gate> {
623
624         private final DataValidator qosDataValidator = new DataValidator(new QosValidatorProviderFactory().build());
625         private final Set<InstanceIdentifier<Gate>> updateQueue = Sets.newConcurrentHashSet();
626
627         public QosDataChangeListener() {
628             super(Gate.class);
629         }
630
631         @Override
632         protected void handleCreatedData(final Map<InstanceIdentifier<Gate>, Gate> createdData) {
633
634             final Map<InstanceIdentifier<Gate>, ValidationException> errorMap =
635                     qosDataValidator.validateOneType(createdData, Validator.Extent.NODE_AND_SUBTREE);
636
637             // validate all new objects an update operational datastore
638             if (!errorMap.isEmpty()) {
639                 // bad data write errors to operational datastore
640                 saveErrors(errorMap, createdData);
641             }
642
643             if (createdData.size() > errorMap.size()) {
644                 final Map<InstanceIdentifier<Gate>, Gate> goodData =
645                         Maps.newHashMapWithExpectedSize(createdData.size() - errorMap.size());
646                 for (InstanceIdentifier<Gate> iid : createdData.keySet()) {
647                     if (!errorMap.containsKey(iid)) {
648                         goodData.put(iid, createdData.get(iid));
649                     }
650                 }
651                 addNewGates(goodData);
652             }
653
654         }
655
656         private void addNewGates(final Map<InstanceIdentifier<Gate>, Gate> createdGates) {
657
658             for (InstanceIdentifier<Gate> gateIID : createdGates.keySet()) {
659                 final Gate newGate = createdGates.get(gateIID);
660
661                 final String newGatePathStr = makeGatePathString(gateIID);
662
663                 final InstanceIdentifier<Subscriber> subscriberIID = gateIID.firstIdentifierOf(Subscriber.class);
664                 final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(subscriberIID);
665                 final InetAddress subscriberAddr = getInetAddress(subscriberKey.getSubscriberId());
666                 if (subscriberAddr == null) {
667                     final String msg = String.format("subscriberId must be a valid ipaddress: %s",
668                             subscriberKey.getSubscriberId());
669                     logger.error(msg);
670                     saveGateError(gateIID, newGatePathStr, msg);
671                     continue;
672                 }
673
674                 final Ccap ccap = findCcapForSubscriberId(subscriberAddr);
675                 if (ccap == null) {
676                     final String msg = String.format("Unable to find Ccap for subscriber %s: @ %s",
677                             subscriberKey.getSubscriberId(), newGatePathStr);
678                     logger.error(msg);
679                     saveGateError(gateIID, newGatePathStr, msg);
680                     continue;
681                 }
682
683                 final ServiceClassName scn = newGate.getTrafficProfile().getServiceClassName();
684                 final ServiceFlowDirection scnDirection = findScnOnCcap(scn, ccap);
685                 if (scnDirection == null) {
686                     final String msg = String.format("SCN %s not found on CCAP %s for %s",
687                             scn, ccap.getCcapId(), newGatePathStr);
688                     logger.error(msg);
689                     saveGateError(gateIID, newGatePathStr, msg);
690                     continue;
691                 }
692
693                 final PCMMService pcmmService = pcmmServiceMap.get(ccap.getCcapId());
694                 if (pcmmService == null) {
695                     final String msg = String.format("Unable to locate PCMM Service for CCAP: %s ; with subscriber: %s",
696                             ccap, subscriberKey.getSubscriberId());
697                     logger.error(msg);
698                     saveGateError(gateIID, newGatePathStr, msg);
699                     continue;
700                 }
701
702                 PCMMService.GateSetStatus status = pcmmService.sendGateSet(newGatePathStr, subscriberAddr, newGate, scnDirection);
703                 if (status.didSucceed()) {
704                     gateMap.put(newGatePathStr, newGate);
705                     gateCcapMap.put(newGatePathStr, ccap.getCcapId());
706                 }
707                 final GateBuilder gateBuilder = new GateBuilder();
708                 gateBuilder.setGateId(newGate.getGateId())
709                         .setGatePath(newGatePathStr)
710                         .setCcapId(ccap.getCcapId())
711                         .setCopsGateId(status.getCopsGateId())
712                         .setCopsState(status.didSucceed() ? "success" : "failure");
713                 if (!status.didSucceed()) {
714                     gateBuilder.setError(Collections.singletonList(status.getMessage()));
715                 }
716
717                 Gate operationalGate = gateBuilder.build();
718
719                 mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, gateIID, operationalGate);
720
721             }
722
723         }
724
725         private void saveGateError(@Nonnull final InstanceIdentifier<Gate> gateIID, @Nonnull final String gatePathStr,
726                 @Nonnull final String error) {
727             checkNotNull(gateIID);
728             checkNotNull(error);
729
730             final GateBuilder gateBuilder = new GateBuilder();
731             gateBuilder.setGateId(InstanceIdentifier.keyOf(gateIID).getGateId())
732                     .setGatePath(gatePathStr)
733                     .setCopsGateId("")
734                     .setCopsState("N/A");
735
736                 gateBuilder.setError(Collections.singletonList(error));
737
738             Gate operationalGate = gateBuilder.build();
739
740             mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, gateIID, operationalGate);
741         }
742
743         @Override
744         protected void handleUpdatedData(final Map<InstanceIdentifier<Gate>, Gate> updatedData,
745                 final Map<InstanceIdentifier<Gate>, Gate> originalData) {
746             // TODO actually support updates
747
748             // update operation not allowed -- restore the original config object and complain
749             for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : updatedData.entrySet()) {
750                 if (!originalData.containsKey(entry.getKey())) {
751                     logger.error("No original data found for supposedly updated data: {}", entry.getValue());
752                     continue;
753                 }
754
755                 // If this notification is coming from our modification ignore it.
756                 if (updateQueue.contains(entry.getKey())) {
757                     updateQueue.remove(entry.getKey());
758                     continue;
759                 }
760
761                 final Gate originalGate = originalData.get(entry.getKey());
762
763                 // restores the original data
764                 updateQueue.add(entry.getKey());
765                 mdsalUtils.put(LogicalDatastoreType.CONFIGURATION, entry.getKey(), originalGate);
766                 logger.error("Update not permitted {}", entry.getKey());
767
768             }
769         }
770
771
772
773         @Override
774         protected void handleRemovedData(final Set<InstanceIdentifier<Gate>> removedPaths,
775                 final Map<InstanceIdentifier<Gate>, Gate> originalData) {
776
777             for (final InstanceIdentifier<Gate> removedGateIID : removedPaths) {
778
779                 mdsalUtils.delete(LogicalDatastoreType.OPERATIONAL, removedGateIID);
780
781                 executor.execute(new SubscriberCleaner(removedGateIID));
782
783                 final String gatePathStr = makeGatePathString(removedGateIID);
784
785                     if (gateMap.containsKey(gatePathStr)) {
786                         final Gate thisGate = gateMap.remove(gatePathStr);
787                         final String gateId = thisGate.getGateId();
788                         final String ccapId = gateCcapMap.remove(gatePathStr);
789                         final Ccap thisCcap = ccapMap.get(ccapId);
790                         final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
791                         if (service != null) {
792                             service.sendGateDelete(gatePathStr);
793                             logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr,
794                                     thisGate);
795                         } else {
796                             logger.warn(
797                                     "Unable to send to locate PCMMService to send gate delete message with CCAP - " + thisCcap);
798                         }
799                     }
800
801
802             }
803
804         }
805
806         private String makeGatePathString(InstanceIdentifier<Gate> iid) {
807             final InstanceIdentifier<App> appIID = iid.firstIdentifierOf(App.class);
808             final AppKey appKey = InstanceIdentifier.keyOf(appIID);
809
810             final InstanceIdentifier<Subscriber> subscriberIID = iid.firstIdentifierOf(Subscriber.class);
811             final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(subscriberIID);
812
813             final GateKey gateKey = InstanceIdentifier.keyOf(iid);
814
815             return appKey.getAppId()
816                     + "/" + subscriberKey.getSubscriberId()
817                     + "/" + gateKey.getGateId();
818         }
819     }
820
821 }