ONOS-3422 inter-domain cross connect
depend on ONOS master repo: (Change-Id: Ie90e69c4134d1f71893bf43ee6c290bdbd273aeb)
Change-Id: I3892e780bc6550f8a8d8be622b9fee5322c1dab5
diff --git a/ecord/co/features.xml b/ecord/co/features.xml
new file mode 100644
index 0000000..c68a07b
--- /dev/null
+++ b/ecord/co/features.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ Copyright 2015 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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>
+
+ <!-- based on output of mvn karaf:features-generate-descriptor -->
+ <bundle>mvn:org.glassfish.jersey.core/jersey-client/2.22.1</bundle>
+ <bundle>mvn:javax.ws.rs/javax.ws.rs-api/2.0.1</bundle>
+ <bundle>mvn:org.glassfish.jersey.core/jersey-common/2.22.1</bundle>
+ <bundle>mvn:javax.annotation/javax.annotation-api/1.2</bundle>
+ <bundle>mvn:org.glassfish.jersey.bundles.repackaged/jersey-guava/2.22.1</bundle>
+ <bundle>mvn:org.glassfish.hk2/osgi-resource-locator/1.0.1</bundle>
+ <bundle>mvn:org.glassfish.hk2/hk2-api/2.4.0-b31</bundle>
+ <bundle>mvn:org.glassfish.hk2/hk2-utils/2.4.0-b31</bundle>
+ <bundle>mvn:org.glassfish.hk2.external/aopalliance-repackaged/2.4.0-b31</bundle>
+ <bundle>mvn:org.glassfish.hk2.external/javax.inject/2.4.0-b31</bundle>
+ <bundle>mvn:org.glassfish.hk2/hk2-locator/2.4.0-b31</bundle>
+ <bundle>mvn:org.javassist/javassist/3.18.1-GA</bundle>
+
+ <bundle>mvn:${project.groupId}/onos-app-ecord-co/${project.version}</bundle>
+ </feature>
+</features>
+
diff --git a/ecord/co/pom.xml b/ecord/co/pom.xml
index d804cd3..48a7698 100644
--- a/ecord/co/pom.xml
+++ b/ecord/co/pom.xml
@@ -34,6 +34,8 @@
<properties>
<onos.version>1.5.0-SNAPSHOT</onos.version>
<onos.app.name>org.onosproject.ecord.co</onos.app.name>
+ <!-- TODO App dependency not working? -->
+ <onos.app.requires>org.onosproject.incubator.rpc,org.onosproject.incubator.rpc.grpc</onos.app.requires>
<onos.app.origin>Open Networking Lab</onos.app.origin>
</properties>
@@ -59,7 +61,6 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.11</version>
<scope>test</scope>
</dependency>
@@ -74,12 +75,42 @@
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
- <version>1.9.8</version>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ <version>2.22.1</version>
+ </dependency>
</dependencies>
<build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.karaf.tooling</groupId>
+ <artifactId>karaf-maven-plugin</artifactId>
+ <version>3.0.5</version>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
diff --git a/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchDeviceProvider.java b/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchDeviceProvider.java
index ce0bf3f..a098193 100644
--- a/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchDeviceProvider.java
+++ b/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchDeviceProvider.java
@@ -15,27 +15,52 @@
*/
package org.onosproject.ecord.co;
+import org.apache.commons.lang3.tuple.Pair;
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.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.onosproject.cluster.ClusterService;
import org.onosproject.incubator.rpc.RemoteServiceContext;
import org.onosproject.incubator.rpc.RemoteServiceDirectory;
+import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.MastershipRole;
+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.BasicLinkConfig;
import org.onosproject.net.device.DefaultDeviceDescription;
import org.onosproject.net.device.DeviceDescription;
import org.onosproject.net.device.DeviceProvider;
import org.onosproject.net.device.DeviceProviderRegistry;
import org.onosproject.net.device.DeviceProviderService;
+import org.onosproject.net.device.PortDescription;
import org.onosproject.net.provider.AbstractProvider;
import org.onosproject.net.provider.ProviderId;
import org.slf4j.Logger;
-import java.net.URI;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.net.URI;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import static org.onosproject.ecord.co.BigSwitchManager.REALIZED_BY;
+import static org.onosproject.net.config.basics.SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY;
import static org.slf4j.LoggerFactory.getLogger;
/**
@@ -57,32 +82,61 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RemoteServiceDirectory rpcService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry cfgRegistry;
+
+ private String dpidScheme = SCHEME;
+
+ private String rpcScheme = "grpc";
+
+ private int rpcPort = 11984;
+ // Metro ONOS IP
+ private String metroIp = "172.16.218.128";
+
private BigSwitch bigSwitch;
private DeviceDescription bigSwitchDescription;
private DeviceProviderRegistry providerRegistry;
private DeviceProviderService providerService;
private BigSwitchListener listener = new InternalListener();
+ private final ConfigFactory<ConnectPoint, CrossConnectConfig> xcConfigFactory
+ = new ConfigFactory<ConnectPoint, CrossConnectConfig>(CONNECT_POINT_SUBJECT_FACTORY,
+ CrossConnectConfig.class,
+ "cross-connect") {
+ @Override
+ public CrossConnectConfig createConfig() {
+ return new CrossConnectConfig();
+ }
+ };
+
@Activate
public void activate() {
// Create big switch device and description
- DeviceId deviceId = DeviceId.deviceId(SCHEME + ':' + clusterService.getLocalNode().ip());
+ DeviceId deviceId = DeviceId.deviceId(dpidScheme + ':' + clusterService.getLocalNode().ip());
bigSwitch = new BigSwitch(deviceId, this.id());
buildDeviceDescription();
// Register this device provider remotely
// TODO: make remote configurable
- RemoteServiceContext remoteServiceContext = rpcService.get(URI.create("local://localhost"));
+ RemoteServiceContext remoteServiceContext
+ = rpcService.get(URI.create(rpcScheme + "://" + metroIp + ":" + rpcPort));
providerRegistry = remoteServiceContext.get(DeviceProviderRegistry.class);
providerService = providerRegistry.register(this);
+
+ NetworkConfigListener cfglistener = new InternalConfigListener();
+ cfgRegistry.addListener(cfglistener);
+ cfgRegistry.registerConfigFactory(xcConfigFactory);
+
// Start big switch service and register device
bigSwitchService.addListener(listener);
registerDevice();
+
LOG.info("Started");
}
@Deactivate
public void deactivate() {
unregisterDevice();
+ cfgRegistry.unregisterConfigFactory(xcConfigFactory);
providerRegistry.unregister(this);
// Won't hurt but necessary?
providerService = null;
@@ -92,12 +146,104 @@
private void registerDevice() {
providerService.deviceConnected(bigSwitch.id(), bigSwitchDescription);
providerService.updatePorts(bigSwitch.id(), bigSwitchService.getPorts());
+ bigSwitchService.getPorts().stream()
+ .forEach(this::advertiseCrossConnectLinks);
}
private void unregisterDevice() {
providerService.deviceDisconnected(bigSwitch.id());
}
+ private ConnectPoint toConnectPoint(String strCp) {
+ String[] split = strCp.split("/");
+ if (split.length != 2) {
+ LOG.warn("Unexpected annotation %s:%s", REALIZED_BY, strCp);
+ return null;
+ }
+ DeviceId did = DeviceId.deviceId(split[0]);
+ PortNumber num = PortNumber.fromString(split[1]);
+ return new ConnectPoint(did, num);
+ }
+
+ private Optional<Pair<ConnectPoint, ConnectPoint>> crossConnectLink(PortDescription bigPort) {
+ String sPhyCp = bigPort.annotations().value(REALIZED_BY);
+ if (sPhyCp == null) {
+ return Optional.empty();
+ }
+
+ ConnectPoint phyCp = toConnectPoint(sPhyCp);
+ if (phyCp != null) {
+ CrossConnectConfig config = cfgRegistry.getConfig(phyCp, CrossConnectConfig.class);
+ if (config != null) {
+ return config.remote()
+ .map(remCp -> Pair.of(new ConnectPoint(bigSwitch.id(), bigPort.portNumber()), remCp));
+ }
+ }
+ return Optional.empty();
+ }
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private ObjectNode crossConnectLinksJson(Pair<ConnectPoint, ConnectPoint> link) {
+ ObjectNode linksCfg = MAPPER.createObjectNode();
+
+ ObjectNode basic = MAPPER.createObjectNode();
+ basic.putObject("basic")
+ .put(BasicLinkConfig.IS_DURABLE, true)
+ .put(BasicLinkConfig.TYPE, "DIRECT");
+ linksCfg.set(String.format("%s/%s-%s/%s",
+ link.getLeft().deviceId(), link.getLeft().port(),
+ link.getRight().deviceId(), link.getRight().port()),
+ basic);
+ linksCfg.set(String.format("%s/%s-%s/%s",
+ link.getRight().deviceId(), link.getRight().port(),
+ link.getLeft().deviceId(), link.getLeft().port()),
+ basic);
+ return linksCfg;
+ }
+
+ private boolean postNetworkConfig(String subject, ObjectNode cfg) {
+ // TODO Slice out REST Client code as library?
+ Client client = ClientBuilder.newClient();
+
+ client.property(ClientProperties.FOLLOW_REDIRECTS, true);
+
+ // Trying to do JSON processing using Jackson triggered OSGi nightmare
+ //client.register(JacksonFeature.class);
+
+ final Map<String, String> env = System.getenv();
+ // TODO Where should we get the user/password from?
+ String user = env.getOrDefault("ONOS_WEB_USER", "onos");
+ String pass = env.getOrDefault("ONOS_WEB_PASS", "rocks");
+ HttpAuthenticationFeature auth = HttpAuthenticationFeature.basic(user, pass);
+ client.register(auth);
+
+ // TODO configurable base path
+ WebTarget target = client.target("http://" + metroIp + ":8181/onos/v1/")
+ .path("network/configuration/")
+ .path(subject);
+
+ Response response = target.request(MediaType.APPLICATION_JSON)
+ .post(Entity.entity(cfg.toString(), MediaType.APPLICATION_JSON));
+
+
+ if (response.getStatusInfo() != Response.Status.OK) {
+ LOG.error("POST failed {}\n{}", response, cfg.toString());
+ return false;
+ }
+ return true;
+ }
+
+ private void advertiseCrossConnectLinks(PortDescription port) {
+ crossConnectLink(port).ifPresent(xcLink -> {
+ LOG.debug("CrossConnect {} is {}!",
+ xcLink,
+ port.isEnabled() ? "up" : "down");
+ // TODO check port status and add/remove cross connect Link
+ postNetworkConfig("links", crossConnectLinksJson(xcLink));
+ });
+ }
+
private class InternalListener implements BigSwitchListener {
@Override
public void event(BigSwitchEvent event) {
@@ -105,9 +251,16 @@
case PORT_ADDED:
case PORT_REMOVED:
providerService.updatePorts(bigSwitch.id(), bigSwitchService.getPorts());
+ // if the subject's underlying port was a cross connect port,
+ // advertise cross-connect link to Metro-ONOS view
+ advertiseCrossConnectLinks(event.subject());
break;
+
case PORT_UPDATED:
providerService.portStatusChanged(bigSwitch.id(), event.subject());
+ // if the subject's underlying port was a cross connect port,
+ // advertise cross-connect link to Metro-ONOS view
+ advertiseCrossConnectLinks(event.subject());
break;
default:
break;
@@ -140,4 +293,15 @@
public boolean isReachable(DeviceId deviceId) {
return true;
}
+
+ public class InternalConfigListener implements NetworkConfigListener {
+
+ @Override
+ public void event(NetworkConfigEvent event) {
+ if (event.configClass() == CrossConnectConfig.class) {
+ bigSwitchService.getPorts().stream()
+ .forEach(BigSwitchDeviceProvider.this::advertiseCrossConnectLinks);
+ }
+ }
+ }
}
diff --git a/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchEvent.java b/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchEvent.java
index 4a1252e..2c50dcd 100644
--- a/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchEvent.java
+++ b/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchEvent.java
@@ -18,6 +18,8 @@
import org.onosproject.event.AbstractEvent;
import org.onosproject.net.device.PortDescription;
+// TODO probably Event subject should contain Device info.
+// e.g., (DeviceId, PortDescription)
public class BigSwitchEvent extends AbstractEvent<BigSwitchEvent.Type, PortDescription> {
public enum Type {
diff --git a/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchManager.java b/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchManager.java
index d90af33..b58f2a7 100644
--- a/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchManager.java
+++ b/ecord/co/src/main/java/org/onosproject/ecord/co/BigSwitchManager.java
@@ -56,8 +56,13 @@
public class BigSwitchManager
extends AbstractListenerManager<BigSwitchEvent, BigSwitchListener>
implements BigSwitchService {
+
+
private static final Logger log = getLogger(BigSwitchDeviceProvider.class);
+ // annotation on a big switch port
+ public static final String REALIZED_BY = "bigswitch:realizedBy";
+
private static final String PORT_MAP = "ecord-port-map";
private static final String PORT_COUNTER = "ecord-port-counter";
private static final Serializer SERIALIZER = Serializer.using(KryoNamespaces.API);
@@ -107,22 +112,28 @@
@Override
public List<PortDescription> getPorts() {
return portMap.keySet().stream()
- .map(cp -> toPortDescription(cp))
+ .map(cp -> toVirtualPortDescription(cp))
.collect(Collectors.toList());
}
/**
* Convert connect point to port description.
*
- * @param cp connect point
- * @return port description
+ * @param cp connect point of physical port
+ * @return port description of virtual big switch port
*/
- private PortDescription toPortDescription(ConnectPoint cp) {
+ private PortDescription toVirtualPortDescription(ConnectPoint cp) {
Port p = deviceService.getPort(cp.deviceId(), cp.port());
- // This is annoying
+ if (p == null) {
+ return null;
+ }
+ // copy annotation
DefaultAnnotations.Builder annot = DefaultAnnotations.builder();
p.annotations().keys()
.forEach(k -> annot.set(k, p.annotations().value(k)));
+ // add annotation about underlying physical connect-point
+ annot.set(REALIZED_BY, String.format("%s/%s", cp.deviceId().toString(),
+ cp.port().toString()));
return new DefaultPortDescription(
PortNumber.portNumber(portMap.get(cp).value()),
@@ -147,20 +158,24 @@
log.debug("Edge event {} {}", event.subject(), event.type());
BigSwitchEvent.Type bigSwitchEvent;
+ PortDescription descr = null;
switch (event.type()) {
case EDGE_PORT_ADDED:
portMap.put(event.subject(), portCounter.getAndIncrement());
+ descr = toVirtualPortDescription(event.subject());
bigSwitchEvent = BigSwitchEvent.Type.PORT_ADDED;
break;
case EDGE_PORT_REMOVED:
+ descr = toVirtualPortDescription(event.subject());
portMap.remove(event.subject());
bigSwitchEvent = BigSwitchEvent.Type.PORT_REMOVED;
break;
default:
return;
}
-
- post(new BigSwitchEvent(bigSwitchEvent, toPortDescription(event.subject())));
+ if (descr != null) {
+ post(new BigSwitchEvent(bigSwitchEvent, descr));
+ }
}
}
@@ -190,7 +205,7 @@
// Update if state of existing edge changed
ConnectPoint cp = new ConnectPoint(event.subject().id(), event.port().number());
if (portMap.containsKey(cp)) {
- post(new BigSwitchEvent(BigSwitchEvent.Type.PORT_UPDATED, toPortDescription(cp)));
+ post(new BigSwitchEvent(BigSwitchEvent.Type.PORT_UPDATED, toVirtualPortDescription(cp)));
}
break;
default:
diff --git a/ecord/co/src/main/java/org/onosproject/ecord/co/CrossConnectConfig.java b/ecord/co/src/main/java/org/onosproject/ecord/co/CrossConnectConfig.java
new file mode 100644
index 0000000..fb76cea
--- /dev/null
+++ b/ecord/co/src/main/java/org/onosproject/ecord/co/CrossConnectConfig.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ecord.co;
+
+import java.util.Optional;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+
+import com.google.common.annotations.Beta;
+
+// This Config is not specific to E-CORD, so it might make sense to move it out
+/**
+ * Configuration information about Cross Connect ports.
+ */
+@Beta
+public class CrossConnectConfig extends Config<ConnectPoint> {
+
+ /**
+ * Indicates remote ConnectPoint connected to this cross connect port.
+ *
+ * @return ConnectPoint information
+ */
+ public Optional<ConnectPoint> remote() {
+ String s = get("remote", null);
+ if (s == null) {
+ return Optional.empty();
+ }
+ String[] split = s.split("/");
+ if (split.length != 2) {
+ // ill-formed String
+ return Optional.empty();
+ }
+ DeviceId did = DeviceId.deviceId(split[0]);
+ PortNumber num = PortNumber.fromString(split[1]);
+ return Optional.of(new ConnectPoint(did, num));
+ }
+
+ /**
+ * Specifies the remote ConnectPoint connected to this cross connect port.
+ *
+ * @param remote device connect point identifier
+ * @return self
+ */
+ public CrossConnectConfig remote(ConnectPoint remote) {
+ if (remote == null) {
+ return (CrossConnectConfig) setOrClear("remote", (String) null);
+ }
+
+ return (CrossConnectConfig) setOrClear("remote", String.format("%s/%s", remote.deviceId(), remote.port()));
+ }
+}