c6eb7c5a47ad6ff4547e54b52775d73226e11702
[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.net.intent.impl.compiler;
17
18 import org.apache.commons.lang3.tuple.Pair;
19 import org.apache.felix.scr.annotations.Activate;
20 import org.apache.felix.scr.annotations.Deactivate;
21 import org.apache.felix.scr.annotations.Modified;
22 import org.apache.felix.scr.annotations.Property;
23 import org.apache.felix.scr.annotations.Reference;
24 import org.apache.felix.scr.annotations.ReferenceCardinality;
25 import org.onlab.util.Tools;
26 import org.onosproject.cfg.ComponentConfigService;
27 import org.onosproject.core.ApplicationId;
28 import org.onosproject.core.CoreService;
29 import org.onosproject.net.AnnotationKeys;
30 import org.onosproject.net.ConnectPoint;
31 import org.onosproject.net.OchPort;
32 import org.onosproject.net.OduCltPort;
33 import org.onosproject.net.OduSignalType;
34 import org.onosproject.net.Port;
35 import org.onosproject.net.device.DeviceService;
36 import org.onosproject.net.flow.DefaultFlowRule;
37 import org.onosproject.net.flow.DefaultTrafficSelector;
38 import org.onosproject.net.flow.DefaultTrafficTreatment;
39 import org.onosproject.net.flow.FlowRule;
40 import org.onosproject.net.flow.TrafficSelector;
41 import org.onosproject.net.flow.TrafficTreatment;
42 import org.onosproject.net.intent.FlowRuleIntent;
43 import org.onosproject.net.intent.Intent;
44 import org.onosproject.net.intent.IntentCompiler;
45 import org.onosproject.net.intent.IntentExtensionService;
46 import org.onosproject.net.intent.IntentId;
47 import org.onosproject.net.intent.IntentService;
48 import org.onosproject.net.intent.OpticalCircuitIntent;
49 import org.onosproject.net.intent.OpticalConnectivityIntent;
50 import org.onosproject.net.intent.impl.IntentCompilationException;
51 import org.onosproject.net.newresource.ResourceAllocation;
52 import org.onosproject.net.newresource.ResourcePath;
53 import org.onosproject.net.newresource.ResourceService;
54 import org.onosproject.net.resource.device.IntentSetMultimap;
55 import org.onosproject.net.resource.link.LinkResourceAllocations;
56 import org.osgi.service.component.ComponentContext;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 import java.util.Collections;
61 import java.util.Dictionary;
62 import java.util.LinkedList;
63 import java.util.List;
64 import java.util.Optional;
65 import java.util.Set;
66
67 import static com.google.common.base.Preconditions.checkArgument;
68
69 /**
70  * An intent compiler for {@link org.onosproject.net.intent.OpticalCircuitIntent}.
71  */
72 // For now, remove component designation until dependency on the new resource manager is available.
73 // @Component(immediate = true)
74 public class OpticalCircuitIntentCompiler implements IntentCompiler<OpticalCircuitIntent> {
75
76     private static final Logger log = LoggerFactory.getLogger(OpticalCircuitIntentCompiler.class);
77
78     private static final int DEFAULT_MAX_CAPACITY = 10;
79
80     @Property(name = "maxCapacity", intValue = DEFAULT_MAX_CAPACITY,
81             label = "Maximum utilization of an optical connection.")
82
83     private int maxCapacity = DEFAULT_MAX_CAPACITY;
84
85     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86     protected ComponentConfigService cfgService;
87
88     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89     protected IntentExtensionService intentManager;
90
91     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92     protected CoreService coreService;
93
94     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95     protected DeviceService deviceService;
96
97     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98     protected ResourceService resourceService;
99
100     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101     protected IntentSetMultimap intentSetMultimap;
102
103     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104     protected IntentService intentService;
105
106     private ApplicationId appId;
107
108     @Modified
109     public void modified(ComponentContext context) {
110         Dictionary properties = context.getProperties();
111
112         //TODO for reduction check if the new capacity is smaller than the size of the current mapping
113         String propertyString = Tools.get(properties, "maxCapacity");
114
115         //Ignore if propertyString is empty
116         if (!propertyString.isEmpty()) {
117             try {
118                 int temp = Integer.parseInt(propertyString);
119                 //Ensure value is non-negative but allow zero as a way to shutdown the link
120                 if (temp >= 0) {
121                     maxCapacity = temp;
122                 }
123             } catch (NumberFormatException e) {
124                 //Malformed arguments lead to no change of value (user should be notified of error)
125               log.error("The value '{}' for maxCapacity was not parsable as an integer.", propertyString, e);
126             }
127         } else {
128             //Notify of empty value but do not return (other properties will also go in this function)
129             log.error("The value for maxCapacity was set to an empty value.");
130         }
131
132     }
133
134     @Activate
135     public void activate(ComponentContext context) {
136         appId = coreService.registerApplication("org.onosproject.net.intent");
137         intentManager.registerCompiler(OpticalCircuitIntent.class, this);
138         cfgService.registerProperties(getClass());
139         modified(context);
140     }
141
142     @Deactivate
143     public void deactivate() {
144         intentManager.unregisterCompiler(OpticalCircuitIntent.class);
145         cfgService.unregisterProperties(getClass(), false);
146     }
147
148     @Override
149     public List<Intent> compile(OpticalCircuitIntent intent, List<Intent> installable,
150                                 Set<LinkResourceAllocations> resources) {
151         // Check if ports are OduClt ports
152         ConnectPoint src = intent.getSrc();
153         ConnectPoint dst = intent.getDst();
154         Port srcPort = deviceService.getPort(src.deviceId(), src.port());
155         Port dstPort = deviceService.getPort(dst.deviceId(), dst.port());
156         checkArgument(srcPort instanceof OduCltPort);
157         checkArgument(dstPort instanceof OduCltPort);
158
159         log.debug("Compiling optical circuit intent between {} and {}", src, dst);
160
161         // Reserve OduClt ports
162         ResourcePath srcPortPath = new ResourcePath(src.deviceId(), src.port());
163         ResourcePath dstPortPath = new ResourcePath(dst.deviceId(), dst.port());
164         List<ResourceAllocation> allocation = resourceService.allocate(intent.id(), srcPortPath, dstPortPath);
165         if (allocation.isEmpty()) {
166             throw new IntentCompilationException("Unable to reserve ports for intent " + intent);
167         }
168
169         LinkedList<Intent> intents = new LinkedList<>();
170
171         FlowRuleIntent circuitIntent;
172         OpticalConnectivityIntent connIntent = findOpticalConnectivityIntent(intent);
173
174         // Create optical connectivity intent if needed
175         if (connIntent == null) {
176             // Find OCh ports with available resources
177             Pair<OchPort, OchPort> ochPorts = findPorts(intent);
178
179             if (ochPorts == null) {
180                 return Collections.emptyList();
181             }
182
183             // Create optical connectivity intent
184             ConnectPoint srcCP = new ConnectPoint(src.elementId(), ochPorts.getLeft().number());
185             ConnectPoint dstCP = new ConnectPoint(dst.elementId(), ochPorts.getRight().number());
186             // FIXME: hardcoded ODU signal type
187             connIntent = OpticalConnectivityIntent.builder()
188                     .appId(appId)
189                     .src(srcCP)
190                     .dst(dstCP)
191                     .signalType(OduSignalType.ODU4)
192                     .bidirectional(intent.isBidirectional())
193                     .build();
194             intentService.submit(connIntent);
195         }
196
197         // Create optical circuit intent
198         List<FlowRule> rules = new LinkedList<>();
199         rules.add(connectPorts(src, connIntent.getSrc(), intent.priority()));
200         rules.add(connectPorts(connIntent.getDst(), dst, intent.priority()));
201
202         // Create flow rules for reverse path
203         if (intent.isBidirectional()) {
204             rules.add(connectPorts(connIntent.getSrc(), src, intent.priority()));
205             rules.add(connectPorts(dst, connIntent.getDst(), intent.priority()));
206         }
207
208         circuitIntent = new FlowRuleIntent(appId, rules, intent.resources());
209
210         // Save circuit to connectivity intent mapping
211         intentSetMultimap.allocateMapping(connIntent.id(), intent.id());
212         intents.add(circuitIntent);
213
214         return intents;
215     }
216
217     /**
218      * Checks if current allocations on given resource can satisfy request.
219      * If the resource is null, return true.
220      *
221      * @param resource the resource on which to map the intent
222      * @return true if the resource can accept the request, false otherwise
223      */
224     private boolean isAvailable(IntentId resource) {
225         if (resource == null) {
226             return true;
227         }
228
229         Set<IntentId> mapping = intentSetMultimap.getMapping(resource);
230
231         if (mapping == null) {
232             return true;
233         }
234
235         return mapping.size() < maxCapacity;
236     }
237
238     private boolean isAllowed(OpticalCircuitIntent circuitIntent, OpticalConnectivityIntent connIntent) {
239         ConnectPoint srcStaticPort = staticPort(circuitIntent.getSrc());
240         if (srcStaticPort != null) {
241             if (!srcStaticPort.equals(connIntent.getSrc())) {
242                 return false;
243             }
244         }
245
246         ConnectPoint dstStaticPort = staticPort(circuitIntent.getDst());
247         if (dstStaticPort != null) {
248             if (!dstStaticPort.equals(connIntent.getDst())) {
249                 return false;
250             }
251         }
252
253         return true;
254     }
255
256     /**
257      * Returns existing and available optical connectivity intent that matches the given circuit intent.
258      *
259      * @param circuitIntent optical circuit intent
260      * @return existing optical connectivity intent, null otherwise.
261      */
262     private OpticalConnectivityIntent findOpticalConnectivityIntent(OpticalCircuitIntent circuitIntent) {
263         for (Intent intent : intentService.getIntents()) {
264             if (!(intent instanceof OpticalConnectivityIntent)) {
265                 continue;
266             }
267
268             OpticalConnectivityIntent connIntent = (OpticalConnectivityIntent) intent;
269
270             ConnectPoint src = circuitIntent.getSrc();
271             ConnectPoint dst = circuitIntent.getDst();
272             // Ignore if the intents don't have identical src and dst devices
273             if (!src.deviceId().equals(connIntent.getSrc().deviceId()) &&
274                     !dst.deviceId().equals(connIntent.getDst().deviceId())) {
275                 continue;
276             }
277
278             if (!isAllowed(circuitIntent, connIntent)) {
279                 continue;
280             }
281
282             if (isAvailable(connIntent.id())) {
283                 return connIntent;
284             }
285         }
286
287         return null;
288     }
289
290     private ConnectPoint staticPort(ConnectPoint connectPoint) {
291         Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
292
293         String staticPort = port.annotations().value(AnnotationKeys.STATIC_PORT);
294
295         // FIXME: need a better way to match the port
296         if (staticPort != null) {
297             for (Port p : deviceService.getPorts(connectPoint.deviceId())) {
298                 if (staticPort.equals(p.number().name())) {
299                     return new ConnectPoint(p.element().id(), p.number());
300                 }
301             }
302         }
303
304         return null;
305     }
306
307     private OchPort findAvailableOchPort(ConnectPoint oduPort) {
308         // First see if the port mappings are constrained
309         ConnectPoint ochCP = staticPort(oduPort);
310
311         if (ochCP != null) {
312             OchPort ochPort = (OchPort) deviceService.getPort(ochCP.deviceId(), ochCP.port());
313             Optional<IntentId> intentId =
314                     resourceService.getResourceAllocation(new ResourcePath(ochCP.deviceId(), ochCP.port()))
315                             .map(ResourceAllocation::consumer)
316                             .filter(x -> x instanceof IntentId)
317                             .map(x -> (IntentId) x);
318
319             if (isAvailable(intentId.orElse(null))) {
320                 return ochPort;
321             }
322         }
323
324         // No port constraints, so find any port that works
325         List<Port> ports = deviceService.getPorts(oduPort.deviceId());
326
327         for (Port port : ports) {
328             if (!(port instanceof OchPort)) {
329                 continue;
330             }
331
332             Optional<IntentId> intentId =
333                     resourceService.getResourceAllocation(new ResourcePath(oduPort.deviceId(), port.number()))
334                             .map(ResourceAllocation::consumer)
335                             .filter(x -> x instanceof IntentId)
336                             .map(x -> (IntentId) x);
337             if (isAvailable(intentId.orElse(null))) {
338                 return (OchPort) port;
339             }
340         }
341
342         return null;
343     }
344
345     private Pair<OchPort, OchPort> findPorts(OpticalCircuitIntent intent) {
346
347         OchPort srcPort = findAvailableOchPort(intent.getSrc());
348         if (srcPort == null) {
349             return null;
350         }
351
352         OchPort dstPort = findAvailableOchPort(intent.getDst());
353         if (dstPort == null) {
354             return null;
355         }
356
357         return Pair.of(srcPort, dstPort);
358     }
359
360     /**
361      * Builds flow rule for mapping between two ports.
362      *
363      * @param src source port
364      * @param dst destination port
365      * @return flow rules
366      */
367     private FlowRule connectPorts(ConnectPoint src, ConnectPoint dst, int priority) {
368         checkArgument(src.deviceId().equals(dst.deviceId()));
369
370         TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
371         TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
372
373         selectorBuilder.matchInPort(src.port());
374         //selectorBuilder.add(Criteria.matchCltSignalType)
375         treatmentBuilder.setOutput(dst.port());
376         //treatmentBuilder.add(Instructions.modL1OduSignalType)
377
378         FlowRule flowRule = DefaultFlowRule.builder()
379                 .forDevice(src.deviceId())
380                 .withSelector(selectorBuilder.build())
381                 .withTreatment(treatmentBuilder.build())
382                 .withPriority(priority)
383                 .fromApp(appId)
384                 .makePermanent()
385                 .build();
386
387         return flowRule;
388     }
389 }