Refactored OpenstackSwitching app
[DONE]
- Restructured to activate or deactivate switching and routing app separately
- Fixed to add or remove host when port is detected or vanished
- Use openstack node service to get integration bridges and data IP
[TODO]
- Remove use of OpenstackPortInfo
- Support installing flow rules for exising VMs
- Call security group update method when port update triggered from OpenStack
Change-Id: Ic0b2ac3f7ab07f0e20c97c6edfdd1928b9767baf
diff --git a/apps/openstacknetworking/switching/BUCK b/apps/openstacknetworking/switching/BUCK
new file mode 100644
index 0000000..5e991af
--- /dev/null
+++ b/apps/openstacknetworking/switching/BUCK
@@ -0,0 +1,28 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//core/store/serializers:onos-core-serializers',
+ '//apps/openstackinterface/api:onos-apps-openstackinterface-api',
+ '//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
+ '//apps/openstacknode:onos-apps-openstacknode',
+ '//apps/dhcp/api:onos-apps-dhcp-api',
+]
+
+BUNDLES = [
+ '//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
+ '//apps/openstacknetworking/web:onos-apps-openstacknetworking-web',
+ '//apps/openstacknetworking/switching:onos-apps-openstacknetworking-switching',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+)
+
+onos_app (
+ app_name = 'org.onosproject.openstackswitching',
+ title = 'OpenStack Switching App',
+ category = 'Utility',
+ url = 'http://onosproject.org',
+ description = 'OpenStack Switching application.',
+ included_bundles = BUNDLES,
+ required_apps = [ 'org.onosproject.openstackinterface', 'org.onosproject.openstacknode', 'org.onosproject.dhcp' ]
+)
diff --git a/apps/openstacknetworking/switching/features.xml b/apps/openstacknetworking/switching/features.xml
new file mode 100644
index 0000000..1792b61
--- /dev/null
+++ b/apps/openstacknetworking/switching/features.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ Copyright 2016-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.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+ <feature name="${project.artifactId}" version="${project.version}"
+ description="${project.description}">
+ <feature>onos-api</feature>
+ <bundle>mvn:${project.groupId}/onos-app-openstacknetworking-api/${project.version}</bundle>
+ <bundle>mvn:${project.groupId}/onos-app-openstacknetworking-web/${project.version}</bundle>
+ <bundle>mvn:${project.groupId}/onos-app-openstackswitching/${project.version}</bundle>
+ </feature>
+</features>
diff --git a/apps/openstacknetworking/switching/pom.xml b/apps/openstacknetworking/switching/pom.xml
new file mode 100644
index 0000000..4135e19
--- /dev/null
+++ b/apps/openstacknetworking/switching/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2016-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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-openstacknetworking</artifactId>
+ <version>1.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>onos-app-openstackswitching</artifactId>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <onos.app.name>org.onosproject.openstackswitching</onos.app.name>
+ <onos.app.title>Openstack Switching App</onos.app.title>
+ <onos.app.category>Traffic Steering</onos.app.category>
+ <onos.app.url>http://onosproject.org</onos.app.url>
+ <onos.app.requires>
+ org.onosproject.dhcp,
+ org.onosproject.openstackinterface,
+ org.onosproject.openstacknode
+ </onos.app.requires>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-openstacknetworking-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-openstackinterface-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-openstacknode</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-dhcp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-dhcp-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-misc</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/AbstractVmHandler.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/AbstractVmHandler.java
new file mode 100644
index 0000000..9bf2baa
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/AbstractVmHandler.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2016-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.onosproject.openstacknetworking.switching;
+
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.Ip4Address;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.switching.Constants.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides abstract virtual machine handler.
+ */
+public abstract class AbstractVmHandler {
+ protected final Logger log = getLogger(getClass());
+
+ protected final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+ protected CoreService coreService;
+ protected MastershipService mastershipService;
+ protected HostService hostService;
+
+ protected ApplicationId appId;
+ protected HostListener hostListener = new InternalHostListener();
+
+ protected void activate() {
+ ServiceDirectory services = new DefaultServiceDirectory();
+ coreService = services.get(CoreService.class);
+ mastershipService = services.get(MastershipService.class);
+ hostService = services.get(HostService.class);
+
+ appId = coreService.registerApplication(Constants.APP_ID);
+ hostService.addListener(hostListener);
+
+ log.info("Started");
+ }
+
+ protected void deactivate() {
+ hostService.removeListener(hostListener);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ abstract void hostDetected(Host host);
+
+ abstract void hostRemoved(Host host);
+
+ protected boolean isValidHost(Host host) {
+ return !host.ipAddresses().isEmpty() &&
+ host.annotations().value(VXLAN_ID) != null &&
+ host.annotations().value(NETWORK_ID) != null &&
+ host.annotations().value(TENANT_ID) != null &&
+ host.annotations().value(PORT_ID) != null;
+ }
+
+ protected Set<Host> getVmsInDifferentCnode(Host host) {
+ return Tools.stream(hostService.getHosts())
+ .filter(h -> !h.location().deviceId().equals(host.location().deviceId()))
+ .filter(this::isValidHost)
+ .filter(h -> Objects.equals(getVni(h), getVni(host)))
+ .collect(Collectors.toSet());
+ }
+
+ protected Optional<Host> getVmByPortId(String portId) {
+ return Tools.stream(hostService.getHosts())
+ .filter(this::isValidHost)
+ .filter(host -> host.annotations().value(PORT_ID).equals(portId))
+ .findFirst();
+ }
+
+ protected Ip4Address getIp(Host host) {
+ return host.ipAddresses().stream().findFirst().get().getIp4Address();
+ }
+
+ protected String getVni(Host host) {
+ return host.annotations().value(VXLAN_ID);
+ }
+
+ protected String getTenantId(Host host) {
+ return host.annotations().value(TENANT_ID);
+ }
+
+ private class InternalHostListener implements HostListener {
+
+ @Override
+ public void event(HostEvent event) {
+ Host host = event.subject();
+ if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+ // do not allow to proceed without mastership
+ return;
+ }
+
+ if (!isValidHost(host)) {
+ log.debug("Invalid host event, ignore it {}", host);
+ return;
+ }
+
+ switch (event.type()) {
+ case HOST_UPDATED:
+ case HOST_ADDED:
+ eventExecutor.execute(() -> hostDetected(host));
+ break;
+ case HOST_REMOVED:
+ eventExecutor.execute(() -> hostRemoved(host));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/Constants.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/Constants.java
new file mode 100644
index 0000000..b173f29
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/Constants.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016-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.onosproject.openstacknetworking.switching;
+
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * Provides constants used in OpenStack node services.
+ */
+public final class Constants {
+
+ private Constants() {
+ }
+
+ public static final String APP_ID = "org.onosproject.openstackswitching";
+
+ public static final String PORTNAME_PREFIX_VM = "tap";
+ public static final String PORTNAME_PREFIX_ROUTER = "qr-";
+ public static final String PORTNAME_PREFIX_TUNNEL = "vxlan";
+
+ // TODO remove this
+ public static final String ROUTER_INTERFACE = "network:router_interface";
+ public static final String DEVICE_OWNER_GATEWAY = "network:router_gateway";
+
+ public static final Ip4Address DNS_SERVER_IP = Ip4Address.valueOf("8.8.8.8");
+ public static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
+ public static final int DHCP_INFINITE_LEASE = -1;
+
+ public static final String NETWORK_ID = "networkId";
+ public static final String PORT_ID = "portId";
+ public static final String VXLAN_ID = "vxlanId";
+ public static final String TENANT_ID = "tenantId";
+ public static final String GATEWAY_IP = "gatewayIp";
+ public static final String CREATE_TIME = "createTime";
+
+ public static final int SWITCHING_RULE_PRIORITY = 30000;
+ public static final int TUNNELTAG_RULE_PRIORITY = 30000;
+ public static final int ACL_RULE_PRIORITY = 30000;
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackArpHandler.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackArpHandler.java
new file mode 100644
index 0000000..c855403
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackArpHandler.java
@@ -0,0 +1,221 @@
+/*
+* Copyright 2016-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.onosproject.openstacknetworking.switching;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+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.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.openstacknetworking.switching.Constants.*;
+
+/**
+ * Handles ARP packet from VMs.
+ */
+@Component(immediate = true)
+public final class OpenstackArpHandler extends AbstractVmHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String GATEWAY_MAC = "gatewayMac";
+ private static final String DEFAULT_GATEWAY_MAC = "1f:1f:1f:1f:1f:1f";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackInterfaceService openstackService;
+
+ @Property(name = GATEWAY_MAC, value = DEFAULT_GATEWAY_MAC,
+ label = "Fake MAC address for virtual network subnet gateway")
+ private String gatewayMac = DEFAULT_GATEWAY_MAC;
+
+ private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+ private final Set<Ip4Address> gateways = Sets.newConcurrentHashSet();
+
+ @Activate
+ protected void activate() {
+ packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ packetService.removeProcessor(packetProcessor);
+ super.deactivate();
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ String updatedMac;
+
+ updatedMac = Tools.get(properties, GATEWAY_MAC);
+ if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(gatewayMac)) {
+ gatewayMac = updatedMac;
+ }
+
+ log.info("Modified");
+ }
+
+ /**
+ * Processes ARP request packets.
+ * It checks if the target IP is owned by a known host first and then ask to
+ * OpenStack if it's not. This ARP proxy does not support overlapping IP.
+ *
+ * @param context packet context
+ * @param ethPacket ethernet packet
+ */
+ private void processPacketIn(PacketContext context, Ethernet ethPacket) {
+ ARP arpPacket = (ARP) ethPacket.getPayload();
+ if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
+ return;
+ }
+
+ Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+ MacAddress replyMac = gateways.contains(targetIp) ? MacAddress.valueOf(gatewayMac) :
+ getMacFromHostService(targetIp);
+ if (replyMac.equals(MacAddress.NONE)) {
+ replyMac = getMacFromOpenstack(targetIp);
+ }
+
+ if (replyMac == MacAddress.NONE) {
+ log.debug("Failed to find MAC address for {}", targetIp.toString());
+ return;
+ }
+
+ Ethernet ethReply = ARP.buildArpReply(
+ targetIp.getIp4Address(),
+ replyMac,
+ ethPacket);
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(context.inPacket().receivedFrom().port())
+ .build();
+
+ packetService.emit(new DefaultOutboundPacket(
+ context.inPacket().receivedFrom().deviceId(),
+ treatment,
+ ByteBuffer.wrap(ethReply.serialize())));
+ }
+
+ /**
+ * Returns MAC address of a host with a given target IP address by asking to
+ * OpenStack. It does not support overlapping IP.
+ *
+ * @param targetIp target ip address
+ * @return mac address, or null if it fails to fetch the mac
+ */
+ private MacAddress getMacFromOpenstack(IpAddress targetIp) {
+ checkNotNull(targetIp);
+
+ OpenstackPort openstackPort = openstackService.ports()
+ .stream()
+ .filter(port -> port.fixedIps().containsValue(targetIp.getIp4Address()))
+ .findFirst()
+ .orElse(null);
+
+ if (openstackPort != null) {
+ log.debug("Found MAC from OpenStack for {}", targetIp.toString());
+ return openstackPort.macAddress();
+ } else {
+ return MacAddress.NONE;
+ }
+ }
+
+ /**
+ * Returns MAC address of a host with a given target IP address by asking to
+ * host service. It does not support overlapping IP.
+ *
+ * @param targetIp target ip
+ * @return mac address, or null if it fails to find the mac
+ */
+ private MacAddress getMacFromHostService(IpAddress targetIp) {
+ checkNotNull(targetIp);
+
+ Host host = hostService.getHostsByIp(targetIp)
+ .stream()
+ .findFirst()
+ .orElse(null);
+
+ if (host != null) {
+ log.debug("Found MAC from host service for {}", targetIp.toString());
+ return host.mac();
+ } else {
+ return MacAddress.NONE;
+ }
+ }
+
+ @Override
+ protected void hostDetected(Host host) {
+ OpenstackNetwork osNet = openstackService.network(host.annotations().value(NETWORK_ID));
+ if (osNet == null) {
+ log.warn("Failed to get OpenStack network for {}", host);
+ return;
+ }
+ osNet.subnets().stream()
+ .forEach(subnet -> gateways.add(Ip4Address.valueOf(subnet.gatewayIp())));
+ }
+
+ @Override
+ protected void hostRemoved(Host host) {
+ // TODO remove subnet gateway from gateways if no hosts exists on that subnet
+ }
+
+ private class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ if (context.isHandled()) {
+ return;
+ }
+
+ Ethernet ethPacket = context.inPacket().parsed();
+ if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
+ return;
+ }
+ processPacketIn(context, ethPacket);
+ }
+ }
+}
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSecurityGroupRulePopulator.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSecurityGroupRulePopulator.java
new file mode 100644
index 0000000..7477404
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSecurityGroupRulePopulator.java
@@ -0,0 +1,365 @@
+/*
+* Copyright 2016-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.onosproject.openstacknetworking.switching;
+
+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;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onlab.util.Tools;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.OpenstackSecurityGroup;
+import org.onosproject.openstackinterface.OpenstackSecurityGroupRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.openstacknetworking.switching.Constants.*;
+
+/**
+ * Populates flows rules for Security Groups of VMs.
+ *
+ */
+@Component(immediate = true)
+public class OpenstackSecurityGroupRulePopulator extends AbstractVmHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackInterfaceService openstackService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowObjectiveService flowObjectiveService;
+
+ private static final String PROTO_ICMP = "ICMP";
+ private static final String PROTO_TCP = "TCP";
+ private static final String PROTO_UDP = "UDP";
+ private static final String ETHTYPE_IPV4 = "IPV4";
+
+ private final Map<Host, Set<SecurityGroupRule>> securityGroupRuleMap = Maps.newConcurrentMap();
+
+ @Activate
+ protected void activate() {
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ // TODO call this when port is updated from OpenStack
+ public void updateSecurityGroup(OpenstackPort osPort) {
+ if (!osPort.status().equals(OpenstackPort.PortStatus.ACTIVE)) {
+ return;
+ }
+
+ Optional<Host> host = getVmByPortId(osPort.id());
+ if (!host.isPresent()) {
+ log.warn("No host found with {}", osPort);
+ return;
+ }
+ eventExecutor.execute(() -> updateSecurityGroupRules(host.get(), true));
+ }
+
+ /**
+ * Populates security group rules for all VMs in the supplied tenant ID.
+ * VMs in the same tenant tend to be engaged to each other by sharing the
+ * same security groups or setting the remote to another security group.
+ * To make the implementation simpler and robust, it tries to reinstall
+ * security group rules for all the VMs in the same tenant whenever a new
+ * VM is detected or port is updated.
+ *
+ * @param tenantId tenant id to update security group rules
+ */
+ private void populateSecurityGroupRules(String tenantId, boolean install) {
+ securityGroupRuleMap.entrySet().stream()
+ .filter(entry -> getTenantId(entry.getKey()).equals(tenantId))
+ .forEach(entry -> {
+ Host local = entry.getKey();
+ entry.getValue().stream().forEach(sgRule -> {
+ setSecurityGroupRule(local.location().deviceId(),
+ sgRule.rule(),
+ getIp(local),
+ sgRule.remoteIp(), install);
+ });
+ });
+ log.debug("Updated security group rules for {}", tenantId);
+ }
+
+ private void setSecurityGroupRule(DeviceId deviceId, OpenstackSecurityGroupRule sgRule,
+ Ip4Address vmIp, IpPrefix remoteIp,
+ boolean install) {
+ ForwardingObjective.Builder foBuilder = buildFlowObjective(sgRule, vmIp, remoteIp);
+ if (foBuilder == null) {
+ return;
+ }
+
+ if (install) {
+ flowObjectiveService.forward(deviceId, foBuilder.add());
+ } else {
+ flowObjectiveService.forward(deviceId, foBuilder.remove());
+ }
+ }
+
+ private ForwardingObjective.Builder buildFlowObjective(OpenstackSecurityGroupRule sgRule,
+ Ip4Address vmIp,
+ IpPrefix remoteIp) {
+ if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
+ // do nothing if the remote IP is my IP
+ return null;
+ }
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
+
+ return DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(DefaultTrafficTreatment.builder().build())
+ .withPriority(ACL_RULE_PRIORITY)
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .fromApp(appId);
+ }
+
+ private void buildMatchs(TrafficSelector.Builder sBuilder, OpenstackSecurityGroupRule sgRule,
+ Ip4Address vmIp, IpPrefix remoteIp) {
+ buildMatchEthType(sBuilder, sgRule.ethertype());
+ buildMatchDirection(sBuilder, sgRule.direction(), vmIp);
+ buildMatchProto(sBuilder, sgRule.protocol());
+ buildMatchPort(sBuilder, sgRule.protocol(), sgRule.direction(),
+ sgRule.portRangeMax(), sgRule.portRangeMin());
+ buildMatchRemoteIp(sBuilder, remoteIp, sgRule.direction());
+ }
+
+ private void buildMatchDirection(TrafficSelector.Builder sBuilder,
+ OpenstackSecurityGroupRule.Direction direction,
+ Ip4Address vmIp) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, 32));
+ } else {
+ sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, 32));
+ }
+ }
+
+ private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
+ // Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4);
+ if (etherType != null && !Objects.equals(etherType, "null") &&
+ !etherType.toUpperCase().equals(ETHTYPE_IPV4)) {
+ log.debug("EthType {} is not supported yet in Security Group", etherType);
+ }
+ }
+
+ private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder, IpPrefix remoteIpPrefix,
+ OpenstackSecurityGroupRule.Direction direction) {
+ if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchIPDst(remoteIpPrefix);
+ } else {
+ sBuilder.matchIPSrc(remoteIpPrefix);
+ }
+ }
+ }
+
+ private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
+ if (protocol != null) {
+ switch (protocol.toUpperCase()) {
+ case PROTO_ICMP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
+ break;
+ case PROTO_TCP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
+ break;
+ case PROTO_UDP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
+ break;
+ default:
+ }
+ }
+ }
+
+ private void buildMatchPort(TrafficSelector.Builder sBuilder, String protocol,
+ OpenstackSecurityGroupRule.Direction direction,
+ int portMin, int portMax) {
+ if (portMin > 0 && portMax > 0 && portMin == portMax) {
+ if (protocol.toUpperCase().equals(PROTO_TCP)) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchTcpDst(TpPort.tpPort(portMax));
+ } else {
+ sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
+ }
+ } else if (protocol.toUpperCase().equals(PROTO_UDP)) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchUdpDst(TpPort.tpPort(portMax));
+ } else {
+ sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
+ }
+ }
+ }
+ }
+
+ private void updateSecurityGroupRulesMap(Host host) {
+ OpenstackPort osPort = openstackService.port(host.annotations().value(PORT_ID));
+ if (osPort == null) {
+ log.debug("Failed to get OpenStack port information for {}", host);
+ return;
+ }
+
+ Set<SecurityGroupRule> rules = Sets.newHashSet();
+ osPort.securityGroups().stream().forEach(sgId -> {
+ OpenstackSecurityGroup osSecGroup = openstackService.securityGroup(sgId);
+ if (osSecGroup != null) {
+ osSecGroup.rules().stream().forEach(rule -> rules.addAll(getSgRules(rule)));
+ } else {
+ // TODO handle the case that the security group removed
+ log.warn("Failed to get security group {}", sgId);
+ }
+ });
+ securityGroupRuleMap.put(host, rules);
+ }
+
+ /**
+ * Returns set of security group rules with individual remote IP by
+ * converting remote group to actual IP address.
+ *
+ * @param sgRule security group rule
+ * @return set of security group rules
+ */
+ private Set<SecurityGroupRule> getSgRules(OpenstackSecurityGroupRule sgRule) {
+ Set<SecurityGroupRule> sgRules = Sets.newHashSet();
+ if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().equals("null")) {
+ sgRules = getRemoteIps(sgRule.tenantId(), sgRule.remoteGroupId())
+ .stream()
+ .map(remoteIp -> new SecurityGroupRule(sgRule, remoteIp))
+ .collect(Collectors.toSet());
+ } else {
+ sgRules.add(new SecurityGroupRule(sgRule, sgRule.remoteIpPrefix()));
+ }
+ return sgRules;
+ }
+
+ /**
+ * Returns a set of host IP addresses engaged with supplied security group ID.
+ * It only searches a VM in the same tenant boundary.
+ *
+ * @param tenantId tenant id
+ * @param sgId security group id
+ * @return set of ip addresses in ip prefix format
+ */
+ private Set<IpPrefix> getRemoteIps(String tenantId, String sgId) {
+ Set<IpPrefix> remoteIps = Sets.newHashSet();
+ securityGroupRuleMap.entrySet().stream()
+ .filter(entry -> Objects.equals(getTenantId(entry.getKey()), tenantId))
+ .forEach(entry -> {
+ if (entry.getValue().stream()
+ .anyMatch(rule -> rule.rule().secuityGroupId().equals(sgId))) {
+ remoteIps.add(IpPrefix.valueOf(getIp(entry.getKey()), 32));
+ }
+ });
+ return remoteIps;
+ }
+
+ private void updateSecurityGroupRules(Host host, boolean isHostAdded) {
+ String tenantId = getTenantId(host);
+ populateSecurityGroupRules(tenantId, false);
+
+ if (isHostAdded) {
+ updateSecurityGroupRulesMap(host);
+ } else {
+ securityGroupRuleMap.remove(host);
+ }
+
+ Tools.stream(hostService.getHosts())
+ .filter(h -> Objects.equals(getTenantId(h), getTenantId(host)))
+ .forEach(this::updateSecurityGroupRulesMap);
+
+ populateSecurityGroupRules(tenantId, true);
+ }
+
+ @Override
+ protected void hostDetected(Host host) {
+ updateSecurityGroupRules(host, true);
+ log.info("Applied security group rules for {}", host);
+ }
+
+ @Override
+ protected void hostRemoved(Host host) {
+ updateSecurityGroupRules(host, false);
+ log.info("Applied security group rules for {}", host);
+ }
+
+ private final class SecurityGroupRule {
+ private final OpenstackSecurityGroupRule rule;
+ private final IpPrefix remoteIp;
+
+ private SecurityGroupRule(OpenstackSecurityGroupRule rule, IpPrefix remoteIp) {
+ this.rule = rule;
+ this.remoteIp = remoteIp;
+ }
+
+ private OpenstackSecurityGroupRule rule() {
+ return rule;
+ }
+
+ private IpPrefix remoteIp() {
+ return remoteIp;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof SecurityGroupRule) {
+ SecurityGroupRule that = (SecurityGroupRule) obj;
+ if (Objects.equals(rule, that.rule) &&
+ Objects.equals(remoteIp, that.remoteIp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rule, remoteIp);
+ }
+ }
+}
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingManager.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingManager.java
new file mode 100644
index 0000000..24ab533
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingManager.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2016-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.onosproject.openstacknetworking.switching;
+
+import com.google.common.base.Strings;
+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;
+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.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcp.DhcpService;
+import org.onosproject.dhcp.IpAssignment;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackNetwork;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.OpenstackSubnet;
+import org.onosproject.openstacknetworking.OpenstackPortInfo;
+import org.onosproject.openstacknetworking.OpenstackSwitchingService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.openstacknetworking.switching.Constants.*;
+
+@Service
+@Component(immediate = true)
+/**
+ * Populates forwarding rules for VMs created by Openstack.
+ */
+public final class OpenstackSwitchingManager extends AbstractProvider
+ implements OpenstackSwitchingService, HostProvider {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostProviderRegistry hostProviderRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpService dhcpService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackInterfaceService openstackService;
+
+ private final ExecutorService deviceEventExecutor =
+ Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "device-event"));
+ private final ExecutorService configEventExecutor =
+ Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "config-event"));
+ private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
+
+ private HostProviderService hostProvider;
+
+ /**
+ * Creates OpenStack switching host provider.
+ */
+ public OpenstackSwitchingManager() {
+ super(new ProviderId("host", APP_ID));
+ }
+
+ @Activate
+ protected void activate() {
+ coreService.registerApplication(APP_ID);
+ deviceService.addListener(internalDeviceListener);
+ hostProvider = hostProviderRegistry.register(this);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ hostProviderRegistry.unregister(this);
+ deviceService.removeListener(internalDeviceListener);
+
+ deviceEventExecutor.shutdown();
+ configEventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ // no probe is required
+ }
+
+ @Override
+ // TODO remove this and openstackPortInfo
+ public Map<String, OpenstackPortInfo> openstackPortInfo() {
+ Map<String, OpenstackPortInfo> portInfoMap = Maps.newHashMap();
+
+ Tools.stream(hostService.getHosts()).filter(this::isValidHost).forEach(host -> {
+ Port port = deviceService.getPort(
+ host.location().deviceId(),
+ host.location().port());
+
+ OpenstackPortInfo portInfo = OpenstackPortInfo.builder()
+ .setDeviceId(host.location().deviceId())
+ .setHostMac(host.mac())
+ .setNetworkId(host.annotations().value(NETWORK_ID))
+ .setGatewayIP(Ip4Address.valueOf(host.annotations().value(GATEWAY_IP)))
+ .setVni(Long.valueOf(host.annotations().value(VXLAN_ID)))
+ .setHostIp(host.ipAddresses().stream().findFirst().get().getIp4Address())
+ .build();
+
+ portInfoMap.put(port.annotations().value(PORT_NAME), portInfo);
+ });
+
+ return portInfoMap;
+ }
+
+ // TODO remove this and openstackPortInfo
+ private boolean isValidHost(Host host) {
+ return !host.ipAddresses().isEmpty() &&
+ host.annotations().value(VXLAN_ID) != null &&
+ host.annotations().value(NETWORK_ID) != null &&
+ host.annotations().value(TENANT_ID) != null &&
+ host.annotations().value(GATEWAY_IP) != null &&
+ host.annotations().value(PORT_ID) != null;
+ }
+
+ private void processPortAdded(Port port) {
+ OpenstackPort osPort = openstackService.port(port);
+ if (osPort == null) {
+ log.warn("Failed to get OpenStack port for {}", port);
+ return;
+ }
+
+ OpenstackNetwork osNet = openstackService.network(osPort.networkId());
+ if (osNet == null) {
+ log.warn("Failed to get OpenStack network {}",
+ osPort.networkId());
+ return;
+ }
+
+ registerDhcpInfo(osPort);
+ ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+ // TODO remove this and openstackPortInfo
+ String gatewayIp = osNet.subnets().stream().findFirst().get().gatewayIp();
+
+ // Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
+ // existing instances.
+ DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+ .set(NETWORK_ID, osPort.networkId())
+ .set(PORT_ID, osPort.id())
+ .set(VXLAN_ID, osNet.segmentId())
+ .set(TENANT_ID, osNet.tenantId())
+ // TODO remove this and openstackPortInfo
+ .set(GATEWAY_IP, gatewayIp)
+ .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
+
+ HostDescription hostDesc = new DefaultHostDescription(
+ osPort.macAddress(),
+ VlanId.NONE,
+ new HostLocation(connectPoint, System.currentTimeMillis()),
+ Sets.newHashSet(osPort.fixedIps().values()),
+ annotations.build());
+
+ HostId hostId = HostId.hostId(osPort.macAddress());
+ hostProvider.hostDetected(hostId, hostDesc, false);
+ }
+
+ private void processPortRemoved(Port port) {
+ ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+ hostService.getConnectedHosts(connectPoint).stream()
+ .forEach(host -> {
+ dhcpService.removeStaticMapping(host.mac());
+ hostProvider.hostVanished(host.id());
+ });
+ }
+
+ private void registerDhcpInfo(OpenstackPort openstackPort) {
+ checkNotNull(openstackPort);
+ checkArgument(!openstackPort.fixedIps().isEmpty());
+
+ OpenstackSubnet openstackSubnet = openstackService.subnets().stream()
+ .filter(n -> n.networkId().equals(openstackPort.networkId()))
+ .findFirst().orElse(null);
+ if (openstackSubnet == null) {
+ log.warn("Failed to find subnet for {}", openstackPort);
+ return;
+ }
+
+ Ip4Address ipAddress = openstackPort.fixedIps().values().stream().findFirst().get();
+ IpPrefix subnetPrefix = IpPrefix.valueOf(openstackSubnet.cidr());
+ Ip4Address broadcast = Ip4Address.makeMaskedAddress(
+ ipAddress,
+ subnetPrefix.prefixLength());
+
+ // TODO: supports multiple DNS servers
+ Ip4Address domainServer = openstackSubnet.dnsNameservers().isEmpty() ?
+ DNS_SERVER_IP : openstackSubnet.dnsNameservers().get(0);
+
+ IpAssignment ipAssignment = IpAssignment.builder()
+ .ipAddress(ipAddress)
+ .leasePeriod(DHCP_INFINITE_LEASE)
+ .timestamp(new Date())
+ .subnetMask(Ip4Address.makeMaskPrefix(subnetPrefix.prefixLength()))
+ .broadcast(broadcast)
+ .domainServer(domainServer)
+ .assignmentStatus(Option_RangeNotEnforced)
+ .routerAddress(Ip4Address.valueOf(openstackSubnet.gatewayIp()))
+ .build();
+
+ dhcpService.setStaticMapping(openstackPort.macAddress(), ipAssignment);
+ }
+
+ private class InternalDeviceListener implements DeviceListener {
+
+ @Override
+ public void event(DeviceEvent event) {
+ Device device = event.subject();
+ if (!mastershipService.isLocalMaster(device.id())) {
+ // do not allow to proceed without mastership
+ return;
+ }
+
+ Port port = event.port();
+ if (port == null) {
+ return;
+ }
+
+ String portName = port.annotations().value(PORT_NAME);
+ if (Strings.isNullOrEmpty(portName) ||
+ !portName.startsWith(PORTNAME_PREFIX_VM)) {
+ // handles VM connected port event only
+ return;
+ }
+
+ switch (event.type()) {
+ case PORT_UPDATED:
+ if (!event.port().isEnabled()) {
+ deviceEventExecutor.execute(() -> processPortRemoved(event.port()));
+ }
+ break;
+ case PORT_ADDED:
+ deviceEventExecutor.execute(() -> processPortAdded(event.port()));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingRulePopulator.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingRulePopulator.java
new file mode 100644
index 0000000..732be7d
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSwitchingRulePopulator.java
@@ -0,0 +1,293 @@
+/*
+* Copyright 2016-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.onosproject.openstacknetworking.switching;
+
+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.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.device.DeviceService;
+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.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.onosproject.openstacknetworking.switching.Constants.*;
+
+/**
+ * Populates switching flow rules.
+ */
+@Component(immediate = true)
+public final class OpenstackSwitchingRulePopulator extends AbstractVmHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowObjectiveService flowObjectiveService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService nodeService;
+
+
+ private static final String TUNNEL_DST = "tunnelDst";
+
+ @Activate
+ protected void activate() {
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ private void populateSwitchingRules(Host host) {
+ populateFlowRulesForTunnelTag(host);
+ populateFlowRulesForTrafficToSameCnode(host);
+ populateFlowRulesForTrafficToDifferentCnode(host);
+
+ log.debug("Populated switching rule for {}", host);
+ }
+
+ private void populateFlowRulesForTunnelTag(Host host) {
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchInPort(host.location().port());
+
+ tBuilder.setTunnelId(Long.valueOf(getVni(host)));
+
+ ForwardingObjective fo = DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(tBuilder.build())
+ .withPriority(TUNNELTAG_RULE_PRIORITY)
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .fromApp(appId)
+ .add();
+
+ flowObjectiveService.forward(host.location().deviceId(), fo);
+ }
+
+ private void populateFlowRulesForTrafficToSameCnode(Host host) {
+ //For L2 Switching Case
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(getIp(host).toIpPrefix())
+ .matchTunnelId(Long.valueOf(getVni(host)));
+
+ tBuilder.setOutput(host.location().port());
+
+ ForwardingObjective fo = DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(tBuilder.build())
+ .withPriority(SWITCHING_RULE_PRIORITY)
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .fromApp(appId)
+ .add();
+
+ flowObjectiveService.forward(host.location().deviceId(), fo);
+ }
+
+ private void populateFlowRulesForTrafficToDifferentCnode(Host host) {
+ Ip4Address localVmIp = getIp(host);
+ DeviceId localDeviceId = host.location().deviceId();
+ Optional<IpAddress> localDataIp = nodeService.dataIp(localDeviceId);
+
+ if (!localDataIp.isPresent()) {
+ log.debug("Failed to get data IP for device {}",
+ host.location().deviceId());
+ return;
+ }
+
+ String vni = getVni(host);
+ getVmsInDifferentCnode(host).forEach(remoteVm -> {
+ Optional<IpAddress> remoteDataIp = nodeService.dataIp(remoteVm.location().deviceId());
+ if (remoteDataIp.isPresent()) {
+ setVxLanFlowRule(vni,
+ localDeviceId,
+ remoteDataIp.get().getIp4Address(),
+ getIp(remoteVm));
+
+ setVxLanFlowRule(vni,
+ remoteVm.location().deviceId(),
+ localDataIp.get().getIp4Address(),
+ localVmIp);
+ }
+ });
+ }
+
+ private void setVxLanFlowRule(String vni, DeviceId deviceId, Ip4Address remoteIp,
+ Ip4Address vmIp) {
+ Optional<PortNumber> tunnelPort = nodeService.tunnelPort(deviceId);
+ if (!tunnelPort.isPresent()) {
+ log.warn("Failed to get tunnel port from {}", deviceId);
+ return;
+ }
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchTunnelId(Long.parseLong(vni))
+ .matchIPDst(vmIp.toIpPrefix());
+ tBuilder.extension(buildNiciraExtenstion(deviceId, remoteIp), deviceId)
+ .setOutput(tunnelPort.get());
+
+ ForwardingObjective fo = DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(tBuilder.build())
+ .withPriority(SWITCHING_RULE_PRIORITY)
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .fromApp(appId)
+ .add();
+
+ flowObjectiveService.forward(deviceId, fo);
+ }
+
+ private void removeSwitchingRules(Host host) {
+ removeFlowRuleForTunnelTag(host);
+ removeFlowRuleForVMsInSameCnode(host);
+ removeFlowRuleForVMsInDiffrentCnode(host);
+
+ log.debug("Removed switching rule for {}", host);
+ }
+
+ private void removeFlowRuleForTunnelTag(Host host) {
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchInPort(host.location().port());
+
+ ForwardingObjective fo = DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(tBuilder.build())
+ .withPriority(TUNNELTAG_RULE_PRIORITY)
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .fromApp(appId)
+ .remove();
+
+ flowObjectiveService.forward(host.location().deviceId(), fo);
+ }
+
+ private void removeFlowRuleForVMsInSameCnode(Host host) {
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(getIp(host).toIpPrefix())
+ .matchTunnelId(Long.valueOf(getVni(host)));
+
+ ForwardingObjective fo = DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(DefaultTrafficTreatment.builder().build())
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .withPriority(SWITCHING_RULE_PRIORITY)
+ .fromApp(appId)
+ .remove();
+
+ flowObjectiveService.forward(host.location().deviceId(), fo);
+ }
+
+ private void removeFlowRuleForVMsInDiffrentCnode(Host host) {
+ DeviceId deviceId = host.location().deviceId();
+ final boolean anyPortRemainedInSameCnode = hostService.getConnectedHosts(deviceId)
+ .stream()
+ .filter(this::isValidHost)
+ .anyMatch(h -> Objects.equals(getVni(h), getVni(host)));
+
+ getVmsInDifferentCnode(host).forEach(h -> {
+ removeVxLanFlowRule(h.location().deviceId(), getIp(host), getVni(host));
+ if (!anyPortRemainedInSameCnode) {
+ removeVxLanFlowRule(deviceId, getIp(h), getVni(host));
+ }
+ });
+ }
+
+ private void removeVxLanFlowRule(DeviceId deviceId, Ip4Address vmIp, String vni) {
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchTunnelId(Long.valueOf(vni))
+ .matchIPDst(vmIp.toIpPrefix());
+
+ ForwardingObjective fo = DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(DefaultTrafficTreatment.builder().build())
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .withPriority(SWITCHING_RULE_PRIORITY)
+ .fromApp(appId)
+ .remove();
+
+ flowObjectiveService.forward(deviceId, fo);
+ }
+
+ private ExtensionTreatment buildNiciraExtenstion(DeviceId deviceId, Ip4Address remoteIp) {
+ Device device = deviceService.getDevice(deviceId);
+ if (device != null && !device.is(ExtensionTreatmentResolver.class)) {
+ log.error("The extension treatment is not supported");
+ return null;
+ }
+
+ ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
+ ExtensionTreatment treatment = resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+ try {
+ treatment.setPropertyValue(TUNNEL_DST, remoteIp);
+ return treatment;
+ } catch (ExtensionPropertyException e) {
+ log.warn("Failed to get tunnelDst extension treatment for {}", deviceId);
+ return null;
+ }
+ }
+
+ @Override
+ protected void hostDetected(Host host) {
+ populateSwitchingRules(host);
+ log.info("Added new virtual machine to switching service {}", host);
+ }
+
+ @Override
+ protected void hostRemoved(Host host) {
+ removeSwitchingRules(host);
+ log.info("Removed virtual machine from switching service {}", host);
+ }
+}
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/package-info.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/package-info.java
new file mode 100644
index 0000000..5b6cfb3
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-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.
+ */
+
+/**
+ * OpenStack switch implementation.
+ */
+package org.onosproject.openstacknetworking.switching;