blob: 4729a84b4e2b0838f420f9324efc574b3fa4e2db [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 */
67@Component(immediate = true, enabled = false)
68@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) {
185 Map<ResourceType, Set<? extends ResourceAllocation>> freeResources = getFreeResourcesEx(link);
186 Set<ResourceAllocation> allFree = new HashSet<>();
187 for (Set<? extends ResourceAllocation> r:freeResources.values()) {
188 allFree.addAll(r);
189 }
190 return allFree;
191 }
192
193 private Map<ResourceType, Set<? extends ResourceAllocation>> getFreeResourcesEx(Link link) {
194 // returns capacity - allocated
195
196 checkNotNull(link);
197 Map<ResourceType, Set<? extends ResourceAllocation>> free = new HashMap<>();
198 final Map<ResourceType, Set<? extends ResourceAllocation>> caps = getResourceCapacity(link);
199 final Iterable<LinkResourceAllocations> allocations = getAllocations(link);
200
201 for (ResourceType type : ResourceType.values()) {
202 // there should be class/category of resources
203 switch (type) {
204 case BANDWIDTH:
205 {
206 Set<? extends ResourceAllocation> bw = caps.get(ResourceType.BANDWIDTH);
207 if (bw == null || bw.isEmpty()) {
208 bw = Sets.newHashSet(new BandwidthResourceAllocation(EMPTY_BW));
209 }
210
211 BandwidthResourceAllocation cap = (BandwidthResourceAllocation) bw.iterator().next();
212 double freeBw = cap.bandwidth().toDouble();
213
214 // enumerate current allocations, subtracting resources
215 for (LinkResourceAllocations alloc : allocations) {
216 Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
217 for (ResourceAllocation a : types) {
218 if (a instanceof BandwidthResourceAllocation) {
219 BandwidthResourceAllocation bwA = (BandwidthResourceAllocation) a;
220 freeBw -= bwA.bandwidth().toDouble();
221 }
222 }
223 }
224
225 free.put(type, Sets.newHashSet(new BandwidthResourceAllocation(Bandwidth.valueOf(freeBw))));
226 break;
227 }
228
229 case LAMBDA:
230 {
231 Set<? extends ResourceAllocation> lmd = caps.get(type);
232 if (lmd == null || lmd.isEmpty()) {
233 // nothing left
234 break;
235 }
236 Set<LambdaResourceAllocation> freeL = new HashSet<>();
237 for (ResourceAllocation r : lmd) {
238 if (r instanceof LambdaResourceAllocation) {
239 freeL.add((LambdaResourceAllocation) r);
240 }
241 }
242
243 // enumerate current allocations, removing resources
244 for (LinkResourceAllocations alloc : allocations) {
245 Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
246 for (ResourceAllocation a : types) {
247 if (a instanceof LambdaResourceAllocation) {
248 freeL.remove(a);
249 }
250 }
251 }
252
253 free.put(type, freeL);
254 break;
255 }
256
257 default:
258 break;
259 }
260 }
261 return free;
262 }
263
264 @Override
265 public void allocateResources(LinkResourceAllocations allocations) {
266 checkNotNull(allocations);
267
268 for (int i = 0; i < maxAllocateRetries; ++i) {
269 TransactionContext tx = theInstance.newTransactionContext();
270 tx.beginTransaction();
271 try {
272
273 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
274 // should this be conditional write?
275 intentAllocs.put(allocations.intendId(), allocations);
276
277 for (Link link : allocations.links()) {
278 allocateLinkResource(tx, link, allocations);
279 }
280
281 tx.commitTransaction();
282 return;
283 } catch (TransactionException e) {
284 log.debug("Failed to commit allocations for {}. [retry={}]",
285 allocations.intendId(), i);
286 log.trace(" details {} ", allocations, e);
287 continue;
288 } catch (Exception e) {
289 log.error("Exception thrown, rolling back", e);
290 tx.rollbackTransaction();
291 throw e;
292 }
293 }
294 }
295
296 private void allocateLinkResource(TransactionContext tx, Link link,
297 LinkResourceAllocations allocations) {
298
299 // requested resources
300 Set<ResourceAllocation> reqs = allocations.getResourceAllocation(link);
301
302 Map<ResourceType, Set<? extends ResourceAllocation>> available = getFreeResourcesEx(link);
303 for (ResourceAllocation req : reqs) {
304 Set<? extends ResourceAllocation> avail = available.get(req.type());
305 if (req instanceof BandwidthResourceAllocation) {
306 // check if allocation should be accepted
307 if (avail.isEmpty()) {
308 checkState(!avail.isEmpty(),
309 "There's no Bandwidth resource on %s?",
310 link);
311 }
312 BandwidthResourceAllocation bw = (BandwidthResourceAllocation) avail.iterator().next();
313 double bwLeft = bw.bandwidth().toDouble();
314 bwLeft -= ((BandwidthResourceAllocation) req).bandwidth().toDouble();
315 if (bwLeft < 0) {
316 // FIXME throw appropriate Exception
317 checkState(bwLeft >= 0,
318 "There's no Bandwidth left on %s. %s",
319 link, bwLeft);
320 }
321 } else if (req instanceof LambdaResourceAllocation) {
322
323 // check if allocation should be accepted
324 if (!avail.contains(req)) {
325 // requested lambda was not available
326 // FIXME throw appropriate exception
327 checkState(avail.contains(req),
328 "Allocating %s on %s failed",
329 req, link);
330 }
331 }
332 }
333 // all requests allocatable => add allocation
334 final LinkKey linkKey = LinkKey.linkKey(link);
335 STxMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
336 final List<LinkResourceAllocations> before = linkAllocs.get(linkKey);
337 List<LinkResourceAllocations> after = new ArrayList<>(before.size() + 1);
338 after.addAll(before);
339 after.add(allocations);
340 linkAllocs.replace(linkKey, before, after);
341 }
342
343 @Override
344 public LinkResourceEvent releaseResources(LinkResourceAllocations allocations) {
345 checkNotNull(allocations);
346
347 final IntentId intendId = allocations.intendId();
348 final Collection<Link> links = allocations.links();
349
350 boolean success = false;
351 do {
352 // TODO: smaller tx unit to lower the chance of collisions?
353 TransactionContext tx = theInstance.newTransactionContext();
354 tx.beginTransaction();
355 try {
356 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
357 intentAllocs.remove(intendId);
358
359 STxMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
360
361 for (Link link : links) {
362 final LinkKey linkId = LinkKey.linkKey(link);
363
364 List<LinkResourceAllocations> before = linkAllocs.get(linkId);
365 if (before == null || before.isEmpty()) {
366 // something is wrong, but it is already freed
367 log.warn("There was no resource left to release on {}", linkId);
368 continue;
369 }
370 List<LinkResourceAllocations> after = new ArrayList<>(before);
371 after.remove(allocations);
372 linkAllocs.replace(linkId, before, after);
373 }
374
375 tx.commitTransaction();
376 success = true;
377 } catch (TransactionException e) {
378 log.debug("Transaction failed, retrying");
379 } catch (Exception e) {
380 log.error("Exception thrown during releaseResource {}",
381 allocations, e);
382 tx.rollbackTransaction();
383 throw e;
384 }
385 } while (!success);
386
387 // Issue events to force recompilation of intents.
388 final List<LinkResourceAllocations> releasedResources =
389 ImmutableList.of(allocations);
390 return new LinkResourceEvent(
391 LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE,
392 releasedResources);
393 }
394
395 @Override
396 public LinkResourceAllocations getAllocations(IntentId intentId) {
397 checkNotNull(intentId);
398 TransactionOptions opt = new TransactionOptions();
399 // read-only and will never be commited, thus does not need durability
400 opt.setTransactionType(TransactionType.LOCAL);
401 TransactionContext tx = theInstance.newTransactionContext(opt);
402 tx.beginTransaction();
403 try {
404 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
405 return intentAllocs.get(intentId);
406 } finally {
407 tx.rollbackTransaction();
408 }
409 }
410
411 @Override
412 public List<LinkResourceAllocations> getAllocations(Link link) {
413 checkNotNull(link);
414 final LinkKey key = LinkKey.linkKey(link);
415
416 TransactionOptions opt = new TransactionOptions();
417 // read-only and will never be commited, thus does not need durability
418 opt.setTransactionType(TransactionType.LOCAL);
419 TransactionContext tx = theInstance.newTransactionContext(opt);
420 tx.beginTransaction();
421 List<LinkResourceAllocations> res = null;
422 try {
423 STxMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
424 res = linkAllocs.get(key);
425 } finally {
426 tx.rollbackTransaction();
427 }
428
429 if (res == null) {
430 // try to add empty list
431 TransactionContext tx2 = theInstance.newTransactionContext();
432 tx2.beginTransaction();
433 try {
434 res = getLinkAllocs(tx2).putIfAbsent(key, new ArrayList<>());
435 tx2.commitTransaction();
436 if (res == null) {
437 return Collections.emptyList();
438 } else {
439 return res;
440 }
441 } catch (TransactionException e) {
442 // concurrently added?
443 return getAllocations(link);
444 } catch (Exception e) {
445 tx.rollbackTransaction();
446 }
447 }
448 return res;
449
450 }
451
452 @Override
453 public Iterable<LinkResourceAllocations> getAllocations() {
454 TransactionContext tx = theInstance.newTransactionContext();
455 tx.beginTransaction();
456 try {
457 STxMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
458 return intentAllocs.values();
459 } finally {
460 tx.rollbackTransaction();
461 }
462 }
463}