Fix issue where NodeConnector ADDED events were propagated as NodeConnector CHANGED...
[controller.git] / opendaylight / containermanager / implementation / src / main / java / org / opendaylight / controller / containermanager / internal / ContainerManager.java
1
2 /*
3  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9
10 package org.opendaylight.controller.containermanager.internal;
11
12 import java.io.File;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import java.io.ObjectInputStream;
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.EnumSet;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Locale;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import java.util.concurrent.ConcurrentMap;
28 import java.util.concurrent.CopyOnWriteArrayList;
29
30 import org.eclipse.osgi.framework.console.CommandInterpreter;
31 import org.eclipse.osgi.framework.console.CommandProvider;
32 import org.opendaylight.controller.appauth.authorization.Authorization;
33 import org.opendaylight.controller.clustering.services.CacheConfigException;
34 import org.opendaylight.controller.clustering.services.CacheExistException;
35 import org.opendaylight.controller.clustering.services.ICacheUpdateAware;
36 import org.opendaylight.controller.clustering.services.IClusterGlobalServices;
37 import org.opendaylight.controller.clustering.services.IClusterServices;
38 import org.opendaylight.controller.configuration.ConfigurationObject;
39 import org.opendaylight.controller.configuration.IConfigurationAware;
40 import org.opendaylight.controller.configuration.IConfigurationService;
41 import org.opendaylight.controller.containermanager.ContainerChangeEvent;
42 import org.opendaylight.controller.containermanager.ContainerConfig;
43 import org.opendaylight.controller.containermanager.ContainerData;
44 import org.opendaylight.controller.containermanager.ContainerFlowChangeEvent;
45 import org.opendaylight.controller.containermanager.ContainerFlowConfig;
46 import org.opendaylight.controller.containermanager.IContainerAuthorization;
47 import org.opendaylight.controller.containermanager.IContainerManager;
48 import org.opendaylight.controller.containermanager.NodeConnectorsChangeEvent;
49 import org.opendaylight.controller.sal.authorization.AppRoleLevel;
50 import org.opendaylight.controller.sal.authorization.Privilege;
51 import org.opendaylight.controller.sal.authorization.Resource;
52 import org.opendaylight.controller.sal.authorization.ResourceGroup;
53 import org.opendaylight.controller.sal.authorization.UserLevel;
54 import org.opendaylight.controller.sal.core.ContainerFlow;
55 import org.opendaylight.controller.sal.core.IContainerAware;
56 import org.opendaylight.controller.sal.core.IContainerListener;
57 import org.opendaylight.controller.sal.core.IContainerLocalListener;
58 import org.opendaylight.controller.sal.core.Node;
59 import org.opendaylight.controller.sal.core.NodeConnector;
60 import org.opendaylight.controller.sal.core.UpdateType;
61 import org.opendaylight.controller.sal.match.Match;
62 import org.opendaylight.controller.sal.utils.GlobalConstants;
63 import org.opendaylight.controller.sal.utils.IObjectReader;
64 import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
65 import org.opendaylight.controller.sal.utils.NodeCreator;
66 import org.opendaylight.controller.sal.utils.ServiceHelper;
67 import org.opendaylight.controller.sal.utils.Status;
68 import org.opendaylight.controller.sal.utils.StatusCode;
69 import org.opendaylight.controller.topologymanager.ITopologyManager;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72
73 public class ContainerManager extends Authorization<String> implements IContainerManager, IObjectReader,
74         CommandProvider, ICacheUpdateAware<String, Object>, IContainerInternal, IContainerAuthorization,
75         IConfigurationAware {
76     private static final Logger logger = LoggerFactory.getLogger(ContainerManager.class);
77     private static String CONTAINERS_FILE_NAME = "containers.conf";
78     private static final String allContainersGroup = "allContainers";
79     private IClusterGlobalServices clusterServices;
80     private IConfigurationService configurationService;
81     /*
82      * Collection containing the configuration objects. This is configuration
83      * world: container names (also the map key) are maintained as they were
84      * configured by user, same case
85      */
86     private ConcurrentMap<String, ContainerConfig> containerConfigs;
87     private ConcurrentMap<String, ContainerData> containerData;
88     private ConcurrentMap<NodeConnector, CopyOnWriteArrayList<String>> nodeConnectorToContainers;
89     private ConcurrentMap<Node, Set<String>> nodeToContainers;
90     private ConcurrentMap<String, Object> containerChangeEvents;
91     private final Set<IContainerAware> iContainerAware = Collections.synchronizedSet(new HashSet<IContainerAware>());
92     private final Set<IContainerListener> iContainerListener = Collections
93             .synchronizedSet(new HashSet<IContainerListener>());
94     private final Set<IContainerLocalListener> iContainerLocalListener = Collections
95             .synchronizedSet(new HashSet<IContainerLocalListener>());
96
97     void setIContainerListener(IContainerListener s) {
98         if (this.iContainerListener != null) {
99             this.iContainerListener.add(s);
100             /*
101              * At boot with startup, containers are created before listeners have
102              * joined. Replaying here the first container creation notification for
103              * the joining listener when containers are already present. Also
104              * replaying all the node connectors and container flows additions
105              * to the existing containers.
106              */
107             if (!this.containerData.isEmpty()) {
108                 s.containerModeUpdated(UpdateType.ADDED);
109             }
110             for (ConcurrentMap.Entry<NodeConnector, CopyOnWriteArrayList<String>> entry : nodeConnectorToContainers
111                     .entrySet()) {
112                 NodeConnector port = entry.getKey();
113                 for (String container : entry.getValue()) {
114                     s.nodeConnectorUpdated(container, port, UpdateType.ADDED);
115                 }
116             }
117             for (Map.Entry<String, ContainerData> container : containerData.entrySet()) {
118                 for (ContainerFlow cFlow : container.getValue().getContainerFlowSpecs()) {
119                     s.containerFlowUpdated(container.getKey(), cFlow, cFlow, UpdateType.ADDED);
120                 }
121             }
122         }
123     }
124
125     void unsetIContainerListener(IContainerListener s) {
126         if (this.iContainerListener != null) {
127             this.iContainerListener.remove(s);
128         }
129     }
130
131     void setIContainerLocalListener(IContainerLocalListener s) {
132         if (this.iContainerLocalListener != null) {
133             this.iContainerLocalListener.add(s);
134         }
135     }
136
137     void unsetIContainerLocalListener(IContainerLocalListener s) {
138         if (this.iContainerLocalListener != null) {
139             this.iContainerLocalListener.remove(s);
140         }
141     }
142
143     public void setIContainerAware(IContainerAware iContainerAware) {
144         if (!this.iContainerAware.contains(iContainerAware)) {
145             this.iContainerAware.add(iContainerAware);
146             // Now call the container creation for all the known containers so far
147             for (String container : getContainerNameList()) {
148                 iContainerAware.containerCreate(container.toLowerCase(Locale.ENGLISH));
149             }
150         }
151     }
152
153     public void unsetIContainerAware(IContainerAware iContainerAware) {
154         this.iContainerAware.remove(iContainerAware);
155         // There is no need to do cleanup of the component when
156         // unregister because it will be taken care by the Container
157         // component itself
158     }
159
160     public void setClusterServices(IClusterGlobalServices i) {
161         this.clusterServices = i;
162         logger.debug("IClusterServices set");
163     }
164
165     public void unsetClusterServices(IClusterGlobalServices i) {
166         if (this.clusterServices == i) {
167             this.clusterServices = null;
168             logger.debug("IClusterServices Unset");
169         }
170     }
171
172     public void setConfigurationService(IConfigurationService service) {
173         logger.trace("Got configuration service set request {}", service);
174         this.configurationService = service;
175     }
176
177     public void unsetConfigurationService(IConfigurationService service) {
178         logger.trace("Got configuration service UNset request");
179         this.configurationService = null;
180     }
181
182     private void allocateCaches() {
183         logger.debug("Container Manager allocating caches");
184
185         if (clusterServices == null) {
186             logger.warn("un-initialized Cluster Services, can't allocate caches");
187             return;
188         }
189         try {
190             clusterServices.createCache("containermgr.containerConfigs", EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
191
192             clusterServices.createCache("containermgr.event.containerChange",
193                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
194
195             clusterServices.createCache("containermgr.containerData", EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
196
197             clusterServices.createCache("containermgr.nodeConnectorToContainers",
198                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
199
200             clusterServices.createCache("containermgr.nodeToContainers", EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
201
202             clusterServices.createCache("containermgr.containerGroups", EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
203
204             clusterServices.createCache("containermgr.containerAuthorizations",
205                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
206
207             clusterServices.createCache("containermgr.roles", EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
208         } catch (CacheConfigException cce) {
209             logger.error("Cache configuration invalid - check cache mode");
210         } catch (CacheExistException ce) {
211             logger.error("Cache already exits - destroy and recreate if needed");
212         }
213     }
214
215     @SuppressWarnings({ "unchecked" })
216     private void retrieveCaches() {
217         logger.debug("Container Manager retrieving caches");
218
219         if (clusterServices == null) {
220             logger.warn("un-initialized Cluster Services, can't retrieve caches");
221             return;
222         }
223
224         containerConfigs = (ConcurrentMap<String, ContainerConfig>) clusterServices.getCache("containermgr.containerConfigs");
225
226         containerChangeEvents = (ConcurrentMap<String, Object>) clusterServices.getCache("containermgr.event.containerChange");
227
228         containerData = (ConcurrentMap<String, ContainerData>) clusterServices.getCache("containermgr.containerData");
229
230         nodeConnectorToContainers = (ConcurrentMap<NodeConnector, CopyOnWriteArrayList<String>>) clusterServices
231                 .getCache("containermgr.nodeConnectorToContainers");
232
233         nodeToContainers = (ConcurrentMap<Node, Set<String>>) clusterServices.getCache("containermgr.nodeToContainers");
234
235         resourceGroups = (ConcurrentMap<String, Set<String>>) clusterServices.getCache("containermgr.containerGroups");
236
237         groupsAuthorizations = (ConcurrentMap<String, Set<ResourceGroup>>) clusterServices
238                 .getCache("containermgr.containerAuthorizations");
239
240         roles = (ConcurrentMap<String, AppRoleLevel>) clusterServices.getCache("containermgr.roles");
241
242         if (inContainerMode()) {
243             for (Map.Entry<String, ContainerConfig> entry : containerConfigs.entrySet()) {
244                 // Notify global and local listeners about the mode change
245                 notifyContainerChangeInternal(entry.getValue(), UpdateType.ADDED, true);
246             }
247         }
248     }
249
250     @Override
251     public void entryCreated(String containerName, String cacheName, boolean originLocal) {
252
253     }
254
255     @Override
256     public void entryUpdated(String key, Object value, String cacheName, boolean originLocal) {
257         /*
258          * This is were container manager replays a configuration event that was
259          * notified by its peer from a cluster node where the configuration
260          * happened. Only the global listeners, the cluster unaware classes,
261          * (mainly the shim classes in the sdn protocol plugins) need to receive
262          * these notifications on this cluster node. The cluster aware classes,
263          * like the functional modules which reacts on these events, must _not_
264          * be notified to avoid parallel computation in the cluster.
265          */
266         if (!originLocal) {
267             if (value instanceof NodeConnectorsChangeEvent) {
268                 NodeConnectorsChangeEvent event = (NodeConnectorsChangeEvent) value;
269                 List<NodeConnector> ncList = event.getNodeConnectors();
270                 notifyContainerEntryChangeInternal(key, ncList, event.getUpdateType(), false);
271             } else if (value instanceof ContainerFlowChangeEvent) {
272                 ContainerFlowChangeEvent event = (ContainerFlowChangeEvent) value;
273                 notifyCFlowChangeInternal(key, event.getConfigList(), event.getUpdateType(), false);
274             } else if (value instanceof ContainerChangeEvent) {
275                 ContainerChangeEvent event = (ContainerChangeEvent) value;
276                 notifyContainerChangeInternal(event.getConfig(), event.getUpdateType(), false);
277             }
278         }
279     }
280
281     @Override
282     public void entryDeleted(String containerName, String cacheName, boolean originLocal) {
283     }
284
285     public ContainerManager() {
286     }
287
288     public void init() {
289
290     }
291
292     public void start() {
293         // Get caches from cluster manager
294         allocateCaches();
295         retrieveCaches();
296
297         // Allocates default groups and association to default roles
298         createDefaultAuthorizationGroups();
299
300         // Read startup configuration and create local database
301         loadContainerConfig();
302     }
303
304     public void destroy() {
305         // Clear local states
306         this.iContainerAware.clear();
307         this.iContainerListener.clear();
308         this.iContainerLocalListener.clear();
309     }
310
311     /**
312      * Adds/Remove the list of flow specs to/from the specified container. This
313      * function is supposed to be called after all the validation checks have
314      * already been run on the proposed configuration.
315      */
316     private Status updateContainerFlow(String containerName, List<ContainerFlowConfig> confList, boolean delete) {
317         ContainerData container = getContainerByName(containerName);
318         if (container == null) {
319             return new Status(StatusCode.GONE, "Container not present");
320         }
321
322         for (ContainerFlowConfig conf : confList) {
323             // Validation was fine. Modify the database now.
324             for (Match match : conf.getMatches()) {
325                 ContainerFlow cFlow = new ContainerFlow(match);
326                 if (delete) {
327                     logger.trace("Removing Flow Spec {} from Container {}", conf.getName(), containerName);
328                     container.deleteFlowSpec(cFlow);
329                 } else {
330                     logger.trace("Adding Flow Spec {} to Container {}", conf.getName(), containerName);
331                     container.addFlowSpec(cFlow);
332
333                 }
334                 // Update Database
335                 putContainerDataByName(containerName, container);
336             }
337         }
338         return new Status(StatusCode.SUCCESS);
339     }
340
341     /**
342      * Adds/Remove this container to/from the Container database, no updates are going
343      * to be generated here other that the destroying and creation of the container.
344      * This function is supposed to be called after all the validation checks
345      * have already been run on the configuration object
346      */
347     private Status updateContainerDatabase(ContainerConfig containerConf, boolean delete) {
348         /*
349          * Back-end world here, container names are all stored in lower case
350          */
351         String containerName = containerConf.getContainerName();
352         ContainerData container = getContainerByName(containerName);
353         if (delete && container == null) {
354             return new Status(StatusCode.NOTFOUND, "Container is not present");
355         }
356         if (!delete && container != null) {
357             // A container with the same (lower case) name already exists
358             return new Status(StatusCode.CONFLICT, "A container with the same name already exists");
359         }
360         if (delete) {
361             logger.debug("Removing container {}", containerName);
362             removeNodeToContainersMapping(container);
363             removeNodeConnectorToContainersMapping(container);
364             removeContainerDataByName(containerName);
365         } else {
366             logger.debug("Adding container {}", containerName);
367             container = new ContainerData(containerConf);
368             putContainerDataByName(containerName, container);
369
370             // If flow specs are specified, add them
371             if (containerConf.hasFlowSpecs()) {
372                 updateContainerFlow(containerName, containerConf.getContainerFlowConfigs(), delete);
373             }
374
375             // If ports are specified, add them
376             if (!containerConf.getPortList().isEmpty()) {
377                 updateContainerEntryDatabase(containerName, containerConf.getPortList(), delete);
378             }
379         }
380         return new Status(StatusCode.SUCCESS);
381     }
382
383     private void removeNodeConnectorToContainersMapping(ContainerData container) {
384         Iterator<Entry<NodeConnector, CopyOnWriteArrayList<String>>> it = nodeConnectorToContainers.entrySet().iterator();
385         String containerName = container.getContainerName();
386         for (; it.hasNext();) {
387             Entry<NodeConnector, CopyOnWriteArrayList<String>> entry = it.next();
388             final NodeConnector nc = entry.getKey();
389             final CopyOnWriteArrayList<String> slist = entry.getValue();
390             for (final String sdata : slist) {
391                 if (sdata.equalsIgnoreCase(containerName)) {
392                     logger.debug("Removing NodeConnector->Containers mapping, nodeConnector: {}", nc);
393                     slist.remove(containerName);
394                     if (slist.isEmpty()) {
395                         nodeConnectorToContainers.remove(nc);
396                     } else {
397                         nodeConnectorToContainers.put(nc, slist);
398                     }
399                     break;
400                 }
401             }
402         }
403     }
404
405     private void removeNodeToContainersMapping(ContainerData container) {
406         for (Entry<Node, Set<String>> entry : nodeToContainers.entrySet()) {
407             Node node = entry.getKey();
408             for (String sdata : entry.getValue()) {
409                 if (sdata.equals(container.getContainerName())) {
410                     logger.debug("Removing Node->Containers mapping, node {} container {}", node, sdata);
411                     Set<String> value = nodeToContainers.get(node);
412                     value.remove(sdata);
413                     nodeToContainers.put(node, value);
414                     break;
415                 }
416             }
417         }
418     }
419
420     /**
421      * Adds/Remove container data to/from the container. This function is supposed to be
422      * called after all the validation checks have already been run on the
423      * configuration object
424      */
425     private Status updateContainerEntryDatabase(String containerName, List<NodeConnector> nodeConnectors, boolean delete) {
426         ContainerData container = getContainerByName(containerName);
427         // Presence check
428         if (container == null) {
429             return new Status(StatusCode.NOTFOUND, "Container Not Found");
430         }
431
432         // Check changes in the portlist
433         for (NodeConnector port : nodeConnectors) {
434             Node node = port.getNode();
435             if (delete) {
436                 container.removePortFromSwitch(port);
437                 putContainerDataByName(containerName, container);
438
439                 /* remove <sp> - container mapping */
440                 if (nodeConnectorToContainers.containsKey(port)) {
441                     nodeConnectorToContainers.remove(port);
442                 }
443                 /*
444                  * If no more ports in the switch, remove switch from container
445                  * Generate switchRemoved Event
446                  */
447                 if (container.portListEmpty(node)) {
448                     logger.debug("Port List empty for switch {}", node);
449                     putContainerDataByName(containerName, container);
450                     // remove node->containers mapping
451                     Set<String> slist = nodeToContainers.get(node);
452                     if (slist != null) {
453                         logger.debug("Removing container from switch-container list. node{}, container{}", node, containerName);
454                         slist.remove(container.getContainerName());
455                         nodeToContainers.put(node, slist);
456                         if (slist.isEmpty()) {
457                             logger.debug("Container list empty for switch {}. removing switch-container mapping", node);
458                             nodeToContainers.remove(node);
459                         }
460                     }
461                 }
462             } else {
463                 if (container.isSwitchInContainer(node) == false) {
464                     Set<String> value = nodeToContainers.get(node);
465                     // Add node->containers mapping
466                     if (value == null) {
467                         value = new HashSet<String>();
468                         logger.debug("Creating new Container Set for switch {}", node);
469                     }
470                     value.add(container.getContainerName());
471                     nodeToContainers.put(node, value);
472                 }
473                 container.addPortToSwitch(port);
474                 putContainerDataByName(containerName, container);
475
476                 // added nc->containers mapping
477                 CopyOnWriteArrayList<String> slist = nodeConnectorToContainers.get(port);
478                 if (slist == null) {
479                     slist = new CopyOnWriteArrayList<String>();
480                 }
481                 slist.add(container.getContainerName());
482                 nodeConnectorToContainers.put(port, slist);
483             }
484         }
485         return new Status(StatusCode.SUCCESS);
486     }
487
488     private Status validateContainerFlowAddRemoval(String containerName, ContainerFlow cFlow, boolean delete) {
489         /*
490          * It used to be the comment below: ~~~~~~~~~~~~~~~~~~~~ If Link Sharing
491          * at Host facing interfaces, then disallow last ContainerFlow removal
492          * ~~~~~~~~~~~~~~~~~~~~ But the interface being host facing is a
493          * condition that can change at runtime and so the final effect will be
494          * unreliable. So now we will always allow the container flow removal,
495          * if this is a link host facing and is shared by many that will cause
496          * issues but that validation should be done not at the configuration
497          * but in the UI/northbound side.
498          */
499         ContainerData container = this.getContainerByName(containerName);
500         if (container == null) {
501             String error = String.format("Cannot validate flow specs for container %s: (Container does not exist)", containerName);
502             logger.warn(error);
503             return new Status(StatusCode.BADREQUEST, error);
504         }
505
506         if (delete) {
507             Set<NodeConnector> thisContainerPorts = container.getNodeConnectors();
508             // Go through all the installed containers
509             for (Map.Entry<String, ContainerData> entry : containerData.entrySet()) {
510                 if (containerName.equalsIgnoreCase(entry.getKey())) {
511                     continue;
512                 }
513                 // Derive the common ports
514                 Set<NodeConnector> commonPorts = entry.getValue().getNodeConnectors();
515                 commonPorts.retainAll(thisContainerPorts);
516                 if (commonPorts.isEmpty()) {
517                     continue;
518                 }
519
520                 // Check if this operation would remove the only flow spec
521                 // assigned to this container
522                 if (container.getFlowSpecCount() == 1) {
523                     if (!container.hasStaticVlanAssigned()) {
524                         // Ports are shared and static vlan is not present: this
525                         // is a failure
526                         // regardless the shared ports are host facing or
527                         // interswitch ports
528                         return new Status(StatusCode.BADREQUEST, "Container shares port with another container: "
529                                 + "The only one flow spec assigned to this container cannot be removed,"
530                                 + "because this container is not assigned any static vlan");
531                     }
532
533                     // Check on host facing port
534                     ITopologyManager topologyManager = (ITopologyManager) ServiceHelper.getInstance(
535                             ITopologyManager.class, GlobalConstants.DEFAULT.toString(), this);
536                     if (topologyManager == null) {
537                         return new Status(StatusCode.NOSERVICE,
538                                 "Cannot validate the request: Required service is not available");
539                     }
540                     for (NodeConnector nc : commonPorts) {
541                         /*
542                          * Shared link case : For internal port check if it has
543                          * a vlan configured. If vlan is configured, allow the
544                          * flowspec to be deleted If the port is host-facing, do
545                          * not allow the flowspec to be deleted
546                          */
547                         if (!topologyManager.isInternal(nc)) {
548                             return new Status(StatusCode.BADREQUEST, String.format(
549                                     "Port %s is shared and is host facing port: "
550                                             + "The only one flow spec assigned to this container cannot be removed", nc));
551                         }
552                     }
553                 }
554             }
555         } else {
556             // Adding a new flow spec: need to check if other containers with common
557             // ports do not have same flow spec
558             Set<NodeConnector> thisContainerPorts = container.getNodeConnectors();
559             List<ContainerFlow> proposed = new ArrayList<ContainerFlow>(container.getContainerFlowSpecs());
560             proposed.add(cFlow);
561             for (Map.Entry<String, ContainerData> entry : containerData.entrySet()) {
562                 if (containerName.equalsIgnoreCase(entry.getKey())) {
563                     continue;
564                 }
565                 ContainerData otherContainer = entry.getValue();
566                 Set<NodeConnector> commonPorts = otherContainer.getNodeConnectors();
567                 commonPorts.retainAll(thisContainerPorts);
568
569                 if (!commonPorts.isEmpty()) {
570                     Status status = checkCommonContainerFlow(otherContainer.getContainerFlowSpecs(), proposed);
571                     if (!status.isSuccess()) {
572                         return new Status(StatusCode.BADREQUEST, String.format(
573                                 "Container %s which shares ports with this container has overlapping flow spec: %s",
574                                 entry.getKey(), status.getDescription()));
575                     }
576                 }
577             }
578         }
579
580         return new Status(StatusCode.SUCCESS);
581     }
582
583     /**
584      * Checks if the passed list of node connectors can be safely applied to the
585      * specified existing container in terms of port sharing with other containers.
586      *
587      * @param containerName
588      *            the name of the existing container
589      * @param portList
590      *            the list of node connectors to be added to the container
591      * @return the status object representing the result of the check
592      */
593     private Status validatePortSharing(String containerName, List<NodeConnector> portList) {
594         ContainerData container = this.getContainerByName(containerName);
595         if (container == null) {
596             String error = String
597                     .format("Cannot validate port sharing for container %s: (container does not exist)", containerName);
598             logger.error(error);
599             return new Status(StatusCode.BADREQUEST, error);
600         }
601         return validatePortSharingInternal(portList, container.getContainerFlowSpecs());
602     }
603
604     /**
605      * Checks if the proposed container configuration is valid to be applied in
606      * terms of port sharing with other containers.
607      *
608      * @param containerConf
609      *            the container configuration object containing the list of node
610      *            connectors
611      * @return the status object representing the result of the check
612      */
613     private Status validatePortSharing(ContainerConfig containerConf) {
614         return validatePortSharingInternal(containerConf.getPortList(), containerConf.getContainerFlowSpecs());
615     }
616
617     /*
618      * If any port is shared with an existing container, need flowSpec to be
619      * configured. If no flowSpec for this or other container, or if containers have any
620      * overlapping flowspec in common, then let the caller know this
621      * configuration has to be rejected.
622      */
623     private Status validatePortSharingInternal(List<NodeConnector> portList, List<ContainerFlow> flowSpecList) {
624         for (NodeConnector port : portList) {
625             List<String> slist = nodeConnectorToContainers.get(port);
626             if (slist != null && !slist.isEmpty()) {
627                 for (String otherContainerName : slist) {
628                     String msg = null;
629                     ContainerData other = containerData.get(otherContainerName);
630                     if (flowSpecList.isEmpty()) {
631                         msg = String.format("Port %s is shared and flow spec is empty for this container", port);
632                     } else if (other.isFlowSpecEmpty()) {
633                         msg = String.format("Port %s is shared and flow spec is empty for the other container", port);
634                     } else if (!checkCommonContainerFlow(flowSpecList, other.getContainerFlowSpecs()).isSuccess()) {
635                         msg = String.format("Port %s is shared and other container has common flow spec", port);
636                     }
637                     if (msg != null) {
638                         logger.debug(msg);
639                         return new Status(StatusCode.BADREQUEST, msg);
640                     }
641                 }
642             }
643         }
644         return new Status(StatusCode.SUCCESS);
645     }
646
647     /**
648      * Utility function to check if two lists of container flows share any same
649      * or overlapping container flows.
650      *
651      * @param oneFlowList
652      *            One of the two lists of container flows to test
653      * @param twoFlowList
654      *            One of the two lists of container flows to test
655      * @return The status of the check. Either SUCCESS or CONFLICT. In case of
656      *         conflict, the Status will contain the description for the failed
657      *         check.
658      */
659     private Status checkCommonContainerFlow(List<ContainerFlow> oneFlowList, List<ContainerFlow> twoFlowList) {
660         for (ContainerFlow oneFlow : oneFlowList) {
661             for (ContainerFlow twoFlow : twoFlowList) {
662                 if (oneFlow.getMatch().intersetcs(twoFlow.getMatch())) {
663                     return new Status(StatusCode.CONFLICT, String.format("Flow Specs overlap: %s %s",
664                             oneFlow.getMatch(), twoFlow.getMatch()));
665                 }
666             }
667         }
668         return new Status(StatusCode.SUCCESS);
669     }
670
671     /**
672      * Return the ContainerData object for the passed container name. Given this is a
673      * backend database, the lower case version of the passed name is used while
674      * searching for the corresponding ContainerData object.
675      *
676      * @param name
677      *            The container name in any case
678      * @return The corresponding ContainerData object
679      */
680     private ContainerData getContainerByName(String name) {
681         return containerData.get(name.toLowerCase(Locale.ENGLISH));
682     }
683
684     /**
685      * Add a ContainerData object for the given container name.
686      *
687      * @param name
688      *            The container name in any case
689      * @param sData
690      *            The container data object
691      */
692     private void putContainerDataByName(String name, ContainerData sData) {
693         containerData.put(name.toLowerCase(Locale.ENGLISH), sData);
694     }
695
696     /**
697      * Removes the ContainerData object for the given container name.
698      *
699      * @param name
700      *            The container name in any case
701      */
702     private void removeContainerDataByName(String name) {
703         containerData.remove(name.toLowerCase(Locale.ENGLISH));
704     }
705
706     @Override
707     public List<ContainerConfig> getContainerConfigList() {
708         return new ArrayList<ContainerConfig>(containerConfigs.values());
709     }
710
711     @Override
712     public ContainerConfig getContainerConfig(String containerName) {
713         ContainerConfig target = containerConfigs.get(containerName);
714         return (target == null) ? null : new ContainerConfig(target);
715     }
716
717     @Override
718     public List<String> getContainerNameList() {
719         /*
720          * Return container names as they were configured by user (case sensitive)
721          * along with the default container
722          */
723         List<String> containerNameList = new ArrayList<String>();
724         containerNameList.add(GlobalConstants.DEFAULT.toString());
725         containerNameList.addAll(containerConfigs.keySet());
726         return containerNameList;
727     }
728
729     @Override
730     public Map<String, List<ContainerFlowConfig>> getContainerFlows() {
731         Map<String, List<ContainerFlowConfig>> flowSpecConfig = new HashMap<String, List<ContainerFlowConfig>>();
732         for (Map.Entry<String, ContainerConfig> entry : containerConfigs.entrySet()) {
733             List<ContainerFlowConfig> set = entry.getValue().getContainerFlowConfigs();
734             flowSpecConfig.put(entry.getKey(), set);
735         }
736         return flowSpecConfig;
737     }
738
739     private Status saveContainerConfig() {
740         return saveContainerConfigLocal();
741     }
742
743     public Status saveContainerConfigLocal() {
744         Status status = configurationService.persistConfiguration(
745                 new ArrayList<ConfigurationObject>(containerConfigs.values()), CONTAINERS_FILE_NAME);
746
747         if (!status.isSuccess()) {
748             return new Status(StatusCode.INTERNALERROR, "Failed to save container configurations: "
749                     + status.getDescription());
750         }
751         return status;
752     }
753
754     private void removeComponentsStartUpfiles(String containerName) {
755         String startupLocation = String.format("./%s", GlobalConstants.STARTUPHOME.toString());
756         String containerPrint = String.format("_%s.", containerName.toLowerCase(Locale.ENGLISH));
757
758         File directory = new File(startupLocation);
759         String[] fileList = directory.list();
760
761         logger.trace("Deleting startup configuration files for container {}", containerName);
762         if (fileList != null) {
763             for (String fileName : fileList) {
764                 if (fileName.contains(containerPrint)) {
765                     String fullPath = String.format("%s/%s", startupLocation, fileName);
766                     File file = new File(fullPath);
767                     boolean done = file.delete();
768                     logger.trace("{} {}", (done ? "Deleted: " : "Failed to delete: "), fileName);
769                 }
770             }
771         }
772     }
773
774     /**
775      * Create and initialize default all resource group and create association
776      * with default well known users and profiles, if not already learnt from
777      * another cluster node
778      */
779     private void createDefaultAuthorizationGroups() {
780         allResourcesGroupName = ContainerManager.allContainersGroup;
781
782         // Add the default container to the all containers group if needed
783         String defaultContainer = GlobalConstants.DEFAULT.toString();
784         Set<String> allContainers = (resourceGroups.containsKey(allResourcesGroupName)) ? resourceGroups
785                 .get(allResourcesGroupName) : new HashSet<String>();
786         if (!allContainers.contains(defaultContainer)) {
787             // Add Default container
788             allContainers.add(defaultContainer);
789             // Update cluster
790             resourceGroups.put(allResourcesGroupName, allContainers);
791         }
792
793         // Add the controller well known roles, if not known already
794         if (!roles.containsKey(UserLevel.SYSTEMADMIN.toString())) {
795             roles.put(UserLevel.SYSTEMADMIN.toString(), AppRoleLevel.APPADMIN);
796         }
797         if (!roles.containsKey(UserLevel.NETWORKADMIN.toString())) {
798             roles.put(UserLevel.NETWORKADMIN.toString(), AppRoleLevel.APPADMIN);
799         }
800         if (!roles.containsKey(UserLevel.NETWORKOPERATOR.toString())) {
801             roles.put(UserLevel.NETWORKOPERATOR.toString(), AppRoleLevel.APPOPERATOR);
802         }
803
804         /*
805          * Create and add the all containers user groups and associate them to the
806          * default well known user roles, if not present already
807          */
808         if (!groupsAuthorizations.containsKey(UserLevel.NETWORKADMIN.toString())) {
809             Set<ResourceGroup> writeProfile = new HashSet<ResourceGroup>(1);
810             Set<ResourceGroup> readProfile = new HashSet<ResourceGroup>(1);
811             writeProfile.add(new ResourceGroup(allResourcesGroupName, Privilege.WRITE));
812             readProfile.add(new ResourceGroup(allResourcesGroupName, Privilege.READ));
813             groupsAuthorizations.put(UserLevel.SYSTEMADMIN.toString(), writeProfile);
814             groupsAuthorizations.put(UserLevel.NETWORKADMIN.toString(), writeProfile);
815             groupsAuthorizations.put(UserLevel.NETWORKOPERATOR.toString(), readProfile);
816         }
817     }
818
819     /**
820      * Until manual configuration is not available, automatically maintain the
821      * well known resource groups
822      *
823      * @param containerName
824      * @param delete
825      */
826     private void updateResourceGroups(ContainerConfig containerConf, boolean delete) {
827         // Container Roles and Container Resource Group
828         String containerName = containerConf.getContainer();
829         String groupName = containerConf.getContainerGroupName();
830         String containerAdminRole = containerConf.getContainerAdminRole();
831         String containerOperatorRole = containerConf.getContainerOperatorRole();
832         Set<String> allContainerSet = resourceGroups.get(allResourcesGroupName);
833         if (delete) {
834             resourceGroups.remove(groupName);
835             groupsAuthorizations.remove(containerAdminRole);
836             groupsAuthorizations.remove(containerOperatorRole);
837             roles.remove(containerAdminRole);
838             roles.remove(containerOperatorRole);
839             // Update the all container group
840             allContainerSet.remove(containerName);
841         } else {
842             Set<String> resources = new HashSet<String>(1);
843             resources.add(containerName);
844             resourceGroups.put(groupName, resources);
845             Set<ResourceGroup> adminGroups = new HashSet<ResourceGroup>(1);
846             Set<ResourceGroup> operatorGroups = new HashSet<ResourceGroup>(1);
847             adminGroups.add(new ResourceGroup(groupName, Privilege.WRITE));
848             operatorGroups.add(new ResourceGroup(groupName, Privilege.READ));
849             groupsAuthorizations.put(containerAdminRole, adminGroups);
850             groupsAuthorizations.put(containerOperatorRole, operatorGroups);
851             roles.put(containerAdminRole, AppRoleLevel.APPADMIN);
852             roles.put(containerOperatorRole, AppRoleLevel.APPOPERATOR);
853             // Update the all containers resource group
854             allContainerSet.add(containerName);
855         }
856         // Update resource groups in cluster
857         resourceGroups.put(allResourcesGroupName, allContainerSet);
858     }
859
860     /**
861      * Notify ContainerAware listeners of the creation/deletion of the container
862      *
863      * @param containerName
864      * @param delete
865      *            true is container was removed, false otherwise
866      */
867     private void notifyContainerAwareListeners(String containerName, boolean delete) {
868         // Back-end World: container name forced to lower case
869         String name = containerName.toLowerCase(Locale.ENGLISH);
870
871         synchronized (this.iContainerAware) {
872             for (IContainerAware i : this.iContainerAware) {
873                 if (delete) {
874                     i.containerDestroy(name);
875                 } else {
876                     i.containerCreate(name);
877                 }
878             }
879         }
880     }
881
882     /**
883      * Notify the ContainerListener listeners in case the container mode has
884      * changed following a container configuration operation Note: this call
885      * must happen after the configuration db has been updated
886      *
887      * @param lastActionDelete
888      *            true if the last container configuration operation was a
889      *            container delete operation
890      * @param notifyLocal
891      *            if true, the notification is also sent to the
892      *            IContainerLocalListener classes besides the IContainerListener
893      *            classes
894      */
895     private void notifyContainerModeChange(boolean lastActionDelete, boolean notifyLocal) {
896         if (lastActionDelete == false && containerConfigs.size() == 1) {
897             logger.trace("First container Creation. Inform listeners");
898             synchronized (this.iContainerListener) {
899                 for (IContainerListener i : this.iContainerListener) {
900                     i.containerModeUpdated(UpdateType.ADDED);
901                 }
902             }
903             if (notifyLocal) {
904                 synchronized (this.iContainerLocalListener) {
905                     for (IContainerLocalListener i : this.iContainerLocalListener) {
906                         i.containerModeUpdated(UpdateType.ADDED);
907                     }
908                 }
909             }
910         } else if (lastActionDelete == true && containerConfigs.isEmpty()) {
911             logger.trace("Last container Deletion. Inform listeners");
912             synchronized (this.iContainerListener) {
913                 for (IContainerListener i : this.iContainerListener) {
914                     i.containerModeUpdated(UpdateType.REMOVED);
915                 }
916             }
917             if (notifyLocal) {
918                 synchronized (this.iContainerLocalListener) {
919                     for (IContainerLocalListener i : this.iContainerLocalListener) {
920                         i.containerModeUpdated(UpdateType.REMOVED);
921                     }
922                 }
923             }
924         }
925     }
926
927     private Status addRemoveContainerEntries(String containerName, List<String> nodeConnectorsString, boolean delete) {
928         // Construct action message
929         String action = String.format("Node connector(s) %s container %s: %s", delete ? "removal from" : "addition to",
930                 containerName, nodeConnectorsString);
931
932         // Validity Check
933         if (nodeConnectorsString == null || nodeConnectorsString.isEmpty()) {
934             return new Status(StatusCode.BADREQUEST, "Node connector list is null or empty");
935         }
936
937         // Presence check
938         ContainerConfig entryConf = containerConfigs.get(containerName);
939         if (entryConf == null) {
940             String msg = String.format("Container not found: %s", containerName);
941             String error = String.format("Failed to apply %s: (%s)", action, msg);
942             logger.warn(error);
943             return new Status(StatusCode.NOTFOUND, msg);
944         }
945
946         // Validation check
947         Status status = ContainerConfig.validateNodeConnectors(nodeConnectorsString);
948         if (!status.isSuccess()) {
949             String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
950             logger.warn(error);
951             return status;
952         }
953
954         List<NodeConnector> nodeConnectors = ContainerConfig.nodeConnectorsFromString(nodeConnectorsString);
955
956         // Port sharing check
957         if (!delete) {
958             /*
959              * Check if the ports being added to this container already belong to
960              * other containers. If so check whether the the appropriate flow specs
961              * are configured on this container
962              */
963             status = validatePortSharing(containerName, nodeConnectors);
964             if (!status.isSuccess()) {
965                 String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
966                 logger.warn(error);
967                 return status;
968             }
969         }
970
971         // Update Database
972         status = updateContainerEntryDatabase(containerName, nodeConnectors, delete);
973         if (!status.isSuccess()) {
974             String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
975             logger.warn(error);
976             return status;
977         }
978
979         // Update Configuration
980         status = (delete) ? entryConf.removeNodeConnectors(nodeConnectorsString) : entryConf
981                 .addNodeConnectors(nodeConnectorsString);
982         if (!status.isSuccess()) {
983             String error = String.format("Failed to modify config for %s: (%s)", action, status.getDescription());
984             logger.warn(error);
985             // Revert backend changes
986             Status statusRevert = updateContainerEntryDatabase(containerName, nodeConnectors, !delete);
987             if (!statusRevert.isSuccess()) {
988                 // Unlikely
989                 logger.error("Failed to revert changes in database (CRITICAL)");
990             }
991             return status;
992         }
993
994         // Update cluster Configuration cache
995         containerConfigs.put(containerName, entryConf);
996
997         // Notify global and local listeners
998         UpdateType update = (delete) ? UpdateType.REMOVED : UpdateType.ADDED;
999         notifyContainerEntryChangeInternal(containerName, nodeConnectors, update, true);
1000         // Trigger cluster notification
1001         containerChangeEvents.put(containerName, new NodeConnectorsChangeEvent(nodeConnectors, update));
1002
1003         return status;
1004     }
1005
1006     private void notifyContainerChangeInternal(ContainerConfig conf, UpdateType update, boolean notifyLocal) {
1007         String containerName = conf.getContainerName();
1008         logger.trace("Notifying listeners on {} for container {}", update, containerName);
1009         // Back-end World: container name forced to lower case
1010         String container = containerName.toLowerCase(Locale.ENGLISH);
1011         boolean delete = (update == UpdateType.REMOVED);
1012         // Check if a container mode change notification is needed
1013         notifyContainerModeChange(delete, notifyLocal);
1014         // Notify listeners
1015         notifyContainerAwareListeners(container, delete);
1016
1017         /*
1018          * This is a quick fix until configuration service becomes the
1019          * centralized configuration management place. Here container manager
1020          * will remove the startup files for all the bundles that are present in
1021          * the container being deleted. Do the cleanup here in Container manger
1022          * as do not want to put this temporary code in Configuration manager
1023          * yet which is ODL.
1024          */
1025         if (delete) {
1026             // TODO: remove when Config Mgr takes over
1027             removeComponentsStartUpfiles(containerName);
1028         }
1029     }
1030
1031     private void notifyContainerEntryChangeInternal(String containerName, List<NodeConnector> ncList, UpdateType update, boolean notifyLocal) {
1032         logger.trace("Notifying listeners on {} for ports {} in container {}", update, ncList, containerName);
1033         // Back-end World: container name forced to lower case
1034         String container = containerName.toLowerCase(Locale.ENGLISH);
1035         for (NodeConnector nodeConnector : ncList) {
1036             // Now signal that the port has been added/removed
1037             synchronized (this.iContainerListener) {
1038                 for (IContainerListener i : this.iContainerListener) {
1039                     i.nodeConnectorUpdated(container, nodeConnector, update);
1040                 }
1041             }
1042             // Check if the Functional Modules need to be notified as well
1043             if (notifyLocal) {
1044                 synchronized (this.iContainerLocalListener) {
1045                     for (IContainerLocalListener i : this.iContainerLocalListener) {
1046                         i.nodeConnectorUpdated(container, nodeConnector, update);
1047                     }
1048                 }
1049             }
1050         }
1051     }
1052
1053     private void notifyCFlowChangeInternal(String containerName, List<ContainerFlowConfig> confList, UpdateType update,
1054             boolean notifyLocal) {
1055         logger.trace("Notifying listeners on {} for flow specs {} in container {}", update, confList, containerName);
1056         // Back-end World: container name forced to lower case
1057         String container = containerName.toLowerCase(Locale.ENGLISH);
1058
1059         for (ContainerFlowConfig conf : confList) {
1060             for (Match match : conf.getMatches()) {
1061                 ContainerFlow cFlow = new ContainerFlow(match);
1062                 synchronized (this.iContainerListener) {
1063                     for (IContainerListener i : this.iContainerListener) {
1064                         i.containerFlowUpdated(container, cFlow, cFlow, update);
1065                     }
1066                 }
1067                 // Check if the Functional Modules need to be notified as well
1068                 if (notifyLocal) {
1069                     synchronized (this.iContainerLocalListener) {
1070                         for (IContainerLocalListener i : this.iContainerLocalListener) {
1071                             i.containerFlowUpdated(container, cFlow, cFlow, update);
1072                         }
1073                     }
1074                 }
1075             }
1076         }
1077     }
1078
1079     private Status addRemoveContainerFlow(String containerName, List<ContainerFlowConfig> cFlowConfList, boolean delete) {
1080         // Construct action message
1081         String action = String.format("Flow spec(s) %s container %s: %s", delete ? "removal from" : "addition to",
1082                 containerName, cFlowConfList);
1083
1084         // Presence check
1085         ContainerConfig containerConfig = this.containerConfigs.get(containerName);
1086         if (containerConfig == null) {
1087             String msg = String.format("Container not found: %s", containerName);
1088             String error = String.format("Failed to apply %s: (%s)", action, msg);
1089             logger.warn(error);
1090             return new Status(StatusCode.NOTFOUND, "Container not present");
1091         }
1092
1093         // Validity check, check for overlaps on current container configuration
1094         Status status = containerConfig.validateContainerFlowModify(cFlowConfList, delete);
1095         if (!status.isSuccess()) {
1096             String msg = status.getDescription();
1097             String error = String.format("Failed to apply %s: (%s)", action, msg);
1098             logger.warn(error);
1099             return new Status(StatusCode.BADREQUEST, msg);
1100         }
1101
1102         // Validate the operation in terms to the port sharing with other containers
1103         for (ContainerFlowConfig conf : cFlowConfList) {
1104             for (Match match : conf.getMatches()) {
1105                 ContainerFlow cFlow = new ContainerFlow(match);
1106                 status = validateContainerFlowAddRemoval(containerName, cFlow, delete);
1107                 if (!status.isSuccess()) {
1108                     String msg = "Validation failed: " + status.getDescription();
1109                     String error = String.format("Failed to apply %s: (%s)", action, msg);
1110                     logger.warn(error);
1111                     return new Status(StatusCode.BADREQUEST, msg);
1112                 }
1113             }
1114         }
1115
1116         // Update Database
1117         status = updateContainerFlow(containerName, cFlowConfList, delete);
1118         if (!status.isSuccess()) {
1119             String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
1120             logger.error(error);
1121             return status;
1122         }
1123
1124         // Update Configuration
1125         status = (delete) ? containerConfig.removeContainerFlows(cFlowConfList) : containerConfig
1126                 .addContainerFlows(cFlowConfList);
1127         if (!status.isSuccess()) {
1128             String error = String.format("Failed to modify config for %s: (%s)", action, status.getDescription());
1129             logger.error(error);
1130             // Revert backend changes
1131             Status statusRevert = updateContainerFlow(containerName, cFlowConfList, !delete);
1132             if (!statusRevert.isSuccess()) {
1133                 // Unlikely
1134                 logger.error("Failed to revert changes in database (CRITICAL)");
1135             }
1136             return status;
1137         }
1138         // Update cluster cache
1139         this.containerConfigs.put(containerName, containerConfig);
1140
1141         // Notify global and local listeners
1142         UpdateType update = (delete) ? UpdateType.REMOVED : UpdateType.ADDED;
1143         notifyCFlowChangeInternal(containerName, cFlowConfList, update, true);
1144         // Trigger cluster notification
1145         containerChangeEvents.put(containerName, new ContainerFlowChangeEvent(cFlowConfList, update));
1146
1147         return status;
1148     }
1149
1150     private Status addRemoveContainer(ContainerConfig containerConf, boolean delete) {
1151         // Construct action message
1152         String action = String.format("Container %s", delete ? "removal" : "creation");
1153
1154         // Valid configuration check
1155         Status status = null;
1156         String error = (containerConfigs == null) ? String.format("Invalid %s configuration: (null config object)", action)
1157                 : (!(status = containerConf.validate()).isSuccess()) ? String.format("Invalid %s configuration: (%s)",
1158                         action, status.getDescription()) : null;
1159         if (error != null) {
1160             logger.warn(error);
1161             return new Status(StatusCode.BADREQUEST, error);
1162         }
1163
1164         // Configuration presence check
1165         String containerName = containerConf.getContainerName();
1166         if (delete) {
1167             if (!containerConfigs.containsKey(containerName)) {
1168                 String msg = String.format("%s Failed: (Container does not exist: %s)", action, containerName);
1169                 logger.warn(msg);
1170                 return new Status(StatusCode.NOTFOUND, msg);
1171             }
1172         } else {
1173             if (containerConfigs.containsKey(containerName)) {
1174                 String msg = String.format("%s Failed: (Container already exist: %s)", action, containerName);
1175                 logger.warn(msg);
1176                 return new Status(StatusCode.CONFLICT, msg);
1177             }
1178         }
1179
1180         /*
1181          * The proposed container configuration could be a complex one containing
1182          * both ports and flow spec. If so, check if it has shared ports with
1183          * other existing containers. If that is the case verify flow spec isolation
1184          * is in place. No need to check on flow spec validation first. This
1185          * would take care of both
1186          */
1187         if (!delete) {
1188             status = validatePortSharing(containerConf);
1189             if (!status.isSuccess()) {
1190                 error = String.format("%s Failed: (%s)", action, status.getDescription());
1191                 logger.error(error);
1192                 return status;
1193             }
1194         }
1195
1196         // Update Database
1197         status = updateContainerDatabase(containerConf, delete);
1198
1199         // Abort and exit here if back-end database update failed
1200         if (!status.isSuccess()) {
1201             return status;
1202         }
1203
1204         /*
1205          * Update Configuration: This will trigger the notifications on cache
1206          * update callback locally and on the other cluster nodes
1207          */
1208         if (delete) {
1209             this.containerConfigs.remove(containerName);
1210         } else {
1211             this.containerConfigs.put(containerName, containerConf);
1212         }
1213
1214         // Automatically create and populate user and resource groups
1215         updateResourceGroups(containerConf, delete);
1216
1217         // Notify global and local listeners
1218         UpdateType update = (delete) ? UpdateType.REMOVED : UpdateType.ADDED;
1219         notifyContainerChangeInternal(containerConf, update, true);
1220
1221         // Trigger cluster notification
1222         containerChangeEvents.put(containerName, new ContainerChangeEvent(containerConf, update));
1223
1224         if (update == UpdateType.ADDED) {
1225             if (containerConf.hasFlowSpecs()) {
1226                 List<ContainerFlowConfig> specList = containerConf.getContainerFlowConfigs();
1227                 // Notify global and local listeners about flow spec addition
1228                 notifyCFlowChangeInternal(containerName, specList, update, true);
1229
1230                 // Trigger cluster notification
1231                 containerChangeEvents.put(containerName, new ContainerFlowChangeEvent(specList, update));
1232             }
1233
1234             if (containerConf.hasNodeConnectors()) {
1235                 List<NodeConnector> ncList = containerConf.getPortList();
1236                 // Notify global and local listeners about port(s) addition
1237                 notifyContainerEntryChangeInternal(containerName, ncList, update, true);
1238                 // Trigger cluster notification
1239                 containerChangeEvents.put(containerName, new NodeConnectorsChangeEvent(ncList, update));
1240             }
1241         }
1242
1243         if (delete) {
1244             clusterServices.removeContainerCaches(containerName);
1245         }
1246         return status;
1247     }
1248
1249     @Override
1250     public Status addContainer(ContainerConfig containerConf) {
1251         return addRemoveContainer(containerConf, false);
1252     }
1253
1254     @Override
1255     public Status removeContainer(ContainerConfig containerConf) {
1256         return addRemoveContainer(containerConf, true);
1257     }
1258
1259     @Override
1260     public Status removeContainer(String containerName) {
1261         // Construct action message
1262         String action = String.format("Container removal: %s", containerName);
1263
1264         ContainerConfig containerConf = containerConfigs.get(containerName);
1265         if (containerConf == null) {
1266             String msg = String.format("Container not found");
1267             String error = String.format("Failed to apply %s: (%s)", action, msg);
1268             logger.warn(error);
1269             return new Status(StatusCode.NOTFOUND, msg);
1270         }
1271         return addRemoveContainer(containerConf, true);
1272     }
1273
1274     @Override
1275     public Status addContainerEntry(String containerName, List<String> nodeConnectors) {
1276         return addRemoveContainerEntries(containerName, nodeConnectors, false);
1277     }
1278
1279     @Override
1280     public Status removeContainerEntry(String containerName, List<String> nodeConnectors) {
1281         return addRemoveContainerEntries(containerName, nodeConnectors, true);
1282     }
1283
1284     @Override
1285     public Status addContainerFlows(String containerName, List<ContainerFlowConfig> fSpecConf) {
1286         return addRemoveContainerFlow(containerName, fSpecConf, false);
1287     }
1288
1289     @Override
1290     public Status removeContainerFlows(String containerName, List<ContainerFlowConfig> fSpecConf) {
1291         return addRemoveContainerFlow(containerName, fSpecConf, true);
1292     }
1293
1294     @Override
1295     public Status removeContainerFlows(String containerName, Set<String> names) {
1296         // Construct action message
1297         String action = String.format("Flow spec(s) removal from container %s: %s", containerName, names);
1298
1299         // Presence check
1300         ContainerConfig sc = containerConfigs.get(containerName);
1301         if (sc == null) {
1302             String msg = String.format("Container not found: %s", containerName);
1303             String error = String.format("Failed to apply %s: (%s)", action, msg);
1304             logger.warn(error);
1305             return new Status(StatusCode.NOTFOUND, msg);
1306         }
1307         List<ContainerFlowConfig> list = sc.getContainerFlowConfigs(names);
1308         if (list.isEmpty() || list.size() != names.size()) {
1309             String msg = String.format("Cannot find all the specified flow specs");
1310             String error = String.format("Failed to apply %s: (%s)", action, msg);
1311             logger.warn(error);
1312             return new Status(StatusCode.BADREQUEST, msg);
1313         }
1314         return addRemoveContainerFlow(containerName, list, true);
1315     }
1316
1317     @Override
1318     public List<ContainerFlowConfig> getContainerFlows(String containerName) {
1319         ContainerConfig sc = containerConfigs.get(containerName);
1320         return (sc == null) ? new ArrayList<ContainerFlowConfig>(0) : sc.getContainerFlowConfigs();
1321     }
1322
1323     @Override
1324     public List<String> getContainerFlowNameList(String containerName) {
1325         ContainerConfig sc = containerConfigs.get(containerName);
1326         return (sc == null) ? new ArrayList<String>(0) : sc.getContainerFlowConfigsNames();
1327     }
1328
1329     @Override
1330     public Object readObject(ObjectInputStream ois) throws FileNotFoundException, IOException, ClassNotFoundException {
1331         // Perform the class deserialization locally, from inside the package
1332         // where the class is defined
1333         return ois.readObject();
1334     }
1335
1336     private void loadContainerConfig() {
1337         for (ConfigurationObject conf : configurationService.retrieveConfiguration(this, CONTAINERS_FILE_NAME)) {
1338             addContainer((ContainerConfig) conf);
1339         }
1340     }
1341
1342     public void _psc(CommandInterpreter ci) {
1343         for (Map.Entry<String, ContainerConfig> entry : containerConfigs.entrySet()) {
1344             ContainerConfig sc = entry.getValue();
1345             ci.println(String.format("%s: %s", sc.getContainerName(), sc.toString()));
1346         }
1347         ci.println("Total number of containers: " + containerConfigs.entrySet().size());
1348     }
1349
1350     public void _pfc(CommandInterpreter ci) {
1351         for (Map.Entry<String, ContainerConfig> entry : containerConfigs.entrySet()) {
1352             ContainerConfig sc = entry.getValue();
1353             ci.println(String.format("%s: %s", sc.getContainerName(), sc.getContainerFlowConfigs()));
1354         }
1355     }
1356
1357     public void _psd(CommandInterpreter ci) {
1358         for (String containerName : containerData.keySet()) {
1359             ContainerData sd = containerData.get(containerName);
1360             for (Node sid : sd.getSwPorts().keySet()) {
1361                 Set<NodeConnector> s = sd.getSwPorts().get(sid);
1362                 ci.println("\t" + sid + " : " + s);
1363             }
1364
1365             for (ContainerFlow s : sd.getContainerFlowSpecs()) {
1366                 ci.println("\t" + s.toString());
1367             }
1368         }
1369     }
1370
1371     public void _psp(CommandInterpreter ci) {
1372         for (NodeConnector sp : nodeConnectorToContainers.keySet()) {
1373             ci.println(nodeConnectorToContainers.get(sp));
1374         }
1375     }
1376
1377     public void _psm(CommandInterpreter ci) {
1378         for (Node sp : nodeToContainers.keySet()) {
1379             ci.println(nodeToContainers.get(sp));
1380         }
1381     }
1382
1383     public void _addContainer(CommandInterpreter ci) {
1384         String containerName = ci.nextArgument();
1385         if (containerName == null) {
1386             ci.print("Container Name not specified");
1387             return;
1388         }
1389         String staticVlan = ci.nextArgument();
1390         ContainerConfig containerConfig = new ContainerConfig(containerName, staticVlan, null, null);
1391         ci.println(this.addRemoveContainer(containerConfig, false));
1392     }
1393
1394     public void _createContainer(CommandInterpreter ci) {
1395         String containerName = ci.nextArgument();
1396         if (containerName == null) {
1397             ci.print("Container Name not specified");
1398             return;
1399         }
1400         String staticVlan = ci.nextArgument();
1401         if (staticVlan == null) {
1402             ci.print("Static Vlan not specified");
1403             return;
1404         }
1405         List<String> ports = new ArrayList<String>();
1406         for (long l = 1L; l < 10L; l++) {
1407             ports.add(NodeConnectorCreator.createOFNodeConnector((short) 1, NodeCreator.createOFNode(l)).toString());
1408         }
1409         List<ContainerFlowConfig> cFlowList = new ArrayList<ContainerFlowConfig>();
1410         cFlowList.add(this.createSampleContainerFlowConfig("tcp", true));
1411         ContainerConfig containerConfig = new ContainerConfig(containerName, staticVlan, ports, cFlowList);
1412         ci.println(this.addRemoveContainer(containerConfig, false));
1413     }
1414
1415     public void _removeContainer(CommandInterpreter ci) {
1416         String containerName = ci.nextArgument();
1417         if (containerName == null) {
1418             ci.print("Container Name not specified");
1419             return;
1420         }
1421         ContainerConfig containerConfig = new ContainerConfig(containerName, "", null, null);
1422         ci.println(this.addRemoveContainer(containerConfig, true));
1423     }
1424
1425     public void _addContainerEntry(CommandInterpreter ci) {
1426         String containerName = ci.nextArgument();
1427         if (containerName == null) {
1428             ci.print("Container Name not specified");
1429             return;
1430         }
1431         String nodeId = ci.nextArgument();
1432         if (nodeId == null) {
1433             ci.print("Node Id not specified");
1434             return;
1435         }
1436         String portId = ci.nextArgument();
1437         if (portId == null) {
1438             ci.print("Port not specified");
1439             return;
1440         }
1441         Node node = NodeCreator.createOFNode(Long.valueOf(nodeId));
1442         Short port = Short.valueOf(portId);
1443         NodeConnector nc = NodeConnectorCreator.createOFNodeConnector(port, node);
1444         List<String> portList = new ArrayList<String>(1);
1445         portList.add(nc.toString());
1446         ci.println(this.addRemoveContainerEntries(containerName, portList, false));
1447     }
1448
1449     public void _removeContainerEntry(CommandInterpreter ci) {
1450         String containerName = ci.nextArgument();
1451         if (containerName == null) {
1452             ci.print("Container Name not specified");
1453             return;
1454         }
1455         String nodeId = ci.nextArgument();
1456         if (nodeId == null) {
1457             ci.print("Node Id not specified");
1458             return;
1459         }
1460         String portId = ci.nextArgument();
1461         if (portId == null) {
1462             ci.print("Port not specified");
1463             return;
1464         }
1465         Node node = NodeCreator.createOFNode(Long.valueOf(nodeId));
1466         Short port = Short.valueOf(portId);
1467         NodeConnector nc = NodeConnectorCreator.createOFNodeConnector(port, node);
1468         List<String> portList = new ArrayList<String>(1);
1469         portList.add(nc.toString());
1470         ci.println(this.addRemoveContainerEntries(containerName, portList, true));
1471     }
1472
1473     private ContainerFlowConfig createSampleContainerFlowConfig(String cflowName, boolean boolUnidirectional) {
1474         ContainerFlowConfig cfg = new ContainerFlowConfig(cflowName, "9.9.1.0/24", "19.9.1.2", "TCP", "1234", "25");
1475         return cfg;
1476     }
1477
1478     public void _addContainerFlow(CommandInterpreter ci) {
1479         String containerName = ci.nextArgument();
1480         if (containerName == null) {
1481             ci.print("Container Name not specified");
1482             return;
1483         }
1484         String cflowName = ci.nextArgument();
1485         if (cflowName == null) {
1486             ci.print("cflowName not specified");
1487             return;
1488         }
1489         String unidirectional = ci.nextArgument();
1490         boolean boolUnidirectional = Boolean.parseBoolean(unidirectional);
1491         List<ContainerFlowConfig> list = new ArrayList<ContainerFlowConfig>();
1492         list.add(createSampleContainerFlowConfig(cflowName, boolUnidirectional));
1493         ci.println(this.addRemoveContainerFlow(containerName, list, false));
1494     }
1495
1496     public void _removeContainerFlow(CommandInterpreter ci) {
1497         String containerName = ci.nextArgument();
1498         if (containerName == null) {
1499             ci.print("Container Name not specified");
1500             return;
1501         }
1502         String cflowName = ci.nextArgument();
1503         if (cflowName == null) {
1504             ci.print("cflowName not specified");
1505             return;
1506         }
1507         Set<String> set = new HashSet<String>(1);
1508         set.add(cflowName);
1509         ci.println(this.removeContainerFlows(containerName, set));
1510     }
1511
1512     @Override
1513     public String getHelp() {
1514         StringBuffer help = new StringBuffer();
1515         help.append("---ContainerManager Testing---\n");
1516         help.append("\tpsc        - Print ContainerConfigs\n");
1517         help.append("\tpfc        - Print FlowSpecConfigs\n");
1518         help.append("\tpsd        - Print ContainerData\n");
1519         help.append("\tpsp        - Print nodeConnectorToContainers\n");
1520         help.append("\tpsm        - Print nodeToContainers\n");
1521         help.append("\t addContainer <containerName> <staticVlan> \n");
1522         help.append("\t removeContainer <containerName> \n");
1523         help.append("\t addContainerEntry <containerName> <nodeId> <port> \n");
1524         help.append("\t removeContainerEntry <containerName> <nodeId> <port> \n");
1525         help.append("\t addContainerFlow <containerName> <cflowName> <unidirectional true/false>\n");
1526         help.append("\t removeContainerFlow <containerName> <cflowName> \n");
1527         return help.toString();
1528     }
1529
1530     @Override
1531     public boolean doesContainerExist(String containerName) {
1532         // Test for default container
1533         if (GlobalConstants.DEFAULT.toString().equalsIgnoreCase(containerName)) {
1534             return true;
1535         }
1536         // Test for non-default one
1537         return (getContainerByName(containerName) != null);
1538     }
1539
1540     @Override
1541     public ContainerData getContainerData(String containerName) {
1542         return (getContainerByName(containerName));
1543     }
1544
1545     @Override
1546     public Status saveConfiguration() {
1547         return saveContainerConfig();
1548     }
1549
1550     public void _containermgrGetRoles(CommandInterpreter ci) {
1551         ci.println("Configured roles for Container Mgr:");
1552         List<String> list = this.getRoles();
1553         for (String role : list) {
1554             ci.println(role + "\t" + roles.get(role));
1555         }
1556     }
1557
1558     public void _containermgrGetAuthorizedGroups(CommandInterpreter ci) {
1559         String roleName = ci.nextArgument();
1560         if (roleName == null || roleName.trim().isEmpty()) {
1561             ci.println("Invalid argument");
1562             ci.println("mmGetAuthorizedGroups <role_name>");
1563             return;
1564         }
1565         ci.println("Resource Groups associated to role " + roleName + ":");
1566         List<ResourceGroup> list = this.getAuthorizedGroups(roleName);
1567         for (ResourceGroup group : list) {
1568             ci.println(group.toString());
1569         }
1570     }
1571
1572     public void _containermgrGetAuthorizedResources(CommandInterpreter ci) {
1573         String roleName = ci.nextArgument();
1574         if (roleName == null || roleName.trim().isEmpty()) {
1575             ci.println("Invalid argument");
1576             ci.println("mmGetAuthorizedResources <role_name>");
1577             return;
1578         }
1579         ci.println("Resource associated to role " + roleName + ":");
1580         List<Resource> list = this.getAuthorizedResources(roleName);
1581         for (Resource resource : list) {
1582             ci.println(resource.toString());
1583         }
1584     }
1585
1586     public void _containermgrGetResourcesForGroup(CommandInterpreter ci) {
1587         String groupName = ci.nextArgument();
1588         if (groupName == null || groupName.trim().isEmpty()) {
1589             ci.println("Invalid argument");
1590             ci.println("containermgrResourcesForGroup <group_name>");
1591             return;
1592         }
1593         ci.println("Group " + groupName + " contains the following resources:");
1594         List<Object> resources = this.getResources(groupName);
1595         for (Object resource : resources) {
1596             ci.println(resource.toString());
1597         }
1598     }
1599
1600     public void _containermgrGetUserLevel(CommandInterpreter ci) {
1601         String userName = ci.nextArgument();
1602         if (userName == null || userName.trim().isEmpty()) {
1603             ci.println("Invalid argument");
1604             ci.println("containermgrGetUserLevel <user_name>");
1605             return;
1606         }
1607         ci.println("User " + userName + " has level: " + this.getUserLevel(userName));
1608     }
1609
1610     public void _containermgrGetUserResources(CommandInterpreter ci) {
1611         String userName = ci.nextArgument();
1612         if (userName == null || userName.trim().isEmpty()) {
1613             ci.println("Invalid argument");
1614             ci.println("containermgrGetUserResources <user_name>");
1615             return;
1616         }
1617         ci.println("User " + userName + " owns the following resources: ");
1618         Set<Resource> resources = this.getAllResourcesforUser(userName);
1619         for (Resource resource : resources) {
1620             ci.println(resource.toString());
1621         }
1622     }
1623
1624     /*
1625      * For scalability testing where as of now controller gui is unresponsive
1626      * providing here an osgi hook to trigger the save config so that DT do not
1627      * have to reaply the scalable configuration each time they restart the
1628      * controller
1629      */
1630     // TODO: remove when no longer needed
1631     public void _saveConfig(CommandInterpreter ci) {
1632         Status status = new Status(StatusCode.NOSERVICE, "Configuration service not reachable");
1633
1634         IConfigurationService configService = (IConfigurationService) ServiceHelper.getGlobalInstance(
1635                 IConfigurationService.class, this);
1636         if (configService != null) {
1637             status = configService.saveConfigurations();
1638         }
1639         ci.println(status.toString());
1640     }
1641
1642     @Override
1643     public List<String> getContainerNames() {
1644         return getContainerNameList();
1645     }
1646
1647     @Override
1648     public boolean hasNonDefaultContainer() {
1649         return !containerConfigs.keySet().isEmpty();
1650     }
1651
1652     @Override
1653     public boolean inContainerMode() {
1654         return this.containerConfigs.size() > 0;
1655     }
1656 }