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