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