+ public Map<String, List<ContainerFlowConfig>> getContainerFlows() {
+ Map<String, List<ContainerFlowConfig>> flowSpecConfig = new HashMap<String, List<ContainerFlowConfig>>();
+ for (Map.Entry<String, ContainerConfig> entry : containerConfigs.entrySet()) {
+ List<ContainerFlowConfig> set = entry.getValue().getContainerFlowConfigs();
+ flowSpecConfig.put(entry.getKey(), set);
+ }
+ return flowSpecConfig;
+ }
+
+ private void loadConfigurations() {
+ /*
+ * Read containers, container flows and finally containers' entries from file
+ * and program the database accordingly
+ */
+ if ((clusterServices != null) && (clusterServices.amICoordinator())) {
+ loadContainerConfig();
+ }
+ }
+
+ private Status saveContainerConfig() {
+ return saveContainerConfigLocal();
+ }
+
+ public Status saveContainerConfigLocal() {
+ ObjectWriter objWriter = new ObjectWriter();
+
+ Status status = objWriter.write(new ConcurrentHashMap<String, ContainerConfig>(containerConfigs), containersFileName);
+ if (!status.isSuccess()) {
+ return new Status(StatusCode.INTERNALERROR, "Failed to save container configurations: "
+ + status.getDescription());
+ }
+ return new Status(StatusCode.SUCCESS);
+ }
+
+ private void removeComponentsStartUpfiles(String containerName) {
+ String startupLocation = String.format("./%s", GlobalConstants.STARTUPHOME.toString());
+ String containerPrint = String.format("_%s.", containerName.toLowerCase(Locale.ENGLISH));
+
+ File directory = new File(startupLocation);
+ String[] fileList = directory.list();
+
+ logger.trace("Deleting startup configuration files for container {}", containerName);
+ if (fileList != null) {
+ for (String fileName : fileList) {
+ if (fileName.contains(containerPrint)) {
+ String fullPath = String.format("%s/%s", startupLocation, fileName);
+ File file = new File(fullPath);
+ boolean done = file.delete();
+ logger.trace("{} {}", (done ? "Deleted: " : "Failed to delete: "), fileName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Create and initialize default all resource group and create association
+ * with default well known users and profiles, if not already learnt from
+ * another cluster node
+ */
+ private void createDefaultAuthorizationGroups() {
+ allResourcesGroupName = ContainerManager.allContainersGroup;
+
+ // Add the default container to the all containers group if needed
+ String defaultContainer = GlobalConstants.DEFAULT.toString();
+ Set<String> allContainers = (resourceGroups.containsKey(allResourcesGroupName)) ? resourceGroups
+ .get(allResourcesGroupName) : new HashSet<String>();
+ if (!allContainers.contains(defaultContainer)) {
+ // Add Default container
+ allContainers.add(defaultContainer);
+ // Update cluster
+ resourceGroups.put(allResourcesGroupName, allContainers);
+ }
+
+ // Add the controller well known roles, if not known already
+ if (!roles.containsKey(UserLevel.SYSTEMADMIN.toString())) {
+ roles.put(UserLevel.SYSTEMADMIN.toString(), AppRoleLevel.APPADMIN);
+ }
+ if (!roles.containsKey(UserLevel.NETWORKADMIN.toString())) {
+ roles.put(UserLevel.NETWORKADMIN.toString(), AppRoleLevel.APPADMIN);
+ }
+ if (!roles.containsKey(UserLevel.NETWORKOPERATOR.toString())) {
+ roles.put(UserLevel.NETWORKOPERATOR.toString(), AppRoleLevel.APPOPERATOR);
+ }
+
+ /*
+ * Create and add the all containers user groups and associate them to the
+ * default well known user roles, if not present already
+ */
+ if (!groupsAuthorizations.containsKey(UserLevel.NETWORKADMIN.toString())) {
+ Set<ResourceGroup> writeProfile = new HashSet<ResourceGroup>(1);
+ Set<ResourceGroup> readProfile = new HashSet<ResourceGroup>(1);
+ writeProfile.add(new ResourceGroup(allResourcesGroupName, Privilege.WRITE));
+ readProfile.add(new ResourceGroup(allResourcesGroupName, Privilege.READ));
+ groupsAuthorizations.put(UserLevel.SYSTEMADMIN.toString(), writeProfile);
+ groupsAuthorizations.put(UserLevel.NETWORKADMIN.toString(), writeProfile);
+ groupsAuthorizations.put(UserLevel.NETWORKOPERATOR.toString(), readProfile);
+ }
+ }
+
+ /**
+ * Until manual configuration is not available, automatically maintain the
+ * well known resource groups
+ *
+ * @param containerName
+ * @param delete
+ */
+ private void updateResourceGroups(ContainerConfig containerConf, boolean delete) {
+ // Container Roles and Container Resource Group
+ String containerName = containerConf.getContainer();
+ String groupName = containerConf.getContainerGroupName();
+ String containerAdminRole = containerConf.getContainerAdminRole();
+ String containerOperatorRole = containerConf.getContainerOperatorRole();
+ Set<String> allContainerSet = resourceGroups.get(allResourcesGroupName);
+ if (delete) {
+ resourceGroups.remove(groupName);
+ groupsAuthorizations.remove(containerAdminRole);
+ groupsAuthorizations.remove(containerOperatorRole);
+ roles.remove(containerAdminRole);
+ roles.remove(containerOperatorRole);
+ // Update the all container group
+ allContainerSet.remove(containerName);
+ } else {
+ Set<String> resources = new HashSet<String>(1);
+ resources.add(containerName);
+ resourceGroups.put(groupName, resources);
+ Set<ResourceGroup> adminGroups = new HashSet<ResourceGroup>(1);
+ Set<ResourceGroup> operatorGroups = new HashSet<ResourceGroup>(1);
+ adminGroups.add(new ResourceGroup(groupName, Privilege.WRITE));
+ operatorGroups.add(new ResourceGroup(groupName, Privilege.READ));
+ groupsAuthorizations.put(containerAdminRole, adminGroups);
+ groupsAuthorizations.put(containerOperatorRole, operatorGroups);
+ roles.put(containerAdminRole, AppRoleLevel.APPADMIN);
+ roles.put(containerOperatorRole, AppRoleLevel.APPOPERATOR);
+ // Update the all containers resource group
+ allContainerSet.add(containerName);
+ }
+ // Update resource groups in cluster
+ resourceGroups.put(allResourcesGroupName, allContainerSet);
+ }
+
+ /**
+ * Notify ContainerAware listeners of the creation/deletion of the container
+ *
+ * @param containerName
+ * @param delete
+ * true is container was removed, false otherwise
+ */
+ private void notifyContainerAwareListeners(String containerName, boolean delete) {
+ // Back-end World: container name forced to lower case
+ String name = containerName.toLowerCase(Locale.ENGLISH);
+
+ synchronized (this.iContainerAware) {
+ for (IContainerAware i : this.iContainerAware) {
+ if (delete) {
+ i.containerDestroy(name);
+ } else {
+ i.containerCreate(name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Notify the ContainerListener listeners in case the container mode has
+ * changed following a container configuration operation Note: this call
+ * must happen after the configuration db has been updated
+ *
+ * @param lastActionDelete
+ * true if the last container configuration operation was a
+ * container delete operation
+ * @param notifyLocal
+ * if true, the notification is also sent to the
+ * IContainerLocalListener classes besides the IContainerListener
+ * classes
+ */
+ private void notifyContainerModeChange(boolean lastActionDelete, boolean notifyLocal) {
+ if (lastActionDelete == false && containerConfigs.size() == 1) {
+ logger.info("First container Creation. Inform listeners");
+ synchronized (this.iContainerListener) {
+ for (IContainerListener i : this.iContainerListener) {
+ i.containerModeUpdated(UpdateType.ADDED);
+ }
+ }
+ if (notifyLocal) {
+ synchronized (this.iContainerLocalListener) {
+ for (IContainerLocalListener i : this.iContainerLocalListener) {
+ i.containerModeUpdated(UpdateType.ADDED);
+ }
+ }
+ }
+ } else if (lastActionDelete == true && containerConfigs.isEmpty()) {
+ logger.info("Last container Deletion. Inform listeners");
+ synchronized (this.iContainerListener) {
+ for (IContainerListener i : this.iContainerListener) {
+ i.containerModeUpdated(UpdateType.REMOVED);
+ }
+ }
+ if (notifyLocal) {
+ synchronized (this.iContainerLocalListener) {
+ for (IContainerLocalListener i : this.iContainerLocalListener) {
+ i.containerModeUpdated(UpdateType.REMOVED);
+ }
+ }
+ }
+ }
+ }
+
+ private Status addRemoveContainerEntries(String containerName, List<String> nodeConnectorsString, boolean delete) {
+ // Construct action message
+ String action = String.format("Node connector(s) %s container %s: %s", delete ? "removal from" : "addition to",
+ containerName, nodeConnectorsString);
+
+ // Validity Check
+ if (nodeConnectorsString == null || nodeConnectorsString.isEmpty()) {
+ return new Status(StatusCode.BADREQUEST, "Node connector list is null or empty");
+ }
+
+ // Presence check
+ ContainerConfig entryConf = containerConfigs.get(containerName);
+ if (entryConf == null) {
+ String msg = String.format("Container not found: %s", containerName);
+ String error = String.format("Failed to apply %s: (%s)", action, msg);
+ logger.warn(error);
+ return new Status(StatusCode.NOTFOUND, msg);
+ }
+
+ // Validation check
+ Status status = ContainerConfig.validateNodeConnectors(nodeConnectorsString);
+ if (!status.isSuccess()) {
+ String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
+ logger.warn(error);
+ return status;
+ }
+
+ List<NodeConnector> nodeConnectors = ContainerConfig.nodeConnectorsFromString(nodeConnectorsString);
+
+ // Port sharing check
+ if (!delete) {
+ /*
+ * Check if the ports being added to this container already belong to
+ * other containers. If so check whether the the appropriate flow specs
+ * are configured on this container
+ */
+ status = validatePortSharing(containerName, nodeConnectors);
+ if (!status.isSuccess()) {
+ String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
+ logger.warn(error);
+ return status;
+ }
+ }
+
+ // Update Database
+ status = updateContainerEntryDatabase(containerName, nodeConnectors, delete);
+ if (!status.isSuccess()) {
+ String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
+ logger.warn(error);
+ return status;
+ }
+
+ // Update Configuration
+ status = (delete) ? entryConf.removeNodeConnectors(nodeConnectorsString) : entryConf
+ .addNodeConnectors(nodeConnectorsString);
+ if (!status.isSuccess()) {
+ String error = String.format("Failed to modify config for %s: (%s)", action, status.getDescription());
+ logger.warn(error);
+ // Revert backend changes
+ Status statusRevert = updateContainerEntryDatabase(containerName, nodeConnectors, !delete);
+ if (!statusRevert.isSuccess()) {
+ // Unlikely
+ logger.error("Failed to revert changes in database (CRITICAL)");
+ }
+ return status;
+ }
+
+ // Update cluster Configuration cache
+ containerConfigs.put(containerName, entryConf);
+
+ // Notify global and local listeners
+ UpdateType update = (delete) ? UpdateType.REMOVED : UpdateType.ADDED;
+ notifyContainerEntryChangeInternal(containerName, nodeConnectors, update, true);
+ // Trigger cluster notification
+ containerChangeEvents.put(containerName, new NodeConnectorsChangeEvent(nodeConnectors, update));
+
+ return status;
+ }
+
+ private void notifyContainerChangeInternal(ContainerConfig conf, UpdateType update, boolean notifyLocal) {
+ String containerName = conf.getContainerName();
+ logger.trace("Notifying listeners on {} for container {}", update, containerName);
+ // Back-end World: container name forced to lower case
+ String container = containerName.toLowerCase(Locale.ENGLISH);
+ boolean delete = (update == UpdateType.REMOVED);
+ // Check if a container mode change notification is needed
+ notifyContainerModeChange(delete, notifyLocal);
+ // Notify listeners
+ notifyContainerAwareListeners(container, delete);
+
+ /*
+ * This is a quick fix until configuration service becomes the
+ * centralized configuration management place. Here container manager
+ * will remove the startup files for all the bundles that are present in
+ * the container being deleted. Do the cleanup here in Container manger
+ * as do not want to put this temporary code in Configuration manager
+ * yet which is ODL.
+ */
+ if (delete) {
+ // TODO: remove when Config Mgr takes over
+ removeComponentsStartUpfiles(containerName);
+ }
+ }
+
+ private void notifyContainerEntryChangeInternal(String containerName, List<NodeConnector> ncList, UpdateType update, boolean notifyLocal) {
+ logger.trace("Notifying listeners on {} for ports {} in container {}", update, ncList, containerName);
+ // Back-end World: container name forced to lower case
+ String container = containerName.toLowerCase(Locale.ENGLISH);
+ for (NodeConnector nodeConnector : ncList) {
+ // Now signal that the port has been added/removed
+ synchronized (this.iContainerListener) {
+ for (IContainerListener i : this.iContainerListener) {
+ i.nodeConnectorUpdated(container, nodeConnector, update);
+ }
+ }
+ // Check if the Functional Modules need to be notified as well
+ if (notifyLocal) {
+ synchronized (this.iContainerLocalListener) {
+ for (IContainerLocalListener i : this.iContainerLocalListener) {
+ i.nodeConnectorUpdated(container, nodeConnector, update);
+ }
+ }
+ }
+ }
+ }
+
+ private void notifyCFlowChangeInternal(String containerName, List<ContainerFlowConfig> confList, UpdateType update,
+ boolean notifyLocal) {
+ logger.trace("Notifying listeners on {} for flow specs {} in container {}", update, confList, containerName);
+ // Back-end World: container name forced to lower case
+ String container = containerName.toLowerCase(Locale.ENGLISH);
+
+ for (ContainerFlowConfig conf : confList) {
+ for (Match match : conf.getMatches()) {
+ ContainerFlow cFlow = new ContainerFlow(match);
+ synchronized (this.iContainerListener) {
+ for (IContainerListener i : this.iContainerListener) {
+ i.containerFlowUpdated(container, cFlow, cFlow, update);
+ }
+ }
+ // Check if the Functional Modules need to be notified as well
+ if (notifyLocal) {
+ synchronized (this.iContainerLocalListener) {
+ for (IContainerLocalListener i : this.iContainerLocalListener) {
+ i.containerFlowUpdated(container, cFlow, cFlow, update);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Status addRemoveContainerFlow(String containerName, List<ContainerFlowConfig> cFlowConfList, boolean delete) {
+ // Construct action message
+ String action = String.format("Flow spec(s) %s container %s: %s", delete ? "removal from" : "addition to",
+ containerName, cFlowConfList);
+
+ // Presence check
+ ContainerConfig containerConfig = this.containerConfigs.get(containerName);
+ if (containerConfig == null) {
+ String msg = String.format("Container not found: %s", containerName);
+ String error = String.format("Failed to apply %s: (%s)", action, msg);
+ logger.warn(error);
+ return new Status(StatusCode.NOTFOUND, "Container not present");
+ }
+
+ // Validity check, check for overlaps on current container configuration
+ Status status = containerConfig.validateContainerFlowModify(cFlowConfList, delete);
+ if (!status.isSuccess()) {
+ String msg = status.getDescription();
+ String error = String.format("Failed to apply %s: (%s)", action, msg);
+ logger.warn(error);
+ return new Status(StatusCode.BADREQUEST, msg);
+ }
+
+ // Validate the operation in terms to the port sharing with other containers
+ for (ContainerFlowConfig conf : cFlowConfList) {
+ for (Match match : conf.getMatches()) {
+ ContainerFlow cFlow = new ContainerFlow(match);
+ status = validateContainerFlowAddRemoval(containerName, cFlow, delete);
+ if (!status.isSuccess()) {
+ String msg = "Validation failed: " + status.getDescription();
+ String error = String.format("Failed to apply %s: (%s)", action, msg);
+ logger.warn(error);
+ return new Status(StatusCode.BADREQUEST, msg);
+ }
+ }
+ }
+
+ // Update Database
+ status = updateContainerFlow(containerName, cFlowConfList, delete);
+ if (!status.isSuccess()) {
+ String error = String.format("Failed to apply %s: (%s)", action, status.getDescription());
+ logger.error(error);
+ return status;
+ }
+
+ // Update Configuration
+ status = (delete) ? containerConfig.removeContainerFlows(cFlowConfList) : containerConfig
+ .addContainerFlows(cFlowConfList);
+ if (!status.isSuccess()) {
+ String error = String.format("Failed to modify config for %s: (%s)", action, status.getDescription());
+ logger.error(error);
+ // Revert backend changes
+ Status statusRevert = updateContainerFlow(containerName, cFlowConfList, !delete);
+ if (!statusRevert.isSuccess()) {
+ // Unlikely
+ logger.error("Failed to revert changes in database (CRITICAL)");
+ }
+ return status;
+ }
+ // Update cluster cache
+ this.containerConfigs.put(containerName, containerConfig);
+
+ // Notify global and local listeners
+ UpdateType update = (delete) ? UpdateType.REMOVED : UpdateType.ADDED;
+ notifyCFlowChangeInternal(containerName, cFlowConfList, update, true);
+ // Trigger cluster notification
+ containerChangeEvents.put(containerName, new ContainerFlowChangeEvent(cFlowConfList, update));
+
+ return status;
+ }
+
+ private Status addRemoveContainer(ContainerConfig containerConf, boolean delete) {
+ // Construct action message
+ String action = String.format("Container %s", delete ? "removal" : "creation");
+
+ // Valid configuration check
+ Status status = null;
+ String error = (containerConfigs == null) ? String.format("Invalid %s configuration: (null config object)", action)
+ : (!(status = containerConf.validate()).isSuccess()) ? String.format("Invalid %s configuration: (%s)",
+ action, status.getDescription()) : null;
+ if (error != null) {
+ logger.warn(error);
+ return new Status(StatusCode.BADREQUEST, error);
+ }
+
+ // Configuration presence check
+ String containerName = containerConf.getContainerName();
+ if (delete) {
+ if (!containerConfigs.containsKey(containerName)) {
+ String msg = String.format("%s Failed: (Container does not exist: %s)", action, containerName);
+ logger.warn(msg);
+ return new Status(StatusCode.NOTFOUND, msg);
+ }
+ } else {
+ if (containerConfigs.containsKey(containerName)) {
+ String msg = String.format("%s Failed: (Container already exist: %s)", action, containerName);
+ logger.warn(msg);
+ return new Status(StatusCode.CONFLICT, msg);
+ }
+ }
+
+ /*
+ * The proposed container configuration could be a complex one containing
+ * both ports and flow spec. If so, check if it has shared ports with
+ * other existing containers. If that is the case verify flow spec isolation
+ * is in place. No need to check on flow spec validation first. This
+ * would take care of both
+ */
+ if (!delete) {
+ status = validatePortSharing(containerConf);
+ if (!status.isSuccess()) {
+ error = String.format("%s Failed: (%s)", action, status.getDescription());
+ logger.error(error);
+ return status;
+ }
+ }
+
+ // Update Database
+ status = updateContainerDatabase(containerConf, delete);
+
+ // Abort and exit here if back-end database update failed
+ if (!status.isSuccess()) {
+ return status;
+ }
+
+ /*
+ * Update Configuration: This will trigger the notifications on cache
+ * update callback locally and on the other cluster nodes
+ */
+ if (delete) {
+ this.containerConfigs.remove(containerName);
+ } else {
+ this.containerConfigs.put(containerName, containerConf);
+ }
+
+ // Automatically create and populate user and resource groups
+ updateResourceGroups(containerConf, delete);
+
+ // Notify global and local listeners
+ UpdateType update = (delete) ? UpdateType.REMOVED : UpdateType.ADDED;
+ notifyContainerChangeInternal(containerConf, update, true);
+
+ // Trigger cluster notification
+ containerChangeEvents.put(containerName, new ContainerChangeEvent(containerConf, update));
+
+ if (update == UpdateType.ADDED) {
+ if (containerConf.hasFlowSpecs()) {
+ List<ContainerFlowConfig> specList = containerConf.getContainerFlowConfigs();
+ // Notify global and local listeners about flow spec addition
+ notifyCFlowChangeInternal(containerName, specList, update, true);
+
+ // Trigger cluster notification
+ containerChangeEvents.put(containerName, new ContainerFlowChangeEvent(specList, update));
+ }
+
+ if (containerConf.hasNodeConnectors()) {
+ List<NodeConnector> ncList = containerConf.getPortList();
+ // Notify global and local listeners about port(s) addition
+ notifyContainerEntryChangeInternal(containerName, ncList, update, true);
+ // Trigger cluster notification
+ containerChangeEvents.put(containerName, new NodeConnectorsChangeEvent(ncList, update));
+ }
+ }
+
+ if (delete) {
+ clusterServices.removeContainerCaches(containerName);
+ }
+ return status;
+ }
+
+ @Override
+ public Status addContainer(ContainerConfig containerConf) {
+ return addRemoveContainer(containerConf, false);
+ }
+
+ @Override
+ public Status removeContainer(ContainerConfig containerConf) {
+ return addRemoveContainer(containerConf, true);
+ }
+
+ @Override
+ public Status removeContainer(String containerName) {
+ // Construct action message
+ String action = String.format("Container removal: %s", containerName);
+
+ ContainerConfig containerConf = containerConfigs.get(containerName);
+ if (containerConf == null) {
+ String msg = String.format("Container not found");
+ String error = String.format("Failed to apply %s: (%s)", action, msg);
+ logger.warn(error);
+ return new Status(StatusCode.NOTFOUND, msg);
+ }
+ return addRemoveContainer(containerConf, true);
+ }
+
+ @Override
+ public Status addContainerEntry(String containerName, List<String> nodeConnectors) {
+ return addRemoveContainerEntries(containerName, nodeConnectors, false);
+ }
+
+ @Override
+ public Status removeContainerEntry(String containerName, List<String> nodeConnectors) {
+ return addRemoveContainerEntries(containerName, nodeConnectors, true);
+ }
+
+ @Override
+ public Status addContainerFlows(String containerName, List<ContainerFlowConfig> fSpecConf) {
+ return addRemoveContainerFlow(containerName, fSpecConf, false);
+ }
+
+ @Override
+ public Status removeContainerFlows(String containerName, List<ContainerFlowConfig> fSpecConf) {
+ return addRemoveContainerFlow(containerName, fSpecConf, true);
+ }
+
+ @Override
+ public Status removeContainerFlows(String containerName, Set<String> names) {
+ // Construct action message
+ String action = String.format("Flow spec(s) removal from container %s: %s", containerName, names);
+
+ // Presence check
+ ContainerConfig sc = containerConfigs.get(containerName);
+ if (sc == null) {
+ String msg = String.format("Container not found: %s", containerName);
+ String error = String.format("Failed to apply %s: (%s)", action, msg);
+ logger.warn(error);
+ return new Status(StatusCode.NOTFOUND, msg);
+ }
+ List<ContainerFlowConfig> list = sc.getContainerFlowConfigs(names);
+ if (list.isEmpty() || list.size() != names.size()) {
+ String msg = String.format("Cannot find all the specified flow specs");
+ String error = String.format("Failed to apply %s: (%s)", action, msg);
+ logger.warn(error);
+ return new Status(StatusCode.BADREQUEST, msg);
+ }
+ return addRemoveContainerFlow(containerName, list, true);