blob: 12af7bcc7db5b48c4e50195f7127812694895466 [file] [log] [blame]
/*
* 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.incubator.store.virtual.impl;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onosproject.incubator.net.virtual.NetworkId;
import org.onosproject.incubator.net.virtual.VirtualNetworkIntentStore;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentData;
import org.onosproject.net.intent.IntentEvent;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.IntentStoreDelegate;
import org.onosproject.net.intent.Key;
import org.onosproject.store.Timestamp;
import org.slf4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.intent.IntentState.PURGE_REQ;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Simple single-instance implementation of the intent store for virtual networks.
*/
@Component(immediate = true)
@Service
public class SimpleVirtualIntentStore
extends AbstractVirtualStore<IntentEvent, IntentStoreDelegate>
implements VirtualNetworkIntentStore {
private final Logger log = getLogger(getClass());
private final Map<NetworkId, Map<Key, IntentData>> currentByNetwork =
Maps.newConcurrentMap();
private final Map<NetworkId, Map<Key, IntentData>> pendingByNetwork =
Maps.newConcurrentMap();
@Activate
public void activate() {
log.info("Started");
}
@Deactivate
public void deactivate() {
log.info("Stopped");
}
@Override
public long getIntentCount(NetworkId networkId) {
return getCurrentMap(networkId).size();
}
@Override
public Iterable<Intent> getIntents(NetworkId networkId) {
return getCurrentMap(networkId).values().stream()
.map(IntentData::intent)
.collect(Collectors.toList());
}
@Override
public Iterable<IntentData> getIntentData(NetworkId networkId,
boolean localOnly, long olderThan) {
if (localOnly || olderThan > 0) {
long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
final SystemClockTimestamp time = new SystemClockTimestamp(older);
return getCurrentMap(networkId).values().stream()
.filter(data -> data.version().isOlderThan(time) &&
(!localOnly || isMaster(networkId, data.key())))
.collect(Collectors.toList());
}
return Lists.newArrayList(getCurrentMap(networkId).values());
}
@Override
public IntentState getIntentState(NetworkId networkId, Key intentKey) {
IntentData data = getCurrentMap(networkId).get(intentKey);
return (data != null) ? data.state() : null;
}
@Override
public List<Intent> getInstallableIntents(NetworkId networkId, Key intentKey) {
IntentData data = getCurrentMap(networkId).get(intentKey);
if (data != null) {
return data.installables();
}
return null;
}
@Override
public void write(NetworkId networkId, IntentData newData) {
checkNotNull(newData);
synchronized (this) {
// TODO this could be refactored/cleaned up
IntentData currentData = getCurrentMap(networkId).get(newData.key());
IntentData pendingData = getPendingMap(networkId).get(newData.key());
if (IntentData.isUpdateAcceptable(currentData, newData)) {
if (pendingData != null) {
if (pendingData.state() == PURGE_REQ) {
getCurrentMap(networkId).remove(newData.key(), newData);
} else {
getCurrentMap(networkId).put(newData.key(), IntentData.copy(newData));
}
if (pendingData.version().compareTo(newData.version()) <= 0) {
// pendingData version is less than or equal to newData's
// Note: a new update for this key could be pending (it's version will be greater)
getPendingMap(networkId).remove(newData.key());
}
}
IntentEvent.getEvent(newData).ifPresent(e -> notifyDelegate(networkId, e));
}
}
}
@Override
public void batchWrite(NetworkId networkId, Iterable<IntentData> updates) {
for (IntentData data : updates) {
write(networkId, data);
}
}
@Override
public Intent getIntent(NetworkId networkId, Key key) {
IntentData data = getCurrentMap(networkId).get(key);
return (data != null) ? data.intent() : null;
}
@Override
public IntentData getIntentData(NetworkId networkId, Key key) {
IntentData currentData = getCurrentMap(networkId).get(key);
if (currentData == null) {
return null;
}
return IntentData.copy(currentData);
}
@Override
public void addPending(NetworkId networkId, IntentData data) {
if (data.version() == null) { // recompiled intents will already have a version
data = new IntentData(data.intent(), data.state(), new SystemClockTimestamp());
}
synchronized (this) {
IntentData existingData = getPendingMap(networkId).get(data.key());
if (existingData == null ||
// existing version is strictly less than data's version
// Note: if they are equal, we already have the update
// TODO maybe we should still make this <= to be safe?
existingData.version().compareTo(data.version()) < 0) {
getPendingMap(networkId).put(data.key(), data);
checkNotNull(delegateMap.get(networkId), "Store delegate is not set")
.process(IntentData.copy(data));
IntentEvent.getEvent(data).ifPresent(e -> notifyDelegate(networkId, e));
} else {
log.debug("IntentData {} is older than existing: {}",
data, existingData);
}
//TODO consider also checking the current map at this point
}
}
@Override
public boolean isMaster(NetworkId networkId, Key intentKey) {
return true;
}
@Override
public Iterable<Intent> getPending(NetworkId networkId) {
return getPendingMap(networkId).values().stream()
.map(IntentData::intent)
.collect(Collectors.toList());
}
@Override
public Iterable<IntentData> getPendingData(NetworkId networkId) {
return Lists.newArrayList(getPendingMap(networkId).values());
}
@Override
public IntentData getPendingData(NetworkId networkId, Key intentKey) {
return getPendingMap(networkId).get(intentKey);
}
@Override
public Iterable<IntentData> getPendingData(NetworkId networkId,
boolean localOnly, long olderThan) {
long older = System.nanoTime() - olderThan * 1_000_000; //convert ms to ns
final SystemClockTimestamp time = new SystemClockTimestamp(older);
return getPendingMap(networkId).values().stream()
.filter(data -> data.version().isOlderThan(time) &&
(!localOnly || isMaster(networkId, data.key())))
.collect(Collectors.toList());
}
/**
* Returns the current intent map for a specific virtual network.
*
* @param networkId a virtual network identifier
* @return the current map for the requested virtual network
*/
private Map<Key, IntentData> getCurrentMap(NetworkId networkId) {
currentByNetwork.computeIfAbsent(networkId,
n -> Maps.newConcurrentMap());
return currentByNetwork.get(networkId);
}
/**
* Returns the pending intent map for a specific virtual network.
*
* @param networkId a virtual network identifier
* @return the pending intent map for the requested virtual network
*/
private Map<Key, IntentData> getPendingMap(NetworkId networkId) {
pendingByNetwork.computeIfAbsent(networkId,
n -> Maps.newConcurrentMap());
return pendingByNetwork.get(networkId);
}
public class SystemClockTimestamp implements Timestamp {
private final long nanoTimestamp;
public SystemClockTimestamp() {
nanoTimestamp = System.nanoTime();
}
public SystemClockTimestamp(long timestamp) {
nanoTimestamp = timestamp;
}
@Override
public int compareTo(Timestamp o) {
checkArgument(o instanceof SystemClockTimestamp,
"Must be SystemClockTimestamp", o);
SystemClockTimestamp that = (SystemClockTimestamp) o;
return ComparisonChain.start()
.compare(this.nanoTimestamp, that.nanoTimestamp)
.result();
}
}
}