Sho SHIMIZU | b0a47d4 | 2015-02-19 13:26:30 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2015 Open Networking Laboratory |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | package org.onosproject.net.intent.impl; |
| 17 | |
| 18 | import com.google.common.collect.ImmutableMap; |
| 19 | import org.onosproject.net.flow.FlowRuleOperation; |
| 20 | import org.onosproject.net.flow.FlowRuleOperations; |
| 21 | import org.onosproject.net.flow.FlowRuleOperationsContext; |
| 22 | import org.onosproject.net.intent.Intent; |
| 23 | import org.onosproject.net.intent.IntentData; |
| 24 | import org.onosproject.net.intent.IntentException; |
| 25 | import org.onosproject.net.intent.IntentInstaller; |
| 26 | import org.onosproject.net.intent.IntentStore; |
| 27 | import org.slf4j.Logger; |
| 28 | import org.slf4j.LoggerFactory; |
| 29 | |
| 30 | import java.util.ArrayList; |
| 31 | import java.util.Collection; |
| 32 | import java.util.Collections; |
| 33 | import java.util.Iterator; |
| 34 | import java.util.List; |
| 35 | import java.util.Map; |
| 36 | import java.util.concurrent.ConcurrentHashMap; |
| 37 | import java.util.concurrent.ConcurrentMap; |
| 38 | |
| 39 | import static com.google.common.base.Preconditions.checkState; |
| 40 | import static org.onlab.util.Tools.isNullOrEmpty; |
| 41 | import static org.onosproject.net.intent.IntentState.FAILED; |
| 42 | import static org.onosproject.net.intent.IntentState.INSTALLED; |
| 43 | import static org.onosproject.net.intent.IntentState.WITHDRAWN; |
| 44 | |
| 45 | // TODO: consider a better name |
| 46 | class InstallerRegistry { |
| 47 | |
| 48 | private static final Logger log = LoggerFactory.getLogger(InstallerRegistry.class); |
| 49 | |
| 50 | private final ConcurrentMap<Class<? extends Intent>, |
| 51 | IntentInstaller<? extends Intent>> installers = new ConcurrentHashMap<>(); |
| 52 | /** |
| 53 | * Registers the specified installer for the given installable intent class. |
| 54 | * |
| 55 | * @param cls installable intent class |
| 56 | * @param installer intent installer |
| 57 | * @param <T> the type of installable intent |
| 58 | */ |
| 59 | <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) { |
| 60 | installers.put(cls, installer); |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Unregisters the installer for the given installable intent class. |
| 65 | * |
| 66 | * @param cls installable intent class |
| 67 | * @param <T> the type of installable intent |
| 68 | */ |
| 69 | <T extends Intent> void unregisterInstaller(Class<T> cls) { |
| 70 | installers.remove(cls); |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Returns immutable set of bindings of currently registered intent installers. |
| 75 | * |
| 76 | * @return the set of installer bindings |
| 77 | */ |
| 78 | Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() { |
| 79 | return ImmutableMap.copyOf(installers); |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Returns the corresponding intent installer to the specified installable intent. |
| 84 | * |
| 85 | * @param intent intent |
| 86 | * @param <T> the type of installable intent |
| 87 | * @return intent installer corresponding to the specified installable intent |
| 88 | */ |
| 89 | private <T extends Intent> IntentInstaller<T> getInstaller(T intent) { |
| 90 | @SuppressWarnings("unchecked") |
| 91 | IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass()); |
| 92 | if (installer == null) { |
| 93 | throw new IntentException("no installer for class " + intent.getClass()); |
| 94 | } |
| 95 | return installer; |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Registers an intent installer of the specified intent if an intent installer |
| 100 | * for the intent is not registered. This method traverses the class hierarchy of |
| 101 | * the intent. Once an intent installer for a parent type is found, this method |
| 102 | * registers the found intent installer. |
| 103 | * |
| 104 | * @param intent intent |
| 105 | */ |
| 106 | private void registerSubclassInstallerIfNeeded(Intent intent) { |
| 107 | if (!installers.containsKey(intent.getClass())) { |
| 108 | Class<?> cls = intent.getClass(); |
| 109 | while (cls != Object.class) { |
| 110 | // As long as we're within the Intent class descendants |
| 111 | if (Intent.class.isAssignableFrom(cls)) { |
| 112 | IntentInstaller<?> installer = installers.get(cls); |
| 113 | if (installer != null) { |
| 114 | installers.put(intent.getClass(), installer); |
| 115 | return; |
| 116 | } |
| 117 | } |
| 118 | cls = cls.getSuperclass(); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Generate a {@link FlowRuleOperations} instance from the specified intent data. |
| 125 | * |
| 126 | * @param current intent data stored in the store |
| 127 | * @param pending intent data being processed |
| 128 | * @param store intent store saving the intent state in this method |
| 129 | * @param trackerService objective tracker that is used in this method |
| 130 | * @return flow rule operations |
| 131 | */ |
| 132 | public FlowRuleOperations coordinate(IntentData current, IntentData pending, |
| 133 | IntentStore store, ObjectiveTrackerService trackerService) { |
| 134 | List<Intent> oldInstallables = (current != null) ? current.installables() : null; |
| 135 | List<Intent> newInstallables = pending.installables(); |
| 136 | |
| 137 | checkState(isNullOrEmpty(oldInstallables) || |
| 138 | oldInstallables.size() == newInstallables.size(), |
| 139 | "Old and New Intent must have equivalent installable intents."); |
| 140 | |
| 141 | List<List<Collection<FlowRuleOperation>>> plans = new ArrayList<>(); |
| 142 | for (int i = 0; i < newInstallables.size(); i++) { |
| 143 | Intent newInstallable = newInstallables.get(i); |
| 144 | registerSubclassInstallerIfNeeded(newInstallable); |
| 145 | //TODO consider migrating installers to FlowRuleOperations |
| 146 | /* FIXME |
| 147 | - we need to do another pass on this method about that doesn't |
| 148 | require the length of installables to be equal, and also doesn't |
| 149 | depend on ordering |
| 150 | - we should also reconsider when to start/stop tracking resources |
| 151 | */ |
| 152 | if (isNullOrEmpty(oldInstallables)) { |
| 153 | plans.add(getInstaller(newInstallable).install(newInstallable)); |
| 154 | } else { |
| 155 | Intent oldInstallable = oldInstallables.get(i); |
| 156 | checkState(oldInstallable.getClass().equals(newInstallable.getClass()), |
| 157 | "Installable Intent type mismatch."); |
| 158 | trackerService.removeTrackedResources(pending.key(), oldInstallable.resources()); |
| 159 | plans.add(getInstaller(newInstallable).replace(oldInstallable, newInstallable)); |
| 160 | } |
| 161 | trackerService.addTrackedResources(pending.key(), newInstallable.resources()); |
| 162 | // } catch (IntentException e) { |
| 163 | // log.warn("Unable to update intent {} due to:", oldIntent.id(), e); |
| 164 | // //FIXME... we failed. need to uninstall (if same) or revert (if different) |
| 165 | // trackerService.removeTrackedResources(newIntent.id(), newInstallable.resources()); |
| 166 | // exception = e; |
| 167 | // batches = uninstallIntent(oldIntent, oldInstallables); |
| 168 | // } |
| 169 | } |
| 170 | |
| 171 | return merge(plans).build(new FlowRuleOperationsContext() { // TODO move this out |
| 172 | @Override |
| 173 | public void onSuccess(FlowRuleOperations ops) { |
| 174 | log.debug("Completed installing: {}", pending.key()); |
| 175 | pending.setState(INSTALLED); |
| 176 | store.write(pending); |
| 177 | } |
| 178 | |
| 179 | @Override |
| 180 | public void onError(FlowRuleOperations ops) { |
| 181 | log.warn("Failed installation: {} {} on {}", pending.key(), |
| 182 | pending.intent(), ops); |
| 183 | //TODO store.write(pending.setState(BROKEN)); |
| 184 | pending.setState(FAILED); |
| 185 | store.write(pending); |
| 186 | } |
| 187 | }); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Generate a {@link FlowRuleOperations} instance from the specified intent data. |
| 192 | * |
| 193 | * @param current intent data stored in the store |
| 194 | * @param pending intent date being processed |
| 195 | * @param store intent store saving the intent state in this method |
| 196 | * @param trackerService objective tracker that is used in this method |
| 197 | * @return flow rule operations |
| 198 | */ |
| 199 | FlowRuleOperations uninstallCoordinate(IntentData current, IntentData pending, |
| 200 | IntentStore store, ObjectiveTrackerService trackerService) { |
| 201 | List<Intent> installables = current.installables(); |
| 202 | List<List<Collection<FlowRuleOperation>>> plans = new ArrayList<>(); |
| 203 | for (Intent installable : installables) { |
| 204 | plans.add(getInstaller(installable).uninstall(installable)); |
| 205 | trackerService.removeTrackedResources(pending.key(), installable.resources()); |
| 206 | } |
| 207 | |
| 208 | return merge(plans).build(new FlowRuleOperationsContext() { |
| 209 | @Override |
| 210 | public void onSuccess(FlowRuleOperations ops) { |
| 211 | log.debug("Completed withdrawing: {}", pending.key()); |
| 212 | pending.setState(WITHDRAWN); |
| 213 | pending.setInstallables(Collections.emptyList()); |
| 214 | store.write(pending); |
| 215 | } |
| 216 | |
| 217 | @Override |
| 218 | public void onError(FlowRuleOperations ops) { |
| 219 | log.warn("Failed withdraw: {}", pending.key()); |
| 220 | pending.setState(FAILED); |
| 221 | store.write(pending); |
| 222 | } |
| 223 | }); |
| 224 | } |
| 225 | |
| 226 | |
| 227 | // TODO needs tests... or maybe it's just perfect |
| 228 | private FlowRuleOperations.Builder merge(List<List<Collection<FlowRuleOperation>>> plans) { |
| 229 | FlowRuleOperations.Builder builder = FlowRuleOperations.builder(); |
| 230 | // Build a batch one stage at a time |
| 231 | for (int stageNumber = 0;; stageNumber++) { |
| 232 | // Get the sub-stage from each plan (List<Set<FlowRuleOperation>) |
| 233 | for (Iterator<List<Collection<FlowRuleOperation>>> itr = plans.iterator(); itr.hasNext();) { |
| 234 | List<Collection<FlowRuleOperation>> plan = itr.next(); |
| 235 | if (plan.size() <= stageNumber) { |
| 236 | // we have consumed all stages from this plan, so remove it |
| 237 | itr.remove(); |
| 238 | continue; |
| 239 | } |
| 240 | // write operations from this sub-stage into the builder |
| 241 | Collection<FlowRuleOperation> stage = plan.get(stageNumber); |
| 242 | for (FlowRuleOperation entry : stage) { |
| 243 | builder.operation(entry); |
| 244 | } |
| 245 | } |
| 246 | // we are done with the stage, start the next one... |
| 247 | if (plans.isEmpty()) { |
| 248 | break; // we don't need to start a new stage, we are done. |
| 249 | } |
| 250 | builder.newStage(); |
| 251 | } |
| 252 | return builder; |
| 253 | } |
| 254 | } |