blob: c2a1a7c04cb2073f33e8098af35cb385682f0b33 [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;
Sho SHIMIZUe7db6142015-11-04 11:24:22 -080019import com.google.common.collect.ImmutableList;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070020import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Reference;
23import org.apache.felix.scr.annotations.ReferenceCardinality;
24import org.apache.felix.scr.annotations.Service;
Thomas Vachuska762a2d82016-01-04 10:25:20 -080025import org.onlab.util.Tools;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070026import org.onosproject.net.newresource.ResourceConsumer;
Sho SHIMIZUfa62b472015-11-02 17:35:46 -080027import org.onosproject.net.newresource.ResourceEvent;
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -070028import org.onosproject.net.newresource.ResourcePath;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070029import org.onosproject.net.newresource.ResourceStore;
Sho SHIMIZUfa62b472015-11-02 17:35:46 -080030import org.onosproject.net.newresource.ResourceStoreDelegate;
31import org.onosproject.store.AbstractStore;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070032import org.onosproject.store.serializers.KryoNamespaces;
33import org.onosproject.store.service.ConsistentMap;
Thomas Vachuska762a2d82016-01-04 10:25:20 -080034import org.onosproject.store.service.ConsistentMapException;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070035import org.onosproject.store.service.Serializer;
36import org.onosproject.store.service.StorageService;
37import org.onosproject.store.service.TransactionContext;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070038import org.onosproject.store.service.TransactionalMap;
39import org.onosproject.store.service.Versioned;
40import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070043import java.util.ArrayList;
44import java.util.Arrays;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070045import java.util.Collection;
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070046import java.util.Collections;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070047import java.util.Iterator;
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070048import java.util.LinkedHashSet;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070049import java.util.List;
50import java.util.Map;
51import java.util.Optional;
52import java.util.stream.Collectors;
53
54import static com.google.common.base.Preconditions.checkArgument;
55import static com.google.common.base.Preconditions.checkNotNull;
Sho SHIMIZUfa62b472015-11-02 17:35:46 -080056import static org.onosproject.net.newresource.ResourceEvent.Type.*;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070057
58/**
59 * Implementation of ResourceStore using TransactionalMap.
60 */
Sho SHIMIZU9a2b8292015-10-28 13:00:16 -070061@Component(immediate = true)
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070062@Service
63@Beta
Sho SHIMIZUfa62b472015-11-02 17:35:46 -080064public class ConsistentResourceStore extends AbstractStore<ResourceEvent, ResourceStoreDelegate>
65 implements ResourceStore {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070066 private static final Logger log = LoggerFactory.getLogger(ConsistentResourceStore.class);
67
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070068 private static final String CONSUMER_MAP = "onos-resource-consumers";
69 private static final String CHILD_MAP = "onos-resource-children";
70 private static final Serializer SERIALIZER = Serializer.using(
71 Arrays.asList(KryoNamespaces.BASIC, KryoNamespaces.API));
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070072
Thomas Vachuska762a2d82016-01-04 10:25:20 -080073 // TODO: We should provide centralized values for this
74 private static final int MAX_RETRIES = 5;
75 private static final int RETRY_DELAY = 1_000; // millis
76
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070077 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected StorageService service;
79
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070080 private ConsistentMap<ResourcePath, ResourceConsumer> consumerMap;
81 private ConsistentMap<ResourcePath, List<ResourcePath>> childMap;
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070082
83 @Activate
84 public void activate() {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -070085 consumerMap = service.<ResourcePath, ResourceConsumer>consistentMapBuilder()
86 .withName(CONSUMER_MAP)
87 .withSerializer(SERIALIZER)
88 .build();
89 childMap = service.<ResourcePath, List<ResourcePath>>consistentMapBuilder()
90 .withName(CHILD_MAP)
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070091 .withSerializer(SERIALIZER)
92 .build();
Sho SHIMIZUe7db6142015-11-04 11:24:22 -080093
Thomas Vachuska762a2d82016-01-04 10:25:20 -080094 Tools.retryable(() -> childMap.put(ResourcePath.ROOT, ImmutableList.of()),
95 ConsistentMapException.class, MAX_RETRIES, RETRY_DELAY);
Madan Jampanic7f49f92015-12-10 11:35:06 -080096 log.info("Started");
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -070097 }
98
99 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700100 public Optional<ResourceConsumer> getConsumer(ResourcePath resource) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700101 checkNotNull(resource);
102
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700103 Versioned<ResourceConsumer> consumer = consumerMap.get(resource);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700104 if (consumer == null) {
105 return Optional.empty();
106 }
107
108 return Optional.of(consumer.value());
109 }
110
111 @Override
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700112 public boolean register(List<ResourcePath> resources) {
113 checkNotNull(resources);
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700114
115 TransactionContext tx = service.transactionContextBuilder().build();
116 tx.begin();
117
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800118 TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
119 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700120
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800121 Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
122 .filter(x -> x.parent().isPresent())
123 .collect(Collectors.groupingBy(x -> x.parent().get()));
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700124
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800125 for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
126 if (!isRegistered(childTxMap, entry.getKey())) {
127 return abortTransaction(tx);
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700128 }
129
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800130 if (!appendValues(childTxMap, entry.getKey(), entry.getValue())) {
131 return abortTransaction(tx);
132 }
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700133 }
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800134
Sho SHIMIZUfa62b472015-11-02 17:35:46 -0800135 boolean success = tx.commit();
136 if (success) {
137 List<ResourceEvent> events = resources.stream()
138 .filter(x -> x.parent().isPresent())
139 .map(x -> new ResourceEvent(RESOURCE_ADDED, x))
140 .collect(Collectors.toList());
141 notifyDelegate(events);
142 }
143 return success;
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700144 }
145
146 @Override
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700147 public boolean unregister(List<ResourcePath> resources) {
148 checkNotNull(resources);
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700149
150 TransactionContext tx = service.transactionContextBuilder().build();
151 tx.begin();
152
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800153 TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
154 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
155 TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
156 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700157
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800158 Map<ResourcePath, List<ResourcePath>> resourceMap = resources.stream()
159 .filter(x -> x.parent().isPresent())
160 .collect(Collectors.groupingBy(x -> x.parent().get()));
Sho SHIMIZU83e17a02015-08-20 14:07:05 -0700161
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800162 // even if one of the resources is allocated to a consumer,
163 // all unregistrations are regarded as failure
164 for (Map.Entry<ResourcePath, List<ResourcePath>> entry: resourceMap.entrySet()) {
165 if (entry.getValue().stream().anyMatch(x -> consumerTxMap.get(x) != null)) {
166 return abortTransaction(tx);
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700167 }
168
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800169 if (!removeValues(childTxMap, entry.getKey(), entry.getValue())) {
170 return abortTransaction(tx);
171 }
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700172 }
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800173
Sho SHIMIZUfa62b472015-11-02 17:35:46 -0800174 boolean success = tx.commit();
175 if (success) {
176 List<ResourceEvent> events = resources.stream()
177 .filter(x -> x.parent().isPresent())
178 .map(x -> new ResourceEvent(RESOURCE_REMOVED, x))
179 .collect(Collectors.toList());
180 notifyDelegate(events);
181 }
182 return success;
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700183 }
184
185 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700186 public boolean allocate(List<ResourcePath> resources, ResourceConsumer consumer) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700187 checkNotNull(resources);
188 checkNotNull(consumer);
189
190 TransactionContext tx = service.transactionContextBuilder().build();
191 tx.begin();
192
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800193 TransactionalMap<ResourcePath, List<ResourcePath>> childTxMap =
194 tx.getTransactionalMap(CHILD_MAP, SERIALIZER);
195 TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
196 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700197
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800198 for (ResourcePath resource: resources) {
199 if (!isRegistered(childTxMap, resource)) {
200 return abortTransaction(tx);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700201 }
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700202
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800203 ResourceConsumer oldValue = consumerTxMap.put(resource, consumer);
204 if (oldValue != null) {
205 return abortTransaction(tx);
206 }
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700207 }
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800208
209 return tx.commit();
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700210 }
211
212 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700213 public boolean release(List<ResourcePath> resources, List<ResourceConsumer> consumers) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700214 checkNotNull(resources);
215 checkNotNull(consumers);
216 checkArgument(resources.size() == consumers.size());
217
218 TransactionContext tx = service.transactionContextBuilder().build();
219 tx.begin();
220
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800221 TransactionalMap<ResourcePath, ResourceConsumer> consumerTxMap =
222 tx.getTransactionalMap(CONSUMER_MAP, SERIALIZER);
223 Iterator<ResourcePath> resourceIte = resources.iterator();
224 Iterator<ResourceConsumer> consumerIte = consumers.iterator();
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700225
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800226 while (resourceIte.hasNext() && consumerIte.hasNext()) {
227 ResourcePath resource = resourceIte.next();
228 ResourceConsumer consumer = consumerIte.next();
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700229
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800230 // if this single release fails (because the resource is allocated to another consumer,
231 // the whole release fails
232 if (!consumerTxMap.remove(resource, consumer)) {
233 return abortTransaction(tx);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700234 }
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700235 }
Sho SHIMIZU1e0a34c2015-11-02 16:52:29 -0800236
237 return tx.commit();
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700238 }
239
240 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700241 public Collection<ResourcePath> getResources(ResourceConsumer consumer) {
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700242 checkNotNull(consumer);
243
244 // NOTE: getting all entries may become performance bottleneck
245 // TODO: revisit for better backend data structure
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700246 return consumerMap.entrySet().stream()
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700247 .filter(x -> x.getValue().value().equals(consumer))
248 .map(Map.Entry::getKey)
249 .collect(Collectors.toList());
250 }
251
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700252 @Override
Sho SHIMIZUe7f4f3f2015-10-13 16:27:25 -0700253 public Collection<ResourcePath> getChildResources(ResourcePath parent) {
254 checkNotNull(parent);
255
256 Versioned<List<ResourcePath>> children = childMap.get(parent);
257 if (children == null) {
258 return Collections.emptyList();
259 }
260
261 return children.value();
262 }
263
264 @Override
Sho SHIMIZU1f5e5912015-08-10 17:00:00 -0700265 public <T> Collection<ResourcePath> getAllocatedResources(ResourcePath parent, Class<T> cls) {
266 checkNotNull(parent);
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700267 checkNotNull(cls);
268
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700269 Versioned<List<ResourcePath>> children = childMap.get(parent);
270 if (children == null) {
271 return Collections.emptyList();
272 }
273
274 return children.value().stream()
Sho SHIMIZUc9546a32015-11-10 11:22:28 -0800275 .filter(x -> x.last().getClass().equals(cls))
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700276 .filter(consumerMap::containsKey)
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700277 .collect(Collectors.toList());
278 }
Sho SHIMIZUd29847f2015-08-13 09:10:59 -0700279
280 /**
281 * Abort the transaction.
282 *
283 * @param tx transaction context
284 * @return always false
285 */
286 private boolean abortTransaction(TransactionContext tx) {
287 tx.abort();
288 return false;
289 }
290
291 /**
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700292 * Appends the values to the existing values associated with the specified key.
Sho SHIMIZU4568c412015-08-21 16:39:07 -0700293 * If the map already has all the given values, appending will not happen.
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700294 *
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 */
Sho SHIMIZU2a1b2332015-08-18 22:40:12 -0700302 private <K, V> boolean appendValues(TransactionalMap<K, List<V>> map, K key, List<V> values) {
Sho SHIMIZU73130f72015-11-09 17:10:08 -0800303 List<V> oldValues = map.putIfAbsent(key, new ArrayList<>(values));
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700304 if (oldValues == null) {
Sho SHIMIZU93a74b32015-11-09 11:48:23 -0800305 return true;
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700306 }
307
Sho SHIMIZU4568c412015-08-21 16:39:07 -0700308 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));
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700316 }
317
318 /**
Sho SHIMIZUba1f83b2015-10-14 08:11:20 -0700319 * Removes the values from the existing values associated with the specified key.
Sho SHIMIZU5618ee52015-08-21 17:19:44 -0700320 * If the map doesn't contain the given values, removal will not happen.
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700321 *
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);
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700331 if (oldValues == null) {
Sho SHIMIZU93a74b32015-11-09 11:48:23 -0800332 map.put(key, new ArrayList<>());
333 return true;
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700334 }
335
Sho SHIMIZU5618ee52015-08-21 17:19:44 -0700336 LinkedHashSet<V> oldSet = new LinkedHashSet<>(oldValues);
337 if (values.stream().allMatch(x -> !oldSet.contains(x))) {
338 // don't write map because none of the values are stored
339 return true;
340 }
341
342 oldSet.removeAll(values);
343 return map.replace(key, oldValues, new ArrayList<>(oldSet));
Sho SHIMIZU2d8a13a2015-08-18 22:37:41 -0700344 }
345
346 /**
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700347 * Checks if the specified resource is registered as a child of a resource in the map.
348 *
349 * @param map map storing parent - child relationship of resources
350 * @param resource resource to be checked
351 * @return true if the resource is registered, false otherwise.
352 */
353 private boolean isRegistered(TransactionalMap<ResourcePath, List<ResourcePath>> map, ResourcePath resource) {
354 // root is always regarded to be registered
Sho SHIMIZUc9546a32015-11-10 11:22:28 -0800355 if (!resource.parent().isPresent()) {
Sho SHIMIZUba41fc12015-08-12 15:43:22 -0700356 return true;
357 }
358
359 List<ResourcePath> value = map.get(resource.parent().get());
360 return value != null && value.contains(resource);
361 }
Sho SHIMIZU78ee25c2015-07-16 15:54:14 -0700362}