c9aaba5a902c7b02a6da803d1235479eda87c668
[onosfw.git] /
1 /*
2  * Copyright 2015 Open Networking Laboratory
3  *
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package org.onosproject.store.newresource.impl;
17
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;
36
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;
44 import java.util.Map;
45 import java.util.Optional;
46 import java.util.stream.Collectors;
47
48 import static com.google.common.base.Preconditions.checkArgument;
49 import static com.google.common.base.Preconditions.checkNotNull;
50
51 /**
52  * Implementation of ResourceStore using TransactionalMap.
53  */
54 @Component(immediate = true)
55 @Service
56 @Beta
57 public class ConsistentResourceStore implements ResourceStore {
58     private static final Logger log = LoggerFactory.getLogger(ConsistentResourceStore.class);
59
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));
64
65     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66     protected StorageService service;
67
68     private ConsistentMap<ResourcePath, ResourceConsumer> consumerMap;
69     private ConsistentMap<ResourcePath, List<ResourcePath>> childMap;
70
71     @Activate
72     public void activate() {
73         consumerMap = service.<ResourcePath, ResourceConsumer>consistentMapBuilder()
74                 .withName(CONSUMER_MAP)
75                 .withSerializer(SERIALIZER)
76                 .build();
77         childMap = service.<ResourcePath, List<ResourcePath>>consistentMapBuilder()
78                 .withName(CHILD_MAP)
79                 .withSerializer(SERIALIZER)
80                 .build();
81     }
82
83     @Override
84     public Optional<ResourceConsumer> getConsumer(ResourcePath resource) {
85         checkNotNull(resource);
86
87         Versioned<ResourceConsumer> consumer = consumerMap.get(resource);
88         if (consumer == null) {
89             return Optional.empty();
90         }
91
92         return Optional.of(consumer.value());
93     }
94
95     @Override
96     public boolean register(List<ResourcePath> resources) {
97         checkNotNull(resources);
98
99         TransactionContext tx = service.transactionContextBuilder().build();
100         tx.begin();
101
102         TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
103                 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
104
105         Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
106                 .filter(x -> x.parent().isPresent())
107                 .collect(Collectors.groupingBy(x -> x.parent().get()));
108
109         for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
110             if (!isRegistered(childTxMap, entry.getKey())) {
111                 return abortTransaction(tx);
112             }
113
114             if (!appendValues(childTxMap, entry.getKey(), entry.getValue())) {
115                 return abortTransaction(tx);
116             }
117         }
118
119         return tx.commit();
120     }
121
122     @Override
123     public boolean unregister(List<ResourcePath> resources) {
124         checkNotNull(resources);
125
126         TransactionContext tx = service.transactionContextBuilder().build();
127         tx.begin();
128
129         TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
130                 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
131         TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
132                 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
133
134         Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
135                 .filter(x -> x.parent().isPresent())
136                 .collect(Collectors.groupingBy(x -> x.parent().get()));
137
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);
143             }
144
145             if (!removeValues(childTxMap, entry.getKey(), entry.getValue())) {
146                 return abortTransaction(tx);
147             }
148         }
149
150         return tx.commit();
151     }
152
153     @Override
154     public boolean allocate(List<ResourcePath> resources, ResourceConsumer consumer) {
155         checkNotNull(resources);
156         checkNotNull(consumer);
157
158         TransactionContext tx = service.transactionContextBuilder().build();
159         tx.begin();
160
161         TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
162                 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
163         TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
164                 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
165
166         for (ResourcePath resource: resources) {
167             if (!isRegistered(childTxMap, resource)) {
168                 return abortTransaction(tx);
169             }
170
171             ResourceConsumer oldValue = consumerTxMap.put(resource, consumer);
172             if (oldValue != null) {
173                 return abortTransaction(tx);
174             }
175         }
176
177         return tx.commit();
178     }
179
180     @Override
181     public boolean release(List<ResourcePath> resources, List<ResourceConsumer> consumers) {
182         checkNotNull(resources);
183         checkNotNull(consumers);
184         checkArgument(resources.size() == consumers.size());
185
186         TransactionContext tx = service.transactionContextBuilder().build();
187         tx.begin();
188
189         TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
190                 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
191         Iterator<ResourcePath> resourceIte = resources.iterator();
192         Iterator<ResourceConsumer> consumerIte = consumers.iterator();
193
194         while (resourceIte.hasNext() && consumerIte.hasNext()) {
195             ResourcePath resource = resourceIte.next();
196             ResourceConsumer consumer = consumerIte.next();
197
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);
202             }
203         }
204
205         return tx.commit();
206     }
207
208     @Override
209     public Collection<ResourcePath> getResources(ResourceConsumer consumer) {
210         checkNotNull(consumer);
211
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());
218     }
219
220     @Override
221     public Collection<ResourcePath> getChildResources(ResourcePath parent) {
222         checkNotNull(parent);
223
224         Versioned<List<ResourcePath>> children = childMap.get(parent);
225         if (children == null) {
226             return Collections.emptyList();
227         }
228
229         return children.value();
230     }
231
232     @Override
233     public <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) {
234         checkNotNull(parent);
235         checkNotNull(cls);
236
237         Versioned<List<ResourcePath>> children = childMap.get(parent);
238         if (children == null) {
239             return Collections.emptyList();
240         }
241
242         return children.value().stream()
243                 .filter(x -> x.lastComponent().getClass().equals(cls))
244                 .filter(consumerMap::containsKey)
245                 .collect(Collectors.toList());
246     }
247
248     /**
249      * Abort the transaction.
250      *
251      * @param tx transaction context
252      * @return always false
253      */
254     private boolean abortTransaction(TransactionContext tx) {
255         tx.abort();
256         return false;
257     }
258
259     /**
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.
262      *
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.
269      */
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));
274         }
275
276         LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
277         if (oldSet.containsAll(values)) {
278             // don't write to map because all values are already stored
279             return true;
280         }
281
282         oldSet.addAll(values);
283         return map.replace(key, oldValues, new ArrayList<>(oldSet));
284     }
285
286     /**
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.
289      *
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
296      */
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<>());
301         }
302
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
306             return true;
307         }
308
309         oldSet.removeAll(values);
310         return map.replace(key, oldValues, new ArrayList<>(oldSet));
311     }
312
313     /**
314      * Checks if the specified resource is registered as a child of a resource in the map.
315      *
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.
319      */
320     private boolean isRegistered(TransactionalMap<ResourcePath, List<ResourcePath>> map, ResourcePath resource) {
321         // root is always regarded to be registered
322         if (resource.isRoot()) {
323             return true;
324         }
325
326         List<ResourcePath> value = map.get(resource.parent().get());
327         return value != null && value.contains(resource);
328     }
329 }