Sho SHIMIZU

Separate ResourceStore into stores for discrete and continuous type

This is a preliminary work for ONOS-4281.

Change-Id: Ifed9c761eb16f6a249a9d069948edc7421301617
/*
* Copyright 2016-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.store.resource.impl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.onlab.util.GuavaCollectors;
import org.onlab.util.Tools;
import org.onosproject.net.resource.ContinuousResource;
import org.onosproject.net.resource.ContinuousResourceId;
import org.onosproject.net.resource.DiscreteResourceId;
import org.onosproject.net.resource.Resource;
import org.onosproject.net.resource.ResourceAllocation;
import org.onosproject.net.resource.ResourceConsumer;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.ConsistentMapException;
import org.onosproject.store.service.StorageService;
import org.onosproject.store.service.TransactionContext;
import org.onosproject.store.service.Versioned;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.ContinuousResourceAllocation;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.MAX_RETRIES;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.RETRY_DELAY;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.SERIALIZER;
import static org.onosproject.store.resource.impl.ResourceStoreUtil.hasEnoughResource;
class ConsistentContinuousResourceStore {
private ConsistentMap<ContinuousResourceId, ContinuousResourceAllocation> consumers;
private ConsistentMap<DiscreteResourceId, Set<ContinuousResource>> childMap;
ConsistentContinuousResourceStore(StorageService service) {
this.consumers = service.<ContinuousResourceId, ContinuousResourceAllocation>consistentMapBuilder()
.withName(MapNames.CONTINUOUS_CONSUMER_MAP)
.withSerializer(SERIALIZER)
.build();
this.childMap = service.<DiscreteResourceId, Set<ContinuousResource>>consistentMapBuilder()
.withName(MapNames.CONTINUOUS_CHILD_MAP)
.withSerializer(SERIALIZER)
.build();
Tools.retryable(() -> childMap.put(Resource.ROOT.id(), new LinkedHashSet<>()),
ConsistentMapException.class, MAX_RETRIES, RETRY_DELAY);
}
TransactionalContinuousResourceStore transactional(TransactionContext tx) {
return new TransactionalContinuousResourceStore(tx);
}
// computational complexity: O(n) where n is the number of the existing allocations for the resource
List<ResourceAllocation> getResourceAllocations(ContinuousResourceId resource) {
Versioned<ContinuousResourceAllocation> allocations = consumers.get(resource);
if (allocations == null) {
return ImmutableList.of();
}
return allocations.value().allocations().stream()
.filter(x -> x.resource().id().equals(resource))
.collect(GuavaCollectors.toImmutableList());
}
Set<ContinuousResource> getChildResources(DiscreteResourceId parent) {
Versioned<Set<ContinuousResource>> children = childMap.get(parent);
if (children == null) {
return ImmutableSet.of();
}
return children.value();
}
public boolean isAvailable(ContinuousResource resource) {
// check if it's registered or not.
Versioned<Set<ContinuousResource>> children = childMap.get(resource.parent().get().id());
if (children == null) {
return false;
}
ContinuousResource registered = children.value().stream()
.filter(c -> c.id().equals(resource.id()))
.findFirst()
.get();
if (registered.value() < resource.value()) {
// Capacity < requested, can never satisfy
return false;
}
// check if there's enough left
Versioned<ContinuousResourceAllocation> allocation = consumers.get(resource.id());
if (allocation == null) {
// no allocation (=no consumer) full registered resources available
return true;
}
return hasEnoughResource(allocation.value().original(), resource, allocation.value());
}
<T> Stream<ContinuousResource> getAllocatedResources(DiscreteResourceId parent, Class<T> cls) {
Set<ContinuousResource> children = getChildResources(parent);
if (children.isEmpty()) {
return Stream.of();
}
return children.stream()
.filter(x -> x.id().equals(parent.child(cls)))
// we don't use cascading simple predicates like follows to reduce accesses to consistent map
// .filter(x -> continuousConsumers.containsKey(x.id()))
// .filter(x -> continuousConsumers.get(x.id()) != null)
// .filter(x -> !continuousConsumers.get(x.id()).value().allocations().isEmpty());
.filter(resource -> {
Versioned<ContinuousResourceAllocation> allocation = consumers.get(resource.id());
if (allocation == null) {
return false;
}
return !allocation.value().allocations().isEmpty();
});
}
Stream<ContinuousResource> getResources(ResourceConsumer consumer) {
return consumers.values().stream()
.flatMap(x -> x.value().allocations().stream()
.map(y -> Maps.immutableEntry(x.value().original(), y)))
.filter(x -> x.getValue().consumer().equals(consumer))
.map(x -> x.getKey());
}
}
/*
* Copyright 2016-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.store.resource.impl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.onlab.util.Tools;
import org.onosproject.net.resource.DiscreteResource;
import org.onosproject.net.resource.DiscreteResourceId;
import org.onosproject.net.resource.Resource;
import org.onosproject.net.resource.ResourceAllocation;
import org.onosproject.net.resource.ResourceConsumer;
import org.onosproject.net.resource.Resources;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.ConsistentMapException;
import org.onosproject.store.service.StorageService;
import org.onosproject.store.service.TransactionContext;
import org.onosproject.store.service.Versioned;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.MAX_RETRIES;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.RETRY_DELAY;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.SERIALIZER;
class ConsistentDiscreteResourceStore {
private ConsistentMap<DiscreteResourceId, ResourceConsumer> consumers;
private ConsistentMap<DiscreteResourceId, Set<DiscreteResource>> childMap;
ConsistentDiscreteResourceStore(StorageService service) {
this.consumers = service.<DiscreteResourceId, ResourceConsumer>consistentMapBuilder()
.withName(MapNames.DISCRETE_CONSUMER_MAP)
.withSerializer(SERIALIZER)
.build();
this.childMap = service.<DiscreteResourceId, Set<DiscreteResource>>consistentMapBuilder()
.withName(MapNames.DISCRETE_CHILD_MAP)
.withSerializer(SERIALIZER)
.build();
Tools.retryable(() -> childMap.put(Resource.ROOT.id(), new LinkedHashSet<>()),
ConsistentMapException.class, MAX_RETRIES, RETRY_DELAY);
}
TransactionalDiscreteResourceStore transactional(TransactionContext tx) {
return new TransactionalDiscreteResourceStore(tx);
}
// computational complexity: O(1)
List<ResourceAllocation> getResourceAllocations(DiscreteResourceId resource) {
Versioned<ResourceConsumer> consumer = consumers.get(resource);
if (consumer == null) {
return ImmutableList.of();
}
return ImmutableList.of(new ResourceAllocation(Resources.discrete(resource).resource(), consumer.value()));
}
Set<DiscreteResource> getChildResources(DiscreteResourceId parent) {
Versioned<Set<DiscreteResource>> children = childMap.get(parent);
if (children == null) {
return ImmutableSet.of();
}
return children.value();
}
boolean isAvailable(DiscreteResource resource) {
return getResourceAllocations(resource.id()).isEmpty();
}
<T> Stream<DiscreteResource> getAllocatedResources(DiscreteResourceId parent, Class<T> cls) {
Set<DiscreteResource> children = getChildResources(parent);
if (children.isEmpty()) {
return Stream.of();
}
return children.stream()
.filter(x -> x.isTypeOf(cls))
.filter(x -> consumers.containsKey(x.id()));
}
Stream<DiscreteResource> getResources(ResourceConsumer consumer) {
return consumers.entrySet().stream()
.filter(x -> x.getValue().value().equals(consumer))
.map(Map.Entry::getKey)
.map(x -> Resources.discrete(x).resource());
}
}
/*
* Copyright 2016-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.store.resource.impl;
final class MapNames {
static final String DISCRETE_CONSUMER_MAP = "onos-discrete-consumers";
static final String DISCRETE_CHILD_MAP = "onos-resource-discrete-children";
static final String CONTINUOUS_CONSUMER_MAP = "onos-continuous-consumers";
static final String CONTINUOUS_CHILD_MAP = "onos-resource-continuous-children";
// prohibit contruction
private MapNames() {}
}
/*
* Copyright 2016-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.store.resource.impl;
import org.onosproject.net.resource.ContinuousResource;
final class ResourceStoreUtil {
// prohibit construction
private ResourceStoreUtil() {}
/**
* Checks if there is enough resource volume to allocated the requested resource
* against the specified resource.
*
* @param original original resource
* @param request requested resource
* @param allocation current allocation of the resource
* @return true if there is enough resource volume. Otherwise, false.
*/
// computational complexity: O(n) where n is the number of allocations
static boolean hasEnoughResource(ContinuousResource original,
ContinuousResource request,
ConsistentResourceStore.ContinuousResourceAllocation allocation) {
if (allocation == null) {
return request.value() <= original.value();
}
double allocated = allocation.allocations().stream()
.filter(x -> x.resource() instanceof ContinuousResource)
.map(x -> (ContinuousResource) x.resource())
.mapToDouble(ContinuousResource::value)
.sum();
double left = original.value() - allocated;
return request.value() <= left;
}
}
/*
* Copyright 2016-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.store.resource.impl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import org.onlab.util.GuavaCollectors;
import org.onosproject.net.resource.ContinuousResource;
import org.onosproject.net.resource.ContinuousResourceId;
import org.onosproject.net.resource.DiscreteResourceId;
import org.onosproject.net.resource.Resource;
import org.onosproject.net.resource.ResourceAllocation;
import org.onosproject.net.resource.ResourceConsumer;
import org.onosproject.store.resource.impl.ConsistentResourceStore.ContinuousResourceAllocation;
import org.onosproject.store.service.TransactionContext;
import org.onosproject.store.service.TransactionalMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.SERIALIZER;
import static org.onosproject.store.resource.impl.ResourceStoreUtil.hasEnoughResource;
class TransactionalContinuousResourceStore {
private final Logger log = LoggerFactory.getLogger(getClass());
private final TransactionalMap<DiscreteResourceId, Set<ContinuousResource>> childMap;
private final TransactionalMap<ContinuousResourceId, ContinuousResourceAllocation> consumers;
TransactionalContinuousResourceStore(TransactionContext tx) {
this.childMap = tx.getTransactionalMap(MapNames.CONTINUOUS_CHILD_MAP, SERIALIZER);
this.consumers = tx.getTransactionalMap(MapNames.CONTINUOUS_CONSUMER_MAP, SERIALIZER);
}
// iterate over the values in the set: O(n) operation
Optional<Resource> lookup(ContinuousResourceId id) {
if (!id.parent().isPresent()) {
return Optional.of(Resource.ROOT);
}
Set<ContinuousResource> values = childMap.get(id.parent().get());
if (values == null) {
return Optional.empty();
}
return values.stream()
.filter(x -> x.id().equals(id))
.map(x -> (Resource) x)
.findFirst();
}
boolean appendValues(DiscreteResourceId key, List<ContinuousResource> values) {
Set<ContinuousResource> requested = new LinkedHashSet<>(values);
Set<ContinuousResource> oldValues = childMap.putIfAbsent(key, requested);
if (oldValues == null) {
return true;
}
Set<ContinuousResource> addedValues = Sets.difference(requested, oldValues);
// no new value, then no-op
if (addedValues.isEmpty()) {
// don't write to map because all values are already stored
return true;
}
Set<ContinuousResourceId> addedIds = addedValues.stream()
.map(ContinuousResource::id)
.collect(Collectors.toSet());
// if the value is not found but the same ID is found
// (this happens only when being continuous resource)
if (oldValues.stream().anyMatch(x -> addedIds.contains(x.id()))) {
// no-op, but indicating failure (reject the request)
return false;
}
Set<ContinuousResource> newValues = new LinkedHashSet<>(oldValues);
newValues.addAll(addedValues);
return childMap.replace(key, oldValues, newValues);
}
boolean removeValues(DiscreteResourceId key, List<ContinuousResource> values) {
Set<ContinuousResource> oldValues = childMap.putIfAbsent(key, new LinkedHashSet<>());
if (oldValues == null) {
log.trace("No-Op removing values. key {} did not exist", key);
return true;
}
if (values.stream().allMatch(x -> !oldValues.contains(x))) {
// don't write map because none of the values are stored
log.trace("No-Op removing values. key {} did not contain {}", key, values);
return true;
}
LinkedHashSet<ContinuousResource> newValues = new LinkedHashSet<>(oldValues);
newValues.removeAll(values);
return childMap.replace(key, oldValues, newValues);
}
boolean isAllocated(ContinuousResourceId id) {
ContinuousResourceAllocation allocations = consumers.get(id);
return allocations != null && !allocations.allocations().isEmpty();
}
boolean allocate(ResourceConsumer consumer, ContinuousResource request) {
// if the resource is not registered, then abort
Optional<Resource> lookedUp = lookup(request.id());
if (!lookedUp.isPresent()) {
return false;
}
// Down cast: this must be safe as ContinuousResource is associated with ContinuousResourceId
ContinuousResource original = (ContinuousResource) lookedUp.get();
ContinuousResourceAllocation allocations = consumers.get(request.id());
if (!hasEnoughResource(original, request, allocations)) {
return false;
}
boolean success = appendValue(original, new ResourceAllocation(request, consumer));
if (!success) {
return false;
}
return true;
}
// Appends the specified ResourceAllocation to the existing values stored in the map
// computational complexity: O(n) where n is the number of the elements in the associated allocation
private boolean appendValue(ContinuousResource original, ResourceAllocation value) {
ContinuousResourceAllocation oldValue = consumers.putIfAbsent(original.id(),
new ContinuousResourceAllocation(original, ImmutableList.of(value)));
if (oldValue == null) {
return true;
}
if (oldValue.allocations().contains(value)) {
// don't write to map because all values are already stored
return true;
}
ContinuousResourceAllocation newValue = new ContinuousResourceAllocation(original,
ImmutableList.<ResourceAllocation>builder()
.addAll(oldValue.allocations())
.add(value)
.build());
return consumers.replace(original.id(), oldValue, newValue);
}
boolean release(ContinuousResource resource, ResourceConsumer consumer) {
ContinuousResourceAllocation oldAllocation = consumers.get(resource.id());
ImmutableList<ResourceAllocation> newAllocations = oldAllocation.allocations().stream()
.filter(x -> !(x.consumer().equals(consumer) &&
((ContinuousResource) x.resource()).value() == resource.value()))
.collect(GuavaCollectors.toImmutableList());
if (!consumers.replace(resource.id(), oldAllocation,
new ContinuousResourceAllocation(oldAllocation.original(), newAllocations))) {
return false;
}
return true;
}
}
/*
* Copyright 2016-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.store.resource.impl;
import com.google.common.collect.Sets;
import org.onosproject.net.resource.DiscreteResource;
import org.onosproject.net.resource.DiscreteResourceId;
import org.onosproject.net.resource.Resource;
import org.onosproject.net.resource.ResourceConsumer;
import org.onosproject.net.resource.Resources;
import org.onosproject.store.service.TransactionContext;
import org.onosproject.store.service.TransactionalMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static org.onosproject.store.resource.impl.ConsistentResourceStore.SERIALIZER;
class TransactionalDiscreteResourceStore {
private final Logger log = LoggerFactory.getLogger(getClass());
private final TransactionalMap<DiscreteResourceId, Set<DiscreteResource>> childMap;
private final TransactionalMap<DiscreteResourceId, ResourceConsumer> consumers;
TransactionalDiscreteResourceStore(TransactionContext tx) {
this.childMap = tx.getTransactionalMap(MapNames.DISCRETE_CHILD_MAP, SERIALIZER);
this.consumers = tx.getTransactionalMap(MapNames.DISCRETE_CONSUMER_MAP, SERIALIZER);
}
// check the existence in the set: O(1) operation
Optional<Resource> lookup(DiscreteResourceId id) {
if (!id.parent().isPresent()) {
return Optional.of(Resource.ROOT);
}
Set<DiscreteResource> values = childMap.get(id.parent().get());
if (values == null) {
return Optional.empty();
}
DiscreteResource resource = Resources.discrete(id).resource();
if (values.contains(resource)) {
return Optional.of(resource);
} else {
return Optional.empty();
}
}
boolean appendValues(DiscreteResourceId key, List<DiscreteResource> values) {
Set<DiscreteResource> requested = new LinkedHashSet<>(values);
Set<DiscreteResource> oldValues = childMap.putIfAbsent(key, requested);
if (oldValues == null) {
return true;
}
Set<DiscreteResource> addedValues = Sets.difference(requested, oldValues);
// no new value, then no-op
if (addedValues.isEmpty()) {
// don't write to map because all values are already stored
return true;
}
Set<DiscreteResource> newValues = new LinkedHashSet<>(oldValues);
newValues.addAll(addedValues);
return childMap.replace(key, oldValues, newValues);
}
boolean removeValues(DiscreteResourceId key, List<DiscreteResource> values) {
Set<DiscreteResource> oldValues = childMap.putIfAbsent(key, new LinkedHashSet<>());
if (oldValues == null) {
log.trace("No-Op removing values. key {} did not exist", key);
return true;
}
if (values.stream().allMatch(x -> !oldValues.contains(x))) {
// don't write map because none of the values are stored
log.trace("No-Op removing values. key {} did not contain {}", key, values);
return true;
}
LinkedHashSet<DiscreteResource> newValues = new LinkedHashSet<>(oldValues);
newValues.removeAll(values);
return childMap.replace(key, oldValues, newValues);
}
boolean isAllocated(DiscreteResourceId id) {
return consumers.get(id) != null;
}
boolean allocate(ResourceConsumer consumer, DiscreteResource resource) {
// if the resource is not registered, then abort
Optional<Resource> lookedUp = lookup(resource.id());
if (!lookedUp.isPresent()) {
return false;
}
ResourceConsumer oldValue = consumers.put(resource.id(), consumer);
return oldValue == null;
}
boolean release(DiscreteResource resource, ResourceConsumer consumer) {
// if this single release fails (because the resource is allocated to another consumer)
// the whole release fails
if (!consumers.remove(resource.id(), consumer)) {
return false;
}
return true;
}
}