blob: ba63cdd7edc748ba77c8b6e1d099303230ccf24f [file] [log] [blame]
Hyunsun Moon187bf532017-01-19 10:57:40 +09001/*
2 * Copyright 2017-present 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.opencord.cordvtn.impl.handler;
17
18import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Lists;
21import com.google.common.collect.Maps;
22import org.apache.felix.scr.annotations.Activate;
23import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Deactivate;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.apache.felix.scr.annotations.Service;
28import org.onlab.packet.Ethernet;
29import org.onlab.packet.Ip4Address;
30import org.onlab.packet.IpAddress;
31import org.onlab.packet.IpPrefix;
32import org.onosproject.cluster.ClusterService;
33import org.onosproject.cluster.LeadershipService;
34import org.onosproject.cluster.NodeId;
35import org.onosproject.core.GroupId;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.PortNumber;
38import org.onosproject.net.flow.DefaultFlowRule;
39import org.onosproject.net.flow.DefaultTrafficSelector;
40import org.onosproject.net.flow.DefaultTrafficTreatment;
41import org.onosproject.net.flow.FlowRule;
42import org.onosproject.net.flow.TrafficSelector;
43import org.onosproject.net.flow.TrafficTreatment;
44import org.onosproject.net.flow.instructions.ExtensionTreatment;
45import org.onosproject.net.group.DefaultGroupDescription;
46import org.onosproject.net.group.DefaultGroupKey;
47import org.onosproject.net.group.Group;
48import org.onosproject.net.group.GroupBucket;
49import org.onosproject.net.group.GroupBuckets;
50import org.onosproject.net.group.GroupDescription;
51import org.onosproject.net.group.GroupKey;
52import org.onosproject.net.group.GroupService;
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +090053import org.opencord.cordvtn.api.core.CordVtnPipeline;
Hyunsun Moon187bf532017-01-19 10:57:40 +090054import org.opencord.cordvtn.api.core.Instance;
Hyunsun Moon187bf532017-01-19 10:57:40 +090055import org.opencord.cordvtn.api.core.ServiceNetworkEvent;
56import org.opencord.cordvtn.api.core.ServiceNetworkListener;
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +090057import org.opencord.cordvtn.api.core.ServiceNetworkService;
Hyunsun Moon187bf532017-01-19 10:57:40 +090058import org.opencord.cordvtn.api.net.NetworkId;
59import org.opencord.cordvtn.api.net.ServiceNetwork;
60import org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType;
61import org.opencord.cordvtn.api.node.CordVtnNode;
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +090062import org.opencord.cordvtn.api.node.CordVtnNodeService;
Hyunsun Moon187bf532017-01-19 10:57:40 +090063import org.slf4j.Logger;
64
65import java.util.List;
66import java.util.Map;
67import java.util.Objects;
68import java.util.Set;
69import java.util.stream.Collectors;
70
71import static org.onosproject.net.group.DefaultGroupBucket.createSelectGroupBucket;
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +090072import static org.opencord.cordvtn.api.core.CordVtnPipeline.*;
Hyunsun Moon187bf532017-01-19 10:57:40 +090073import static org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType.BIDIRECTIONAL;
74import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.*;
Hyunsun Moon187bf532017-01-19 10:57:40 +090075import static org.slf4j.LoggerFactory.getLogger;
76
77/**
78 * Provisions service dependencies between service networks.
79 */
80@Component(immediate = true)
81@Service
82public class DependencyHandler extends AbstractInstanceHandler {
83
84 protected final Logger log = getLogger(getClass());
85
86 private static final String ERR_NET_FAIL = "Failed to get VTN network ";
87 private static final String ADDED = "Added ";
88 private static final String REMOVED = "Removed ";
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected GroupService groupService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected LeadershipService leadershipService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected ClusterService clusterService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900100 protected ServiceNetworkService snetService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 protected CordVtnNodeService nodeService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon187bf532017-01-19 10:57:40 +0900106 protected CordVtnPipeline pipeline;
107
Hyunsun Moon187bf532017-01-19 10:57:40 +0900108 private final ServiceNetworkListener snetListener = new InternalServiceNetworkListener();
109 private NodeId localNodeId;
110
111 @Activate
112 protected void activate() {
113 netTypes = ImmutableSet.of(PRIVATE, PUBLIC, VSG);
114 super.activate();
115 localNodeId = clusterService.getLocalNode().id();
116 leadershipService.runForLeadership(appId.name());
117 snetService.addListener(snetListener);
118 }
119
120 @Deactivate
121 protected void deactivate() {
122 super.deactivate();
123 snetService.removeListener(snetListener);
124 leadershipService.withdraw(appId.name());
125 }
126
127 @Override
128 public void instanceDetected(Instance instance) {
129 ServiceNetwork snet = snetService.serviceNetwork(instance.netId());
130 if (snet == null) {
131 final String error = ERR_NET_FAIL + instance.netId();
132 throw new IllegalStateException(error);
133 }
134 if (!snet.providers().isEmpty()) {
135 updateSubscriberInstances(snet, instance, true);
136 }
137 // TODO check if subscribers on this network
138 updateProviderInstances(snet);
139 }
140
141 @Override
142 public void instanceRemoved(Instance instance) {
143 ServiceNetwork snet = snetService.serviceNetwork(instance.netId());
144 if (snet == null) {
145 final String error = ERR_NET_FAIL + instance.netId();
146 throw new IllegalStateException(error);
147 }
148 if (!snet.providers().isEmpty()) {
149 updateSubscriberInstances(snet, instance, false);
150 }
151 // TODO check if subscribers on this network and remove group if unused
152 updateProviderInstances(snet);
153 }
154
155 private void dependencyAdded(ServiceNetwork subscriber, ServiceNetwork provider,
156 DependencyType type) {
157 populateDependencyRules(subscriber, provider, type, true);
158 log.info("Dependency is created subscriber:{}, provider:{}, type: {}",
159 subscriber.name(),
160 provider.name(), type.name());
161 }
162
163 private void dependencyRemoved(ServiceNetwork subscriber, ServiceNetwork provider,
164 DependencyType type) {
165 populateDependencyRules(subscriber, provider, type, false);
166 if (!isProviderInUse(provider.id())) {
167 removeGroup(provider.id());
168 }
169 log.info("Dependency is removed subscriber:{}, provider:{}, type: {}",
170 subscriber.name(),
171 provider.name(), type.name());
172 }
173
174 private void updateProviderInstances(ServiceNetwork provider) {
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900175 Set<DeviceId> devices = nodeService.completeNodes().stream()
Hyunsun Moon187bf532017-01-19 10:57:40 +0900176 .map(CordVtnNode::integrationBridgeId)
177 .collect(Collectors.toSet());
178
179 GroupKey groupKey = getGroupKey(provider.id());
180 for (DeviceId deviceId : devices) {
181 Group group = groupService.getGroup(deviceId, groupKey);
182 if (group == null) {
183 continue;
184 }
185 List<GroupBucket> oldBuckets = group.buckets().buckets();
186 List<GroupBucket> newBuckets = getProviderGroupBuckets(
187 deviceId,
188 provider.segmentId().id(),
189 getInstances(provider.id())).buckets();
190 if (oldBuckets.equals(newBuckets)) {
191 continue;
192 }
193
194 List<GroupBucket> bucketsToRemove = Lists.newArrayList(oldBuckets);
195 bucketsToRemove.removeAll(newBuckets);
196 if (!bucketsToRemove.isEmpty()) {
197 groupService.removeBucketsFromGroup(
198 deviceId,
199 groupKey,
200 new GroupBuckets(bucketsToRemove),
201 groupKey, appId);
202 log.debug("Removed buckets from provider({}) group on {}: {}",
203 provider.id(), deviceId, bucketsToRemove);
204 }
205
206 List<GroupBucket> bucketsToAdd = Lists.newArrayList(newBuckets);
207 bucketsToAdd.removeAll(oldBuckets);
208 if (!bucketsToAdd.isEmpty()) {
209 groupService.addBucketsToGroup(
210 deviceId,
211 groupKey,
212 new GroupBuckets(bucketsToAdd),
213 groupKey, appId);
214 log.debug("Added buckets to provider({}) group on {}: {}",
215 provider.id(), deviceId, bucketsToAdd);
216 }
217 }
218 }
219
220 private void updateSubscriberInstances(ServiceNetwork subscriber, Instance instance,
221 boolean isDetected) {
222 DeviceId deviceId = instance.deviceId();
223 final String isAdded = isDetected ? ADDED : REMOVED;
224 subscriber.providers().keySet().forEach(providerId -> {
225 populateInPortRule(
226 ImmutableMap.of(deviceId, ImmutableSet.of(instance.portNumber())),
227 ImmutableMap.of(deviceId, getGroupId(providerId, deviceId)),
228 isDetected);
229 log.info(isAdded + "subscriber instance({}) for provider({})",
230 instance.host().id(), providerId.id());
231 });
232 }
233
234 private boolean isProviderInUse(NetworkId providerId) {
235 return snetService.serviceNetworks().stream()
236 .flatMap(net -> net.providers().keySet().stream())
Hyunsun Moon851e57f2017-02-23 19:38:40 +0900237 .anyMatch(provider -> Objects.equals(provider, providerId));
Hyunsun Moon187bf532017-01-19 10:57:40 +0900238 }
239
240 private void removeGroup(NetworkId netId) {
241 GroupKey groupKey = getGroupKey(netId);
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900242 nodeService.completeNodes().forEach(node -> {
Hyunsun Moon851e57f2017-02-23 19:38:40 +0900243 DeviceId deviceId = node.integrationBridgeId();
244 Group group = groupService.getGroup(deviceId, groupKey);
245 if (group != null) {
246 groupService.removeGroup(deviceId, groupKey, appId);
247 }
248 });
Hyunsun Moon187bf532017-01-19 10:57:40 +0900249 log.debug("Removed group for network {}", netId);
250 }
251
252 private GroupId getGroupId(NetworkId netId, DeviceId deviceId) {
253 return new GroupId(Objects.hash(netId, deviceId));
254 }
255
256 private GroupKey getGroupKey(NetworkId netId) {
257 return new DefaultGroupKey(netId.id().getBytes());
258 }
259
260 private GroupId getProviderGroup(ServiceNetwork provider, DeviceId deviceId) {
261 GroupKey groupKey = getGroupKey(provider.id());
262 Group group = groupService.getGroup(deviceId, groupKey);
263 GroupId groupId = getGroupId(provider.id(), deviceId);
264
265 if (group != null) {
266 return groupId;
267 }
268
269 GroupBuckets buckets = getProviderGroupBuckets(
270 deviceId, provider.segmentId().id(), getInstances(provider.id()));
271 GroupDescription groupDescription = new DefaultGroupDescription(
272 deviceId,
273 GroupDescription.Type.SELECT,
274 buckets,
275 groupKey,
276 groupId.id(),
277 appId);
278
279 groupService.addGroup(groupDescription);
280 return groupId;
281 }
282
283 private void populateDependencyRules(ServiceNetwork subscriber, ServiceNetwork provider,
284 DependencyType type, boolean install) {
285 Map<DeviceId, GroupId> providerGroups = Maps.newHashMap();
286 Map<DeviceId, Set<PortNumber>> subscriberPorts = Maps.newHashMap();
287
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900288 nodeService.completeNodes().forEach(node -> {
Hyunsun Moon187bf532017-01-19 10:57:40 +0900289 DeviceId deviceId = node.integrationBridgeId();
290 GroupId groupId = getProviderGroup(provider, deviceId);
291 providerGroups.put(deviceId, groupId);
292
293 Set<PortNumber> ports = getInstances(subscriber.id())
294 .stream()
295 .filter(instance -> instance.deviceId().equals(deviceId))
296 .map(Instance::portNumber)
297 .collect(Collectors.toSet());
298 subscriberPorts.put(deviceId, ports);
299 });
300
301 // TODO support IPv6
302 IpPrefix sSubnet = subscriber.subnet().getIp4Prefix();
303 IpPrefix pSubnet = provider.subnet().getIp4Prefix();
304
305 populateInPortRule(subscriberPorts, providerGroups, install);
306 populateIndirectAccessRule(
307 sSubnet,
308 provider.serviceIp().getIp4Address(),
309 providerGroups,
310 install);
311 populateDirectAccessRule(sSubnet, pSubnet, install);
312 if (type == BIDIRECTIONAL) {
313 populateDirectAccessRule(pSubnet, sSubnet, install);
314 }
315 }
316
317 private void populateIndirectAccessRule(IpPrefix srcSubnet, IpAddress serviceIp,
318 Map<DeviceId, GroupId> outGroups,
319 boolean install) {
320 // TODO support IPv6
321 TrafficSelector selector = DefaultTrafficSelector.builder()
322 .matchEthType(Ethernet.TYPE_IPV4)
323 .matchIPSrc(srcSubnet)
324 .matchIPDst(serviceIp.toIpPrefix())
325 .build();
326
327 for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
328 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
329 .group(outGroup.getValue())
330 .build();
331
332 FlowRule flowRule = DefaultFlowRule.builder()
333 .fromApp(appId)
334 .withSelector(selector)
335 .withTreatment(treatment)
336 .withPriority(PRIORITY_HIGH)
337 .forDevice(outGroup.getKey())
338 .forTable(TABLE_ACCESS)
339 .makePermanent()
340 .build();
341
342 pipeline.processFlowRule(install, flowRule);
343 }
344 }
345
346 private void populateDirectAccessRule(IpPrefix srcIp, IpPrefix dstIp, boolean install) {
347 // TODO support IPv6
348 TrafficSelector selector = DefaultTrafficSelector.builder()
349 .matchEthType(Ethernet.TYPE_IPV4)
350 .matchIPSrc(srcIp)
351 .matchIPDst(dstIp)
352 .build();
353
354 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
355 .transition(TABLE_DST)
356 .build();
357
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900358 nodeService.completeNodes().forEach(node -> {
Hyunsun Moon187bf532017-01-19 10:57:40 +0900359 DeviceId deviceId = node.integrationBridgeId();
360 FlowRule flowRuleDirect = DefaultFlowRule.builder()
361 .fromApp(appId)
362 .withSelector(selector)
363 .withTreatment(treatment)
364 .withPriority(PRIORITY_DEFAULT)
365 .forDevice(deviceId)
366 .forTable(TABLE_ACCESS)
367 .makePermanent()
368 .build();
369
370 pipeline.processFlowRule(install, flowRuleDirect);
371 });
372 }
373
374 private void populateInPortRule(Map<DeviceId, Set<PortNumber>> subscriberPorts,
375 Map<DeviceId, GroupId> providerGroups,
376 boolean install) {
377 for (Map.Entry<DeviceId, Set<PortNumber>> entry : subscriberPorts.entrySet()) {
378 Set<PortNumber> ports = entry.getValue();
379 DeviceId deviceId = entry.getKey();
380 GroupId groupId = providerGroups.get(deviceId);
381 if (groupId == null) {
382 continue;
383 }
Hyunsun Moon851e57f2017-02-23 19:38:40 +0900384 ports.forEach(port -> {
Hyunsun Moon187bf532017-01-19 10:57:40 +0900385 TrafficSelector selector = DefaultTrafficSelector.builder()
386 .matchInPort(port)
387 .build();
388
389 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
390 .group(groupId)
391 .build();
392
393 FlowRule flowRule = DefaultFlowRule.builder()
394 .fromApp(appId)
395 .withSelector(selector)
396 .withTreatment(treatment)
397 .withPriority(PRIORITY_DEFAULT)
398 .forDevice(deviceId)
399 .forTable(TABLE_IN_SERVICE)
400 .makePermanent()
401 .build();
402
403 pipeline.processFlowRule(install, flowRule);
404 });
405 }
406 }
407
408 private GroupBuckets getProviderGroupBuckets(DeviceId deviceId, long tunnelId,
409 Set<Instance> instances) {
410 List<GroupBucket> buckets = Lists.newArrayList();
Hyunsun Moon851e57f2017-02-23 19:38:40 +0900411 instances.forEach(instance -> {
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900412 Ip4Address tunnelIp = dataIp(instance.deviceId()).getIp4Address();
Hyunsun Moon187bf532017-01-19 10:57:40 +0900413
414 if (deviceId.equals(instance.deviceId())) {
415 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
416 .setEthDst(instance.mac())
417 .setOutput(instance.portNumber())
418 .build();
419 buckets.add(createSelectGroupBucket(treatment));
420 } else {
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900421 ExtensionTreatment tunnelDst = tunnelDstTreatment(deviceId, tunnelIp);
Hyunsun Moon187bf532017-01-19 10:57:40 +0900422 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
423 .setEthDst(instance.mac())
424 .extension(tunnelDst, deviceId)
425 .setTunnelId(tunnelId)
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900426 .setOutput(tunnelPort(instance.deviceId()))
Hyunsun Moon187bf532017-01-19 10:57:40 +0900427 .build();
428 buckets.add(createSelectGroupBucket(treatment));
429 }
430 });
431 return new GroupBuckets(buckets);
432 }
433
434 private class InternalServiceNetworkListener implements ServiceNetworkListener {
435
436 @Override
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900437 public void event(ServiceNetworkEvent event) {
438 eventExecutor.execute(() -> {
439 NodeId leader = leadershipService.getLeader(appId.name());
440 if (!Objects.equals(localNodeId, leader)) {
441 // do not allow to proceed without leadership
442 return;
443 }
444 handle(event);
445 });
Hyunsun Moon187bf532017-01-19 10:57:40 +0900446 }
447
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900448 private void handle(ServiceNetworkEvent event) {
Hyunsun Moon187bf532017-01-19 10:57:40 +0900449 switch (event.type()) {
450 case SERVICE_NETWORK_PROVIDER_ADDED:
451 log.debug("Dependency added: {}", event);
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900452 dependencyAdded(event.subject(),
453 event.provider().provider(),
454 event.provider().type());
Hyunsun Moon187bf532017-01-19 10:57:40 +0900455 break;
456 case SERVICE_NETWORK_PROVIDER_REMOVED:
457 log.debug("Dependency removed: {}", event);
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +0900458 dependencyRemoved(event.subject(),
459 event.provider().provider(),
460 event.provider().type());
Hyunsun Moon187bf532017-01-19 10:57:40 +0900461 break;
462 case SERVICE_NETWORK_CREATED:
463 case SERVICE_NETWORK_UPDATED:
464 // TODO handle dependency rule related element updates
465 case SERVICE_NETWORK_REMOVED:
466 case SERVICE_PORT_CREATED:
467 case SERVICE_PORT_UPDATED:
468 case SERVICE_PORT_REMOVED:
469 default:
470 // do nothing for the other events
471 break;
472 }
473 }
474 }
475}