Hyunsun Moon
Committed by Gerrit Code Review

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
Showing 39 changed files with 1444 additions and 1777 deletions
......@@ -117,8 +117,8 @@ APPS = [
'//apps/mlb:onos-apps-mlb-oar',
'//apps/openstackinterface:onos-apps-openstackinterface-oar',
'//apps/openstacknetworking:onos-apps-openstacknetworking-oar',
'//apps/openstacknetworking/openstackrouting:onos-apps-openstacknetworking-openstackrouting-oar',
'//apps/openstacknetworking/openstackswitching:onos-apps-openstacknetworking-openstackswitching-oar',
'//apps/openstacknetworking/routing:onos-apps-openstacknetworking-routing-oar',
'//apps/openstacknetworking/switching:onos-apps-openstacknetworking-switching-oar',
'//apps/mobility:onos-apps-mobility-oar',
'//apps/optical:onos-apps-optical-oar',
'//apps/newoptical:onos-apps-newoptical-oar',
......
......@@ -8,5 +8,5 @@ onos_app (
category = 'Utility',
url = 'http://onosproject.org',
included_bundles = BUNDLES,
required_apps = [ 'org.onosproject.openstackinterface' ],
required_apps = [ 'org.onosproject.openstackrouting', 'org.onosproject.openstackswitching' ]
)
......
......@@ -19,14 +19,12 @@ import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
import org.onosproject.net.DeviceId;
import java.util.Collection;
import java.util.Collections;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Contains OpenstackPort Information.
*/
// TODO remove this
public final class OpenstackPortInfo {
private final Ip4Address hostIp;
private final MacAddress hostMac;
......@@ -34,7 +32,6 @@ public final class OpenstackPortInfo {
private final long vni;
private final Ip4Address gatewayIP;
private final String networkId;
private final Collection<String> securityGroups;
/**
* Returns OpenstackPortInfo reference.
......@@ -45,17 +42,15 @@ public final class OpenstackPortInfo {
* @param vni tunnel ID
* @param gatewayIP gateway IP address
* @param networkId network identifier
* @param securityGroups security group list
*/
public OpenstackPortInfo(Ip4Address hostIp, MacAddress hostMac, DeviceId deviceId, long vni,
Ip4Address gatewayIP, String networkId, Collection<String> securityGroups) {
Ip4Address gatewayIP, String networkId) {
this.hostIp = hostIp;
this.hostMac = hostMac;
this.deviceId = deviceId;
this.vni = vni;
this.gatewayIP = gatewayIP;
this.networkId = networkId;
this.securityGroups = securityGroups;
}
/**
......@@ -113,15 +108,6 @@ public final class OpenstackPortInfo {
}
/**
* Returns Security Group ID list.
*
* @return list of Security Group ID
*/
public Collection<String> securityGroups() {
return Collections.unmodifiableCollection(securityGroups);
}
/**
* Returns the builder of the OpenstackPortInfo.
*
* @return OpenstackPortInfo builder reference
......@@ -140,7 +126,6 @@ public final class OpenstackPortInfo {
private DeviceId deviceId;
private long vni;
private Ip4Address gatewayIP;
private Collection<String> securityGroups;
private String networkId;
/**
......@@ -210,23 +195,12 @@ public final class OpenstackPortInfo {
}
/**
* Sets the security group ID list.
*
* @param securityGroups security group ID list
* @return Builder reference
*/
public Builder setSecurityGroups(Collection<String> securityGroups) {
this.securityGroups = securityGroups;
return this;
}
/**
* Builds the OpenstackPortInfo reference.
*
* @return OpenstackPortInfo reference
*/
public OpenstackPortInfo build() {
return new OpenstackPortInfo(hostIp, hostMac, deviceId, vni, gatewayIP, networkId, securityGroups);
return new OpenstackPortInfo(hostIp, hostMac, deviceId, vni, gatewayIP, networkId);
}
}
}
......
......@@ -15,57 +15,19 @@
*/
package org.onosproject.openstacknetworking;
import org.onosproject.openstackinterface.OpenstackNetwork;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstackinterface.OpenstackSubnet;
import java.util.Map;
/**
* Handles port management REST API from Openstack for VMs.
*/
// TODO remove this
public interface OpenstackSwitchingService {
/**
* Store the port information created by Openstack.
*
* @param openstackPort port information
*/
void createPorts(OpenstackPort openstackPort);
/**
* Removes flow rules corresponding to the port removed by Openstack.
*
* @param uuid UUID
*/
void removePort(String uuid);
/**
* Updates flow rules corresponding to the port information updated by Openstack.
*
* @param openstackPort OpenStack port
*/
void updatePort(OpenstackPort openstackPort);
/**
* Stores the network information created by openstack.
*
* @param openstackNetwork network information
*/
void createNetwork(OpenstackNetwork openstackNetwork);
/**
* Stores the subnet information created by openstack.
*
* @param openstackSubnet subnet information
*/
void createSubnet(OpenstackSubnet openstackSubnet);
/**
* Retruns OpenstackPortInfo map.
*
* @return OpenstackPortInfo map
*/
// TODO remove this
Map<String, OpenstackPortInfo> openstackPortInfo();
}
......
<?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.
-->
<app name="org.onosproject.openstacknetworking" origin="ON.Lab" version="${project.version}"
category="Utility" url="http://onosproject.org" title="OpenStack Networking App"
featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
features="${project.artifactId}"
apps="org.onosproject.openstackswitching,org.onosproject.openstackrouting">
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/onos-app-openstacknetworking-api/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-app-openstacknetworking-web/${project.version}</artifact>
</app>
<?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/xsd/maven-4.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-openstacknetworking-app</artifactId>
<packaging>pom</packaging>
<properties>
<onos.app.readme>Openstack Networking Application.</onos.app.readme>
</properties>
<description>SONA Openstack Networking main Application</description>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-openstackswitching</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-openstackrouting</artifactId>
<version>${project.version}</version>
</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-openstacknetworking-web</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
<?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.
-->
<app name="org.onosproject.openstackrouting" origin="ON.Lab" version="${project.version}"
category="default" url="http://onosproject.org"
featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
features="${project.artifactId}" >
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/onos-app-openstackrouting/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-app-openstacknetworking-api/${project.version}</artifact>
</app>
<?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-openstackrouting/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-app-openstacknetworking-api/${project.version}</bundle>
</feature>
</features>
<?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.
-->
<app name="org.onosproject.openstackswitching" origin="ON.Lab" version="${project.version}"
category="default" url="http://onosproject.org"
featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
features="${project.artifactId}"
apps="org.onosproject.dhcp">
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/onos-app-openstackswitching/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-app-openstacknetworking-api/${project.version}</artifact>
</app>
/*
* 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.ARP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.net.Host;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketService;
import org.onosproject.openstackinterface.OpenstackInterfaceService;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstacknetworking.OpenstackPortInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.Collection;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Handles ARP packet from VMs.
*/
public class OpenstackArpHandler {
private static Logger log = LoggerFactory
.getLogger(OpenstackArpHandler.class);
private static final MacAddress GATEWAY_MAC = MacAddress.valueOf("1f:1f:1f:1f:1f:1f");
private PacketService packetService;
private OpenstackInterfaceService openstackService;
private HostService hostService;
/**
* Returns OpenstackArpHandler reference.
*
* @param openstackService OpenstackNetworkingService reference
* @param packetService PacketService reference
* @param hostService host service
*/
public OpenstackArpHandler(OpenstackInterfaceService openstackService, PacketService packetService,
HostService hostService) {
this.openstackService = openstackService;
this.packetService = packetService;
this.hostService = hostService;
}
/**
* 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 pkt ARP request packet
* @param openstackPortInfoCollection collection of port information
*/
public void processPacketIn(InboundPacket pkt, Collection<OpenstackPortInfo> openstackPortInfoCollection) {
Ethernet ethRequest = pkt.parsed();
ARP arp = (ARP) ethRequest.getPayload();
if (arp.getOpCode() != ARP.OP_REQUEST) {
return;
}
IpAddress sourceIp = Ip4Address.valueOf(arp.getSenderProtocolAddress());
MacAddress srcMac = MacAddress.valueOf(arp.getSenderHardwareAddress());
OpenstackPortInfo portInfo = openstackPortInfoCollection.stream()
.filter(p -> p.ip().equals(sourceIp) && p.mac().equals(srcMac)).findFirst().orElse(null);
IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
MacAddress dstMac;
if (targetIp.equals(portInfo == null ? null : portInfo.gatewayIP())) {
dstMac = GATEWAY_MAC;
} else {
dstMac = getMacFromHostService(targetIp);
if (dstMac == null) {
dstMac = getMacFromOpenstack(targetIp);
}
}
if (dstMac == null) {
log.debug("Failed to find MAC address for {}", targetIp.toString());
return;
}
Ethernet ethReply = ARP.buildArpReply(targetIp.getIp4Address(),
dstMac,
ethRequest);
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(pkt.receivedFrom().port())
.build();
packetService.emit(new DefaultOutboundPacket(
pkt.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))
.findFirst()
.orElse(null);
if (openstackPort != null) {
log.debug("Found MAC from OpenStack for {}", targetIp.toString());
return openstackPort.macAddress();
} else {
return null;
}
}
/**
* 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 null;
}
}
}
/*
* 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.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.DeviceId;
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.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.openstackinterface.OpenstackInterfaceService;
import org.onosproject.openstackinterface.OpenstackSecurityGroup;
import org.onosproject.openstackinterface.OpenstackSecurityGroupRule;
import org.onosproject.openstacknetworking.OpenstackPortInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* Populates flows rules for Security Groups of VMs.
*
*/
public class OpenstackSecurityGroupRulePopulator {
private static Logger log = LoggerFactory
.getLogger(OpenstackSecurityGroupRulePopulator.class);
private OpenstackInterfaceService openstackService;
private FlowObjectiveService flowObjectiveService;
private ApplicationId appId;
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 static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
private static final int ACL_RULE_PRIORITY = 30000;
/**
* Constructor.
*
* @param appId application ID
* @param openstackService OpenStack interface service
* @param flowObjectiveService flow objective service
*/
public OpenstackSecurityGroupRulePopulator(ApplicationId appId, OpenstackInterfaceService openstackService,
FlowObjectiveService flowObjectiveService) {
this.appId = appId;
this.openstackService = openstackService;
this.flowObjectiveService = flowObjectiveService;
}
/**
* Populates flow rules for security groups.
*
* @param id Device ID
* @param sgId Security Group ID
* @param vmIp VM IP address
* @param portInfoMap Port Info map
*/
public void populateSecurityGroupRules(DeviceId id, String sgId, Ip4Address vmIp,
Map<String, OpenstackPortInfo> portInfoMap) {
OpenstackSecurityGroup securityGroup = openstackService.securityGroup(sgId);
if (securityGroup != null) {
securityGroup.rules().stream().forEach(sgRule -> {
if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().equals("null")) {
openstackService.ports().stream()
.filter(port -> port.securityGroups().contains(sgRule.remoteGroupId()))
.flatMap(port -> port.fixedIps().values().stream())
.forEach(remoteIp -> setSecurityGroupRule(id, sgRule,
vmIp, IpPrefix.valueOf((IpAddress) remoteIp, 32)));
} else {
setSecurityGroupRule(id, sgRule, vmIp, sgRule.remoteIpPrefix());
}
});
openstackService.ports().stream().forEach(osPort ->
osPort.securityGroups().stream().forEach(remoteVmSgId -> {
OpenstackSecurityGroup remoteVmSg = openstackService.securityGroup(remoteVmSgId);
remoteVmSg.rules().stream()
.filter(remoteVmSgRule -> remoteVmSgRule.remoteGroupId().equals(sgId))
.forEach(remoteVmSgRule -> {
Ip4Address remoteVmIp =
(Ip4Address) osPort.fixedIps().values().stream().findAny().orElse(null);
OpenstackPortInfo osPortInfo = portInfoMap.get(OpenstackSwitchingManager.PORTNAME_PREFIX_VM
+ osPort.id().substring(0, 11));
if (osPortInfo != null && remoteVmIp != null) {
setSecurityGroupRule(osPortInfo.deviceId(), remoteVmSgRule, remoteVmIp,
IpPrefix.valueOf(vmIp, 32));
}
});
}));
}
}
/**
* Removes flow rules for security groups.
*
* @param id Device ID
* @param sgId Security Group ID to remove
* @param vmIp VM IP address
* @param portInfoMap port info map
* @param securityGroupMap security group info map
*/
public void removeSecurityGroupRules(DeviceId id, String sgId, Ip4Address vmIp,
Map<String, OpenstackPortInfo> portInfoMap,
Map<String, OpenstackSecurityGroup> securityGroupMap) {
OpenstackSecurityGroup securityGroup = securityGroupMap.get(sgId);
if (securityGroup != null) {
securityGroup.rules().stream().forEach(sgRule -> {
if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().equals("null")) {
portInfoMap.values().stream()
.filter(portInfo -> portInfo.securityGroups().contains(sgRule.remoteGroupId()))
.map(OpenstackPortInfo::ip)
.forEach(remoteIp -> {
removeSecurityGroupRule(id, sgRule, vmIp, IpPrefix.valueOf(remoteIp, 32));
});
} else {
removeSecurityGroupRule(id, sgRule, vmIp, sgRule.remoteIpPrefix());
}
});
portInfoMap.values().stream()
.forEach(portInfo -> portInfo.securityGroups()
.forEach(remoteVmSgId -> {
OpenstackSecurityGroup remoteVmSg = securityGroupMap.get(remoteVmSgId);
remoteVmSg.rules().stream()
.filter(remoteVmSgRule -> remoteVmSgRule.remoteGroupId().equals(sgId))
.forEach(remoteVmSgRule -> removeSecurityGroupRule(portInfo.deviceId(),
remoteVmSgRule, portInfo.ip(), IpPrefix.valueOf(vmIp, 32)));
}));
}
}
private void setSecurityGroupRule(DeviceId id, OpenstackSecurityGroupRule sgRule,
Ip4Address vmIp, IpPrefix remoteIp) {
ForwardingObjective.Builder foBuilder = buildFlowObjective(id, sgRule, vmIp, remoteIp);
if (foBuilder != null) {
flowObjectiveService.forward(id, foBuilder.add());
}
}
private void removeSecurityGroupRule(DeviceId id, OpenstackSecurityGroupRule sgRule,
Ip4Address vmIp, IpPrefix remoteIp) {
ForwardingObjective.Builder foBuilder = buildFlowObjective(id, sgRule, vmIp, remoteIp);
if (foBuilder != null) {
flowObjectiveService.forward(id, foBuilder.remove());
}
}
ForwardingObjective.Builder buildFlowObjective(DeviceId id, OpenstackSecurityGroupRule sgRule,
Ip4Address vmIp, IpPrefix remoteIp) {
if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
return null;
}
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
ForwardingObjective.Builder foBuilder = DefaultForwardingObjective.builder()
.withSelector(sBuilder.build())
.withTreatment(tBuilder.build())
.withPriority(ACL_RULE_PRIORITY)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(appId);
return foBuilder;
}
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 && ethertype != "null" &&
!ethertype.toUpperCase().equals(ETHTYPE_IPV4)) {
log.error("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));
}
}
}
}
}
/*
* 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.ImmutableMap;
import com.google.common.collect.Maps;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpPrefix;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.dhcp.DhcpService;
import org.onosproject.dhcp.IpAssignment;
import org.onosproject.event.AbstractEvent;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
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.net.packet.InboundPacket;
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.onosproject.openstackinterface.OpenstackSecurityGroup;
import org.onosproject.openstackinterface.OpenstackSubnet;
import org.onosproject.openstacknetworking.OpenstackPortInfo;
import org.onosproject.openstacknetworking.OpenstackSubjectFactories;
import org.onosproject.openstacknetworking.OpenstackNetworkingConfig;
import org.onosproject.openstacknetworking.OpenstackSwitchingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.Collection;
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;
@Service
@Component(immediate = true)
/**
* Populates forwarding rules for VMs created by Openstack.
*/
public class OpenstackSwitchingManager implements OpenstackSwitchingService {
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DhcpService dhcpService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DriverService driverService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry networkConfig;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackInterfaceService openstackService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService configService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
public static final String PORTNAME_PREFIX_VM = "tap";
public static final String PORTNAME_PREFIX_ROUTER = "qr-";
public static final String PORTNAME_PREFIX_TUNNEL = "vxlan";
public static final String PORTNAME = "portName";
private 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");
private static final String FORWARD_SLASH = "/";
private static final int DHCP_INFINITE_LEASE = -1;
public static final String SONA_DRIVER_NAME = "sona";
private ApplicationId appId;
private OpenstackArpHandler arpHandler;
private OpenstackSecurityGroupRulePopulator sgRulePopulator;
private ExecutorService deviceEventExecutorService =
Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "device-event"));
private ExecutorService configEventExecutorService =
Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "config-event"));
private InternalPacketProcessor internalPacketProcessor = new InternalPacketProcessor();
private InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
private InternalHostListener internalHostListener = new InternalHostListener();
private final Map<String, OpenstackPortInfo> openstackPortInfoMap = Maps.newHashMap();
private Map<String, OpenstackSecurityGroup> securityGroupMap = Maps.newConcurrentMap();
private final ConfigFactory configFactory =
new ConfigFactory(OpenstackSubjectFactories.USER_DEFINED_SUBJECT_FACTORY, OpenstackNetworkingConfig.class,
"config") {
@Override
public OpenstackNetworkingConfig createConfig() {
return new OpenstackNetworkingConfig();
}
};
private final NetworkConfigListener configListener = new InternalConfigListener();
private OpenstackNetworkingConfig config;
@Activate
protected void activate() {
appId = coreService
.registerApplication("org.onosproject.openstackswitching");
packetService.addProcessor(internalPacketProcessor, PacketProcessor.director(1));
deviceService.addListener(internalDeviceListener);
hostService.addListener(internalHostListener);
configRegistry.registerConfigFactory(configFactory);
configService.addListener(configListener);
arpHandler = new OpenstackArpHandler(openstackService, packetService, hostService);
sgRulePopulator = new OpenstackSecurityGroupRulePopulator(appId, openstackService, flowObjectiveService);
networkConfig.registerConfigFactory(configFactory);
networkConfig.addListener(configListener);
readConfiguration();
log.info("Started");
}
@Deactivate
protected void deactivate() {
packetService.removeProcessor(internalPacketProcessor);
deviceService.removeListener(internalDeviceListener);
deviceEventExecutorService.shutdown();
configEventExecutorService.shutdown();
hostService.removeListener(internalHostListener);
configService.removeListener(configListener);
configRegistry.unregisterConfigFactory(configFactory);
log.info("Stopped");
}
@Override
public void createPorts(OpenstackPort openstackPort) {
if (!openstackPort.deviceOwner().equals(ROUTER_INTERFACE)
&& !openstackPort.deviceOwner().equals(DEVICE_OWNER_GATEWAY)
&& !openstackPort.fixedIps().isEmpty()) {
registerDhcpInfo(openstackPort);
}
}
@Override
public void removePort(String uuid) {
// When VMs are remvoed, the flow rules for the VMs are removed using ONOS port update event.
// But, when router is removed, no ONOS port event occurs and we need to use Neutron port event.
// Here we should not touch any rules for VMs.
log.debug("port {} was removed", uuid);
String routerPortName = PORTNAME_PREFIX_ROUTER + uuid.substring(0, 11);
OpenstackPortInfo routerPortInfo = openstackPortInfoMap.get(routerPortName);
if (routerPortInfo != null) {
dhcpService.removeStaticMapping(routerPortInfo.mac());
deviceService.getPorts(routerPortInfo.deviceId()).forEach(port -> {
String pName = port.annotations().value(PORTNAME);
if (pName.equals(routerPortName)) {
OpenstackSwitchingRulePopulator rulePopulator =
new OpenstackSwitchingRulePopulator(appId, flowObjectiveService,
deviceService, openstackService, driverService, config);
rulePopulator.removeSwitchingRules(port, openstackPortInfoMap);
openstackPortInfoMap.remove(routerPortName);
return;
}
});
}
}
@Override
public void updatePort(OpenstackPort openstackPort) {
if (openstackPort.status().equals(OpenstackPort.PortStatus.ACTIVE)) {
String portName = PORTNAME_PREFIX_VM + openstackPort.id().substring(0, 11);
OpenstackPortInfo osPortInfo = openstackPortInfoMap.get(portName);
if (osPortInfo != null) {
// Remove all security group rules based on the ones stored in security group map.
osPortInfo.securityGroups().stream().forEach(
sgId -> sgRulePopulator.removeSecurityGroupRules(osPortInfo.deviceId(), sgId,
osPortInfo.ip(), openstackPortInfoMap, securityGroupMap));
// Add all security group rules based on the updated security group.
openstackPort.securityGroups().stream().forEach(
sgId -> sgRulePopulator.populateSecurityGroupRules(osPortInfo.deviceId(), sgId,
osPortInfo.ip(), openstackPortInfoMap));
updatePortMap(osPortInfo.deviceId(), portName, openstackService.networks(),
openstackService.subnets(), openstackPort);
}
}
}
@Override
public void createNetwork(OpenstackNetwork openstackNetwork) {
//TODO
}
@Override
public void createSubnet(OpenstackSubnet openstackSubnet) {
//TODO
}
@Override
public Map<String, OpenstackPortInfo> openstackPortInfo() {
return ImmutableMap.copyOf(this.openstackPortInfoMap);
}
private void processPortUpdated(Device device, Port port) {
String portName = port.annotations().value(PORTNAME);
synchronized (openstackPortInfoMap) {
if (portName.startsWith(PORTNAME_PREFIX_VM)) {
if (port.isEnabled()) {
OpenstackSwitchingRulePopulator rulePopulator =
new OpenstackSwitchingRulePopulator(appId, flowObjectiveService,
deviceService, openstackService, driverService, config);
rulePopulator.populateSwitchingRules(device, port);
OpenstackPort openstackPort = rulePopulator.openstackPort(port);
Ip4Address vmIp = (Ip4Address) openstackPort.fixedIps().values().stream()
.findAny().orElseGet(null);
openstackPort.securityGroups().stream().forEach(
sgId -> sgRulePopulator.populateSecurityGroupRules(device.id(), sgId, vmIp,
openstackPortInfoMap));
updatePortMap(device.id(), port.annotations().value(PORTNAME),
openstackService.networks(), openstackService.subnets(), openstackPort);
//In case portupdate event is driven by vm shutoff from openstack
} else if (!port.isEnabled() && openstackPortInfoMap.containsKey(portName)) {
log.debug("Flowrules according to the port {} were removed", port.number());
OpenstackSwitchingRulePopulator rulePopulator =
new OpenstackSwitchingRulePopulator(appId, flowObjectiveService,
deviceService, openstackService, driverService, config);
rulePopulator.removeSwitchingRules(port, openstackPortInfoMap);
openstackPortInfoMap.get(portName).securityGroups().stream().forEach(
sgId -> sgRulePopulator.removeSecurityGroupRules(device.id(), sgId,
openstackPortInfoMap.get(portName).ip(), openstackPortInfoMap, securityGroupMap));
dhcpService.removeStaticMapping(openstackPortInfoMap.get(port.annotations().value(PORTNAME)).mac());
openstackPortInfoMap.remove(port.annotations().value(PORTNAME));
}
}
}
}
private void processPortRemoved(Port port) {
log.debug("port {} is removed", port.toString());
}
private void initializeFlowRules() {
OpenstackSwitchingRulePopulator rulePopulator =
new OpenstackSwitchingRulePopulator(appId, flowObjectiveService,
deviceService, openstackService, driverService, config);
Collection<OpenstackNetwork> networks = openstackService.networks();
Collection<OpenstackSubnet> subnets = openstackService.subnets();
deviceService.getDevices().forEach(device -> {
log.debug("device {} num of ports {} ", device.id(),
deviceService.getPorts(device.id()).size());
deviceService.getPorts(device.id()).stream()
.filter(port -> port.annotations().value(PORTNAME).startsWith(PORTNAME_PREFIX_VM) ||
port.annotations().value(PORTNAME).startsWith(PORTNAME_PREFIX_ROUTER))
.forEach(vmPort -> {
OpenstackPort osPort = rulePopulator.openstackPort(vmPort);
if (osPort != null && !osPort.deviceOwner().equals(DEVICE_OWNER_GATEWAY)) {
rulePopulator.populateSwitchingRules(device, vmPort);
Ip4Address vmIp = (Ip4Address) osPort.fixedIps().values().stream()
.findAny().orElseGet(null);
osPort.securityGroups().stream().forEach(
sgId -> sgRulePopulator.populateSecurityGroupRules(device.id(),
sgId, vmIp, openstackPortInfoMap));
updatePortMap(device.id(), vmPort.annotations().value(PORTNAME), networks,
subnets, osPort);
registerDhcpInfo(osPort);
} else {
log.warn("No openstackPort information for port {}", vmPort);
}
}
);
}
);
}
private void updatePortMap(DeviceId deviceId, String portName, Collection<OpenstackNetwork> networks,
Collection<OpenstackSubnet> subnets, OpenstackPort openstackPort) {
long vni;
OpenstackNetwork openstackNetwork = networks.stream()
.filter(n -> n.id().equals(openstackPort.networkId()))
.findAny().orElse(null);
if (openstackNetwork != null) {
vni = Long.parseLong(openstackNetwork.segmentId());
} else {
log.debug("updatePortMap failed because there's no OpenstackNetwork matches {}", openstackPort.networkId());
return;
}
OpenstackSubnet openstackSubnet = subnets.stream()
.filter(n -> n.networkId().equals(openstackPort.networkId()))
.findFirst().get();
Ip4Address gatewayIPAddress = Ip4Address.valueOf(openstackSubnet.gatewayIp());
OpenstackPortInfo.Builder portBuilder = OpenstackPortInfo.builder()
.setDeviceId(deviceId)
.setHostIp((Ip4Address) openstackPort.fixedIps().values().stream().findFirst().orElse(null))
.setHostMac(openstackPort.macAddress())
.setVni(vni)
.setGatewayIP(gatewayIPAddress)
.setNetworkId(openstackPort.networkId())
.setSecurityGroups(openstackPort.securityGroups());
openstackPortInfoMap.put(portName, portBuilder.build());
openstackPort.securityGroups().stream().forEach(sgId -> {
if (!securityGroupMap.containsKey(sgId)) {
securityGroupMap.put(sgId, openstackService.securityGroup(sgId));
}
});
}
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 InternalPacketProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
// FIXME: use GatewayNode list to check if the ARP packet is from GatewayNode's
if (context.isHandled()) {
return;
} else if (!SONA_DRIVER_NAME.equals(driverService
.getDriver(context.inPacket().receivedFrom().deviceId()).name())) {
return;
}
InboundPacket pkt = context.inPacket();
Ethernet ethernet = pkt.parsed();
if (ethernet != null && ethernet.getEtherType() == Ethernet.TYPE_ARP) {
arpHandler.processPacketIn(pkt, openstackPortInfoMap.values());
}
}
}
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent hostEvent) {
deviceEventExecutorService.execute(new InternalEventHandler(hostEvent));
}
}
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent deviceEvent) {
deviceEventExecutorService.execute(new InternalEventHandler(deviceEvent));
}
}
private class InternalEventHandler implements Runnable {
volatile AbstractEvent event;
InternalEventHandler(AbstractEvent event) {
this.event = event;
}
@Override
public void run() {
if (event instanceof DeviceEvent) {
DeviceEvent deviceEvent = (DeviceEvent) event;
switch (deviceEvent.type()) {
case DEVICE_ADDED:
log.debug("device {} is added", deviceEvent.subject().id());
break;
case DEVICE_AVAILABILITY_CHANGED:
Device device = deviceEvent.subject();
if (deviceService.isAvailable(device.id())) {
log.debug("device {} is added", device.id());
}
break;
case PORT_ADDED:
processPortUpdated(deviceEvent.subject(), deviceEvent.port());
break;
case PORT_UPDATED:
processPortUpdated(deviceEvent.subject(), deviceEvent.port());
break;
case PORT_REMOVED:
processPortRemoved(deviceEvent.port());
break;
default:
log.debug("Unsupported deviceEvent type {}", deviceEvent.type().toString());
break;
}
} else if (event instanceof HostEvent) {
HostEvent hostEvent = (HostEvent) event;
switch (hostEvent.type()) {
case HOST_REMOVED:
log.debug("host {} was removed", hostEvent.subject().toString());
break;
default:
log.debug("Unsupported hostEvent type {}", hostEvent.type().toString());
break;
}
}
}
}
private void readConfiguration() {
config = configService.getConfig("openstacknetworking", OpenstackNetworkingConfig.class);
if (config == null) {
log.error("No configuration found");
return;
}
arpHandler = new OpenstackArpHandler(openstackService, packetService, hostService);
sgRulePopulator = new OpenstackSecurityGroupRulePopulator(appId, openstackService, flowObjectiveService);
initializeFlowRules();
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (!event.configClass().equals(OpenstackNetworkingConfig.class)) {
return;
}
if (event.type().equals(NetworkConfigEvent.Type.CONFIG_ADDED) ||
event.type().equals(NetworkConfigEvent.Type.CONFIG_UPDATED)) {
configEventExecutorService.execute(OpenstackSwitchingManager.this::readConfiguration);
}
}
}
}
/*
* 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.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
import org.onosproject.net.device.DeviceService;
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.ExtensionTreatment;
import org.onosproject.net.flow.instructions.ExtensionPropertyException;
import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
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.OpenstackNetwork;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstacknetworking.OpenstackNetworkingConfig;
import org.onosproject.openstacknetworking.OpenstackPortInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Map;
/**
* Populates switching flow rules.
*/
public class OpenstackSwitchingRulePopulator {
private static Logger log = LoggerFactory
.getLogger(OpenstackSwitchingRulePopulator.class);
private static final int SWITCHING_RULE_PRIORITY = 30000;
private static final int TUNNELTAG_RULE_PRIORITY = 30000;
private static final String PORT_NAME = "portName";
private static final String TUNNEL_DST = "tunnelDst";
private FlowObjectiveService flowObjectiveService;
private DriverService driverService;
private DeviceService deviceService;
private ApplicationId appId;
private OpenstackNetworkingConfig config;
private Collection<OpenstackNetwork> openstackNetworkList;
private Collection<OpenstackPort> openstackPortList;
/**
* Creates OpenstackSwitchingRulPopulator.
*
* @param appId application id
* @param flowObjectiveService FlowObjectiveService reference
* @param deviceService DeviceService reference
* @param openstackService openstack interface service
* @param driverService DriverService reference
* @param config OpenstackRoutingConfig
*/
public OpenstackSwitchingRulePopulator(ApplicationId appId,
FlowObjectiveService flowObjectiveService,
DeviceService deviceService,
OpenstackInterfaceService openstackService,
DriverService driverService,
OpenstackNetworkingConfig config) {
this.flowObjectiveService = flowObjectiveService;
this.deviceService = deviceService;
this.driverService = driverService;
this.appId = appId;
this.config = config;
openstackNetworkList = openstackService.networks();
openstackPortList = openstackService.ports();
}
/**
* Populates flow rules for the VM created.
*
* @param device device to populate rules to
* @param port port for the VM created
*/
public void populateSwitchingRules(Device device, Port port) {
populateFlowRulesForTunnelTag(device, port);
populateFlowRulesForTrafficToSameCnode(device, port);
populateFlowRulesForTrafficToDifferentCnode(device, port);
}
/**
* Populate the flow rules for tagging tunnelId according to which inport is came from.
*
* @param device device to put the rules
* @param port port info of the VM
*/
private void populateFlowRulesForTunnelTag(Device device, Port port) {
Ip4Address vmIp = getFixedIpAddressForPort(port.annotations().value(PORT_NAME));
String portName = port.annotations().value(PORT_NAME);
String vni = getVniForPort(portName);
if (vmIp != null) {
setFlowRuleForTunnelTag(device.id(), port, vni);
}
}
private void setFlowRuleForTunnelTag(DeviceId deviceId, Port port, String vni) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchInPort(port.number());
tBuilder.setTunnelId(Long.parseLong(vni));
ForwardingObjective fo = DefaultForwardingObjective.builder()
.withSelector(sBuilder.build())
.withTreatment(tBuilder.build())
.withPriority(TUNNELTAG_RULE_PRIORITY)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(appId)
.add();
flowObjectiveService.forward(deviceId, fo);
}
/**
* Populates the flow rules for traffic to VMs in the same Cnode as the sender.
*
* @param device device to put the rules
* @param port port info of the VM
*/
private void populateFlowRulesForTrafficToSameCnode(Device device, Port port) {
Ip4Address vmIp = getFixedIpAddressForPort(port.annotations().value(PORT_NAME));
String portName = port.annotations().value(PORT_NAME);
String vni = getVniForPort(portName);
if (vmIp != null) {
setFlowRuleForVMsInSameCnode(vmIp, device.id(), port, vni);
}
}
/**
* Sets the flow rules for traffic between VMs in the same Cnode.
*
* @param ip4Address VM IP address
* @param id device ID to put rules
* @param port VM port
* @param vni VM VNI
*/
private void setFlowRuleForVMsInSameCnode(Ip4Address ip4Address, DeviceId id,
Port port, String vni) {
//For L2 Switching Case
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(ip4Address.toIpPrefix())
.matchTunnelId(Long.parseLong(vni));
tBuilder.setOutput(port.number());
ForwardingObjective fo = DefaultForwardingObjective.builder()
.withSelector(sBuilder.build())
.withTreatment(tBuilder.build())
.withPriority(SWITCHING_RULE_PRIORITY)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(appId)
.add();
flowObjectiveService.forward(id, fo);
}
/**
* Populates the flow rules for traffic to VMs in different Cnode using
* Nicira extention.
*
* @param device device to put rules
* @param port port information of the VM
*/
private void populateFlowRulesForTrafficToDifferentCnode(Device device, Port port) {
String portName = port.annotations().value(PORT_NAME);
Ip4Address fixedIp = getFixedIpAddressForPort(portName);
String vni = getVniForPort(portName);
Ip4Address hostDpIpAddress = config.nodes().get(device.id());
if (hostDpIpAddress == null) {
log.debug("There's no openstack node information for device id {}", device.id().toString());
return;
}
deviceService.getAvailableDevices().forEach(d -> {
if (!d.equals(device)) {
deviceService.getPorts(d.id()).forEach(p -> {
String pName = p.annotations().value(PORT_NAME);
if (!p.equals(port) && vni.equals(getVniForPort(pName))) {
Ip4Address hostxDpIpAddress = config.nodes().get(d.id());
Ip4Address fixedIpx = getFixedIpAddressForPort(pName);
if (port.isEnabled()) {
setVxLanFlowRule(vni, device.id(), hostxDpIpAddress, fixedIpx);
setVxLanFlowRule(vni, d.id(), hostDpIpAddress, fixedIp);
}
}
});
}
});
}
/**
* Sets the flow rules between traffic from VMs in different Cnode.
*
* @param vni VNI
* @param deviceId device ID
* @param hostIp host IP of the VM
* @param vmIp fixed IP of the VM
*/
private void setVxLanFlowRule(String vni, DeviceId deviceId, Ip4Address hostIp,
Ip4Address vmIp) {
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, hostIp), deviceId)
.setOutput(getTunnelPort(deviceId));
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);
}
/**
* Returns OpenstackPort object for the Port reference given.
*
* @param port Port object
* @return OpenstackPort reference, or null when not found
*/
public OpenstackPort openstackPort(Port port) {
String uuid = port.annotations().value(PORT_NAME).substring(3);
return openstackPortList.stream().filter(p -> p.id().startsWith(uuid))
.findAny().orElse(null);
}
/**
* Remove flows rules for the removed VM.
*
* @param removedPort removedport info
* @param openstackPortInfoMap openstackPortInfoMap
*/
public void removeSwitchingRules(Port removedPort, Map<String,
OpenstackPortInfo> openstackPortInfoMap) {
OpenstackPortInfo openstackPortInfo = openstackPortInfoMap
.get(removedPort.annotations().value(PORT_NAME));
DeviceId deviceId = openstackPortInfo.deviceId();
Ip4Address vmIp = openstackPortInfo.ip();
PortNumber portNumber = removedPort.number();
long vni = openstackPortInfo.vni();
removeFlowRuleForTunnelTag(deviceId, portNumber);
removeFlowRuleForVMsInSameCnode(deviceId, vmIp, vni);
removeFlowRuleForVMsInDiffrentCnode(removedPort, deviceId, vmIp, vni, openstackPortInfoMap);
}
/**
* Removes flow rules for tagging tunnelId.
*
* @param deviceId device id
* @param portNumber port number
*/
private void removeFlowRuleForTunnelTag(DeviceId deviceId, PortNumber portNumber) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchInPort(portNumber);
ForwardingObjective fo = DefaultForwardingObjective.builder()
.withSelector(sBuilder.build())
.withTreatment(tBuilder.build())
.withPriority(TUNNELTAG_RULE_PRIORITY)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(appId)
.remove();
flowObjectiveService.forward(deviceId, fo);
}
/**
* Removes the flow rules for traffic between VMs in the same Cnode.
*
* @param deviceId device id on which removed VM was run
* @param vmIp ip of the removed VM
* @param vni vni which removed VM was belonged
*/
private void removeFlowRuleForVMsInSameCnode(DeviceId deviceId, Ip4Address vmIp, long vni) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(vmIp.toIpPrefix())
.matchTunnelId(vni);
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);
}
/**
* Removes the flow rules for traffic between VMs in the different Cnode.
*
* @param removedPort removedport info
* @param deviceId device id on which removed VM was run
* @param vmIp ip of the removed VM
* @param vni vni which removed VM was belonged
* @param openstackPortInfoMap openstackPortInfoMap
*/
private void removeFlowRuleForVMsInDiffrentCnode(Port removedPort, DeviceId deviceId, Ip4Address vmIp,
long vni, Map<String, OpenstackPortInfo> openstackPortInfoMap) {
final boolean anyPortRemainedInSameCnode
= checkIfAnyPortRemainedInSameCnode(removedPort, deviceId, vni, openstackPortInfoMap);
openstackPortInfoMap.forEach((port, portInfo) -> {
if (portInfo.vni() == vni && !portInfo.deviceId().equals(deviceId)) {
removeVxLanFlowRule(portInfo.deviceId(), vmIp, vni);
if (!anyPortRemainedInSameCnode) {
removeVxLanFlowRule(deviceId, portInfo.ip(), vni);
}
}
});
}
/**
* Removes the flow rules between traffic from VMs in different Cnode.
*
* @param deviceId device id
* @param vmIp ip
* @param vni vni which removed VM was belonged
*/
private void removeVxLanFlowRule(DeviceId deviceId, Ip4Address vmIp, long vni) {
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
sBuilder.matchEthType(Ethernet.TYPE_IPV4)
.matchTunnelId(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);
}
/**
* Checks if there is any port remained with same vni at the device, on which the removed VM was run.
*
* @param removedPort removedport info
* @param deviceId device id on which removed VM was run
* @param vni vni which removed VM was belonged
* @param openstackPortInfoMap openstackPortInfoMap
* @return true if there is, false otherwise
*/
private boolean checkIfAnyPortRemainedInSameCnode(Port removedPort, DeviceId deviceId, long vni,
Map<String, OpenstackPortInfo> openstackPortInfoMap) {
for (Map.Entry<String, OpenstackPortInfo> entry : openstackPortInfoMap.entrySet()) {
if (!removedPort.annotations().value(PORT_NAME).equals(entry.getKey())) {
if (entry.getValue().vni() == vni && entry.getValue().deviceId().equals(deviceId)) {
return true;
}
}
}
return false;
}
/**
* Returns the VNI of the VM of the port.
*
* @param portName VM port
* @return VNI
*/
private String getVniForPort(String portName) {
String uuid = portName.substring(3);
OpenstackPort port = openstackPortList.stream()
.filter(p -> p.id().startsWith(uuid))
.findAny().orElse(null);
if (port == null) {
log.debug("No port information for port {}", portName);
return null;
}
OpenstackNetwork network = openstackNetworkList.stream()
.filter(n -> n.id().equals(port.networkId()))
.findAny().orElse(null);
if (network == null) {
log.warn("No VNI information for network {}", port.networkId());
return null;
}
return network.segmentId();
}
/**
* Returns the Fixed IP address of the VM.
*
* @param portName VM port info
* @return IP address of the VM
*/
private Ip4Address getFixedIpAddressForPort(String portName) {
String uuid = portName.substring(3);
OpenstackPort port = openstackPortList.stream()
.filter(p -> p.id().startsWith(uuid))
.findFirst().orElse(null);
if (port == null) {
log.error("There is no port information for port name {}", portName);
return null;
}
if (port.fixedIps().isEmpty()) {
log.error("There is no fixed IP info in the port information");
return null;
}
return (Ip4Address) port.fixedIps().values().toArray()[0];
}
private ExtensionTreatment buildNiciraExtenstion(DeviceId id, Ip4Address hostIp) {
Driver driver = driverService.getDriver(id);
DriverHandler driverHandler = new DefaultDriverHandler(new DefaultDriverData(driver, id));
ExtensionTreatmentResolver resolver = driverHandler.behaviour(ExtensionTreatmentResolver.class);
ExtensionTreatment extensionInstruction =
resolver.getExtensionInstruction(
ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST.type());
try {
extensionInstruction.setPropertyValue(TUNNEL_DST, hostIp);
} catch (ExtensionPropertyException e) {
log.error("Error setting Nicira extension setting {}", e);
}
return extensionInstruction;
}
private PortNumber getTunnelPort(DeviceId deviceId) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).equals(
OpenstackSwitchingManager.PORTNAME_PREFIX_TUNNEL))
.findAny().orElse(null);
if (port == null) {
log.error("No TunnelPort was created.");
return null;
}
return port.number();
}
}
......@@ -29,11 +29,10 @@
<packaging>pom</packaging>
<modules>
<module>app</module>
<module>web</module>
<module>api</module>
<module>openstackswitching</module>
<module>openstackrouting</module>
<module>web</module>
<module>switching</module>
<module>routing</module>
</modules>
<description>SONA Openstack Networking Application</description>
......
......@@ -4,12 +4,13 @@ COMPILE_DEPS = [
'//apps/openstackinterface/api:onos-apps-openstackinterface-api',
'//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
'//apps/scalablegateway:onos-apps-scalablegateway',
'//apps/openstacknode:onos-apps-openstacknode',
]
BUNDLES = [
'//apps/openstackinterface/api:onos-apps-openstackinterface-api',
'//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
'//apps/openstacknetworking/openstackrouting:onos-apps-openstacknetworking-openstackrouting',
'//apps/openstacknetworking/web:onos-apps-openstacknetworking-web',
'//apps/openstacknetworking/routing:onos-apps-openstacknetworking-routing',
]
osgi_jar_with_tests (
......@@ -17,9 +18,11 @@ osgi_jar_with_tests (
)
onos_app (
app_name = 'org.onosproject.openstackrouting',
title = 'OpenStack Routing App',
category = 'Utility',
url = 'http://onosproject.org',
description = 'OpenStack routing application.',
included_bundles = BUNDLES,
required_apps = [ 'org.onosproject.openstackinterface', 'org.onosproject.openstacknode', 'org.onosproject.scalablegateway' ]
)
......
......@@ -20,5 +20,6 @@
<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-openstackrouting/${project.version}</bundle>
</feature>
</features>
......
......@@ -28,6 +28,17 @@
<artifactId>onos-app-openstackrouting</artifactId>
<packaging>bundle</packaging>
<properties>
<onos.app.name>org.onosproject.openstackrouting</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.openstackinterface,
org.onosproject.openstacknode
</onos.app.requires>
</properties>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
......
......@@ -3,14 +3,14 @@ COMPILE_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/openstackinterface/api:onos-apps-openstackinterface-api',
'//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
'//apps/openstacknetworking/openstackswitching:onos-apps-openstacknetworking-openstackswitching',
'//apps/dhcp/api:onos-apps-dhcp-api',
'//apps/openstacknetworking/web:onos-apps-openstacknetworking-web',
'//apps/openstacknetworking/switching:onos-apps-openstacknetworking-switching',
]
osgi_jar_with_tests (
......@@ -18,9 +18,11 @@ osgi_jar_with_tests (
)
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' ]
)
......
......@@ -19,6 +19,7 @@
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>
......
......@@ -28,8 +28,24 @@
<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>
......@@ -41,6 +57,11 @@
</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>
......
/*
* 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;
}
}
}
}
/*
* 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
/*
* 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);
}
}
}
/*
* 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);
}
}
}
/*
* 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;
}
}
}
}
/*
* 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);
}
}
......@@ -15,16 +15,7 @@
*/
package org.onosproject.openstacknetworking.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstackinterface.web.OpenstackPortCodec;
import org.onosproject.openstacknetworking.OpenstackPortInfo;
import org.onosproject.openstacknetworking.OpenstackRoutingService;
import org.onosproject.openstacknetworking.OpenstackSwitchingService;
import org.onosproject.rest.AbstractWebResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
......@@ -43,47 +34,16 @@ import java.io.InputStream;
@Path("ports")
public class OpenstackPortWebResource extends AbstractWebResource {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String PORTNAME_PREFIX_VM = "tap";
private static final OpenstackPortCodec PORT_CODEC = new OpenstackPortCodec();
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createPorts(InputStream input) {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectNode portNode = (ObjectNode) mapper.readTree(input);
OpenstackPort openstackPort = PORT_CODEC.decode(portNode, this);
OpenstackSwitchingService switchingService =
getService(OpenstackSwitchingService.class);
switchingService.createPorts(openstackPort);
log.debug("REST API ports is called with {}", portNode.toString());
return Response.status(Response.Status.OK).build();
} catch (Exception e) {
log.error("Creates Port failed because of exception {}",
e.toString());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
.build();
}
return Response.status(Response.Status.OK).build();
}
@Path("{portUUID}")
@DELETE
public Response deletePorts(@PathParam("portUUID") String id) {
OpenstackSwitchingService switchingService =
getService(OpenstackSwitchingService.class);
OpenstackPortInfo portInfo = switchingService.openstackPortInfo()
.get(PORTNAME_PREFIX_VM.concat(id.substring(0, 11)));
OpenstackRoutingService routingService =
getService(OpenstackRoutingService.class);
routingService.checkDisassociatedFloatingIp(id, portInfo);
switchingService.removePort(id);
return Response.noContent().build();
}
......@@ -92,23 +52,7 @@ public class OpenstackPortWebResource extends AbstractWebResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response updatePorts(InputStream input) {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectNode portNode = (ObjectNode) mapper.readTree(input);
OpenstackPort openstackPort = PORT_CODEC.decode(portNode, this);
OpenstackSwitchingService switchingService =
getService(OpenstackSwitchingService.class);
switchingService.updatePort(openstackPort);
log.debug("REST API update port is called with {}", portNode.toString());
return Response.status(Response.Status.OK).build();
} catch (Exception e) {
log.error("Update Port failed because of exception {}",
e.toString());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.toString())
.build();
}
// TODO call security group update here
return Response.status(Response.Status.OK).build();
}
}
......
......@@ -16,5 +16,5 @@ onos_app (
category = 'Utility',
url = 'http://onosproject.org',
description = 'SONA Openstack Node Bootstrap Application.',
required_app = [ 'org.onosproject.ovsdb-base' ],
required_apps = [ 'org.onosproject.ovsdb-base', 'org.onosproject.drivers.ovsdb' ]
)
......
......@@ -38,7 +38,8 @@
<onos.app.url>http://onosproject.org</onos.app.url>
<onos.app.readme>SONA Openstack Node Bootstrap Application</onos.app.readme>
<onos.app.requires>
org.onosproject.ovsdb-base
org.onosproject.ovsdb-base,
org.onosproject.drivers.ovsdb
</onos.app.requires>
</properties>
......
......@@ -242,7 +242,7 @@ public final class OpenstackNodeManager implements OpenstackNodeService {
deviceService.removeListener(deviceListener);
nodeStore.removeListener(nodeStoreListener);
componentConfigService.unregisterProperties(getClass(), true);
componentConfigService.unregisterProperties(getClass(), false);
configRegistry.unregisterConfigFactory(configFactory);
leadershipService.withdraw(appId.name());
......
......@@ -54,8 +54,8 @@ APPS = [
'//apps/mlb:onos-apps-mlb-oar',
'//apps/openstackinterface:onos-apps-openstackinterface-oar',
'//apps/openstacknetworking:onos-apps-openstacknetworking-oar',
'//apps/openstacknetworking/openstackrouting:onos-apps-openstacknetworking-openstackrouting-oar',
'//apps/openstacknetworking/openstackswitching:onos-apps-openstacknetworking-openstackswitching-oar',
'//apps/openstacknetworking/routing:onos-apps-openstacknetworking-routing-oar',
'//apps/openstacknetworking/switching:onos-apps-openstacknetworking-switching-oar',
'//apps/mobility:onos-apps-mobility-oar',
'//apps/optical:onos-apps-optical-oar',
'//apps/pathpainter:onos-apps-pathpainter-oar',
......