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