alshabib
Committed by Ali Al-Shabibi

removing cord-related applications

Change-Id: Ia232adeecf0f5ea17061b3fb7ca851b6a00e0f99
Showing 96 changed files with 0 additions and 12114 deletions
......@@ -99,7 +99,6 @@ APPS = [
# Apps
'//apps/dhcp:onos-apps-dhcp-oar',
'//apps/fwd:onos-apps-fwd-oar',
'//apps/aaa:onos-apps-aaa-oar',
'//apps/acl:onos-apps-acl-oar',
'//apps/bgprouter:onos-apps-bgprouter-oar',
'//apps/cip:onos-apps-cip-oar',
......@@ -109,10 +108,7 @@ APPS = [
'//apps/segmentrouting:onos-apps-segmentrouting-oar',
'//apps/gangliametrics:onos-apps-gangliametrics-oar',
'//apps/graphitemetrics:onos-apps-graphitemetrics-oar',
'//apps/igmp:onos-apps-igmp-oar',
'//apps/cordmcast:onos-apps-cordmcast-oar',
'//apps/flowanalyzer:onos-apps-flowanalyzer-oar',
'//apps/olt:onos-apps-olt-oar',
'//apps/influxdbmetrics:onos-apps-influxdbmetrics-oar',
'//apps/metrics:onos-apps-metrics-oar',
'//apps/mfwd:onos-apps-mfwd-oar',
......@@ -137,7 +133,6 @@ APPS = [
'//apps/test/loadtest:onos-apps-test-loadtest-oar',
'//apps/test/messaging-perf:onos-apps-test-messaging-perf-oar',
'//apps/virtualbng:onos-apps-virtualbng-oar',
'//apps/cordvtn:onos-apps-cordvtn-oar',
'//apps/vpls:onos-apps-vpls-oar',
'//apps/vrouter:onos-apps-vrouter-oar',
'//apps/vtn:onos-apps-vtn-oar',
......@@ -145,7 +140,6 @@ APPS = [
'//apps/openstacknode:onos-apps-openstacknode-oar',
'//apps/cpman/app:onos-apps-cpman-app-oar',
'//apps/xosclient:onos-apps-xosclient-oar',
'//apps/cordconfig:onos-apps-cordconfig',
]
APP_JARS = [
......@@ -154,7 +148,6 @@ APP_JARS = [
'//apps/dhcp/api:onos-apps-dhcp-api',
'//apps/dhcp/app:onos-apps-dhcp-app',
'//apps/fwd:onos-apps-fwd',
'//apps/olt:onos-apps-olt-api',
'//apps/iptopology-api:onos-apps-iptopology-api',
'//apps/openstacknode:onos-apps-openstacknode',
'//apps/routing:onos-apps-routing',
......
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:org.apache.karaf.shell.console',
'//cli:onos-cli',
]
TEST_DEPS = [
'//lib:TEST_ADAPTERS',
'//core/common:onos-core-common',
]
osgi_jar_with_tests (
deps = COMPILE_DEPS,
test_deps = TEST_DEPS,
)
onos_app (
title = 'Authentication App',
category = 'Security',
url = 'http://onosproject.org',
description = 'ONOS authentication application.',
)
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<app name="org.onosproject.aaa" origin="ATT" version="${project.version}"
category="Security" url="http://onosproject.org" title="Authentication App"
featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
features="${project.artifactId}">
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
</app>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ 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.
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
<feature name="${project.artifactId}" version="${project.version}"
description="${project.description}">
<feature>onos-api</feature>
<bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
</feature>
</features>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-aaa</artifactId>
<packaging>bundle</packaging>
<description>ONOS authentication application</description>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-osgi</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.onosproject</groupId>
<artifactId>onos-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
/*
* 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.aaa;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.Config;
import org.onosproject.net.config.basics.BasicElementConfig;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Network config for the AAA app.
*/
public class AaaConfig extends Config<ApplicationId> {
private static final String RADIUS_IP = "radiusIp";
private static final String RADIUS_SERVER_PORT = "1812";
private static final String RADIUS_MAC = "radiusMac";
private static final String NAS_IP = "nasIp";
private static final String NAS_MAC = "nasMac";
private static final String RADIUS_SECRET = "radiusSecret";
private static final String RADIUS_SWITCH = "radiusSwitch";
private static final String RADIUS_PORT = "radiusPort";
// RADIUS server IP address
protected static final String DEFAULT_RADIUS_IP = "10.128.10.4";
// RADIUS MAC address
protected static final String DEFAULT_RADIUS_MAC = "00:00:00:00:01:10";
// NAS IP address
protected static final String DEFAULT_NAS_IP = "10.128.9.244";
// NAS MAC address
protected static final String DEFAULT_NAS_MAC = "00:00:00:00:10:01";
// RADIUS server shared secret
protected static final String DEFAULT_RADIUS_SECRET = "ONOSecret";
// Radius Switch Id
protected static final String DEFAULT_RADIUS_SWITCH = "of:90e2ba82f97791e9";
// Radius Port Number
protected static final String DEFAULT_RADIUS_PORT = "129";
// Radius Server UDP Port Number
protected static final String DEFAULT_RADIUS_SERVER_PORT = "1812";
/**
* Gets the value of a string property, protecting for an empty
* JSON object.
*
* @param name name of the property
* @param defaultValue default value if none has been specified
* @return String value if one os found, default value otherwise
*/
private String getStringProperty(String name, String defaultValue) {
if (object == null) {
return defaultValue;
}
return get(name, defaultValue);
}
/**
* Returns the NAS ip.
*
* @return ip address or null if not set
*/
public InetAddress nasIp() {
try {
return InetAddress.getByName(getStringProperty(NAS_IP, DEFAULT_NAS_IP));
} catch (UnknownHostException e) {
return null;
}
}
/**
* Sets the NAS ip.
*
* @param ip new ip address; null to clear
* @return self
*/
public BasicElementConfig nasIp(String ip) {
return (BasicElementConfig) setOrClear(NAS_IP, ip);
}
/**
* Returns the RADIUS server ip.
*
* @return ip address or null if not set
*/
public InetAddress radiusIp() {
try {
return InetAddress.getByName(getStringProperty(RADIUS_IP, DEFAULT_RADIUS_IP));
} catch (UnknownHostException e) {
return null;
}
}
/**
* Sets the RADIUS server ip.
*
* @param ip new ip address; null to clear
* @return self
*/
public BasicElementConfig radiusIp(String ip) {
return (BasicElementConfig) setOrClear(RADIUS_IP, ip);
}
/**
* Returns the RADIUS MAC address.
*
* @return mac address or null if not set
*/
public String radiusMac() {
return getStringProperty(RADIUS_MAC, DEFAULT_RADIUS_MAC);
}
/**
* Sets the RADIUS MAC address.
*
* @param mac new MAC address; null to clear
* @return self
*/
public BasicElementConfig radiusMac(String mac) {
return (BasicElementConfig) setOrClear(RADIUS_MAC, mac);
}
/**
* Returns the RADIUS MAC address.
*
* @return mac address or null if not set
*/
public String nasMac() {
return getStringProperty(NAS_MAC, DEFAULT_NAS_MAC);
}
/**
* Sets the RADIUS MAC address.
*
* @param mac new MAC address; null to clear
* @return self
*/
public BasicElementConfig nasMac(String mac) {
return (BasicElementConfig) setOrClear(NAS_MAC, mac);
}
/**
* Returns the RADIUS secret.
*
* @return radius secret or null if not set
*/
public String radiusSecret() {
return getStringProperty(RADIUS_SECRET, DEFAULT_RADIUS_SECRET);
}
/**
* Sets the RADIUS secret.
*
* @param secret new MAC address; null to clear
* @return self
*/
public BasicElementConfig radiusSecret(String secret) {
return (BasicElementConfig) setOrClear(RADIUS_SECRET, secret);
}
/**
* Returns the ID of the RADIUS switch.
*
* @return radius switch ID or null if not set
*/
public String radiusSwitch() {
return getStringProperty(RADIUS_SWITCH, DEFAULT_RADIUS_SWITCH);
}
/**
* Sets the ID of the RADIUS switch.
*
* @param switchId new RADIUS switch ID; null to clear
* @return self
*/
public BasicElementConfig radiusSwitch(String switchId) {
return (BasicElementConfig) setOrClear(RADIUS_SWITCH, switchId);
}
/**
* Returns the RADIUS port.
*
* @return radius port or null if not set
*/
public long radiusPort() {
return Integer.parseInt(getStringProperty(RADIUS_PORT, DEFAULT_RADIUS_PORT));
}
/**
* Sets the RADIUS port.
*
* @param port new RADIUS port; null to clear
* @return self
*/
public BasicElementConfig radiusPort(long port) {
return (BasicElementConfig) setOrClear(RADIUS_PORT, port);
}
/**
* Returns the RADIUS server UDP port.
*
* @return radius server UDP port.
*/
public short radiusServerUdpPort() {
return Short.parseShort(getStringProperty(RADIUS_SERVER_PORT,
DEFAULT_RADIUS_SERVER_PORT));
}
/**
* Sets the RADIUS port.
*
* @param port new RADIUS UDP port; -1 to clear
* @return self
*/
public BasicElementConfig radiusServerUdpPort(short port) {
return (BasicElementConfig) setOrClear(RADIUS_SERVER_PORT, (long) port);
}
}
/*
* Copyright 2015 AT&T Foundry
*
* 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.aaa;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.DeserializationException;
import org.onlab.packet.EAP;
import org.onlab.packet.EAPOL;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.onlab.packet.RADIUS;
import org.onlab.packet.RADIUSAttribute;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
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.NetworkConfigRegistry;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.slf4j.Logger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
import static org.onosproject.net.packet.PacketPriority.CONTROL;
import static org.slf4j.LoggerFactory.getLogger;
/**
* AAA application for ONOS.
*/
@Component(immediate = true)
public class AaaManager {
// for verbose output
private final Logger log = getLogger(getClass());
// a list of our dependencies :
// to register with ONOS as an application - described next
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
// to receive Packet-in events that we'll respond to
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry netCfgService;
// Parsed RADIUS server addresses
protected InetAddress radiusIpAddress;
protected String radiusMacAddress;
// NAS IP address
protected InetAddress nasIpAddress;
protected String nasMacAddress;
// RADIUS server secret
protected String radiusSecret;
// ID of RADIUS switch
protected String radiusSwitch;
// RADIUS port number
protected long radiusPort;
// RADIUS server TCP port number
protected short radiusServerPort;
// our application-specific event handler
private ReactivePacketProcessor processor = new ReactivePacketProcessor();
// our unique identifier
private ApplicationId appId;
// Socket used for UDP communications with RADIUS server
private DatagramSocket radiusSocket;
// Executor for RADIUS communication thread
private ExecutorService executor;
// Configuration properties factory
private final ConfigFactory factory =
new ConfigFactory<ApplicationId, AaaConfig>(APP_SUBJECT_FACTORY,
AaaConfig.class,
"AAA") {
@Override
public AaaConfig createConfig() {
return new AaaConfig();
}
};
// Listener for config changes
private final InternalConfigListener cfgListener = new InternalConfigListener();
/**
* Builds an EAPOL packet based on the given parameters.
*
* @param dstMac destination MAC address
* @param srcMac source MAC address
* @param vlan vlan identifier
* @param eapolType EAPOL type
* @param eap EAP payload
* @return Ethernet frame
*/
private static Ethernet buildEapolResponse(MacAddress dstMac, MacAddress srcMac,
short vlan, byte eapolType, EAP eap) {
Ethernet eth = new Ethernet();
eth.setDestinationMACAddress(dstMac.toBytes());
eth.setSourceMACAddress(srcMac.toBytes());
eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
if (vlan != Ethernet.VLAN_UNTAGGED) {
eth.setVlanID(vlan);
}
//eapol header
EAPOL eapol = new EAPOL();
eapol.setEapolType(eapolType);
eapol.setPacketLength(eap.getLength());
//eap part
eapol.setPayload(eap);
eth.setPayload(eapol);
eth.setPad(true);
return eth;
}
private void initializeLocalState() {
try {
radiusSocket = new DatagramSocket(null);
radiusSocket.setReuseAddress(true);
radiusSocket.bind(new InetSocketAddress(radiusServerPort));
} catch (Exception ex) {
log.error("Can't open RADIUS socket", ex);
}
executor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("AAA-radius-%d").build());
executor.execute(radiusListener);
}
@Activate
public void activate() {
netCfgService.addListener(cfgListener);
netCfgService.registerConfigFactory(factory);
// "org.onosproject.aaa" is the FQDN of our app
appId = coreService.registerApplication("org.onosproject.aaa");
cfgListener.reconfigureNetwork(netCfgService.getConfig(appId, AaaConfig.class));
// register our event handler
packetService.addProcessor(processor, PacketProcessor.director(2));
requestIntercepts();
StateMachine.initializeMaps();
initializeLocalState();
log.info("Started");
}
@Deactivate
public void deactivate() {
withdrawIntercepts();
// de-register and null our handler
packetService.removeProcessor(processor);
processor = null;
StateMachine.destroyMaps();
radiusSocket.close();
executor.shutdownNow();
log.info("Stopped");
}
protected void sendRadiusPacket(RADIUS radiusPacket) {
try {
final byte[] data = radiusPacket.serialize();
final DatagramSocket socket = radiusSocket;
DatagramPacket packet =
new DatagramPacket(data, data.length,
radiusIpAddress, radiusServerPort);
socket.send(packet);
} catch (IOException e) {
log.info("Cannot send packet to RADIUS server", e);
}
}
/**
* Request packet in via PacketService.
*/
private void requestIntercepts() {
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
packetService.requestPackets(selector.build(),
CONTROL, appId);
}
/**
* Cancel request for packet in via PacketService.
*/
private void withdrawIntercepts() {
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
packetService.cancelPackets(selector.build(), CONTROL, appId);
}
/**
* Send the ethernet packet to the supplicant.
*
* @param ethernetPkt the ethernet packet
* @param connectPoint the connect point to send out
*/
private void sendPacketToSupplicant(Ethernet ethernetPkt, ConnectPoint connectPoint) {
TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
treatment, ByteBuffer.wrap(ethernetPkt.serialize()));
packetService.emit(packet);
}
// our handler defined as a private inner class
/**
* Packet processor responsible for forwarding packets along their paths.
*/
private class ReactivePacketProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
// Extract the original Ethernet frame from the packet information
InboundPacket pkt = context.inPacket();
Ethernet ethPkt = pkt.parsed();
if (ethPkt == null) {
return;
}
try {
// identify if incoming packet comes from supplicant (EAP) or RADIUS
switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
case EAPOL:
handleSupplicantPacket(context.inPacket());
break;
default:
log.trace("Skipping Ethernet packet type {}",
EthType.EtherType.lookup(ethPkt.getEtherType()));
}
} catch (StateMachineException e) {
log.warn("Unable to process RADIUS packet:", e);
}
}
/**
* Creates and initializes common fields of a RADIUS packet.
*
* @param stateMachine state machine for the request
* @param eapPacket EAP packet
* @return RADIUS packet
*/
private RADIUS getRadiusPayload(StateMachine stateMachine, byte identifier, EAP eapPacket) {
RADIUS radiusPayload =
new RADIUS(RADIUS.RADIUS_CODE_ACCESS_REQUEST,
eapPacket.getIdentifier());
// set Request Authenticator in StateMachine
stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
radiusPayload.setIdentifier(identifier);
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME,
stateMachine.username());
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
AaaManager.this.nasIpAddress.getAddress());
radiusPayload.encapsulateMessage(eapPacket);
return radiusPayload;
}
/**
* Handles PAE packets (supplicant).
*
* @param inPacket Ethernet packet coming from the supplicant
*/
private void handleSupplicantPacket(InboundPacket inPacket) throws StateMachineException {
Ethernet ethPkt = inPacket.parsed();
// Where does it come from?
MacAddress srcMac = ethPkt.getSourceMAC();
DeviceId deviceId = inPacket.receivedFrom().deviceId();
PortNumber portNumber = inPacket.receivedFrom().port();
String sessionId = deviceId.toString() + portNumber.toString();
StateMachine stateMachine = StateMachine.lookupStateMachineBySessionId(sessionId);
if (stateMachine == null) {
stateMachine = new StateMachine(sessionId);
}
EAPOL eapol = (EAPOL) ethPkt.getPayload();
switch (eapol.getEapolType()) {
case EAPOL.EAPOL_START:
stateMachine.start();
stateMachine.setSupplicantConnectpoint(inPacket.receivedFrom());
//send an EAP Request/Identify to the supplicant
EAP eapPayload = new EAP(EAP.REQUEST, stateMachine.identifier(), EAP.ATTR_IDENTITY, null);
Ethernet eth = buildEapolResponse(srcMac, MacAddress.valueOf(nasMacAddress),
ethPkt.getVlanID(), EAPOL.EAPOL_PACKET,
eapPayload);
stateMachine.setSupplicantAddress(srcMac);
stateMachine.setVlanId(ethPkt.getVlanID());
sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
break;
case EAPOL.EAPOL_LOGOFF:
if (stateMachine.state() == StateMachine.STATE_AUTHORIZED) {
stateMachine.logoff();
}
break;
case EAPOL.EAPOL_PACKET:
RADIUS radiusPayload;
// check if this is a Response/Identify or a Response/TLS
EAP eapPacket = (EAP) eapol.getPayload();
byte dataType = eapPacket.getDataType();
switch (dataType) {
case EAP.ATTR_IDENTITY:
// request id access to RADIUS
stateMachine.setUsername(eapPacket.getData());
radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret);
sendRadiusPacket(radiusPayload);
// change the state to "PENDING"
stateMachine.requestAccess();
break;
case EAP.ATTR_MD5:
// verify if the EAP identifier corresponds to the
// challenge identifier from the client state
// machine.
if (eapPacket.getIdentifier() == stateMachine.challengeIdentifier()) {
//send the RADIUS challenge response
radiusPayload =
getRadiusPayload(stateMachine,
stateMachine.identifier(),
eapPacket);
if (stateMachine.challengeState() != null) {
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
stateMachine.challengeState());
}
radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret);
sendRadiusPacket(radiusPayload);
}
break;
case EAP.ATTR_TLS:
// request id access to RADIUS
radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
if (stateMachine.challengeState() != null) {
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
stateMachine.challengeState());
}
stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret);
sendRadiusPacket(radiusPayload);
if (stateMachine.state() != StateMachine.STATE_PENDING) {
stateMachine.requestAccess();
}
break;
default:
return;
}
break;
default:
log.trace("Skipping EAPOL message {}", eapol.getEapolType());
}
}
}
class RadiusListener implements Runnable {
/**
* Handles RADIUS packets.
*
* @param radiusPacket RADIUS packet coming from the RADIUS server.
* @throws StateMachineException if an illegal state transition is triggered
*/
protected void handleRadiusPacket(RADIUS radiusPacket) throws StateMachineException {
StateMachine stateMachine = StateMachine.lookupStateMachineById(radiusPacket.getIdentifier());
if (stateMachine == null) {
log.error("Invalid session identifier, exiting...");
return;
}
EAP eapPayload;
Ethernet eth;
switch (radiusPacket.getCode()) {
case RADIUS.RADIUS_CODE_ACCESS_CHALLENGE:
RADIUSAttribute radiusAttrState = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE);
byte[] challengeState = null;
if (radiusAttrState != null) {
challengeState = radiusAttrState.getValue();
}
eapPayload = radiusPacket.decapsulateMessage();
stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
eth = buildEapolResponse(stateMachine.supplicantAddress(),
MacAddress.valueOf(nasMacAddress),
stateMachine.vlanId(),
EAPOL.EAPOL_PACKET,
eapPayload);
sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
break;
case RADIUS.RADIUS_CODE_ACCESS_ACCEPT:
//send an EAPOL - Success to the supplicant.
byte[] eapMessage =
radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE).getValue();
eapPayload = new EAP();
eapPayload = (EAP) eapPayload.deserialize(eapMessage, 0, eapMessage.length);
eth = buildEapolResponse(stateMachine.supplicantAddress(),
MacAddress.valueOf(nasMacAddress),
stateMachine.vlanId(),
EAPOL.EAPOL_PACKET,
eapPayload);
sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
stateMachine.authorizeAccess();
break;
case RADIUS.RADIUS_CODE_ACCESS_REJECT:
stateMachine.denyAccess();
break;
default:
log.warn("Unknown RADIUS message received with code: {}", radiusPacket.getCode());
}
}
@Override
public void run() {
boolean done = false;
int packetNumber = 1;
log.info("UDP listener thread starting up");
RADIUS inboundRadiusPacket;
while (!done) {
try {
byte[] packetBuffer = new byte[RADIUS.RADIUS_MAX_LENGTH];
DatagramPacket inboundBasePacket =
new DatagramPacket(packetBuffer, packetBuffer.length);
DatagramSocket socket = radiusSocket;
socket.receive(inboundBasePacket);
log.info("Packet #{} received", packetNumber++);
try {
inboundRadiusPacket =
RADIUS.deserializer()
.deserialize(inboundBasePacket.getData(),
0,
inboundBasePacket.getLength());
handleRadiusPacket(inboundRadiusPacket);
} catch (DeserializationException dex) {
log.error("Cannot deserialize packet", dex);
} catch (StateMachineException sme) {
log.error("Illegal state machine operation", sme);
}
} catch (IOException e) {
log.info("Socket was closed, exiting listener thread");
done = true;
}
}
}
}
RadiusListener radiusListener = new RadiusListener();
private class InternalConfigListener implements NetworkConfigListener {
/**
* Reconfigures the AAA application according to the
* configuration parameters passed.
*
* @param cfg configuration object
*/
private void reconfigureNetwork(AaaConfig cfg) {
AaaConfig newCfg;
if (cfg == null) {
newCfg = new AaaConfig();
} else {
newCfg = cfg;
}
if (newCfg.nasIp() != null) {
nasIpAddress = newCfg.nasIp();
}
if (newCfg.radiusIp() != null) {
radiusIpAddress = newCfg.radiusIp();
}
if (newCfg.radiusMac() != null) {
radiusMacAddress = newCfg.radiusMac();
}
if (newCfg.nasMac() != null) {
nasMacAddress = newCfg.nasMac();
}
if (newCfg.radiusSecret() != null) {
radiusSecret = newCfg.radiusSecret();
}
if (newCfg.radiusSwitch() != null) {
radiusSwitch = newCfg.radiusSwitch();
}
if (newCfg.radiusPort() != -1) {
radiusPort = newCfg.radiusPort();
}
if (newCfg.radiusServerUdpPort() != -1) {
radiusServerPort = newCfg.radiusServerUdpPort();
}
}
@Override
public void event(NetworkConfigEvent event) {
if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
event.configClass().equals(AaaConfig.class)) {
AaaConfig cfg = netCfgService.getConfig(appId, AaaConfig.class);
reconfigureNetwork(cfg);
radiusSocket.close();
executor.shutdownNow();
initializeLocalState();
log.info("Reconfigured");
}
}
}
}
/*
* 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.aaa;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
/**
* Shows the users in the aaa.
*/
@Command(scope = "onos", name = "aaa-users",
description = "Shows the aaa users")
public class AaaShowUsersCommand extends AbstractShellCommand {
@Override
protected void execute() {
String[] state = {
"IDLE",
"STARTED",
"PENDING",
"AUTHORIZED",
"UNAUTHORIZED"
};
for (StateMachine stateMachine : StateMachine.sessionIdMap().values()) {
String deviceId = stateMachine.supplicantConnectpoint().deviceId().toString();
String portNum = stateMachine.supplicantConnectpoint().port().toString();
String username = new String(stateMachine.username());
print("UserName=%s,CurrentState=%s,DeviceId=%s,PortNumber=%s",
username, state[stateMachine.state()], deviceId, portNum);
}
}
}
/*
*
* Copyright 2015 AT&T Foundry
*
* 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.aaa;
import java.util.BitSet;
import java.util.Map;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.slf4j.Logger;
import com.google.common.collect.Maps;
import static org.slf4j.LoggerFactory.getLogger;
/**
* AAA Finite State Machine.
*/
class StateMachine {
//INDEX to identify the state in the transition table
static final int STATE_IDLE = 0;
static final int STATE_STARTED = 1;
static final int STATE_PENDING = 2;
static final int STATE_AUTHORIZED = 3;
static final int STATE_UNAUTHORIZED = 4;
//INDEX to identify the transition in the transition table
static final int TRANSITION_START = 0; // --> started
static final int TRANSITION_REQUEST_ACCESS = 1;
static final int TRANSITION_AUTHORIZE_ACCESS = 2;
static final int TRANSITION_DENY_ACCESS = 3;
static final int TRANSITION_LOGOFF = 4;
//map of access identifiers (issued at EAPOL START)
static BitSet bitSet = new BitSet();
private int identifier = -1;
private byte challengeIdentifier;
private byte[] challengeState;
private byte[] username;
private byte[] requestAuthenticator;
// Supplicant connectivity info
private ConnectPoint supplicantConnectpoint;
private MacAddress supplicantAddress;
private short vlanId;
private String sessionId = null;
private final Logger log = getLogger(getClass());
private State[] states = {
new Idle(), new Started(), new Pending(), new Authorized(), new Unauthorized()
};
//State transition table
/*
state IDLE | STARTED | PENDING | AUTHORIZED | UNAUTHORIZED
////
input
----------------------------------------------------------------------------------------------------
START STARTED | _ | _ | STARTED | _
REQUEST_ACCESS _ | PENDING | _ | _ | _
AUTHORIZE_ACCESS _ | _ | AUTHORIZED | _ | _
DENY_ACCESS _ | - | UNAUTHORIZED | _ | _
LOGOFF _ | _ | _ | IDLE | IDLE
*/
private int[] idleTransition =
{STATE_STARTED, STATE_IDLE, STATE_IDLE, STATE_IDLE, STATE_IDLE};
private int[] startedTransition =
{STATE_STARTED, STATE_PENDING, STATE_STARTED, STATE_STARTED, STATE_STARTED};
private int[] pendingTransition =
{STATE_PENDING, STATE_PENDING, STATE_AUTHORIZED, STATE_UNAUTHORIZED, STATE_PENDING};
private int[] authorizedTransition =
{STATE_STARTED, STATE_AUTHORIZED, STATE_AUTHORIZED, STATE_AUTHORIZED, STATE_IDLE};
private int[] unauthorizedTransition =
{STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_IDLE};
//THE TRANSITION TABLE
private int[][] transition =
{idleTransition, startedTransition, pendingTransition, authorizedTransition,
unauthorizedTransition};
private int currentState = STATE_IDLE;
// Maps of state machines. Each state machine is represented by an
// unique identifier on the switch: dpid + port number
private static Map<String, StateMachine> sessionIdMap;
private static Map<Integer, StateMachine> identifierMap;
public static void initializeMaps() {
sessionIdMap = Maps.newConcurrentMap();
identifierMap = Maps.newConcurrentMap();
}
public static void destroyMaps() {
sessionIdMap = null;
identifierMap = null;
}
public static Map<String, StateMachine> sessionIdMap() {
return sessionIdMap;
}
public static StateMachine lookupStateMachineById(byte identifier) {
return identifierMap.get((int) identifier);
}
public static StateMachine lookupStateMachineBySessionId(String sessionId) {
return sessionIdMap.get(sessionId);
} /**
* State Machine Constructor.
*
* @param sessionId session Id represented by the switch dpid + port number
*/
public StateMachine(String sessionId) {
log.info("Creating a new state machine for {}", sessionId);
this.sessionId = sessionId;
sessionIdMap.put(sessionId, this);
}
/**
* Gets the connect point for the supplicant side.
*
* @return supplicant connect point
*/
public ConnectPoint supplicantConnectpoint() {
return supplicantConnectpoint;
}
/**
* Sets the supplicant side connect point.
*
* @param supplicantConnectpoint supplicant select point.
*/
public void setSupplicantConnectpoint(ConnectPoint supplicantConnectpoint) {
this.supplicantConnectpoint = supplicantConnectpoint;
}
/**
* Gets the MAC address of the supplicant.
*
* @return supplicant MAC address
*/
public MacAddress supplicantAddress() {
return supplicantAddress;
}
/**
* Sets the supplicant MAC address.
*
* @param supplicantAddress new supplicant MAC address
*/
public void setSupplicantAddress(MacAddress supplicantAddress) {
this.supplicantAddress = supplicantAddress;
}
/**
* Gets the client's Vlan ID.
*
* @return client vlan ID
*/
public short vlanId() {
return vlanId;
}
/**
* Sets the client's vlan ID.
*
* @param vlanId new client vlan ID
*/
public void setVlanId(short vlanId) {
this.vlanId = vlanId;
}
/**
* Gets the client id that is requesting for access.
*
* @return The client id.
*/
public String sessionId() {
return this.sessionId;
}
/**
* Create the identifier for the state machine (happens when goes to STARTED state).
*/
private void createIdentifier() throws StateMachineException {
log.debug("Creating Identifier.");
int index;
try {
//find the first available spot for identifier assignment
index = StateMachine.bitSet.nextClearBit(0);
//there is a limit of 256 identifiers
if (index == 256) {
throw new StateMachineException("Cannot handle any new identifier. Limit is 256.");
}
} catch (IndexOutOfBoundsException e) {
throw new StateMachineException(e.getMessage());
}
log.info("Assigning identifier {}", index);
StateMachine.bitSet.set(index);
this.identifier = index;
}
/**
* Set the challenge identifier and the state issued by the RADIUS.
*
* @param challengeIdentifier The challenge identifier set into the EAP packet from the RADIUS message.
* @param challengeState The challenge state from the RADIUS.
*/
protected void setChallengeInfo(byte challengeIdentifier, byte[] challengeState) {
this.challengeIdentifier = challengeIdentifier;
this.challengeState = challengeState;
}
/**
* Set the challenge identifier issued by the RADIUS on the access challenge request.
*
* @param challengeIdentifier The challenge identifier set into the EAP packet from the RADIUS message.
*/
protected void setChallengeIdentifier(byte challengeIdentifier) {
log.info("Set Challenge Identifier to {}", challengeIdentifier);
this.challengeIdentifier = challengeIdentifier;
}
/**
* Gets the challenge EAP identifier set by the RADIUS.
*
* @return The challenge EAP identifier.
*/
protected byte challengeIdentifier() {
return this.challengeIdentifier;
}
/**
* Set the challenge state info issued by the RADIUS.
*
* @param challengeState The challenge state from the RADIUS.
*/
protected void setChallengeState(byte[] challengeState) {
log.info("Set Challenge State");
this.challengeState = challengeState;
}
/**
* Gets the challenge state set by the RADIUS.
*
* @return The challenge state.
*/
protected byte[] challengeState() {
return this.challengeState;
}
/**
* Set the username.
*
* @param username The username sent to the RADIUS upon access request.
*/
protected void setUsername(byte[] username) {
this.username = username;
}
/**
* Gets the username.
*
* @return The requestAuthenticator.
*/
protected byte[] requestAuthenticator() {
return this.requestAuthenticator;
}
/**
* Sets the authenticator.
*
* @param authenticator The username sent to the RADIUS upon access request.
*/
protected void setRequestAuthenticator(byte[] authenticator) {
this.requestAuthenticator = authenticator;
}
/**
* Gets the username.
*
* @return The username.
*/
protected byte[] username() {
return this.username;
}
/**
* Return the identifier of the state machine.
*
* @return The state machine identifier.
*/
public byte identifier() {
return (byte) this.identifier;
}
protected void deleteIdentifier() {
if (this.identifier != -1) {
log.info("Freeing up " + this.identifier);
//this state machine should be deleted and free up the identifier
StateMachine.bitSet.clear(this.identifier);
this.identifier = -1;
}
}
/**
* Move to the next state.
*
* @param msg message
*/
private void next(int msg) {
currentState = transition[currentState][msg];
log.info("Current State " + currentState);
}
/**
* Client has requested the start action to allow network access.
*
* @throws StateMachineException if authentication protocol is violated
*/
public void start() throws StateMachineException {
states[currentState].start();
//move to the next state
next(TRANSITION_START);
createIdentifier();
identifierMap.put(identifier, this);
}
/**
* An Identification information has been sent by the supplicant.
* Move to the next state if possible.
*
* @throws StateMachineException if authentication protocol is violated
*/
public void requestAccess() throws StateMachineException {
states[currentState].requestAccess();
//move to the next state
next(TRANSITION_REQUEST_ACCESS);
}
/**
* RADIUS has accepted the identification.
* Move to the next state if possible.
*
* @throws StateMachineException if authentication protocol is violated
*/
public void authorizeAccess() throws StateMachineException {
states[currentState].radiusAccepted();
//move to the next state
next(TRANSITION_AUTHORIZE_ACCESS);
// TODO: put in calls to launch vSG here
deleteIdentifier();
}
/**
* RADIUS has denied the identification.
* Move to the next state if possible.
*
* @throws StateMachineException if authentication protocol is violated
*/
public void denyAccess() throws StateMachineException {
states[currentState].radiusDenied();
//move to the next state
next(TRANSITION_DENY_ACCESS);
deleteIdentifier();
}
/**
* Logoff request has been requested.
* Move to the next state if possible.
*
* @throws StateMachineException if authentication protocol is violated
*/
public void logoff() throws StateMachineException {
states[currentState].logoff();
//move to the next state
next(TRANSITION_LOGOFF);
}
/**
* Gets the current state.
*
* @return The current state. Could be STATE_IDLE, STATE_STARTED, STATE_PENDING, STATE_AUTHORIZED,
* STATE_UNAUTHORIZED.
*/
public int state() {
return currentState;
}
@Override
public String toString() {
return ("sessionId: " + this.sessionId) + "\t" + ("identifier: " + this.identifier) + "\t" +
("state: " + this.currentState);
}
abstract class State {
private final Logger log = getLogger(getClass());
private String name = "State";
public void start() throws StateMachineInvalidTransitionException {
log.warn("START transition from this state is not allowed.");
}
public void requestAccess() throws StateMachineInvalidTransitionException {
log.warn("REQUEST ACCESS transition from this state is not allowed.");
}
public void radiusAccepted() throws StateMachineInvalidTransitionException {
log.warn("AUTHORIZE ACCESS transition from this state is not allowed.");
}
public void radiusDenied() throws StateMachineInvalidTransitionException {
log.warn("DENY ACCESS transition from this state is not allowed.");
}
public void logoff() throws StateMachineInvalidTransitionException {
log.warn("LOGOFF transition from this state is not allowed.");
}
}
/**
* Idle state: supplicant is logged of from the network.
*/
class Idle extends State {
private final Logger log = getLogger(getClass());
private String name = "IDLE_STATE";
public void start() {
log.info("Moving from IDLE state to STARTED state.");
}
}
/**
* Started state: supplicant has entered the network and informed the authenticator.
*/
class Started extends State {
private final Logger log = getLogger(getClass());
private String name = "STARTED_STATE";
public void requestAccess() {
log.info("Moving from STARTED state to PENDING state.");
}
}
/**
* Pending state: supplicant has been identified by the authenticator but has not access yet.
*/
class Pending extends State {
private final Logger log = getLogger(getClass());
private String name = "PENDING_STATE";
public void radiusAccepted() {
log.info("Moving from PENDING state to AUTHORIZED state.");
}
public void radiusDenied() {
log.info("Moving from PENDING state to UNAUTHORIZED state.");
}
}
/**
* Authorized state: supplicant port has been accepted, access is granted.
*/
class Authorized extends State {
private final Logger log = getLogger(getClass());
private String name = "AUTHORIZED_STATE";
public void start() {
log.info("Moving from AUTHORIZED state to STARTED state.");
}
public void logoff() {
log.info("Moving from AUTHORIZED state to IDLE state.");
}
}
/**
* Unauthorized state: supplicant port has been rejected, access is denied.
*/
class Unauthorized extends State {
private final Logger log = getLogger(getClass());
private String name = "UNAUTHORIZED_STATE";
public void logoff() {
log.info("Moving from UNAUTHORIZED state to IDLE state.");
}
}
}
/*
*
* Copyright 2015 AT&T Foundry
*
* 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.aaa;
/**
* Exception for the State Machine.
*/
class StateMachineException extends Exception {
public StateMachineException(String message) {
super(message);
}
}
/*
*
* Copyright 2015 AT&T Foundry
*
* 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.aaa;
/**
* Exception raised when the transition from one state to another is invalid.
*/
class StateMachineInvalidTransitionException extends StateMachineException {
public StateMachineInvalidTransitionException(String message) {
super(message);
}
}
/*
* 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.
*/
/**
* AAA implmentation.
*/
package org.onosproject.aaa;
<!--
~ 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.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onosproject.aaa.AaaShowUsersCommand"/>
</command>
</command-bundle>
</blueprint>
/*
* 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.aaa;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.onlab.packet.EAP;
import org.onlab.packet.EAPOL;
import org.onlab.packet.Ethernet;
import org.onosproject.core.CoreServiceAdapter;
import org.onosproject.net.config.Config;
import org.onosproject.net.config.NetworkConfigRegistryAdapter;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
/**
* Set of tests of the ONOS application component. These use an existing RADIUS
* server and sends live packets over the network to it.
*/
@Ignore ("This should not be run as part of the standard build")
public class AaaIntegrationTest extends AaaTestBase {
private AaaManager aaa;
/**
* Mocks the network config registry.
*/
@SuppressWarnings("unchecked")
static final class TestNetworkConfigRegistry
extends NetworkConfigRegistryAdapter {
@Override
public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
return (C) new AaaConfig();
}
}
/**
* Sets up the services required by the AAA application.
*/
@Before
public void setUp() {
aaa = new AaaManager();
aaa.netCfgService = new TestNetworkConfigRegistry();
aaa.coreService = new CoreServiceAdapter();
aaa.packetService = new MockPacketService();
aaa.activate();
}
/**
* Fetches the sent packet at the given index. The requested packet
* must be the last packet on the list.
*
* @param index index into sent packets array
* @return packet
*/
private Ethernet fetchPacket(int index) {
for (int iteration = 0; iteration < 20; iteration++) {
if (savedPackets.size() > index) {
return (Ethernet) savedPackets.get(index);
} else {
try {
Thread.sleep(250);
} catch (Exception ex) {
return null;
}
}
}
return null;
}
/**
* Tests the authentication path through the AAA application by sending
* packets to the RADIUS server and checking the state machine
* transitions.
*
* @throws Exception when an unhandled error occurs
*/
@Test
public void testAuthentication() throws Exception {
// (1) Supplicant start up
Ethernet startPacket = constructSupplicantStartPacket();
sendPacket(startPacket);
Ethernet responsePacket = fetchPacket(0);
assertThat(responsePacket, notNullValue());
checkRadiusPacket(aaa, responsePacket, EAP.REQUEST);
// (2) Supplicant identify
Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null);
sendPacket(identifyPacket);
// State machine should have been created by now
StateMachine stateMachine =
StateMachine.lookupStateMachineBySessionId(SESSION_ID);
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
// (3) RADIUS MD5 challenge
Ethernet radiusChallengeMD5Packet = fetchPacket(1);
assertThat(radiusChallengeMD5Packet, notNullValue());
checkRadiusPacket(aaa, radiusChallengeMD5Packet, EAP.REQUEST);
// (4) Supplicant MD5 response
Ethernet md5RadiusPacket =
constructSupplicantIdentifyPacket(stateMachine,
EAP.ATTR_MD5,
stateMachine.challengeIdentifier(),
radiusChallengeMD5Packet);
sendPacket(md5RadiusPacket);
// (5) RADIUS Success
Ethernet successRadiusPacket = fetchPacket(2);
assertThat(successRadiusPacket, notNullValue());
EAPOL successEapol = (EAPOL) successRadiusPacket.getPayload();
EAP successEap = (EAP) successEapol.getPayload();
assertThat(successEap.getCode(), is(EAP.SUCCESS));
// State machine should be in authorized state
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
}
}
/*
* 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.aaa;
import com.google.common.base.Charsets;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.BasePacket;
import org.onlab.packet.DeserializationException;
import org.onlab.packet.EAP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.RADIUS;
import org.onlab.packet.RADIUSAttribute;
import org.onosproject.core.CoreServiceAdapter;
import org.onosproject.net.config.Config;
import org.onosproject.net.config.NetworkConfigRegistryAdapter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
/**
* Set of tests of the ONOS application component.
*/
public class AaaManagerTest extends AaaTestBase {
static final String BAD_IP_ADDRESS = "198.51.100.0";
private AaaManager aaaManager;
class AaaManagerWithoutRadiusServer extends AaaManager {
protected void sendRadiusPacket(RADIUS radiusPacket) {
savePacket(radiusPacket);
}
}
/**
* Mocks the AAAConfig class to force usage of an unroutable address for the
* RADIUS server.
*/
static class MockAaaConfig extends AaaConfig {
@Override
public InetAddress radiusIp() {
try {
return InetAddress.getByName(BAD_IP_ADDRESS);
} catch (UnknownHostException ex) {
// can't happen
throw new IllegalStateException(ex);
}
}
}
/**
* Mocks the network config registry.
*/
@SuppressWarnings("unchecked")
private static final class TestNetworkConfigRegistry
extends NetworkConfigRegistryAdapter {
@Override
public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
AaaConfig aaaConfig = new MockAaaConfig();
return (C) aaaConfig;
}
}
/**
* Constructs an Ethernet packet containing a RADIUS challenge
* packet.
*
* @param challengeCode code to use in challenge packet
* @param challengeType type to use in challenge packet
* @return Ethernet packet
*/
private RADIUS constructRadiusCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
String challenge = "12345678901234567";
EAP eap = new EAP(challengeType, (byte) 1, challengeType,
challenge.getBytes(Charsets.US_ASCII));
eap.setIdentifier((byte) 1);
RADIUS radius = new RADIUS();
radius.setCode(challengeCode);
radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
challenge.getBytes(Charsets.US_ASCII));
radius.setPayload(eap);
radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
eap.serialize());
return radius;
}
/**
* Sets up the services required by the AAA application.
*/
@Before
public void setUp() {
aaaManager = new AaaManagerWithoutRadiusServer();
aaaManager.netCfgService = new TestNetworkConfigRegistry();
aaaManager.coreService = new CoreServiceAdapter();
aaaManager.packetService = new MockPacketService();
aaaManager.activate();
}
/**
* Tears down the AAA application.
*/
@After
public void tearDown() {
aaaManager.deactivate();
}
/**
* Extracts the RADIUS packet from a packet sent by the supplicant.
*
* @param radius RADIUS packet sent by the supplicant
* @throws DeserializationException if deserialization of the packet contents
* fails.
*/
private void checkRadiusPacketFromSupplicant(RADIUS radius)
throws DeserializationException {
assertThat(radius, notNullValue());
EAP eap = radius.decapsulateMessage();
assertThat(eap, notNullValue());
}
/**
* Fetches the sent packet at the given index. The requested packet
* must be the last packet on the list.
*
* @param index index into sent packets array
* @return packet
*/
private BasePacket fetchPacket(int index) {
BasePacket packet = savedPackets.get(index);
assertThat(packet, notNullValue());
return packet;
}
/**
* Tests the authentication path through the AAA application.
*
* @throws DeserializationException if packed deserialization fails.
*/
@Test
public void testAuthentication() throws Exception {
// (1) Supplicant start up
Ethernet startPacket = constructSupplicantStartPacket();
sendPacket(startPacket);
Ethernet responsePacket = (Ethernet) fetchPacket(0);
checkRadiusPacket(aaaManager, responsePacket, EAP.ATTR_IDENTITY);
// (2) Supplicant identify
Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null);
sendPacket(identifyPacket);
RADIUS radiusIdentifyPacket = (RADIUS) fetchPacket(1);
checkRadiusPacketFromSupplicant(radiusIdentifyPacket);
assertThat(radiusIdentifyPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
assertThat(new String(radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME).getValue()),
is("testuser"));
IpAddress nasIp =
IpAddress.valueOf(IpAddress.Version.INET,
radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
.getValue());
assertThat(nasIp.toString(), is(aaaManager.nasIpAddress.getHostAddress()));
// State machine should have been created by now
StateMachine stateMachine =
StateMachine.lookupStateMachineBySessionId(SESSION_ID);
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
// (3) RADIUS MD5 challenge
RADIUS radiusCodeAccessChallengePacket =
constructRadiusCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_MD5);
aaaManager.radiusListener.handleRadiusPacket(radiusCodeAccessChallengePacket);
Ethernet radiusChallengeMD5Packet = (Ethernet) fetchPacket(2);
checkRadiusPacket(aaaManager, radiusChallengeMD5Packet, EAP.ATTR_MD5);
// (4) Supplicant MD5 response
Ethernet md5RadiusPacket =
constructSupplicantIdentifyPacket(stateMachine,
EAP.ATTR_MD5,
stateMachine.challengeIdentifier(),
radiusChallengeMD5Packet);
sendPacket(md5RadiusPacket);
RADIUS responseMd5RadiusPacket = (RADIUS) fetchPacket(3);
checkRadiusPacketFromSupplicant(responseMd5RadiusPacket);
assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 0));
assertThat(responseMd5RadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
// State machine should be in pending state
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
// (5) RADIUS Success
RADIUS successPacket =
constructRadiusCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_ACCEPT, EAP.SUCCESS);
aaaManager.radiusListener.handleRadiusPacket((successPacket));
Ethernet supplicantSuccessPacket = (Ethernet) fetchPacket(4);
checkRadiusPacket(aaaManager, supplicantSuccessPacket, EAP.SUCCESS);
// State machine should be in authorized state
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
}
/**
* Tests the default configuration.
*/
@Test
public void testConfig() {
assertThat(aaaManager.nasIpAddress.getHostAddress(), is(AaaConfig.DEFAULT_NAS_IP));
assertThat(aaaManager.nasMacAddress, is(AaaConfig.DEFAULT_NAS_MAC));
assertThat(aaaManager.radiusIpAddress.getHostAddress(), is(BAD_IP_ADDRESS));
assertThat(aaaManager.radiusMacAddress, is(AaaConfig.DEFAULT_RADIUS_MAC));
}
}
/*
* 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.aaa;
import org.onlab.packet.BasePacket;
import org.onlab.packet.EAP;
import org.onlab.packet.EAPOL;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.onosproject.net.packet.DefaultInboundPacket;
import org.onosproject.net.packet.DefaultPacketContext;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketServiceAdapter;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.onosproject.net.NetTestTools.connectPoint;
/**
* Common methods for AAA app testing.
*/
public class AaaTestBase {
MacAddress clientMac = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
MacAddress serverMac = MacAddress.valueOf("2a:2a:2a:2a:2a:2a");
// Our session id will be the device ID ("of:1") with the port ("1") concatenated
static final String SESSION_ID = "of:11";
List<BasePacket> savedPackets = new LinkedList<>();
PacketProcessor packetProcessor;
/**
* Saves the given packet onto the saved packets list.
*
* @param packet packet to save
*/
void savePacket(BasePacket packet) {
savedPackets.add(packet);
}
/**
* Keeps a reference to the PacketProcessor and saves the OutboundPackets.
*/
class MockPacketService extends PacketServiceAdapter {
@Override
public void addProcessor(PacketProcessor processor, int priority) {
packetProcessor = processor;
}
@Override
public void emit(OutboundPacket packet) {
try {
Ethernet eth = Ethernet.deserializer().deserialize(packet.data().array(),
0, packet.data().array().length);
savePacket(eth);
} catch (Exception e) {
fail(e.getMessage());
}
}
}
/**
* Mocks the DefaultPacketContext.
*/
final class TestPacketContext extends DefaultPacketContext {
private TestPacketContext(long time, InboundPacket inPkt,
OutboundPacket outPkt, boolean block) {
super(time, inPkt, outPkt, block);
}
@Override
public void send() {
// We don't send anything out.
}
}
/**
* Sends an Ethernet packet to the process method of the Packet Processor.
*
* @param reply Ethernet packet
*/
void sendPacket(Ethernet reply) {
final ByteBuffer byteBuffer = ByteBuffer.wrap(reply.serialize());
InboundPacket inPacket = new DefaultInboundPacket(connectPoint("1", 1),
reply,
byteBuffer);
PacketContext context = new TestPacketContext(127L, inPacket, null, false);
packetProcessor.process(context);
}
/**
* Constructs an Ethernet packet containing identification payload.
*
* @return Ethernet packet
*/
Ethernet constructSupplicantIdentifyPacket(StateMachine stateMachine,
byte type,
byte id,
Ethernet radiusChallenge)
throws Exception {
Ethernet eth = new Ethernet();
eth.setDestinationMACAddress(clientMac.toBytes());
eth.setSourceMACAddress(serverMac.toBytes());
eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
eth.setVlanID((short) 2);
String username = "testuser";
byte[] data = username.getBytes();
if (type == EAP.ATTR_MD5) {
String password = "testpassword";
EAPOL eapol = (EAPOL) radiusChallenge.getPayload();
EAP eap = (EAP) eapol.getPayload();
byte[] identifier = new byte[password.length() + eap.getData().length];
identifier[0] = stateMachine.challengeIdentifier();
System.arraycopy(password.getBytes(), 0, identifier, 1, password.length());
System.arraycopy(eap.getData(), 1, identifier, 1 + password.length(), 16);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(identifier);
data = new byte[17];
data[0] = (byte) 16;
System.arraycopy(hash, 0, data, 1, 16);
}
EAP eap = new EAP(EAP.RESPONSE, (byte) 1, type,
data);
eap.setIdentifier(id);
// eapol header
EAPOL eapol = new EAPOL();
eapol.setEapolType(EAPOL.EAPOL_PACKET);
eapol.setPacketLength(eap.getLength());
// eap part
eapol.setPayload(eap);
eth.setPayload(eapol);
eth.setPad(true);
return eth;
}
/**
* Constructs an Ethernet packet containing a EAPOL_START Payload.
*
* @return Ethernet packet
*/
Ethernet constructSupplicantStartPacket() {
Ethernet eth = new Ethernet();
eth.setDestinationMACAddress(clientMac.toBytes());
eth.setSourceMACAddress(serverMac.toBytes());
eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
eth.setVlanID((short) 2);
EAP eap = new EAP(EAPOL.EAPOL_START, (byte) 2, EAPOL.EAPOL_START, null);
// eapol header
EAPOL eapol = new EAPOL();
eapol.setEapolType(EAPOL.EAPOL_START);
eapol.setPacketLength(eap.getLength());
// eap part
eapol.setPayload(eap);
eth.setPayload(eapol);
eth.setPad(true);
return eth;
}
/**
* Checks the contents of a RADIUS packet being sent to the RADIUS server.
*
* @param radiusPacket packet to check
* @param code expected code
*/
void checkRadiusPacket(AaaManager aaaManager, Ethernet radiusPacket, byte code) {
assertThat(radiusPacket.getSourceMAC(),
is(MacAddress.valueOf(aaaManager.nasMacAddress)));
assertThat(radiusPacket.getDestinationMAC(), is(serverMac));
assertThat(radiusPacket.getPayload(), instanceOf(EAPOL.class));
EAPOL eapol = (EAPOL) radiusPacket.getPayload();
assertThat(eapol, notNullValue());
assertThat(eapol.getEapolType(), is(EAPOL.EAPOL_PACKET));
assertThat(eapol.getPayload(), instanceOf(EAP.class));
EAP eap = (EAP) eapol.getPayload();
assertThat(eap, notNullValue());
assertThat(eap.getCode(), is(code));
}
}
/*
*
* Copyright 2015 AT&T Foundry
*
* 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.aaa;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class StateMachineTest {
StateMachine stateMachine = null;
@Before
public void setUp() {
System.out.println("Set Up.");
StateMachine.bitSet.clear();
StateMachine.initializeMaps();
stateMachine = new StateMachine("session0");
}
@After
public void tearDown() {
System.out.println("Tear Down.");
StateMachine.bitSet.clear();
StateMachine.destroyMaps();
stateMachine = null;
}
@Test
/**
* Test all the basic inputs from state to state: IDLE -> STARTED -> PENDING -> AUTHORIZED -> IDLE
*/
public void basic() throws StateMachineException {
System.out.println("======= BASIC =======.");
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
stateMachine.start();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
stateMachine.requestAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.authorizeAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
stateMachine.logoff();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
}
@Test
/**
* Test all inputs from an IDLE state (starting with the ones that are not impacting the current state)
*/
public void testIdleState() throws StateMachineException {
System.out.println("======= IDLE STATE TEST =======.");
stateMachine.requestAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
stateMachine.authorizeAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
stateMachine.denyAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
stateMachine.logoff();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
stateMachine.start();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
}
@Test
/**
* Test all inputs from an STARTED state (starting with the ones that are not impacting the current state)
*/
public void testStartedState() throws StateMachineException {
System.out.println("======= STARTED STATE TEST =======.");
stateMachine.start();
stateMachine.authorizeAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
stateMachine.denyAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
stateMachine.logoff();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
stateMachine.start();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
stateMachine.requestAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
}
@Test
/**
* Test all inputs from a PENDING state (starting with the ones that are not impacting the current state).
* The next valid state for this test is AUTHORIZED
*/
public void testPendingStateToAuthorized() throws StateMachineException {
System.out.println("======= PENDING STATE TEST (AUTHORIZED) =======.");
stateMachine.start();
stateMachine.requestAccess();
stateMachine.logoff();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.start();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.requestAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.authorizeAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
stateMachine.denyAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
}
@Test
/**
* Test all inputs from an PENDING state (starting with the ones that are not impacting the current state).
* The next valid state for this test is UNAUTHORIZED
*/
public void testPendingStateToUnauthorized() throws StateMachineException {
System.out.println("======= PENDING STATE TEST (DENIED) =======.");
stateMachine.start();
stateMachine.requestAccess();
stateMachine.logoff();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.start();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.requestAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.denyAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
stateMachine.authorizeAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
}
@Test
/**
* Test all inputs from an AUTHORIZED state (starting with the ones that are not impacting the current state).
*/
public void testAuthorizedState() throws StateMachineException {
System.out.println("======= AUTHORIZED STATE TEST =======.");
stateMachine.start();
stateMachine.requestAccess();
stateMachine.authorizeAccess();
stateMachine.start();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
stateMachine.requestAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
stateMachine.authorizeAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
stateMachine.denyAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
stateMachine.logoff();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
}
@Test
/**
* Test all inputs from an UNAUTHORIZED state (starting with the ones that are not impacting the current state).
*/
public void testUnauthorizedState() throws StateMachineException {
System.out.println("======= UNAUTHORIZED STATE TEST =======.");
stateMachine.start();
stateMachine.requestAccess();
stateMachine.denyAccess();
stateMachine.start();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
stateMachine.requestAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
stateMachine.authorizeAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
stateMachine.denyAccess();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
stateMachine.logoff();
Assert.assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
}
@Test
public void testIdentifierAvailability() throws StateMachineException {
System.out.println("======= IDENTIFIER TEST =======.");
byte identifier = stateMachine.identifier();
System.out.println("State: " + stateMachine.state());
System.out.println("Identifier: " + Byte.toUnsignedInt(identifier));
Assert.assertEquals(-1, identifier);
stateMachine.start();
StateMachine sm247 = null;
StateMachine sm3 = null;
//create 255 others state machines
for (int i = 1; i <= 255; i++) {
StateMachine sm = new StateMachine("session" + i);
sm.start();
byte id = sm.identifier();
Assert.assertEquals(i, Byte.toUnsignedInt(id));
if (i == 3) {
sm3 = sm;
System.out.println("SM3: " + sm3.toString());
}
if (i == 247) {
sm247 = sm;
System.out.println("SM247: " + sm247.toString());
}
}
//simulate the state machine for a specific session and logoff so we can free up a spot for an identifier
//let's choose identifier 247 then we free up 3
Assert.assertNotNull(sm247);
sm247.requestAccess();
sm247.authorizeAccess();
sm247.logoff();
Assert.assertNotNull(sm3);
sm3.requestAccess();
sm3.authorizeAccess();
sm3.logoff();
StateMachine otherSM3 = new StateMachine("session3b");
otherSM3.start();
otherSM3.requestAccess();
byte id3 = otherSM3.identifier();
Assert.assertEquals(3, Byte.toUnsignedInt(id3));
StateMachine otherSM247 = new StateMachine("session247b");
otherSM247.start();
otherSM247.requestAccess();
byte id247 = otherSM247.identifier();
Assert.assertEquals(247, Byte.toUnsignedInt(id247));
}
@Test
public void testSessionIdLookups() {
String sessionId1 = "session1";
String sessionId2 = "session2";
String sessionId3 = "session3";
StateMachine machine1ShouldBeNull =
StateMachine.lookupStateMachineBySessionId(sessionId1);
assertNull(machine1ShouldBeNull);
StateMachine machine2ShouldBeNull =
StateMachine.lookupStateMachineBySessionId(sessionId2);
assertNull(machine2ShouldBeNull);
StateMachine stateMachine1 = new StateMachine(sessionId1);
StateMachine stateMachine2 = new StateMachine(sessionId2);
assertEquals(stateMachine1,
StateMachine.lookupStateMachineBySessionId(sessionId1));
assertEquals(stateMachine2,
StateMachine.lookupStateMachineBySessionId(sessionId2));
assertNull(StateMachine.lookupStateMachineBySessionId(sessionId3));
}
@Test
public void testIdentifierLookups() throws StateMachineException {
String sessionId1 = "session1";
String sessionId2 = "session2";
StateMachine machine1ShouldBeNull =
StateMachine.lookupStateMachineById((byte) 1);
assertNull(machine1ShouldBeNull);
StateMachine machine2ShouldBeNull =
StateMachine.lookupStateMachineById((byte) 2);
assertNull(machine2ShouldBeNull);
StateMachine stateMachine1 = new StateMachine(sessionId1);
stateMachine1.start();
StateMachine stateMachine2 = new StateMachine(sessionId2);
stateMachine2.start();
assertEquals(stateMachine1,
StateMachine.lookupStateMachineById(stateMachine1.identifier()));
assertEquals(stateMachine2,
StateMachine.lookupStateMachineById(stateMachine2.identifier()));
}
}
COMPILE_DEPS = [
'//lib:CORE_DEPS',
]
osgi_jar_with_tests (
deps = COMPILE_DEPS,
)
onos_app (
title = 'CORD Configuration',
category = 'Utility',
url = 'http://onosproject.org',
description = 'CORD configuration meta application.',
)
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-cord-config</artifactId>
<packaging>bundle</packaging>
<description>Cord configuration meta applications </description>
<properties>
<onos.app.name>org.onosproject.cord-config</onos.app.name>
<onos.app.title>Cord Configuratuon Meta Application</onos.app.title>
<onos.app.category>Utility</onos.app.category>
<onos.app.url>http://opencord.org</onos.app.url>
</properties>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
</dependency>
</dependencies>
</project>
/*
* 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.cordconfig.access;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.Config;
import java.util.Map;
import java.util.Optional;
import static org.onosproject.net.config.Config.FieldPresence.MANDATORY;
import static org.onosproject.net.config.Config.FieldPresence.OPTIONAL;
/**
* Represents configuration for an OLT agent.
*/
public class AccessAgentConfig extends Config<DeviceId> {
private static final String OLTS = "olts";
private static final String AGENT_MAC = "mac";
// TODO: Remove this, it is only useful as long as XOS doesn't manage this.
private static final String VTN_LOCATION = "vtn-location";
@Override
public boolean isValid() {
return hasOnlyFields(OLTS, AGENT_MAC, VTN_LOCATION) &&
isMacAddress(AGENT_MAC, MANDATORY) &&
isConnectPoint(VTN_LOCATION, OPTIONAL) &&
isValidOlts();
}
/**
* Gets the access agent configuration for this device.
*
* @return access agent configuration
*/
public AccessAgentData getAgent() {
JsonNode olts = node.get(OLTS);
Map<ConnectPoint, MacAddress> oltMacInfo = Maps.newHashMap();
olts.fields().forEachRemaining(item -> oltMacInfo.put(
new ConnectPoint(subject(), PortNumber.fromString(item.getKey())),
MacAddress.valueOf(item.getValue().asText())));
MacAddress agentMac = MacAddress.valueOf(node.path(AGENT_MAC).asText());
JsonNode vtn = node.path(VTN_LOCATION);
Optional<ConnectPoint> vtnLocation;
if (vtn.isMissingNode()) {
vtnLocation = Optional.empty();
} else {
vtnLocation = Optional.of(ConnectPoint.deviceConnectPoint(vtn.asText()));
}
return new AccessAgentData(subject(), oltMacInfo, agentMac, vtnLocation);
}
private boolean isValidOlts() {
JsonNode olts = node.get(OLTS);
if (!olts.isObject()) {
return false;
}
return !Iterators.any(olts.fields(), item -> !StringUtils.isNumeric(item.getKey()) ||
!isMacAddress((ObjectNode) olts, item.getKey(), MANDATORY));
}
}
/*
* 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.cordconfig.access;
import com.google.common.collect.ImmutableMap;
import org.onlab.packet.MacAddress;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import java.util.Map;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Information about an access agent.
*/
public class AccessAgentData {
private static final String DEVICE_ID_MISSING = "Device ID cannot be null";
private static final String OLT_INFO_MISSING = "OLT information cannot be null";
private static final String AGENT_MAC_MISSING = "Agent mac cannot be null";
private static final String VTN_MISSING = "VTN location cannot be null";
private final Map<ConnectPoint, MacAddress> oltMacInfo;
private final MacAddress agentMac;
private final Optional<ConnectPoint> vtnLocation;
private final DeviceId deviceId;
/**
* Constucts an agent configuration for a given device.
*
* @param deviceId access device id
* @param oltMacInfo a map of olt chips and their mac address
* @param agentMac the mac address of the agent
* @param vtnLocation the location of the agent
*/
public AccessAgentData(DeviceId deviceId, Map<ConnectPoint, MacAddress> oltMacInfo,
MacAddress agentMac, Optional<ConnectPoint> vtnLocation) {
this.deviceId = checkNotNull(deviceId, DEVICE_ID_MISSING);
this.oltMacInfo = checkNotNull(oltMacInfo, OLT_INFO_MISSING);
this.agentMac = checkNotNull(agentMac, AGENT_MAC_MISSING);
this.vtnLocation = checkNotNull(vtnLocation, VTN_MISSING);
}
/**
* Retrieves the access device ID.
*
* @return device ID
*/
public DeviceId deviceId() {
return deviceId;
}
/**
* Returns the mapping of olt chips to mac addresses. Each chip is
* symbolized by a connect point.
*
* @return a mapping of chips (as connect points) to mac addresses
*/
public Map<ConnectPoint, MacAddress> getOltMacInfo() {
return ImmutableMap.copyOf(oltMacInfo);
}
/**
* Reuturns the agents mac address.
*
* @return a mac address
*/
public MacAddress getAgentMac() {
return agentMac;
}
/**
* Returns the location of the agent.
*
* @return a connection point
*/
public Optional<ConnectPoint> getVtnLocation() {
return vtnLocation;
}
}
/*
* 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.cordconfig.access;
import org.onosproject.net.DeviceId;
import com.fasterxml.jackson.databind.JsonNode;
import org.onlab.packet.VlanId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.Config;
import java.util.Optional;
/**
* Config object for access device data.
*/
public class AccessDeviceConfig extends Config<DeviceId> {
private static final String UPLINK = "uplink";
private static final String VLAN = "vlan";
private static final String DEFAULT_VLAN = "defaultVlan";
/**
* Gets the access device configuration for this device.
*
* @return access device configuration
*/
public AccessDeviceData getOlt() {
PortNumber uplink = PortNumber.portNumber(node.path(UPLINK).asText());
VlanId vlan = VlanId.vlanId(Short.parseShort(node.path(VLAN).asText()));
JsonNode defaultVlanNode = node.path(DEFAULT_VLAN);
Optional<VlanId> defaultVlan;
if (defaultVlanNode.isMissingNode()) {
defaultVlan = Optional.empty();
} else {
defaultVlan = Optional.of(VlanId.vlanId(Short.parseShort(defaultVlanNode.asText())));
}
return new AccessDeviceData(subject(), uplink, vlan, defaultVlan);
}
}
/*
* 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.cordconfig.access;
import org.onlab.packet.VlanId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Information about an access device.
*/
public class AccessDeviceData {
private static final String DEVICE_ID_MISSING = "Device ID cannot be null";
private static final String UPLINK_MISSING = "Uplink cannot be null";
private static final String VLAN_MISSING = "VLAN ID cannot be null";
private final DeviceId deviceId;
private final PortNumber uplink;
private final VlanId vlan;
private final Optional<VlanId> defaultVlan;
/**
* Class constructor.
*
* @param deviceId access device ID
* @param uplink uplink port number
* @param vlan device VLAN ID
* @param defaultVlan default device VLAN ID
*/
public AccessDeviceData(DeviceId deviceId, PortNumber uplink, VlanId vlan,
Optional<VlanId> defaultVlan) {
this.deviceId = checkNotNull(deviceId, DEVICE_ID_MISSING);
this.uplink = checkNotNull(uplink, UPLINK_MISSING);
this.vlan = checkNotNull(vlan, VLAN_MISSING);
this.defaultVlan = checkNotNull(defaultVlan);
}
/**
* Retrieves the access device ID.
*
* @return device ID
*/
public DeviceId deviceId() {
return deviceId;
}
/**
* Retrieves the uplink port number.
*
* @return port number
*/
public PortNumber uplink() {
return uplink;
}
/**
* Retrieves the VLAN ID assigned to the device.
*
* @return VLAN ID
*/
public VlanId vlan() {
return vlan;
}
/**
* Retrieves the default VLAN ID that will be used for this device.
*
* @return default VLAN ID
*/
public Optional<VlanId> defaultVlan() {
return defaultVlan;
}
}
/*
* 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.
*/
/**
* Meta Application for hosting common cord configuration classes.
*/
package org.onosproject.cordconfig.access;
\ No newline at end of file
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:javax.ws.rs-api',
'//lib:jersey-client',
'//lib:jersey-common',
'//utils/rest:onlab-rest',
'//apps/olt:onos-apps-olt-api',
'//apps/cordconfig:onos-apps-cordconfig',
]
BUNDLES = [
'//apps/olt:onos-apps-olt-api',
'//apps/cordmcast:onos-apps-cordmcast',
]
osgi_jar_with_tests (
deps = COMPILE_DEPS,
)
onos_app (
title = 'CORD Multicast App',
category = 'Traffic Steering',
url = 'http://onosproject.org',
description = 'CORD Multicast application',
included_bundles = BUNDLES,
)
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-cord-mcast</artifactId>
<packaging>bundle</packaging>
<description>CORD Multicast application</description>
<properties>
<onos.app.name>org.onosproject.cordmcast</onos.app.name>
<onos.app.category>Traffic Steering</onos.app.category>
<onos.app.title>CORD Multicast App</onos.app.title>
<onos.app.url>http://opencord.org</onos.app.url>
<onos.app.requires>org.onosproject.cord-config</onos.app.requires>
</properties>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cord-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-misc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.22.2</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-olt-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
/*
* 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.cordmcast;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.VlanId;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.codec.CodecService;
import org.onosproject.codec.JsonCodec;
import org.onosproject.cordconfig.access.AccessDeviceConfig;
import org.onosproject.cordconfig.access.AccessDeviceData;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
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.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.DefaultNextObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.net.mcast.McastEvent;
import org.onosproject.net.mcast.McastListener;
import org.onosproject.net.mcast.McastRoute;
import org.onosproject.net.mcast.McastRouteInfo;
import org.onosproject.net.mcast.MulticastRouteService;
import org.onosproject.rest.AbstractWebResource;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.util.Dictionary;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.net.MediaType.JSON_UTF_8;
import static org.onlab.util.Tools.get;
import static org.slf4j.LoggerFactory.getLogger;
/**
* CORD multicast provisioning application. Operates by listening to
* events on the multicast rib and provisioning groups to program multicast
* flows on the dataplane.
*/
@Component(immediate = true)
public class CordMcast {
private static final int DEFAULT_REST_TIMEOUT_MS = 2000;
private static final int DEFAULT_PRIORITY = 500;
private static final short DEFAULT_MCAST_VLAN = 4000;
private static final String DEFAULT_SYNC_HOST = "10.90.0.8:8181";
private static final String DEFAULT_USER = "karaf";
private static final String DEFAULT_PASSWORD = "karaf";
private static final boolean DEFAULT_VLAN_ENABLED = true;
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MulticastRouteService mcastService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CodecService codecService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry networkConfig;
protected McastListener listener = new InternalMulticastListener();
private InternalNetworkConfigListener configListener =
new InternalNetworkConfigListener();
//TODO: move this to a ec map
private Map<IpAddress, Integer> groups = Maps.newConcurrentMap();
private ApplicationId appId;
@Property(name = "mcastVlan", intValue = DEFAULT_MCAST_VLAN,
label = "VLAN for multicast traffic")
private int mcastVlan = DEFAULT_MCAST_VLAN;
@Property(name = "vlanEnabled", boolValue = DEFAULT_VLAN_ENABLED,
label = "Use vlan for multicast traffic?")
private boolean vlanEnabled = DEFAULT_VLAN_ENABLED;
@Property(name = "priority", intValue = DEFAULT_PRIORITY,
label = "Priority for multicast rules")
private int priority = DEFAULT_PRIORITY;
@Property(name = "syncHost", value = DEFAULT_SYNC_HOST,
label = "host:port to synchronize routes to")
private String syncHost = DEFAULT_SYNC_HOST;
@Property(name = "username", value = DEFAULT_USER,
label = "Username for REST password authentication")
private String user = DEFAULT_USER;
@Property(name = "password", value = DEFAULT_PASSWORD,
label = "Password for REST authentication")
private String password = DEFAULT_PASSWORD;
private String fabricOnosUrl;
private Map<DeviceId, AccessDeviceData> oltData = new ConcurrentHashMap<>();
private static final Class<AccessDeviceConfig> CONFIG_CLASS =
AccessDeviceConfig.class;
private ConfigFactory<DeviceId, AccessDeviceConfig> configFactory =
new ConfigFactory<DeviceId, AccessDeviceConfig>(
SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessDevice") {
@Override
public AccessDeviceConfig createConfig() {
return new AccessDeviceConfig();
}
};
@Activate
public void activate(ComponentContext context) {
componentConfigService.registerProperties(getClass());
modified(context);
appId = coreService.registerApplication("org.onosproject.cordmcast");
clearRemoteRoutes();
networkConfig.registerConfigFactory(configFactory);
networkConfig.addListener(configListener);
networkConfig.getSubjects(DeviceId.class, AccessDeviceConfig.class).forEach(
subject -> {
AccessDeviceConfig config = networkConfig.getConfig(subject, AccessDeviceConfig.class);
if (config != null) {
AccessDeviceData data = config.getOlt();
oltData.put(data.deviceId(), data);
}
}
);
mcastService.addListener(listener);
mcastService.getRoutes().stream()
.map(r -> new ImmutablePair<>(r, mcastService.fetchSinks(r)))
.filter(pair -> pair.getRight() != null && !pair.getRight().isEmpty())
.forEach(pair -> pair.getRight().forEach(sink -> provisionGroup(pair.getLeft(),
sink)));
log.info("Started");
}
@Deactivate
public void deactivate() {
componentConfigService.unregisterProperties(getClass(), false);
mcastService.removeListener(listener);
networkConfig.unregisterConfigFactory(configFactory);
networkConfig.removeListener(configListener);
log.info("Stopped");
}
@Modified
public void modified(ComponentContext context) {
Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
try {
String s = get(properties, "username");
user = isNullOrEmpty(s) ? DEFAULT_USER : s.trim();
s = get(properties, "password");
password = isNullOrEmpty(s) ? DEFAULT_PASSWORD : s.trim();
s = get(properties, "mcastVlan");
mcastVlan = isNullOrEmpty(s) ? DEFAULT_MCAST_VLAN : Short.parseShort(s.trim());
s = get(properties, "vlanEnabled");
vlanEnabled = isNullOrEmpty(s) ? DEFAULT_VLAN_ENABLED : Boolean.parseBoolean(s.trim());
s = get(properties, "priority");
priority = isNullOrEmpty(s) ? DEFAULT_PRIORITY : Integer.parseInt(s.trim());
s = get(properties, "syncHost");
syncHost = isNullOrEmpty(s) ? DEFAULT_SYNC_HOST : s.trim();
} catch (Exception e) {
user = DEFAULT_USER;
password = DEFAULT_PASSWORD;
syncHost = DEFAULT_SYNC_HOST;
mcastVlan = DEFAULT_MCAST_VLAN;
vlanEnabled = false;
priority = DEFAULT_PRIORITY;
}
fabricOnosUrl = createRemoteUrl(syncHost);
}
private static String createRemoteUrl(String remoteHost) {
return "http://" + remoteHost + "/onos/v1/mcast";
}
private class InternalMulticastListener implements McastListener {
@Override
public void event(McastEvent event) {
McastRouteInfo info = event.subject();
switch (event.type()) {
case ROUTE_ADDED:
break;
case ROUTE_REMOVED:
break;
case SOURCE_ADDED:
break;
case SINK_ADDED:
if (!info.sink().isPresent()) {
log.warn("No sink given after sink added event: {}", info);
return;
}
provisionGroup(info.route(), info.sink().get());
break;
case SINK_REMOVED:
unprovisionGroup(event.subject());
break;
default:
log.warn("Unknown mcast event {}", event.type());
}
}
}
private void unprovisionGroup(McastRouteInfo info) {
if (info.sinks().isEmpty()) {
removeRemoteRoute(info.route());
}
if (!info.sink().isPresent()) {
log.warn("No sink given after sink removed event: {}", info);
return;
}
ConnectPoint loc = info.sink().get();
NextObjective next = DefaultNextObjective.builder()
.fromApp(appId)
.addTreatment(DefaultTrafficTreatment.builder().setOutput(loc.port()).build())
.withType(NextObjective.Type.BROADCAST)
.withId(groups.get(info.route().group()))
.removeFromExisting(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
//TODO: change to debug
log.info("Next Objective {} installed", objective.id());
}
@Override
public void onError(Objective objective, ObjectiveError error) {
//TODO: change to debug
log.info("Next Objective {} failed, because {}",
objective.id(),
error);
}
});
flowObjectiveService.next(loc.deviceId(), next);
}
private void provisionGroup(McastRoute route, ConnectPoint sink) {
checkNotNull(route, "Route cannot be null");
checkNotNull(sink, "Sink cannot be null");
AccessDeviceData oltInfo = oltData.get(sink.deviceId());
if (oltInfo == null) {
log.warn("Unknown OLT device : {}", sink.deviceId());
return;
}
final AtomicBoolean sync = new AtomicBoolean(false);
Integer nextId = groups.computeIfAbsent(route.group(), (g) -> {
Integer id = flowObjectiveService.allocateNextId();
NextObjective next = DefaultNextObjective.builder()
.fromApp(appId)
.addTreatment(DefaultTrafficTreatment.builder().setOutput(sink.port()).build())
.withType(NextObjective.Type.BROADCAST)
.withId(id)
.add(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
//TODO: change to debug
log.info("Next Objective {} installed", objective.id());
}
@Override
public void onError(Objective objective, ObjectiveError error) {
//TODO: change to debug
log.info("Next Objective {} failed, because {}",
objective.id(),
error);
}
});
flowObjectiveService.next(sink.deviceId(), next);
TrafficSelector.Builder mcast = DefaultTrafficSelector.builder()
.matchInPort(oltInfo.uplink())
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(g.toIpPrefix());
if (vlanEnabled) {
mcast.matchVlanId(VlanId.vlanId((short) mcastVlan));
}
ForwardingObjective fwd = DefaultForwardingObjective.builder()
.fromApp(appId)
.nextStep(id)
.makePermanent()
.withFlag(ForwardingObjective.Flag.VERSATILE)
.withPriority(priority)
.withSelector(mcast.build())
.add(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
//TODO: change to debug
log.info("Forwarding objective installed {}", objective);
}
@Override
public void onError(Objective objective, ObjectiveError error) {
//TODO: change to debug
log.info("Forwarding objective failed {}", objective);
}
});
flowObjectiveService.forward(sink.deviceId(), fwd);
sync.set(true);
return id;
});
if (!sync.get()) {
NextObjective next = DefaultNextObjective.builder()
.fromApp(appId)
.addTreatment(DefaultTrafficTreatment.builder().setOutput(sink.port()).build())
.withType(NextObjective.Type.BROADCAST)
.withId(nextId)
.addToExisting(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
//TODO: change to debug
log.info("Next Objective {} installed", objective.id());
}
@Override
public void onError(Objective objective, ObjectiveError error) {
//TODO: change to debug
log.info("Next Objective {} failed, because {}",
objective.id(),
error);
}
});
flowObjectiveService.next(sink.deviceId(), next);
}
addRemoteRoute(route);
}
private void addRemoteRoute(McastRoute route) {
checkNotNull(route);
if (syncHost == null) {
log.warn("No host configured for synchronization; route will be dropped");
return;
}
log.debug("Sending route {} to other ONOS {}", route, fabricOnosUrl);
Invocation.Builder builder = getClientBuilder(fabricOnosUrl);
ObjectNode json = codecService.getCodec(McastRoute.class)
.encode(route, new AbstractWebResource());
try {
builder.post(Entity.json(json.toString()));
} catch (ProcessingException e) {
log.warn("Unable to send route to remote controller: {}", e.getMessage());
}
}
private void removeRemoteRoute(McastRoute route) {
if (syncHost == null) {
log.warn("No host configured for synchronization; route will be dropped");
return;
}
log.debug("Removing route {} from other ONOS {}", route, fabricOnosUrl);
Invocation.Builder builder = getClientBuilder(fabricOnosUrl)
.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
ObjectNode json = codecService.getCodec(McastRoute.class)
.encode(route, new AbstractWebResource());
builder.method("DELETE", Entity.entity(json.asText(),
MediaType.APPLICATION_OCTET_STREAM));
}
private void clearRemoteRoutes() {
if (syncHost == null) {
log.warn("No host configured for synchronization");
return;
}
log.debug("Clearing remote multicast routes from {}", fabricOnosUrl);
Invocation.Builder builder = getClientBuilder(fabricOnosUrl);
List<McastRoute> mcastRoutes = Lists.newArrayList();
try {
String response = builder
.accept(MediaType.APPLICATION_JSON_TYPE)
.get(String.class);
JsonCodec<McastRoute> routeCodec = codecService.getCodec(McastRoute.class);
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = (ObjectNode) mapper.readTree(response);
ArrayNode list = (ArrayNode) node.path("routes");
list.forEach(n -> mcastRoutes.add(
routeCodec.decode((ObjectNode) n, new AbstractWebResource())));
} catch (IOException e) {
log.warn("Error clearing remote routes", e);
}
mcastRoutes.forEach(this::removeRemoteRoute);
}
private Invocation.Builder getClientBuilder(String uri) {
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
client.property(ClientProperties.CONNECT_TIMEOUT, DEFAULT_REST_TIMEOUT_MS);
client.property(ClientProperties.READ_TIMEOUT, DEFAULT_REST_TIMEOUT_MS);
client.register(HttpAuthenticationFeature.basic(user, password));
WebTarget wt = client.target(uri);
return wt.request(JSON_UTF_8.toString());
}
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
AccessDeviceConfig config =
networkConfig.getConfig((DeviceId) event.subject(), CONFIG_CLASS);
if (config != null) {
oltData.put(config.getOlt().deviceId(), config.getOlt());
}
break;
case CONFIG_REGISTERED:
case CONFIG_UNREGISTERED:
break;
case CONFIG_REMOVED:
oltData.remove(event.subject());
break;
default:
break;
}
}
@Override
public boolean isRelevant(NetworkConfigEvent event) {
return event.configClass().equals(CONFIG_CLASS);
}
}
}
/*
* 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.
*/
/**
* Application for provisioning multicast streams in the context of cord.
*/
package org.onosproject.cordmcast;
\ No newline at end of file
# app builds but is currently non functional. It needs transitive runtime
# dependencies.
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:org.apache.karaf.shell.console',
'//lib:javax.ws.rs-api',
'//lib:jsch',
'//utils/rest:onlab-rest',
'//cli:onos-cli',
'//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',
]
BUNDLES = [
'//apps/cordvtn:onos-apps-cordvtn',
]
EXCLUDED_BUNDLES = [
'//lib:jsch',
]
osgi_jar_with_tests (
deps = COMPILE_DEPS,
web_context = '/onos/cordvtn',
)
onos_app (
title = 'CORD VTN REST API',
category = 'Traffic Steering',
url = 'http://onosproject.org',
included_bundles = BUNDLES,
excluded_bundles = EXCLUDED_BUNDLES,
description = 'APIs for interacting with the CORD VTN application.',
required_apps = [ 'org.onosproject.cord-config', 'org.onosproject.xosclient', 'org.onosproject.dhcp', 'org.onosproject.ovsdb' ],
)
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<app name="org.onosproject.cordvtn" origin="ON.Lab" version="${project.version}"
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,org.onosproject.cord-config">
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</artifact>
</app>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
<feature name="${project.artifactId}" version="${project.version}" description="${project.description}">
<feature>onos-api</feature>
<bundle>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</bundle>
<bundle>wrap:mvn:com.jcraft/jsch/0.1.53$Bundle-SymbolicName=jsch&amp;Bundle-Version=0.1.53</bundle>
</feature>
</features>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-cordvtn</artifactId>
<packaging>bundle</packaging>
<description>Virtual tenant network service for CORD</description>
<properties>
<web.context>/onos/cordvtn</web.context>
<api.version>1.0.0</api.version>
<api.title>CORD VTN REST API</api.title>
<api.description>
APIs for interacting with the CORD VTN application.
</api.description>
<api.package>org.onosproject.cordvtn.rest</api.package>
</properties>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-ovsdb-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-dhcp-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-xos-client</artifactId>
<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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<_wab>src/main/webapp/</_wab>
<Include-Resource>
WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
{maven-resources}
</Include-Resource>
<Bundle-SymbolicName>
${project.groupId}.${project.artifactId}
</Bundle-SymbolicName>
<Import-Package>
*,org.glassfish.jersey.servlet
</Import-Package>
<Web-ContextPath>${web.context}</Web-ContextPath>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
/*
* 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.api;
/**
* Entity capable of handling a subject connected and disconnected situation.
*/
public interface ConnectionHandler<T> {
/**
* Processes the connected subject.
*
* @param subject subject
*/
void connected(T subject);
/**
* Processes the disconnected subject.
*
* @param subject subject.
*/
void disconnected(T subject);
}
/*
* 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.api;
import com.fasterxml.jackson.databind.JsonNode;
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;
import org.onosproject.net.DeviceId;
import org.onosproject.net.config.Config;
import org.onosproject.xosclient.api.OpenStackAccess;
import org.onosproject.xosclient.api.XosAccess;
import org.slf4j.Logger;
import java.util.Map;
import java.util.Set;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Configuration object for CordVtn service.
*/
public class CordVtnConfig extends Config<ApplicationId> {
protected final Logger log = getLogger(getClass());
public static final String PRIVATE_GATEWAY_MAC = "privateGatewayMac";
public static final String PUBLIC_GATEWAYS = "publicGateways";
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";
public static final String HOSTNAME = "hostname";
public static final String HOST_MANAGEMENT_IP = "hostManagementIp";
public static final String DATA_PLANE_IP = "dataPlaneIp";
public static final String DATA_PLANE_INTF = "dataPlaneIntf";
public static final String BRIDGE_ID = "bridgeId";
public static final String SSH = "ssh";
public static final String SSH_PORT = "sshPort";
public static final String SSH_USER = "sshUser";
public static final String SSH_KEY_FILE = "sshKeyFile";
public static final String OPENSTACK = "openstack";
public static final String XOS = "xos";
public static final String ENDPOINT = "endpoint";
public static final String TENANT = "tenant";
public static final String USER = "user";
public static final String PASSWORD = "password";
/**
* Returns the set of nodes read from network config.
*
* @return set of CordVtnNodeConfig or empty set
*/
public Set<CordVtnNode> cordVtnNodes() {
Set<CordVtnNode> nodes = Sets.newHashSet();
JsonNode cordvtnNodes = object.get(CORDVTN_NODES);
if (cordvtnNodes == null) {
log.debug("No CORD VTN nodes found");
return nodes;
}
JsonNode sshNode = object.get(SSH);
if (sshNode == null) {
log.warn("SSH information not found");
return nodes;
}
for (JsonNode cordvtnNode : cordvtnNodes) {
try {
NetworkAddress hostMgmt = NetworkAddress.valueOf(getConfig(cordvtnNode, HOST_MANAGEMENT_IP));
NetworkAddress localMgmt = NetworkAddress.valueOf(getConfig(object, LOCAL_MANAGEMENT_IP));
if (hostMgmt.prefix().contains(localMgmt.prefix()) ||
localMgmt.prefix().contains(hostMgmt.prefix())) {
log.error("hostMamt and localMgmt cannot be overlapped, skip this node");
continue;
}
Ip4Address hostMgmtIp = hostMgmt.ip().getIp4Address();
SshAccessInfo sshInfo = new SshAccessInfo(
hostMgmtIp,
TpPort.tpPort(Integer.parseInt(getConfig(sshNode, SSH_PORT))),
getConfig(sshNode, SSH_USER), getConfig(sshNode, SSH_KEY_FILE));
String hostname = getConfig(cordvtnNode, HOSTNAME);
CordVtnNode newNode = new CordVtnNode(
hostname, hostMgmt, localMgmt,
NetworkAddress.valueOf(getConfig(cordvtnNode, DATA_PLANE_IP)),
TpPort.tpPort(Integer.parseInt(getConfig(object, OVSDB_PORT))),
sshInfo,
DeviceId.deviceId(getConfig(cordvtnNode, BRIDGE_ID)),
getConfig(cordvtnNode, DATA_PLANE_INTF),
CordVtnNodeState.noState());
nodes.add(newNode);
} catch (IllegalArgumentException | NullPointerException e) {
log.error("{}", e);
}
}
return nodes;
}
/**
* Returns value of a given path. If the path is missing, show log and return
* null.
*
* @param path path
* @return value or null
*/
private String getConfig(JsonNode jsonNode, String path) {
jsonNode = jsonNode.path(path);
if (jsonNode.isMissingNode()) {
log.error("{} is not configured", path);
return null;
} else {
return jsonNode.asText();
}
}
/**
* Returns private network gateway MAC address.
*
* @return mac address, or null
*/
public MacAddress privateGatewayMac() {
JsonNode jsonNode = object.get(PRIVATE_GATEWAY_MAC);
if (jsonNode == null) {
return null;
}
try {
return MacAddress.valueOf(jsonNode.asText());
} catch (IllegalArgumentException e) {
log.error("Wrong MAC address format {}", jsonNode.asText());
return null;
}
}
/**
* Returns public network gateway IP and MAC address pairs.
*
* @return map of ip and mac address
*/
public Map<IpAddress, MacAddress> publicGateways() {
JsonNode jsonNodes = object.get(PUBLIC_GATEWAYS);
if (jsonNodes == null) {
return Maps.newHashMap();
}
Map<IpAddress, MacAddress> publicGateways = Maps.newHashMap();
jsonNodes.forEach(jsonNode -> {
try {
publicGateways.put(
IpAddress.valueOf(jsonNode.path(GATEWAY_IP).asText()),
MacAddress.valueOf(jsonNode.path(GATEWAY_MAC).asText()));
} catch (IllegalArgumentException | NullPointerException e) {
log.error("Wrong address format {}", e.toString());
}
});
return publicGateways;
}
/**
* 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
*/
public XosAccess xosAccess() {
JsonNode jsonNode = object.get(XOS);
if (jsonNode == null) {
return null;
}
try {
return new XosAccess(getConfig(jsonNode, ENDPOINT),
getConfig(jsonNode, USER),
getConfig(jsonNode, PASSWORD));
} catch (NullPointerException e) {
log.error("Failed to get XOS access");
return null;
}
}
/**
* Returns OpenStack API access information.
*
* @return openstack access
*/
public OpenStackAccess openstackAccess() {
JsonNode jsonNode = object.get(OPENSTACK);
if (jsonNode == null) {
log.error("Failed to get OpenStack configurations");
return null;
}
try {
return new OpenStackAccess(
jsonNode.path(ENDPOINT).asText(),
jsonNode.path(TENANT).asText(),
jsonNode.path(USER).asText(),
jsonNode.path(PASSWORD).asText());
} catch (IllegalArgumentException | NullPointerException e) {
log.error("Failed to get OpenStack configurations");
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.api;
import com.google.common.base.MoreObjects;
import org.onlab.packet.TpPort;
import org.onosproject.net.DeviceId;
import java.util.Comparator;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Representation of a compute infrastructure node for CORD VTN service.
*/
public final class CordVtnNode {
private final String hostname;
private final NetworkAddress hostMgmtIp;
private final NetworkAddress localMgmtIp;
private final NetworkAddress dpIp;
private final TpPort ovsdbPort;
private final SshAccessInfo sshInfo;
private final DeviceId bridgeId;
private final String dpIntf;
private final CordVtnNodeState state;
public static final Comparator<CordVtnNode> CORDVTN_NODE_COMPARATOR =
(node1, node2) -> node1.hostname().compareTo(node2.hostname());
/**
* Creates a new node.
*
* @param hostname hostname
* @param hostMgmtIp host management network address
* @param localMgmtIp local management network address
* @param dpIp data plane network address
* @param ovsdbPort port number for OVSDB connection
* @param sshInfo SSH access information
* @param bridgeId integration bridge identifier
* @param dpIntf data plane interface name
* @param state cordvtn node state
*/
public CordVtnNode(String hostname, NetworkAddress hostMgmtIp, NetworkAddress localMgmtIp,
NetworkAddress dpIp, TpPort ovsdbPort, SshAccessInfo sshInfo,
DeviceId bridgeId, String dpIntf, CordVtnNodeState state) {
this.hostname = checkNotNull(hostname, "hostname cannot be null");
this.hostMgmtIp = checkNotNull(hostMgmtIp, "hostMgmtIp cannot be null");
this.localMgmtIp = checkNotNull(localMgmtIp, "localMgmtIp cannot be null");
this.dpIp = checkNotNull(dpIp, "dpIp cannot be null");
this.ovsdbPort = checkNotNull(ovsdbPort, "ovsdbPort cannot be null");
this.sshInfo = checkNotNull(sshInfo, "sshInfo cannot be null");
this.bridgeId = checkNotNull(bridgeId, "bridgeId cannot be null");
this.dpIntf = checkNotNull(dpIntf, "dpIntf cannot be null");
this.state = state;
}
/**
* Returns cordvtn node with new state.
*
* @param node cordvtn node
* @param state cordvtn node init state
* @return cordvtn node
*/
public static CordVtnNode getUpdatedNode(CordVtnNode node, CordVtnNodeState state) {
return new CordVtnNode(node.hostname,
node.hostMgmtIp, node.localMgmtIp, node.dpIp,
node.ovsdbPort,
node.sshInfo,
node.bridgeId,
node.dpIntf, state);
}
/**
* Returns the hostname.
*
* @return hostname
*/
public String hostname() {
return this.hostname;
}
/**
* Returns the host management network address.
*
* @return network address
*/
public NetworkAddress hostMgmtIp() {
return this.hostMgmtIp;
}
/**
* Returns the local management network address.
*
* @return network address
*/
public NetworkAddress localMgmtIp() {
return this.localMgmtIp;
}
/**
* Returns the data plane network address.
*
* @return network address
*/
public NetworkAddress dpIp() {
return this.dpIp;
}
/**
* Returns the port number used for OVSDB connection.
*
* @return port number
*/
public TpPort ovsdbPort() {
return this.ovsdbPort;
}
/**
* Returns the SSH access information.
*
* @return ssh access information
*/
public SshAccessInfo sshInfo() {
return this.sshInfo;
}
/**
* Returns the identifier of the integration bridge.
*
* @return device id
*/
public DeviceId intBrId() {
return this.bridgeId;
}
/**
* Returns the identifier of the OVSDB device.
*
* @return device id
*/
public DeviceId ovsdbId() {
return DeviceId.deviceId("ovsdb:" + this.hostMgmtIp.ip().toString());
}
/**
* Returns data plane interface name.
*
* @return data plane interface name
*/
public String dpIntf() {
return this.dpIntf;
}
/**
* Returns the state of the node.
*
* @return state
*/
public CordVtnNodeState state() {
return this.state;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof CordVtnNode) {
CordVtnNode that = (CordVtnNode) obj;
if (Objects.equals(hostname, that.hostname) &&
Objects.equals(hostMgmtIp, that.hostMgmtIp) &&
Objects.equals(localMgmtIp, that.localMgmtIp) &&
Objects.equals(dpIp, that.dpIp) &&
Objects.equals(ovsdbPort, that.ovsdbPort) &&
Objects.equals(sshInfo, that.sshInfo) &&
Objects.equals(bridgeId, that.bridgeId) &&
Objects.equals(dpIntf, that.dpIntf)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(hostname, hostMgmtIp, localMgmtIp, dpIp,
ovsdbPort, sshInfo, bridgeId, dpIntf);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("hostname", hostname)
.add("hostMgmtIp", hostMgmtIp)
.add("localMgmtIp", localMgmtIp)
.add("dpIp", dpIp)
.add("port", ovsdbPort)
.add("sshInfo", sshInfo)
.add("bridgeId", bridgeId)
.add("dpIntf", dpIntf)
.add("state", state)
.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;
/**
* Entity that defines possible init state of the cordvtn node.
*/
public interface CordVtnNodeState {
/**
* Returns null for no state.
*
* @return null
*/
static CordVtnNodeState noState() {
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.api;
import org.onosproject.xosclient.api.VtnServiceId;
/**
* Service for provisioning overlay virtual networks on compute nodes.
*/
public interface CordVtnService {
String CORDVTN_APP_ID = "org.onosproject.cordvtn";
/**
* Creates dependencies for a given tenant service.
*
* @param tServiceId id of the service which has a dependency
* @param pServiceId id of the service which provide dependency
* @param isBidirectional true to enable bidirectional connectivity between two services
*/
void createServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId,
boolean isBidirectional);
/**
* Removes all dependencies from a given tenant service.
*
* @param tServiceId id of the service which has a dependency
* @param pServiceId id of the service which provide dependency
*/
void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId);
}
/*
* 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);
}
/*
* 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.MoreObjects;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Representation of a network address, which consists of IP address and prefix.
*/
public final class NetworkAddress {
private final IpAddress ip;
private final IpPrefix prefix;
/**
* Constructor for a given IP address and prefix.
*
* @param ip ip address
* @param prefix ip prefix
*/
public NetworkAddress(IpAddress ip, IpPrefix prefix) {
this.ip = ip;
this.prefix = prefix;
}
/**
* Converts a CIDR notation string into a network address.
*
* @param cidr cidr
* @return network address
* @throws IllegalArgumentException if the cidr is not valid
*/
public static NetworkAddress valueOf(String cidr) {
checkArgument(cidr.contains("/"));
IpAddress ipAddress = IpAddress.valueOf(cidr.split("/")[0]);
IpPrefix ipPrefix = IpPrefix.valueOf(cidr);
return new NetworkAddress(ipAddress, ipPrefix);
}
/**
* Returns the IP address value of the network address.
*
* @return ip address
*/
public IpAddress ip() {
return this.ip;
}
/**
* Returns the IP prefix value of the network address.
*
* @return ip prefix
*/
public IpPrefix prefix() {
return this.prefix;
}
/**
* Converts a network address to a CIDR notation.
*
* @return cidr notation string
*/
public String cidr() {
return ip.toString() + "/" + prefix.prefixLength();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof NetworkAddress) {
NetworkAddress that = (NetworkAddress) obj;
if (Objects.equals(ip, that.ip) && Objects.equals(prefix, that.prefix)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(ip, prefix);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("IpAddress", ip)
.add("IpPrefix", prefix)
.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;
import com.google.common.base.MoreObjects;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.TpPort;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Representation of SSH access information.
*/
public final class SshAccessInfo {
private final Ip4Address remoteIp;
private final TpPort port;
private final String user;
private final String privateKey;
/**
* Creates a new SSH access information.
*
* @param remoteIp ssh remote ip address
* @param port ssh port number
* @param user user name
* @param privateKey path of ssh private key
*/
public SshAccessInfo(Ip4Address remoteIp, TpPort port, String user, String privateKey) {
this.remoteIp = checkNotNull(remoteIp);
this.port = checkNotNull(port);
this.user = checkNotNull(user);
this.privateKey = checkNotNull(privateKey);
}
/**
* Returns the remote IP address.
*
* @return ip address
*/
public Ip4Address remoteIp() {
return this.remoteIp;
}
/**
* Returns the port number.
*
* @return ssh port
*/
public TpPort port() {
return this.port;
}
/**
* Returns the user name.
*
* @return user name
*/
public String user() {
return this.user;
}
/**
* Returns the private key path.
*
* @return privateKey
*/
public String privateKey() {
return privateKey;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof SshAccessInfo) {
SshAccessInfo that = (SshAccessInfo) obj;
if (Objects.equals(remoteIp, that.remoteIp) &&
Objects.equals(port, that.port) &&
Objects.equals(user, that.user) &&
Objects.equals(privateKey, that.privateKey)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(remoteIp, port, user, privateKey);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("remoteIp", remoteIp)
.add("port", port)
.add("user", user)
.add("privateKey", privateKey)
.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.
*/
/**
* API for CORD VTN application.
*/
package org.onosproject.cordvtn.api;
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.cordvtn.cli;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cordvtn.impl.CordVtnPipeline;
/**
* Deletes nodes from the service.
*/
@Command(scope = "onos", name = "cordvtn-flush-rules",
description = "Flush flow rules installed by CORD VTN")
public class CordVtnFlushRules extends AbstractShellCommand {
@Override
protected void execute() {
CordVtnPipeline pipeline = AbstractShellCommand.get(CordVtnPipeline.class);
pipeline.flushRules();
print("Successfully flushed");
}
}
/*
* 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.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cordvtn.api.CordVtnNode;
import org.onosproject.cordvtn.impl.CordVtnNodeManager;
import org.onosproject.net.Device;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.driver.DriverService;
/**
* Checks detailed node init state.
*/
@Command(scope = "onos", name = "cordvtn-node-check",
description = "Shows detailed node init state")
public class CordVtnNodeCheckCommand extends AbstractShellCommand {
@Argument(index = 0, name = "hostname", description = "Hostname",
required = true, multiValued = false)
private String hostname = null;
@Override
protected void execute() {
CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);
CordVtnNode node = nodeManager.getNodes()
.stream()
.filter(n -> n.hostname().equals(hostname))
.findFirst()
.orElse(null);
if (node == null) {
print("Cannot find %s from registered nodes", hostname);
return;
}
print(nodeManager.checkNodeInitState(node));
print("%n[DEBUG]");
Device device = deviceService.getDevice(node.intBrId());
String driver = get(DriverService.class).getDriver(device.id()).name();
print("%s available=%s driver=%s %s",
device.id(),
deviceService.isAvailable(device.id()),
driver,
device.annotations());
deviceService.getPorts(node.intBrId()).forEach(port -> {
Object portIsEnabled = port.isEnabled() ? "enabled" : "disabled";
print("port=%s state=%s %s",
port.number(),
portIsEnabled,
port.annotations());
});
}
}
/*
* 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.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cordvtn.impl.CordVtnNodeManager;
import org.onosproject.cordvtn.api.CordVtnNode;
import java.util.NoSuchElementException;
/**
* Deletes nodes from the service.
*/
@Command(scope = "onos", name = "cordvtn-node-delete",
description = "Deletes nodes from CORD VTN service")
public class CordVtnNodeDeleteCommand extends AbstractShellCommand {
@Argument(index = 0, name = "hostnames", description = "Hostname(s)",
required = true, multiValued = true)
private String[] hostnames = null;
@Override
protected void execute() {
CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
for (String hostname : hostnames) {
CordVtnNode node;
try {
node = nodeManager.getNodes()
.stream()
.filter(n -> n.hostname().equals(hostname))
.findFirst().get();
} catch (NoSuchElementException e) {
print("Unable to find %s", hostname);
continue;
}
nodeManager.deleteNode(node);
}
}
}
/*
* 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.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cordvtn.impl.CordVtnNodeManager;
import org.onosproject.cordvtn.api.CordVtnNode;
import java.util.NoSuchElementException;
/**
* Initializes nodes for CordVtn service.
*/
@Command(scope = "onos", name = "cordvtn-node-init",
description = "Initializes nodes for CORD VTN service")
public class CordVtnNodeInitCommand extends AbstractShellCommand {
@Argument(index = 0, name = "hostnames", description = "Hostname(s)",
required = true, multiValued = true)
private String[] hostnames = null;
@Override
protected void execute() {
CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
for (String hostname : hostnames) {
CordVtnNode node;
try {
node = nodeManager.getNodes()
.stream()
.filter(n -> n.hostname().equals(hostname))
.findFirst().get();
} catch (NoSuchElementException e) {
print("Unable to find %s", hostname);
continue;
}
nodeManager.addOrUpdateNode(node);
}
}
}
/*
* 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.cli;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cordvtn.impl.CordVtnNodeManager;
import org.onosproject.cordvtn.api.CordVtnNode;
import java.util.Collections;
import java.util.List;
/**
* Lists all nodes registered to the service.
*/
@Command(scope = "onos", name = "cordvtn-nodes",
description = "Lists all nodes registered in CORD VTN service")
public class CordVtnNodeListCommand extends AbstractShellCommand {
private static final String COMPLETE = "COMPLETE";
private static final String INCOMPLETE = "INCOMPLETE";
@Override
protected void execute() {
CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
List<CordVtnNode> nodes = nodeManager.getNodes();
Collections.sort(nodes, CordVtnNode.CORDVTN_NODE_COMPARATOR);
if (outputJson()) {
print("%s", json(nodeManager, nodes));
} else {
for (CordVtnNode node : nodes) {
print("hostname=%s, hostMgmtIp=%s, dpIp=%s, br-int=%s, dpIntf=%s, init=%s",
node.hostname(),
node.hostMgmtIp().cidr(),
node.dpIp().cidr(),
node.intBrId().toString(),
node.dpIntf(),
getState(nodeManager, node));
}
print("Total %s nodes", nodeManager.getNodeCount());
}
}
private JsonNode json(CordVtnNodeManager nodeManager, List<CordVtnNode> nodes) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (CordVtnNode node : nodes) {
result.add(mapper.createObjectNode()
.put("hostname", node.hostname())
.put("hostManagementIp", node.hostMgmtIp().cidr())
.put("dataPlaneIp", node.dpIp().cidr())
.put("bridgeId", node.intBrId().toString())
.put("dataPlaneInterface", node.dpIntf())
.put("init", getState(nodeManager, node)));
}
return result;
}
private String getState(CordVtnNodeManager nodeManager, CordVtnNode node) {
return nodeManager.isNodeInitComplete(node) ? COMPLETE : INCOMPLETE;
}
}
/*
* 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.
*/
/**
* Console commands to manage OVSDB nodes for cordvtn.
*/
package org.onosproject.cordvtn.cli;
\ No newline at end of file
/*
* 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.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.Ip4Prefix;
import org.onosproject.cordvtn.api.CordVtnNode;
import org.onosproject.cordvtn.api.CordVtnService;
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.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.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.xosclient.api.VtnService;
import org.onosproject.xosclient.api.VtnServiceId;
import org.slf4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static 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 service dependency capabilities between network services.
*/
@Component(immediate = true)
@Service
public class CordVtn extends CordVtnInstanceHandler implements CordVtnService {
protected final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected GroupService groupService;
@Activate
protected void activate() {
eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
hostListener = new InternalHostListener();
super.activate();
}
@Deactivate
protected void deactivate() {
super.deactivate();
}
@Override
public void createServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId,
boolean isBidirectional) {
VtnService tService = getVtnService(tServiceId);
VtnService pService = getVtnService(pServiceId);
if (tService == null || pService == null) {
log.error("Failed to create dependency between {} and {}",
tServiceId, pServiceId);
return;
}
log.info("Created dependency between {} and {}", tService.name(), pService.name());
serviceDependencyRules(tService, pService, isBidirectional, true);
}
@Override
public void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId) {
VtnService tService = getVtnService(tServiceId);
VtnService pService = getVtnService(pServiceId);
if (tService == null || pService == null) {
log.error("Failed to remove dependency between {} and {}",
tServiceId, pServiceId);
return;
}
log.info("Removed dependency between {} and {}", tService.name(), pService.name());
serviceDependencyRules(tService, pService, true, false);
}
@Override
public void instanceDetected(Instance instance) {
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
return;
}
// 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));
updateProviderServiceInstances(service);
}
@Override
public void instanceRemoved(Instance instance) {
VtnService service = getVtnService(instance.serviceId());
if (service == null) {
return;
}
if (!service.providerServices().isEmpty()) {
removeInstanceFromTenantService(instance, service);
}
if (!service.tenantServices().isEmpty()) {
updateProviderServiceInstances(service);
}
}
private void updateProviderServiceInstances(VtnService service) {
GroupKey groupKey = getGroupKey(service.id());
Set<DeviceId> devices = nodeManager.completeNodes().stream()
.map(CordVtnNode::intBrId)
.collect(Collectors.toSet());
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;
}
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 = Lists.newArrayList(oldBuckets);
bucketsToRemove.removeAll(newBuckets);
if (!bucketsToRemove.isEmpty()) {
groupService.removeBucketsFromGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToRemove),
groupKey, appId);
}
List<GroupBucket> bucketsToAdd = Lists.newArrayList(newBuckets);
bucketsToAdd.removeAll(oldBuckets);
if (!bucketsToAdd.isEmpty()) {
groupService.addBucketsToGroup(
deviceId,
groupKey,
new GroupBuckets(bucketsToAdd),
groupKey, appId);
}
}
}
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();
inPorts.put(instance.deviceId(), Sets.newHashSet(instance.portNumber()));
outGroups.put(instance.deviceId(), getGroupId(pServiceId, instance.deviceId()));
inServiceRule(inPorts, outGroups, false);
});
}
private void serviceDependencyRules(VtnService tService, VtnService pService,
boolean isBidirectional, boolean install) {
Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
nodeManager.completeNodes().stream().forEach(node -> {
DeviceId deviceId = node.intBrId();
GroupId groupId = createServiceGroup(deviceId, pService);
outGroups.put(deviceId, groupId);
Set<PortNumber> tServiceInstances = getInstances(tService.id())
.stream()
.filter(instance -> instance.deviceId().equals(deviceId))
.map(Instance::portNumber)
.collect(Collectors.toSet());
inPorts.put(deviceId, tServiceInstances);
});
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);
});
}
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();
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(PRIORITY_DEFAULT)
.forDevice(deviceId)
.forTable(TABLE_IN_SERVICE)
.makePermanent()
.build();
pipeline.processFlowRule(install, flowRule);
});
}
}
private GroupId getGroupId(VtnServiceId serviceId, DeviceId deviceId) {
return new DefaultGroupId(Objects.hash(serviceId, deviceId));
}
private GroupKey getGroupKey(VtnServiceId serviceId) {
return new DefaultGroupKey(serviceId.id().getBytes());
}
private GroupId createServiceGroup(DeviceId deviceId, VtnService 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;
}
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();
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()));
});
return new GroupBuckets(buckets);
}
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;
}
}
}
}
/*
* 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 com.google.common.collect.Maps;
import org.onlab.packet.ARP;
import org.onlab.packet.EthType;
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;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketService;
import org.slf4j.Logger;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Handles ARP requests for virtual network service IPs.
*/
public class CordVtnArpProxy {
protected final Logger log = getLogger(getClass());
private final ApplicationId appId;
private final PacketService packetService;
private final HostService hostService;
private final Map<Ip4Address, MacAddress> gateways = Maps.newConcurrentMap();
/**
* Default constructor.
*
* @param appId application id
* @param packetService packet service
* @param hostService host service reference
*/
public CordVtnArpProxy(ApplicationId appId, PacketService packetService, HostService hostService) {
this.appId = appId;
this.packetService = packetService;
this.hostService = hostService;
}
/**
* Requests ARP packet.
*/
public void requestPacket() {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(EthType.EtherType.ARP.ethType().toShort())
.build();
packetService.requestPackets(selector,
PacketPriority.CONTROL,
appId,
Optional.empty());
}
/**
* Cancels ARP packet.
*/
public void cancelPacket() {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(EthType.EtherType.ARP.ethType().toShort())
.build();
packetService.cancelPackets(selector,
PacketPriority.CONTROL,
appId,
Optional.empty());
}
/**
* Adds a given gateway IP and MAC address to this ARP proxy.
*
* @param gatewayIp gateway ip address
* @param gatewayMac gateway mac address
*/
public void addGateway(IpAddress gatewayIp, MacAddress gatewayMac) {
checkNotNull(gatewayIp);
checkNotNull(gatewayMac);
gateways.put(gatewayIp.getIp4Address(), gatewayMac);
}
/**
* Removes a given service IP address from this ARP proxy.
*
* @param gatewayIp gateway ip address
*/
public void removeGateway(IpAddress gatewayIp) {
checkNotNull(gatewayIp);
gateways.remove(gatewayIp.getIp4Address());
}
/**
* Emits ARP reply with fake MAC address for a given ARP request.
* It only handles requests for the registered service IPs, and the other
* requests can be handled by other ARP handlers like openstackSwitching or
* proxyArp, for example.
*
* @param context packet context
* @param ethPacket ethernet packet
*/
public void processArpPacket(PacketContext context, Ethernet ethPacket) {
ARP arpPacket = (ARP) ethPacket.getPayload();
if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
return;
}
Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
MacAddress gatewayMac = gateways.get(targetIp);
MacAddress replyMac = gatewayMac != null ? gatewayMac : getMacFromHostService(targetIp);
if (replyMac.equals(MacAddress.NONE)) {
log.debug("Failed to find MAC for {}", targetIp.toString());
context.block();
return;
}
log.trace("Send ARP reply for {} with {}", targetIp.toString(), replyMac.toString());
Ethernet ethReply = ARP.buildArpReply(
targetIp,
replyMac,
ethPacket);
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(context.inPacket().receivedFrom().port())
.build();
packetService.emit(new DefaultOutboundPacket(
context.inPacket().receivedFrom().deviceId(),
treatment,
ByteBuffer.wrap(ethReply.serialize())));
context.block();
}
/**
* Emits gratuitous ARP when a gateway mac address has been changed.
*
* @param gatewayIp gateway ip address to update MAC
* @param instances set of instances to send gratuitous ARP packet
*/
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());
return;
}
Ethernet ethArp = buildGratuitousArp(gatewayIp.getIp4Address(), gatewayMac);
instances.stream().forEach(instance -> {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(instance.portNumber())
.build();
packetService.emit(new DefaultOutboundPacket(
instance.deviceId(),
treatment,
ByteBuffer.wrap(ethArp.serialize())));
});
}
/**
* Builds gratuitous ARP packet with a given IP and MAC address.
*
* @param ip ip address for TPA and SPA
* @param mac new mac address
* @return ethernet packet
*/
private Ethernet buildGratuitousArp(IpAddress ip, MacAddress mac) {
Ethernet eth = new Ethernet();
eth.setEtherType(Ethernet.TYPE_ARP);
eth.setSourceMACAddress(mac);
eth.setDestinationMACAddress(MacAddress.BROADCAST);
ARP arp = new ARP();
arp.setOpCode(ARP.OP_REQUEST);
arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
arp.setProtocolType(ARP.PROTO_TYPE_IP);
arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
arp.setSenderHardwareAddress(mac.toBytes());
arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
arp.setSenderProtocolAddress(ip.getIp4Address().toOctets());
arp.setTargetProtocolAddress(ip.getIp4Address().toOctets());
eth.setPayload(arp);
return eth;
}
/**
* Returns MAC address of a host with a given target IP address by asking to
* host service. It does not support overlapping IP.
*
* @param targetIp target ip
* @return mac address, or NONE mac address if it fails to find the mac
*/
private MacAddress getMacFromHostService(IpAddress targetIp) {
checkNotNull(targetIp);
Host host = hostService.getHostsByIp(targetIp)
.stream()
.findFirst()
.orElse(null);
if (host != null) {
log.trace("Found MAC from host service for {}", targetIp.toString());
return host.mac();
} else {
return MacAddress.NONE;
}
}
}
/*
* 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.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.dhcp.IpAssignment;
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.Date;
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.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
import static org.onosproject.xosclient.api.VtnService.NetworkType.MANAGEMENT;
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");
private static final int DHCP_INFINITE_LEASE = -1;
@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) {
Ip4Address broadcast = Ip4Address.makeMaskedAddress(
instance.ipAddress(),
service.subnet().prefixLength());
IpAssignment.Builder ipBuilder = IpAssignment.builder()
.ipAddress(instance.ipAddress())
.leasePeriod(DHCP_INFINITE_LEASE)
.timestamp(new Date())
.subnetMask(Ip4Address.makeMaskPrefix(service.subnet().prefixLength()))
.broadcast(broadcast)
.domainServer(DEFAULT_DNS)
.assignmentStatus(Option_RangeNotEnforced);
if (service.networkType() != MANAGEMENT) {
ipBuilder = ipBuilder.routerAddress(service.serviceIp().getIp4Address());
}
log.debug("Set static DHCP mapping for {} {}", instance.mac(), instance.ipAddress());
dhcpService.setStaticMapping(instance.mac(), ipBuilder.build());
}
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;
}
}
}
}
/*
* 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 com.google.common.collect.Sets;
import com.jcraft.jsch.Session;
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.IpAddress;
import org.onlab.util.ItemNotFoundException;
import org.onlab.util.KryoNamespace;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.LeadershipService;
import org.onosproject.cluster.NodeId;
import org.onosproject.cordvtn.api.ConnectionHandler;
import org.onosproject.cordvtn.api.CordVtnConfig;
import org.onosproject.cordvtn.api.CordVtnNode;
import org.onosproject.cordvtn.api.CordVtnNodeState;
import org.onosproject.cordvtn.api.CordVtnService;
import org.onosproject.cordvtn.api.NetworkAddress;
import org.onosproject.cordvtn.api.SshAccessInfo;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultAnnotations;
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;
import org.onosproject.net.behaviour.DefaultTunnelDescription;
import org.onosproject.net.behaviour.TunnelConfig;
import org.onosproject.net.behaviour.TunnelDescription;
import org.onosproject.net.behaviour.TunnelName;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceAdminService;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.host.HostService;
import org.onosproject.ovsdb.controller.OvsdbClientService;
import org.onosproject.ovsdb.controller.OvsdbController;
import org.onosproject.ovsdb.controller.OvsdbNodeId;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.MapEvent;
import org.onosproject.store.service.MapEventListener;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.onosproject.store.service.Versioned;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
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 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;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Reads node information from the network config file and handles the config
* update events.
* Only a leader controller performs the node addition or deletion.
*/
@Component(immediate = true)
@Service(value = CordVtnNodeManager.class)
public class CordVtnNodeManager {
protected final Logger log = getLogger(getClass());
private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(CordVtnNode.class)
.register(NodeState.class)
.register(SshAccessInfo.class)
.register(NetworkAddress.class);
private static final String DEFAULT_BRIDGE = "br-int";
private static final String VPORT_PREFIX = "tap";
private static final String OK = "OK";
private static final String NO = "NO";
private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS = new HashMap<String, String>() {
{
put("key", "flow");
put("remote_ip", "flow");
}
};
private static final int DPID_BEGIN = 3;
private static final int OFPORT = 6653;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService configService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StorageService storageService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceAdminService adminService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OvsdbController controller;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterService clusterService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LeadershipService leadershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CordVtnInstanceManager instanceManager;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CordVtnPipeline pipeline;
private final ExecutorService eventExecutor =
newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-node", "event-handler"));
private final NetworkConfigListener configListener = new InternalConfigListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
private final MapEventListener<String, CordVtnNode> nodeStoreListener = new InternalMapListener();
private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
private final BridgeHandler bridgeHandler = new BridgeHandler();
private ConsistentMap<String, CordVtnNode> nodeStore;
private ApplicationId appId;
private NodeId localNodeId;
private enum NodeState implements CordVtnNodeState {
INIT {
@Override
public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
if (!nodeManager.isOvsdbConnected(node)) {
nodeManager.connectOvsdb(node);
} else {
nodeManager.createIntegrationBridge(node);
}
}
},
BRIDGE_CREATED {
@Override
public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
if (!nodeManager.isOvsdbConnected(node)) {
nodeManager.connectOvsdb(node);
} else {
nodeManager.createTunnelInterface(node);
nodeManager.addDataPlaneInterface(node);
}
}
},
PORTS_ADDED {
@Override
public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
nodeManager.setIpAddress(node);
}
},
COMPLETE {
@Override
public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
nodeManager.postInit(node);
}
},
INCOMPLETE {
@Override
public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
}
};
public abstract void process(CordVtnNodeManager nodeManager, CordVtnNode node);
}
@Activate
protected void activate() {
appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
localNodeId = clusterService.getLocalNode().id();
leadershipService.runForLeadership(appId.name());
nodeStore = storageService.<String, CordVtnNode>consistentMapBuilder()
.withSerializer(Serializer.using(NODE_SERIALIZER.build()))
.withName("cordvtn-nodestore")
.withApplicationId(appId)
.build();
nodeStore.addListener(nodeStoreListener);
deviceService.addListener(deviceListener);
configService.addListener(configListener);
log.info("Started");
}
@Deactivate
protected void deactivate() {
configService.removeListener(configListener);
deviceService.removeListener(deviceListener);
nodeStore.removeListener(nodeStoreListener);
leadershipService.withdraw(appId.name());
eventExecutor.shutdown();
log.info("Stopped");
}
/**
* Adds or updates a new node to the service.
*
* @param node cordvtn node
*/
public void addOrUpdateNode(CordVtnNode node) {
checkNotNull(node);
nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, getNodeState(node)));
}
/**
* Deletes a node from the service.
*
* @param node cordvtn node
*/
public void deleteNode(CordVtnNode node) {
checkNotNull(node);
if (isOvsdbConnected(node)) {
disconnectOvsdb(node);
}
nodeStore.remove(node.hostname());
}
/**
* Returns node initialization state.
*
* @param node cordvtn node
* @return true if initial node setup is completed, otherwise false
*/
public boolean isNodeInitComplete(CordVtnNode node) {
checkNotNull(node);
return nodeStore.containsKey(node.hostname()) && getNodeState(node).equals(NodeState.COMPLETE);
}
/**
* Returns detailed node initialization state.
*
* @param node cordvtn node
* @return string including detailed node init state
*/
public String checkNodeInitState(CordVtnNode node) {
checkNotNull(node);
if (!nodeStore.containsKey(node.hostname())) {
log.warn("Node {} does not exist, add node first", node.hostname());
return null;
}
Session session = connect(node.sshInfo());
if (session == null) {
log.debug("Failed to SSH to {}", node.hostname());
return null;
}
Set<IpAddress> intBrIps = getCurrentIps(session, DEFAULT_BRIDGE);
String result = String.format(
"Current state : %s%n" +
"br-int created and connected to ONOS : %s (%s)%n" +
"VXLAN interface added to br-int : %s%n" +
"Data plane interface is added to br-int and enabled : %s (%s)%n" +
"IP flushed from data plane interface : %s (%s)%n" +
"Data plane IP added to br-int : %s (%s)%n" +
"Local management IP added to br-int : %s (%s)",
node.state(),
isBrIntCreated(node) ? OK : NO, node.intBrId(),
isTunnelIntfCreated(node) ? OK : NO,
isDataPlaneIntfAdded(node) ? OK : NO, node.dpIntf(),
isInterfaceUp(session, node.dpIntf()) &&
getCurrentIps(session, node.dpIntf()).isEmpty() ? OK : NO, node.dpIntf(),
intBrIps.contains(node.dpIp().ip()) ? OK : NO, node.dpIp().cidr(),
intBrIps.contains(node.localMgmtIp().ip()) ? OK : NO, node.localMgmtIp().cidr());
disconnect(session);
return result;
}
/**
* Returns the number of the nodes known to the service.
*
* @return number of nodes
*/
public int getNodeCount() {
return nodeStore.size();
}
/**
* Returns all nodes known to the service.
*
* @return list of nodes
*/
public List<CordVtnNode> getNodes() {
return nodeStore.values().stream().map(Versioned::value).collect(Collectors.toList());
}
/**
* Returns all nodes in complete state.
*
* @return set of nodes
*/
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 physical data plane IP address of a given device.
*
* @param deviceId integration bridge device id
* @return ip address; null otherwise
*/
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();
}
/**
* 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);
}
/**
* 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);
}
/**
* Performs tasks after node initialization.
* It disconnects unnecessary OVSDB connection and installs initial flow
* rules on the device.
*
* @param node cordvtn node
*/
private void postInit(CordVtnNode node) {
disconnectOvsdb(node);
pipeline.initPipeline(node, dpPort(node.intBrId()), tunnelPort(node.intBrId()));
deviceService.getPorts(node.intBrId()).stream()
.filter(port -> portName(port).startsWith(VPORT_PREFIX) &&
port.isEnabled())
.forEach(port -> instanceManager.addInstance(connectPoint(port)));
hostService.getHosts().forEach(host -> {
if (deviceService.getPort(host.location().deviceId(),
host.location().port()) == null) {
instanceManager.removeInstance(connectPoint(host));
}
});
log.info("Finished init {}", node.hostname());
}
/**
* Sets a new state for a given cordvtn node.
*
* @param node cordvtn node
* @param newState new node state
*/
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;
}
}
/**
* Returns connection state of OVSDB server for a given node.
*
* @param node cordvtn node
* @return true if it is connected, false otherwise
*/
private boolean isOvsdbConnected(CordVtnNode node) {
checkNotNull(node);
OvsdbClientService ovsdbClient = getOvsdbClient(node);
return deviceService.isAvailable(node.ovsdbId()) &&
ovsdbClient != null && ovsdbClient.isConnected();
}
/**
* Connects to OVSDB server for a given node.
*
* @param node cordvtn node
*/
private void connectOvsdb(CordVtnNode node) {
checkNotNull(node);
if (!nodeStore.containsKey(node.hostname())) {
log.warn("Node {} does not exist", node.hostname());
return;
}
if (!isOvsdbConnected(node)) {
controller.connect(node.hostMgmtIp().ip(), node.ovsdbPort());
}
}
/**
* Disconnects OVSDB server for a given node.
*
* @param node cordvtn node
*/
private void disconnectOvsdb(CordVtnNode node) {
checkNotNull(node);
if (!nodeStore.containsKey(node.hostname())) {
log.warn("Node {} does not exist", node.hostname());
return;
}
if (isOvsdbConnected(node)) {
OvsdbClientService ovsdbClient = getOvsdbClient(node);
ovsdbClient.disconnect();
}
}
/**
* Returns OVSDB client for a given node.
*
* @param node cordvtn node
* @return OVSDB client, or null if it fails to get OVSDB client
*/
private OvsdbClientService getOvsdbClient(CordVtnNode node) {
checkNotNull(node);
OvsdbClientService ovsdbClient = controller.getOvsdbClient(
new OvsdbNodeId(node.hostMgmtIp().ip(), node.ovsdbPort().toInt()));
if (ovsdbClient == null) {
log.trace("Couldn't find OVSDB client for {}", node.hostname());
}
return ovsdbClient;
}
/**
* Creates an integration bridge for a given node.
*
* @param node cordvtn node
*/
private void createIntegrationBridge(CordVtnNode node) {
if (isBrIntCreated(node)) {
return;
}
List<ControllerInfo> controllers = new ArrayList<>();
Sets.newHashSet(clusterService.getNodes()).stream()
.forEach(controller -> {
ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
controllers.add(ctrlInfo);
});
String dpid = node.intBrId().toString().substring(DPID_BEGIN);
try {
Device device = deviceService.getDevice(node.ovsdbId());
if (device.is(BridgeConfig.class)) {
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());
}
} catch (ItemNotFoundException e) {
log.warn("Failed to create integration bridge on {}", node.hostname());
}
}
/**
* Creates tunnel interface to the integration bridge for a given node.
*
* @param node cordvtn node
*/
private void createTunnelInterface(CordVtnNode node) {
if (isTunnelIntfCreated(node)) {
return;
}
DefaultAnnotations.Builder optionBuilder = DefaultAnnotations.builder();
for (String key : DEFAULT_TUNNEL_OPTIONS.keySet()) {
optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
}
TunnelDescription description = new DefaultTunnelDescription(
null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
optionBuilder.build());
try {
Device device = deviceService.getDevice(node.ovsdbId());
if (device.is(TunnelConfig.class)) {
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());
}
} catch (ItemNotFoundException e) {
log.warn("Failed to create tunnel interface on {}", node.hostname());
}
}
/**
* Adds data plane interface to a given node.
*
* @param node cordvtn node
*/
private void addDataPlaneInterface(CordVtnNode node) {
if (isDataPlaneIntfAdded(node)) {
return;
}
Session session = connect(node.sshInfo());
if (session == null) {
log.debug("Failed to SSH to {}", node.hostname());
return;
}
if (!isInterfaceUp(session, node.dpIntf())) {
log.warn("Interface {} is not available", node.dpIntf());
return;
}
disconnect(session);
try {
Device device = deviceService.getDevice(node.ovsdbId());
if (device.is(BridgeConfig.class)) {
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());
}
} catch (ItemNotFoundException e) {
log.warn("Failed to add {} on {}", node.dpIntf(), node.hostname());
}
}
/**
* Flushes IP address from data plane interface and adds data plane IP address
* to integration bridge.
*
* @param node cordvtn node
*/
private void setIpAddress(CordVtnNode node) {
Session session = connect(node.sshInfo());
if (session == null) {
log.debug("Failed to SSH to {}", node.hostname());
return;
}
getCurrentIps(session, DEFAULT_BRIDGE).stream()
.filter(ip -> !ip.equals(node.localMgmtIp().ip()))
.filter(ip -> !ip.equals(node.dpIp().ip()))
.forEach(ip -> deleteIp(session, ip, DEFAULT_BRIDGE));
boolean result = flushIp(session, node.dpIntf()) &&
setInterfaceUp(session, node.dpIntf()) &&
addIp(session, node.dpIp(), DEFAULT_BRIDGE) &&
addIp(session, node.localMgmtIp(), DEFAULT_BRIDGE) &&
setInterfaceUp(session, DEFAULT_BRIDGE);
disconnect(session);
if (result) {
setNodeState(node, NodeState.COMPLETE);
}
}
/**
* Checks if integration bridge exists and available.
*
* @param node cordvtn node
* @return true if the bridge is available, false otherwise
*/
private boolean isBrIntCreated(CordVtnNode node) {
return (deviceService.getDevice(node.intBrId()) != null
&& deviceService.isAvailable(node.intBrId()));
}
/**
* Checks if tunnel interface exists.
*
* @param node cordvtn node
* @return true if the interface exists, false otherwise
*/
private boolean isTunnelIntfCreated(CordVtnNode node) {
return deviceService.getPorts(node.intBrId())
.stream()
.filter(p -> portName(p).contains(DEFAULT_TUNNEL) &&
p.isEnabled())
.findAny().isPresent();
}
/**
* Checks if data plane interface exists.
*
* @param node cordvtn node
* @return true if the interface exists, false otherwise
*/
private boolean isDataPlaneIntfAdded(CordVtnNode node) {
return deviceService.getPorts(node.intBrId())
.stream()
.filter(p -> portName(p).contains(node.dpIntf()) &&
p.isEnabled())
.findAny().isPresent();
}
/**
* Checks if the IP addresses are correctly set.
*
* @param node cordvtn node
* @return true if the IP is set, false otherwise
*/
private boolean isIpAddressSet(CordVtnNode node) {
Session session = connect(node.sshInfo());
if (session == null) {
log.debug("Failed to SSH to {}", node.hostname());
return false;
}
Set<IpAddress> intBrIps = getCurrentIps(session, DEFAULT_BRIDGE);
boolean result = getCurrentIps(session, node.dpIntf()).isEmpty() &&
isInterfaceUp(session, node.dpIntf()) &&
intBrIps.contains(node.dpIp().ip()) &&
intBrIps.contains(node.localMgmtIp().ip()) &&
isInterfaceUp(session, DEFAULT_BRIDGE);
disconnect(session);
return result;
}
/**
* Returns connect point of a given port.
*
* @param port port
* @return connect point
*/
private ConnectPoint connectPoint(Port port) {
return new ConnectPoint(port.element().id(), port.number());
}
/**
* Returns connect point of a given host.
*
* @param host host
* @return connect point
*/
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 = nodeByOvsdbId(device.id());
if (node != null) {
setNodeState(node, getNodeState(node));
} else {
log.debug("{} is detected on unregistered node, ignore it.", device.id());
}
}
@Override
public void disconnected(Device device) {
if (!deviceService.isAvailable(device.id())) {
log.debug("Device {} is disconnected", device.id());
adminService.removeDevice(device.id());
}
}
}
private class BridgeHandler implements ConnectionHandler<Device> {
@Override
public void connected(Device device) {
CordVtnNode node = nodeByBridgeId(device.id());
if (node != null) {
setNodeState(node, getNodeState(node));
} else {
log.debug("{} is detected on unregistered node, ignore it.", device.id());
}
}
@Override
public void disconnected(Device device) {
CordVtnNode node = nodeByBridgeId(device.id());
if (node != null) {
log.debug("Integration Bridge is disconnected from {}", node.hostname());
setNodeState(node, NodeState.INCOMPLETE);
}
}
/**
* Handles port added situation.
* If the added port is tunnel or data plane interface, proceed to the remaining
* node initialization. Otherwise, do nothing.
*
* @param port port
*/
public void portAdded(Port 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);
return;
}
log.info("Port {} is added to {}", portName, node.hostname());
if (portName.startsWith(VPORT_PREFIX)) {
if (isNodeStateComplete(node)) {
instanceManager.addInstance(connectPoint(port));
} else {
log.debug("VM is detected on incomplete node, ignore it.", portName);
}
} else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
setNodeState(node, getNodeState(node));
}
}
/**
* Handles port removed situation.
* If the removed port is tunnel or data plane interface, proceed to the remaining
* node initialization.Others, do nothing.
*
* @param port port
*/
public void portRemoved(Port port) {
CordVtnNode node = nodeByBridgeId((DeviceId) port.element().id());
String portName = portName(port);
if (node == null) {
return;
}
log.info("Port {} is removed from {}", portName, node.hostname());
if (portName.startsWith(VPORT_PREFIX)) {
if (isNodeStateComplete(node)) {
instanceManager.removeInstance(connectPoint(port));
} else {
log.debug("VM is vanished from incomplete node, ignore it.", portName);
}
} else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
setNodeState(node, NodeState.INCOMPLETE);
}
}
}
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
if (!Objects.equals(localNodeId, leaderNodeId)) {
// do not allow to proceed without leadership
return;
}
Device device = event.subject();
ConnectionHandler<Device> handler =
(device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
switch (event.type()) {
case PORT_ADDED:
eventExecutor.execute(() -> bridgeHandler.portAdded(event.port()));
break;
case PORT_UPDATED:
if (!event.port().isEnabled()) {
eventExecutor.execute(() -> bridgeHandler.portRemoved(event.port()));
}
break;
case DEVICE_ADDED:
case DEVICE_AVAILABILITY_CHANGED:
if (deviceService.isAvailable(device.id())) {
eventExecutor.execute(() -> handler.connected(device));
} else {
eventExecutor.execute(() -> handler.disconnected(device));
}
break;
default:
break;
}
}
}
/**
* Reads cordvtn nodes from config file.
*/
private void readConfiguration() {
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found");
return;
}
config.cordVtnNodes().forEach(this::addOrUpdateNode);
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
if (!Objects.equals(localNodeId, leaderNodeId)) {
// do not allow to proceed without leadership
return;
}
if (!event.configClass().equals(CordVtnConfig.class)) {
return;
}
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
eventExecutor.execute(CordVtnNodeManager.this::readConfiguration);
break;
default:
break;
}
}
}
private class InternalMapListener implements MapEventListener<String, CordVtnNode> {
@Override
public void event(MapEvent<String, CordVtnNode> event) {
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
if (!Objects.equals(localNodeId, leaderNodeId)) {
// do not allow to proceed without leadership
return;
}
CordVtnNode oldNode;
CordVtnNode newNode;
switch (event.type()) {
case UPDATE:
oldNode = event.oldValue().value();
newNode = event.newValue().value();
log.info("Reloaded {}", newNode.hostname());
if (!newNode.equals(oldNode)) {
log.debug("New node: {}", newNode);
}
// performs init procedure even if the node is not changed
// for robustness since it's no harm to run init procedure
// multiple times
eventExecutor.execute(() -> initNode(newNode));
break;
case INSERT:
newNode = event.newValue().value();
log.info("Added {}", newNode.hostname());
eventExecutor.execute(() -> initNode(newNode));
break;
case REMOVE:
oldNode = event.oldValue().value();
log.info("Removed {}", oldNode.hostname());
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;
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 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 com.google.common.collect.Sets;
import com.google.common.io.CharStreams;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.onlab.packet.IpAddress;
import org.onosproject.cordvtn.api.NetworkAddress;
import org.onosproject.cordvtn.api.SshAccessInfo;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger;
/**
* {@code RemoteIpCommandUtil} provides methods to help execute Linux IP commands to a remote server.
* It opens individual exec channels for each command. User can create a session with {@code connect}
* method and then execute a series commands. After done with all commands, the session must be closed
* explicitly by calling {@code disconnect}.
*/
public final class RemoteIpCommandUtil {
protected static final Logger log = getLogger(RemoteIpCommandUtil.class);
private static final String STRICT_HOST_CHECKING = "StrictHostKeyChecking";
private static final String DEFAULT_STRICT_HOST_CHECKING = "no";
private static final int DEFAULT_SESSION_TIMEOUT = 60000; // milliseconds
private static final String IP_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
private static final String IP_ADDR_SHOW = "sudo ip addr show %s";
private static final String IP_ADDR_FLUSH = "sudo ip addr flush %s";
private static final String IP_ADDR_ADD = "sudo ip addr add %s dev %s";
private static final String IP_ADDR_DELETE = "sudo ip addr delete %s dev %s";
private static final String IP_LINK_SHOW = "sudo ip link show %s";
private static final String IP_LINK_UP = "sudo ip link set %s up";
/**
* Default constructor.
*/
private RemoteIpCommandUtil() {
}
/**
* Adds a given IP address to a given device.
*
* @param session ssh connection
* @param ip network address
* @param device device name to assign the ip address
* @return true if the command succeeds, or false
*/
public static boolean addIp(Session session, NetworkAddress ip, String device) {
if (session == null || !session.isConnected()) {
return false;
}
executeCommand(session, String.format(IP_ADDR_ADD, ip.cidr(), device));
Set<IpAddress> result = getCurrentIps(session, device);
return result.contains(ip.ip());
}
/**
* Removes the IP address from a given device.
*
* @param session ssh connection
* @param ip ip address
* @param device device name
* @return true if the command succeeds, or false
*/
public static boolean deleteIp(Session session, IpAddress ip, String device) {
if (session == null || !session.isConnected()) {
return false;
}
executeCommand(session, String.format(IP_ADDR_DELETE, ip, device));
Set<IpAddress> result = getCurrentIps(session, device);
return !result.contains(ip);
}
/**
* Removes all IP address on a given device.
*
* @param session ssh connection
* @param device device name
* @return true if the command succeeds, or false
*/
public static boolean flushIp(Session session, String device) {
if (session == null || !session.isConnected()) {
return false;
}
executeCommand(session, String.format(IP_ADDR_FLUSH, device));
return getCurrentIps(session, device).isEmpty();
}
/**
* Returns a set of IP address that a given device has.
*
* @param session ssh connection
* @param device device name
* @return set of IP prefix or empty set
*/
public static Set<IpAddress> getCurrentIps(Session session, String device) {
if (session == null || !session.isConnected()) {
return Sets.newHashSet();
}
String output = executeCommand(session, String.format(IP_ADDR_SHOW, device));
Set<IpAddress> result = Pattern.compile(" |/")
.splitAsStream(output)
.filter(s -> s.matches(IP_PATTERN))
.map(IpAddress::valueOf)
.collect(Collectors.toSet());
return result;
}
/**
* Sets link state up for a given device.
*
* @param session ssh connection
* @param device device name
* @return true if the command succeeds, or false
*/
public static boolean setInterfaceUp(Session session, String device) {
if (session == null || !session.isConnected()) {
return false;
}
executeCommand(session, String.format(IP_LINK_UP, device));
return isInterfaceUp(session, device);
}
/**
* Checks if a given interface is up or not.
*
* @param session ssh connection
* @param device device name
* @return true if the interface is up, or false
*/
public static boolean isInterfaceUp(Session session, String device) {
if (session == null || !session.isConnected()) {
return false;
}
String output = executeCommand(session, String.format(IP_LINK_SHOW, device));
return output != null && output.contains("UP");
}
/**
* Creates a new session with a given access information.
*
* @param sshInfo information to ssh to the remove server
* @return ssh session, or null
*/
public static Session connect(SshAccessInfo sshInfo) {
try {
JSch jsch = new JSch();
jsch.addIdentity(sshInfo.privateKey());
Session session = jsch.getSession(sshInfo.user(),
sshInfo.remoteIp().toString(),
sshInfo.port().toInt());
session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
session.connect(DEFAULT_SESSION_TIMEOUT);
return session;
} catch (JSchException e) {
log.debug("Failed to connect to {} due to {}", sshInfo.toString(), e.toString());
return null;
}
}
/**
* Closes a connection.
*
* @param session session
*/
public static void disconnect(Session session) {
if (session.isConnected()) {
session.disconnect();
}
}
/**
* Executes a given command. It opens exec channel for the command and closes
* the channel when it's done.
*
* @param session ssh connection to a remote server
* @param command command to execute
* @return command output string if the command succeeds, or null
*/
private static String executeCommand(Session session, String command) {
if (session == null || !session.isConnected()) {
return null;
}
log.trace("Execute command {} to {}", command, session.getHost());
try {
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
InputStream output = channel.getInputStream();
channel.connect();
String result = CharStreams.toString(new InputStreamReader(output));
channel.disconnect();
return result;
} catch (JSchException | IOException e) {
log.debug("Failed to execute command {} due to {}", command, e.toString());
return null;
}
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Implementation for CORD VTN application.
*/
package org.onosproject.cordvtn.impl;
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.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
/*
* 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.rest;
import org.onlab.rest.AbstractWebApplication;
import java.util.Set;
/**
* CORD VTN Web application.
*/
public class CordVtnWebApplication extends AbstractWebApplication {
@Override
public Set<Class<?>> getClasses() {
return getClasses(ServiceDependencyWebResource.class,
NeutronMl2NetworksWebResource.class,
NeutronMl2SubnetsWebResource.class,
NeutronMl2PortsWebResource.class);
}
}
/*
* 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.rest;
import org.onosproject.rest.AbstractWebResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.InputStream;
/**
* Dummy Neutron ML2 mechanism driver.
* It just returns OK for networks resource requests.
*/
@Path("networks")
public class NeutronMl2NetworksWebResource extends AbstractWebResource {
protected final Logger log = LoggerFactory.getLogger(getClass());
private static final String NETWORKS_MESSAGE = "Received networks %s";
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createNetwork(InputStream input) {
log.trace(String.format(NETWORKS_MESSAGE, "create"));
return Response.status(Response.Status.OK).build();
}
@PUT
@Path("{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response updateNetwork(@PathParam("id") String id, InputStream input) {
log.trace(String.format(NETWORKS_MESSAGE, "update"));
return Response.status(Response.Status.OK).build();
}
@DELETE
@Path("{id}")
public Response deleteNetwork(@PathParam("id") String id) {
log.trace(String.format(NETWORKS_MESSAGE, "delete"));
return Response.noContent().build();
}
}
/*
* 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.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.impl.service.VsgInstanceHandler;
import org.onosproject.net.HostId;
import org.onosproject.rest.AbstractWebResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.InputStream;
import java.util.Map;
/**
* Dummy Neutron ML2 mechanism driver.
* It just returns OK for ports resource requests except for the port update.
*/
@Path("ports")
public class NeutronMl2PortsWebResource extends AbstractWebResource {
protected final Logger log = LoggerFactory.getLogger(getClass());
private static final String PORTS_MESSAGE = "Received ports %s";
private static final String PORT = "port";
private static final String DEVICE_ID = "device_id";
private static final String NAME = "name";
private static final String MAC_ADDRESS = "mac_address";
private static final String ADDRESS_PAIRS = "allowed_address_pairs";
private static final String IP_ADDERSS = "ip_address";
private static final String STAG_PREFIX = "stag-";
private static final int STAG_BEGIN_INDEX = 5;
private final VsgInstanceHandler service = DefaultServiceDirectory.getService(VsgInstanceHandler.class);
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createPorts(InputStream input) {
log.trace(String.format(PORTS_MESSAGE, "create"));
return Response.status(Response.Status.OK).build();
}
@PUT
@Path("{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
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);
log.trace("{}", jsonNode.toString());
String deviceId = jsonNode.path(DEVICE_ID).asText();
String name = jsonNode.path(NAME).asText();
if (deviceId.isEmpty() || name.isEmpty() || !name.startsWith(STAG_PREFIX)) {
// ignore all updates other than allowed address pairs
return Response.status(Response.Status.OK).build();
}
// this is allowed address pairs updates
MacAddress mac = MacAddress.valueOf(jsonNode.path(MAC_ADDRESS).asText());
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());
vsgInstances.put(pairIp, pairMac);
});
service.updateVsgInstances(HostId.hostId(mac),
name.substring(STAG_BEGIN_INDEX),
vsgInstances);
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
return Response.status(Response.Status.OK).build();
}
@Path("{id}")
@DELETE
public Response deletePorts(@PathParam("id") String id) {
log.trace(String.format(PORTS_MESSAGE, "delete"));
return Response.noContent().build();
}
}
/*
* 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.rest;
import org.onosproject.rest.AbstractWebResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.InputStream;
/**
* Dummy Neutron ML2 mechanism driver.
* It just returns OK for subnets resource requests.
*/
@Path("subnets")
public class NeutronMl2SubnetsWebResource extends AbstractWebResource {
protected final Logger log = LoggerFactory.getLogger(getClass());
private static final String SUBNETS_MESSAGE = "Received subnets %s";
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createSubnet(InputStream input) {
log.trace(String.format(SUBNETS_MESSAGE, "create"));
return Response.status(Response.Status.OK).build();
}
@PUT
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response updateSubnet(@PathParam("id") String id, InputStream input) {
log.trace(String.format(SUBNETS_MESSAGE, "update"));
return Response.status(Response.Status.OK).build();
}
@DELETE
@Path("{id}")
public Response deleteSubnet(@PathParam("id") String id) {
log.trace(String.format(SUBNETS_MESSAGE, "delete"));
return Response.noContent().build();
}
}
/*
* 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.rest;
import org.onosproject.cordvtn.api.CordVtnService;
import org.onosproject.rest.AbstractWebResource;
import org.onosproject.xosclient.api.VtnServiceId;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* Manages service dependency.
*/
@Path("service-dependency")
public class ServiceDependencyWebResource extends AbstractWebResource {
private final CordVtnService service = get(CordVtnService.class);
private static final String BIDIRECTION = "b";
/**
* Creates service dependencies with unidirectional access between the services.
*
* @param tServiceId tenant service id
* @param pServiceId provider service id
* @return 200 OK
*/
@POST
@Path("{tenantServiceId}/{providerServiceId}")
@Produces(MediaType.APPLICATION_JSON)
public Response createServiceDependency(@PathParam("tenantServiceId") String tServiceId,
@PathParam("providerServiceId") String pServiceId) {
service.createServiceDependency(VtnServiceId.of(tServiceId),
VtnServiceId.of(pServiceId),
false);
return Response.status(Response.Status.OK).build();
}
/**
* Creates service dependencies with an access type extension between the services.
*
* @param tServiceId tenant service id
* @param pServiceId provider service id
* @param direction b for bidirectional access, otherwise unidirectional access
* @return 200 OK
*/
@POST
@Path("{tenantServiceId}/{providerServiceId}/{direction}")
@Produces(MediaType.APPLICATION_JSON)
public Response createServiceDependency(@PathParam("tenantServiceId") String tServiceId,
@PathParam("providerServiceId") String pServiceId,
@PathParam("direction") String direction) {
service.createServiceDependency(VtnServiceId.of(tServiceId),
VtnServiceId.of(pServiceId),
direction.equals(BIDIRECTION));
return Response.status(Response.Status.OK).build();
}
/**
* Removes service dependencies.
*
* @param tServiceId tenant service id
* @param pServiceId provider service id
* @return 204 NO CONTENT
*/
@DELETE
@Path("{tenantServiceId}/{providerServiceId}")
public Response removeServiceDependency(@PathParam("tenantServiceId") String tServiceId,
@PathParam("providerServiceId") String pServiceId) {
service.removeServiceDependency(VtnServiceId.of(tServiceId), VtnServiceId.of(pServiceId));
return Response.noContent().build();
}
}
/*
* 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.
*/
/**
* REST APIs for CORD VTN.
*/
package org.onosproject.cordvtn.rest;
\ No newline at end of file
<!--
~ 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.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onosproject.cordvtn.cli.CordVtnNodeListCommand"/>
</command>
<command>
<action class="org.onosproject.cordvtn.cli.CordVtnNodeDeleteCommand"/>
</command>
<command>
<action class="org.onosproject.cordvtn.cli.CordVtnNodeInitCommand"/>
</command>
<command>
<action class="org.onosproject.cordvtn.cli.CordVtnNodeCheckCommand"/>
</command>
<command>
<action class="org.onosproject.cordvtn.cli.CordVtnFlushRules"/>
</command>
</command-bundle>
</blueprint>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="ONOS" version="2.5">
<display-name>CORD VTN REST API v1.0</display-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>admin</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>karaf</realm-name>
</login-config>
<servlet>
<servlet-name>JAX-RS Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.onosproject.cordvtn.rest.CordVtnWebApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Service</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:org.apache.karaf.shell.console',
'//cli:onos-cli',
'//apps/olt:onos-apps-olt-api',
'//apps/cordconfig:onos-apps-cordconfig',
]
BUNDLES = [
'//apps/olt:onos-apps-olt-api',
'//apps/igmp:onos-apps-igmp',
]
osgi_jar_with_tests (
deps = COMPILE_DEPS,
)
onos_app (
title = 'IGMP App',
category = 'Traffic Steering',
url = 'http://onosproject.org',
description = 'Internet Group Message Protocol',
included_bundles = BUNDLES,
)
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-igmp</artifactId>
<packaging>bundle</packaging>
<description>Internet Group Message Protocol</description>
<properties>
<onos.app.name>org.onosproject.igmp</onos.app.name>
<onos.app.title>IGMP App</onos.app.title>
<onos.app.category>Traffic Steering</onos.app.category>
<onos.app.url>http://onosproject.org</onos.app.url>
<onos.app.readme>IGMP implementation.</onos.app.readme>
<onos.app.requires>org.onosproject.cord-config</onos.app.requires>
</properties>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-olt-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-osgi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cord-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
<!-- This is needed by ComponentContext, used for tunable configuration -->
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
/*
* 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.igmp;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IGMP;
import org.onlab.packet.IGMPMembership;
import org.onlab.packet.IGMPQuery;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.util.SafeRecurringTask;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.cordconfig.access.AccessDeviceConfig;
import org.onosproject.cordconfig.access.AccessDeviceData;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
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.NetworkConfigRegistry;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flowobjective.DefaultFilteringObjective;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.net.mcast.McastRoute;
import org.onosproject.net.mcast.MulticastRouteService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Internet Group Management Protocol.
*/
@Component(immediate = true)
public class IgmpSnoop {
private final Logger log = getLogger(getClass());
private static final String DEST_MAC = "01:00:5E:00:00:01";
private static final String DEST_IP = "224.0.0.1";
private static final int DEFAULT_QUERY_PERIOD_SECS = 60;
private static final byte DEFAULT_IGMP_RESP_CODE = 100;
@Property(name = "multicastAddress",
label = "Define the multicast base range to listen to")
private String multicastAddress = IpPrefix.IPV4_MULTICAST_PREFIX.toString();
@Property(name = "queryPeriod", intValue = DEFAULT_QUERY_PERIOD_SECS,
label = "Delay in seconds between successive query runs")
private int queryPeriod = DEFAULT_QUERY_PERIOD_SECS;
@Property(name = "maxRespCode", byteValue = DEFAULT_IGMP_RESP_CODE,
label = "Maximum time allowed before sending a responding report")
private byte maxRespCode = DEFAULT_IGMP_RESP_CODE;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry networkConfig;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MulticastRouteService multicastService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
private ScheduledFuture<?> queryTask;
private final ScheduledExecutorService queryService =
Executors.newSingleThreadScheduledExecutor(groupedThreads("onos/igmp-query",
"membership-query"));
private Map<DeviceId, AccessDeviceData> oltData = new ConcurrentHashMap<>();
private Map<IpAddress, IpAddress> ssmTranslateTable = new ConcurrentHashMap<>();
private DeviceListener deviceListener = new InternalDeviceListener();
private IgmpPacketProcessor processor = new IgmpPacketProcessor();
private static ApplicationId appId;
private InternalNetworkConfigListener configListener =
new InternalNetworkConfigListener();
private static final Class<AccessDeviceConfig> CONFIG_CLASS =
AccessDeviceConfig.class;
private static final Class<IgmpSsmTranslateConfig> SSM_TRANSLATE_CONFIG_CLASS =
IgmpSsmTranslateConfig.class;
private ConfigFactory<DeviceId, AccessDeviceConfig> configFactory =
new ConfigFactory<DeviceId, AccessDeviceConfig>(
SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessDevice") {
@Override
public AccessDeviceConfig createConfig() {
return new AccessDeviceConfig();
}
};
private ConfigFactory<ApplicationId, IgmpSsmTranslateConfig> ssmTranslateConfigFactory =
new ConfigFactory<ApplicationId, IgmpSsmTranslateConfig>(
SubjectFactories.APP_SUBJECT_FACTORY, SSM_TRANSLATE_CONFIG_CLASS, "ssmTranslate", true) {
@Override
public IgmpSsmTranslateConfig createConfig() {
return new IgmpSsmTranslateConfig();
}
};
private ByteBuffer queryPacket;
@Activate
public void activate(ComponentContext context) {
componentConfigService.registerProperties(getClass());
modified(context);
appId = coreService.registerApplication("org.onosproject.igmp");
packetService.addProcessor(processor, PacketProcessor.director(1));
networkConfig.registerConfigFactory(configFactory);
networkConfig.registerConfigFactory(ssmTranslateConfigFactory);
networkConfig.addListener(configListener);
networkConfig.getSubjects(DeviceId.class, AccessDeviceConfig.class).forEach(
subject -> {
AccessDeviceConfig config = networkConfig.getConfig(subject,
AccessDeviceConfig.class);
if (config != null) {
AccessDeviceData data = config.getOlt();
oltData.put(data.deviceId(), data);
}
}
);
IgmpSsmTranslateConfig ssmTranslateConfig =
networkConfig.getConfig(appId, IgmpSsmTranslateConfig.class);
if (ssmTranslateConfig != null) {
Collection<McastRoute> translations = ssmTranslateConfig.getSsmTranslations();
for (McastRoute route : translations) {
ssmTranslateTable.put(route.group(), route.source());
}
}
oltData.keySet().stream()
.flatMap(did -> deviceService.getPorts(did).stream())
.filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
.filter(p -> p.isEnabled())
.forEach(p -> processFilterObjective((DeviceId) p.element().id(), p, false));
deviceService.addListener(deviceListener);
restartQueryTask();
log.info("Started");
}
@Deactivate
public void deactivate() {
packetService.removeProcessor(processor);
processor = null;
deviceService.removeListener(deviceListener);
networkConfig.removeListener(configListener);
networkConfig.unregisterConfigFactory(configFactory);
networkConfig.unregisterConfigFactory(ssmTranslateConfigFactory);
queryTask.cancel(true);
queryService.shutdownNow();
componentConfigService.unregisterProperties(getClass(), false);
log.info("Stopped");
}
@Modified
protected void modified(ComponentContext context) {
Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
// TODO read multicastAddress from config
String strQueryPeriod = Tools.get(properties, "queryPeriod");
String strResponseCode = Tools.get(properties, "maxRespCode");
try {
byte newMaxRespCode = Byte.parseByte(strResponseCode);
if (maxRespCode != newMaxRespCode) {
maxRespCode = newMaxRespCode;
queryPacket = buildQueryPacket();
}
int newQueryPeriod = Integer.parseInt(strQueryPeriod);
if (newQueryPeriod != queryPeriod) {
queryPeriod = newQueryPeriod;
restartQueryTask();
}
} catch (NumberFormatException e) {
log.warn("Error parsing config input", e);
}
log.info("queryPeriod set to {}", queryPeriod);
log.info("maxRespCode set to {}", maxRespCode);
}
private void restartQueryTask() {
if (queryTask != null) {
queryTask.cancel(true);
}
queryPacket = buildQueryPacket();
queryTask = queryService.scheduleWithFixedDelay(
SafeRecurringTask.wrap(this::querySubscribers),
0,
queryPeriod,
TimeUnit.SECONDS);
}
private void processFilterObjective(DeviceId devId, Port port, boolean remove) {
//TODO migrate to packet requests when packet service uses filtering objectives
DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
builder = remove ? builder.deny() : builder.permit();
FilteringObjective igmp = builder
.withKey(Criteria.matchInPort(port.number()))
.addCondition(Criteria.matchEthType(EthType.EtherType.IPV4.ethType()))
.addCondition(Criteria.matchIPProtocol(IPv4.PROTOCOL_IGMP))
.withMeta(DefaultTrafficTreatment.builder()
.setOutput(PortNumber.CONTROLLER).build())
.fromApp(appId)
.withPriority(10000)
.add(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
log.info("Igmp filter for {} on {} installed.",
devId, port);
}
@Override
public void onError(Objective objective, ObjectiveError error) {
log.info("Igmp filter for {} on {} failed because {}.",
devId, port, error);
}
});
flowObjectiveService.filter(devId, igmp);
}
private void processMembership(IGMP pkt, ConnectPoint location) {
pkt.getGroups().forEach(group -> {
if (!(group instanceof IGMPMembership)) {
log.warn("Wrong group type in IGMP membership");
return;
}
IGMPMembership membership = (IGMPMembership) group;
IpAddress groupAddress = membership.getGaddr();
if (membership.getRecordType() == IGMPMembership.MODE_IS_INCLUDE ||
membership.getRecordType() == IGMPMembership.CHANGE_TO_INCLUDE_MODE) {
if (membership.getSources().isEmpty()) {
McastRoute route = ssmTranslateRoute(groupAddress);
if (route != null) {
removeRoute(route, location);
}
} else {
membership.getSources().stream()
.map(source -> new McastRoute(source, groupAddress, McastRoute.Type.IGMP))
.forEach(route -> addRoute(route, location));
}
} else if (membership.getRecordType() == IGMPMembership.MODE_IS_EXCLUDE ||
membership.getRecordType() == IGMPMembership.CHANGE_TO_EXCLUDE_MODE) {
if (membership.getSources().isEmpty()) {
McastRoute route = ssmTranslateRoute(groupAddress);
if (route != null) {
addRoute(route, location);
}
} else {
membership.getSources().stream()
.map(source -> new McastRoute(source, groupAddress, McastRoute.Type.IGMP))
.forEach(route -> removeRoute(route, location));
}
}
});
}
private McastRoute ssmTranslateRoute(IpAddress group) {
IpAddress source = ssmTranslateTable.get(group);
if (source == null) {
log.warn("No SSM translate source found for group {}", group);
return null;
}
return new McastRoute(source, group, McastRoute.Type.IGMP);
}
private void addRoute(McastRoute route, ConnectPoint location) {
multicastService.add(route);
multicastService.addSink(route, location);
}
private void removeRoute(McastRoute route, ConnectPoint location) {
multicastService.removeSink(route, location);
// TODO remove route if all sinks are gone
}
private ByteBuffer buildQueryPacket() {
IGMP igmp = new IGMP();
igmp.setIgmpType(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY);
igmp.setMaxRespCode(maxRespCode);
IGMPQuery query = new IGMPQuery(IpAddress.valueOf("0.0.0.0"), 0);
igmp.addGroup(query);
IPv4 ip = new IPv4();
ip.setDestinationAddress(DEST_IP);
ip.setProtocol(IPv4.PROTOCOL_IGMP);
ip.setSourceAddress("192.168.1.1");
ip.setTtl((byte) 1);
ip.setPayload(igmp);
Ethernet eth = new Ethernet();
eth.setDestinationMACAddress(DEST_MAC);
eth.setSourceMACAddress("DE:AD:BE:EF:BA:11");
eth.setEtherType(Ethernet.TYPE_IPV4);
eth.setPayload(ip);
eth.setPad(true);
return ByteBuffer.wrap(eth.serialize());
}
private void querySubscribers() {
oltData.keySet().stream()
.flatMap(did -> deviceService.getPorts(did).stream())
.filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
.filter(p -> p.isEnabled())
.forEach(p -> {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(p.number()).build();
packetService.emit(new DefaultOutboundPacket((DeviceId) p.element().id(),
treatment, queryPacket));
});
}
/**
* Packet processor responsible for handling IGMP packets.
*/
private class IgmpPacketProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
// Stop processing if the packet has been handled, since we
// can't do any more to it.
if (context.isHandled()) {
return;
}
InboundPacket pkt = context.inPacket();
Ethernet ethPkt = pkt.parsed();
if (ethPkt == null) {
return;
}
// IPv6 MLD packets are handled by ICMP6. We'll only deal with IPv4.
if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
return;
}
IPv4 ip = (IPv4) ethPkt.getPayload();
IpAddress gaddr = IpAddress.valueOf(ip.getDestinationAddress());
IpAddress saddr = Ip4Address.valueOf(ip.getSourceAddress());
log.trace("Packet ({}, {}) -> ingress port: {}", saddr, gaddr,
context.inPacket().receivedFrom());
if (ip.getProtocol() != IPv4.PROTOCOL_IGMP ||
!IpPrefix.IPV4_MULTICAST_PREFIX.contains(gaddr)) {
return;
}
if (IpPrefix.IPV4_MULTICAST_PREFIX.contains(saddr)) {
log.debug("IGMP Picked up a packet with a multicast source address.");
return;
}
IGMP igmp = (IGMP) ip.getPayload();
switch (igmp.getIgmpType()) {
case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
processMembership(igmp, pkt.receivedFrom());
break;
case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
log.debug("Received a membership query {} from {}",
igmp, pkt.receivedFrom());
break;
case IGMP.TYPE_IGMPV1_MEMBERSHIP_REPORT:
case IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT:
case IGMP.TYPE_IGMPV2_LEAVE_GROUP:
log.debug("IGMP version 1 & 2 message types are not currently supported. Message type: {}",
igmp.getIgmpType());
break;
default:
log.warn("Unknown IGMP message type: {}", igmp.getIgmpType());
break;
}
}
}
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
DeviceId devId = event.subject().id();
switch (event.type()) {
case DEVICE_ADDED:
case DEVICE_UPDATED:
case DEVICE_REMOVED:
case DEVICE_SUSPENDED:
case DEVICE_AVAILABILITY_CHANGED:
case PORT_STATS_UPDATED:
break;
case PORT_ADDED:
if (!oltData.get(devId).uplink().equals(event.port().number()) &&
event.port().isEnabled()) {
processFilterObjective(event.subject().id(), event.port(), false);
}
break;
case PORT_UPDATED:
if (oltData.get(devId).uplink().equals(event.port().number())) {
break;
}
if (event.port().isEnabled()) {
processFilterObjective(event.subject().id(), event.port(), false);
} else {
processFilterObjective(event.subject().id(), event.port(), true);
}
break;
case PORT_REMOVED:
processFilterObjective(event.subject().id(), event.port(), true);
break;
default:
log.warn("Unknown device event {}", event.type());
break;
}
}
@Override
public boolean isRelevant(DeviceEvent event) {
return oltData.containsKey(event.subject().id());
}
}
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
if (event.configClass().equals(CONFIG_CLASS)) {
AccessDeviceConfig config =
networkConfig.getConfig((DeviceId) event.subject(), CONFIG_CLASS);
if (config != null) {
oltData.put(config.getOlt().deviceId(), config.getOlt());
provisionDefaultFlows((DeviceId) event.subject());
}
}
if (event.configClass().equals(SSM_TRANSLATE_CONFIG_CLASS)) {
IgmpSsmTranslateConfig config =
networkConfig.getConfig((ApplicationId) event.subject(),
SSM_TRANSLATE_CONFIG_CLASS);
if (config != null) {
ssmTranslateTable.clear();
config.getSsmTranslations().forEach(
route -> ssmTranslateTable.put(route.group(), route.source()));
}
}
break;
case CONFIG_REGISTERED:
case CONFIG_UNREGISTERED:
break;
case CONFIG_REMOVED:
if (event.configClass().equals(SSM_TRANSLATE_CONFIG_CLASS)) {
ssmTranslateTable.clear();
} else if (event.configClass().equals(CONFIG_CLASS)) {
oltData.remove(event.subject());
}
default:
break;
}
}
}
private void provisionDefaultFlows(DeviceId deviceId) {
List<Port> ports = deviceService.getPorts(deviceId);
ports.stream()
.filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
.filter(p -> p.isEnabled())
.forEach(p -> processFilterObjective((DeviceId) p.element().id(), p, false));
}
}
/*
* 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.igmp;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.packet.IpAddress;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.config.Config;
import org.onosproject.net.mcast.McastRoute;
import java.util.ArrayList;
import java.util.List;
/**
* IGMP SSM translate configuration.
*/
public class IgmpSsmTranslateConfig extends Config<ApplicationId> {
private static final String SOURCE = "source";
private static final String GROUP = "group";
@Override
public boolean isValid() {
for (JsonNode node : array) {
if (!hasOnlyFields((ObjectNode) node, SOURCE, GROUP)) {
return false;
}
if (!(isIpAddress((ObjectNode) node, SOURCE, FieldPresence.MANDATORY) &&
isIpAddress((ObjectNode) node, GROUP, FieldPresence.MANDATORY))) {
return false;
}
}
return true;
}
/**
* Gets the list of SSM translations.
*
* @return SSM translations
*/
public List<McastRoute> getSsmTranslations() {
List<McastRoute> translations = new ArrayList();
for (JsonNode node : array) {
translations.add(
new McastRoute(
IpAddress.valueOf(node.path(SOURCE).asText().trim()),
IpAddress.valueOf(node.path(GROUP).asText().trim()),
McastRoute.Type.STATIC));
}
return translations;
}
}
/*
* 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.
*/
/**
* IGMP implementation.
*/
package org.onosproject.igmp;
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:javax.ws.rs-api',
'//lib:jersey-client',
'//lib:org.apache.karaf.shell.console',
'//utils/rest:onlab-rest',
'//cli:onos-cli',
'//core/store/serializers:onos-core-serializers',
'//apps/cordconfig:onos-apps-cordconfig',
]
BUNDLES = [
':onos-apps-olt-api',
':onos-apps-olt',
]
osgi_jar_with_tests (
name = 'onos-apps-olt-api',
srcs = glob(['api/' + SRC + '*.java']),
deps = COMPILE_DEPS,
visibility = ['PUBLIC'],
)
osgi_jar_with_tests (
srcs = glob(['app/' + SRC + '*.java']),
deps = COMPILE_DEPS + [':onos-apps-olt-api'],
visibility = ['PUBLIC'],
)
onos_app (
title = 'ONOS OLT REST API',
category = 'Security',
url = 'http://onosproject.org',
description = 'OLT application for CORD.',
included_bundles = BUNDLES,
)
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>onos-olt</artifactId>
<groupId>org.onosproject</groupId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-olt-api</artifactId>
<packaging>bundle</packaging>
<url>http://onosproject.org</url>
<description>CORD OLT application API</description>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cord-config</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>org.onosproject.olt</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
/*
* 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.olt;
import org.onlab.packet.VlanId;
import org.onosproject.event.AbstractEvent;
import org.onosproject.net.DeviceId;
import java.util.Optional;
/**
* Describes an access device event.
*/
public class AccessDeviceEvent extends AbstractEvent<AccessDeviceEvent.Type, DeviceId> {
private final Optional<VlanId> sVlan;
private final Optional<VlanId> cVlan;
public enum Type {
/**
* A subscriber was registered and provisioned.
*/
SUBSCRIBER_REGISTERED,
/**
* A subscriber was unregistered and deprovisioned.
*/
SUBSCRIBER_UNREGISTERED,
/**
* An access device connected.
*/
DEVICE_CONNECTED,
/**
* An access device disconnected.
*/
DEVICE_DISCONNECTED
}
/**
*
* Creates an event of a given type and for the specified device,
* along with the cVlanId and sVlanId. The vlan fields may not be provisioned
* if the event is related to the access device (dis)connection.
*
* @param type the event type
* @param deviceId the device id
* @param sVlanId the service vlan
* @param cVlanId the customer vlan
*/
public AccessDeviceEvent(Type type, DeviceId deviceId,
VlanId sVlanId,
VlanId cVlanId) {
super(type, deviceId);
this.sVlan = Optional.ofNullable(sVlanId);
this.cVlan = Optional.ofNullable(cVlanId);
}
/**
*
* Creates an event of a given type and for the specified device, and timestamp
* along with the cVlanId and sVlanId. The vlan fields may not be provisioned
* if the event is related to the access device (dis)connection.
*
* @param type the event type
* @param deviceId the device id
* @param time a timestamp
* @param sVlanId the service vlan
* @param cVlanId the customer vlan
*/
protected AccessDeviceEvent(Type type, DeviceId deviceId, long time,
VlanId sVlanId,
VlanId cVlanId) {
super(type, deviceId, time);
this.sVlan = Optional.ofNullable(sVlanId);
this.cVlan = Optional.ofNullable(cVlanId);
}
public Optional<VlanId> sVlanId() {
return sVlan;
}
public Optional<VlanId> cVlanId() {
return cVlan;
}
}
/*
* 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.olt;
import org.onosproject.event.EventListener;
/**
* Entity capable of receiving access device related events.
*/
public interface AccessDeviceListener extends EventListener<AccessDeviceEvent> {
}
/*
* 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.olt;
import org.onlab.packet.VlanId;
import org.onosproject.cordconfig.access.AccessDeviceData;
import org.onosproject.event.ListenerService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import java.util.Collection;
import java.util.Map;
/**
* Service for interacting with an access device (OLT).
*/
public interface AccessDeviceService
extends ListenerService<AccessDeviceEvent, AccessDeviceListener> {
/**
* Provisions connectivity for a subscriber on an access device.
*
* @param port subscriber's connection point
* @param vlan VLAN ID to provision for subscriber
*/
void provisionSubscriber(ConnectPoint port, VlanId vlan);
/**
* Removes provisioned connectivity for a subscriber from an access device.
*
* @param port subscriber's connection point
*/
void removeSubscriber(ConnectPoint port);
/**
* Returns information about the provisioned subscribers.
*
* @return subscribers
*/
Collection<Map.Entry<ConnectPoint, VlanId>> getSubscribers();
/**
* Returns the map of configured OLTs.
*
* @return a map
*/
Map<DeviceId, AccessDeviceData> fetchOlts();
}
/*
* 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.
*/
/**
* OLT application api.
*/
package org.onosproject.olt;
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<app name="org.onosproject.olt" origin="ON.Lab" version="${project.version}"
category="Traffic Steering" url="http://onosproject.org" title="Optical Line Terminal App"
featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
features="${project.artifactId}" apps="org.onosproject.cord-config">
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-app-olt-api/${project.version}</artifact>
</app>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
<feature name="${project.artifactId}" version="${project.version}"
description="${project.description}">
<feature>onos-api</feature>
<bundle>mvn:${project.groupId}/onos-app-olt-api/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-app-olt/${project.version}</bundle>
</feature>
</features>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-olt</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>onos-app-olt</artifactId>
<packaging>bundle</packaging>
<description>OLT application for CORD</description>
<properties>
<web.context>/onos/olt</web.context>
<api.version>1.0.0</api.version>
<api.title>ONOS OLT REST API</api.title>
<api.description>
APIs for interacting with the CORD OLT application.
</api.description>
<api.package>org.onosproject.olt.rest</api.package>
</properties>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cord-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-app-olt-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<_wab>src/main/webapp/</_wab>
<Include-Resource>
WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
{maven-resources}
</Include-Resource>
<Bundle-SymbolicName>
${project.groupId}.${project.artifactId}
</Bundle-SymbolicName>
<Import-Package>
*,org.glassfish.jersey.servlet
</Import-Package>
<Web-ContextPath>${web.context}</Web-ContextPath>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
/*
* 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.olt.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cordconfig.access.AccessDeviceData;
import org.onosproject.net.DeviceId;
import org.onosproject.olt.AccessDeviceService;
import java.util.Map;
/**
* Shows configured OLTs.
*/
@Command(scope = "onos", name = "olts",
description = "Shows configured OLTs")
public class ShowOltCommand extends AbstractShellCommand {
@Argument(index = 0, name = "deviceId", description = "Access device ID",
required = false, multiValued = false)
private String strDeviceId = null;
@Override
protected void execute() {
AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
Map<DeviceId, AccessDeviceData> data = service.fetchOlts();
if (strDeviceId != null) {
DeviceId deviceId = DeviceId.deviceId(strDeviceId);
print("OLT %s:", deviceId);
display(data.get(deviceId));
} else {
data.keySet().forEach(did -> {
print("OLT %s:", did);
display(data.get(did));
});
}
}
private void display(AccessDeviceData accessDeviceData) {
print("\tvlan : %s", accessDeviceData.vlan());
print("\tuplink : %s", accessDeviceData.uplink());
}
}
/*
* 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.olt.cli;
import org.apache.karaf.shell.commands.Command;
import org.onlab.packet.VlanId;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.net.ConnectPoint;
import org.onosproject.olt.AccessDeviceService;
import java.util.Map;
/**
* Shows provisioned subscribers.
*/
@Command(scope = "onos", name = "subscribers",
description = "Shows provisioned subscribers")
public class ShowSubscribersCommand extends AbstractShellCommand {
private static final String FORMAT = "port=%s, cvlan=%s";
@Override
protected void execute() {
AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
service.getSubscribers().forEach(this::display);
}
private void display(Map.Entry<ConnectPoint, VlanId> subscriber) {
print(FORMAT, subscriber.getKey(), subscriber.getValue());
}
}
/*
* 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.olt.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.packet.VlanId;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.olt.AccessDeviceService;
/**
* Adds a subscriber to an access device.
*/
@Command(scope = "onos", name = "add-subscriber-access",
description = "Adds a subscriber to an access device")
public class SubscriberAddCommand extends AbstractShellCommand {
@Argument(index = 0, name = "deviceId", description = "Access device ID",
required = true, multiValued = false)
private String strDeviceId = null;
@Argument(index = 1, name = "port", description = "Subscriber port number",
required = true, multiValued = false)
private String strPort = null;
@Argument(index = 2, name = "vlanId",
description = "VLAN ID to add",
required = true, multiValued = false)
private String strVlanId = null;
@Override
protected void execute() {
AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
DeviceId deviceId = DeviceId.deviceId(strDeviceId);
PortNumber port = PortNumber.portNumber(strPort);
VlanId vlan = VlanId.vlanId(Short.parseShort(strVlanId));
ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
service.provisionSubscriber(connectPoint, vlan);
}
}
/*
* 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.olt.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.olt.AccessDeviceService;
/**
* Adds a subscriber to an access device.
*/
@Command(scope = "onos", name = "remove-subscriber-access",
description = "Adds a subscriber to an access device")
public class SubscriberRemoveCommand extends AbstractShellCommand {
@Argument(index = 0, name = "deviceId", description = "Access device ID",
required = true, multiValued = false)
private String strDeviceId = null;
@Argument(index = 1, name = "port", description = "Subscriber port number",
required = true, multiValued = false)
private String strPort = null;
@Override
protected void execute() {
AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
DeviceId deviceId = DeviceId.deviceId(strDeviceId);
PortNumber port = PortNumber.portNumber(strPort);
ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
service.removeSubscriber(connectPoint);
}
}
/*
* 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.
*/
/**
* OLT application handling PMC OLT hardware.
*/
package org.onosproject.olt.cli;
/*
* 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.olt.impl;
import com.google.common.collect.Maps;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
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.EthType;
import org.onlab.packet.VlanId;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.cordconfig.access.AccessDeviceConfig;
import org.onosproject.cordconfig.access.AccessDeviceData;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.event.AbstractListenerManager;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
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.NetworkConfigRegistry;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flowobjective.DefaultFilteringObjective;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.olt.AccessDeviceEvent;
import org.onosproject.olt.AccessDeviceListener;
import org.onosproject.olt.AccessDeviceService;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onlab.util.Tools.get;
import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provisions rules on access devices.
*/
@Service
@Component(immediate = true)
public class Olt
extends AbstractListenerManager<AccessDeviceEvent, AccessDeviceListener>
implements AccessDeviceService {
private static final short DEFAULT_VLAN = 0;
private static final String SUBSCRIBERS = "existing-subscribers";
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry networkConfig;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StorageService storageService;
@Property(name = "defaultVlan", intValue = DEFAULT_VLAN,
label = "Default VLAN RG<->ONU traffic")
private int defaultVlan = DEFAULT_VLAN;
private final DeviceListener deviceListener = new InternalDeviceListener();
private ApplicationId appId;
private ExecutorService oltInstallers = Executors.newFixedThreadPool(4,
groupedThreads("onos/olt-service",
"olt-installer-%d"));
private Map<DeviceId, AccessDeviceData> oltData = new ConcurrentHashMap<>();
private Map<ConnectPoint, VlanId> subscribers;
private InternalNetworkConfigListener configListener =
new InternalNetworkConfigListener();
private static final Class<AccessDeviceConfig> CONFIG_CLASS =
AccessDeviceConfig.class;
private ConfigFactory<DeviceId, AccessDeviceConfig> configFactory =
new ConfigFactory<DeviceId, AccessDeviceConfig>(
SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessDevice") {
@Override
public AccessDeviceConfig createConfig() {
return new AccessDeviceConfig();
}
};
@Activate
public void activate(ComponentContext context) {
modified(context);
appId = coreService.registerApplication("org.onosproject.olt");
componentConfigService.registerProperties(getClass());
eventDispatcher.addSink(AccessDeviceEvent.class, listenerRegistry);
networkConfig.registerConfigFactory(configFactory);
networkConfig.addListener(configListener);
networkConfig.getSubjects(DeviceId.class, AccessDeviceConfig.class).forEach(
subject -> {
AccessDeviceConfig config = networkConfig.getConfig(subject, AccessDeviceConfig.class);
if (config != null) {
AccessDeviceData data = config.getOlt();
oltData.put(data.deviceId(), data);
}
}
);
oltData.keySet().stream()
.flatMap(did -> deviceService.getPorts(did).stream())
.filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
.filter(p -> p.isEnabled())
.forEach(p -> processFilteringObjectives((DeviceId) p.element().id(),
p.number(), true));
subscribers = storageService.<ConnectPoint, VlanId>consistentMapBuilder()
.withName(SUBSCRIBERS)
.withSerializer(Serializer.using(KryoNamespaces.API))
.build().asJavaMap();
deviceService.addListener(deviceListener);
log.info("Started with Application ID {}", appId.id());
}
@Deactivate
public void deactivate() {
componentConfigService.unregisterProperties(getClass(), false);
deviceService.removeListener(deviceListener);
networkConfig.removeListener(configListener);
networkConfig.unregisterConfigFactory(configFactory);
log.info("Stopped");
}
@Modified
public void modified(ComponentContext context) {
Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
try {
String s = get(properties, "defaultVlan");
defaultVlan = isNullOrEmpty(s) ? DEFAULT_VLAN : Integer.parseInt(s.trim());
} catch (Exception e) {
defaultVlan = DEFAULT_VLAN;
}
}
@Override
public void provisionSubscriber(ConnectPoint port, VlanId vlan) {
AccessDeviceData olt = oltData.get(port.deviceId());
if (olt == null) {
log.warn("No data found for OLT device {}", port.deviceId());
return;
}
provisionVlans(olt.deviceId(), olt.uplink(), port.port(), vlan, olt.vlan(),
olt.defaultVlan());
}
@Override
public void removeSubscriber(ConnectPoint port) {
AccessDeviceData olt = oltData.get(port.deviceId());
if (olt == null) {
log.warn("No data found for OLT device {}", port.deviceId());
return;
}
VlanId subscriberVlan = subscribers.remove(port);
if (subscriberVlan == null) {
log.warn("Unknown subscriber at location {}", port);
return;
}
unprovisionSubscriber(olt.deviceId(), olt.uplink(), port.port(), subscriberVlan,
olt.vlan(), olt.defaultVlan());
}
@Override
public Collection<Map.Entry<ConnectPoint, VlanId>> getSubscribers() {
return subscribers.entrySet();
}
@Override
public Map<DeviceId, AccessDeviceData> fetchOlts() {
return Maps.newHashMap(oltData);
}
private void unprovisionSubscriber(DeviceId deviceId, PortNumber uplink,
PortNumber subscriberPort, VlanId subscriberVlan,
VlanId deviceVlan, Optional<VlanId> defaultVlan) {
CompletableFuture<ObjectiveError> downFuture = new CompletableFuture();
CompletableFuture<ObjectiveError> upFuture = new CompletableFuture();
ForwardingObjective.Builder upFwd = upBuilder(uplink, subscriberPort,
subscriberVlan, deviceVlan,
defaultVlan);
ForwardingObjective.Builder downFwd = downBuilder(uplink, subscriberPort,
subscriberVlan, deviceVlan,
defaultVlan);
flowObjectiveService.forward(deviceId, upFwd.remove(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
upFuture.complete(null);
}
@Override
public void onError(Objective objective, ObjectiveError error) {
upFuture.complete(error);
}
}));
flowObjectiveService.forward(deviceId, downFwd.remove(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
downFuture.complete(null);
}
@Override
public void onError(Objective objective, ObjectiveError error) {
downFuture.complete(error);
}
}));
upFuture.thenAcceptBothAsync(downFuture, (upStatus, downStatus) -> {
if (upStatus == null && downStatus == null) {
post(new AccessDeviceEvent(AccessDeviceEvent.Type.SUBSCRIBER_UNREGISTERED,
deviceId,
deviceVlan,
subscriberVlan));
} else if (downStatus != null) {
log.error("Subscriber with vlan {} on device {} " +
"on port {} failed downstream uninstallation: {}",
subscriberVlan, deviceId, subscriberPort, downStatus);
} else if (upStatus != null) {
log.error("Subscriber with vlan {} on device {} " +
"on port {} failed upstream uninstallation: {}",
subscriberVlan, deviceId, subscriberPort, upStatus);
}
}, oltInstallers);
}
private void provisionVlans(DeviceId deviceId, PortNumber uplinkPort,
PortNumber subscriberPort,
VlanId subscriberVlan, VlanId deviceVlan,
Optional<VlanId> defaultVlan) {
CompletableFuture<ObjectiveError> downFuture = new CompletableFuture();
CompletableFuture<ObjectiveError> upFuture = new CompletableFuture();
ForwardingObjective.Builder upFwd = upBuilder(uplinkPort, subscriberPort,
subscriberVlan, deviceVlan,
defaultVlan);
ForwardingObjective.Builder downFwd = downBuilder(uplinkPort, subscriberPort,
subscriberVlan, deviceVlan,
defaultVlan);
ConnectPoint cp = new ConnectPoint(deviceId, subscriberPort);
subscribers.put(cp, subscriberVlan);
flowObjectiveService.forward(deviceId, upFwd.add(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
upFuture.complete(null);
}
@Override
public void onError(Objective objective, ObjectiveError error) {
upFuture.complete(error);
}
}));
flowObjectiveService.forward(deviceId, downFwd.add(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
downFuture.complete(null);
}
@Override
public void onError(Objective objective, ObjectiveError error) {
downFuture.complete(error);
}
}));
upFuture.thenAcceptBothAsync(downFuture, (upStatus, downStatus) -> {
if (upStatus == null && downStatus == null) {
post(new AccessDeviceEvent(AccessDeviceEvent.Type.SUBSCRIBER_REGISTERED,
deviceId,
deviceVlan,
subscriberVlan));
} else if (downStatus != null) {
log.error("Subscriber with vlan {} on device {} " +
"on port {} failed downstream installation: {}",
subscriberVlan, deviceId, subscriberPort, downStatus);
} else if (upStatus != null) {
log.error("Subscriber with vlan {} on device {} " +
"on port {} failed upstream installation: {}",
subscriberVlan, deviceId, subscriberPort, upStatus);
}
}, oltInstallers);
}
private ForwardingObjective.Builder downBuilder(PortNumber uplinkPort,
PortNumber subscriberPort,
VlanId subscriberVlan,
VlanId deviceVlan,
Optional<VlanId> defaultVlan) {
TrafficSelector downstream = DefaultTrafficSelector.builder()
.matchVlanId(deviceVlan)
.matchInPort(uplinkPort)
.matchInnerVlanId(subscriberVlan)
.build();
TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
.popVlan()
.setVlanId(defaultVlan.orElse(VlanId.vlanId((short) this.defaultVlan)))
.setOutput(subscriberPort)
.build();
return DefaultForwardingObjective.builder()
.withFlag(ForwardingObjective.Flag.VERSATILE)
.withPriority(1000)
.makePermanent()
.withSelector(downstream)
.fromApp(appId)
.withTreatment(downstreamTreatment);
}
private ForwardingObjective.Builder upBuilder(PortNumber uplinkPort,
PortNumber subscriberPort,
VlanId subscriberVlan,
VlanId deviceVlan,
Optional<VlanId> defaultVlan) {
TrafficSelector upstream = DefaultTrafficSelector.builder()
.matchVlanId(defaultVlan.orElse(VlanId.vlanId((short) this.defaultVlan)))
.matchInPort(subscriberPort)
.build();
TrafficTreatment upstreamTreatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(subscriberVlan)
.pushVlan()
.setVlanId(deviceVlan)
.setOutput(uplinkPort)
.build();
return DefaultForwardingObjective.builder()
.withFlag(ForwardingObjective.Flag.VERSATILE)
.withPriority(1000)
.makePermanent()
.withSelector(upstream)
.fromApp(appId)
.withTreatment(upstreamTreatment);
}
private void processFilteringObjectives(DeviceId devId, PortNumber port, boolean install) {
if (!mastershipService.isLocalMaster(devId)) {
return;
}
DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
FilteringObjective eapol = (install ? builder.permit() : builder.deny())
.withKey(Criteria.matchInPort(port))
.addCondition(Criteria.matchEthType(EthType.EtherType.EAPOL.ethType()))
.withMeta(DefaultTrafficTreatment.builder()
.setOutput(PortNumber.CONTROLLER).build())
.fromApp(appId)
.withPriority(1000)
.add(new ObjectiveContext() {
@Override
public void onSuccess(Objective objective) {
log.info("Eapol filter for {} on {} installed.",
devId, port);
}
@Override
public void onError(Objective objective, ObjectiveError error) {
log.info("Eapol filter for {} on {} failed because {}",
devId, port, error);
}
});
flowObjectiveService.filter(devId, eapol);
}
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
DeviceId devId = event.subject().id();
if (!oltData.containsKey(devId)) {
return;
}
switch (event.type()) {
//TODO: Port handling and bookkeeping should be improved once
// olt firmware handles correct behaviour.
case PORT_ADDED:
if (!oltData.get(devId).uplink().equals(event.port().number()) &&
event.port().isEnabled()) {
processFilteringObjectives(devId, event.port().number(), true);
}
break;
case PORT_REMOVED:
AccessDeviceData olt = oltData.get(devId);
VlanId vlan = subscribers.get(new ConnectPoint(devId,
event.port().number()));
unprovisionSubscriber(devId, olt.uplink(),
event.port().number(),
vlan, olt.vlan(), olt.defaultVlan());
if (!oltData.get(devId).uplink().equals(event.port().number()) &&
event.port().isEnabled()) {
processFilteringObjectives(devId, event.port().number(), false);
}
break;
case PORT_UPDATED:
if (oltData.get(devId).uplink().equals(event.port().number())) {
break;
}
if (event.port().isEnabled()) {
processFilteringObjectives(devId, event.port().number(), true);
} else {
processFilteringObjectives(devId, event.port().number(), false);
}
break;
case DEVICE_ADDED:
post(new AccessDeviceEvent(
AccessDeviceEvent.Type.DEVICE_CONNECTED, devId,
null, null));
provisionDefaultFlows(devId);
break;
case DEVICE_REMOVED:
post(new AccessDeviceEvent(
AccessDeviceEvent.Type.DEVICE_DISCONNECTED, devId,
null, null));
break;
case DEVICE_AVAILABILITY_CHANGED:
if (deviceService.isAvailable(devId)) {
post(new AccessDeviceEvent(
AccessDeviceEvent.Type.DEVICE_CONNECTED, devId,
null, null));
} else {
post(new AccessDeviceEvent(
AccessDeviceEvent.Type.DEVICE_DISCONNECTED, devId,
null, null));
}
break;
case DEVICE_UPDATED:
case DEVICE_SUSPENDED:
case PORT_STATS_UPDATED:
default:
return;
}
}
}
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
AccessDeviceConfig config =
networkConfig.getConfig((DeviceId) event.subject(), CONFIG_CLASS);
if (config != null) {
oltData.put(config.getOlt().deviceId(), config.getOlt());
provisionDefaultFlows((DeviceId) event.subject());
}
break;
case CONFIG_REGISTERED:
case CONFIG_UNREGISTERED:
break;
case CONFIG_REMOVED:
oltData.remove(event.subject());
default:
break;
}
}
@Override
public boolean isRelevant(NetworkConfigEvent event) {
return event.configClass().equals(CONFIG_CLASS);
}
}
private void provisionDefaultFlows(DeviceId deviceId) {
if (!mastershipService.isLocalMaster(deviceId)) {
return;
}
List<Port> ports = deviceService.getPorts(deviceId);
ports.stream()
.filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
.filter(p -> p.isEnabled())
.forEach(p -> processFilteringObjectives((DeviceId) p.element().id(),
p.number(), true));
}
}
/*
* 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.
*/
/**
* OLT application handling PMC OLT hardware.
*/
package org.onosproject.olt.impl;
/*
* 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.olt.rest;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.onlab.packet.VlanId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.olt.AccessDeviceService;
import org.onosproject.rest.AbstractWebResource;
/**
* OLT REST APIs.
*/
@Path("oltapp")
public class OltWebResource extends AbstractWebResource {
/**
* Provision a subscriber.
*
* @param device device id
* @param port port number
* @param vlan vlan id
* @return 200 OK
*/
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("{device}/{port}/{vlan}")
public Response provisionSubscriber(
@PathParam("device")String device,
@PathParam("port")long port,
@PathParam("vlan")short vlan) {
AccessDeviceService service = get(AccessDeviceService.class);
DeviceId deviceId = DeviceId.deviceId(device);
PortNumber portNumber = PortNumber.portNumber(port);
VlanId vlanId = VlanId.vlanId(vlan);
ConnectPoint connectPoint = new ConnectPoint(deviceId, portNumber);
service.provisionSubscriber(connectPoint, vlanId);
return ok("").build();
}
/**
* Remove the provisioning for a subscriber.
*
* @param device device id
* @param port port number
* @return 204 NO CONTENT
*/
@DELETE
@Path("{device}/{port}")
public Response removeSubscriber(
@PathParam("device")String device,
@PathParam("port")long port) {
AccessDeviceService service = get(AccessDeviceService.class);
DeviceId deviceId = DeviceId.deviceId(device);
PortNumber portNumber = PortNumber.portNumber(port);
ConnectPoint connectPoint = new ConnectPoint(deviceId, portNumber);
service.removeSubscriber(connectPoint);
return Response.noContent().build();
}
}
/*
* 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.
*/
/**
* REST APIs for the OLT application.
*/
package org.onosproject.olt.rest;
<!--
~ 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.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onosproject.olt.cli.SubscriberAddCommand"/>
<completers>
<ref component-id="deviceIdCompleter"/>
<null/>
</completers>
</command>
<command>
<action class="org.onosproject.olt.cli.SubscriberRemoveCommand"/>
<completers>
<ref component-id="deviceIdCompleter"/>
<null/>
</completers>
</command>
<command>
<action class="org.onosproject.olt.cli.ShowOltCommand"/>
<completers>
<ref component-id="deviceIdCompleter"/>
<null/>
</completers>
</command>
<command>
<action class="org.onosproject.olt.cli.ShowSubscribersCommand"/>
</command>
</command-bundle>
<bean id="deviceIdCompleter" class="org.onosproject.cli.net.DeviceIdCompleter"/>
</blueprint>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="ONOS" version="2.5">
<display-name>OLT REST API v1.0</display-name>
<servlet>
<servlet-name>JAX-RS Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
org.onosproject.olt.rest.OltWebResource
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Service</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps</artifactId>
<version>1.6.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>onos-olt</artifactId>
<packaging>pom</packaging>
<description>OLT application for CORD</description>
<modules>
<module>api</module>
<module>app</module>
</modules>
</project>
......@@ -29,7 +29,6 @@
<description>ONOS sample applications</description>
<modules>
<module>aaa</module>
<module>acl</module>
<module>faultmanagement</module>
<module>fwd</module>
......@@ -51,14 +50,11 @@
<module>pcep-api</module>
<module>iptopology-api</module>
<module>pce</module>
<module>olt</module>
<module>cip</module>
<module>flowanalyzer</module>
<module>vtn</module>
<module>dhcp</module>
<module>cordvtn</module>
<module>mfwd</module>
<module>igmp</module>
<module>pim</module>
<module>mlb</module>
<module>pathpainter</module>
......@@ -66,7 +62,6 @@
<module>cpman</module>
<module>events</module>
<module>vrouter</module>
<module>cordmcast</module>
<module>vpls</module>
<module>openstacknode</module>
<module>openstacknetworking</module>
......@@ -75,7 +70,6 @@
<module>gangliametrics</module>
<module>graphitemetrics</module>
<module>xosclient</module>
<module>cordconfig</module>
<module>scalablegateway</module>
</modules>
......
......@@ -40,16 +40,12 @@ APPS = [
# Apps
'//apps/dhcp:onos-apps-dhcp-oar',
'//apps/fwd:onos-apps-fwd-oar',
'//apps/aaa:onos-apps-aaa-oar',
'//apps/acl:onos-apps-acl-oar',
'//apps/bgprouter:onos-apps-bgprouter-oar',
'//apps/proxyarp:onos-apps-proxyarp-oar',
'//apps/segmentrouting:onos-apps-segmentrouting-oar',
'//apps/gangliametrics:onos-apps-gangliametrics-oar',
'//apps/graphitemetrics:onos-apps-graphitemetrics-oar',
'//apps/igmp:onos-apps-igmp-oar',
'//apps/cordmcast:onos-apps-cordmcast-oar',
'//apps/olt:onos-apps-olt-oar',
'//apps/influxdbmetrics:onos-apps-influxdbmetrics-oar',
'//apps/metrics:onos-apps-metrics-oar',
'//apps/mfwd:onos-apps-mfwd-oar',
......@@ -66,7 +62,6 @@ APPS = [
'//apps/reactive-routing:onos-apps-reactive-routing-oar',
'//apps/sdnip:onos-apps-sdnip-oar',
'//apps/virtualbng:onos-apps-virtualbng-oar',
'//apps/cordvtn:onos-apps-cordvtn-oar',
'//apps/vpls:onos-apps-vpls-oar',
'//apps/vrouter:onos-apps-vrouter-oar',
'//apps/vtn:onos-apps-vtn-oar',
......