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