blob: d466c95e1a2e021f4e0dc3bbf05ac1beed6ece45 [file] [log] [blame]
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -07001/*
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 */
16package org.onosproject.store.newresource.impl;
17
18import com.google.common.annotations.Beta;
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Reference;
22import org.apache.felix.scr.annotations.ReferenceCardinality;
23import org.apache.felix.scr.annotations.Service;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070024import org.onosproject.net.newresource.ResourceConsumer;
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -070025import org.onosproject.net.newresource.ResourcePath;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070026import org.onosproject.net.newresource.ResourceStore;
27import org.onosproject.store.serializers.KryoNamespaces;
28import org.onosproject.store.service.ConsistentMap;
29import org.onosproject.store.service.Serializer;
30import org.onosproject.store.service.StorageService;
31import org.onosproject.store.service.TransactionContext;
32import org.onosproject.store.service.TransactionException;
33import org.onosproject.store.service.TransactionalMap;
34import org.onosproject.store.service.Versioned;
35import org.slf4j.Logger;
36import org.slf4j.LoggerFactory;
37
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070038import java.util.ArrayList;
39import java.util.Arrays;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070040import java.util.Collection;
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070041import java.util.Collections;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070042import java.util.Iterator;
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070043import java.util.LinkedHashSet;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070044import java.util.List;
45import java.util.Map;
46import java.util.Optional;
47import java.util.stream.Collectors;
48
49import static com.google.common.base.Preconditions.checkArgument;
50import static com.google.common.base.Preconditions.checkNotNull;
51
52/**
53 * Implementation of ResourceStore using TransactionalMap.
54 */
Sho SHIMIZU9a2b8292015-10-28 13:00:16 -070055@Component(immediate = true)
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070056@Service
57@Beta
58public class ConsistentResourceStore implements ResourceStore {
59 private static final Logger log = LoggerFactory.getLogger(ConsistentResourceStore.class);
60
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070061 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));
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070065
66 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
67 protected StorageService service;
68
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070069 private ConsistentMap<ResourcePath, ResourceConsumer> consumerMap;
70 private ConsistentMap<ResourcePath, List<ResourcePath>> childMap;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070071
72 @Activate
73 public void activate() {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070074 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)
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070080 .withSerializer(SERIALIZER)
81 .build();
82 }
83
84 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -070085 public Optional<ResourceConsumer> getConsumer(ResourcePath resource) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070086 checkNotNull(resource);
87
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070088 Versioned<ResourceConsumer> consumer = consumerMap.get(resource);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070089 if (consumer == null) {
90 return Optional.empty();
91 }
92
93 return Optional.of(consumer.value());
94 }
95
96 @Override
Sho SHIMIZU83e17a02015-08-20 14:07:05 -070097 public boolean register(List<ResourcePath> resources) {
98 checkNotNull(resources);
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070099
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
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700107 Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
108 .filter(x -> x.parent().isPresent())
109 .collect(Collectors.groupingBy(x -> x.parent().get()));
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700110
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700111 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 }
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700119 }
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
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700129 public boolean unregister(List<ResourcePath> resources) {
130 checkNotNull(resources);
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700131
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
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700141 Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
142 .filter(x -> x.parent().isPresent())
143 .collect(Collectors.groupingBy(x -> x.parent().get()));
144
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700145 // even if one of the resources is allocated to a consumer,
146 // all unregistrations are regarded as failure
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700147 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 }
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700151
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700152 if (!removeValues(childTxMap, entry.getKey(), entry.getValue())) {
153 return abortTransaction(tx);
154 }
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700155 }
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
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700165 public boolean allocate(List<ResourcePath> resources, ResourceConsumer consumer) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700166 checkNotNull(resources);
167 checkNotNull(consumer);
168
169 TransactionContext tx = service.transactionContextBuilder().build();
170 tx.begin();
171
172 try {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700173 TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
174 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
175 TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
176 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
177
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700178 for (ResourcePath resource: resources) {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700179 if (!isRegistered(childTxMap, resource)) {
180 return abortTransaction(tx);
181 }
182
183 ResourceConsumer oldValue = consumerTxMap.put(resource, consumer);
184 if (oldValue != null) {
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700185 return abortTransaction(tx);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700186 }
187 }
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700188
189 return commitTransaction(tx);
Sho SHIMIZU264e4b72015-08-12 12:22:14 -0700190 } catch (TransactionException e) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700191 log.error("Exception thrown, abort the transaction", e);
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700192 return abortTransaction(tx);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700193 }
194 }
195
196 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700197 public boolean release(List<ResourcePath> resources, List<ResourceConsumer> consumers) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700198 checkNotNull(resources);
199 checkNotNull(consumers);
200 checkArgument(resources.size() == consumers.size());
201
202 TransactionContext tx = service.transactionContextBuilder().build();
203 tx.begin();
204
205 try {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700206 TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
207 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700208 Iterator<ResourcePath> resourceIte = resources.iterator();
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700209 Iterator<ResourceConsumer> consumerIte = consumers.iterator();
210
211 while (resourceIte.hasNext() && consumerIte.hasNext()) {
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700212 ResourcePath resource = resourceIte.next();
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700213 ResourceConsumer consumer = consumerIte.next();
214
215 // if this single release fails (because the resource is allocated to another consumer,
216 // the whole release fails
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700217 if (!consumerTxMap.remove(resource, consumer)) {
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700218 return abortTransaction(tx);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700219 }
220 }
221
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700222 return commitTransaction(tx);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700223 } catch (TransactionException e) {
224 log.error("Exception thrown, abort the transaction", e);
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700225 return abortTransaction(tx);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700226 }
227 }
228
229 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700230 public Collection<ResourcePath> getResources(ResourceConsumer consumer) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700231 checkNotNull(consumer);
232
233 // NOTE: getting all entries may become performance bottleneck
234 // TODO: revisit for better backend data structure
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700235 return consumerMap.entrySet().stream()
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700236 .filter(x -> x.getValue().value().equals(consumer))
237 .map(Map.Entry::getKey)
238 .collect(Collectors.toList());
239 }
240
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700241 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700242 public <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) {
243 checkNotNull(parent);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700244 checkNotNull(cls);
245
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700246 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)
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700254 .collect(Collectors.toList());
255 }
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700256
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 }
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700278
279 /**
280 * Appends the values to the existing values associated with the specified key.
Sho SHIMIZU4568c412015-08-21 16:39:07 -0700281 * If the map already has all the given values, appending will not happen.
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700282 *
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 */
Sho SHIMIZU2a1b2332015-08-18 22:40:12 -0700290 private <K, V> boolean appendValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700291 List<V> oldValues = map.get(key);
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700292 if (oldValues == null) {
Sho SHIMIZU4568c412015-08-21 16:39:07 -0700293 return map.replace(key, oldValues, new ArrayList<>(values));
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700294 }
295
Sho SHIMIZU4568c412015-08-21 16:39:07 -0700296 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));
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700304 }
305
306 /**
Sho SHIMIZUba1f83b2015-10-14 08:11:20 -0700307 * Removes the values from the existing values associated with the specified key.
Sho SHIMIZU5618ee52015-08-21 17:19:44 -0700308 * If the map doesn't contain the given values, removal will not happen.
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700309 *
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);
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700319 if (oldValues == null) {
Sho SHIMIZU5618ee52015-08-21 17:19:44 -0700320 return map.replace(key, oldValues, new ArrayList<>());
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700321 }
322
Sho SHIMIZU5618ee52015-08-21 17:19:44 -0700323 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));
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700331 }
332
333 /**
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700334 * 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
Sho SHIMIZU01120782015-08-21 15:48:43 -0700342 if (resource.isRoot()) {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700343 return true;
344 }
345
346 List<ResourcePath> value = map.get(resource.parent().get());
347 return value != null && value.contains(resource);
348 }
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700349}