2  * Copyright 2015 Open Networking Laboratory
 
   4  * Licensed under the Apache License, Version 2.0 (the "License");
 
   5  * you may not use this file except in compliance with the License.
 
   6  * You may obtain a copy of the License at
 
   8  *     http://www.apache.org/licenses/LICENSE-2.0
 
  10  * Unless required by applicable law or agreed to in writing, software
 
  11  * distributed under the License is distributed on an "AS IS" BASIS,
 
  12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  13  * See the License for the specific language governing permissions and
 
  14  * limitations under the License.
 
  16 package org.onosproject.store.newresource.impl;
 
  18 import com.google.common.annotations.Beta;
 
  19 import org.apache.felix.scr.annotations.Activate;
 
  20 import org.apache.felix.scr.annotations.Component;
 
  21 import org.apache.felix.scr.annotations.Reference;
 
  22 import org.apache.felix.scr.annotations.ReferenceCardinality;
 
  23 import org.apache.felix.scr.annotations.Service;
 
  24 import org.onosproject.net.newresource.ResourceConsumer;
 
  25 import org.onosproject.net.newresource.ResourcePath;
 
  26 import org.onosproject.net.newresource.ResourceStore;
 
  27 import org.onosproject.store.serializers.KryoNamespaces;
 
  28 import org.onosproject.store.service.ConsistentMap;
 
  29 import org.onosproject.store.service.Serializer;
 
  30 import org.onosproject.store.service.StorageService;
 
  31 import org.onosproject.store.service.TransactionContext;
 
  32 import org.onosproject.store.service.TransactionalMap;
 
  33 import org.onosproject.store.service.Versioned;
 
  34 import org.slf4j.Logger;
 
  35 import org.slf4j.LoggerFactory;
 
  37 import java.util.ArrayList;
 
  38 import java.util.Arrays;
 
  39 import java.util.Collection;
 
  40 import java.util.Collections;
 
  41 import java.util.Iterator;
 
  42 import java.util.LinkedHashSet;
 
  43 import java.util.List;
 
  45 import java.util.Optional;
 
  46 import java.util.stream.Collectors;
 
  48 import static com.google.common.base.Preconditions.checkArgument;
 
  49 import static com.google.common.base.Preconditions.checkNotNull;
 
  52  * Implementation of ResourceStore using TransactionalMap.
 
  54 @Component(immediate = true)
 
  57 public class ConsistentResourceStore implements ResourceStore {
 
  58     private static final Logger log = LoggerFactory.getLogger(ConsistentResourceStore.class);
 
  60     private static final String CONSUMER_MAP = "onos-resource-consumers";
 
  61     private static final String CHILD_MAP = "onos-resource-children";
 
  62     private static final Serializer SERIALIZER = Serializer.using(
 
  63             Arrays.asList(KryoNamespaces.BASIC, KryoNamespaces.API));
 
  65     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
 
  66     protected StorageService service;
 
  68     private ConsistentMap<ResourcePath, ResourceConsumer> consumerMap;
 
  69     private ConsistentMap<ResourcePath, List<ResourcePath>> childMap;
 
  72     public void activate() {
 
  73         consumerMap = service.<ResourcePath, ResourceConsumer>consistentMapBuilder()
 
  74                 .withName(CONSUMER_MAP)
 
  75                 .withSerializer(SERIALIZER)
 
  77         childMap = service.<ResourcePath, List<ResourcePath>>consistentMapBuilder()
 
  79                 .withSerializer(SERIALIZER)
 
  84     public Optional<ResourceConsumer> getConsumer(ResourcePath resource) {
 
  85         checkNotNull(resource);
 
  87         Versioned<ResourceConsumer> consumer = consumerMap.get(resource);
 
  88         if (consumer == null) {
 
  89             return Optional.empty();
 
  92         return Optional.of(consumer.value());
 
  96     public boolean register(List<ResourcePath> resources) {
 
  97         checkNotNull(resources);
 
  99         TransactionContext tx = service.transactionContextBuilder().build();
 
 102         TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
 
 103                 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
 
 105         Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
 
 106                 .filter(x -> x.parent().isPresent())
 
 107                 .collect(Collectors.groupingBy(x -> x.parent().get()));
 
 109         for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
 
 110             if (!isRegistered(childTxMap, entry.getKey())) {
 
 111                 return abortTransaction(tx);
 
 114             if (!appendValues(childTxMap, entry.getKey(), entry.getValue())) {
 
 115                 return abortTransaction(tx);
 
 123     public boolean unregister(List<ResourcePath> resources) {
 
 124         checkNotNull(resources);
 
 126         TransactionContext tx = service.transactionContextBuilder().build();
 
 129         TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
 
 130                 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
 
 131         TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
 
 132                 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
 
 134         Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
 
 135                 .filter(x -> x.parent().isPresent())
 
 136                 .collect(Collectors.groupingBy(x -> x.parent().get()));
 
 138         // even if one of the resources is allocated to a consumer,
 
 139         // all unregistrations are regarded as failure
 
 140         for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
 
 141             if (entry.getValue().stream().anyMatch(x -> consumerTxMap.get(x) != null)) {
 
 142                 return abortTransaction(tx);
 
 145             if (!removeValues(childTxMap, entry.getKey(), entry.getValue())) {
 
 146                 return abortTransaction(tx);
 
 154     public boolean allocate(List<ResourcePath> resources, ResourceConsumer consumer) {
 
 155         checkNotNull(resources);
 
 156         checkNotNull(consumer);
 
 158         TransactionContext tx = service.transactionContextBuilder().build();
 
 161         TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
 
 162                 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
 
 163         TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
 
 164                 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
 
 166         for (ResourcePath resource: resources) {
 
 167             if (!isRegistered(childTxMap, resource)) {
 
 168                 return abortTransaction(tx);
 
 171             ResourceConsumer oldValue = consumerTxMap.put(resource, consumer);
 
 172             if (oldValue != null) {
 
 173                 return abortTransaction(tx);
 
 181     public boolean release(List<ResourcePath> resources, List<ResourceConsumer> consumers) {
 
 182         checkNotNull(resources);
 
 183         checkNotNull(consumers);
 
 184         checkArgument(resources.size() == consumers.size());
 
 186         TransactionContext tx = service.transactionContextBuilder().build();
 
 189         TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
 
 190                 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
 
 191         Iterator<ResourcePath> resourceIte = resources.iterator();
 
 192         Iterator<ResourceConsumer> consumerIte = consumers.iterator();
 
 194         while (resourceIte.hasNext() && consumerIte.hasNext()) {
 
 195             ResourcePath resource = resourceIte.next();
 
 196             ResourceConsumer consumer = consumerIte.next();
 
 198             // if this single release fails (because the resource is allocated to another consumer,
 
 199             // the whole release fails
 
 200             if (!consumerTxMap.remove(resource, consumer)) {
 
 201                 return abortTransaction(tx);
 
 209     public Collection<ResourcePath> getResources(ResourceConsumer consumer) {
 
 210         checkNotNull(consumer);
 
 212         // NOTE: getting all entries may become performance bottleneck
 
 213         // TODO: revisit for better backend data structure
 
 214         return consumerMap.entrySet().stream()
 
 215                 .filter(x -> x.getValue().value().equals(consumer))
 
 216                 .map(Map.Entry::getKey)
 
 217                 .collect(Collectors.toList());
 
 221     public Collection<ResourcePath> getChildResources(ResourcePath parent) {
 
 222         checkNotNull(parent);
 
 224         Versioned<List<ResourcePath>> children = childMap.get(parent);
 
 225         if (children == null) {
 
 226             return Collections.emptyList();
 
 229         return children.value();
 
 233     public <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) {
 
 234         checkNotNull(parent);
 
 237         Versioned<List<ResourcePath>> children = childMap.get(parent);
 
 238         if (children == null) {
 
 239             return Collections.emptyList();
 
 242         return children.value().stream()
 
 243                 .filter(x -> x.lastComponent().getClass().equals(cls))
 
 244                 .filter(consumerMap::containsKey)
 
 245                 .collect(Collectors.toList());
 
 249      * Abort the transaction.
 
 251      * @param tx transaction context
 
 252      * @return always false
 
 254     private boolean abortTransaction(TransactionContext tx) {
 
 260      * Appends the values to the existing values associated with the specified key.
 
 261      * If the map already has all the given values, appending will not happen.
 
 263      * @param map map holding multiple values for a key
 
 264      * @param key key specifying values
 
 265      * @param values values to be appended
 
 266      * @param <K> type of the key
 
 267      * @param <V> type of the element of the list
 
 268      * @return true if the operation succeeds, false otherwise.
 
 270     private <K, V> boolean appendValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
 
 271         List<V> oldValues = map.get(key);
 
 272         if (oldValues == null) {
 
 273             return map.replace(key, oldValues, new ArrayList<>(values));
 
 276         LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
 
 277         if (oldSet.containsAll(values)) {
 
 278             // don't write to map because all values are already stored
 
 282         oldSet.addAll(values);
 
 283         return map.replace(key, oldValues, new ArrayList<>(oldSet));
 
 287      * Removes the values from the existing values associated with the specified key.
 
 288      * If the map doesn't contain the given values, removal will not happen.
 
 290      * @param map map holding multiple values for a key
 
 291      * @param key key specifying values
 
 292      * @param values values to be removed
 
 293      * @param <K> type of the key
 
 294      * @param <V> type of the element of the list
 
 295      * @return true if the operation succeeds, false otherwise
 
 297     private <K, V> boolean removeValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
 
 298         List<V> oldValues = map.get(key);
 
 299         if (oldValues == null) {
 
 300             return map.replace(key, oldValues, new ArrayList<>());
 
 303         LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
 
 304         if (values.stream().allMatch(x -> !oldSet.contains(x))) {
 
 305             // don't write map because none of the values are stored
 
 309         oldSet.removeAll(values);
 
 310         return map.replace(key, oldValues, new ArrayList<>(oldSet));
 
 314      * Checks if the specified resource is registered as a child of a resource in the map.
 
 316      * @param map map storing parent - child relationship of resources
 
 317      * @param resource resource to be checked
 
 318      * @return true if the resource is registered, false otherwise.
 
 320     private boolean isRegistered(TransactionalMap<ResourcePath, List<ResourcePath>> map, ResourcePath resource) {
 
 321         // root is always regarded to be registered
 
 322         if (resource.isRoot()) {
 
 326         List<ResourcePath> value = map.get(resource.parent().get());
 
 327         return value != null && value.contains(resource);