From e63291850fd0795c5700e25e67e5dee89ba54c5f Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Tue, 1 Dec 2015 05:49:27 -0800 Subject: onos commit hash c2999f30c69e50df905a9d175ef80b3f23a98514 Change-Id: I2bb8562c4942b6d6a6d60b663db2e17540477b81 Signed-off-by: Ashlee Young --- .../main/java/org/onosproject/cordvtn/CordVtn.java | 287 ++++++++++++++++++++- .../java/org/onosproject/cordvtn/CordVtnNode.java | 133 ++++++++++ .../onosproject/cordvtn/CordVtnRuleInstaller.java | 231 +++++++++++++++++ .../org/onosproject/cordvtn/DestinationInfo.java | 190 ++++++++++++++ .../cordvtn/cli/CordVtnNodeAddCommand.java | 64 +++++ .../cordvtn/cli/CordVtnNodeDeleteCommand.java | 57 ++++ .../cordvtn/cli/CordVtnNodeInitCommand.java | 57 ++++ .../cordvtn/cli/CordVtnNodeListCommand.java | 74 ++++++ 8 files changed, 1085 insertions(+), 8 deletions(-) create mode 100644 framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java create mode 100644 framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java create mode 100644 framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/DestinationInfo.java create mode 100644 framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeAddCommand.java create mode 100644 framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeDeleteCommand.java create mode 100644 framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java create mode 100644 framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeListCommand.java (limited to 'framework/src/onos/apps/cordvtn/src') diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java index c3bf77c5..67297741 100644 --- a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java @@ -15,6 +15,8 @@ */ package org.onosproject.cordvtn; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; @@ -23,6 +25,7 @@ import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onlab.util.ItemNotFoundException; +import org.onlab.packet.IpAddress; import org.onlab.util.KryoNamespace; import org.onosproject.cluster.ClusterService; import org.onosproject.core.ApplicationId; @@ -31,9 +34,11 @@ import org.onosproject.net.DefaultAnnotations; import org.onosproject.net.Device; import org.onosproject.net.DeviceId; import org.onosproject.net.Host; +import org.onosproject.net.HostId; import org.onosproject.net.Port; import org.onosproject.net.behaviour.BridgeConfig; import org.onosproject.net.behaviour.BridgeName; +import org.onosproject.net.ConnectPoint; import org.onosproject.net.behaviour.ControllerInfo; import org.onosproject.net.behaviour.DefaultTunnelDescription; import org.onosproject.net.behaviour.TunnelConfig; @@ -45,9 +50,13 @@ import org.onosproject.net.device.DeviceListener; import org.onosproject.net.device.DeviceService; import org.onosproject.net.driver.DriverHandler; import org.onosproject.net.driver.DriverService; +import org.onosproject.net.flowobjective.FlowObjectiveService; import org.onosproject.net.host.HostEvent; import org.onosproject.net.host.HostListener; import org.onosproject.net.host.HostService; +import org.onosproject.openstackswitching.OpenstackNetwork; +import org.onosproject.openstackswitching.OpenstackPort; +import org.onosproject.openstackswitching.OpenstackSwitchingService; import org.onosproject.ovsdb.controller.OvsdbClientService; import org.onosproject.ovsdb.controller.OvsdbController; import org.onosproject.ovsdb.controller.OvsdbNodeId; @@ -62,8 +71,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; import static org.onlab.util.Tools.groupedThreads; @@ -72,8 +83,8 @@ import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN; import static org.slf4j.LoggerFactory.getLogger; /** - * Provides initial setup or cleanup for provisioning virtual tenant networks - * on ovsdb, integration bridge and vm when they are added or deleted. + * Provisions virtual tenant networks with service chaining capability + * in OpenStack environment. */ @Component(immediate = true) @Service @@ -86,7 +97,8 @@ public class CordVtn implements CordVtnService { .register(KryoNamespaces.API) .register(CordVtnNode.class) .register(NodeState.class); - private static final String DEFAULT_BRIDGE_NAME = "br-int"; + private static final String DEFAULT_BRIDGE = "br-int"; + private static final String VPORT_PREFIX = "tap"; private static final String DEFAULT_TUNNEL = "vxlan"; private static final Map DEFAULT_TUNNEL_OPTIONS = new HashMap() { { @@ -115,12 +127,18 @@ public class CordVtn implements CordVtnService { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected DeviceAdminService adminService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowObjectiveService flowObjectiveService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected OvsdbController controller; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ClusterService clusterService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OpenstackSwitchingService openstackService; + private final ExecutorService eventExecutor = Executors .newFixedThreadPool(NUM_THREADS, groupedThreads("onos/cordvtn", "event-handler")); @@ -132,6 +150,8 @@ public class CordVtn implements CordVtnService { private final VmHandler vmHandler = new VmHandler(); private ConsistentMap nodeStore; + private Map hostNetworkMap = Maps.newHashMap(); + private CordVtnRuleInstaller ruleInstaller; private enum NodeState { @@ -185,6 +205,8 @@ public class CordVtn implements CordVtnService { .withApplicationId(appId) .build(); + ruleInstaller = new CordVtnRuleInstaller(appId, flowObjectiveService, + driverService, DEFAULT_TUNNEL); deviceService.addListener(deviceListener); hostService.addListener(hostListener); @@ -314,11 +336,27 @@ public class CordVtn implements CordVtnService { /** * Performs tasks after node initialization. + * First disconnect unnecessary OVSDB connection and then installs flow rules + * for existing VMs if there are any. * * @param node cordvtn node */ private void postInit(CordVtnNode node) { disconnect(node); + + Set vNets = Sets.newHashSet(); + hostService.getConnectedHosts(node.intBrId()) + .stream() + .forEach(host -> { + OpenstackNetwork vNet = getOpenstackNetworkByHost(host); + if (vNet != null) { + log.info("VM {} is detected", host.id()); + + hostNetworkMap.put(host.id(), vNet.id()); + vNets.add(vNet); + } + }); + vNets.stream().forEach(this::installFlowRules); } /** @@ -443,7 +481,7 @@ public class CordVtn implements CordVtnService { } List controllers = new ArrayList<>(); - Sets.newHashSet(clusterService.getNodes()) + Sets.newHashSet(clusterService.getNodes()).stream() .forEach(controller -> { ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp"); controllers.add(ctrlInfo); @@ -453,7 +491,7 @@ public class CordVtn implements CordVtnService { try { DriverHandler handler = driverService.createHandler(node.ovsdbId()); BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class); - bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE_NAME), dpid, controllers); + bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers); } catch (ItemNotFoundException e) { log.warn("Failed to create integration bridge on {}", node.ovsdbId()); } @@ -474,13 +512,12 @@ public class CordVtn implements CordVtnService { optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key)); } TunnelDescription description = - new DefaultTunnelDescription(null, null, VXLAN, - TunnelName.tunnelName(DEFAULT_TUNNEL), + new DefaultTunnelDescription(null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL), optionBuilder.build()); try { DriverHandler handler = driverService.createHandler(node.ovsdbId()); TunnelConfig tunnelConfig = handler.behaviour(TunnelConfig.class); - tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE_NAME), description); + tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description); } catch (ItemNotFoundException e) { log.warn("Failed to create tunnel interface on {}", node.ovsdbId()); } @@ -516,6 +553,212 @@ public class CordVtn implements CordVtnService { } } + /** + * Returns tunnel port of the device. + * + * @param bridgeId device id + * @return port, null if no tunnel port exists on a given device + */ + private Port getTunnelPort(DeviceId bridgeId) { + try { + return deviceService.getPorts(bridgeId).stream() + .filter(p -> p.annotations().value("portName").contains(DEFAULT_TUNNEL) + && p.isEnabled()) + .findFirst().get(); + } catch (NoSuchElementException e) { + return null; + } + } + + /** + * Returns remote ip address for tunneling. + * + * @param bridgeId device id + * @return ip address, null if no such device exists + */ + private IpAddress getRemoteIp(DeviceId bridgeId) { + CordVtnNode node = getNodeByBridgeId(bridgeId); + if (node != null) { + // TODO get data plane IP for tunneling + return node.ovsdbIp(); + } else { + return null; + } + } + + /** + * Returns destination information of all ports associated with a given + * OpenStack network. Output of the destination information is set to local + * port or tunnel port according to a given device id. + * + * @param deviceId device id to install flow rules + * @param vNet OpenStack network + * @return list of flow information, empty list if no flow information exists + */ + private List getSameNetworkPortsInfo(DeviceId deviceId, OpenstackNetwork vNet) { + List dstInfos = Lists.newArrayList(); + long tunnelId = Long.valueOf(vNet.segmentId()); + + for (OpenstackPort vPort : openstackService.ports(vNet.id())) { + ConnectPoint cp = getConnectPoint(vPort); + if (cp == null) { + log.debug("Couldn't find connection point for OpenStack port {}", vPort.id()); + continue; + } + + DestinationInfo.Builder dBuilder = cp.deviceId().equals(deviceId) ? + DestinationInfo.builder(deviceService.getPort(cp.deviceId(), cp.port())) : + DestinationInfo.builder(getTunnelPort(deviceId)) + .setRemoteIp(getRemoteIp(cp.deviceId())); + + dBuilder.setMac(vPort.macAddress()) + .setTunnelId(tunnelId); + dstInfos.add(dBuilder.build()); + } + return dstInfos; + } + + /** + * Returns local ports associated with a given OpenStack network. + * + * @param bridgeId device id + * @param vNet OpenStack network + * @return port list, empty list if no port exists + */ + private List getLocalSameNetworkPorts(DeviceId bridgeId, OpenstackNetwork vNet) { + List ports = new ArrayList<>(); + openstackService.ports(vNet.id()).stream().forEach(port -> { + ConnectPoint cp = getConnectPoint(port); + if (cp != null && cp.deviceId().equals(bridgeId)) { + ports.add(deviceService.getPort(cp.deviceId(), cp.port())); + } + }); + return ports; + } + + /** + * Returns OpenStack port associated with a given host. + * + * @param host host + * @return OpenStack port, or null if no port has been found + */ + private OpenstackPort getOpenstackPortByHost(Host host) { + Port port = deviceService.getPort(host.location().deviceId(), + host.location().port()); + return openstackService.port(port); + } + + /** + * Returns OpenStack network associated with a given host. + * + * @param host host + * @return OpenStack network, or null if no network has been found + */ + private OpenstackNetwork getOpenstackNetworkByHost(Host host) { + OpenstackPort vPort = getOpenstackPortByHost(host); + if (vPort != null) { + return openstackService.network(vPort.networkId()); + } else { + return null; + } + } + + /** + * Returns port name with OpenStack port information. + * + * @param vPort OpenStack port + * @return port name + */ + private String getPortName(OpenstackPort vPort) { + checkNotNull(vPort); + return VPORT_PREFIX + vPort.id().substring(0, 10); + } + + /** + * Returns connect point of a given OpenStack port. + * It assumes there's only one physical port associated with an OpenStack port. + * + * @param vPort openstack port + * @return connect point, null if no such port exists + */ + private ConnectPoint getConnectPoint(OpenstackPort vPort) { + try { + Host host = hostService.getHostsByMac(vPort.macAddress()) + .stream() + .findFirst() + .get(); + return new ConnectPoint(host.location().deviceId(), host.location().port()); + } catch (NoSuchElementException e) { + log.debug("Not a valid host with {}", vPort.macAddress()); + return null; + } + } + + /** + * Installs flow rules for a given OpenStack network. + * + * @param vNet OpenStack network + */ + private void installFlowRules(OpenstackNetwork vNet) { + checkNotNull(vNet, "Tenant network should not be null"); + + for (Device device : deviceService.getAvailableDevices(SWITCH)) { + List dstInfos = getSameNetworkPortsInfo(device.id(), vNet); + + for (Port inPort : getLocalSameNetworkPorts(device.id(), vNet)) { + List localInInfos = dstInfos.stream() + .filter(info -> !info.output().equals(inPort)) + .collect(Collectors.toList()); + ruleInstaller.installFlowRulesLocalIn(device.id(), inPort, localInInfos); + } + + Port tunPort = getTunnelPort(device.id()); + List tunnelInInfos = dstInfos.stream() + .filter(info -> !info.output().equals(tunPort)) + .collect(Collectors.toList()); + ruleInstaller.installFlowRulesTunnelIn(device.id(), tunPort, tunnelInInfos); + } + } + + /** + * Uninstalls flow rules associated with a given host for a given OpenStack network. + * + * @param vNet OpenStack network + * @param host removed host + */ + private void uninstallFlowRules(OpenstackNetwork vNet, Host host) { + checkNotNull(vNet, "Tenant network should not be null"); + + Port removedPort = deviceService.getPort(host.location().deviceId(), + host.location().port()); + + for (Device device : deviceService.getAvailableDevices(SWITCH)) { + List dstInfos = getSameNetworkPortsInfo(device.id(), vNet); + + for (Port inPort : getLocalSameNetworkPorts(device.id(), vNet)) { + List localInInfos = Lists.newArrayList( + DestinationInfo.builder(getTunnelPort(device.id())) + .setTunnelId(Long.valueOf(vNet.segmentId())) + .setMac(host.mac()) + .setRemoteIp(getRemoteIp(host.location().deviceId())) + .build()); + ruleInstaller.uninstallFlowRules(device.id(), inPort, localInInfos); + } + + if (device.id().equals(host.location().deviceId())) { + Port tunPort = getTunnelPort(device.id()); + List tunnelInInfo = Lists.newArrayList( + DestinationInfo.builder(removedPort) + .setTunnelId(Long.valueOf(vNet.segmentId())) + .setMac(host.mac()) + .build()); + + ruleInstaller.uninstallFlowRules(device.id(), tunPort, tunnelInInfo); + ruleInstaller.uninstallFlowRules(device.id(), removedPort, dstInfos); + } + } + } + private class InternalDeviceListener implements DeviceListener { @Override @@ -644,12 +887,40 @@ public class CordVtn implements CordVtnService { @Override public void connected(Host host) { + CordVtnNode node = getNodeByBridgeId(host.location().deviceId()); + if (node == null || !getNodeState(node).equals(NodeState.COMPLETE)) { + // do nothing for the host on unregistered or unprepared device + return; + } + + OpenstackNetwork vNet = getOpenstackNetworkByHost(host); + if (vNet == null) { + return; + } + log.info("VM {} is detected", host.id()); + + hostNetworkMap.put(host.id(), vNet.id()); + installFlowRules(vNet); } @Override public void disconnected(Host host) { + CordVtnNode node = getNodeByBridgeId(host.location().deviceId()); + if (node == null || !getNodeState(node).equals(NodeState.COMPLETE)) { + // do nothing for the host on unregistered or unprepared device + return; + } + + OpenstackNetwork vNet = openstackService.network(hostNetworkMap.get(host.id())); + if (vNet == null) { + return; + } + log.info("VM {} is vanished", host.id()); + + uninstallFlowRules(vNet, host); + hostNetworkMap.remove(host.id()); } } } diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java new file mode 100644 index 00000000..439d16e1 --- /dev/null +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnNode.java @@ -0,0 +1,133 @@ +/* + * Copyright 2014-2015 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.onosproject.cordvtn; + +import com.google.common.base.MoreObjects; +import org.onlab.packet.IpAddress; +import org.onlab.packet.TpPort; +import org.onosproject.net.DeviceId; + +import java.util.Comparator; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Representation of a compute infrastructure node for CORD VTN service. + */ +public final class CordVtnNode { + + private final String hostname; + private final IpAddress ovsdbIp; + private final TpPort ovsdbPort; + private final DeviceId bridgeId; + + public static final Comparator CORDVTN_NODE_COMPARATOR = + (node1, node2) -> node1.hostname().compareTo(node2.hostname()); + + /** + * Creates a new node. + * + * @param hostname hostname + * @param ovsdbIp OVSDB server IP address + * @param ovsdbPort OVSDB server port number + * @param bridgeId integration bridge identifier + */ + public CordVtnNode(String hostname, IpAddress ovsdbIp, TpPort ovsdbPort, DeviceId bridgeId) { + this.hostname = checkNotNull(hostname); + this.ovsdbIp = checkNotNull(ovsdbIp); + this.ovsdbPort = checkNotNull(ovsdbPort); + this.bridgeId = checkNotNull(bridgeId); + } + + /** + * Returns the OVSDB server IP address. + * + * @return ip address + */ + public IpAddress ovsdbIp() { + return this.ovsdbIp; + } + + /** + * Returns the OVSDB server port number. + * + * @return port number + */ + public TpPort ovsdbPort() { + return this.ovsdbPort; + } + + /** + * Returns the hostname. + * + * @return hostname + */ + public String hostname() { + return this.hostname; + } + + /** + * Returns the identifier of the integration bridge. + * + * @return device id + */ + public DeviceId intBrId() { + return this.bridgeId; + } + + /** + * Returns the identifier of the OVSDB device. + * + * @return device id + */ + public DeviceId ovsdbId() { + return DeviceId.deviceId("ovsdb:" + this.ovsdbIp.toString()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof CordVtnNode) { + CordVtnNode that = (CordVtnNode) obj; + if (Objects.equals(hostname, that.hostname) && + Objects.equals(ovsdbIp, that.ovsdbIp) && + Objects.equals(ovsdbPort, that.ovsdbPort) && + Objects.equals(bridgeId, that.bridgeId)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(hostname, ovsdbIp, ovsdbPort); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("host", hostname) + .add("ip", ovsdbIp) + .add("port", ovsdbPort) + .add("bridgeId", bridgeId) + .toString(); + } +} diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java new file mode 100644 index 00000000..9e22997c --- /dev/null +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java @@ -0,0 +1,231 @@ +/* + * Copyright 2015 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.onosproject.cordvtn; + +import org.onlab.packet.Ip4Address; +import org.onlab.util.ItemNotFoundException; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.behaviour.ExtensionTreatmentResolver; +import org.onosproject.net.driver.DefaultDriverData; +import org.onosproject.net.driver.DefaultDriverHandler; +import org.onosproject.net.driver.Driver; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.net.driver.DriverService; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.ExtensionPropertyException; +import org.onosproject.net.flow.instructions.ExtensionTreatment; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.slf4j.Logger; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Populates rules for virtual tenant network. + */ +public final class CordVtnRuleInstaller { + protected final Logger log = getLogger(getClass()); + + private static final int DEFAULT_PRIORITY = 5000; + + private final ApplicationId appId; + private final FlowObjectiveService flowObjectiveService; + private final DriverService driverService; + private final String tunnelType; + + /** + * Creates a new rule installer. + * + * @param appId application id + * @param flowObjectiveService flow objective service + * @param driverService driver service + * @param tunnelType tunnel type + */ + public CordVtnRuleInstaller(ApplicationId appId, + FlowObjectiveService flowObjectiveService, + DriverService driverService, + String tunnelType) { + this.appId = appId; + this.flowObjectiveService = flowObjectiveService; + this.driverService = driverService; + this.tunnelType = checkNotNull(tunnelType); + } + + /** + * Installs flow rules for tunnel in traffic. + * + * @param deviceId device id to install flow rules + * @param inPort in port + * @param dstInfos list of destination info + */ + public void installFlowRulesTunnelIn(DeviceId deviceId, Port inPort, List dstInfos) { + dstInfos.stream().forEach(dstInfo -> { + ForwardingObjective.Builder fBuilder = vtnRulesSameNode(inPort, dstInfo); + if (fBuilder != null) { + flowObjectiveService.forward(deviceId, fBuilder.add()); + } + }); + } + + /** + * Installs flow rules for local in traffic. + * + * @param deviceId device id to install flow rules + * @param inPort in port + * @param dstInfos list of destination info + */ + public void installFlowRulesLocalIn(DeviceId deviceId, Port inPort, List dstInfos) { + dstInfos.stream().forEach(dstInfo -> { + ForwardingObjective.Builder fBuilder = isTunnelPort(dstInfo.output()) ? + vtnRulesRemoteNode(deviceId, inPort, dstInfo) : vtnRulesSameNode(inPort, dstInfo); + + if (fBuilder != null) { + flowObjectiveService.forward(deviceId, fBuilder.add()); + } + }); + } + + /** + * Uninstalls flow rules associated with a given port from a given device. + * + * @param deviceId device id + * @param inPort port associated with removed host + * @param dstInfos list of destination info + */ + public void uninstallFlowRules(DeviceId deviceId, Port inPort, List dstInfos) { + dstInfos.stream().forEach(dstInfo -> { + ForwardingObjective.Builder fBuilder = isTunnelPort(dstInfo.output()) ? + vtnRulesRemoteNode(deviceId, inPort, dstInfo) : vtnRulesSameNode(inPort, dstInfo); + + if (fBuilder != null) { + flowObjectiveService.forward(deviceId, fBuilder.remove()); + } + }); + } + + /** + * Returns forwarding objective builder to provision basic virtual tenant network. + * This method cares for the traffics whose source and destination device is the same. + * + * @param inPort in port + * @param dstInfo destination information + * @return forwarding objective builder + */ + private ForwardingObjective.Builder vtnRulesSameNode(Port inPort, DestinationInfo dstInfo) { + checkArgument(inPort.element().id().equals(dstInfo.output().element().id())); + + TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + + sBuilder.matchInPort(inPort.number()) + .matchEthDst(dstInfo.mac()); + if (isTunnelPort(inPort)) { + sBuilder.matchTunnelId(dstInfo.tunnelId()); + } + + tBuilder.setOutput(dstInfo.output().number()); + + return DefaultForwardingObjective.builder() + .withSelector(sBuilder.build()) + .withTreatment(tBuilder.build()) + .withPriority(DEFAULT_PRIORITY) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .fromApp(appId) + .makePermanent(); + } + + /** + * Returns forwarding objective builder to provision basic virtual tenant network. + * This method cares for the traffics whose source and destination is not the same. + * + * @param deviceId device id to install flow rules + * @param inPort in port + * @param dstInfo destination information + * @return forwarding objective, or null if it fails to build it + */ + private ForwardingObjective.Builder vtnRulesRemoteNode(DeviceId deviceId, Port inPort, DestinationInfo dstInfo) { + checkArgument(isTunnelPort(dstInfo.output())); + + TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + + ExtensionTreatment extTreatment = + getTunnelDstInstruction(deviceId, dstInfo.remoteIp().getIp4Address()); + if (extTreatment == null) { + return null; + } + + sBuilder.matchInPort(inPort.number()) + .matchEthDst(dstInfo.mac()); + + tBuilder.extension(extTreatment, deviceId) + .setTunnelId(dstInfo.tunnelId()) + .setOutput(dstInfo.output().number()); + + return DefaultForwardingObjective.builder() + .withSelector(sBuilder.build()) + .withTreatment(tBuilder.build()) + .withPriority(DEFAULT_PRIORITY) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .fromApp(appId) + .makePermanent(); + } + + /** + * Checks if a given port is tunnel interface or not. + * It assumes the tunnel interface contains tunnelType string in its name. + * + * @param port port + * @return true if the port is tunnel interface, false otherwise. + */ + private boolean isTunnelPort(Port port) { + return port.annotations().value("portName").contains(tunnelType); + } + + /** + * Returns extension instruction to set tunnel destination. + * + * @param deviceId device id + * @param remoteIp tunnel destination address + * @return extension treatment or null if it fails to get instruction + */ + private ExtensionTreatment getTunnelDstInstruction(DeviceId deviceId, Ip4Address remoteIp) { + try { + Driver driver = driverService.getDriver(deviceId); + DriverHandler handler = new DefaultDriverHandler(new DefaultDriverData(driver, deviceId)); + ExtensionTreatmentResolver resolver = handler.behaviour(ExtensionTreatmentResolver.class); + + ExtensionTreatment treatment = resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type()); + treatment.setPropertyValue("tunnelDst", remoteIp); + + return treatment; + } catch (ItemNotFoundException | UnsupportedOperationException | ExtensionPropertyException e) { + log.error("Failed to get extension instruction to set tunnel dst {}", deviceId); + return null; + } + } +} diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/DestinationInfo.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/DestinationInfo.java new file mode 100644 index 00000000..290cc170 --- /dev/null +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/DestinationInfo.java @@ -0,0 +1,190 @@ +/* + * Copyright 2014-2015 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.onosproject.cordvtn; + +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onosproject.net.Port; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Contains destination information. + */ +public final class DestinationInfo { + + private final Port output; + private final List ip; + private final MacAddress mac; + private final IpAddress remoteIp; + private final long tunnelId; + + /** + * Creates a new destination information. + * + * @param output output port + * @param ip destination ip address + * @param mac destination mac address + * @param remoteIp tunnel remote ip address + * @param tunnelId segment id + */ + public DestinationInfo(Port output, List ip, MacAddress mac, + IpAddress remoteIp, long tunnelId) { + this.output = checkNotNull(output); + this.ip = ip; + this.mac = mac; + this.remoteIp = remoteIp; + this.tunnelId = tunnelId; + } + + /** + * Returns output port. + * + * @return port + */ + public Port output() { + return output; + } + + /** + * Returns destination ip addresses. + * + * @return list of ip address + */ + public List ip() { + return ip; + } + + /** + * Returns destination mac address. + * + * @return mac address + */ + public MacAddress mac() { + return mac; + } + + /** + * Returns tunnel remote ip address. + * + * @return ip address + */ + public IpAddress remoteIp() { + return remoteIp; + } + + /** + * Returns tunnel id. + * + * @return tunnel id + */ + public long tunnelId() { + return tunnelId; + } + + /** + * Returns a new destination info builder. + * + * @return destination info builder + */ + public static DestinationInfo.Builder builder(Port output) { + return new Builder(output); + } + + /** + * DestinationInfo builder class. + */ + public static final class Builder { + + private final Port output; + private List ip; + private MacAddress mac; + private IpAddress remoteIp; + private long tunnelId; + + /** + * Creates a new destination information builder. + * + * @param output output port + */ + public Builder(Port output) { + this.output = checkNotNull(output, "Output port cannot be null"); + } + + /** + * Sets the destination ip address. + * + * @param ip ip address + * @return destination info builder + */ + public Builder setIp(List ip) { + this.ip = checkNotNull(ip, "IP cannot be null"); + return this; + } + + /** + * Sets the destination mac address. + * + * @param mac mac address + * @return destination info builder + */ + public Builder setMac(MacAddress mac) { + this.mac = checkNotNull(mac, "MAC address cannot be null"); + return this; + } + + /** + * Sets the tunnel remote ip address. + * + * @param remoteIp ip address + * @return destination info builder + */ + public Builder setRemoteIp(IpAddress remoteIp) { + this.remoteIp = checkNotNull(remoteIp, "Remote IP address cannot be null"); + return this; + } + + /** + * Sets the tunnel id. + * + * @param tunnelId tunnel id + * @return destination info builder + */ + public Builder setTunnelId(long tunnelId) { + this.tunnelId = checkNotNull(tunnelId, "Tunnel ID cannot be null"); + return this; + } + + /** + * Build a destination information. + * + * @return destination info object + */ + public DestinationInfo build() { + return new DestinationInfo(this); + } + } + + private DestinationInfo(Builder builder) { + output = builder.output; + ip = builder.ip; + mac = builder.mac; + remoteIp = builder.remoteIp; + tunnelId = builder.tunnelId; + } +} diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeAddCommand.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeAddCommand.java new file mode 100644 index 00000000..1b7d9866 --- /dev/null +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeAddCommand.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015 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.onosproject.cordvtn.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onlab.packet.IpAddress; +import org.onlab.packet.TpPort; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cordvtn.CordVtnService; +import org.onosproject.cordvtn.CordVtnNode; +import org.onosproject.net.DeviceId; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Adds a new node to the service. + */ +@Command(scope = "onos", name = "cordvtn-node-add", + description = "Adds a new node to CORD VTN service") +public class CordVtnNodeAddCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "hostname", description = "Hostname", + required = true, multiValued = false) + private String hostname = null; + + @Argument(index = 1, name = "ovsdb", + description = "OVSDB server listening address (ip:port)", + required = true, multiValued = false) + private String ovsdb = null; + + @Argument(index = 2, name = "bridgeId", + description = "Device ID of integration bridge", + required = true, multiValued = false) + private String bridgeId = null; + + @Override + protected void execute() { + checkArgument(ovsdb.contains(":"), "OVSDB address should be ip:port format"); + checkArgument(bridgeId.startsWith("of:"), "bridgeId should be of:dpid format"); + + CordVtnService service = AbstractShellCommand.get(CordVtnService.class); + String[] ipPort = ovsdb.split(":"); + CordVtnNode node = new CordVtnNode(hostname, + IpAddress.valueOf(ipPort[0]), + TpPort.tpPort(Integer.parseInt(ipPort[1])), + DeviceId.deviceId(bridgeId)); + service.addNode(node); + } +} diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeDeleteCommand.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeDeleteCommand.java new file mode 100644 index 00000000..0446fc6a --- /dev/null +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeDeleteCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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.onosproject.cordvtn.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cordvtn.CordVtnService; +import org.onosproject.cordvtn.CordVtnNode; + +import java.util.NoSuchElementException; + +/** + * Deletes nodes from the service. + */ +@Command(scope = "onos", name = "cordvtn-node-delete", + description = "Deletes nodes from CORD VTN service") +public class CordVtnNodeDeleteCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "hostnames", description = "Hostname(s)", + required = true, multiValued = true) + private String[] hostnames = null; + + @Override + protected void execute() { + CordVtnService service = AbstractShellCommand.get(CordVtnService.class); + + for (String hostname : hostnames) { + CordVtnNode node; + try { + node = service.getNodes() + .stream() + .filter(n -> n.hostname().equals(hostname)) + .findFirst().get(); + } catch (NoSuchElementException e) { + print("Unable to find %s", hostname); + continue; + } + + service.deleteNode(node); + } + } +} diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java new file mode 100644 index 00000000..dd77a9c3 --- /dev/null +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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.onosproject.cordvtn.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cordvtn.CordVtnService; +import org.onosproject.cordvtn.CordVtnNode; + +import java.util.NoSuchElementException; + +/** + * Initializes nodes for CordVtn service. + */ +@Command(scope = "onos", name = "cordvtn-node-init", + description = "Initializes nodes for CORD VTN service") +public class CordVtnNodeInitCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "hostnames", description = "Hostname(s)", + required = true, multiValued = true) + private String[] hostnames = null; + + @Override + protected void execute() { + CordVtnService service = AbstractShellCommand.get(CordVtnService.class); + + for (String hostname : hostnames) { + CordVtnNode node; + try { + node = service.getNodes() + .stream() + .filter(n -> n.hostname().equals(hostname)) + .findFirst().get(); + } catch (NoSuchElementException e) { + print("Unable to find %s", hostname); + continue; + } + + service.initNode(node); + } + } +} diff --git a/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeListCommand.java b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeListCommand.java new file mode 100644 index 00000000..83e58598 --- /dev/null +++ b/framework/src/onos/apps/cordvtn/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeListCommand.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 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.onosproject.cordvtn.cli; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cordvtn.CordVtnService; +import org.onosproject.cordvtn.CordVtnNode; + +import java.util.Collections; +import java.util.List; + +/** + * Lists all nodes registered to the service. + */ +@Command(scope = "onos", name = "cordvtn-nodes", + description = "Lists all nodes registered in CORD VTN service") +public class CordVtnNodeListCommand extends AbstractShellCommand { + + @Override + protected void execute() { + CordVtnService service = AbstractShellCommand.get(CordVtnService.class); + List nodes = service.getNodes(); + Collections.sort(nodes, CordVtnNode.CORDVTN_NODE_COMPARATOR); + + if (outputJson()) { + print("%s", json(service, nodes)); + } else { + for (CordVtnNode node : nodes) { + print("hostname=%s, ovsdb=%s, br-int=%s, init=%s", + node.hostname(), + node.ovsdbIp().toString() + ":" + node.ovsdbPort().toString(), + node.intBrId().toString(), + getState(service, node)); + } + print("Total %s nodes", service.getNodeCount()); + } + } + + private JsonNode json(CordVtnService service, List nodes) { + ObjectMapper mapper = new ObjectMapper(); + ArrayNode result = mapper.createArrayNode(); + for (CordVtnNode node : nodes) { + String ipPort = node.ovsdbIp().toString() + ":" + node.ovsdbPort().toString(); + result.add(mapper.createObjectNode() + .put("hostname", node.hostname()) + .put("ovsdb", ipPort) + .put("brInt", node.intBrId().toString()) + .put("init", getState(service, node))); + } + return result; + } + + private String getState(CordVtnService service, CordVtnNode node) { + return service.getNodeInitState(node) ? "COMPLETE" : "INCOMPLETE"; + } +} -- cgit 1.2.3-korg