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