/* * Copyright 2015 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; 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; private Timestamp version; private NodeId origin; private int errorCount; private List installables; /** * 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 */ public IntentData(Intent intent, IntentState state, Timestamp version) { this.intent = intent; this.state = state; this.request = state; this.version = version; } /** * Copy constructor. * * @param intentData intent data to copy */ public IntentData(IntentData intentData) { checkNotNull(intentData); intent = intentData.intent; state = intentData.state; request = intentData.request; version = intentData.version; origin = intentData.origin; installables = intentData.installables; errorCount = intentData.errorCount; } // kryo constructor protected IntentData() { intent = null; request = 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 version of the intent for this key. * * @return intent version */ public Timestamp version() { return version; } /** * Sets the origin, which is the node that created the intent. * * @param origin origin instance */ public void setOrigin(NodeId origin) { this.origin = origin; } /** * 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.state = newState; } /** * Sets the version for this intent data. *

* The store should call this method only once when the IntentData is * first passed into the pending map. Ideally, an IntentData is timestamped * on the same thread that the called used to submit the intents. *

* * @param version the version/timestamp for this intent data */ public void setVersion(Timestamp version) { this.version = version; } /** * 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; } /** * Sets the intent installables to the given list of intents. * * @param installables list of installables for this intent */ public void setInstallables(List installables) { this.installables = ImmutableList.copyOf(installables); } /** * Returns the installables associated with this intent. * * @return list of installable intents */ public List 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())) { 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) { return false; } // 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) { return false; } // FALLTHROUGH case WITHDRAWN: if (currentState == WITHDRAWN) { 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) { return false; } return true; case CORRUPT: if (currentState == CORRUPT) { 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("intent", intent()) .add("origin", origin()) .add("installables", installables()) .toString(); } }