687576c3fb24ad4d7182ebbd53bc08abe0b1f825
[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.TransactionException;
33 import org.onosproject.store.service.TransactionalMap;
34 import org.onosproject.store.service.Versioned;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.Iterator;
43 import java.util.LinkedHashSet;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Optional;
47 import java.util.stream.Collectors;
48
49 import static com.google.common.base.Preconditions.checkArgument;
50 import static com.google.common.base.Preconditions.checkNotNull;
51
52 /**
53  * Implementation of ResourceStore using TransactionalMap.
54  */
55 @Component(immediate = true)
56 @Service
57 @Beta
58 public class ConsistentResourceStore implements ResourceStore {
59     private static final Logger log = LoggerFactory.getLogger(ConsistentResourceStore.class);
60
61     private static final String CONSUMER_MAP = "onos-resource-consumers";
62     private static final String CHILD_MAP = "onos-resource-children";
63     private static final Serializer SERIALIZER = Serializer.using(
64             Arrays.asList(KryoNamespaces.BASIC, KryoNamespaces.API));
65
66     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
67     protected StorageService service;
68
69     private ConsistentMap<ResourcePath, ResourceConsumer> consumerMap;
70     private ConsistentMap<ResourcePath, List<ResourcePath>> childMap;
71
72     @Activate
73     public void activate() {
74         consumerMap = service.<ResourcePath, ResourceConsumer>consistentMapBuilder()
75                 .withName(CONSUMER_MAP)
76                 .withSerializer(SERIALIZER)
77                 .build();
78         childMap = service.<ResourcePath, List<ResourcePath>>consistentMapBuilder()
79                 .withName(CHILD_MAP)
80                 .withSerializer(SERIALIZER)
81                 .build();
82     }
83
84     @Override
85     public Optional<ResourceConsumer> getConsumer(ResourcePath resource) {
86         checkNotNull(resource);
87
88         Versioned<ResourceConsumer> consumer = consumerMap.get(resource);
89         if (consumer == null) {
90             return Optional.empty();
91         }
92
93         return Optional.of(consumer.value());
94     }
95
96     @Override
97     public boolean register(List<ResourcePath> resources) {
98         checkNotNull(resources);
99
100         TransactionContext tx = service.transactionContextBuilder().build();
101         tx.begin();
102
103         try {
104             TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
105                     tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
106
107             Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
108                     .filter(x -> x.parent().isPresent())
109                     .collect(Collectors.groupingBy(x -> x.parent().get()));
110
111             for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
112                 if (!isRegistered(childTxMap, entry.getKey())) {
113                     return abortTransaction(tx);
114                 }
115
116                 if (!appendValues(childTxMap, entry.getKey(), entry.getValue())) {
117                     return abortTransaction(tx);
118                 }
119             }
120
121             return commitTransaction(tx);
122         } catch (TransactionException e) {
123             log.error("Exception thrown, abort the transaction", e);
124             return abortTransaction(tx);
125         }
126     }
127
128     @Override
129     public boolean unregister(List<ResourcePath> resources) {
130         checkNotNull(resources);
131
132         TransactionContext tx = service.transactionContextBuilder().build();
133         tx.begin();
134
135         try {
136             TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
137                     tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
138             TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
139                     tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
140
141             Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
142                     .filter(x -> x.parent().isPresent())
143                     .collect(Collectors.groupingBy(x -> x.parent().get()));
144
145             // even if one of the resources is allocated to a consumer,
146             // all unregistrations are regarded as failure
147             for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
148                 if (entry.getValue().stream().anyMatch(x -> consumerTxMap.get(x) != null)) {
149                     return abortTransaction(tx);
150                 }
151
152                 if (!removeValues(childTxMap, entry.getKey(), entry.getValue())) {
153                     return abortTransaction(tx);
154                 }
155             }
156
157             return commitTransaction(tx);
158         } catch (TransactionException e) {
159             log.error("Exception thrown, abort the transaction", e);
160             return abortTransaction(tx);
161         }
162     }
163
164     @Override
165     public boolean allocate(List<ResourcePath> resources, ResourceConsumer consumer) {
166         checkNotNull(resources);
167         checkNotNull(consumer);
168
169         TransactionContext tx = service.transactionContextBuilder().build();
170         tx.begin();
171
172         try {
173             TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
174                     tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
175             TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
176                     tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
177
178             for (ResourcePath resource: resources) {
179                 if (!isRegistered(childTxMap, resource)) {
180                     return abortTransaction(tx);
181                 }
182
183                 ResourceConsumer oldValue = consumerTxMap.put(resource, consumer);
184                 if (oldValue != null) {
185                     return abortTransaction(tx);
186                 }
187             }
188
189             return commitTransaction(tx);
190         } catch (TransactionException e) {
191             log.error("Exception thrown, abort the transaction", e);
192             return abortTransaction(tx);
193         }
194     }
195
196     @Override
197     public boolean release(List<ResourcePath> resources, List<ResourceConsumer> consumers) {
198         checkNotNull(resources);
199         checkNotNull(consumers);
200         checkArgument(resources.size() == consumers.size());
201
202         TransactionContext tx = service.transactionContextBuilder().build();
203         tx.begin();
204
205         try {
206             TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
207                     tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
208             Iterator<ResourcePath> resourceIte = resources.iterator();
209             Iterator<ResourceConsumer> consumerIte = consumers.iterator();
210
211             while (resourceIte.hasNext() && consumerIte.hasNext()) {
212                 ResourcePath resource = resourceIte.next();
213                 ResourceConsumer consumer = consumerIte.next();
214
215                 // if this single release fails (because the resource is allocated to another consumer,
216                 // the whole release fails
217                 if (!consumerTxMap.remove(resource, consumer)) {
218                     return abortTransaction(tx);
219                 }
220             }
221
222             return commitTransaction(tx);
223         } catch (TransactionException e) {
224             log.error("Exception thrown, abort the transaction", e);
225             return abortTransaction(tx);
226         }
227     }
228
229     @Override
230     public Collection<ResourcePath> getResources(ResourceConsumer consumer) {
231         checkNotNull(consumer);
232
233         // NOTE: getting all entries may become performance bottleneck
234         // TODO: revisit for better backend data structure
235         return consumerMap.entrySet().stream()
236                 .filter(x -> x.getValue().value().equals(consumer))
237                 .map(Map.Entry::getKey)
238                 .collect(Collectors.toList());
239     }
240
241     @Override
242     public Collection<ResourcePath> getChildResources(ResourcePath parent) {
243         checkNotNull(parent);
244
245         Versioned<List<ResourcePath>> children = childMap.get(parent);
246         if (children == null) {
247             return Collections.emptyList();
248         }
249
250         return children.value();
251     }
252
253     @Override
254     public <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) {
255         checkNotNull(parent);
256         checkNotNull(cls);
257
258         Versioned<List<ResourcePath>> children = childMap.get(parent);
259         if (children == null) {
260             return Collections.emptyList();
261         }
262
263         return children.value().stream()
264                 .filter(x -> x.lastComponent().getClass().equals(cls))
265                 .filter(consumerMap::containsKey)
266                 .collect(Collectors.toList());
267     }
268
269     /**
270      * Abort the transaction.
271      *
272      * @param tx transaction context
273      * @return always false
274      */
275     private boolean abortTransaction(TransactionContext tx) {
276         tx.abort();
277         return false;
278     }
279
280     /**
281      * Commit the transaction.
282      *
283      * @param tx transaction context
284      * @return always true
285      */
286     private boolean commitTransaction(TransactionContext tx) {
287         tx.commit();
288         return true;
289     }
290
291     /**
292      * Appends the values to the existing values associated with the specified key.
293      * If the map already has all the given values, appending will not happen.
294      *
295      * @param map map holding multiple values for a key
296      * @param key key specifying values
297      * @param values values to be appended
298      * @param <K> type of the key
299      * @param <V> type of the element of the list
300      * @return true if the operation succeeds, false otherwise.
301      */
302     private <K, V> boolean appendValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
303         List<V> oldValues = map.get(key);
304         if (oldValues == null) {
305             return map.replace(key, oldValues, new ArrayList<>(values));
306         }
307
308         LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
309         if (oldSet.containsAll(values)) {
310             // don't write to map because all values are already stored
311             return true;
312         }
313
314         oldSet.addAll(values);
315         return map.replace(key, oldValues, new ArrayList<>(oldSet));
316     }
317
318     /**
319      * Removes the values from the existing values associated with the specified key.
320      * If the map doesn't contain the given values, removal will not happen.
321      *
322      * @param map map holding multiple values for a key
323      * @param key key specifying values
324      * @param values values to be removed
325      * @param <K> type of the key
326      * @param <V> type of the element of the list
327      * @return true if the operation succeeds, false otherwise
328      */
329     private <K, V> boolean removeValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
330         List<V> oldValues = map.get(key);
331         if (oldValues == null) {
332             return map.replace(key, oldValues, new ArrayList<>());
333         }
334
335         LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
336         if (values.stream().allMatch(x -> !oldSet.contains(x))) {
337             // don't write map because none of the values are stored
338             return true;
339         }
340
341         oldSet.removeAll(values);
342         return map.replace(key, oldValues, new ArrayList<>(oldSet));
343     }
344
345     /**
346      * Checks if the specified resource is registered as a child of a resource in the map.
347      *
348      * @param map map storing parent - child relationship of resources
349      * @param resource resource to be checked
350      * @return true if the resource is registered, false otherwise.
351      */
352     private boolean isRegistered(TransactionalMap<ResourcePath, List<ResourcePath>> map, ResourcePath resource) {
353         // root is always regarded to be registered
354         if (resource.isRoot()) {
355             return true;
356         }
357
358         List<ResourcePath> value = map.get(resource.parent().get());
359         return value != null && value.contains(resource);
360     }
361 }