/* * Copyright 2015-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; 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 final Timestamp version; private NodeId origin; private int errorCount; private List<Intent> 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) { 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 */ 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 */ 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 */ 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; } /** * Create a new instance based on the original instance with new installables. * * @param original original data * @param installables new installable intents to set */ public IntentData(IntentData original, List<Intent> installables) { this(original); this.installables = checkNotNull(installables).isEmpty() ? Collections.emptyList() : 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 version of the intent for this key. * * @return intent version */ public Timestamp version() { return version; } /** * 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; } /** * 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())) { 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(); } }