Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 1 | /* |
Brian O'Connor | 5ab426f | 2016-04-09 01:19:45 -0700 | [diff] [blame] | 2 | * Copyright 2016-present Open Networking Laboratory |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 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 | */ |
Yuta HIGUCHI | d95d590 | 2016-06-27 00:18:45 -0700 | [diff] [blame] | 16 | package org.onosproject.net.optical.intent.impl.compiler; |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 17 | |
| 18 | |
| 19 | import org.apache.felix.scr.annotations.Activate; |
| 20 | import org.apache.felix.scr.annotations.Component; |
| 21 | import org.apache.felix.scr.annotations.Deactivate; |
| 22 | import org.apache.felix.scr.annotations.Reference; |
| 23 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
| 24 | import org.onosproject.core.ApplicationId; |
| 25 | import org.onosproject.core.CoreService; |
| 26 | import org.onosproject.net.ConnectPoint; |
| 27 | import org.onosproject.net.DeviceId; |
| 28 | import org.onosproject.net.Link; |
| 29 | import org.onosproject.net.LinkKey; |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 30 | import org.onosproject.net.OduSignalId; |
| 31 | import org.onosproject.net.OduSignalType; |
| 32 | import org.onosproject.net.OduSignalUtils; |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 33 | import org.onosproject.net.Path; |
| 34 | import org.onosproject.net.Port; |
| 35 | import org.onosproject.net.TributarySlot; |
| 36 | import org.onosproject.net.device.DeviceService; |
| 37 | import org.onosproject.net.flow.DefaultFlowRule; |
| 38 | import org.onosproject.net.flow.DefaultTrafficSelector; |
| 39 | import org.onosproject.net.flow.DefaultTrafficTreatment; |
| 40 | import org.onosproject.net.flow.FlowRule; |
| 41 | import org.onosproject.net.flow.TrafficSelector; |
| 42 | import org.onosproject.net.flow.TrafficTreatment; |
| 43 | import org.onosproject.net.flow.criteria.Criteria; |
| 44 | import org.onosproject.net.flow.instructions.Instructions; |
| 45 | import org.onosproject.net.intent.FlowRuleIntent; |
| 46 | import org.onosproject.net.intent.Intent; |
| 47 | import org.onosproject.net.intent.IntentCompiler; |
| 48 | import org.onosproject.net.intent.IntentExtensionService; |
| 49 | import org.onosproject.net.intent.OpticalOduIntent; |
HIGUCHI Yuta | 4c0ef6b | 2016-05-02 19:45:41 -0700 | [diff] [blame] | 50 | import org.onosproject.net.optical.OduCltPort; |
HIGUCHI Yuta | 5be3e82 | 2016-05-03 13:51:42 -0700 | [diff] [blame] | 51 | import org.onosproject.net.optical.OtuPort; |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 52 | import org.onosproject.net.resource.Resource; |
| 53 | import org.onosproject.net.resource.ResourceService; |
| 54 | import org.onosproject.net.resource.ResourceAllocation; |
| 55 | import org.onosproject.net.resource.Resources; |
| 56 | import org.onosproject.net.topology.LinkWeight; |
| 57 | import org.onosproject.net.topology.Topology; |
| 58 | import org.onosproject.net.topology.TopologyService; |
| 59 | import org.slf4j.Logger; |
| 60 | import org.slf4j.LoggerFactory; |
| 61 | |
| 62 | import com.google.common.collect.ImmutableList; |
| 63 | import com.google.common.collect.ImmutableSet; |
| 64 | import com.google.common.collect.Lists; |
| 65 | import com.google.common.collect.Sets; |
| 66 | |
| 67 | import java.util.ArrayList; |
| 68 | import java.util.Collections; |
| 69 | import java.util.HashMap; |
| 70 | import java.util.LinkedList; |
| 71 | import java.util.List; |
| 72 | import java.util.Map; |
| 73 | import java.util.Set; |
| 74 | import java.util.stream.Collectors; |
| 75 | import java.util.stream.Stream; |
| 76 | |
| 77 | import static com.google.common.base.Preconditions.checkArgument; |
| 78 | import static org.onosproject.net.LinkKey.linkKey; |
HIGUCHI Yuta | 4c0ef6b | 2016-05-02 19:45:41 -0700 | [diff] [blame] | 79 | import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView; |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 80 | |
| 81 | /** |
| 82 | * An intent compiler for {@link org.onosproject.net.intent.OpticalOduIntent}. |
| 83 | */ |
| 84 | @Component(immediate = true) |
| 85 | public class OpticalOduIntentCompiler implements IntentCompiler<OpticalOduIntent> { |
| 86 | |
| 87 | private static final Logger log = LoggerFactory.getLogger(OpticalOduIntentCompiler.class); |
| 88 | |
| 89 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 90 | protected IntentExtensionService intentManager; |
| 91 | |
| 92 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 93 | protected CoreService coreService; |
| 94 | |
| 95 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 96 | protected TopologyService topologyService; |
| 97 | |
| 98 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 99 | protected DeviceService deviceService; |
| 100 | |
| 101 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 102 | protected ResourceService resourceService; |
| 103 | |
| 104 | private ApplicationId appId; |
| 105 | |
| 106 | @Activate |
| 107 | public void activate() { |
HIGUCHI Yuta | 4c0ef6b | 2016-05-02 19:45:41 -0700 | [diff] [blame] | 108 | deviceService = opticalView(deviceService); |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 109 | appId = coreService.registerApplication("org.onosproject.net.intent"); |
| 110 | intentManager.registerCompiler(OpticalOduIntent.class, this); |
| 111 | } |
| 112 | |
| 113 | @Deactivate |
| 114 | public void deactivate() { |
| 115 | intentManager.unregisterCompiler(OpticalOduIntent.class); |
| 116 | } |
| 117 | |
| 118 | @Override |
| 119 | public List<Intent> compile(OpticalOduIntent intent, List<Intent> installable) { |
| 120 | // Check if ports are OduClt ports |
| 121 | ConnectPoint src = intent.getSrc(); |
| 122 | ConnectPoint dst = intent.getDst(); |
| 123 | Port srcPort = deviceService.getPort(src.deviceId(), src.port()); |
| 124 | Port dstPort = deviceService.getPort(dst.deviceId(), dst.port()); |
| 125 | checkArgument(srcPort instanceof OduCltPort); |
| 126 | checkArgument(dstPort instanceof OduCltPort); |
| 127 | |
| 128 | log.debug("Compiling optical ODU intent between {} and {}", src, dst); |
| 129 | |
| 130 | // Release of intent resources here is only a temporary solution for handling the |
| 131 | // case of recompiling due to intent restoration (when intent state is FAILED). |
| 132 | // TODO: try to release intent resources in IntentManager. |
| 133 | resourceService.release(intent.id()); |
| 134 | |
| 135 | // Check OduClt ports availability |
| 136 | Resource srcPortResource = Resources.discrete(src.deviceId(), src.port()).resource(); |
| 137 | Resource dstPortResource = Resources.discrete(dst.deviceId(), dst.port()).resource(); |
| 138 | // If ports are not available, compilation fails |
| 139 | if (!Stream.of(srcPortResource, dstPortResource).allMatch(resourceService::isAvailable)) { |
Yuta HIGUCHI | d95d590 | 2016-06-27 00:18:45 -0700 | [diff] [blame] | 140 | throw new OpticalIntentCompilationException("Ports for the intent are not available. Intent: " + intent); |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 141 | } |
| 142 | List<Resource> intentResources = new ArrayList<>(); |
| 143 | intentResources.add(srcPortResource); |
| 144 | intentResources.add(dstPortResource); |
| 145 | |
| 146 | // Calculate available light paths |
| 147 | Set<Path> paths = getOpticalPaths(intent); |
| 148 | |
| 149 | if (paths.isEmpty()) { |
Yuta HIGUCHI | d95d590 | 2016-06-27 00:18:45 -0700 | [diff] [blame] | 150 | throw new OpticalIntentCompilationException("Unable to find suitable lightpath for intent " + intent); |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 151 | } |
| 152 | |
| 153 | // Use first path that can be successfully reserved |
| 154 | for (Path path : paths) { |
| 155 | |
| 156 | // Find available Tributary Slots on both directions of path |
| 157 | Map<LinkKey, Set<TributarySlot>> slotsMap = findAvailableTributarySlots(intent, path); |
| 158 | if (slotsMap.isEmpty()) { |
| 159 | continue; |
| 160 | } |
| 161 | List<Resource> tributarySlotResources = convertToResources(slotsMap); |
| 162 | if (!tributarySlotResources.stream().allMatch(resourceService::isAvailable)) { |
| 163 | continue; |
| 164 | } |
| 165 | |
| 166 | intentResources.addAll(tributarySlotResources); |
| 167 | |
| 168 | allocateResources(intent, intentResources); |
| 169 | |
| 170 | List<FlowRule> rules = new LinkedList<>(); |
| 171 | |
| 172 | // Create rules for forward and reverse path |
| 173 | rules = createRules(intent, intent.getSrc(), intent.getDst(), path, slotsMap, false); |
| 174 | if (intent.isBidirectional()) { |
| 175 | rules.addAll(createRules(intent, intent.getDst(), intent.getSrc(), path, slotsMap, true)); |
| 176 | } |
| 177 | |
| 178 | return Collections.singletonList(new FlowRuleIntent(appId, |
| 179 | rules, ImmutableSet.copyOf(path.links()))); |
| 180 | } |
| 181 | |
Yuta HIGUCHI | d95d590 | 2016-06-27 00:18:45 -0700 | [diff] [blame] | 182 | throw new OpticalIntentCompilationException("Unable to find suitable lightpath for intent " + intent); |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 183 | } |
| 184 | |
| 185 | /** |
| 186 | * Find available TributarySlots across path. |
| 187 | * |
| 188 | * @param intent |
| 189 | * @param path path in OTU topology |
| 190 | * @return Map of Linkey and Set of available TributarySlots on its ports |
| 191 | */ |
| 192 | private Map<LinkKey, Set<TributarySlot>> findAvailableTributarySlots(OpticalOduIntent intent, Path path) { |
| 193 | Set<LinkKey> linkRequest = Sets.newHashSetWithExpectedSize(path.links().size()); |
| 194 | for (int i = 0; i < path.links().size(); i++) { |
| 195 | LinkKey link = linkKey(path.links().get(i)); |
| 196 | linkRequest.add(link); |
| 197 | } |
| 198 | |
| 199 | return findTributarySlots(intent, linkRequest); |
| 200 | } |
| 201 | |
| 202 | private List<Resource> convertToResources(Map<LinkKey, Set<TributarySlot>> slotsMap) { |
| 203 | // Same TributarySlots are used for both directions |
| 204 | Set<Resource> resources = slotsMap.entrySet().stream() |
| 205 | .flatMap(x -> x.getValue() |
| 206 | .stream() |
| 207 | .flatMap(ts-> Stream.of( |
| 208 | Resources.discrete(x.getKey().src().deviceId(), x.getKey().src().port()) |
| 209 | .resource().child(ts), |
| 210 | Resources.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port()) |
| 211 | .resource().child(ts)))) |
| 212 | .collect(Collectors.toSet()); |
| 213 | return (ImmutableList.copyOf(resources)); |
| 214 | } |
| 215 | |
| 216 | private void allocateResources(Intent intent, List<Resource> resources) { |
| 217 | // reserve all of required resources |
| 218 | List<ResourceAllocation> allocations = resourceService.allocate(intent.id(), resources); |
| 219 | if (allocations.isEmpty()) { |
| 220 | log.info("Resource allocation for {} failed (resource request: {})", intent, resources); |
Yuta HIGUCHI | d95d590 | 2016-06-27 00:18:45 -0700 | [diff] [blame] | 221 | throw new OpticalIntentCompilationException("Unable to allocate resources: " + resources); |
Rimon Ashkenazy | 27438ff | 2016-03-22 15:57:45 +0200 | [diff] [blame] | 222 | } |
| 223 | } |
| 224 | |
| 225 | private Map<LinkKey, Set<TributarySlot>> findTributarySlots(OpticalOduIntent intent, Set<LinkKey> links) { |
| 226 | OduSignalType oduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(intent.getSignalType()); |
| 227 | int requestedTsNum = oduSignalType.tributarySlots(); |
| 228 | |
| 229 | Map<LinkKey, Set<TributarySlot>> slotsMap = new HashMap<>(); |
| 230 | for (LinkKey link : links) { |
| 231 | Set<TributarySlot> common = findCommonTributarySlotsOnCps(link.src(), link.dst()); |
| 232 | if (common.isEmpty() || (common.size() < requestedTsNum)) { |
| 233 | log.debug("Failed to find TributarySlots on link {} requestedTsNum={}", link, requestedTsNum); |
| 234 | return Collections.emptyMap(); // failed to find enough available TributarySlots on a link |
| 235 | } |
| 236 | slotsMap.put(link, common.stream() |
| 237 | .limit(requestedTsNum) |
| 238 | .collect(Collectors.toSet())); |
| 239 | } |
| 240 | return slotsMap; |
| 241 | } |
| 242 | |
| 243 | /** |
| 244 | * Calculates optical paths in OTU topology. |
| 245 | * |
| 246 | * @param intent optical ODU intent |
| 247 | * @return set of paths in OTU topology |
| 248 | */ |
| 249 | private Set<Path> getOpticalPaths(OpticalOduIntent intent) { |
| 250 | // Route in OTU topology |
| 251 | Topology topology = topologyService.currentTopology(); |
| 252 | |
| 253 | LinkWeight weight = edge -> { |
| 254 | // Disregard inactive or non-optical links |
| 255 | if (edge.link().state() == Link.State.INACTIVE) { |
| 256 | return -1; |
| 257 | } |
| 258 | if (edge.link().type() != Link.Type.OPTICAL) { |
| 259 | return -1; |
| 260 | } |
| 261 | // Find path with available TributarySlots resources |
| 262 | if (!isAvailableTributarySlots(intent, edge.link())) { |
| 263 | return -1; |
| 264 | } |
| 265 | return 1; |
| 266 | }; |
| 267 | |
| 268 | ConnectPoint start = intent.getSrc(); |
| 269 | ConnectPoint end = intent.getDst(); |
| 270 | |
| 271 | return topologyService.getPaths(topology, start.deviceId(), end.deviceId(), weight); |
| 272 | } |
| 273 | |
| 274 | private boolean isAvailableTributarySlots(OpticalOduIntent intent, Link link) { |
| 275 | OduSignalType oduSignalType = OduSignalUtils.mappingCltSignalTypeToOduSignalType(intent.getSignalType()); |
| 276 | int requestedTsNum = oduSignalType.tributarySlots(); |
| 277 | |
| 278 | Set<TributarySlot> common = findCommonTributarySlotsOnCps(link.src(), link.dst()); |
| 279 | if (common.isEmpty() || (common.size() < requestedTsNum)) { |
| 280 | log.debug("Not enough available TributarySlots on link {} requestedTsNum={}", link, requestedTsNum); |
| 281 | return false; |
| 282 | } |
| 283 | return true; |
| 284 | } |
| 285 | |
| 286 | /** |
| 287 | * Create rules for the forward (or the reverse) path of the intent. |
| 288 | * |
| 289 | * @param intent OpticalOduIntent intent |
| 290 | * @param path path found for intent |
| 291 | * @param slotsMap Map of LinkKey and TributarySlots resources |
| 292 | * @return list of flow rules |
| 293 | */ |
| 294 | private List<FlowRule> createRules(OpticalOduIntent intent, ConnectPoint src, ConnectPoint dst, |
| 295 | Path path, Map<LinkKey, Set<TributarySlot>> slotsMap, boolean reverse) { |
| 296 | // Build the ingress OTN rule |
| 297 | TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); |
| 298 | selector.matchInPort(src.port()); |
| 299 | OduSignalType oduCltPortOduSignalType = |
| 300 | OduSignalUtils.mappingCltSignalTypeToOduSignalType(intent.getSignalType()); |
| 301 | selector.add(Criteria.matchOduSignalType(oduCltPortOduSignalType)); |
| 302 | |
| 303 | List<FlowRule> rules = new LinkedList<>(); |
| 304 | ConnectPoint current = src; |
| 305 | |
| 306 | List<Link> links = ((!reverse) ? path.links() : Lists.reverse(path.links())); |
| 307 | |
| 308 | for (Link link : links) { |
| 309 | Set<TributarySlot> slots = slotsMap.get(linkKey(link)); |
| 310 | OtuPort otuPort = (OtuPort) (deviceService.getPort(link.src().deviceId(), link.src().port())); |
| 311 | OduSignalType otuPortOduSignalType = |
| 312 | OduSignalUtils.mappingOtuSignalTypeToOduSignalType(otuPort.signalType()); |
| 313 | |
| 314 | TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(); |
| 315 | OduSignalId oduSignalId = null; |
| 316 | // use Instruction of OduSignalId only in case of ODU Multiplexing |
| 317 | if (oduCltPortOduSignalType != otuPortOduSignalType) { |
| 318 | oduSignalId = OduSignalUtils.buildOduSignalId(otuPortOduSignalType, slots); |
| 319 | treat.add(Instructions.modL1OduSignalId(oduSignalId)); |
| 320 | } |
| 321 | ConnectPoint next = ((!reverse) ? link.src() : link.dst()); |
| 322 | treat.setOutput(next.port()); |
| 323 | |
| 324 | FlowRule rule = createFlowRule(intent, current.deviceId(), selector.build(), treat.build()); |
| 325 | rules.add(rule); |
| 326 | |
| 327 | current = ((!reverse) ? link.dst() : link.src()); |
| 328 | selector = DefaultTrafficSelector.builder(); |
| 329 | selector.matchInPort(current.port()); |
| 330 | selector.add(Criteria.matchOduSignalType(oduCltPortOduSignalType)); |
| 331 | // use Criteria of OduSignalId only in case of ODU Multiplexing |
| 332 | if (oduCltPortOduSignalType != otuPortOduSignalType) { |
| 333 | selector.add(Criteria.matchOduSignalId(oduSignalId)); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | // Build the egress OTN rule |
| 338 | TrafficTreatment.Builder treatLast = DefaultTrafficTreatment.builder(); |
| 339 | treatLast.setOutput(dst.port()); |
| 340 | |
| 341 | FlowRule rule = createFlowRule(intent, dst.deviceId(), selector.build(), treatLast.build()); |
| 342 | rules.add(rule); |
| 343 | |
| 344 | return rules; |
| 345 | } |
| 346 | |
| 347 | private FlowRule createFlowRule(OpticalOduIntent intent, DeviceId deviceId, |
| 348 | TrafficSelector selector, TrafficTreatment treat) { |
| 349 | return DefaultFlowRule.builder() |
| 350 | .forDevice(deviceId) |
| 351 | .withSelector(selector) |
| 352 | .withTreatment(treat) |
| 353 | .withPriority(intent.priority()) |
| 354 | .fromApp(appId) |
| 355 | .makePermanent() |
| 356 | .build(); |
| 357 | } |
| 358 | |
| 359 | /** |
| 360 | * Finds the common TributarySlots available on the two connect points. |
| 361 | * |
| 362 | * @param src source connect point |
| 363 | * @param dst dest connect point |
| 364 | * @return set of common TributarySlots on both connect points |
| 365 | */ |
| 366 | Set<TributarySlot> findCommonTributarySlotsOnCps(ConnectPoint src, ConnectPoint dst) { |
| 367 | Set<TributarySlot> forward = findTributarySlotsOnCp(src); |
| 368 | Set<TributarySlot> backward = findTributarySlotsOnCp(dst); |
| 369 | return Sets.intersection(forward, backward); |
| 370 | } |
| 371 | |
| 372 | /** |
| 373 | * Finds the TributarySlots available on the connect point. |
| 374 | * |
| 375 | * @param cp connect point |
| 376 | * @return set of TributarySlots available on the connect point |
| 377 | */ |
| 378 | Set<TributarySlot> findTributarySlotsOnCp(ConnectPoint cp) { |
| 379 | return resourceService.getAvailableResourceValues( |
| 380 | Resources.discrete(cp.deviceId(), cp.port()).id(), |
| 381 | TributarySlot.class); |
| 382 | } |
| 383 | } |