CORD-628 Refactored VTN service network and port API
- Removed direct use of Neutron data model and Neutron API
- Extended service network and service port API to have all network
information required for VTN
- Removed unnecessary dependency manager and store
- Removed network state sync method with Neutron and XOS
- Removed Neutron and XOS access information from the network config
- Re-organized API packages
Change-Id: I18f49ec733309315f683dfb2e6be6526056118f1
diff --git a/src/main/java/org/opencord/cordvtn/impl/handler/DependencyHandler.java b/src/main/java/org/opencord/cordvtn/impl/handler/DependencyHandler.java
new file mode 100644
index 0000000..684bc6e
--- /dev/null
+++ b/src/main/java/org/opencord/cordvtn/impl/handler/DependencyHandler.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opencord.cordvtn.impl.handler;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
+import org.opencord.cordvtn.api.core.Instance;
+import org.opencord.cordvtn.api.core.ServiceNetworkAdminService;
+import org.opencord.cordvtn.api.core.ServiceNetworkEvent;
+import org.opencord.cordvtn.api.core.ServiceNetworkListener;
+import org.opencord.cordvtn.api.net.NetworkId;
+import org.opencord.cordvtn.api.net.ServiceNetwork;
+import org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType;
+import org.opencord.cordvtn.api.node.CordVtnNode;
+import org.opencord.cordvtn.impl.CordVtnNodeManager;
+import org.opencord.cordvtn.impl.CordVtnPipeline;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.group.DefaultGroupBucket.createSelectGroupBucket;
+import static org.opencord.cordvtn.api.net.ServiceNetwork.DependencyType.BIDIRECTIONAL;
+import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.*;
+import static org.opencord.cordvtn.impl.CordVtnPipeline.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provisions service dependencies between service networks.
+ */
+@Component(immediate = true)
+@Service
+public class DependencyHandler extends AbstractInstanceHandler {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String ERR_NET_FAIL = "Failed to get VTN network ";
+ private static final String ADDED = "Added ";
+ private static final String REMOVED = "Removed ";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected GroupService groupService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CordVtnPipeline pipeline;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CordVtnNodeManager nodeManager;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ServiceNetworkAdminService snetService;
+
+ private final ServiceNetworkListener snetListener = new InternalServiceNetworkListener();
+ private NodeId localNodeId;
+
+ @Activate
+ protected void activate() {
+ netTypes = ImmutableSet.of(PRIVATE, PUBLIC, VSG);
+ super.activate();
+ localNodeId = clusterService.getLocalNode().id();
+ leadershipService.runForLeadership(appId.name());
+ snetService.addListener(snetListener);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ snetService.removeListener(snetListener);
+ leadershipService.withdraw(appId.name());
+ }
+
+ @Override
+ public void instanceDetected(Instance instance) {
+ ServiceNetwork snet = snetService.serviceNetwork(instance.netId());
+ if (snet == null) {
+ final String error = ERR_NET_FAIL + instance.netId();
+ throw new IllegalStateException(error);
+ }
+ if (!snet.providers().isEmpty()) {
+ updateSubscriberInstances(snet, instance, true);
+ }
+ // TODO check if subscribers on this network
+ updateProviderInstances(snet);
+ }
+
+ @Override
+ public void instanceRemoved(Instance instance) {
+ ServiceNetwork snet = snetService.serviceNetwork(instance.netId());
+ if (snet == null) {
+ final String error = ERR_NET_FAIL + instance.netId();
+ throw new IllegalStateException(error);
+ }
+ if (!snet.providers().isEmpty()) {
+ updateSubscriberInstances(snet, instance, false);
+ }
+ // TODO check if subscribers on this network and remove group if unused
+ updateProviderInstances(snet);
+ }
+
+ private void dependencyAdded(ServiceNetwork subscriber, ServiceNetwork provider,
+ DependencyType type) {
+ populateDependencyRules(subscriber, provider, type, true);
+ log.info("Dependency is created subscriber:{}, provider:{}, type: {}",
+ subscriber.name(),
+ provider.name(), type.name());
+ }
+
+ private void dependencyRemoved(ServiceNetwork subscriber, ServiceNetwork provider,
+ DependencyType type) {
+ populateDependencyRules(subscriber, provider, type, false);
+ if (!isProviderInUse(provider.id())) {
+ removeGroup(provider.id());
+ }
+ log.info("Dependency is removed subscriber:{}, provider:{}, type: {}",
+ subscriber.name(),
+ provider.name(), type.name());
+ }
+
+ private void updateProviderInstances(ServiceNetwork provider) {
+ Set<DeviceId> devices = nodeManager.completeNodes().stream()
+ .map(CordVtnNode::integrationBridgeId)
+ .collect(Collectors.toSet());
+
+ GroupKey groupKey = getGroupKey(provider.id());
+ for (DeviceId deviceId : devices) {
+ Group group = groupService.getGroup(deviceId, groupKey);
+ if (group == null) {
+ continue;
+ }
+ List<GroupBucket> oldBuckets = group.buckets().buckets();
+ List<GroupBucket> newBuckets = getProviderGroupBuckets(
+ deviceId,
+ provider.segmentId().id(),
+ getInstances(provider.id())).buckets();
+ if (oldBuckets.equals(newBuckets)) {
+ continue;
+ }
+
+ List<GroupBucket> bucketsToRemove = Lists.newArrayList(oldBuckets);
+ bucketsToRemove.removeAll(newBuckets);
+ if (!bucketsToRemove.isEmpty()) {
+ groupService.removeBucketsFromGroup(
+ deviceId,
+ groupKey,
+ new GroupBuckets(bucketsToRemove),
+ groupKey, appId);
+ log.debug("Removed buckets from provider({}) group on {}: {}",
+ provider.id(), deviceId, bucketsToRemove);
+ }
+
+ List<GroupBucket> bucketsToAdd = Lists.newArrayList(newBuckets);
+ bucketsToAdd.removeAll(oldBuckets);
+ if (!bucketsToAdd.isEmpty()) {
+ groupService.addBucketsToGroup(
+ deviceId,
+ groupKey,
+ new GroupBuckets(bucketsToAdd),
+ groupKey, appId);
+ log.debug("Added buckets to provider({}) group on {}: {}",
+ provider.id(), deviceId, bucketsToAdd);
+ }
+ }
+ }
+
+ private void updateSubscriberInstances(ServiceNetwork subscriber, Instance instance,
+ boolean isDetected) {
+ DeviceId deviceId = instance.deviceId();
+ final String isAdded = isDetected ? ADDED : REMOVED;
+ subscriber.providers().keySet().forEach(providerId -> {
+ populateInPortRule(
+ ImmutableMap.of(deviceId, ImmutableSet.of(instance.portNumber())),
+ ImmutableMap.of(deviceId, getGroupId(providerId, deviceId)),
+ isDetected);
+ log.info(isAdded + "subscriber instance({}) for provider({})",
+ instance.host().id(), providerId.id());
+ });
+ }
+
+ private boolean isProviderInUse(NetworkId providerId) {
+ return snetService.serviceNetworks().stream()
+ .flatMap(net -> net.providers().keySet().stream())
+ .filter(provider -> Objects.equals(provider, providerId))
+ .findAny().isPresent();
+ }
+
+ private void removeGroup(NetworkId netId) {
+ GroupKey groupKey = getGroupKey(netId);
+ nodeManager.completeNodes().stream()
+ .forEach(node -> {
+ DeviceId deviceId = node.integrationBridgeId();
+ Group group = groupService.getGroup(deviceId, groupKey);
+ if (group != null) {
+ groupService.removeGroup(deviceId, groupKey, appId);
+ }
+ });
+ log.debug("Removed group for network {}", netId);
+ }
+
+ private GroupId getGroupId(NetworkId netId, DeviceId deviceId) {
+ return new GroupId(Objects.hash(netId, deviceId));
+ }
+
+ private GroupKey getGroupKey(NetworkId netId) {
+ return new DefaultGroupKey(netId.id().getBytes());
+ }
+
+ private GroupId getProviderGroup(ServiceNetwork provider, DeviceId deviceId) {
+ GroupKey groupKey = getGroupKey(provider.id());
+ Group group = groupService.getGroup(deviceId, groupKey);
+ GroupId groupId = getGroupId(provider.id(), deviceId);
+
+ if (group != null) {
+ return groupId;
+ }
+
+ GroupBuckets buckets = getProviderGroupBuckets(
+ deviceId, provider.segmentId().id(), getInstances(provider.id()));
+ GroupDescription groupDescription = new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.SELECT,
+ buckets,
+ groupKey,
+ groupId.id(),
+ appId);
+
+ groupService.addGroup(groupDescription);
+ return groupId;
+ }
+
+ private void populateDependencyRules(ServiceNetwork subscriber, ServiceNetwork provider,
+ DependencyType type, boolean install) {
+ Map<DeviceId, GroupId> providerGroups = Maps.newHashMap();
+ Map<DeviceId, Set<PortNumber>> subscriberPorts = Maps.newHashMap();
+
+ nodeManager.completeNodes().stream().forEach(node -> {
+ DeviceId deviceId = node.integrationBridgeId();
+ GroupId groupId = getProviderGroup(provider, deviceId);
+ providerGroups.put(deviceId, groupId);
+
+ Set<PortNumber> ports = getInstances(subscriber.id())
+ .stream()
+ .filter(instance -> instance.deviceId().equals(deviceId))
+ .map(Instance::portNumber)
+ .collect(Collectors.toSet());
+ subscriberPorts.put(deviceId, ports);
+ });
+
+ // TODO support IPv6
+ IpPrefix sSubnet = subscriber.subnet().getIp4Prefix();
+ IpPrefix pSubnet = provider.subnet().getIp4Prefix();
+
+ populateInPortRule(subscriberPorts, providerGroups, install);
+ populateIndirectAccessRule(
+ sSubnet,
+ provider.serviceIp().getIp4Address(),
+ providerGroups,
+ install);
+ populateDirectAccessRule(sSubnet, pSubnet, install);
+ if (type == BIDIRECTIONAL) {
+ populateDirectAccessRule(pSubnet, sSubnet, install);
+ }
+ }
+
+ private void populateIndirectAccessRule(IpPrefix srcSubnet, IpAddress serviceIp,
+ Map<DeviceId, GroupId> outGroups,
+ boolean install) {
+ // TODO support IPv6
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(srcSubnet)
+ .matchIPDst(serviceIp.toIpPrefix())
+ .build();
+
+ for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .group(outGroup.getValue())
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_HIGH)
+ .forDevice(outGroup.getKey())
+ .forTable(TABLE_ACCESS)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+ }
+
+ private void populateDirectAccessRule(IpPrefix srcIp, IpPrefix dstIp, boolean install) {
+ // TODO support IPv6
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(srcIp)
+ .matchIPDst(dstIp)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_DST)
+ .build();
+
+ nodeManager.completeNodes().stream().forEach(node -> {
+ DeviceId deviceId = node.integrationBridgeId();
+ FlowRule flowRuleDirect = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(deviceId)
+ .forTable(TABLE_ACCESS)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRuleDirect);
+ });
+ }
+
+ private void populateInPortRule(Map<DeviceId, Set<PortNumber>> subscriberPorts,
+ Map<DeviceId, GroupId> providerGroups,
+ boolean install) {
+ for (Map.Entry<DeviceId, Set<PortNumber>> entry : subscriberPorts.entrySet()) {
+ Set<PortNumber> ports = entry.getValue();
+ DeviceId deviceId = entry.getKey();
+ GroupId groupId = providerGroups.get(deviceId);
+ if (groupId == null) {
+ continue;
+ }
+ ports.stream().forEach(port -> {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(port)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .group(groupId)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(deviceId)
+ .forTable(TABLE_IN_SERVICE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ });
+ }
+ }
+
+ private GroupBuckets getProviderGroupBuckets(DeviceId deviceId, long tunnelId,
+ Set<Instance> instances) {
+ List<GroupBucket> buckets = Lists.newArrayList();
+ instances.stream().forEach(instance -> {
+ Ip4Address tunnelIp = nodeManager.dataIp(instance.deviceId()).getIp4Address();
+
+ if (deviceId.equals(instance.deviceId())) {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(instance.mac())
+ .setOutput(instance.portNumber())
+ .build();
+ buckets.add(createSelectGroupBucket(treatment));
+ } else {
+ ExtensionTreatment tunnelDst =
+ pipeline.tunnelDstTreatment(deviceId, tunnelIp);
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(instance.mac())
+ .extension(tunnelDst, deviceId)
+ .setTunnelId(tunnelId)
+ .setOutput(nodeManager.tunnelPort(instance.deviceId()))
+ .build();
+ buckets.add(createSelectGroupBucket(treatment));
+ }
+ });
+ return new GroupBuckets(buckets);
+ }
+
+ private class InternalServiceNetworkListener implements ServiceNetworkListener {
+
+ @Override
+ public boolean isRelevant(ServiceNetworkEvent event) {
+ // do not allow to proceed without leadership
+ NodeId leader = leadershipService.getLeader(appId.name());
+ return Objects.equals(localNodeId, leader);
+ }
+
+ @Override
+ public void event(ServiceNetworkEvent event) {
+
+ switch (event.type()) {
+ case SERVICE_NETWORK_PROVIDER_ADDED:
+ log.debug("Dependency added: {}", event);
+ eventExecutor.execute(() -> {
+ dependencyAdded(
+ event.subject(),
+ event.provider().provider(),
+ event.provider().type());
+ });
+ break;
+ case SERVICE_NETWORK_PROVIDER_REMOVED:
+ log.debug("Dependency removed: {}", event);
+ eventExecutor.execute(() -> {
+ dependencyRemoved(
+ event.subject(),
+ event.provider().provider(),
+ event.provider().type());
+ });
+ break;
+ case SERVICE_NETWORK_CREATED:
+ case SERVICE_NETWORK_UPDATED:
+ // TODO handle dependency rule related element updates
+ case SERVICE_NETWORK_REMOVED:
+ case SERVICE_PORT_CREATED:
+ case SERVICE_PORT_UPDATED:
+ case SERVICE_PORT_REMOVED:
+ default:
+ // do nothing for the other events
+ break;
+ }
+ }
+ }
+}