blob: b40ec81e2bc842856644829a98bec6a4e1c9463c [file] [log] [blame]
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -08001/*
2 * Copyright 2014 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.onlab.onos.store.resource.impl;
17
18import java.util.ArrayList;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.HashMap;
22import java.util.HashSet;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26
27import org.apache.felix.scr.annotations.Activate;
28import org.apache.felix.scr.annotations.Component;
29import org.apache.felix.scr.annotations.Deactivate;
30import org.apache.felix.scr.annotations.Reference;
31import org.apache.felix.scr.annotations.ReferenceCardinality;
32import org.apache.felix.scr.annotations.Service;
Yuta HIGUCHIbf366d52014-12-02 12:57:22 -080033import org.onlab.onos.net.AnnotationKeys;
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -080034import org.onlab.onos.net.Link;
35import org.onlab.onos.net.LinkKey;
36import org.onlab.onos.net.intent.IntentId;
37import org.onlab.onos.net.link.LinkService;
38import org.onlab.onos.net.resource.Bandwidth;
39import org.onlab.onos.net.resource.BandwidthResourceAllocation;
40import org.onlab.onos.net.resource.Lambda;
41import org.onlab.onos.net.resource.LambdaResourceAllocation;
42import org.onlab.onos.net.resource.LinkResourceAllocations;
43import org.onlab.onos.net.resource.LinkResourceEvent;
44import org.onlab.onos.net.resource.LinkResourceStore;
45import org.onlab.onos.net.resource.ResourceAllocation;
46import org.onlab.onos.net.resource.ResourceType;
47import org.onlab.onos.store.StoreDelegate;
48import org.onlab.onos.store.hz.AbstractHazelcastStore;
49import org.onlab.onos.store.hz.STxMap;
50import org.slf4j.Logger;
51
52import com.google.common.collect.ImmutableList;
53import com.google.common.collect.ImmutableSet;
54import com.google.common.collect.Sets;
55import com.hazelcast.core.TransactionalMap;
56import com.hazelcast.transaction.TransactionContext;
57import com.hazelcast.transaction.TransactionException;
58import com.hazelcast.transaction.TransactionOptions;
59import com.hazelcast.transaction.TransactionOptions.TransactionType;
60
61import static com.google.common.base.Preconditions.checkNotNull;
62import static com.google.common.base.Preconditions.checkState;
63import static org.slf4j.LoggerFactory.getLogger;
64
65/**
66 * Manages link resources using Hazelcast.
67 */
Yuta HIGUCHI9b108b32014-12-01 11:10:26 -080068@Component(immediate = true, enabled = true)
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -080069@Service
70public class HazelcastLinkResourceStore
71 extends AbstractHazelcastStore<LinkResourceEvent, StoreDelegate<LinkResourceEvent>>
72 implements LinkResourceStore {
73
74
75 private final Logger log = getLogger(getClass());
76
77 // FIXME: what is the Bandwidth unit?
78 private static final Bandwidth DEFAULT_BANDWIDTH = Bandwidth.valueOf(1_000);
79
80 private static final Bandwidth EMPTY_BW = Bandwidth.valueOf(0);
81
82 // table to store current allocations
83 /** LinkKey -> List<LinkResourceAllocations>. */
84 private static final String LINK_RESOURCE_ALLOCATIONS = "LinkResourceAllocations";
85
86 /** IntentId -> LinkResourceAllocations. */
87 private static final String INTENT_ALLOCATIONS = "IntentAllocations";
88
89
90 // TODO make this configurable
91 // number of retries to attempt on allocation failure, due to
92 // concurrent update
93 private static int maxAllocateRetries = 5;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected LinkService linkService;
97
98 // Link annotation key name to use as bandwidth
Yuta HIGUCHIbf366d52014-12-02 12:57:22 -080099 private String bandwidthAnnotation = AnnotationKeys.BANDWIDTH;
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800100 // Link annotation key name to use as max lambda
Yuta HIGUCHIbf366d52014-12-02 12:57:22 -0800101 private String wavesAnnotation = AnnotationKeys.OPTICAL_WAVES;
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800102
103 @Override
104 @Activate
105 public void activate() {
106 super.activate();
107 log.info("Started");
108 }
109
110 @Deactivate
111 public void deactivate() {
112 log.info("Stopped");
113 }
114
115 private STxMap<IntentId, LinkResourceAllocations> getIntentAllocs(TransactionContext tx) {
116 TransactionalMap<byte[], byte[]> raw = tx.getMap(INTENT_ALLOCATIONS);
117 return new STxMap<>(raw, serializer);
118 }
119
120 private STxMap<LinkKey, List<LinkResourceAllocations>> getLinkAllocs(TransactionContext tx) {
121 TransactionalMap<byte[], byte[]> raw = tx.getMap(LINK_RESOURCE_ALLOCATIONS);
122 return new STxMap<>(raw, serializer);
123 }
124
125 private Set<? extends ResourceAllocation> getResourceCapacity(ResourceType type, Link link) {
126 // TODO: plugin/provider mechanism to add resource type in the future?
127 if (type == ResourceType.BANDWIDTH) {
128 return ImmutableSet.of(getBandwidthResourceCapacity(link));
129 }
130 if (type == ResourceType.LAMBDA) {
131 return getLambdaResourceCapacity(link);
132 }
133 return null;
134 }
135
136 private Set<LambdaResourceAllocation> getLambdaResourceCapacity(Link link) {
137 // FIXME enumerate all the possible link/port lambdas
138 Set<LambdaResourceAllocation> allocations = new HashSet<>();
139 try {
140 final int waves = Integer.parseInt(link.annotations().value(wavesAnnotation));
141 for (int i = 1; i <= waves; i++) {
142 allocations.add(new LambdaResourceAllocation(Lambda.valueOf(i)));
143 }
144 } catch (NumberFormatException e) {
145 log.debug("No {} annotation on link %s", wavesAnnotation, link);
146 }
147 return allocations;
148 }
149
150 private BandwidthResourceAllocation getBandwidthResourceCapacity(Link link) {
151
152 // if Link annotation exist, use them
153 // if all fails, use DEFAULT_BANDWIDTH
154
155 Bandwidth bandwidth = null;
156 String strBw = link.annotations().value(bandwidthAnnotation);
157 if (strBw != null) {
158 try {
159 bandwidth = Bandwidth.valueOf(Double.parseDouble(strBw));
160 } catch (NumberFormatException e) {
161 // do nothings
162 bandwidth = null;
163 }
164 }
165
166 if (bandwidth == null) {
167 // fall back, use fixed default
168 bandwidth = DEFAULT_BANDWIDTH;
169 }
170 return new BandwidthResourceAllocation(bandwidth);
171 }
172
173 private Map<ResourceType, Set<? extends ResourceAllocation>> getResourceCapacity(Link link) {
174 Map<ResourceType, Set<? extends ResourceAllocation>> caps = new HashMap<>();
175 for (ResourceType type : ResourceType.values()) {
176 Set<? extends ResourceAllocation> cap = getResourceCapacity(type, link);
177 if (cap != null) {
178 caps.put(type, cap);
179 }
180 }
181 return caps;
182 }
183
184 @Override
185 public Set<ResourceAllocation> getFreeResources(Link link) {
Yuta HIGUCHIbd1aee12014-12-01 20:05:47 -0800186 TransactionOptions opt = new TransactionOptions();
187 // read-only and will never be commited, thus does not need durability
188 opt.setTransactionType(TransactionType.LOCAL);
189 TransactionContext tx = theInstance.newTransactionContext(opt);
190 tx.beginTransaction();
191 try {
192 Map<ResourceType, Set<? extends ResourceAllocation>> freeResources = getFreeResourcesEx(tx, link);
193 Set<ResourceAllocation> allFree = new HashSet<>();
194 for (Set<? extends ResourceAllocation> r : freeResources.values()) {
195 allFree.addAll(r);
196 }
197 return allFree;
198 } finally {
199 tx.rollbackTransaction();
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800200 }
Yuta HIGUCHIbd1aee12014-12-01 20:05:47 -0800201
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800202 }
203
Yuta HIGUCHIbd1aee12014-12-01 20:05:47 -0800204 private Map<ResourceType, Set<? extends ResourceAllocation>> getFreeResourcesEx(TransactionContext tx, Link link) {
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800205 // returns capacity - allocated
206
207 checkNotNull(link);
208 Map<ResourceType, Set<? extends ResourceAllocation>> free = new HashMap<>();
209 final Map<ResourceType, Set<? extends ResourceAllocation>> caps = getResourceCapacity(link);
Yuta HIGUCHIbd1aee12014-12-01 20:05:47 -0800210 final Iterable<LinkResourceAllocations> allocations = getAllocations(tx, link);
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800211
212 for (ResourceType type : ResourceType.values()) {
213 // there should be class/category of resources
214 switch (type) {
215 case BANDWIDTH:
216 {
217 Set<? extends ResourceAllocation> bw = caps.get(ResourceType.BANDWIDTH);
218 if (bw == null || bw.isEmpty()) {
219 bw = Sets.newHashSet(new BandwidthResourceAllocation(EMPTY_BW));
220 }
221
222 BandwidthResourceAllocation cap = (BandwidthResourceAllocation) bw.iterator().next();
223 double freeBw = cap.bandwidth().toDouble();
224
225 // enumerate current allocations, subtracting resources
226 for (LinkResourceAllocations alloc : allocations) {
227 Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
228 for (ResourceAllocation a : types) {
229 if (a instanceof BandwidthResourceAllocation) {
230 BandwidthResourceAllocation bwA = (BandwidthResourceAllocation) a;
231 freeBw -= bwA.bandwidth().toDouble();
232 }
233 }
234 }
235
236 free.put(type, Sets.newHashSet(new BandwidthResourceAllocation(Bandwidth.valueOf(freeBw))));
237 break;
238 }
239
240 case LAMBDA:
241 {
242 Set<? extends ResourceAllocation> lmd = caps.get(type);
243 if (lmd == null || lmd.isEmpty()) {
244 // nothing left
245 break;
246 }
247 Set<LambdaResourceAllocation> freeL = new HashSet<>();
248 for (ResourceAllocation r : lmd) {
249 if (r instanceof LambdaResourceAllocation) {
250 freeL.add((LambdaResourceAllocation) r);
251 }
252 }
253
254 // enumerate current allocations, removing resources
255 for (LinkResourceAllocations alloc : allocations) {
256 Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
257 for (ResourceAllocation a : types) {
258 if (a instanceof LambdaResourceAllocation) {
259 freeL.remove(a);
260 }
261 }
262 }
263
264 free.put(type, freeL);
265 break;
266 }
267
268 default:
269 break;
270 }
271 }
272 return free;
273 }
274
275 @Override
276 public void allocateResources(LinkResourceAllocations allocations) {
277 checkNotNull(allocations);
278
279 for (int i = 0; i < maxAllocateRetries; ++i) {
280 TransactionContext tx = theInstance.newTransactionContext();
281 tx.beginTransaction();
282 try {
283
284 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
285 // should this be conditional write?
286 intentAllocs.put(allocations.intendId(), allocations);
287
288 for (Link link : allocations.links()) {
289 allocateLinkResource(tx, link, allocations);
290 }
291
292 tx.commitTransaction();
293 return;
294 } catch (TransactionException e) {
295 log.debug("Failed to commit allocations for {}. [retry={}]",
296 allocations.intendId(), i);
297 log.trace(" details {} ", allocations, e);
298 continue;
299 } catch (Exception e) {
300 log.error("Exception thrown, rolling back", e);
301 tx.rollbackTransaction();
302 throw e;
303 }
304 }
305 }
306
307 private void allocateLinkResource(TransactionContext tx, Link link,
308 LinkResourceAllocations allocations) {
309
310 // requested resources
311 Set<ResourceAllocation> reqs = allocations.getResourceAllocation(link);
312
Yuta HIGUCHIbd1aee12014-12-01 20:05:47 -0800313 Map<ResourceType, Set<? extends ResourceAllocation>> available = getFreeResourcesEx(tx, link);
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800314 for (ResourceAllocation req : reqs) {
315 Set<? extends ResourceAllocation> avail = available.get(req.type());
316 if (req instanceof BandwidthResourceAllocation) {
317 // check if allocation should be accepted
318 if (avail.isEmpty()) {
319 checkState(!avail.isEmpty(),
320 "There's no Bandwidth resource on %s?",
321 link);
322 }
323 BandwidthResourceAllocation bw = (BandwidthResourceAllocation) avail.iterator().next();
324 double bwLeft = bw.bandwidth().toDouble();
325 bwLeft -= ((BandwidthResourceAllocation) req).bandwidth().toDouble();
326 if (bwLeft < 0) {
327 // FIXME throw appropriate Exception
328 checkState(bwLeft >= 0,
329 "There's no Bandwidth left on %s. %s",
330 link, bwLeft);
331 }
332 } else if (req instanceof LambdaResourceAllocation) {
333
334 // check if allocation should be accepted
335 if (!avail.contains(req)) {
336 // requested lambda was not available
337 // FIXME throw appropriate exception
338 checkState(avail.contains(req),
339 "Allocating %s on %s failed",
340 req, link);
341 }
342 }
343 }
344 // all requests allocatable => add allocation
345 final LinkKey linkKey = LinkKey.linkKey(link);
346 STxMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
347 final List<LinkResourceAllocations> before = linkAllocs.get(linkKey);
348 List<LinkResourceAllocations> after = new ArrayList<>(before.size() + 1);
349 after.addAll(before);
350 after.add(allocations);
351 linkAllocs.replace(linkKey, before, after);
352 }
353
354 @Override
355 public LinkResourceEvent releaseResources(LinkResourceAllocations allocations) {
356 checkNotNull(allocations);
357
358 final IntentId intendId = allocations.intendId();
359 final Collection<Link> links = allocations.links();
360
361 boolean success = false;
362 do {
363 // TODO: smaller tx unit to lower the chance of collisions?
364 TransactionContext tx = theInstance.newTransactionContext();
365 tx.beginTransaction();
366 try {
367 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
368 intentAllocs.remove(intendId);
369
370 STxMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
371
372 for (Link link : links) {
373 final LinkKey linkId = LinkKey.linkKey(link);
374
375 List<LinkResourceAllocations> before = linkAllocs.get(linkId);
376 if (before == null || before.isEmpty()) {
377 // something is wrong, but it is already freed
378 log.warn("There was no resource left to release on {}", linkId);
379 continue;
380 }
381 List<LinkResourceAllocations> after = new ArrayList<>(before);
382 after.remove(allocations);
383 linkAllocs.replace(linkId, before, after);
384 }
385
386 tx.commitTransaction();
387 success = true;
388 } catch (TransactionException e) {
389 log.debug("Transaction failed, retrying");
390 } catch (Exception e) {
391 log.error("Exception thrown during releaseResource {}",
392 allocations, e);
393 tx.rollbackTransaction();
394 throw e;
395 }
396 } while (!success);
397
398 // Issue events to force recompilation of intents.
399 final List<LinkResourceAllocations> releasedResources =
400 ImmutableList.of(allocations);
401 return new LinkResourceEvent(
402 LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE,
403 releasedResources);
404 }
405
406 @Override
407 public LinkResourceAllocations getAllocations(IntentId intentId) {
408 checkNotNull(intentId);
409 TransactionOptions opt = new TransactionOptions();
410 // read-only and will never be commited, thus does not need durability
411 opt.setTransactionType(TransactionType.LOCAL);
412 TransactionContext tx = theInstance.newTransactionContext(opt);
413 tx.beginTransaction();
414 try {
415 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
416 return intentAllocs.get(intentId);
417 } finally {
418 tx.rollbackTransaction();
419 }
420 }
421
422 @Override
423 public List<LinkResourceAllocations> getAllocations(Link link) {
424 checkNotNull(link);
425 final LinkKey key = LinkKey.linkKey(link);
426
427 TransactionOptions opt = new TransactionOptions();
428 // read-only and will never be commited, thus does not need durability
429 opt.setTransactionType(TransactionType.LOCAL);
430 TransactionContext tx = theInstance.newTransactionContext(opt);
431 tx.beginTransaction();
432 List<LinkResourceAllocations> res = null;
433 try {
434 STxMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
435 res = linkAllocs.get(key);
436 } finally {
437 tx.rollbackTransaction();
438 }
439
440 if (res == null) {
441 // try to add empty list
442 TransactionContext tx2 = theInstance.newTransactionContext();
443 tx2.beginTransaction();
444 try {
445 res = getLinkAllocs(tx2).putIfAbsent(key, new ArrayList<>());
446 tx2.commitTransaction();
447 if (res == null) {
448 return Collections.emptyList();
449 } else {
450 return res;
451 }
452 } catch (TransactionException e) {
453 // concurrently added?
454 return getAllocations(link);
455 } catch (Exception e) {
456 tx.rollbackTransaction();
457 }
458 }
459 return res;
Yuta HIGUCHIbd1aee12014-12-01 20:05:47 -0800460 }
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800461
Yuta HIGUCHIbd1aee12014-12-01 20:05:47 -0800462 private Iterable<LinkResourceAllocations> getAllocations(TransactionContext tx,
463 Link link) {
464 checkNotNull(tx);
465 checkNotNull(link);
466 final LinkKey key = LinkKey.linkKey(link);
467
468 STxMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
469 List<LinkResourceAllocations> res = null;
470 res = linkAllocs.get(key);
471 if (res == null) {
472 res = linkAllocs.putIfAbsent(key, new ArrayList<LinkResourceAllocations>());
473 if (res == null) {
474 return Collections.emptyList();
475 } else {
476 return res;
477 }
478 }
479 return null;
Yuta HIGUCHI3cc4d9b2014-11-29 18:17:17 -0800480 }
481
482 @Override
483 public Iterable<LinkResourceAllocations> getAllocations() {
484 TransactionContext tx = theInstance.newTransactionContext();
485 tx.beginTransaction();
486 try {
487 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
488 return intentAllocs.values();
489 } finally {
490 tx.rollbackTransaction();
491 }
492 }
493}