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