Hyunsun Moon

Refactored to handle service instance by service type

- Added service instance handler
- Implemented dummy, vsg, and olt agent instance handler

Change-Id: Id3edd5eecb1caadf0f835cb10a952100e18b283b
......@@ -11,6 +11,7 @@ COMPILE_DEPS = [
'//core/store/serializers:onos-core-serializers',
'//apps/dhcp/api:onos-apps-dhcp-api',
'//apps/xosclient:onos-apps-xosclient',
'//apps/cordconfig:onos-apps-cordconfig',
'//protocols/ovsdb/api:onos-protocols-ovsdb-api',
'//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc',
]
......@@ -35,5 +36,5 @@ onos_app (
included_bundles = BUNDLES,
excluded_bundles = EXCLUDED_BUNDLES,
description = 'APIs for interacting with the CORD VTN application.',
required_apps = [ 'org.onosproject.xosclient', 'org.onosproject.dhcp', 'org.onosproject.ovsdb' ],
required_apps = [ 'org.onosproject.cord-config', 'org.onosproject.xosclient', 'org.onosproject.dhcp', 'org.onosproject.ovsdb' ],
)
......
......@@ -18,7 +18,7 @@
category="Traffic Steering" url="http://onosproject.org" title="CORD Virtual Tenant Network"
featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
features="${project.artifactId}"
apps="org.onosproject.ovsdb-base,org.onosproject.dhcp,org.onosproject.xosclient">
apps="org.onosproject.ovsdb-base,org.onosproject.dhcp,org.onosproject.xosclient,org.onosproject.cord-config">
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</artifact>
</app>
......
......@@ -108,6 +108,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cord-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.53</version>
......
......@@ -20,6 +20,7 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
......@@ -46,6 +47,7 @@ public class CordVtnConfig extends Config<ApplicationId> {
public static final String GATEWAY_IP = "gatewayIp";
public static final String GATEWAY_MAC = "gatewayMac";
public static final String LOCAL_MANAGEMENT_IP = "localManagementIp";
public static final String MANAGEMENT_IP = "managementIpRange";
public static final String OVSDB_PORT = "ovsdbPort";
public static final String CORDVTN_NODES = "nodes";
......@@ -187,6 +189,25 @@ public class CordVtnConfig extends Config<ApplicationId> {
}
/**
* Returns management IP address range.
*
* @return management network ip prefix, or null
*/
public IpPrefix managementIpRange() {
JsonNode jsonNode = object.get(MANAGEMENT_IP);
if (jsonNode == null) {
return null;
}
try {
return IpPrefix.valueOf(jsonNode.asText());
} catch (IllegalArgumentException e) {
log.error("{}:{} wrong address format", MANAGEMENT_IP, jsonNode);
return null;
}
}
/**
* Returns XOS access information.
*
* @return XOS access, or null
......
......@@ -15,14 +15,8 @@
*/
package org.onosproject.cordvtn.api;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.HostId;
import org.onosproject.xosclient.api.VtnServiceId;
import java.util.Map;
/**
* Service for provisioning overlay virtual networks on compute nodes.
*/
......@@ -31,21 +25,6 @@ public interface CordVtnService {
String CORDVTN_APP_ID = "org.onosproject.cordvtn";
/**
* Adds a new VM on a given node and connect point.
*
* @param node cordvtn node
* @param connectPoint connect point
*/
void addServiceVm(CordVtnNode node, ConnectPoint connectPoint);
/**
* Removes a VM from a given node and connect point.
*
* @param connectPoint connect point
*/
void removeServiceVm(ConnectPoint connectPoint);
/**
* Creates dependencies for a given tenant service.
*
* @param tServiceId id of the service which has a dependency
......@@ -62,14 +41,4 @@ public interface CordVtnService {
* @param pServiceId id of the service which provide dependency
*/
void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId);
/**
* Updates virtual service gateways.
*
* @param vSgHost host id of vSG host
* @param serviceVlan service vlan id
* @param vSgs map of ip and mac address of vSGs running in this vSG host
*/
void updateVirtualSubscriberGateways(HostId vSgHost, String serviceVlan,
Map<IpAddress, MacAddress> vSgs);
}
......
/*
* 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.cordvtn.api;
import com.google.common.base.Strings;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.PortNumber;
import org.onosproject.xosclient.api.VtnPortId;
import org.onosproject.xosclient.api.VtnService;
import org.onosproject.xosclient.api.VtnServiceId;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Provides methods to help to handle network service instance.
*/
public final class Instance {
public static final String SERVICE_ID = "serviceId";
public static final String SERVICE_TYPE = "serviceType";
public static final String PORT_ID = "vtnPortId";
public static final String CREATE_TIME = "createTime";
public static final String NESTED_INSTANCE = "nestedInstance";
public static final String TRUE = "true";
private final Host host;
/**
* Default constructor.
*
* @param instance host object of this instance
*/
private Instance(Host instance) {
this.host = instance;
}
/**
* Returns host object of this instance.
*
* @return host
*/
public Host host() {
return this.host;
}
/**
* Returns new instance.
*
* @param host host object of this instance
* @return instance
*/
public static Instance of(Host host) {
checkNotNull(host);
checkArgument(!Strings.isNullOrEmpty(host.annotations().value(SERVICE_ID)));
checkArgument(!Strings.isNullOrEmpty(host.annotations().value(SERVICE_TYPE)));
checkArgument(!Strings.isNullOrEmpty(host.annotations().value(PORT_ID)));
checkArgument(!Strings.isNullOrEmpty(host.annotations().value(CREATE_TIME)));
return new Instance(host);
}
/**
* Returns service ID of a given host.
*
* @return vtn service id
*/
public VtnServiceId serviceId() {
String serviceId = host.annotations().value(SERVICE_ID);
return VtnServiceId.of(serviceId);
}
/**
* Returns service type of a given host.
*
* @return vtn service type
*/
public VtnService.ServiceType serviceType() {
String serviceType = host.annotations().value(SERVICE_TYPE);
return VtnService.ServiceType.valueOf(serviceType);
}
/**
* Returns port ID of a given host.
*
* @return vtn port id
*/
public VtnPortId portId() {
String portId = host.annotations().value(PORT_ID);
return VtnPortId.of(portId);
}
/**
* Returns if the instance is nested container or not.
*
* @return true if it's nested container; false otherwise
*/
public boolean isNestedInstance() {
return host.annotations().value(NESTED_INSTANCE) != null;
}
/**
* Returns MAC address of this instance.
*
* @return mac address
*/
public MacAddress mac() {
return host.mac();
}
/**
* Returns IP address of this instance.
*
* @return ip address
*/
public Ip4Address ipAddress() {
// assume all instance has only one IP address, and only IP4 is supported now
return host.ipAddresses().stream().findFirst().get().getIp4Address();
}
/**
* Returns device ID of this host.
*
* @return device id
*/
public DeviceId deviceId() {
return host.location().deviceId();
}
/**
* Returns the port number where this host is.
*
* @return port number
*/
public PortNumber portNumber() {
return host.location().port();
}
/**
* Returns annotation value with a given key.
*
* @param annotationKey annotation key
* @return annotation value
*/
public String getAnnotation(String annotationKey) {
return host.annotations().value(annotationKey);
}
@Override
public String toString() {
return host.toString();
}
}
/*
* 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.cordvtn.api;
/**
* Handles service instance detection and removal.
*/
public interface InstanceHandler {
/**
* Handles newly detected instance.
*
* @param instance instance
*/
void instanceDetected(Instance instance);
/**
* Handles removed instance.
*
* @param instance instance
*/
void instanceRemoved(Instance instance);
}
......@@ -18,7 +18,7 @@ package org.onosproject.cordvtn.cli;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cordvtn.impl.CordVtnNodeManager;
import org.onosproject.cordvtn.impl.CordVtnPipeline;
/**
* Deletes nodes from the service.
......@@ -29,8 +29,8 @@ public class CordVtnFlushRules extends AbstractShellCommand {
@Override
protected void execute() {
CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
nodeManager.flushRules();
CordVtnPipeline pipeline = AbstractShellCommand.get(CordVtnPipeline.class);
pipeline.flushRules();
print("Successfully flushed");
}
}
......
......@@ -15,7 +15,6 @@
*/
package org.onosproject.cordvtn.impl;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
......@@ -27,210 +26,77 @@ import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.cordvtn.api.CordVtnConfig;
import org.onlab.packet.Ip4Prefix;
import org.onosproject.cordvtn.api.CordVtnNode;
import org.onosproject.cordvtn.api.CordVtnService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.dhcp.DhcpService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.cordvtn.api.Instance;
import org.onosproject.core.DefaultGroupId;
import org.onosproject.core.GroupId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
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.basics.SubjectFactories;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.DefaultGroupKey;
import org.onosproject.net.group.Group;
import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupBuckets;
import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupKey;
import org.onosproject.net.group.GroupService;
import org.onosproject.net.host.DefaultHostDescription;
import org.onosproject.net.host.HostDescription;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
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.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.AbstractProvider;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.xosclient.api.OpenStackAccess;
import org.onosproject.xosclient.api.VtnPort;
import org.onosproject.xosclient.api.VtnPortApi;
import org.onosproject.xosclient.api.VtnPortId;
import org.onosproject.xosclient.api.VtnService;
import org.onosproject.xosclient.api.VtnServiceApi;
import org.onosproject.xosclient.api.VtnServiceId;
import org.onosproject.xosclient.api.XosAccess;
import org.onosproject.xosclient.api.XosClientService;
import org.slf4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
import static org.onosproject.net.group.DefaultGroupBucket.createSelectGroupBucket;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provisions virtual tenant networks with service chaining capability
* in OpenStack environment.
* Provisions service dependency capabilities between network services.
*/
@Component(immediate = true)
@Service
public class CordVtn extends AbstractProvider implements CordVtnService, HostProvider {
public class CordVtn extends CordVtnInstanceHandler implements CordVtnService {
protected final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostProviderRegistry hostProviderRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected GroupService groupService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DhcpService dhcpService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected XosClientService xosClient;
private final ConfigFactory configFactory =
new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
@Override
public CordVtnConfig createConfig() {
return new CordVtnConfig();
}
};
private static final String XOS_ACCESS_ERROR = "XOS access is not configured";
private static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
private static final String DEFAULT_TUNNEL = "vxlan";
private static final String SERVICE_ID = "serviceId";
private static final String PORT_ID = "vtnPortId";
private static final String DATA_PLANE_IP = "dataPlaneIp";
private static final String DATA_PLANE_INTF = "dataPlaneIntf";
private static final String S_TAG = "stag";
private static final String VSG_HOST_ID = "vsgHostId";
private static final String CREATE_TIME = "createTime";
private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
private final ExecutorService eventExecutor =
newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
private final PacketProcessor packetProcessor = new InternalPacketProcessor();
private final HostListener hostListener = new InternalHostListener();
private final NetworkConfigListener configListener = new InternalConfigListener();
private ApplicationId appId;
private HostProviderService hostProvider;
private CordVtnRuleInstaller ruleInstaller;
private CordVtnArpProxy arpProxy;
private volatile XosAccess xosAccess = null;
private volatile OpenStackAccess osAccess = null;
private volatile MacAddress privateGatewayMac = MacAddress.NONE;
/**
* Creates an cordvtn host location provider.
*/
public CordVtn() {
super(new ProviderId("host", CORDVTN_APP_ID));
}
@Activate
protected void activate() {
appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
deviceService,
groupService,
hostService,
configRegistry,
DEFAULT_TUNNEL);
arpProxy = new CordVtnArpProxy(appId, packetService, hostService);
packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
arpProxy.requestPacket();
hostService.addListener(hostListener);
hostProvider = hostProviderRegistry.register(this);
configRegistry.registerConfigFactory(configFactory);
configRegistry.addListener(configListener);
log.info("Started");
eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
hostListener = new InternalHostListener();
super.activate();
}
@Deactivate
protected void deactivate() {
hostProviderRegistry.unregister(this);
hostService.removeListener(hostListener);
packetService.removeProcessor(packetProcessor);
configRegistry.unregisterConfigFactory(configFactory);
configRegistry.removeListener(configListener);
eventExecutor.shutdown();
log.info("Stopped");
}
@Override
public void triggerProbe(Host host) {
/*
* Note: In CORD deployment, we assume that all hosts are configured.
* Therefore no probe is required.
*/
super.deactivate();
}
@Override
public void createServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId,
boolean isBidirectional) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
// TODO remove openstack access when XOS provides all information
VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
VtnService tService = serviceApi.service(tServiceId, osAccess);
VtnService pService = serviceApi.service(pServiceId, osAccess);
VtnService tService = getVtnService(tServiceId);
VtnService pService = getVtnService(pServiceId);
if (tService == null || pService == null) {
log.error("Failed to create dependency between {} and {}",
......@@ -239,18 +105,13 @@ public class CordVtn extends AbstractProvider implements CordVtnService, HostPro
}
log.info("Created dependency between {} and {}", tService.name(), pService.name());
ruleInstaller.populateServiceDependencyRules(tService, pService, isBidirectional, true);
serviceDependencyRules(tService, pService, isBidirectional, true);
}
@Override
public void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
// TODO remove openstack access when XOS provides all information
VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
VtnService tService = serviceApi.service(tServiceId, osAccess);
VtnService pService = serviceApi.service(pServiceId, osAccess);
VtnService tService = getVtnService(tServiceId);
VtnService pService = getVtnService(pServiceId);
if (tService == null || pService == null) {
log.error("Failed to remove dependency between {} and {}",
......@@ -259,427 +120,267 @@ public class CordVtn extends AbstractProvider implements CordVtnService, HostPro
}
log.info("Removed dependency between {} and {}", tService.name(), pService.name());
ruleInstaller.populateServiceDependencyRules(tService, pService, true, false);
serviceDependencyRules(tService, pService, true, false);
}
@Override
public void addServiceVm(CordVtnNode node, ConnectPoint connectPoint) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
String portName = port.annotations().value("portName");
// TODO remove openstack access when XOS provides all information
VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
VtnPort vtnPort = portApi.vtnPort(portName, osAccess);
if (vtnPort == null) {
log.warn("Failed to get port information of {}", portName);
public void instanceDetected(Instance instance) {
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
return;
}
// Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
// existing instances.
DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
.set(SERVICE_ID, vtnPort.serviceId().id())
.set(PORT_ID, vtnPort.id().id())
.set(DATA_PLANE_IP, node.dpIp().ip().toString())
.set(DATA_PLANE_INTF, node.dpIntf())
.set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
// TODO address service specific task in a separate package
String serviceVlan = getServiceVlan(vtnPort);
if (!Strings.isNullOrEmpty(serviceVlan)) {
annotations.set(S_TAG, serviceVlan);
}
HostDescription hostDesc = new DefaultHostDescription(
vtnPort.mac(),
VlanId.NONE,
new HostLocation(connectPoint, System.currentTimeMillis()),
Sets.newHashSet(vtnPort.ip()),
annotations.build());
HostId hostId = HostId.hostId(vtnPort.mac());
hostProvider.hostDetected(hostId, hostDesc, false);
}
// TODO get bidirectional information from XOS once XOS supports
service.tenantServices().stream().forEach(
tServiceId -> createServiceDependency(tServiceId, service.id(), true));
service.providerServices().stream().forEach(
pServiceId -> createServiceDependency(service.id(), pServiceId, true));
@Override
public void removeServiceVm(ConnectPoint connectPoint) {
hostService.getConnectedHosts(connectPoint)
.stream()
.forEach(host -> hostProvider.hostVanished(host.id()));
updateProviderServiceInstances(service);
}
@Override
// TODO address service specific task in a separate package
public void updateVirtualSubscriberGateways(HostId vSgHostId, String serviceVlan,
Map<IpAddress, MacAddress> vSgs) {
Host vSgHost = hostService.getHost(vSgHostId);
if (vSgHost == null || !vSgHost.annotations().value(S_TAG).equals(serviceVlan)) {
log.debug("Invalid vSG updates for {}", serviceVlan);
public void instanceRemoved(Instance instance) {
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
return;
}
log.info("Updates vSGs in {} with {}", vSgHost.id(), vSgs.toString());
vSgs.entrySet().stream()
.filter(entry -> hostService.getHostsByMac(entry.getValue()).isEmpty())
.forEach(entry -> addVirtualSubscriberGateway(
vSgHost,
entry.getKey(),
entry.getValue(),
serviceVlan));
hostService.getConnectedHosts(vSgHost.location()).stream()
.filter(host -> !host.mac().equals(vSgHost.mac()))
.filter(host -> !vSgs.values().contains(host.mac()))
.forEach(host -> {
log.info("Removed vSG {}", host.toString());
hostProvider.hostVanished(host.id());
});
if (!service.providerServices().isEmpty()) {
removeInstanceFromTenantService(instance, service);
}
if (!service.tenantServices().isEmpty()) {
updateProviderServiceInstances(service);
}
/**
* Adds virtual subscriber gateway to the system.
*
* @param vSgHost host virtual machine of this vSG
* @param vSgIp vSG ip address
* @param vSgMac vSG mac address
* @param serviceVlan service vlan
*/
// TODO address service specific task in a separate package
private void addVirtualSubscriberGateway(Host vSgHost, IpAddress vSgIp, MacAddress vSgMac,
String serviceVlan) {
log.info("vSG with IP({}) MAC({}) added", vSgIp.toString(), vSgMac.toString());
HostId hostId = HostId.hostId(vSgMac);
DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
.set(S_TAG, serviceVlan)
.set(VSG_HOST_ID, vSgHost.id().toString())
.set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
HostDescription hostDesc = new DefaultHostDescription(
vSgMac,
VlanId.NONE,
vSgHost.location(),
Sets.newHashSet(vSgIp),
annotations.build());
hostProvider.hostDetected(hostId, hostDesc, false);
}
/**
* Returns public ip addresses of vSGs running inside a give vSG host.
*
* @param vSgHost vSG host
* @return map of ip and mac address, or empty map
*/
// TODO address service specific task in a separate package
private Map<IpAddress, MacAddress> getSubscriberGateways(Host vSgHost) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
private void updateProviderServiceInstances(VtnService service) {
GroupKey groupKey = getGroupKey(service.id());
String vtnPortId = vSgHost.annotations().value(PORT_ID);
String sTag = vSgHost.annotations().value(S_TAG);
Set<DeviceId> devices = nodeManager.completeNodes().stream()
.map(CordVtnNode::intBrId)
.collect(Collectors.toSet());
if (Strings.isNullOrEmpty(vtnPortId) || Strings.isNullOrEmpty(sTag)) {
log.warn("PORT_ID and S_TAG is not set, ignore {}", vSgHost);
return Maps.newHashMap();
for (DeviceId deviceId : devices) {
Group group = groupService.getGroup(deviceId, groupKey);
if (group == null) {
log.trace("No group exists for service {} in {}", service.id(), deviceId);
continue;
}
// TODO remove openstack access when XOS provides all information
VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
VtnPort vtnPort = portApi.vtnPort(VtnPortId.of(vtnPortId), osAccess);
if (vtnPort == null) {
log.warn("Failed to get port information of {}", vSgHost);
return Maps.newHashMap();
}
List<GroupBucket> oldBuckets = group.buckets().buckets();
List<GroupBucket> newBuckets = getServiceGroupBuckets(
deviceId, service.vni(), getInstances(service.id())).buckets();
if (!sTag.equals(getServiceVlan(vtnPort))) {
log.error("Host({}) s-tag does not match with VTN port s-tag", vSgHost);
return Maps.newHashMap();
}
return vtnPort.addressPairs();
if (oldBuckets.equals(newBuckets)) {
continue;
}
/**
* Returns s-tag from a given VTN port.
*
* @param vtnPort vtn port
* @return s-tag string
*/
// TODO address service specific task in a separate package
private String getServiceVlan(VtnPort vtnPort) {
checkNotNull(vtnPort);
String portName = vtnPort.name();
if (portName != null && portName.startsWith(S_TAG)) {
return portName.split("-")[1];
} else {
return null;
}
List<GroupBucket> bucketsToRemove = Lists.newArrayList(oldBuckets);
bucketsToRemove.removeAll(newBuckets);
if (!bucketsToRemove.isEmpty()) {
groupService.removeBucketsFromGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToRemove),
groupKey, appId);
}
/**
* Returns instances with a given network service.
*
* @param serviceId service id
* @return set of hosts
*/
private Set<Host> getInstances(VtnServiceId serviceId) {
return StreamSupport.stream(hostService.getHosts().spliterator(), false)
.filter(host -> Objects.equals(
serviceId.id(),
host.annotations().value(SERVICE_ID)))
.collect(Collectors.toSet());
List<GroupBucket> bucketsToAdd = Lists.newArrayList(newBuckets);
bucketsToAdd.removeAll(oldBuckets);
if (!bucketsToAdd.isEmpty()) {
groupService.addBucketsToGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToAdd),
groupKey, appId);
}
/**
* Registers static DHCP lease for a given host.
*
* @param host host
* @param service cord service
*/
private void registerDhcpLease(Host host, VtnService service) {
List<Ip4Address> options = Lists.newArrayList();
options.add(Ip4Address.makeMaskPrefix(service.subnet().prefixLength()));
options.add(service.serviceIp().getIp4Address());
options.add(service.serviceIp().getIp4Address());
options.add(DEFAULT_DNS);
log.debug("Set static DHCP mapping for {}", host.mac());
dhcpService.setStaticMapping(host.mac(),
host.ipAddresses().stream().findFirst().get().getIp4Address(),
true,
options);
}
/**
* Handles VM detected situation.
*
* @param host host
*/
private void serviceVmAdded(Host host) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
// TODO address service specific task in a separate package
String serviceVlan = host.annotations().value(S_TAG);
if (serviceVlan != null) {
virtualSubscriberGatewayAdded(host, serviceVlan);
}
String serviceId = host.annotations().value(SERVICE_ID);
if (Strings.isNullOrEmpty(serviceId)) {
// ignore this host, it is not a service instance
return;
}
log.info("Instance is detected {}", host);
private void removeInstanceFromTenantService(Instance instance, VtnService service) {
service.providerServices().stream().forEach(pServiceId -> {
Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
// TODO remove openstack access when XOS provides all information
VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
VtnService service = serviceApi.service(VtnServiceId.of(serviceId), osAccess);
if (service == null) {
log.warn("Failed to get VtnService for {}", serviceId);
return;
}
inPorts.put(instance.deviceId(), Sets.newHashSet(instance.portNumber()));
outGroups.put(instance.deviceId(), getGroupId(pServiceId, instance.deviceId()));
switch (service.networkType()) {
case MANAGEMENT:
ruleInstaller.populateManagementNetworkRules(host, service);
break;
case PRIVATE:
arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
case PUBLIC:
default:
// TODO get bidirectional information from XOS once XOS supports
service.tenantServices().stream().forEach(
tServiceId -> createServiceDependency(tServiceId, service.id(), true));
service.providerServices().stream().forEach(
pServiceId -> createServiceDependency(service.id(), pServiceId, true));
ruleInstaller.updateProviderServiceGroup(service);
// sends gratuitous ARP here for the case of adding existing VMs
// when ONOS or cordvtn app is restarted
arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(host));
break;
inServiceRule(inPorts, outGroups, false);
});
}
registerDhcpLease(host, service);
ruleInstaller.populateBasicConnectionRules(host, service, true);
}
private void serviceDependencyRules(VtnService tService, VtnService pService,
boolean isBidirectional, boolean install) {
Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
/**
* Handles VM removed situation.
*
* @param host host
*/
private void serviceVmRemoved(Host host) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
nodeManager.completeNodes().stream().forEach(node -> {
DeviceId deviceId = node.intBrId();
GroupId groupId = createServiceGroup(deviceId, pService);
outGroups.put(deviceId, groupId);
// TODO address service specific task in a separate package
if (host.annotations().value(S_TAG) != null) {
virtualSubscriberGatewayRemoved(host);
}
Set<PortNumber> tServiceInstances = getInstances(tService.id())
.stream()
.filter(instance -> instance.deviceId().equals(deviceId))
.map(Instance::portNumber)
.collect(Collectors.toSet());
inPorts.put(deviceId, tServiceInstances);
});
String serviceId = host.annotations().value(SERVICE_ID);
if (Strings.isNullOrEmpty(serviceId)) {
// ignore this host, it is not a service instance
return;
Ip4Prefix srcRange = tService.subnet().getIp4Prefix();
Ip4Prefix dstRange = pService.subnet().getIp4Prefix();
indirectAccessRule(srcRange, pService.serviceIp().getIp4Address(), outGroups, install);
directAccessRule(srcRange, dstRange, install);
if (isBidirectional) {
directAccessRule(dstRange, srcRange, install);
}
inServiceRule(inPorts, outGroups, install);
}
private void indirectAccessRule(Ip4Prefix srcRange, Ip4Address serviceIp,
Map<DeviceId, GroupId> outGroups, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcRange)
.matchIPDst(serviceIp.toIpPrefix())
.build();
for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.group(outGroup.getValue())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(outGroup.getKey())
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
}
}
private void directAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcRange)
.matchIPDst(dstRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
nodeManager.completeNodes().stream().forEach(node -> {
DeviceId deviceId = node.intBrId();
FlowRule flowRuleDirect = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRuleDirect);
});
}
log.info("Instance is vanished {}", host);
private void inServiceRule(Map<DeviceId, Set<PortNumber>> inPorts,
Map<DeviceId, GroupId> outGroups, boolean install) {
for (Map.Entry<DeviceId, Set<PortNumber>> entry : inPorts.entrySet()) {
Set<PortNumber> ports = entry.getValue();
DeviceId deviceId = entry.getKey();
// TODO remove openstack access when XOS provides all information
VtnServiceApi vtnServiceApi = xosClient.getClient(xosAccess).vtnService();
VtnService service = vtnServiceApi.service(VtnServiceId.of(serviceId), osAccess);
if (service == null) {
log.warn("Failed to get VtnService for {}", serviceId);
return;
GroupId groupId = outGroups.get(deviceId);
if (groupId == null) {
continue;
}
// TODO need to consider the case that the service is removed also
switch (service.networkType()) {
case MANAGEMENT:
break;
case PRIVATE:
if (getInstances(VtnServiceId.of(serviceId)).isEmpty()) {
arpProxy.removeGateway(service.serviceIp());
}
case PUBLIC:
default:
if (!service.tenantServices().isEmpty()) {
ruleInstaller.updateProviderServiceGroup(service);
}
if (!service.providerServices().isEmpty()) {
ruleInstaller.updateTenantServiceVm(host, service);
}
break;
}
ports.stream().forEach(port -> {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(port)
.build();
dhcpService.removeStaticMapping(host.mac());
ruleInstaller.populateBasicConnectionRules(host, service, false);
}
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.group(groupId)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_IN_SERVICE)
.makePermanent()
.build();
/**
* Handles virtual subscriber gateway VM or container.
*
* @param host new host with stag, it can be vsg VM or vsg
* @param serviceVlan service vlan
*/
// TODO address service specific task in a separate package
private void virtualSubscriberGatewayAdded(Host host, String serviceVlan) {
Map<IpAddress, MacAddress> vSgs;
Host vSgHost;
String vSgHostId = host.annotations().value(VSG_HOST_ID);
if (vSgHostId == null) {
log.info("vSG VM detected {}", host.id());
vSgHost = host;
vSgs = getSubscriberGateways(vSgHost);
vSgs.entrySet().stream().forEach(entry -> addVirtualSubscriberGateway(
vSgHost,
entry.getKey(),
entry.getValue(),
serviceVlan));
} else {
vSgHost = hostService.getHost(HostId.hostId(vSgHostId));
if (vSgHost == null) {
return;
pipeline.processFlowRule(install, flowRule);
});
}
log.info("vSG detected {}", host.id());
vSgs = getSubscriberGateways(vSgHost);
}
ruleInstaller.populateSubscriberGatewayRules(vSgHost, vSgs.keySet());
private GroupId getGroupId(VtnServiceId serviceId, DeviceId deviceId) {
return new DefaultGroupId(Objects.hash(serviceId, deviceId));
}
/**
* Handles virtual subscriber gateway removed.
*
* @param vSg vsg host to remove
*/
// TODO address service specific task in a separate package
private void virtualSubscriberGatewayRemoved(Host vSg) {
String vSgHostId = vSg.annotations().value(VSG_HOST_ID);
if (vSgHostId == null) {
return;
private GroupKey getGroupKey(VtnServiceId serviceId) {
return new DefaultGroupKey(serviceId.id().getBytes());
}
Host vSgHost = hostService.getHost(HostId.hostId(vSgHostId));
if (vSgHost == null) {
return;
}
private GroupId createServiceGroup(DeviceId deviceId, VtnService service) {
GroupKey groupKey = getGroupKey(service.id());
Group group = groupService.getGroup(deviceId, groupKey);
GroupId groupId = getGroupId(service.id(), deviceId);
log.info("vSG removed {}", vSg.id());
Map<IpAddress, MacAddress> vSgs = getSubscriberGateways(vSgHost);
ruleInstaller.populateSubscriberGatewayRules(vSgHost, vSgs.keySet());
if (group != null) {
log.debug("Group {} is already exist in {}", service.id(), deviceId);
return groupId;
}
/**
* Sets service network gateway MAC address and sends out gratuitous ARP to all
* VMs to update the gateway MAC address.
*
* @param newMac mac address to update
*/
private void setPrivateGatewayMac(MacAddress newMac) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
GroupBuckets buckets = getServiceGroupBuckets(
deviceId, service.vni(), getInstances(service.id()));
GroupDescription groupDescription = new DefaultGroupDescription(
deviceId,
GroupDescription.Type.SELECT,
buckets,
groupKey,
groupId.id(),
appId);
if (newMac == null || newMac.equals(privateGatewayMac)) {
// no updates, do nothing
return;
groupService.addGroup(groupDescription);
return groupId;
}
privateGatewayMac = newMac;
log.debug("Set service gateway MAC address to {}", privateGatewayMac.toString());
private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId,
Set<Instance> instances) {
List<GroupBucket> buckets = Lists.newArrayList();
instances.stream().forEach(instance -> {
Ip4Address tunnelIp = nodeManager.dpIp(instance.deviceId()).getIp4Address();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
VtnServiceApi vtnServiceApi = xosClient.getClient(xosAccess).vtnService();
vtnServiceApi.services().stream().forEach(serviceId -> {
VtnService service = vtnServiceApi.service(serviceId, osAccess);
if (service != null) {
arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
arpProxy.sendGratuitousArpForGateway(service.serviceIp(), getInstances(serviceId));
}
});
}
/**
* Sets public gateway MAC address.
*
* @param publicGateways gateway ip and mac address pairs
*/
private void setPublicGatewayMac(Map<IpAddress, MacAddress> publicGateways) {
publicGateways.entrySet()
.stream()
.forEach(entry -> {
arpProxy.addGateway(entry.getKey(), entry.getValue());
log.debug("Added public gateway IP {}, MAC {}",
entry.getKey().toString(), entry.getValue().toString());
if (deviceId.equals(instance.deviceId())) {
tBuilder.setEthDst(instance.mac())
.setOutput(instance.portNumber());
} else {
ExtensionTreatment tunnelDst =
pipeline.tunnelDstTreatment(deviceId, tunnelIp);
tBuilder.setEthDst(instance.mac())
.extension(tunnelDst, deviceId)
.setTunnelId(tunnelId)
.setOutput(nodeManager.tunnelPort(instance.deviceId()));
}
buckets.add(createSelectGroupBucket(tBuilder.build()));
});
// TODO notice gateway MAC change to VMs holds this gateway IP
}
/**
* Updates configurations.
*/
private void readConfiguration() {
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found");
return;
}
xosAccess = config.xosAccess();
osAccess = config.openstackAccess();
setPrivateGatewayMac(config.privateGatewayMac());
setPublicGatewayMac(config.publicGateways());
return new GroupBuckets(buckets);
}
private class InternalHostListener implements HostListener {
......@@ -692,50 +393,14 @@ public class CordVtn extends AbstractProvider implements CordVtnService, HostPro
return;
}
Instance instance = Instance.of(host);
switch (event.type()) {
case HOST_UPDATED:
case HOST_ADDED:
eventExecutor.execute(() -> serviceVmAdded(host));
eventExecutor.execute(() -> instanceDetected(instance));
break;
case HOST_REMOVED:
eventExecutor.execute(() -> serviceVmRemoved(host));
break;
default:
break;
}
}
}
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;
}
arpProxy.processArpPacket(context, ethPacket);
}
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (!event.configClass().equals(CordVtnConfig.class)) {
return;
}
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
log.info("Network configuration changed");
eventExecutor.execute(CordVtn.this::readConfiguration);
eventExecutor.execute(() -> instanceRemoved(instance));
break;
default:
break;
......
......@@ -22,6 +22,7 @@ import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.cordvtn.api.Instance;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.Host;
import org.onosproject.net.flow.DefaultTrafficSelector;
......@@ -166,9 +167,9 @@ public class CordVtnArpProxy {
* Emits gratuitous ARP when a gateway mac address has been changed.
*
* @param gatewayIp gateway ip address to update MAC
* @param hosts set of hosts to send gratuitous ARP packet
* @param instances set of instances to send gratuitous ARP packet
*/
public void sendGratuitousArpForGateway(IpAddress gatewayIp, Set<Host> hosts) {
public void sendGratuitousArpForGateway(IpAddress gatewayIp, Set<Instance> instances) {
MacAddress gatewayMac = gateways.get(gatewayIp.getIp4Address());
if (gatewayMac == null) {
log.debug("Gateway {} is not registered to ARP proxy", gatewayIp.toString());
......@@ -176,13 +177,13 @@ public class CordVtnArpProxy {
}
Ethernet ethArp = buildGratuitousArp(gatewayIp.getIp4Address(), gatewayMac);
hosts.stream().forEach(host -> {
instances.stream().forEach(instance -> {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(host.location().port())
.setOutput(instance.portNumber())
.build();
packetService.emit(new DefaultOutboundPacket(
host.location().deviceId(),
instance.deviceId(),
treatment,
ByteBuffer.wrap(ethArp.serialize())));
});
......
/*
* 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.cordvtn.impl;
import org.apache.felix.scr.annotations.Component;
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.Ip4Prefix;
import org.onosproject.cordvtn.api.CordVtnConfig;
import org.onosproject.cordvtn.api.CordVtnNode;
import org.onosproject.cordvtn.api.CordVtnService;
import org.onosproject.cordvtn.api.Instance;
import org.onosproject.cordvtn.api.InstanceHandler;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.Host;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostService;
import org.onosproject.xosclient.api.OpenStackAccess;
import org.onosproject.xosclient.api.VtnService;
import org.onosproject.xosclient.api.VtnServiceApi;
import org.onosproject.xosclient.api.VtnServiceId;
import org.onosproject.xosclient.api.XosAccess;
import org.onosproject.xosclient.api.XosClientService;
import org.slf4j.Logger;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
import static org.onosproject.xosclient.api.VtnService.NetworkType.MANAGEMENT;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provides default virtual network connectivity for service instances.
*/
@Component(immediate = true)
public abstract class CordVtnInstanceHandler implements InstanceHandler {
protected final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected XosClientService xosClient;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CordVtnNodeManager nodeManager;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CordVtnPipeline pipeline;
protected static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
protected static final String XOS_ACCESS_ERROR = "XOS access is not configured";
protected XosAccess xosAccess = null;
protected OpenStackAccess osAccess = null;
protected ApplicationId appId;
protected VtnService.ServiceType serviceType;
protected ExecutorService eventExecutor;
protected HostListener hostListener = new InternalHostListener();
protected NetworkConfigListener configListener = new InternalConfigListener();
protected void activate() {
// sub class should set service type and event executor in its activate method
appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
hostService.addListener(hostListener);
configRegistry.addListener(configListener);
log.info("Started");
}
protected void deactivate() {
hostService.removeListener(hostListener);
configRegistry.removeListener(configListener);
eventExecutor.shutdown();
log.info("Stopped");
}
@Override
public void instanceDetected(Instance instance) {
log.info("Instance is detected {}", instance);
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
log.warn("Failed to get VtnService for {}", instance);
return;
}
if (service.networkType().equals(MANAGEMENT)) {
managementNetworkRules(instance, service, true);
}
defaultConnectionRules(instance, service, true);
}
@Override
public void instanceRemoved(Instance instance) {
log.info("Instance is removed {}", instance);
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
log.warn("Failed to get VtnService for {}", instance);
return;
}
if (service.networkType().equals(MANAGEMENT)) {
managementNetworkRules(instance, service, false);
}
// TODO check if any stale management network rules are
defaultConnectionRules(instance, service, false);
}
protected VtnService getVtnService(VtnServiceId serviceId) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
// TODO remove openstack access when XOS provides all information
VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
VtnService service = serviceApi.service(serviceId, osAccess);
if (service == null) {
log.warn("Failed to get VtnService for {}", serviceId);
}
return service;
}
protected Set<Instance> getInstances(VtnServiceId serviceId) {
return StreamSupport.stream(hostService.getHosts().spliterator(), false)
.filter(host -> Objects.equals(
serviceId.id(),
host.annotations().value(Instance.SERVICE_ID)))
.map(Instance::of)
.collect(Collectors.toSet());
}
private void defaultConnectionRules(Instance instance, VtnService service, boolean install) {
long vni = service.vni();
Ip4Prefix serviceIpRange = service.subnet().getIp4Prefix();
inPortRule(instance, install);
dstIpRule(instance, vni, install);
tunnelInRule(instance, vni, install);
if (install) {
directAccessRule(serviceIpRange, serviceIpRange, true);
serviceIsolationRule(serviceIpRange, true);
} else if (getInstances(service.id()).isEmpty()) {
directAccessRule(serviceIpRange, serviceIpRange, false);
serviceIsolationRule(serviceIpRange, false);
}
}
private void managementNetworkRules(Instance instance, VtnService service, boolean install) {
managementPerInstanceRule(instance, install);
if (install) {
managementBaseRule(instance, service, true);
} else if (!hostService.getConnectedHosts(instance.deviceId()).stream()
.filter(host -> Instance.of(host).serviceId().equals(service.id()))
.findAny()
.isPresent()) {
managementBaseRule(instance, service, false);
}
}
private void managementBaseRule(Instance instance, VtnService service, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(service.serviceIp().getIp4Address())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_MANAGEMENT)
.forDevice(instance.deviceId())
.forTable(TABLE_ZERO)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(service.subnet())
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_MANAGEMENT)
.forDevice(instance.deviceId())
.forTable(TABLE_ZERO)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(service.serviceIp().toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_MANAGEMENT)
.forDevice(instance.deviceId())
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
}
private void managementPerInstanceRule(Instance instance, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(instance.ipAddress().getIp4Address())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(instance.portNumber())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_MANAGEMENT)
.forDevice(instance.deviceId())
.forTable(TABLE_ZERO)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
}
private void inPortRule(Instance instance, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(instance.portNumber())
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(instance.ipAddress().toIpPrefix())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_ACCESS_TYPE)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(instance.deviceId())
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(instance.portNumber())
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_IN_SERVICE)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_LOW)
.forDevice(instance.deviceId())
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
}
private void dstIpRule(Instance instance, long vni, boolean install) {
Ip4Address tunnelIp = nodeManager.dpIp(instance.deviceId()).getIp4Address();
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(instance.ipAddress().toIpPrefix())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setEthDst(instance.mac())
.setOutput(instance.portNumber())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(instance.deviceId())
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
for (CordVtnNode node : nodeManager.completeNodes()) {
if (node.intBrId().equals(instance.deviceId())) {
continue;
}
ExtensionTreatment tunnelDst = pipeline.tunnelDstTreatment(node.intBrId(), tunnelIp);
if (tunnelDst == null) {
continue;
}
treatment = DefaultTrafficTreatment.builder()
.setEthDst(instance.mac())
.setTunnelId(vni)
.extension(tunnelDst, node.intBrId())
.setOutput(nodeManager.tunnelPort(node.intBrId()))
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(node.intBrId())
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
}
}
private void tunnelInRule(Instance instance, long vni, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchTunnelId(vni)
.matchEthDst(instance.mac())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(instance.portNumber())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(instance.deviceId())
.forTable(TABLE_TUNNEL_IN)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
}
private void directAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcRange)
.matchIPDst(dstRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
nodeManager.completeNodes().stream().forEach(node -> {
FlowRule flowRuleDirect = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(node.intBrId())
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRuleDirect);
});
}
private void serviceIsolationRule(Ip4Prefix dstRange, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dstRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.drop()
.build();
nodeManager.completeNodes().stream().forEach(node -> {
FlowRule flowRuleDirect = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_LOW)
.forDevice(node.intBrId())
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRuleDirect);
});
}
protected void readConfiguration() {
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found");
return;
}
osAccess = config.openstackAccess();
xosAccess = config.xosAccess();
}
public 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;
}
Instance instance = Instance.of(host);
if (!Objects.equals(instance.serviceType(), serviceType)) {
// not my service instance, do nothing
return;
}
switch (event.type()) {
case HOST_UPDATED:
case HOST_ADDED:
eventExecutor.execute(() -> instanceDetected(instance));
break;
case HOST_REMOVED:
eventExecutor.execute(() -> instanceRemoved(instance));
break;
default:
break;
}
}
}
public class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (!event.configClass().equals(CordVtnConfig.class)) {
return;
}
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
readConfiguration();
break;
default:
break;
}
}
}
}
/*
* Copyright 2015-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.cordvtn.impl;
import com.google.common.collect.Lists;
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.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.cordvtn.api.CordVtnConfig;
import org.onosproject.cordvtn.api.CordVtnService;
import org.onosproject.cordvtn.api.Instance;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.dhcp.DhcpService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
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.basics.SubjectFactories;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.host.DefaultHostDescription;
import org.onosproject.net.host.HostDescription;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
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.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.AbstractProvider;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.xosclient.api.OpenStackAccess;
import org.onosproject.xosclient.api.VtnPort;
import org.onosproject.xosclient.api.VtnPortApi;
import org.onosproject.xosclient.api.VtnService;
import org.onosproject.xosclient.api.VtnServiceApi;
import org.onosproject.xosclient.api.VtnServiceId;
import org.onosproject.xosclient.api.XosAccess;
import org.onosproject.xosclient.api.XosClientService;
import org.slf4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cordvtn.api.Instance.*;
import static org.onosproject.xosclient.api.VtnService.NetworkType.PRIVATE;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Adds or removes instances to network services.
*/
@Component(immediate = true)
@Service(value = CordVtnInstanceManager.class)
public class CordVtnInstanceManager extends AbstractProvider implements HostProvider {
protected final Logger log = getLogger(getClass());
private static final String XOS_ACCESS_ERROR = "XOS access is not configured";
private static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostProviderRegistry hostProviderRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DhcpService dhcpService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected XosClientService xosClient;
private final ConfigFactory configFactory =
new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
@Override
public CordVtnConfig createConfig() {
return new CordVtnConfig();
}
};
private final ExecutorService eventExecutor =
newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-instance", "event-handler"));
private final PacketProcessor packetProcessor = new InternalPacketProcessor();
private final HostListener hostListener = new InternalHostListener();
private final NetworkConfigListener configListener = new InternalConfigListener();
private ApplicationId appId;
private HostProviderService hostProvider;
private CordVtnArpProxy arpProxy; // TODO make it a component service
private MacAddress privateGatewayMac = MacAddress.NONE;
private XosAccess xosAccess = null;
private OpenStackAccess osAccess = null;
/**
* Creates an cordvtn host location provider.
*/
public CordVtnInstanceManager() {
super(new ProviderId("host", CordVtnService.CORDVTN_APP_ID));
}
@Activate
protected void activate() {
appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
arpProxy = new CordVtnArpProxy(appId, packetService, hostService);
packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
arpProxy.requestPacket();
hostService.addListener(hostListener);
hostProvider = hostProviderRegistry.register(this);
configRegistry.registerConfigFactory(configFactory);
configRegistry.addListener(configListener);
log.info("Started");
}
@Deactivate
protected void deactivate() {
hostProviderRegistry.unregister(this);
hostService.removeListener(hostListener);
packetService.removeProcessor(packetProcessor);
configRegistry.unregisterConfigFactory(configFactory);
configRegistry.removeListener(configListener);
eventExecutor.shutdown();
log.info("Stopped");
}
@Override
public void triggerProbe(Host host) {
/*
* Note: In CORD deployment, we assume that all hosts are configured.
* Therefore no probe is required.
*/
}
/**
* Adds a service instance at a given connect point.
*
* @param connectPoint connect point of the instance
*/
public void addInstance(ConnectPoint connectPoint) {
Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
if (port == null) {
log.debug("No port found from {}", connectPoint);
return;
}
VtnPort vtnPort = getVtnPort(port.annotations().value("portName"));
if (vtnPort == null) {
return;
}
VtnService vtnService = getVtnService(vtnPort.serviceId());
if (vtnService == null) {
return;
}
// Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
// existing instances.
DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
.set(SERVICE_TYPE, vtnService.serviceType().toString())
.set(SERVICE_ID, vtnPort.serviceId().id())
.set(PORT_ID, vtnPort.id().id())
.set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
HostDescription hostDesc = new DefaultHostDescription(
vtnPort.mac(),
VlanId.NONE,
new HostLocation(connectPoint, System.currentTimeMillis()),
Sets.newHashSet(vtnPort.ip()),
annotations.build());
HostId hostId = HostId.hostId(vtnPort.mac());
hostProvider.hostDetected(hostId, hostDesc, false);
}
/**
* Adds a service instance with given host ID and host description.
*
* @param hostId host id
* @param description host description
*/
public void addInstance(HostId hostId, HostDescription description) {
hostProvider.hostDetected(hostId, description, false);
}
/**
* Removes a service instance from a given connect point.
*
* @param connectPoint connect point
*/
public void removeInstance(ConnectPoint connectPoint) {
hostService.getConnectedHosts(connectPoint)
.stream()
.forEach(host -> hostProvider.hostVanished(host.id()));
}
/**
* Removes service instance with given host ID.
*
* @param hostId host id
*/
public void removeInstance(HostId hostId) {
hostProvider.hostVanished(hostId);
}
private void instanceDetected(Instance instance) {
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
return;
}
if (service.networkType().equals(PRIVATE)) {
arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(instance));
}
if (!instance.isNestedInstance()) {
registerDhcpLease(instance, service);
}
}
private void instanceRemoved(Instance instance) {
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
return;
}
if (service.networkType().equals(PRIVATE) && getInstances(service.id()).isEmpty()) {
arpProxy.removeGateway(service.serviceIp());
}
if (!instance.isNestedInstance()) {
dhcpService.removeStaticMapping(instance.mac());
}
}
private void registerDhcpLease(Instance instance, VtnService service) {
List<Ip4Address> options = Lists.newArrayList();
options.add(Ip4Address.makeMaskPrefix(service.subnet().prefixLength()));
options.add(service.serviceIp().getIp4Address());
options.add(service.serviceIp().getIp4Address());
options.add(DEFAULT_DNS);
log.debug("Set static DHCP mapping for {} {}", instance.mac(), instance.ipAddress());
dhcpService.setStaticMapping(instance.mac(),
instance.ipAddress(),
true,
options);
}
private VtnService getVtnService(VtnServiceId serviceId) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
// TODO remove openstack access when XOS provides all information
VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
VtnService service = serviceApi.service(serviceId, osAccess);
if (service == null) {
log.warn("Failed to get VtnService for {}", serviceId);
}
return service;
}
private VtnPort getVtnPort(String portName) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
// TODO remove openstack access when XOS provides all information
VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
VtnPort vtnPort = portApi.vtnPort(portName, osAccess);
if (vtnPort == null) {
log.warn("Failed to get port information of {}", portName);
}
return vtnPort;
}
private Set<Instance> getInstances(VtnServiceId serviceId) {
return StreamSupport.stream(hostService.getHosts().spliterator(), false)
.filter(host -> Objects.equals(
serviceId.id(),
host.annotations().value(Instance.SERVICE_ID)))
.map(Instance::of)
.collect(Collectors.toSet());
}
private void readConfiguration() {
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found");
return;
}
log.info("Load CORD-VTN configurations");
xosAccess = config.xosAccess();
osAccess = config.openstackAccess();
privateGatewayMac = config.privateGatewayMac();
Map<IpAddress, MacAddress> publicGateways = config.publicGateways();
publicGateways.entrySet()
.stream()
.forEach(entry -> {
arpProxy.addGateway(entry.getKey(), entry.getValue());
log.debug("Added public gateway IP {}, MAC {}",
entry.getKey(), entry.getValue());
});
// TODO notice gateway MAC change to VMs holds this gateway IP
}
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;
}
Instance instance = Instance.of(host);
switch (event.type()) {
case HOST_UPDATED:
case HOST_ADDED:
eventExecutor.execute(() -> instanceDetected(instance));
break;
case HOST_REMOVED:
eventExecutor.execute(() -> instanceRemoved(instance));
break;
default:
break;
}
}
}
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;
}
arpProxy.processArpPacket(context, ethPacket);
}
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (!event.configClass().equals(CordVtnConfig.class)) {
return;
}
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
readConfiguration();
break;
default:
break;
}
}
}
}
......@@ -44,6 +44,7 @@ import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.BridgeConfig;
import org.onosproject.net.behaviour.BridgeName;
import org.onosproject.net.behaviour.ControllerInfo;
......@@ -59,8 +60,6 @@ import org.onosproject.net.device.DeviceAdminService;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.group.GroupService;
import org.onosproject.net.host.HostService;
import org.onosproject.ovsdb.controller.OvsdbClientService;
import org.onosproject.ovsdb.controller.OvsdbController;
......@@ -86,6 +85,7 @@ import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cordvtn.impl.CordVtnPipeline.DEFAULT_TUNNEL;
import static org.onosproject.cordvtn.impl.RemoteIpCommandUtil.*;
import static org.onosproject.net.Device.Type.SWITCH;
import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
......@@ -110,7 +110,6 @@ public class CordVtnNodeManager {
.register(NetworkAddress.class);
private static final String DEFAULT_BRIDGE = "br-int";
private static final String DEFAULT_TUNNEL = "vxlan";
private static final String VPORT_PREFIX = "tap";
private static final String OK = "OK";
private static final String NO = "NO";
......@@ -152,19 +151,16 @@ public class CordVtnNodeManager {
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LeadershipService leadershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected GroupService groupService;
protected CordVtnInstanceManager instanceManager;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CordVtnService cordVtnService;
protected CordVtnPipeline pipeline;
private final ExecutorService eventExecutor =
newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtncfg", "event-handler"));
newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-node", "event-handler"));
private final NetworkConfigListener configListener = new InternalConfigListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
......@@ -174,7 +170,6 @@ public class CordVtnNodeManager {
private final BridgeHandler bridgeHandler = new BridgeHandler();
private ConsistentMap<String, CordVtnNode> nodeStore;
private CordVtnRuleInstaller ruleInstaller;
private ApplicationId appId;
private NodeId localNodeId;
......@@ -225,6 +220,7 @@ public class CordVtnNodeManager {
@Activate
protected void activate() {
appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
localNodeId = clusterService.getLocalNode().id();
leadershipService.runForLeadership(appId.name());
......@@ -234,13 +230,6 @@ public class CordVtnNodeManager {
.withApplicationId(appId)
.build();
ruleInstaller = new CordVtnRuleInstaller(appId, flowRuleService,
deviceService,
groupService,
hostService,
configRegistry,
DEFAULT_TUNNEL);
nodeStore.addListener(nodeStoreListener);
deviceService.addListener(deviceListener);
configService.addListener(configListener);
......@@ -286,20 +275,6 @@ public class CordVtnNodeManager {
}
/**
* Initiates node to serve virtual tenant network.
*
* @param node cordvtn node
*/
private void initNode(CordVtnNode node) {
checkNotNull(node);
NodeState state = (NodeState) node.state();
log.debug("Processing node: {} state: {}", node.hostname(), state);
state.process(this, node);
}
/**
* Returns node initialization state.
*
* @param node cordvtn node
......@@ -311,30 +286,6 @@ public class CordVtnNodeManager {
}
/**
* Flush flows installed by cordvtn.
*/
public void flushRules() {
ruleInstaller.flushRules();
}
/**
* Returns if current node state saved in nodeStore is COMPLETE or not.
*
* @param node cordvtn node
* @return true if it's complete state, otherwise false
*/
private boolean isNodeStateComplete(CordVtnNode node) {
checkNotNull(node);
// the state saved in nodeStore can be wrong if IP address settings are changed
// after the node init has been completed since there's no way to detect it
// getNodeState and checkNodeInitState always return correct answer but can be slow
Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
CordVtnNodeState state = versionedNode.value().state();
return state != null && state.equals(NodeState.COMPLETE);
}
/**
* Returns detailed node initialization state.
*
* @param node cordvtn node
......@@ -392,67 +343,96 @@ public class CordVtnNodeManager {
* @return list of nodes
*/
public List<CordVtnNode> getNodes() {
return nodeStore.values().stream()
.map(Versioned::value)
.collect(Collectors.toList());
return nodeStore.values().stream().map(Versioned::value).collect(Collectors.toList());
}
/**
* Returns cordvtn node associated with a given OVSDB device.
* Returns all nodes in complete state.
*
* @param ovsdbId OVSDB device id
* @return cordvtn node, null if it fails to find the node
* @return set of nodes
*/
private CordVtnNode getNodeByOvsdbId(DeviceId ovsdbId) {
return getNodes().stream()
.filter(node -> node.ovsdbId().equals(ovsdbId))
public Set<CordVtnNode> completeNodes() {
return getNodes().stream().filter(this::isNodeInitComplete).collect(Collectors.toSet());
}
/**
* Returns physical data plane port number of a given device.
*
* @param deviceId integration bridge device id
* @return port number; null otherwise
*/
public PortNumber dpPort(DeviceId deviceId) {
CordVtnNode node = nodeByBridgeId(deviceId);
if (node == null) {
log.warn("Failed to get node for {}", deviceId);
return null;
}
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> portName(p).contains(node.dpIntf()) &&
p.isEnabled())
.findFirst().orElse(null);
return port == null ? null : port.number();
}
/**
* Returns cordvtn node associated with a given integration bridge.
* Returns physical data plane IP address of a given device.
*
* @param bridgeId device id of integration bridge
* @return cordvtn node, null if it fails to find the node
* @param deviceId integration bridge device id
* @return ip address; null otherwise
*/
private CordVtnNode getNodeByBridgeId(DeviceId bridgeId) {
return getNodes().stream()
.filter(node -> node.intBrId().equals(bridgeId))
public IpAddress dpIp(DeviceId deviceId) {
CordVtnNode node = nodeByBridgeId(deviceId);
if (node == null) {
log.warn("Failed to get node for {}", deviceId);
return null;
}
return node.dpIp().ip();
}
/**
* Returns tunnel port number of a given device.
*
* @param deviceId integration bridge device id
* @return port number
*/
public PortNumber tunnelPort(DeviceId deviceId) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> portName(p).contains(DEFAULT_TUNNEL))
.findFirst().orElse(null);
return port == null ? null : port.number();
}
/**
* Sets a new state for a given cordvtn node.
* Returns if current node state saved in nodeStore is COMPLETE or not.
*
* @param node cordvtn node
* @param newState new node state
* @return true if it's complete state, otherwise false
*/
private void setNodeState(CordVtnNode node, NodeState newState) {
private boolean isNodeStateComplete(CordVtnNode node) {
checkNotNull(node);
log.debug("Changed {} state: {}", node.hostname(), newState);
nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, newState));
// the state saved in nodeStore can be wrong if IP address settings are changed
// after the node init has been completed since there's no way to detect it
// getNodeState and checkNodeInitState always return correct answer but can be slow
Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
CordVtnNodeState state = versionedNode.value().state();
return state != null && state.equals(NodeState.COMPLETE);
}
/**
* Checks current state of a given cordvtn node and returns it.
* Initiates node to serve virtual tenant network.
*
* @param node cordvtn node
* @return node state
*/
private NodeState getNodeState(CordVtnNode node) {
private void initNode(CordVtnNode node) {
checkNotNull(node);
if (isBrIntCreated(node) && isTunnelIntfCreated(node) &&
isDataPlaneIntfAdded(node) && isIpAddressSet(node)) {
return NodeState.COMPLETE;
} else if (isDataPlaneIntfAdded(node) && isTunnelIntfCreated(node)) {
return NodeState.PORTS_ADDED;
} else if (isBrIntCreated(node)) {
return NodeState.BRIDGE_CREATED;
} else {
return NodeState.INIT;
}
NodeState state = (NodeState) node.state();
log.debug("Processing node: {} state: {}", node.hostname(), state);
state.process(this, node);
}
/**
......@@ -464,20 +444,17 @@ public class CordVtnNodeManager {
*/
private void postInit(CordVtnNode node) {
disconnectOvsdb(node);
pipeline.initPipeline(node, dpPort(node.intBrId()), tunnelPort(node.intBrId()));
ruleInstaller.init(node.intBrId(), node.dpIntf(), node.dpIp().ip());
// add existing hosts to the service
deviceService.getPorts(node.intBrId()).stream()
.filter(port -> getPortName(port).startsWith(VPORT_PREFIX) &&
.filter(port -> portName(port).startsWith(VPORT_PREFIX) &&
port.isEnabled())
.forEach(port -> cordVtnService.addServiceVm(node, getConnectPoint(port)));
.forEach(port -> instanceManager.addInstance(connectPoint(port)));
// remove stale hosts from the service
hostService.getHosts().forEach(host -> {
Port port = deviceService.getPort(host.location().deviceId(), host.location().port());
if (port == null) {
cordVtnService.removeServiceVm(getConnectPoint(host));
if (deviceService.getPort(host.location().deviceId(),
host.location().port()) == null) {
instanceManager.removeInstance(connectPoint(host));
}
});
......@@ -485,13 +462,37 @@ public class CordVtnNodeManager {
}
/**
* Returns port name.
* Sets a new state for a given cordvtn node.
*
* @param port port
* @return port name
* @param node cordvtn node
* @param newState new node state
*/
private String getPortName(Port port) {
return port.annotations().value("portName");
private void setNodeState(CordVtnNode node, NodeState newState) {
checkNotNull(node);
log.debug("Changed {} state: {}", node.hostname(), newState);
nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, newState));
}
/**
* Checks current state of a given cordvtn node and returns it.
*
* @param node cordvtn node
* @return node state
*/
private NodeState getNodeState(CordVtnNode node) {
checkNotNull(node);
if (isBrIntCreated(node) && isTunnelIntfCreated(node) &&
isDataPlaneIntfAdded(node) && isIpAddressSet(node)) {
return NodeState.COMPLETE;
} else if (isDataPlaneIntfAdded(node) && isTunnelIntfCreated(node)) {
return NodeState.PORTS_ADDED;
} else if (isBrIntCreated(node)) {
return NodeState.BRIDGE_CREATED;
} else {
return NodeState.INIT;
}
}
/**
......@@ -587,7 +588,7 @@ public class CordVtnNodeManager {
BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
} else {
log.warn("The bridging behaviour is not supported in device {}", device.id().toString());
log.warn("The bridging behaviour is not supported in device {}", device.id());
}
} catch (ItemNotFoundException e) {
log.warn("Failed to create integration bridge on {}", node.hostname());
......@@ -619,7 +620,7 @@ public class CordVtnNodeManager {
TunnelConfig tunnelConfig = device.as(TunnelConfig.class);
tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
} else {
log.warn("The tunneling behaviour is not supported in device {}", device.id().toString());
log.warn("The tunneling behaviour is not supported in device {}", device.id());
}
} catch (ItemNotFoundException e) {
log.warn("Failed to create tunnel interface on {}", node.hostname());
......@@ -654,7 +655,7 @@ public class CordVtnNodeManager {
BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
bridgeConfig.addPort(BridgeName.bridgeName(DEFAULT_BRIDGE), node.dpIntf());
} else {
log.warn("The bridging behaviour is not supported in device {}", device.id().toString());
log.warn("The bridging behaviour is not supported in device {}", device.id());
}
} catch (ItemNotFoundException e) {
log.warn("Failed to add {} on {}", node.dpIntf(), node.hostname());
......@@ -712,7 +713,7 @@ public class CordVtnNodeManager {
private boolean isTunnelIntfCreated(CordVtnNode node) {
return deviceService.getPorts(node.intBrId())
.stream()
.filter(p -> getPortName(p).contains(DEFAULT_TUNNEL) &&
.filter(p -> portName(p).contains(DEFAULT_TUNNEL) &&
p.isEnabled())
.findAny().isPresent();
}
......@@ -726,7 +727,7 @@ public class CordVtnNodeManager {
private boolean isDataPlaneIntfAdded(CordVtnNode node) {
return deviceService.getPorts(node.intBrId())
.stream()
.filter(p -> getPortName(p).contains(node.dpIntf()) &&
.filter(p -> portName(p).contains(node.dpIntf()) &&
p.isEnabled())
.findAny().isPresent();
}
......@@ -761,7 +762,7 @@ public class CordVtnNodeManager {
* @param port port
* @return connect point
*/
private ConnectPoint getConnectPoint(Port port) {
private ConnectPoint connectPoint(Port port) {
return new ConnectPoint(port.element().id(), port.number());
}
......@@ -771,15 +772,49 @@ public class CordVtnNodeManager {
* @param host host
* @return connect point
*/
private ConnectPoint getConnectPoint(Host host) {
private ConnectPoint connectPoint(Host host) {
return new ConnectPoint(host.location().deviceId(), host.location().port());
}
/**
* Returns cordvtn node associated with a given OVSDB device.
*
* @param ovsdbId OVSDB device id
* @return cordvtn node, null if it fails to find the node
*/
private CordVtnNode nodeByOvsdbId(DeviceId ovsdbId) {
return getNodes().stream()
.filter(node -> node.ovsdbId().equals(ovsdbId))
.findFirst().orElse(null);
}
/**
* Returns cordvtn node associated with a given integration bridge.
*
* @param bridgeId device id of integration bridge
* @return cordvtn node, null if it fails to find the node
*/
private CordVtnNode nodeByBridgeId(DeviceId bridgeId) {
return getNodes().stream()
.filter(node -> node.intBrId().equals(bridgeId))
.findFirst().orElse(null);
}
/**
* Returns port name.
*
* @param port port
* @return port name
*/
private String portName(Port port) {
return port.annotations().value("portName");
}
private class OvsdbHandler implements ConnectionHandler<Device> {
@Override
public void connected(Device device) {
CordVtnNode node = getNodeByOvsdbId(device.id());
CordVtnNode node = nodeByOvsdbId(device.id());
if (node != null) {
setNodeState(node, getNodeState(node));
} else {
......@@ -800,7 +835,7 @@ public class CordVtnNodeManager {
@Override
public void connected(Device device) {
CordVtnNode node = getNodeByBridgeId(device.id());
CordVtnNode node = nodeByBridgeId(device.id());
if (node != null) {
setNodeState(node, getNodeState(node));
} else {
......@@ -810,7 +845,7 @@ public class CordVtnNodeManager {
@Override
public void disconnected(Device device) {
CordVtnNode node = getNodeByBridgeId(device.id());
CordVtnNode node = nodeByBridgeId(device.id());
if (node != null) {
log.debug("Integration Bridge is disconnected from {}", node.hostname());
setNodeState(node, NodeState.INCOMPLETE);
......@@ -825,8 +860,8 @@ public class CordVtnNodeManager {
* @param port port
*/
public void portAdded(Port port) {
CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
String portName = getPortName(port);
CordVtnNode node = nodeByBridgeId((DeviceId) port.element().id());
String portName = portName(port);
if (node == null) {
log.debug("{} is added to unregistered node, ignore it.", portName);
......@@ -837,7 +872,7 @@ public class CordVtnNodeManager {
if (portName.startsWith(VPORT_PREFIX)) {
if (isNodeStateComplete(node)) {
cordVtnService.addServiceVm(node, getConnectPoint(port));
instanceManager.addInstance(connectPoint(port));
} else {
log.debug("VM is detected on incomplete node, ignore it.", portName);
}
......@@ -854,8 +889,8 @@ public class CordVtnNodeManager {
* @param port port
*/
public void portRemoved(Port port) {
CordVtnNode node = getNodeByBridgeId((DeviceId) port.element().id());
String portName = getPortName(port);
CordVtnNode node = nodeByBridgeId((DeviceId) port.element().id());
String portName = portName(port);
if (node == null) {
return;
......@@ -865,7 +900,7 @@ public class CordVtnNodeManager {
if (portName.startsWith(VPORT_PREFIX)) {
if (isNodeStateComplete(node)) {
cordVtnService.removeServiceVm(getConnectPoint(port));
instanceManager.removeInstance(connectPoint(port));
} else {
log.debug("VM is vanished from incomplete node, ignore it.", portName);
}
......@@ -922,7 +957,6 @@ public class CordVtnNodeManager {
log.debug("No configuration found");
return;
}
config.cordVtnNodes().forEach(this::addOrUpdateNode);
}
......
/*
* 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.cordvtn.impl;
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.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.VlanId;
import org.onlab.util.ItemNotFoundException;
import org.onosproject.cordvtn.api.CordVtnNode;
import org.onosproject.cordvtn.api.CordVtnService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleOperationsContext;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.ExtensionPropertyException;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.slf4j.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provides CORD VTN pipeline.
*/
@Component(immediate = true)
@Service(value = CordVtnPipeline.class)
public final class CordVtnPipeline {
protected final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
// tables
public static final int TABLE_ZERO = 0;
public static final int TABLE_IN_PORT = 1;
public static final int TABLE_ACCESS_TYPE = 2;
public static final int TABLE_IN_SERVICE = 3;
public static final int TABLE_DST_IP = 4;
public static final int TABLE_TUNNEL_IN = 5;
public static final int TABLE_VLAN = 6;
// priorities
public static final int PRIORITY_MANAGEMENT = 55000;
public static final int PRIORITY_HIGH = 50000;
public static final int PRIORITY_DEFAULT = 5000;
public static final int PRIORITY_LOW = 4000;
public static final int PRIORITY_ZERO = 0;
public static final int VXLAN_UDP_PORT = 4789;
public static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
public static final String DEFAULT_TUNNEL = "vxlan";
private static final String PORT_NAME = "portName";
private ApplicationId appId;
@Activate
protected void activate() {
appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
log.info("Started");
}
@Deactivate
protected void deactivate() {
log.info("Stopped");
}
/**
* Flush flows installed by this application.
*/
public void flushRules() {
flowRuleService.getFlowRulesById(appId).forEach(flowRule -> processFlowRule(false, flowRule));
}
/**
* Installs table miss rule to a give device.
*
* @param node cordvtn node
* @param dpPort data plane port number
* @param tunnelPort tunnel port number
*/
public void initPipeline(CordVtnNode node, PortNumber dpPort, PortNumber tunnelPort) {
checkNotNull(node);
processTableZero(node.intBrId(), dpPort, node.dpIp().ip());
processInPortTable(node.intBrId(), tunnelPort, dpPort);
processAccessTypeTable(node.intBrId(), dpPort);
processVlanTable(node.intBrId(), dpPort);
}
private void processTableZero(DeviceId deviceId, PortNumber dpPort, IpAddress dpIp) {
// take vxlan packet out onto the physical port
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a vxlan encap'd packet through the Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_UDP)
.matchUdpDst(TpPort.tpPort(VXLAN_UDP_PORT))
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a packet to the data plane ip through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dpIp.toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take an arp packet from physical through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(dpIp.getIp4Address())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take all else to the next table
selector = DefaultTrafficSelector.builder()
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_IN_PORT)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_ZERO)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take all vlan tagged packet to the VLAN table
selector = DefaultTrafficSelector.builder()
.matchVlanId(VlanId.ANY)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_VLAN)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_MANAGEMENT)
.forDevice(deviceId)
.forTable(TABLE_ZERO)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dpPort) {
checkNotNull(tunnelPort);
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(tunnelPort)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_TUNNEL_IN)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
private void processAccessTypeTable(DeviceId deviceId, PortNumber dpPort) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_ZERO)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
private void processVlanTable(DeviceId deviceId, PortNumber dpPort) {
// for traffic going out to WAN, strip vid 500 and take through data plane interface
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchVlanId(VLAN_WAN)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.popVlan()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_VLAN)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchVlanId(VLAN_WAN)
.matchEthType(Ethernet.TYPE_ARP)
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.CONTROLLER)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_HIGH)
.forDevice(deviceId)
.forTable(TABLE_VLAN)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
public void processFlowRule(boolean install, FlowRule rule) {
FlowRuleOperations.Builder oBuilder = FlowRuleOperations.builder();
oBuilder = install ? oBuilder.add(rule) : oBuilder.remove(rule);
flowRuleService.apply(oBuilder.build(new FlowRuleOperationsContext() {
@Override
public void onError(FlowRuleOperations ops) {
log.error(String.format("Failed %s, %s", ops.toString(), rule.toString()));
}
}));
}
public ExtensionTreatment tunnelDstTreatment(DeviceId deviceId, Ip4Address remoteIp) {
try {
Device device = deviceService.getDevice(deviceId);
if (device.is(ExtensionTreatmentResolver.class)) {
ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
ExtensionTreatment treatment =
resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
treatment.setPropertyValue("tunnelDst", remoteIp);
return treatment;
} else {
log.warn("The extension treatment resolving behaviour is not supported in device {}",
device.id().toString());
return null;
}
} catch (ItemNotFoundException | UnsupportedOperationException |
ExtensionPropertyException e) {
log.error("Failed to get extension instruction {}", deviceId);
return null;
}
}
}
/*
* Copyright 2015-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.cordvtn.impl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
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.MacAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.VlanId;
import org.onlab.util.ItemNotFoundException;
import org.onosproject.cordvtn.api.CordVtnConfig;
import org.onosproject.cordvtn.api.CordVtnNode;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.DefaultGroupId;
import org.onosproject.core.GroupId;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleOperationsContext;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.flow.instructions.ExtensionPropertyException;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.group.DefaultGroupBucket;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.DefaultGroupKey;
import org.onosproject.net.group.Group;
import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupBuckets;
import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupKey;
import org.onosproject.net.group.GroupService;
import org.onosproject.net.host.HostService;
import org.onosproject.xosclient.api.VtnService;
import org.onosproject.xosclient.api.VtnServiceId;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Populates rules for CORD VTN service.
*/
public class CordVtnRuleInstaller {
protected final Logger log = getLogger(getClass());
private static final int TABLE_FIRST = 0;
private static final int TABLE_IN_PORT = 1;
private static final int TABLE_ACCESS_TYPE = 2;
private static final int TABLE_IN_SERVICE = 3;
private static final int TABLE_DST_IP = 4;
private static final int TABLE_TUNNEL_IN = 5;
private static final int TABLE_Q_IN_Q = 6;
private static final int MANAGEMENT_PRIORITY = 55000;
private static final int VSG_PRIORITY = 55000;
private static final int HIGH_PRIORITY = 50000;
private static final int DEFAULT_PRIORITY = 5000;
private static final int LOW_PRIORITY = 4000;
private static final int LOWEST_PRIORITY = 0;
private static final int VXLAN_UDP_PORT = 4789;
private static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
private static final String PORT_NAME = "portName";
private static final String DATA_PLANE_INTF = "dataPlaneIntf";
private static final String DATA_PLANE_IP = "dataPlaneIp";
private static final String S_TAG = "stag";
private static final String SERVICE_ID = "serviceId";
private final ApplicationId appId;
private final FlowRuleService flowRuleService;
private final DeviceService deviceService;
private final GroupService groupService;
private final HostService hostService;
private final NetworkConfigRegistry configRegistry;
private final String tunnelType;
/**
* Creates a new rule populator.
*
* @param appId application id
* @param flowRuleService flow rule service
* @param deviceService device service
* @param groupService group service
* @param configRegistry config registry
* @param tunnelType tunnel type
*/
public CordVtnRuleInstaller(ApplicationId appId,
FlowRuleService flowRuleService,
DeviceService deviceService,
GroupService groupService,
HostService hostService,
NetworkConfigRegistry configRegistry,
String tunnelType) {
this.appId = appId;
this.flowRuleService = flowRuleService;
this.deviceService = deviceService;
this.groupService = groupService;
this.hostService = hostService;
this.configRegistry = configRegistry;
this.tunnelType = checkNotNull(tunnelType);
}
/**
* Installs table miss rule to a give device.
*
* @param deviceId device id to install the rules
* @param dpIntf data plane interface name
* @param dpIp data plane ip address
*/
public void init(DeviceId deviceId, String dpIntf, IpAddress dpIp) {
// default is drop packets which can be accomplished without
// a table miss entry for all table.
PortNumber tunnelPort = getTunnelPort(deviceId);
PortNumber dpPort = getDpPort(deviceId, dpIntf);
processFirstTable(deviceId, dpPort, dpIp);
processInPortTable(deviceId, tunnelPort, dpPort);
processAccessTypeTable(deviceId, dpPort);
processQInQTable(deviceId, dpPort);
}
/**
* Flush flows installed by this application.
*/
public void flushRules() {
flowRuleService.getFlowRulesById(appId).forEach(flowRule -> processFlowRule(false, flowRule));
}
/**
* Populates basic rules that connect a VM to the other VMs in the system.
*
* @param host host
* @param service cord service
* @param install true to install or false to remove
*/
public void populateBasicConnectionRules(Host host, VtnService service, boolean install) {
checkNotNull(host);
checkNotNull(service);
DeviceId deviceId = host.location().deviceId();
PortNumber inPort = host.location().port();
MacAddress dstMac = host.mac();
IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
long tunnelId = service.vni();
Ip4Prefix serviceIpRange = service.subnet().getIp4Prefix();
populateLocalInPortRule(deviceId, inPort, hostIp, install);
populateDstIpRule(deviceId, inPort, dstMac, hostIp, tunnelId, getTunnelIp(host), install);
populateTunnelInRule(deviceId, inPort, dstMac, tunnelId, install);
if (install) {
populateDirectAccessRule(serviceIpRange, serviceIpRange, true);
populateServiceIsolationRule(serviceIpRange, true);
} else if (getInstances(service.id()).isEmpty()) {
// removes network related rules only if there's no hosts left in this network
populateDirectAccessRule(serviceIpRange, serviceIpRange, false);
populateServiceIsolationRule(serviceIpRange, false);
}
}
/**
* Creates provider service group and populates service dependency rules.
*
* @param tService tenant cord service
* @param pService provider cord service
* @param isBidirectional true to enable bidirectional connection between two services
* @param install true to install or false to remove
*/
public void populateServiceDependencyRules(VtnService tService, VtnService pService,
boolean isBidirectional, boolean install) {
checkNotNull(tService);
checkNotNull(pService);
Ip4Prefix srcRange = tService.subnet().getIp4Prefix();
Ip4Prefix dstRange = pService.subnet().getIp4Prefix();
Ip4Address serviceIp = pService.serviceIp().getIp4Address();
Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
getVirtualSwitches().stream().forEach(deviceId -> {
GroupId groupId = createServiceGroup(deviceId, pService);
outGroups.put(deviceId, groupId);
Set<PortNumber> tServiceVms = getInstances(tService.id())
.stream()
.filter(host -> host.location().deviceId().equals(deviceId))
.map(host -> host.location().port())
.collect(Collectors.toSet());
inPorts.put(deviceId, tServiceVms);
});
populateIndirectAccessRule(srcRange, serviceIp, outGroups, install);
populateDirectAccessRule(srcRange, dstRange, install);
if (isBidirectional) {
populateDirectAccessRule(dstRange, srcRange, install);
}
populateInServiceRule(inPorts, outGroups, install);
}
/**
* Updates group buckets for a given service to all devices.
*
* @param service cord service
*/
public void updateProviderServiceGroup(VtnService service) {
checkNotNull(service);
GroupKey groupKey = getGroupKey(service.id());
for (DeviceId deviceId : getVirtualSwitches()) {
Group group = groupService.getGroup(deviceId, groupKey);
if (group == null) {
log.trace("No group exists for service {} in {}, do nothing.", service.id(), deviceId);
continue;
}
List<GroupBucket> oldBuckets = group.buckets().buckets();
List<GroupBucket> newBuckets = getServiceGroupBuckets(
deviceId, service.vni(), getInstances(service.id())).buckets();
if (oldBuckets.equals(newBuckets)) {
continue;
}
List<GroupBucket> bucketsToRemove = new ArrayList<>(oldBuckets);
bucketsToRemove.removeAll(newBuckets);
if (!bucketsToRemove.isEmpty()) {
groupService.removeBucketsFromGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToRemove),
groupKey, appId);
}
List<GroupBucket> bucketsToAdd = new ArrayList<>(newBuckets);
bucketsToAdd.removeAll(oldBuckets);
if (!bucketsToAdd.isEmpty()) {
groupService.addBucketsToGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToAdd),
groupKey, appId);
}
}
}
/**
* Updates tenant service indirect access rules when VM is created or removed.
*
* @param host removed vm
* @param service tenant service
*/
public void updateTenantServiceVm(Host host, VtnService service) {
checkNotNull(host);
checkNotNull(service);
DeviceId deviceId = host.location().deviceId();
PortNumber inPort = host.location().port();
service.providerServices().stream().forEach(pServiceId -> {
Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
inPorts.put(deviceId, Sets.newHashSet(inPort));
outGroups.put(deviceId, getGroupId(pServiceId, deviceId));
populateInServiceRule(inPorts, outGroups, false);
});
}
/**
* Populates flow rules for management network access.
*
* @param host host which has management network interface
* @param mService management network service
*/
public void populateManagementNetworkRules(Host host, VtnService mService) {
checkNotNull(mService);
DeviceId deviceId = host.location().deviceId();
IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(mService.serviceIp().getIp4Address())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(hostIp.getIp4Address())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(host.location().port())
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(mService.subnet())
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(mService.serviceIp().toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(MANAGEMENT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates rules for vSG VM.
*
* @param vSgHost vSG host
* @param vSgIps set of ip addresses of vSGs running inside the vSG VM
*/
public void populateSubscriberGatewayRules(Host vSgHost, Set<IpAddress> vSgIps) {
VlanId serviceVlan = getServiceVlan(vSgHost);
PortNumber dpPort = getDpPort(vSgHost);
if (serviceVlan == null || dpPort == null) {
log.warn("Failed to populate rules for vSG VM {}", vSgHost.id());
return;
}
// for traffics with s-tag, strip the tag and take through the vSG VM
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchVlanId(serviceVlan)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(vSgHost.location().port())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSgHost.location().deviceId())
.forTable(TABLE_Q_IN_Q)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// for traffics with customer vlan, tag with the service vlan based on input port with
// lower priority to avoid conflict with WAN tag
selector = DefaultTrafficSelector.builder()
.matchInPort(vSgHost.location().port())
.matchVlanId(serviceVlan)
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSgHost.location().deviceId())
.forTable(TABLE_Q_IN_Q)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// for traffic coming from WAN, tag 500 and take through the vSG VM
// based on destination ip
vSgIps.stream().forEach(ip -> {
TrafficSelector downstream = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(ip.toIpPrefix())
.build();
TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(VLAN_WAN)
.setEthDst(vSgHost.mac())
.setOutput(vSgHost.location().port())
.build();
FlowRule downstreamFlowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(downstream)
.withTreatment(downstreamTreatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSgHost.location().deviceId())
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
processFlowRule(true, downstreamFlowRule);
});
// remove downstream flow rules for the vSG not shown in vSgIps
for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
if (!rule.deviceId().equals(vSgHost.location().deviceId())) {
continue;
}
PortNumber output = getOutputFromTreatment(rule);
if (output == null || !output.equals(vSgHost.location().port()) ||
!isVlanPushFromTreatment(rule)) {
continue;
}
IpPrefix dstIp = getDstIpFromSelector(rule);
if (dstIp != null && !vSgIps.contains(dstIp.address())) {
processFlowRule(false, rule);
}
}
}
/**
* Populates default rules on the first table.
* It includes the rules for shuttling vxlan-encapped packets between ovs and
* linux stack,and external network connectivity.
*
* @param deviceId device id
* @param dpPort data plane interface port number
* @param dpIp data plane ip address
*/
private void processFirstTable(DeviceId deviceId, PortNumber dpPort, IpAddress dpIp) {
// take vxlan packet out onto the physical port
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(PortNumber.LOCAL)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a vxlan encap'd packet through the Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_UDP)
.matchUdpDst(TpPort.tpPort(VXLAN_UDP_PORT))
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take a packet to the data plane ip through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dpIp.toIpPrefix())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take an arp packet from physical through Linux stack
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchEthType(Ethernet.TYPE_ARP)
.matchArpTpa(dpIp.getIp4Address())
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take all else to the next table
selector = DefaultTrafficSelector.builder()
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_IN_PORT)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(LOWEST_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
// take all vlan tagged packet to the Q_IN_Q table
selector = DefaultTrafficSelector.builder()
.matchVlanId(VlanId.ANY)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_Q_IN_Q)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(VSG_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_FIRST)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Forward table miss packets in ACCESS_TYPE table to data plane port.
*
* @param deviceId device id
* @param dpPort data plane interface port number
*/
private void processAccessTypeTable(DeviceId deviceId, PortNumber dpPort) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(LOWEST_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates default rules for IN_PORT table.
* All packets from tunnel port are forwarded to TUNNEL_ID table and all packets
* from data plane interface port to ACCESS_TYPE table.
*
* @param deviceId device id to install the rules
* @param tunnelPort tunnel port number
* @param dpPort data plane interface port number
*/
private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dpPort) {
checkNotNull(tunnelPort);
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(tunnelPort)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_TUNNEL_IN)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates default rules for Q_IN_Q table.
*
* @param deviceId device id
* @param dpPort data plane interface port number
*/
private void processQInQTable(DeviceId deviceId, PortNumber dpPort) {
// for traffic going out to WAN, strip vid 500 and take through data plane interface
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchVlanId(VLAN_WAN)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.popVlan()
.setOutput(dpPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_Q_IN_Q)
.makePermanent()
.build();
processFlowRule(true, flowRule);
selector = DefaultTrafficSelector.builder()
.matchVlanId(VLAN_WAN)
.matchEthType(Ethernet.TYPE_ARP)
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.CONTROLLER)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_Q_IN_Q)
.makePermanent()
.build();
processFlowRule(true, flowRule);
}
/**
* Populates rules for local in port in IN_PORT table.
* Flows from a given in port, whose source IP is service IP transition
* to DST_TYPE table. Other flows transition to IN_SERVICE table.
*
* @param deviceId device id to install the rules
* @param inPort in port
* @param srcIp source ip
* @param install true to install or false to remove
*/
private void populateLocalInPortRule(DeviceId deviceId, PortNumber inPort, IpAddress srcIp,
boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(inPort)
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcIp.toIpPrefix())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_ACCESS_TYPE)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(install, flowRule);
selector = DefaultTrafficSelector.builder()
.matchInPort(inPort)
.build();
treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_IN_SERVICE)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(LOW_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_PORT)
.makePermanent()
.build();
processFlowRule(install, flowRule);
}
/**
* Populates direct VM access rules for ACCESS_TYPE table.
* These rules are installed to all devices.
*
* @param srcRange source ip range
* @param dstRange destination ip range
* @param install true to install or false to remove
*/
private void populateDirectAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcRange)
.matchIPDst(dstRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.transition(TABLE_DST_IP)
.build();
getVirtualSwitches().stream().forEach(deviceId -> {
FlowRule flowRuleDirect = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(install, flowRuleDirect);
});
}
/**
* Populates drop rules that does not match any direct access rules but has
* destination to a different service network in ACCESS_TYPE table.
*
* @param dstRange destination ip range
* @param install true to install or false to remove
*/
private void populateServiceIsolationRule(Ip4Prefix dstRange, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dstRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.drop()
.build();
getVirtualSwitches().stream().forEach(deviceId -> {
FlowRule flowRuleDirect = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(LOW_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(install, flowRuleDirect);
});
}
/**
* Populates indirect service access rules for ACCESS_TYPE table.
* These rules are installed to all devices.
*
* @param srcRange source range
* @param serviceIp service ip
* @param outGroups list of output group
* @param install true to install or false to remove
*/
private void populateIndirectAccessRule(Ip4Prefix srcRange, Ip4Address serviceIp,
Map<DeviceId, GroupId> outGroups, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(srcRange)
.matchIPDst(serviceIp.toIpPrefix())
.build();
for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.group(outGroup.getValue())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(HIGH_PRIORITY)
.forDevice(outGroup.getKey())
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
processFlowRule(install, flowRule);
}
}
/**
* Populates flow rules for IN_SERVICE table.
*
* @param inPorts list of inports related to the service for each device
* @param outGroups set of output groups
* @param install true to install or false to remove
*/
private void populateInServiceRule(Map<DeviceId, Set<PortNumber>> inPorts,
Map<DeviceId, GroupId> outGroups, boolean install) {
checkNotNull(inPorts);
checkNotNull(outGroups);
for (Map.Entry<DeviceId, Set<PortNumber>> entry : inPorts.entrySet()) {
Set<PortNumber> ports = entry.getValue();
DeviceId deviceId = entry.getKey();
GroupId groupId = outGroups.get(deviceId);
if (groupId == null) {
continue;
}
ports.stream().forEach(port -> {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(port)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.group(groupId)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_IN_SERVICE)
.makePermanent()
.build();
processFlowRule(install, flowRule);
});
}
}
/**
* Populates flow rules for DST_IP table.
*
* @param deviceId device id
* @param inPort in port
* @param dstMac mac address
* @param dstIp destination ip
* @param tunnelId tunnel id
* @param tunnelIp tunnel remote ip
* @param install true to install or false to remove
*/
private void populateDstIpRule(DeviceId deviceId, PortNumber inPort, MacAddress dstMac,
IpAddress dstIp, long tunnelId, IpAddress tunnelIp,
boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(dstIp.toIpPrefix())
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setEthDst(dstMac)
.setOutput(inPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
processFlowRule(install, flowRule);
for (DeviceId vSwitchId : getVirtualSwitches()) {
if (vSwitchId.equals(deviceId)) {
continue;
}
ExtensionTreatment tunnelDst = getTunnelDst(vSwitchId, tunnelIp.getIp4Address());
if (tunnelDst == null) {
continue;
}
treatment = DefaultTrafficTreatment.builder()
.setEthDst(dstMac)
.setTunnelId(tunnelId)
.extension(tunnelDst, vSwitchId)
.setOutput(getTunnelPort(vSwitchId))
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(vSwitchId)
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
processFlowRule(install, flowRule);
}
}
/**
* Populates flow rules for TUNNEL_ID table.
*
* @param deviceId device id
* @param inPort in port
* @param mac mac address
* @param tunnelId tunnel id
* @param install true to install or false to remove
*/
private void populateTunnelInRule(DeviceId deviceId, PortNumber inPort, MacAddress mac,
long tunnelId, boolean install) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchTunnelId(tunnelId)
.matchEthDst(mac)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(inPort)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(DEFAULT_PRIORITY)
.forDevice(deviceId)
.forTable(TABLE_TUNNEL_IN)
.makePermanent()
.build();
processFlowRule(install, flowRule);
}
/**
* Installs or uninstall a given rule.
*
* @param install true to install, false to uninstall
* @param rule rule
*/
private void processFlowRule(boolean install, FlowRule rule) {
FlowRuleOperations.Builder oBuilder = FlowRuleOperations.builder();
oBuilder = install ? oBuilder.add(rule) : oBuilder.remove(rule);
flowRuleService.apply(oBuilder.build(new FlowRuleOperationsContext() {
@Override
public void onError(FlowRuleOperations ops) {
log.error(String.format("Failed %s, %s", ops.toString(), rule.toString()));
}
}));
}
/**
* Returns tunnel port of the device.
*
* @param deviceId device id
* @return tunnel port number, or null if no tunnel port exists on a given device
*/
private PortNumber getTunnelPort(DeviceId deviceId) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).contains(tunnelType))
.findFirst().orElse(null);
return port == null ? null : port.number();
}
/**
* Returns data plane interface port name of a given device.
*
* @param deviceId device id
* @param dpIntf data plane interface port name
* @return data plane interface port number, or null if no such port exists
*/
private PortNumber getDpPort(DeviceId deviceId, String dpIntf) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).contains(dpIntf) &&
p.isEnabled())
.findFirst().orElse(null);
return port == null ? null : port.number();
}
/** Returns data plane interface port number of a given host.
*
* @param host host
* @return port number, or null
*/
private PortNumber getDpPort(Host host) {
String portName = host.annotations().value(DATA_PLANE_INTF);
return portName == null ? null : getDpPort(host.location().deviceId(), portName);
}
/**
* Returns service vlan from a given host.
*
* @param host host
* @return vlan id, or null
*/
private VlanId getServiceVlan(Host host) {
String serviceVlan = host.annotations().value(S_TAG);
return serviceVlan == null ? null : VlanId.vlanId(Short.parseShort(serviceVlan));
}
/**
* Returns IP address for tunneling for a given host.
*
* @param host host
* @return ip address, or null
*/
private IpAddress getTunnelIp(Host host) {
String ip = host.annotations().value(DATA_PLANE_IP);
return ip == null ? null : IpAddress.valueOf(ip);
}
/**
* Returns the destination IP from a given flow rule if the rule contains
* the match of it.
*
* @param flowRule flow rule
* @return ip prefix, or null if the rule doesn't have ip match
*/
private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
if (criterion != null && criterion instanceof IPCriterion) {
IPCriterion ip = (IPCriterion) criterion;
return ip.ip();
} else {
return null;
}
}
/**
* Returns the output port number from a given flow rule.
*
* @param flowRule flow rule
* @return port number, or null if the rule does not have output instruction
*/
private PortNumber getOutputFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof Instructions.OutputInstruction)
.findFirst()
.orElse(null);
if (instruction == null) {
return null;
}
return ((Instructions.OutputInstruction) instruction).port();
}
/**
* Returns if a given flow rule has vlan push instruction or not.
*
* @param flowRule flow rule
* @return true if it includes vlan push, or false
*/
private boolean isVlanPushFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof L2ModificationInstruction)
.filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
.findAny()
.orElse(null);
return instruction != null;
}
/**
* Creates a new group for a given service.
*
* @param deviceId device id to create a group
* @param service cord service
* @return group id, or null if it fails to create
*/
private GroupId createServiceGroup(DeviceId deviceId, VtnService service) {
checkNotNull(service);
GroupKey groupKey = getGroupKey(service.id());
Group group = groupService.getGroup(deviceId, groupKey);
GroupId groupId = getGroupId(service.id(), deviceId);
if (group != null) {
log.debug("Group {} is already exist in {}", service.id(), deviceId);
return groupId;
}
GroupBuckets buckets = getServiceGroupBuckets(
deviceId, service.vni(), getInstances(service.id()));
GroupDescription groupDescription = new DefaultGroupDescription(
deviceId,
GroupDescription.Type.SELECT,
buckets,
groupKey,
groupId.id(),
appId);
groupService.addGroup(groupDescription);
return groupId;
}
/**
* Returns group buckets for a given device.
*
* @param deviceId device id
* @param tunnelId tunnel id
* @param hosts list of host
* @return group buckets
*/
private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId,
Set<Host> hosts) {
List<GroupBucket> buckets = Lists.newArrayList();
hosts.stream().forEach(host -> {
Ip4Address tunnelIp = getTunnelIp(host).getIp4Address();
DeviceId hostDevice = host.location().deviceId();
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
.builder()
.setEthDst(host.mac());
if (deviceId.equals(hostDevice)) {
tBuilder.setOutput(host.location().port());
} else {
ExtensionTreatment tunnelDst = getTunnelDst(deviceId, tunnelIp);
tBuilder.extension(tunnelDst, deviceId)
.setTunnelId(tunnelId)
.setOutput(getTunnelPort(hostDevice));
}
buckets.add(DefaultGroupBucket.createSelectGroupBucket(tBuilder.build()));
});
return new GroupBuckets(buckets);
}
/**
* Returns globally unique group ID.
*
* @param serviceId service id
* @param deviceId device id
* @return group id
*/
private GroupId getGroupId(VtnServiceId serviceId, DeviceId deviceId) {
return new DefaultGroupId(Objects.hash(serviceId, deviceId));
}
/**
* Returns group key of a service.
*
* @param serviceId service id
* @return group key
*/
private GroupKey getGroupKey(VtnServiceId serviceId) {
return new DefaultGroupKey(serviceId.id().getBytes());
}
/**
* Returns extension instruction to set tunnel destination.
*
* @param deviceId device id
* @param remoteIp tunnel destination address
* @return extension treatment or null if it fails to get instruction
*/
private ExtensionTreatment getTunnelDst(DeviceId deviceId, Ip4Address remoteIp) {
try {
Device device = deviceService.getDevice(deviceId);
if (device.is(ExtensionTreatmentResolver.class)) {
ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
ExtensionTreatment treatment =
resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
treatment.setPropertyValue("tunnelDst", remoteIp);
return treatment;
} else {
log.warn("The extension treatment resolving behaviour is not supported in device {}",
device.id().toString());
return null;
}
} catch (ItemNotFoundException | UnsupportedOperationException |
ExtensionPropertyException e) {
log.error("Failed to get extension instruction {}", deviceId);
return null;
}
}
/**
* Returns integration bridges configured in the system.
*
* @return set of device ids
*/
private Set<DeviceId> getVirtualSwitches() {
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found for {}", appId.name());
return Sets.newHashSet();
}
return config.cordVtnNodes().stream()
.map(CordVtnNode::intBrId).collect(Collectors.toSet());
}
/**
* Returns instances with a given network service.
*
* @param serviceId service id
* @return set of hosts
*/
private Set<Host> getInstances(VtnServiceId serviceId) {
return StreamSupport.stream(hostService.getHosts().spliterator(), false)
.filter(host -> Objects.equals(
serviceId.id(),
host.annotations().value(SERVICE_ID)))
.collect(Collectors.toSet());
}
}
/*
* 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.cordvtn.impl.service;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.onosproject.cordvtn.api.Instance;
import org.onosproject.cordvtn.api.InstanceHandler;
import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
import org.onosproject.xosclient.api.VtnService;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
/**
* Provides network connectivity for dummy service instances.
*/
@Component(immediate = true)
public class DummyInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
@Activate
protected void activate() {
serviceType = VtnService.ServiceType.DUMMY;
eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-dummy", "event-handler"));
super.activate();
}
@Deactivate
protected void deactivate() {
super.deactivate();
}
@Override
public void instanceDetected(Instance instance) {
super.instanceDetected(instance);
}
@Override
public void instanceRemoved(Instance instance) {
super.instanceRemoved(instance);
}
}
/*
* 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.cordvtn.impl.service;
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.onlab.packet.Ethernet;
import org.onlab.packet.IpPrefix;
import org.onosproject.cordconfig.access.AccessAgentConfig;
import org.onosproject.cordconfig.access.AccessAgentData;
import org.onosproject.cordvtn.api.CordVtnConfig;
import org.onosproject.cordvtn.api.Instance;
import org.onosproject.cordvtn.api.InstanceHandler;
import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.xosclient.api.VtnService;
import java.util.Map;
import java.util.Set;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cordvtn.impl.CordVtnPipeline.PRIORITY_MANAGEMENT;
import static org.onosproject.cordvtn.impl.CordVtnPipeline.TABLE_ACCESS_TYPE;
/**
* Provides network connectivity for OLT agent instances.
*/
@Component(immediate = true)
public class OltAgentInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
private static final Class<AccessAgentConfig> CONFIG_CLASS = AccessAgentConfig.class;
private ConfigFactory<DeviceId, AccessAgentConfig> configFactory =
new ConfigFactory<DeviceId, AccessAgentConfig>(
SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessAgent") {
@Override
public AccessAgentConfig createConfig() {
return new AccessAgentConfig();
}
};
private Map<DeviceId, AccessAgentData> oltAgentData = Maps.newConcurrentMap();
private IpPrefix mgmtIpRange = null;
@Activate
protected void activate() {
eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-olt", "event-handler"));
serviceType = VtnService.ServiceType.OLT_AGENT;
configRegistry.registerConfigFactory(configFactory);
configListener = new InternalConfigListener();
super.activate();
}
@Deactivate
protected void deactivate() {
super.deactivate();
}
@Override
public void instanceDetected(Instance instance) {
log.info("OLT agent instance detected {}", instance);
managementAccessRule(instance.deviceId(), true);
// TODO implement
}
@Override
public void instanceRemoved(Instance instance) {
log.info("OLT agent instance removed {}", instance);
if (getInstances(instance.serviceId()).isEmpty()) {
nodeManager.completeNodes().stream().forEach(node ->
managementAccessRule(node.intBrId(), false));
}
// TODO implement
}
private void managementAccessRule(DeviceId deviceId, boolean install) {
// TODO remove this rule after long term management network is done
if (mgmtIpRange != null) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(mgmtIpRange)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.LOCAL)
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_MANAGEMENT)
.forDevice(deviceId)
.forTable(TABLE_ACCESS_TYPE)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
}
}
private void readAccessAgentConfig() {
Set<DeviceId> deviceSubjects = configRegistry.getSubjects(DeviceId.class, CONFIG_CLASS);
deviceSubjects.stream().forEach(subject -> {
AccessAgentConfig config = configRegistry.getConfig(subject, CONFIG_CLASS);
if (config != null) {
oltAgentData.put(subject, config.getAgent());
}
});
}
@Override
protected void readConfiguration() {
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found");
return;
}
osAccess = config.openstackAccess();
xosAccess = config.xosAccess();
mgmtIpRange = config.managementIpRange();
}
public class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
switch (event.type()) {
case CONFIG_UPDATED:
case CONFIG_ADDED:
if (event.configClass().equals(CordVtnConfig.class)) {
readConfiguration();
} else if (event.configClass().equals(CONFIG_CLASS)) {
readAccessAgentConfig();
}
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.cordvtn.impl.service;
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.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.cordvtn.api.Instance;
import org.onosproject.cordvtn.api.InstanceHandler;
import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
import org.onosproject.cordvtn.impl.CordVtnInstanceManager;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.HostId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.host.DefaultHostDescription;
import org.onosproject.net.host.HostDescription;
import org.onosproject.xosclient.api.VtnPort;
import org.onosproject.xosclient.api.VtnPortApi;
import org.onosproject.xosclient.api.VtnPortId;
import org.onosproject.xosclient.api.VtnService;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cordvtn.api.Instance.*;
import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
/**
* Provides network connectivity for vSG instances.
*/
@Component(immediate = true)
@Service(value = VsgInstanceHandler.class)
public final class VsgInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
private static final String STAG = "stag";
private static final String VSG_VM = "vsgVm";
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CordVtnInstanceManager instanceManager;
@Activate
protected void activate() {
serviceType = VtnService.ServiceType.VSG;
eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-vsg", "event-handler"));
super.activate();
}
@Deactivate
protected void deactivate() {
super.deactivate();
}
@Override
public void instanceDetected(Instance instance) {
if (isVsgContainer(instance)) {
log.info("vSG container detected {}", instance);
// find vsg vm for this vsg container
String vsgVmId = instance.getAnnotation(VSG_VM);
if (Strings.isNullOrEmpty(vsgVmId)) {
log.warn("Failed to find VSG VM for {}", instance);
return;
}
Instance vsgVm = Instance.of(hostService.getHost(HostId.hostId(vsgVmId)));
VtnPort vtnPort = getVtnPort(vsgVm);
if (vtnPort == null || getStag(vtnPort) == null) {
return;
}
populateVsgRules(vsgVm, getStag(vtnPort),
nodeManager.dpPort(vsgVm.deviceId()),
vtnPort.addressPairs().keySet(),
true);
} else {
VtnPort vtnPort = getVtnPort(instance);
if (vtnPort == null || getStag(vtnPort) == null) {
return;
}
vtnPort.addressPairs().entrySet().stream()
.forEach(pair -> addVsgContainer(
instance,
pair.getKey(),
pair.getValue(),
getStag(vtnPort).toString()
));
super.instanceDetected(instance);
}
}
@Override
public void instanceRemoved(Instance instance) {
if (isVsgContainer(instance)) {
log.info("vSG container vanished {}", instance);
// find vsg vm for this vsg container
String vsgVmId = instance.getAnnotation(VSG_VM);
if (Strings.isNullOrEmpty(vsgVmId)) {
log.warn("Failed to find VSG VM for {}", instance);
return;
}
Instance vsgVm = Instance.of(hostService.getHost(HostId.hostId(vsgVmId)));
VtnPort vtnPort = getVtnPort(vsgVm);
if (vtnPort == null || getStag(vtnPort) == null) {
return;
}
populateVsgRules(vsgVm, getStag(vtnPort),
nodeManager.dpPort(vsgVm.deviceId()),
vtnPort.addressPairs().keySet(),
false);
} else {
// TODO remove vsg vm related rules
super.instanceRemoved(instance);
}
}
/**
* Updates set of vSGs in a given vSG VM.
*
* @param vsgVmId vsg vm host id
* @param stag stag
* @param vsgInstances full set of vsg wan ip and mac address pairs in this vsg vm
*/
public void updateVsgInstances(HostId vsgVmId, String stag, Map<IpAddress, MacAddress> vsgInstances) {
if (hostService.getHost(vsgVmId) == null) {
log.debug("vSG VM {} is not added yet, ignore this update", vsgVmId);
return;
}
Instance vsgVm = Instance.of(hostService.getHost(vsgVmId));
if (vsgVm == null) {
log.warn("Failed to find existing vSG VM for STAG: {}", stag);
return;
}
log.info("Updates vSGs in {} with STAG: {}", vsgVm, stag);
// adds vSGs in the address pair
vsgInstances.entrySet().stream()
.filter(addr -> hostService.getHostsByMac(addr.getValue()).isEmpty())
.forEach(addr -> addVsgContainer(
vsgVm,
addr.getKey(),
addr.getValue(),
stag));
// removes vSGs not listed in the address pair
hostService.getConnectedHosts(vsgVm.host().location()).stream()
.filter(host -> !host.mac().equals(vsgVm.mac()))
.filter(host -> !vsgInstances.values().contains(host.mac()))
.forEach(host -> {
log.info("Removed vSG {}", host.toString());
instanceManager.removeInstance(host.id());
});
}
private boolean isVsgContainer(Instance instance) {
return !Strings.isNullOrEmpty(instance.host().annotations().value(STAG));
}
private void addVsgContainer(Instance vsgVm, IpAddress vsgWanIp, MacAddress vsgMac,
String stag) {
HostId hostId = HostId.hostId(vsgMac);
DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
.set(SERVICE_TYPE, vsgVm.serviceType().toString())
.set(SERVICE_ID, vsgVm.serviceId().id())
.set(PORT_ID, vsgVm.portId().id())
.set(NESTED_INSTANCE, TRUE)
.set(STAG, stag)
.set(VSG_VM, vsgVm.host().id().toString())
.set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
HostDescription hostDesc = new DefaultHostDescription(
vsgMac,
VlanId.NONE,
vsgVm.host().location(),
Sets.newHashSet(vsgWanIp),
annotations.build());
instanceManager.addInstance(hostId, hostDesc);
}
private void populateVsgRules(Instance vsgVm, VlanId stag, PortNumber dpPort,
Set<IpAddress> vsgWanIps, boolean install) {
// for traffics with s-tag, strip the tag and take through the vSG VM
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchInPort(dpPort)
.matchVlanId(stag)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(vsgVm.portNumber())
.build();
FlowRule flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(vsgVm.deviceId())
.forTable(TABLE_VLAN)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
// for traffics with customer vlan, tag with the service vlan based on input port with
// lower priority to avoid conflict with WAN tag
selector = DefaultTrafficSelector.builder()
.matchInPort(vsgVm.portNumber())
.matchVlanId(stag)
.build();
treatment = DefaultTrafficTreatment.builder()
.setOutput(dpPort)
.build();
flowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(selector)
.withTreatment(treatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(vsgVm.deviceId())
.forTable(TABLE_VLAN)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
// for traffic coming from WAN, tag 500 and take through the vSG VM
// based on destination ip
vsgWanIps.stream().forEach(ip -> {
TrafficSelector downstream = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(ip.toIpPrefix())
.build();
TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(VLAN_WAN)
.setEthDst(vsgVm.mac())
.setOutput(vsgVm.portNumber())
.build();
FlowRule downstreamFlowRule = DefaultFlowRule.builder()
.fromApp(appId)
.withSelector(downstream)
.withTreatment(downstreamTreatment)
.withPriority(PRIORITY_DEFAULT)
.forDevice(vsgVm.deviceId())
.forTable(TABLE_DST_IP)
.makePermanent()
.build();
pipeline.processFlowRule(install, downstreamFlowRule);
});
// remove downstream flow rules for the vSG not shown in vsgWanIps
for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
if (!rule.deviceId().equals(vsgVm.deviceId())) {
continue;
}
PortNumber output = getOutputFromTreatment(rule);
if (output == null || !output.equals(vsgVm.portNumber()) ||
!isVlanPushFromTreatment(rule)) {
continue;
}
IpPrefix dstIp = getDstIpFromSelector(rule);
if (dstIp != null && !vsgWanIps.contains(dstIp.address())) {
pipeline.processFlowRule(false, rule);
}
}
}
private VtnPort getVtnPort(Instance instance) {
checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
checkNotNull(xosAccess, XOS_ACCESS_ERROR);
VtnPortId vtnPortId = instance.portId();
VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
VtnPort vtnPort = portApi.vtnPort(vtnPortId, osAccess);
if (vtnPort == null) {
log.warn("Failed to get port information of {}", instance);
return null;
}
return vtnPort;
}
// TODO get stag from XOS when XOS provides it, extract if from port name for now
private VlanId getStag(VtnPort vtnPort) {
checkNotNull(vtnPort);
String portName = vtnPort.name();
if (portName != null && portName.startsWith(STAG)) {
return VlanId.vlanId(portName.split("-")[1]);
} else {
return null;
}
}
private PortNumber getOutputFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof Instructions.OutputInstruction)
.findFirst()
.orElse(null);
if (instruction == null) {
return null;
}
return ((Instructions.OutputInstruction) instruction).port();
}
private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
if (criterion != null && criterion instanceof IPCriterion) {
IPCriterion ip = (IPCriterion) criterion;
return ip.ip();
} else {
return null;
}
}
private boolean isVlanPushFromTreatment(FlowRule flowRule) {
Instruction instruction = flowRule.treatment().allInstructions().stream()
.filter(inst -> inst instanceof L2ModificationInstruction)
.filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
.findAny()
.orElse(null);
return instruction != 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.
*/
/**
* Implementation of instance handlers for various network services.
*/
package org.onosproject.cordvtn.impl.service;
\ No newline at end of file
......@@ -18,9 +18,10 @@ package org.onosproject.cordvtn.rest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onosproject.cordvtn.api.CordVtnService;
import org.onosproject.cordvtn.impl.service.VsgInstanceHandler;
import org.onosproject.net.HostId;
import org.onosproject.rest.AbstractWebResource;
import org.slf4j.Logger;
......@@ -57,7 +58,7 @@ public class NeutronMl2PortsWebResource extends AbstractWebResource {
private static final String STAG_PREFIX = "stag-";
private static final int STAG_BEGIN_INDEX = 5;
private final CordVtnService service = get(CordVtnService.class);
private final VsgInstanceHandler service = DefaultServiceDirectory.getService(VsgInstanceHandler.class);
@POST
@Consumes(MediaType.APPLICATION_JSON)
......@@ -74,6 +75,7 @@ public class NeutronMl2PortsWebResource extends AbstractWebResource {
public Response updatePorts(@PathParam("id") String id, InputStream input) {
log.debug(String.format(PORTS_MESSAGE, "update"));
// TODO get vSG updates from XOS to CORD VTN service directly
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(input).get(PORT);
......@@ -88,17 +90,16 @@ public class NeutronMl2PortsWebResource extends AbstractWebResource {
// this is allowed address pairs updates
MacAddress mac = MacAddress.valueOf(jsonNode.path(MAC_ADDRESS).asText());
Map<IpAddress, MacAddress> vSgs = Maps.newHashMap();
Map<IpAddress, MacAddress> vsgInstances = Maps.newHashMap();
jsonNode.path(ADDRESS_PAIRS).forEach(addrPair -> {
IpAddress pairIp = IpAddress.valueOf(addrPair.path(IP_ADDERSS).asText());
MacAddress pairMac = MacAddress.valueOf(addrPair.path(MAC_ADDRESS).asText());
vSgs.put(pairIp, pairMac);
vsgInstances.put(pairIp, pairMac);
});
service.updateVirtualSubscriberGateways(
HostId.hostId(mac),
service.updateVsgInstances(HostId.hostId(mac),
name.substring(STAG_BEGIN_INDEX),
vSgs);
vsgInstances);
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
......