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