10f79eb03c72ec9e91d904e5546ada701459c5f5
[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, enabled = false)
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 <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) {
243         checkNotNull(parent);
244         checkNotNull(cls);
245
246         Versioned<List<ResourcePath>> children = childMap.get(parent);
247         if (children == null) {
248             return Collections.emptyList();
249         }
250
251         return children.value().stream()
252                 .filter(x -> x.lastComponent().getClass().equals(cls))
253                 .filter(consumerMap::containsKey)
254                 .collect(Collectors.toList());
255     }
256
257     /**
258      * Abort the transaction.
259      *
260      * @param tx transaction context
261      * @return always false
262      */
263     private boolean abortTransaction(TransactionContext tx) {
264         tx.abort();
265         return false;
266     }
267
268     /**
269      * Commit the transaction.
270      *
271      * @param tx transaction context
272      * @return always true
273      */
274     private boolean commitTransaction(TransactionContext tx) {
275         tx.commit();
276         return true;
277     }
278
279     /**
280      * Appends the values to the existing values associated with the specified key.
281      * If the map already has all the given values, appending will not happen.
282      *
283      * @param map map holding multiple values for a key
284      * @param key key specifying values
285      * @param values values to be appended
286      * @param <K> type of the key
287      * @param <V> type of the element of the list
288      * @return true if the operation succeeds, false otherwise.
289      */
290     private <K, V> boolean appendValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
291         List<V> oldValues = map.get(key);
292         if (oldValues == null) {
293             return map.replace(key, oldValues, new ArrayList<>(values));
294         }
295
296         LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
297         if (oldSet.containsAll(values)) {
298             // don't write to map because all values are already stored
299             return true;
300         }
301
302         oldSet.addAll(values);
303         return map.replace(key, oldValues, new ArrayList<>(oldSet));
304     }
305
306     /**
307      * Removes the values from the existing values associated with the specified key.
308      * If the map doesn't contain the given values, removal will not happen.
309      *
310      * @param map map holding multiple values for a key
311      * @param key key specifying values
312      * @param values values to be removed
313      * @param <K> type of the key
314      * @param <V> type of the element of the list
315      * @return true if the operation succeeds, false otherwise
316      */
317     private <K, V> boolean removeValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
318         List<V> oldValues = map.get(key);
319         if (oldValues == null) {
320             return map.replace(key, oldValues, new ArrayList<>());
321         }
322
323         LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
324         if (values.stream().allMatch(x -> !oldSet.contains(x))) {
325             // don't write map because none of the values are stored
326             return true;
327         }
328
329         oldSet.removeAll(values);
330         return map.replace(key, oldValues, new ArrayList<>(oldSet));
331     }
332
333     /**
334      * Checks if the specified resource is registered as a child of a resource in the map.
335      *
336      * @param map map storing parent - child relationship of resources
337      * @param resource resource to be checked
338      * @return true if the resource is registered, false otherwise.
339      */
340     private boolean isRegistered(TransactionalMap<ResourcePath, List<ResourcePath>> map, ResourcePath resource) {
341         // root is always regarded to be registered
342         if (resource.isRoot()) {
343             return true;
344         }
345
346         List<ResourcePath> value = map.get(resource.parent().get());
347         return value != null && value.contains(resource);
348     }
349 }