ce25f8683c1de1f11574322cc84c4c738f471315
[onosfw.git] /
1 package org.onosproject.store.resource.impl;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.stream.Collectors;
12
13 import org.apache.felix.scr.annotations.Component;
14 import org.apache.felix.scr.annotations.Reference;
15 import org.apache.felix.scr.annotations.ReferenceCardinality;
16 import org.apache.felix.scr.annotations.Service;
17 import org.apache.felix.scr.annotations.Activate;
18 import org.apache.felix.scr.annotations.Deactivate;
19 import org.onlab.util.Bandwidth;
20 import org.onosproject.net.OmsPort;
21 import org.onosproject.net.device.DeviceService;
22 import org.slf4j.Logger;
23 import org.onlab.util.PositionalParameterStringFormatter;
24 import org.onosproject.net.Link;
25 import org.onosproject.net.LinkKey;
26 import org.onosproject.net.Port;
27 import org.onosproject.net.intent.IntentId;
28 import org.onosproject.net.link.LinkService;
29 import org.onosproject.net.resource.link.BandwidthResource;
30 import org.onosproject.net.resource.link.BandwidthResourceAllocation;
31 import org.onosproject.net.resource.link.LambdaResource;
32 import org.onosproject.net.resource.link.LambdaResourceAllocation;
33 import org.onosproject.net.resource.link.LinkResourceAllocations;
34 import org.onosproject.net.resource.link.LinkResourceEvent;
35 import org.onosproject.net.resource.link.LinkResourceStore;
36 import org.onosproject.net.resource.link.LinkResourceStoreDelegate;
37 import org.onosproject.net.resource.link.MplsLabel;
38 import org.onosproject.net.resource.link.MplsLabelResourceAllocation;
39 import org.onosproject.net.resource.ResourceAllocation;
40 import org.onosproject.net.resource.ResourceAllocationException;
41 import org.onosproject.net.resource.ResourceType;
42 import org.onosproject.store.AbstractStore;
43 import org.onosproject.store.serializers.KryoNamespaces;
44 import org.onosproject.store.service.ConsistentMap;
45 import org.onosproject.store.service.Serializer;
46 import org.onosproject.store.service.StorageService;
47 import org.onosproject.store.service.TransactionContext;
48 import org.onosproject.store.service.TransactionException;
49 import org.onosproject.store.service.TransactionalMap;
50 import org.onosproject.store.service.Versioned;
51
52 import com.google.common.collect.ImmutableList;
53 import com.google.common.collect.ImmutableSet;
54 import com.google.common.collect.Sets;
55
56 import static com.google.common.base.Preconditions.checkNotNull;
57 import static com.google.common.base.Preconditions.checkState;
58 import static org.slf4j.LoggerFactory.getLogger;
59 import static org.onosproject.net.AnnotationKeys.BANDWIDTH;
60
61 /**
62  * Store that manages link resources using Copycat-backed TransactionalMaps.
63  */
64 @Component(immediate = true, enabled = true)
65 @Service
66 public class ConsistentLinkResourceStore extends
67         AbstractStore<LinkResourceEvent, LinkResourceStoreDelegate> implements
68         LinkResourceStore {
69
70     private final Logger log = getLogger(getClass());
71
72     private static final BandwidthResource DEFAULT_BANDWIDTH = new BandwidthResource(Bandwidth.mbps(1_000));
73     private static final BandwidthResource EMPTY_BW = new BandwidthResource(Bandwidth.bps(0));
74
75     // Smallest non-reserved MPLS label
76     private static final int MIN_UNRESERVED_LABEL = 0x10;
77     // Max non-reserved MPLS label = 239
78     private static final int MAX_UNRESERVED_LABEL = 0xEF;
79
80     // table to store current allocations
81     /** LinkKey -> List<LinkResourceAllocations>. */
82     private static final String LINK_RESOURCE_ALLOCATIONS = "LinkAllocations";
83
84     /** IntentId -> LinkResourceAllocations. */
85     private static final String INTENT_ALLOCATIONS = "LinkIntentAllocations";
86
87     private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
88
89     // for reading committed values.
90     private ConsistentMap<IntentId, LinkResourceAllocations> intentAllocMap;
91
92     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93     protected StorageService storageService;
94
95     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96     protected LinkService linkService;
97
98     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99     protected DeviceService deviceService;
100
101     @Activate
102     public void activate() {
103         intentAllocMap = storageService.<IntentId, LinkResourceAllocations>consistentMapBuilder()
104                 .withName(INTENT_ALLOCATIONS)
105                 .withSerializer(SERIALIZER)
106                 .build();
107         log.info("Started");
108     }
109
110     @Deactivate
111     public void deactivate() {
112         log.info("Stopped");
113     }
114
115     private TransactionalMap<IntentId, LinkResourceAllocations> getIntentAllocs(TransactionContext tx) {
116         return tx.getTransactionalMap(INTENT_ALLOCATIONS, SERIALIZER);
117     }
118
119     private TransactionalMap<LinkKey, List<LinkResourceAllocations>> getLinkAllocs(TransactionContext tx) {
120         return tx.getTransactionalMap(LINK_RESOURCE_ALLOCATIONS, SERIALIZER);
121     }
122
123     private TransactionContext getTxContext() {
124         return storageService.transactionContextBuilder().build();
125     }
126
127     private Set<? extends ResourceAllocation> getResourceCapacity(ResourceType type, Link link) {
128         if (type == ResourceType.BANDWIDTH) {
129             return ImmutableSet.of(getBandwidthResourceCapacity(link));
130         }
131         if (type == ResourceType.LAMBDA) {
132             return getLambdaResourceCapacity(link);
133         }
134         if (type == ResourceType.MPLS_LABEL) {
135             return getMplsResourceCapacity();
136         }
137         return ImmutableSet.of();
138     }
139
140     private Set<LambdaResourceAllocation> getLambdaResourceCapacity(Link link) {
141         Set<LambdaResourceAllocation> allocations = new HashSet<>();
142         Port port = deviceService.getPort(link.src().deviceId(), link.src().port());
143         if (port instanceof OmsPort) {
144             OmsPort omsPort = (OmsPort) port;
145
146             // Assume fixed grid for now
147             for (int i = 0; i < omsPort.totalChannels(); i++) {
148                 allocations.add(new LambdaResourceAllocation(LambdaResource.valueOf(i)));
149             }
150         }
151         return allocations;
152     }
153
154     private BandwidthResourceAllocation getBandwidthResourceCapacity(Link link) {
155
156         // if Link annotation exist, use them
157         // if all fails, use DEFAULT_BANDWIDTH
158         BandwidthResource bandwidth = null;
159         String strBw = link.annotations().value(BANDWIDTH);
160         if (strBw != null) {
161             try {
162                 bandwidth = new BandwidthResource(Bandwidth.mbps(Double.parseDouble(strBw)));
163             } catch (NumberFormatException e) {
164                 // do nothings
165                 bandwidth = null;
166             }
167         }
168
169         if (bandwidth == null) {
170             // fall back, use fixed default
171             bandwidth = DEFAULT_BANDWIDTH;
172         }
173         return new BandwidthResourceAllocation(bandwidth);
174     }
175
176     private Set<MplsLabelResourceAllocation> getMplsResourceCapacity() {
177         Set<MplsLabelResourceAllocation> allocations = new HashSet<>();
178         //Ignoring reserved labels of 0 through 15
179         for (int i = MIN_UNRESERVED_LABEL; i <= MAX_UNRESERVED_LABEL; i++) {
180             allocations.add(new MplsLabelResourceAllocation(MplsLabel
181                     .valueOf(i)));
182
183         }
184         return allocations;
185     }
186
187     private Map<ResourceType, Set<? extends ResourceAllocation>> getResourceCapacity(Link link) {
188         Map<ResourceType, Set<? extends ResourceAllocation>> caps = new HashMap<>();
189         for (ResourceType type : ResourceType.values()) {
190             Set<? extends ResourceAllocation> cap = getResourceCapacity(type, link);
191             if (cap != null) {
192                 caps.put(type, cap);
193             }
194         }
195         return caps;
196     }
197
198     @Override
199     public Set<ResourceAllocation> getFreeResources(Link link) {
200         TransactionContext tx = getTxContext();
201
202         tx.begin();
203         try {
204             Map<ResourceType, Set<? extends ResourceAllocation>> freeResources = getFreeResourcesEx(tx, link);
205             Set<ResourceAllocation> allFree = new HashSet<>();
206             freeResources.values().forEach(allFree::addAll);
207             return allFree;
208         } finally {
209             tx.abort();
210         }
211     }
212
213     private Map<ResourceType, Set<? extends ResourceAllocation>> getFreeResourcesEx(TransactionContext tx, Link link) {
214         checkNotNull(tx);
215         checkNotNull(link);
216
217         Map<ResourceType, Set<? extends ResourceAllocation>> free = new HashMap<>();
218         final Map<ResourceType, Set<? extends ResourceAllocation>> caps = getResourceCapacity(link);
219         final Iterable<LinkResourceAllocations> allocations = getAllocations(tx, link);
220
221         for (ResourceType type : ResourceType.values()) {
222             // there should be class/category of resources
223
224             switch (type) {
225                 case BANDWIDTH:
226                     Set<? extends ResourceAllocation> bw = caps.get(type);
227                     if (bw == null || bw.isEmpty()) {
228                         bw = Sets.newHashSet(new BandwidthResourceAllocation(EMPTY_BW));
229                     }
230
231                     BandwidthResourceAllocation cap = (BandwidthResourceAllocation) bw.iterator().next();
232                     double freeBw = cap.bandwidth().toDouble();
233
234                     // enumerate current allocations, subtracting resources
235                     for (LinkResourceAllocations alloc : allocations) {
236                         Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
237                         for (ResourceAllocation a : types) {
238                             if (a instanceof BandwidthResourceAllocation) {
239                                 BandwidthResourceAllocation bwA = (BandwidthResourceAllocation) a;
240                                 freeBw -= bwA.bandwidth().toDouble();
241                             }
242                         }
243                     }
244
245                     free.put(type, Sets.newHashSet(
246                             new BandwidthResourceAllocation(new BandwidthResource(Bandwidth.bps(freeBw)))));
247                     break;
248                 case LAMBDA:
249                     Set<? extends ResourceAllocation> lmd = caps.get(type);
250                     if (lmd == null || lmd.isEmpty()) {
251                         // nothing left
252                         break;
253                     }
254                     Set<LambdaResourceAllocation> freeL = new HashSet<>();
255                     for (ResourceAllocation r : lmd) {
256                         if (r instanceof LambdaResourceAllocation) {
257                             freeL.add((LambdaResourceAllocation) r);
258                         }
259                     }
260
261                     // enumerate current allocations, removing resources
262                     for (LinkResourceAllocations alloc : allocations) {
263                         Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
264                         for (ResourceAllocation a : types) {
265                             if (a instanceof LambdaResourceAllocation) {
266                                 freeL.remove(a);
267                             }
268                         }
269                     }
270
271                     free.put(type, freeL);
272                     break;
273                 case MPLS_LABEL:
274                     Set<? extends ResourceAllocation> mpls = caps.get(type);
275                     if (mpls == null || mpls.isEmpty()) {
276                         // nothing left
277                         break;
278                     }
279                     Set<MplsLabelResourceAllocation> freeLabel = new HashSet<>();
280                     for (ResourceAllocation r : mpls) {
281                         if (r instanceof MplsLabelResourceAllocation) {
282                             freeLabel.add((MplsLabelResourceAllocation) r);
283                         }
284                     }
285
286                     // enumerate current allocations, removing resources
287                     for (LinkResourceAllocations alloc : allocations) {
288                         Set<ResourceAllocation> types = alloc.getResourceAllocation(link);
289                         for (ResourceAllocation a : types) {
290                             if (a instanceof MplsLabelResourceAllocation) {
291                                 freeLabel.remove(a);
292                             }
293                         }
294                     }
295
296                     free.put(type, freeLabel);
297                     break;
298                 default:
299                     log.debug("unsupported ResourceType {}", type);
300                     break;
301             }
302         }
303         return free;
304     }
305
306     @Override
307     public void allocateResources(LinkResourceAllocations allocations) {
308         checkNotNull(allocations);
309         TransactionContext tx = getTxContext();
310
311         tx.begin();
312         try {
313             TransactionalMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
314             intentAllocs.put(allocations.intentId(), allocations);
315             allocations.links().forEach(link -> allocateLinkResource(tx, link, allocations));
316             tx.commit();
317         } catch (Exception e) {
318             log.error("Exception thrown, rolling back", e);
319             tx.abort();
320             throw e;
321         }
322     }
323
324     private void allocateLinkResource(TransactionContext tx, Link link,
325             LinkResourceAllocations allocations) {
326         // requested resources
327         Set<ResourceAllocation> reqs = allocations.getResourceAllocation(link);
328         Map<ResourceType, Set<? extends ResourceAllocation>> available = getFreeResourcesEx(tx, link);
329         for (ResourceAllocation req : reqs) {
330             Set<? extends ResourceAllocation> avail = available.get(req.type());
331             if (req instanceof BandwidthResourceAllocation) {
332                 // check if allocation should be accepted
333                 if (avail.isEmpty()) {
334                     checkState(!avail.isEmpty(),
335                                "There's no Bandwidth resource on %s?",
336                                link);
337                 }
338                 BandwidthResourceAllocation bw = (BandwidthResourceAllocation) avail.iterator().next();
339                 double bwLeft = bw.bandwidth().toDouble();
340                 BandwidthResourceAllocation bwReq = ((BandwidthResourceAllocation) req);
341                 bwLeft -= bwReq.bandwidth().toDouble();
342                 if (bwLeft < 0) {
343                     throw new ResourceAllocationException(
344                             PositionalParameterStringFormatter.format(
345                                     "Unable to allocate bandwidth for link {} "
346                                         + " requested amount is {} current allocation is {}",
347                                     link,
348                                     bwReq.bandwidth().toDouble(),
349                                     bw));
350                 }
351             } else if (req instanceof LambdaResourceAllocation) {
352                 LambdaResourceAllocation lambdaAllocation = (LambdaResourceAllocation) req;
353                 // check if allocation should be accepted
354                 if (!avail.contains(req)) {
355                     // requested lambda was not available
356                     throw new ResourceAllocationException(
357                             PositionalParameterStringFormatter.format(
358                                 "Unable to allocate lambda for link {} lambda is {}",
359                                     link,
360                                     lambdaAllocation.lambda().toInt()));
361                 }
362             } else if (req instanceof MplsLabelResourceAllocation) {
363                 MplsLabelResourceAllocation mplsAllocation = (MplsLabelResourceAllocation) req;
364                 if (!avail.contains(req)) {
365                     throw new ResourceAllocationException(
366                                                           PositionalParameterStringFormatter
367                                                                   .format("Unable to allocate MPLS label for link "
368                                                                           + "{} MPLS label is {}",
369                                                                           link,
370                                                                           mplsAllocation
371                                                                                   .mplsLabel()
372                                                                                   .toString()));
373                 }
374             }
375         }
376         // all requests allocatable => add allocation
377         final LinkKey linkKey = LinkKey.linkKey(link);
378         TransactionalMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
379         List<LinkResourceAllocations> before = linkAllocs.get(linkKey);
380         if (before == null) {
381             List<LinkResourceAllocations> after = new ArrayList<>();
382             after.add(allocations);
383             before = linkAllocs.putIfAbsent(linkKey, after);
384             if (before != null) {
385                 // concurrent allocation detected, retry transaction : is this needed?
386                 log.warn("Concurrent Allocation, retrying");
387                 throw new TransactionException();
388             }
389         } else {
390             List<LinkResourceAllocations> after = new ArrayList<>(before.size() + 1);
391             after.addAll(before);
392             after.add(allocations);
393             linkAllocs.replace(linkKey, before, after);
394         }
395     }
396
397     @Override
398     public LinkResourceEvent releaseResources(LinkResourceAllocations allocations) {
399         checkNotNull(allocations);
400
401         final IntentId intentId = allocations.intentId();
402         final Collection<Link> links = allocations.links();
403         boolean success = false;
404         do {
405             TransactionContext tx = getTxContext();
406             tx.begin();
407             try {
408                 TransactionalMap<IntentId, LinkResourceAllocations> intentAllocs = getIntentAllocs(tx);
409                 intentAllocs.remove(intentId);
410
411                 TransactionalMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
412                 links.forEach(link -> {
413                     final LinkKey linkId = LinkKey.linkKey(link);
414
415                     List<LinkResourceAllocations> before = linkAllocs.get(linkId);
416                     if (before == null || before.isEmpty()) {
417                         // something is wrong, but it is already freed
418                         log.warn("There was no resource left to release on {}", linkId);
419                         return;
420                     }
421                     List<LinkResourceAllocations> after = new ArrayList<>(before);
422                     after.remove(allocations);
423                     linkAllocs.replace(linkId, before, after);
424                 });
425                 tx.commit();
426                 success = true;
427             } catch (TransactionException e) {
428                 log.debug("Transaction failed, retrying", e);
429                 tx.abort();
430             } catch (Exception e) {
431                 log.error("Exception thrown during releaseResource {}", allocations, e);
432                 tx.abort();
433                 throw e;
434             }
435         } while (!success);
436
437         // Issue events to force recompilation of intents.
438         final List<LinkResourceAllocations> releasedResources = ImmutableList.of(allocations);
439         return new LinkResourceEvent(
440                 LinkResourceEvent.Type.ADDITIONAL_RESOURCES_AVAILABLE,
441                 releasedResources);
442
443     }
444
445     @Override
446     public LinkResourceAllocations getAllocations(IntentId intentId) {
447         checkNotNull(intentId);
448         Versioned<LinkResourceAllocations> alloc = null;
449         try {
450             alloc = intentAllocMap.get(intentId);
451         } catch (Exception e) {
452             log.warn("Could not read resource allocation information", e);
453         }
454         return alloc == null ? null : alloc.value();
455     }
456
457     @Override
458     public Iterable<LinkResourceAllocations> getAllocations(Link link) {
459         checkNotNull(link);
460         TransactionContext tx = getTxContext();
461         Iterable<LinkResourceAllocations> res = null;
462         tx.begin();
463         try {
464             res = getAllocations(tx, link);
465         } finally {
466             tx.abort();
467         }
468         return res == null ? Collections.emptyList() : res;
469     }
470
471     @Override
472     public Iterable<LinkResourceAllocations> getAllocations() {
473         try {
474             Set<LinkResourceAllocations> allocs =
475                     intentAllocMap.values().stream().map(Versioned::value).collect(Collectors.toSet());
476             return ImmutableSet.copyOf(allocs);
477         } catch (Exception e) {
478             log.warn("Could not read resource allocation information", e);
479         }
480         return ImmutableSet.of();
481     }
482
483     private Iterable<LinkResourceAllocations> getAllocations(TransactionContext tx, Link link) {
484         checkNotNull(tx);
485         checkNotNull(link);
486         final LinkKey key = LinkKey.linkKey(link);
487         TransactionalMap<LinkKey, List<LinkResourceAllocations>> linkAllocs = getLinkAllocs(tx);
488         List<LinkResourceAllocations> res = null;
489
490         res = linkAllocs.get(key);
491         if (res == null) {
492             res = linkAllocs.putIfAbsent(key, new ArrayList<>());
493
494             if (res == null) {
495                 return Collections.emptyList();
496             } else {
497                 return res;
498             }
499         }
500         return res;
501     }
502
503 }