/* * Copyright 2017-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.net.intent.impl; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.onosproject.net.intent.Intent; import org.onosproject.net.intent.IntentData; import org.onosproject.net.intent.IntentInstallationContext; import org.onosproject.net.intent.IntentOperationContext; import org.onosproject.net.intent.IntentInstaller; import org.onosproject.net.intent.IntentStore; import org.slf4j.Logger; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import static org.onosproject.net.intent.IntentState.*; import static org.slf4j.LoggerFactory.getLogger; /** * Implementation of IntentInstallCoordinator. */ public class InstallCoordinator { private static final String INSTALLER_NOT_FOUND = "Intent installer not found, Intent: {}"; private final Logger log = getLogger(IntentManager.class); private InstallerRegistry installerRegistry; private IntentStore intentStore; /** * Creates an InstallCoordinator. * * @param installerRegistry the installer registry * @param intentStore the Intent store */ public InstallCoordinator(InstallerRegistry installerRegistry, IntentStore intentStore) { this.installerRegistry = installerRegistry; this.intentStore = intentStore; } /** * Applies Intent data to be uninstalled and to be installed. * * @param toUninstall Intent data to be uninstalled * @param toInstall Intent data to be installed */ public void installIntents(Optional<IntentData> toUninstall, Optional<IntentData> toInstall) { // If no any Intents to be uninstalled or installed, ignore it. if (!toUninstall.isPresent() && !toInstall.isPresent()) { return; } // Classify installable Intents to different installers. ArrayListMultimap<IntentInstaller, Intent> uninstallInstallers; ArrayListMultimap<IntentInstaller, Intent> installInstallers; Set<IntentInstaller> allInstallers = Sets.newHashSet(); if (toUninstall.isPresent()) { uninstallInstallers = getInstallers(toUninstall.get()); allInstallers.addAll(uninstallInstallers.keySet()); } else { uninstallInstallers = ArrayListMultimap.create(); } if (toInstall.isPresent()) { installInstallers = getInstallers(toInstall.get()); allInstallers.addAll(installInstallers.keySet()); } else { installInstallers = ArrayListMultimap.create(); } // Generates an installation context for the high level Intent. IntentInstallationContext installationContext = new IntentInstallationContext(toUninstall.orElse(null), toInstall.orElse(null)); //Generates different operation context for different installable Intents. Map<IntentInstaller, IntentOperationContext> contexts = Maps.newHashMap(); allInstallers.forEach(installer -> { List<Intent> intentsToUninstall = uninstallInstallers.get(installer); List<Intent> intentsToInstall = installInstallers.get(installer); // Connect context to high level installation context IntentOperationContext context = new IntentOperationContext(intentsToUninstall, intentsToInstall, installationContext); installationContext.addPendingContext(context); contexts.put(installer, context); }); // Apply contexts to installers contexts.forEach((installer, context) -> { installer.apply(context); }); } /** * Generates a mapping for installable Intents to installers. * * @param intentData the Intent data which contains installable Intents * @return the mapping for installable Intents to installers */ private ArrayListMultimap<IntentInstaller, Intent> getInstallers(IntentData intentData) { ArrayListMultimap<IntentInstaller, Intent> intentInstallers = ArrayListMultimap.create(); intentData.installables().forEach(intent -> { IntentInstaller installer = installerRegistry.getInstaller(intent.getClass()); if (installer != null) { intentInstallers.put(installer, intent); } else { log.warn(INSTALLER_NOT_FOUND, intent); } }); return intentInstallers; } /** * Handles success operation context. * * @param context the operation context */ public void success(IntentOperationContext context) { IntentInstallationContext intentInstallationContext = context.intentInstallationContext(); intentInstallationContext.removePendingContext(context); if (intentInstallationContext.isPendingContextsEmpty()) { finish(intentInstallationContext); } } /** * Handles failed operation context. * * @param context the operation context */ public void failed(IntentOperationContext context) { IntentInstallationContext intentInstallationContext = context.intentInstallationContext(); intentInstallationContext.addErrorContext(context); intentInstallationContext.removePendingContext(context); if (intentInstallationContext.isPendingContextsEmpty()) { finish(intentInstallationContext); } } /** * Completed the installation context and update the Intent store. * * @param intentInstallationContext the installation context */ private void finish(IntentInstallationContext intentInstallationContext) { Set<IntentOperationContext> errCtxs = intentInstallationContext.errorContexts(); Optional<IntentData> toUninstall = intentInstallationContext.toUninstall(); Optional<IntentData> toInstall = intentInstallationContext.toInstall(); // Intent install success if (errCtxs == null || errCtxs.isEmpty()) { if (toInstall.isPresent()) { IntentData installData = toInstall.get(); log.debug("Completed installing: {}", installData.key()); installData = new IntentData(installData, installData.installables()); installData.setState(INSTALLED); intentStore.write(installData); } else if (toUninstall.isPresent()) { IntentData uninstallData = toUninstall.get(); uninstallData = new IntentData(uninstallData, Collections.emptyList()); log.debug("Completed withdrawing: {}", uninstallData.key()); switch (uninstallData.request()) { case INSTALL_REQ: log.warn("{} was requested to withdraw during installation?", uninstallData.intent()); uninstallData.setState(FAILED); break; case WITHDRAW_REQ: default: //TODO "default" case should not happen uninstallData.setState(WITHDRAWN); break; } // Intent has been withdrawn; we can clear the installables intentStore.write(uninstallData); } } else { // if toInstall was cause of error, then recompile (manage/increment counter, when exceeded -> CORRUPT) if (toInstall.isPresent()) { IntentData installData = toInstall.get(); installData.setState(CORRUPT); installData.incrementErrorCount(); intentStore.write(installData); } // if toUninstall was cause of error, then CORRUPT (another job will clean this up) if (toUninstall.isPresent()) { IntentData uninstallData = toUninstall.get(); uninstallData.setState(CORRUPT); uninstallData.incrementErrorCount(); intentStore.write(uninstallData); } } } }