| /* |
| * Copyright 2015-present Open Networking Foundation |
| * |
| * 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; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.base.MoreObjects; |
| import com.google.common.collect.ImmutableList; |
| import org.onosproject.cluster.NodeId; |
| import org.onosproject.store.Timestamp; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.onosproject.net.intent.IntentState.*; |
| |
| /** |
| * A wrapper class that contains an intents, its state, and other metadata for |
| * internal use. |
| */ |
| @Beta |
| public class IntentData { //FIXME need to make this "immutable" |
| // manager should be able to mutate a local copy while processing |
| |
| private static final Logger log = LoggerFactory.getLogger(IntentData.class); |
| |
| private final Intent intent; |
| |
| private final IntentState request; //TODO perhaps we want a full fledged object for requests |
| private IntentState state; |
| /** |
| * Intent's user request version. |
| * <p> |
| * version is assigned when an Intent was picked up by batch worker |
| * and added to pending map. |
| */ |
| private final Timestamp version; |
| /** |
| * Intent's internal state version. |
| */ |
| // ~= mutation count |
| private int internalStateVersion; |
| private NodeId origin; |
| private int errorCount; |
| |
| private List<Intent> installables; |
| |
| /** |
| * Creates IntentData for Intent submit request. |
| * |
| * @param intent to request |
| * @return IntentData |
| */ |
| public static IntentData submit(Intent intent) { |
| return new IntentData(checkNotNull(intent), INSTALL_REQ); |
| } |
| |
| /** |
| * Creates IntentData for Intent withdraw request. |
| * |
| * @param intent to request |
| * @return IntentData |
| */ |
| public static IntentData withdraw(Intent intent) { |
| return new IntentData(checkNotNull(intent), WITHDRAW_REQ); |
| } |
| |
| /** |
| * Creates IntentData for Intent purge request. |
| * |
| * @param intent to request |
| * @return IntentData |
| */ |
| public static IntentData purge(Intent intent) { |
| return new IntentData(checkNotNull(intent), PURGE_REQ); |
| } |
| |
| /** |
| * Creates updated IntentData after assigning task to a node. |
| * |
| * @param data IntentData to update work assignment |
| * @param timestamp to assign to current request |
| * @param node node which was assigned to handle this request (local node id) |
| * @return updated IntentData object |
| */ |
| public static IntentData assign(IntentData data, Timestamp timestamp, NodeId node) { |
| IntentData assigned = new IntentData(data, checkNotNull(timestamp)); |
| assigned.origin = checkNotNull(node); |
| assigned.internalStateVersion++; |
| return assigned; |
| } |
| |
| /** |
| * Creates a copy of given IntentData. |
| * |
| * @param data intent data to copy |
| * @return copy |
| */ |
| public static IntentData copy(IntentData data) { |
| return new IntentData(data); |
| } |
| |
| /** |
| * Creates a copy of given IntentData, and update request version. |
| * |
| * @param data intent data to copy |
| * @param reqVersion request version to be updated |
| * @return copy |
| */ |
| public static IntentData copy(IntentData data, Timestamp reqVersion) { |
| return new IntentData(data, checkNotNull(reqVersion)); |
| } |
| |
| /** |
| * Create a copy of IntentData in next state. |
| * |
| * @param data intent data to copy |
| * @param nextState to transition to |
| * @return next state |
| */ |
| public static IntentData nextState(IntentData data, IntentState nextState) { |
| IntentData next = new IntentData(data); |
| // TODO state machine sanity check |
| next.setState(checkNotNull(nextState)); |
| return next; |
| } |
| |
| // TODO Should this be method of it's own, or |
| // should nextState(*, CORRUPT) call increment error count? |
| /** |
| * Creates a copy of IntentData in corrupt state, |
| * incrementing error count. |
| * |
| * @param data intent data to copy |
| * @return next state |
| */ |
| public static IntentData corrupt(IntentData data) { |
| IntentData next = new IntentData(data); |
| next.setState(IntentState.CORRUPT); |
| next.incrementErrorCount(); |
| return next; |
| } |
| |
| /** |
| * Creates updated IntentData with compilation result. |
| * |
| * @param data IntentData to update |
| * @param installables compilation result |
| * @return updated IntentData object |
| */ |
| public static IntentData compiled(IntentData data, List<Intent> installables) { |
| return new IntentData(data, checkNotNull(installables)); |
| } |
| |
| |
| /** |
| * Constructor for creating IntentData representing user request. |
| * |
| * @param intent this metadata references |
| * @param reqState request state |
| */ |
| private IntentData(Intent intent, |
| IntentState reqState) { |
| this.intent = checkNotNull(intent); |
| this.request = checkNotNull(reqState); |
| this.version = null; |
| this.state = reqState; |
| this.installables = ImmutableList.of(); |
| } |
| |
| /** |
| * Constructor for creating updated IntentData. |
| * |
| * @param original IntentData to copy from |
| * @param newReqVersion new request version |
| */ |
| private IntentData(IntentData original, Timestamp newReqVersion) { |
| intent = original.intent; |
| state = original.state; |
| request = original.request; |
| version = newReqVersion; |
| internalStateVersion = original.internalStateVersion; |
| origin = original.origin; |
| installables = original.installables; |
| errorCount = original.errorCount; |
| } |
| |
| /** |
| * Creates a new intent data object. |
| * |
| * @param intent intent this metadata references |
| * @param state intent state |
| * @param version version of the intent for this key |
| * |
| * @deprecated in 1.11.0 |
| */ |
| // used to create initial IntentData (version = null) |
| @Deprecated |
| public IntentData(Intent intent, IntentState state, Timestamp version) { |
| checkNotNull(intent); |
| checkNotNull(state); |
| |
| this.intent = intent; |
| this.state = state; |
| this.request = state; |
| this.version = version; |
| } |
| |
| /** |
| * Creates a new intent data object. |
| * |
| * @param intent intent this metadata references |
| * @param state intent state |
| * @param version version of the intent for this key |
| * @param origin ID of the node where the data was originally created |
| * |
| * @deprecated in 1.11.0 |
| */ |
| // No longer used in the code base anywhere |
| @Deprecated |
| public IntentData(Intent intent, IntentState state, Timestamp version, NodeId origin) { |
| checkNotNull(intent); |
| checkNotNull(state); |
| checkNotNull(version); |
| checkNotNull(origin); |
| |
| this.intent = intent; |
| this.state = state; |
| this.request = state; |
| this.version = version; |
| this.origin = origin; |
| } |
| |
| /** |
| * Creates a new intent data object. |
| * |
| * @param intent intent this metadata references |
| * @param state intent state |
| * @param request intent request |
| * @param version version of the intent for this key |
| * @param origin ID of the node where the data was originally created |
| * |
| * @deprecated in 1.11.0 |
| */ |
| // No longer used in the code base anywhere |
| // was used when IntentData is picked up by some of the node and was assigned with a version |
| @Deprecated |
| public IntentData(Intent intent, IntentState state, IntentState request, Timestamp version, NodeId origin) { |
| checkNotNull(intent); |
| checkNotNull(state); |
| checkNotNull(request); |
| checkNotNull(version); |
| checkNotNull(origin); |
| |
| this.intent = intent; |
| this.state = state; |
| this.request = request; |
| this.version = version; |
| this.origin = origin; |
| } |
| |
| /** |
| * Copy constructor. |
| * |
| * @param intentData intent data to copy |
| * |
| * @deprecated in 1.11.0 use {@link #copy(IntentData)} instead |
| */ |
| // used to create a defensive copy |
| // to be made private |
| @Deprecated |
| public IntentData(IntentData intentData) { |
| checkNotNull(intentData); |
| |
| intent = intentData.intent; |
| state = intentData.state; |
| request = intentData.request; |
| version = intentData.version; |
| internalStateVersion = intentData.internalStateVersion; |
| origin = intentData.origin; |
| installables = intentData.installables; |
| errorCount = intentData.errorCount; |
| } |
| |
| /** |
| * Create a new instance based on the original instance with new installables. |
| * |
| * @param original original data |
| * @param installables new installable intents to set |
| * |
| * @deprecated in 1.11.0 use {@link #compiled(IntentData, List)} instead |
| */ |
| // used to create an instance who reached stable state |
| // note that state is mutable field, so it gets altered else where |
| // (probably that design is mother of all intent bugs) |
| @Deprecated |
| public IntentData(IntentData original, List<Intent> installables) { |
| this(original); |
| this.internalStateVersion++; |
| |
| this.installables = checkNotNull(installables).isEmpty() ? |
| ImmutableList.of() : ImmutableList.copyOf(installables); |
| } |
| |
| // kryo constructor |
| protected IntentData() { |
| intent = null; |
| request = null; |
| version = null; |
| } |
| |
| /** |
| * Returns the intent this metadata references. |
| * |
| * @return intent |
| */ |
| public Intent intent() { |
| return intent; |
| } |
| |
| /** |
| * Returns the state of the intent. |
| * |
| * @return intent state |
| */ |
| public IntentState state() { |
| return state; |
| } |
| |
| public IntentState request() { |
| return request; |
| } |
| |
| /** |
| * Returns the intent key. |
| * |
| * @return intent key |
| */ |
| public Key key() { |
| return intent.key(); |
| } |
| |
| /** |
| * Returns the request version of the intent for this key. |
| * |
| * @return intent version |
| */ |
| public Timestamp version() { |
| return version; |
| } |
| |
| // had to be made public for the store timestamp provider |
| public int internalStateVersion() { |
| return internalStateVersion; |
| } |
| |
| /** |
| * Returns the origin node that created this intent. |
| * |
| * @return origin node ID |
| */ |
| public NodeId origin() { |
| return origin; |
| } |
| |
| /** |
| * Updates the state of the intent to the given new state. |
| * |
| * @param newState new state of the intent |
| */ |
| public void setState(IntentState newState) { |
| this.internalStateVersion++; |
| this.state = newState; |
| } |
| |
| /** |
| * Increments the error count for this intent. |
| */ |
| public void incrementErrorCount() { |
| errorCount++; |
| } |
| |
| /** |
| * Sets the error count for this intent. |
| * |
| * @param newCount new count |
| */ |
| public void setErrorCount(int newCount) { |
| errorCount = newCount; |
| } |
| |
| /** |
| * Returns the number of times that this intent has encountered an error |
| * during installation or withdrawal. |
| * |
| * @return error count |
| */ |
| public int errorCount() { |
| return errorCount; |
| } |
| |
| /** |
| * Returns the installables associated with this intent. |
| * |
| * @return list of installable intents |
| */ |
| public List<Intent> installables() { |
| return installables != null ? installables : Collections.emptyList(); |
| } |
| |
| /** |
| * Determines whether an intent data update is allowed. The update must |
| * either have a higher version than the current data, or the state |
| * transition between two updates of the same version must be sane. |
| * |
| * @param currentData existing intent data in the store |
| * @param newData new intent data update proposal |
| * @return true if we can apply the update, otherwise false |
| */ |
| public static boolean isUpdateAcceptable(IntentData currentData, IntentData newData) { |
| |
| if (currentData == null) { |
| return true; |
| } else if (currentData.version().isOlderThan(newData.version())) { |
| return true; |
| } else if (currentData.version().isNewerThan(newData.version())) { |
| log.trace("{} update not acceptable: current is newer", newData.key()); |
| return false; |
| } |
| |
| assert (currentData.version().equals(newData.version())); |
| if (currentData.internalStateVersion >= newData.internalStateVersion) { |
| log.trace("{} update not acceptable: current is newer internally", newData.key()); |
| return false; |
| } |
| |
| // current and new data versions are the same |
| IntentState currentState = currentData.state(); |
| IntentState newState = newData.state(); |
| |
| switch (newState) { |
| case INSTALLING: |
| if (currentState == INSTALLING) { |
| log.trace("{} update not acceptable: no-op INSTALLING", newData.key()); |
| return false; |
| } |
| // FALLTHROUGH |
| case REALLOCATING: |
| if (currentState == REALLOCATING) { |
| log.trace("{} update not acceptable: no-op REALLOCATING", newData.key()); |
| return false; |
| } else if (currentState == INSTALLED) { |
| return true; |
| } |
| // FALLTHROUGH |
| case INSTALLED: |
| if (currentState == INSTALLED) { |
| return false; |
| } else if (currentState == WITHDRAWING || currentState == WITHDRAWN |
| || currentState == PURGE_REQ) { |
| log.warn("Invalid state transition from {} to {} for intent {}", |
| currentState, newState, newData.key()); |
| return false; |
| } |
| return true; |
| |
| case WITHDRAWING: |
| if (currentState == WITHDRAWING) { |
| log.trace("{} update not acceptable: no-op WITHDRAWING", newData.key()); |
| return false; |
| } |
| // FALLTHROUGH |
| case WITHDRAWN: |
| if (currentState == WITHDRAWN) { |
| log.trace("{} update not acceptable: no-op WITHDRAWN", newData.key()); |
| return false; |
| } else if (currentState == INSTALLING || currentState == INSTALLED |
| || currentState == PURGE_REQ) { |
| log.warn("Invalid state transition from {} to {} for intent {}", |
| currentState, newState, newData.key()); |
| return false; |
| } |
| return true; |
| |
| case FAILED: |
| if (currentState == FAILED) { |
| log.trace("{} update not acceptable: no-op FAILED", newData.key()); |
| return false; |
| } |
| return true; |
| |
| case CORRUPT: |
| if (currentState == CORRUPT) { |
| log.trace("{} update not acceptable: no-op CORRUPT", newData.key()); |
| return false; |
| } |
| return true; |
| |
| case PURGE_REQ: |
| // TODO we should enforce that only WITHDRAWN intents can be purged |
| return true; |
| |
| case COMPILING: |
| case RECOMPILING: |
| case INSTALL_REQ: |
| case WITHDRAW_REQ: |
| default: |
| log.warn("Invalid state {} for intent {}", newState, newData.key()); |
| return false; |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(intent, version); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| final IntentData other = (IntentData) obj; |
| return Objects.equals(this.intent, other.intent) |
| && Objects.equals(this.version, other.version); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(getClass()) |
| .add("key", key()) |
| .add("state", state()) |
| .add("version", version()) |
| .add("internalStateVersion", internalStateVersion) |
| .add("intent", intent()) |
| .add("origin", origin()) |
| .add("installables", installables()) |
| .toString(); |
| } |
| |
| } |