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