From c7b3c451c77458271c9fdfb196a5704da26399e1 Mon Sep 17 00:00:00 2001 From: Charles Chan Date: Tue, 19 Jun 2018 20:31:57 -0700 Subject: [PATCH] Implement CLI and REST API for Xconnect Deprecate the old way of configuring Xconnect via network config Change-Id: I5b9ac7852517c25805bcbfc0e7b3bec3a52eed9f --- .../segmentrouting/SegmentRoutingManager.java | 37 +- .../segmentrouting/SegmentRoutingService.java | 58 ++ .../segmentrouting/XConnectHandler.java | 3 + .../cli/XconnectAddCommand.java | 66 ++ .../cli/XconnectListCommand.java | 32 + .../cli/XconnectRemoveCommand.java | 48 ++ .../segmentrouting/config/XConnectConfig.java | 3 + .../segmentrouting/mcast/McastHandler.java | 3 +- .../xconnect/api/XconnectCodec.java | 64 ++ .../xconnect/api/XconnectDesc.java | 88 +++ .../xconnect/api/XconnectKey.java | 88 +++ .../xconnect/api/XconnectService.java | 74 +++ .../xconnect/api/package-info.java | 20 + .../xconnect/impl/XconnectManager.java | 607 ++++++++++++++++++ .../xconnect/impl/package-info.java | 20 + .../OSGI-INF/blueprint/shell-config.xml | 23 + apps/segmentrouting/web/BUCK | 2 +- apps/segmentrouting/web/pom.xml | 4 +- .../web/SegmentRoutingWebApplication.java | 8 +- .../web/XconnectWebResource.java | 109 ++++ .../resources/definitions/XconnectCreate.json | 29 + .../resources/definitions/XconnectDelete.json | 20 + 22 files changed, 1373 insertions(+), 33 deletions(-) create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectAddCommand.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectListCommand.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectRemoveCommand.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/package-info.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java create mode 100644 apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/package-info.java create mode 100644 apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/XconnectWebResource.java create mode 100644 apps/segmentrouting/web/src/main/resources/definitions/XconnectCreate.json create mode 100644 apps/segmentrouting/web/src/main/resources/definitions/XconnectDelete.json diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java index 24172311c2..292c94172d 100644 --- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java @@ -125,6 +125,7 @@ import org.onosproject.segmentrouting.mcast.McastStoreKey; import org.onosproject.segmentrouting.storekey.PortNextObjectiveStoreKey; import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey; import org.onosproject.segmentrouting.storekey.XConnectStoreKey; +import org.onosproject.segmentrouting.xconnect.api.XconnectService; import org.onosproject.store.serializers.KryoNamespaces; import org.onosproject.store.service.EventuallyConsistentMap; import org.onosproject.store.service.EventuallyConsistentMapBuilder; @@ -230,6 +231,9 @@ public class SegmentRoutingManager implements SegmentRoutingService { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) public LeadershipService leadershipService; + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY) + public XconnectService xconnectService; + @Property(name = "activeProbing", boolValue = true, label = "Enable active probing to discover dual-homed hosts.") boolean activeProbing = true; @@ -809,11 +813,7 @@ public class SegmentRoutingManager implements SegmentRoutingService { ImmutableMap.copyOf(defaultRoutingHandler.shouldProgramCache); } - /** - * Extracts the application ID from the manager. - * - * @return application ID - */ + @Override public ApplicationId appId() { return appId; } @@ -890,38 +890,21 @@ public class SegmentRoutingManager implements SegmentRoutingService { return tunnelHandler.getTunnel(tunnelId); } - /** - * Returns internal VLAN for untagged hosts on given connect point. - *

- * The internal VLAN is either vlan-untagged for an access port, - * or vlan-native for a trunk port. - * - * @param connectPoint connect point - * @return internal VLAN or null if both vlan-untagged and vlan-native are undefined - */ + @Override public VlanId getInternalVlanId(ConnectPoint connectPoint) { VlanId untaggedVlanId = interfaceService.getUntaggedVlanId(connectPoint); VlanId nativeVlanId = interfaceService.getNativeVlanId(connectPoint); return untaggedVlanId != null ? untaggedVlanId : nativeVlanId; } - /** - * Returns optional pair device ID of given device. - * - * @param deviceId device ID - * @return optional pair device ID. Might be empty if pair device is not configured - */ - Optional getPairDeviceId(DeviceId deviceId) { + @Override + public Optional getPairDeviceId(DeviceId deviceId) { SegmentRoutingDeviceConfig deviceConfig = cfgService.getConfig(deviceId, SegmentRoutingDeviceConfig.class); return Optional.ofNullable(deviceConfig).map(SegmentRoutingDeviceConfig::pairDeviceId); } - /** - * Returns optional pair device local port of given device. - * - * @param deviceId device ID - * @return optional pair device ID. Might be empty if pair device is not configured - */ + + @Override public Optional getPairLocalPort(DeviceId deviceId) { SegmentRoutingDeviceConfig deviceConfig = cfgService.getConfig(deviceId, SegmentRoutingDeviceConfig.class); diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java index 8d6451748c..c8c9044983 100644 --- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java @@ -15,10 +15,14 @@ */ package org.onosproject.segmentrouting; +import com.google.common.annotations.Beta; import com.google.common.collect.Multimap; +import org.apache.commons.lang3.NotImplementedException; import org.onlab.packet.IpAddress; import org.onlab.packet.IpPrefix; +import org.onlab.packet.VlanId; import org.onosproject.cluster.NodeId; +import org.onosproject.core.ApplicationId; import org.onosproject.net.ConnectPoint; import org.onosproject.net.DeviceId; import org.onosproject.net.Link; @@ -38,6 +42,7 @@ import org.onosproject.segmentrouting.mcast.McastStoreKey; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -46,12 +51,18 @@ import java.util.Set; public interface SegmentRoutingService { /** * VLAN cross-connect ACL priority. + * + * @deprecated in ONOS 1.12. Replaced by {@link org.onosproject.segmentrouting.xconnect.api.XconnectService} */ + @Deprecated int XCONNECT_ACL_PRIORITY = 60000; /** * VLAN cross-connect Bridging priority. + * + * @deprecated in ONOS 1.12. Replaced by {@link org.onosproject.segmentrouting.xconnect.api.XconnectService} */ + @Deprecated int XCONNECT_PRIORITY = 1000; /** @@ -302,4 +313,51 @@ public interface SegmentRoutingService { * @return shouldProgram local cache */ Map getShouldProgramCache(); + + /** + * Gets application id. + * + * @return application id + */ + default ApplicationId appId() { + throw new NotImplementedException("appId not implemented"); + } + + /** + * Returns internal VLAN for untagged hosts on given connect point. + *

+ * The internal VLAN is either vlan-untagged for an access port, + * or vlan-native for a trunk port. + * + * @param connectPoint connect point + * @return internal VLAN or null if both vlan-untagged and vlan-native are undefined + */ + @Beta + default VlanId getInternalVlanId(ConnectPoint connectPoint) { + throw new NotImplementedException("getInternalVlanId not implemented"); + } + + + /** + * Returns optional pair device ID of given device. + * + * @param deviceId device ID + * @return optional pair device ID. Might be empty if pair device is not configured + */ + @Beta + default Optional getPairDeviceId(DeviceId deviceId) { + throw new NotImplementedException("getPairDeviceId not implemented"); + } + + + /** + * Returns optional pair device local port of given device. + * + * @param deviceId device ID + * @return optional pair device ID. Might be empty if pair device is not configured + */ + @Beta + default Optional getPairLocalPort(DeviceId deviceId) { + throw new NotImplementedException("getPairLocalPort not implemented"); + } } diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/XConnectHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/XConnectHandler.java index 96c1a38df0..5dafb3f72d 100644 --- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/XConnectHandler.java +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/XConnectHandler.java @@ -54,7 +54,10 @@ import java.util.stream.Collectors; /** * Handles cross connect related events. + * + * @deprecated in ONOS 1.12. Replaced by {@link org.onosproject.segmentrouting.xconnect.impl.XconnectManager} */ +@Deprecated public class XConnectHandler { private static final Logger log = LoggerFactory.getLogger(XConnectHandler.class); private static final String CONFIG_NOT_FOUND = "XConnect config not found"; diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectAddCommand.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectAddCommand.java new file mode 100644 index 0000000000..a1ad929d53 --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectAddCommand.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.cli; + +import com.google.common.collect.Sets; +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.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.segmentrouting.xconnect.api.XconnectService; + +import java.util.Set; + +/** + * Creates Xconnect. + */ +@Command(scope = "onos", name = "sr-xconnect-add", description = "Create Xconnect") +public class XconnectAddCommand extends AbstractShellCommand { + @Argument(index = 0, name = "deviceId", + description = "Device ID", + required = true, multiValued = false) + private String deviceIdStr; + + @Argument(index = 1, name = "vlanId", + description = "VLAN ID", + required = true, multiValued = false) + private String vlanIdStr; + + @Argument(index = 2, name = "port1", + description = "Port 1", + required = true, multiValued = false) + private String port1Str; + + @Argument(index = 3, name = "port2", + description = "Port 2", + required = true, multiValued = false) + private String port2Str; + + + @Override + protected void execute() { + DeviceId deviceId = DeviceId.deviceId(deviceIdStr); + VlanId vlanId = VlanId.vlanId(vlanIdStr); + PortNumber port1 = PortNumber.portNumber(port1Str); + PortNumber port2 = PortNumber.portNumber(port2Str); + Set ports = Sets.newHashSet(port1, port2); + + XconnectService xconnectService = get(XconnectService.class); + xconnectService.addOrUpdateXconnect(deviceId, vlanId, ports); + } +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectListCommand.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectListCommand.java new file mode 100644 index 0000000000..43919c47d3 --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectListCommand.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.cli; + +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.segmentrouting.xconnect.api.XconnectService; + +/** + * Lists Xconnects. + */ +@Command(scope = "onos", name = "sr-xconnect", description = "Lists all Xconnects") +public class XconnectListCommand extends AbstractShellCommand { + @Override + protected void execute() { + XconnectService xconnectService = get(XconnectService.class); + xconnectService.getXconnects().forEach(desc -> print("%s", desc)); + } +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectRemoveCommand.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectRemoveCommand.java new file mode 100644 index 0000000000..224a60000d --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/XconnectRemoveCommand.java @@ -0,0 +1,48 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.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.DeviceId; +import org.onosproject.segmentrouting.xconnect.api.XconnectService; + +/** + * Deletes Xconnect. + */ +@Command(scope = "onos", name = "sr-xconnect-remove", description = "Remove Xconnect") +public class XconnectRemoveCommand extends AbstractShellCommand { + @Argument(index = 0, name = "deviceId", + description = "Device ID", + required = true, multiValued = false) + private String deviceIdStr; + + @Argument(index = 1, name = "vlanId", + description = "VLAN ID", + required = true, multiValued = false) + private String vlanIdStr; + + @Override + protected void execute() { + DeviceId deviceId = DeviceId.deviceId(deviceIdStr); + VlanId vlanId = VlanId.vlanId(vlanIdStr); + + XconnectService xconnectService = get(XconnectService.class); + xconnectService.removeXonnect(deviceId, vlanId); + } +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/XConnectConfig.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/XConnectConfig.java index 76a391742d..6689b93e0f 100644 --- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/XConnectConfig.java +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/XConnectConfig.java @@ -32,7 +32,10 @@ import static com.google.common.base.Preconditions.checkArgument; /** * Configuration object for cross-connect. + * + * @deprecated in ONOS 1.12. Replaced by {@link org.onosproject.segmentrouting.xconnect.impl.XconnectManager} */ +@Deprecated public class XConnectConfig extends Config { private static final String VLAN = "vlan"; private static final String PORTS = "ports"; diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java index 44a600cb8b..88601aa234 100644 --- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java @@ -1613,7 +1613,8 @@ public class McastHandler { // Spine-facing port should have no subnet and no xconnect if (srManager.deviceConfiguration() != null && srManager.deviceConfiguration().getPortSubnets(ingressDevice, port).isEmpty() && - !srManager.xConnectHandler.hasXConnect(new ConnectPoint(ingressDevice, port))) { + (srManager.xconnectService == null || + !srManager.xconnectService.hasXconnect(new ConnectPoint(ingressDevice, port)))) { portBuilder.add(port); } } diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java new file mode 100644 index 0000000000..77824e87cc --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectCodec.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.xconnect.api; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Sets; +import org.onlab.packet.VlanId; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; + +public class XconnectCodec extends JsonCodec { + private static final String DEVICE_ID = "deviceId"; + private static final String VLAN_ID = "vlanId"; + private static final String PORTS = "ports"; + + private static Logger log = LoggerFactory.getLogger(XconnectCodec.class); + + @Override + public ObjectNode encode(XconnectDesc desc, CodecContext context) { + final ObjectNode result = context.mapper().createObjectNode(); + result.put(DEVICE_ID, desc.key().deviceId().toString()); + result.put(VLAN_ID, desc.key().vlanId().toString()); + final ArrayNode portNode = result.putArray(PORTS); + desc.ports().forEach(port -> portNode.add(port.toString())); + + return result; + } + + @Override + public XconnectDesc decode(ObjectNode json, CodecContext context) { + DeviceId deviceId = DeviceId.deviceId(json.path(DEVICE_ID).asText()); + VlanId vlanId = VlanId.vlanId(json.path(VLAN_ID).asText()); + + Set ports = Sets.newHashSet(); + JsonNode portNodes = json.get(PORTS); + if (portNodes != null) { + portNodes.forEach(portNode -> ports.add(PortNumber.portNumber(portNode.asInt()))); + } + + XconnectKey key = new XconnectKey(deviceId, vlanId); + return new XconnectDesc(key, ports); + } +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java new file mode 100644 index 0000000000..0eead313cf --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectDesc.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.xconnect.api; + +import com.google.common.base.MoreObjects; +import org.onosproject.net.PortNumber; + +import java.util.Objects; +import java.util.Set; + +/** + * Xconnect description. + */ +public class XconnectDesc { + private XconnectKey key; + private Set ports; + + /** + * Constructs new Xconnect description with given device ID and VLAN ID. + * + * @param key Xconnect key + * @param ports set of ports + */ + public XconnectDesc(XconnectKey key, Set ports) { + this.key = key; + this.ports = ports; + } + + /** + * Gets Xconnect key. + * + * @return Xconnect key + */ + public XconnectKey key() { + return key; + } + + /** + * Gets ports. + * + * @return set of ports + */ + public Set ports() { + return ports; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof XconnectDesc)) { + return false; + } + final XconnectDesc other = (XconnectDesc) obj; + return Objects.equals(this.key, other.key) && + Objects.equals(this.ports, other.ports); + } + + @Override + public int hashCode() { + return Objects.hash(key, ports); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("key", key) + .add("ports", ports) + .toString(); + } +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java new file mode 100644 index 0000000000..1f2d4462b2 --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectKey.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.xconnect.api; + +import com.google.common.base.MoreObjects; +import org.onlab.packet.VlanId; +import org.onosproject.net.DeviceId; + +import java.util.Objects; + +/** + * Xconnect key. + */ +public class XconnectKey { + private DeviceId deviceId; + private VlanId vlanId; + + /** + * Constructs new XconnectKey with given device ID and VLAN ID. + * + * @param deviceId device ID + * @param vlanId vlan ID + */ + public XconnectKey(DeviceId deviceId, VlanId vlanId) { + this.deviceId = deviceId; + this.vlanId = vlanId; + } + + /** + * Gets device ID. + * + * @return device ID of the Xconnect key + */ + public DeviceId deviceId() { + return deviceId; + } + + /** + * Gets VLAN ID. + * + * @return VLAN ID of the Xconnect key + */ + public VlanId vlanId() { + return vlanId; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof XconnectKey)) { + return false; + } + final XconnectKey other = (XconnectKey) obj; + return Objects.equals(this.deviceId, other.deviceId) && + Objects.equals(this.vlanId, other.vlanId); + } + + @Override + public int hashCode() { + return Objects.hash(deviceId, vlanId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("deviceId", deviceId) + .add("vlanId", vlanId) + .toString(); + } +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java new file mode 100644 index 0000000000..1fede7195e --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/XconnectService.java @@ -0,0 +1,74 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.xconnect.api; + +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.VlanId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; + +import java.util.Set; + +/** + * VLAN cross connect between exactly two ports. + */ +@Service +public interface XconnectService { + + /** + * VLAN cross-connect ACL priority. + */ + int XCONNECT_ACL_PRIORITY = 60000; + + /** + * VLAN cross-connect Bridging priority. + */ + int XCONNECT_PRIORITY = 1000; + + /** + * Creates or updates Xconnect. + * + * @param deviceId device ID + * @param vlanId VLAN ID + * @param ports set of ports + */ + void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set ports); + + /** + * Deletes Xconnect. + * + * @param deviceId device ID + * @param vlanId VLAN ID + */ + void removeXonnect(DeviceId deviceId, VlanId vlanId); + + /** + * Gets Xconnects. + * + * @return set of Xconnect descriptions + */ + Set getXconnects(); + + /** + * Check if there is Xconnect configured on given connect point. + * + * @param cp connect point + * @return true if there is Xconnect configured on the connect point + */ + boolean hasXconnect(ConnectPoint cp); + +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/package-info.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/package-info.java new file mode 100644 index 0000000000..56df70681f --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/api/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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. + */ + +/** + * VLAN cross connect API. + */ +package org.onosproject.segmentrouting.xconnect.api; \ No newline at end of file diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java new file mode 100644 index 0000000000..f997d53e50 --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/XconnectManager.java @@ -0,0 +1,607 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.xconnect.impl; + +import com.google.common.collect.ImmutableSet; +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.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.util.KryoNamespace; +import org.onosproject.codec.CodecService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.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.DefaultNextObjective; +import org.onosproject.net.flowobjective.DefaultObjectiveContext; +import org.onosproject.net.flowobjective.FilteringObjective; +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.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.xconnect.api.XconnectCodec; +import org.onosproject.segmentrouting.xconnect.api.XconnectDesc; +import org.onosproject.segmentrouting.xconnect.api.XconnectKey; +import org.onosproject.segmentrouting.xconnect.api.XconnectService; +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 org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@Service +@Component(immediate = true) +public class XconnectManager implements XconnectService { + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private CodecService codecService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + private StorageService storageService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + public NetworkConfigService netCfgService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + public DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + public FlowObjectiveService flowObjectiveService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + public MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY) + public SegmentRoutingService srService; + + private static final String APP_NAME = "org.onosproject.xconnect"; + private static final String ERROR_NOT_MASTER = "Not master controller"; + + private static Logger log = LoggerFactory.getLogger(XconnectManager.class); + + private ApplicationId appId; + private ConsistentMap> xconnectStore; + private ConsistentMap xconnectNextObjStore; + + private final MapEventListener> xconnectListener = new XconnectMapListener(); + private final DeviceListener deviceListener = new InternalDeviceListener(); + + @Activate + void activate() { + appId = coreService.registerApplication(APP_NAME); + codecService.registerCodec(XconnectDesc.class, new XconnectCodec()); + + KryoNamespace.Builder serializer = KryoNamespace.newBuilder() + .register(KryoNamespaces.API) + .register(XconnectKey.class); + + xconnectStore = storageService.>consistentMapBuilder() + .withName("onos-sr-xconnect") + .withRelaxedReadConsistency() + .withSerializer(Serializer.using(serializer.build())) + .build(); + xconnectStore.addListener(xconnectListener); + + xconnectNextObjStore = storageService.consistentMapBuilder() + .withName("onos-sr-xconnect-next") + .withRelaxedReadConsistency() + .withSerializer(Serializer.using(serializer.build())) + .build(); + + deviceService.addListener(deviceListener); + + log.info("Started"); + } + + @Deactivate + void deactivate() { + xconnectStore.removeListener(xconnectListener); + deviceService.removeListener(deviceListener); + codecService.unregisterCodec(XconnectDesc.class); + + log.info("Stopped"); + } + + @Override + public void addOrUpdateXconnect(DeviceId deviceId, VlanId vlanId, Set ports) { + log.info("Adding or updating xconnect. deviceId={}, vlanId={}, ports={}", + deviceId, vlanId, ports); + final XconnectKey key = new XconnectKey(deviceId, vlanId); + xconnectStore.put(key, ports); + } + + @Override + public void removeXonnect(DeviceId deviceId, VlanId vlanId) { + log.info("Removing xconnect. deviceId={}, vlanId={}", + deviceId, vlanId); + final XconnectKey key = new XconnectKey(deviceId, vlanId); + xconnectStore.remove(key); + } + + @Override + public Set getXconnects() { + return xconnectStore.asJavaMap().entrySet().stream() + .map(e -> new XconnectDesc(e.getKey(), e.getValue())) + .collect(Collectors.toSet()); + } + + @Override + public boolean hasXconnect(ConnectPoint cp) { + return getXconnects().stream().anyMatch(desc -> + desc.key().deviceId().equals(cp.deviceId()) && desc.ports().contains(cp.port()) + ); + } + + private class XconnectMapListener implements MapEventListener> { + @Override + public void event(MapEvent> event) { + XconnectKey key = event.key(); + Versioned> ports = event.newValue(); + Versioned> oldPorts = event.oldValue(); + + switch (event.type()) { + case INSERT: + populateXConnect(key, ports.value()); + break; + case UPDATE: + updateXConnect(key, oldPorts.value(), ports.value()); + break; + case REMOVE: + revokeXConnect(key, oldPorts.value()); + break; + default: + break; + } + } + } + + private class InternalDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + DeviceId deviceId = event.subject().id(); + if (!mastershipService.isLocalMaster(deviceId)) { + return; + } + + switch (event.type()) { + case DEVICE_ADDED: + case DEVICE_AVAILABILITY_CHANGED: + case DEVICE_UPDATED: + if (deviceService.isAvailable(deviceId)) { + init(deviceId); + } else { + cleanup(deviceId); + } + break; + default: + break; + } + } + } + + void init(DeviceId deviceId) { + getXconnects().stream() + .filter(desc -> desc.key().deviceId().equals(deviceId)) + .forEach(desc -> populateXConnect(desc.key(), desc.ports())); + } + + void cleanup(DeviceId deviceId) { + xconnectNextObjStore.entrySet().stream() + .filter(entry -> entry.getKey().deviceId().equals(deviceId)) + .forEach(entry -> xconnectNextObjStore.remove(entry.getKey())); + log.debug("{} is removed from xConnectNextObjStore", deviceId); + } + + /** + * Populates XConnect groups and flows for given key. + * + * @param key XConnect key + * @param ports a set of ports to be cross-connected + */ + private void populateXConnect(XconnectKey key, Set ports) { + if (!mastershipService.isLocalMaster(key.deviceId())) { + log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER); + return; + } + + ports = addPairPort(key.deviceId(), ports); + populateFilter(key, ports); + populateFwd(key, populateNext(key, ports)); + populateAcl(key); + } + + /** + * Populates filtering objectives for given XConnect. + * + * @param key XConnect store key + * @param ports XConnect ports + */ + private void populateFilter(XconnectKey key, Set ports) { + ports.forEach(port -> { + FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port); + ObjectiveContext context = new DefaultObjectiveContext( + (objective) -> log.debug("XConnect FilterObj for {} on port {} populated", + key, port), + (objective, error) -> + log.warn("Failed to populate XConnect FilterObj for {} on port {}: {}", + key, port, error)); + flowObjectiveService.filter(key.deviceId(), filtObjBuilder.add(context)); + }); + } + + /** + * Populates next objectives for given XConnect. + * + * @param key XConnect store key + * @param ports XConnect ports + */ + private NextObjective populateNext(XconnectKey key, Set ports) { + NextObjective nextObj; + if (xconnectNextObjStore.containsKey(key)) { + nextObj = xconnectNextObjStore.get(key).value(); + log.debug("NextObj for {} found, id={}", key, nextObj.id()); + } else { + NextObjective.Builder nextObjBuilder = nextObjBuilder(key, ports); + ObjectiveContext nextContext = new DefaultObjectiveContext( + // To serialize this with kryo + (Serializable & Consumer) (objective) -> + log.debug("XConnect NextObj for {} added", key), + (Serializable & BiConsumer) (objective, error) -> + log.warn("Failed to add XConnect NextObj for {}: {}", key, error) + ); + nextObj = nextObjBuilder.add(nextContext); + flowObjectiveService.next(key.deviceId(), nextObj); + xconnectNextObjStore.put(key, nextObj); + log.debug("NextObj for {} not found. Creating new NextObj with id={}", key, nextObj.id()); + } + return nextObj; + } + + /** + * Populates bridging forwarding objectives for given XConnect. + * + * @param key XConnect store key + * @param nextObj next objective + */ + private void populateFwd(XconnectKey key, NextObjective nextObj) { + ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextObj.id()); + ObjectiveContext fwdContext = new DefaultObjectiveContext( + (objective) -> log.debug("XConnect FwdObj for {} populated", key), + (objective, error) -> + log.warn("Failed to populate XConnect FwdObj for {}: {}", key, error)); + flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.add(fwdContext)); + } + + /** + * Populates ACL forwarding objectives for given XConnect. + * + * @param key XConnect store key + */ + private void populateAcl(XconnectKey key) { + ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId()); + ObjectiveContext aclContext = new DefaultObjectiveContext( + (objective) -> log.debug("XConnect AclObj for {} populated", key), + (objective, error) -> + log.warn("Failed to populate XConnect AclObj for {}: {}", key, error)); + flowObjectiveService.forward(key.deviceId(), aclObjBuilder.add(aclContext)); + } + + /** + * Revokes XConnect groups and flows for given key. + * + * @param key XConnect key + * @param ports XConnect ports + */ + private void revokeXConnect(XconnectKey key, Set ports) { + if (!mastershipService.isLocalMaster(key.deviceId())) { + log.info("Abort populating XConnect {}: {}", key, ERROR_NOT_MASTER); + return; + } + + ports = addPairPort(key.deviceId(), ports); + revokeFilter(key, ports); + if (xconnectNextObjStore.containsKey(key)) { + NextObjective nextObj = xconnectNextObjStore.get(key).value(); + revokeFwd(key, nextObj, null); + revokeNext(key, nextObj, null); + } else { + log.warn("NextObj for {} does not exist in the store.", key); + } + revokeAcl(key); + } + + /** + * Revokes filtering objectives for given XConnect. + * + * @param key XConnect store key + * @param ports XConnect ports + */ + private void revokeFilter(XconnectKey key, Set ports) { + ports.forEach(port -> { + FilteringObjective.Builder filtObjBuilder = filterObjBuilder(key, port); + ObjectiveContext context = new DefaultObjectiveContext( + (objective) -> log.debug("XConnect FilterObj for {} on port {} revoked", + key, port), + (objective, error) -> + log.warn("Failed to revoke XConnect FilterObj for {} on port {}: {}", + key, port, error)); + flowObjectiveService.filter(key.deviceId(), filtObjBuilder.remove(context)); + }); + } + + /** + * Revokes next objectives for given XConnect. + * + * @param key XConnect store key + * @param nextObj next objective + * @param nextFuture completable future for this next objective operation + */ + private void revokeNext(XconnectKey key, NextObjective nextObj, + CompletableFuture nextFuture) { + ObjectiveContext context = new ObjectiveContext() { + @Override + public void onSuccess(Objective objective) { + log.debug("Previous NextObj for {} removed", key); + if (nextFuture != null) { + nextFuture.complete(null); + } + } + + @Override + public void onError(Objective objective, ObjectiveError error) { + log.warn("Failed to remove previous NextObj for {}: {}", key, error); + if (nextFuture != null) { + nextFuture.complete(error); + } + } + }; + flowObjectiveService.next(key.deviceId(), + (NextObjective) nextObj.copy().remove(context)); + xconnectNextObjStore.remove(key); + } + + /** + * Revokes bridging forwarding objectives for given XConnect. + * + * @param key XConnect store key + * @param nextObj next objective + * @param fwdFuture completable future for this forwarding objective operation + */ + private void revokeFwd(XconnectKey key, NextObjective nextObj, + CompletableFuture fwdFuture) { + ForwardingObjective.Builder fwdObjBuilder = fwdObjBuilder(key, nextObj.id()); + ObjectiveContext context = new ObjectiveContext() { + @Override + public void onSuccess(Objective objective) { + log.debug("Previous FwdObj for {} removed", key); + if (fwdFuture != null) { + fwdFuture.complete(null); + } + } + + @Override + public void onError(Objective objective, ObjectiveError error) { + log.warn("Failed to remove previous FwdObj for {}: {}", key, error); + if (fwdFuture != null) { + fwdFuture.complete(error); + } + } + }; + flowObjectiveService.forward(key.deviceId(), fwdObjBuilder.remove(context)); + } + + /** + * Revokes ACL forwarding objectives for given XConnect. + * + * @param key XConnect store key + */ + private void revokeAcl(XconnectKey key) { + ForwardingObjective.Builder aclObjBuilder = aclObjBuilder(key.vlanId()); + ObjectiveContext aclContext = new DefaultObjectiveContext( + (objective) -> log.debug("XConnect AclObj for {} populated", key), + (objective, error) -> + log.warn("Failed to populate XConnect AclObj for {}: {}", key, error)); + flowObjectiveService.forward(key.deviceId(), aclObjBuilder.remove(aclContext)); + } + + /** + * Updates XConnect groups and flows for given key. + * + * @param key XConnect key + * @param prevPorts previous XConnect ports + * @param ports new XConnect ports + */ + private void updateXConnect(XconnectKey key, Set prevPorts, + Set ports) { + // NOTE: ACL flow doesn't include port information. No need to update it. + // Pair port is built-in and thus not going to change. No need to update it. + + // remove old filter + prevPorts.stream().filter(port -> !ports.contains(port)).forEach(port -> + revokeFilter(key, ImmutableSet.of(port))); + // install new filter + ports.stream().filter(port -> !prevPorts.contains(port)).forEach(port -> + populateFilter(key, ImmutableSet.of(port))); + + CompletableFuture fwdFuture = new CompletableFuture<>(); + CompletableFuture nextFuture = new CompletableFuture<>(); + + if (xconnectNextObjStore.containsKey(key)) { + NextObjective nextObj = xconnectNextObjStore.get(key).value(); + revokeFwd(key, nextObj, fwdFuture); + + fwdFuture.thenAcceptAsync(fwdStatus -> { + if (fwdStatus == null) { + log.debug("Fwd removed. Now remove group {}", key); + revokeNext(key, nextObj, nextFuture); + } + }); + + nextFuture.thenAcceptAsync(nextStatus -> { + if (nextStatus == null) { + log.debug("Installing new group and flow for {}", key); + populateFwd(key, populateNext(key, ports)); + } + }); + } else { + log.warn("NextObj for {} does not exist in the store.", key); + } + } + + /** + * Creates a next objective builder for XConnect. + * + * @param key XConnect key + * @param ports set of XConnect ports + * @return next objective builder + */ + private NextObjective.Builder nextObjBuilder(XconnectKey key, Set ports) { + int nextId = flowObjectiveService.allocateNextId(); + TrafficSelector metadata = + DefaultTrafficSelector.builder().matchVlanId(key.vlanId()).build(); + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withId(nextId) + .withType(NextObjective.Type.BROADCAST).fromApp(appId) + .withMeta(metadata); + ports.forEach(port -> { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder(); + tBuilder.setOutput(port); + nextObjBuilder.addTreatment(tBuilder.build()); + }); + return nextObjBuilder; + } + + /** + * Creates a bridging forwarding objective builder for XConnect. + * + * @param key XConnect key + * @param nextId next ID of the broadcast group for this XConnect key + * @return forwarding objective builder + */ + private ForwardingObjective.Builder fwdObjBuilder(XconnectKey key, int nextId) { + /* + * Driver should treat objectives with MacAddress.NONE and !VlanId.NONE + * as the VLAN cross-connect broadcast rules + */ + TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); + sbuilder.matchVlanId(key.vlanId()); + sbuilder.matchEthDst(MacAddress.NONE); + + ForwardingObjective.Builder fob = DefaultForwardingObjective.builder(); + fob.withFlag(ForwardingObjective.Flag.SPECIFIC) + .withSelector(sbuilder.build()) + .nextStep(nextId) + .withPriority(XCONNECT_PRIORITY) + .fromApp(appId) + .makePermanent(); + return fob; + } + + /** + * Creates an ACL forwarding objective builder for XConnect. + * + * @param vlanId cross connect VLAN id + * @return forwarding objective builder + */ + private ForwardingObjective.Builder aclObjBuilder(VlanId vlanId) { + TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); + sbuilder.matchVlanId(vlanId); + + TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); + + ForwardingObjective.Builder fob = DefaultForwardingObjective.builder(); + fob.withFlag(ForwardingObjective.Flag.VERSATILE) + .withSelector(sbuilder.build()) + .withTreatment(tbuilder.build()) + .withPriority(XCONNECT_ACL_PRIORITY) + .fromApp(appId) + .makePermanent(); + return fob; + } + + /** + * Creates a filtering objective builder for XConnect. + * + * @param key XConnect key + * @param port XConnect ports + * @return next objective builder + */ + private FilteringObjective.Builder filterObjBuilder(XconnectKey key, PortNumber port) { + FilteringObjective.Builder fob = DefaultFilteringObjective.builder(); + fob.withKey(Criteria.matchInPort(port)) + .addCondition(Criteria.matchVlanId(key.vlanId())) + .addCondition(Criteria.matchEthDst(MacAddress.NONE)) + .withPriority(XCONNECT_PRIORITY); + return fob.permit().fromApp(appId); + } + + /** + * Add pair port to the given set of port. + * + * @param deviceId device Id + * @param ports ports specified in the xconnect config + * @return port specified in the xconnect config plus the pair port (if configured) + */ + private Set addPairPort(DeviceId deviceId, Set ports) { + if (srService == null) { + return ports; + } + Set newPorts = Sets.newHashSet(ports); + srService.getPairLocalPort(deviceId).ifPresent(newPorts::add); + return newPorts; + } + + // TODO DEVICE listener + // up : init + // down: removeDevice + + +} diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/package-info.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/package-info.java new file mode 100644 index 0000000000..547e3bc7e1 --- /dev/null +++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/xconnect/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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. + */ + +/** + * VLAN cross connect implementation. + */ +package org.onosproject.segmentrouting.xconnect.impl; \ No newline at end of file diff --git a/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml index d575413515..04682e9641 100644 --- a/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml +++ b/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -98,10 +98,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/segmentrouting/web/BUCK b/apps/segmentrouting/web/BUCK index bf6864cd11..9706438690 100644 --- a/apps/segmentrouting/web/BUCK +++ b/apps/segmentrouting/web/BUCK @@ -9,7 +9,7 @@ COMPILE_DEPS = [ osgi_jar_with_tests ( deps = COMPILE_DEPS, web_context = '/onos/segmentrouting', - api_title = 'Segment Routing Rest Server', + api_title = 'Segment Routing REST API', api_version = '1.0', api_description = 'REST API for Segment Routing Application', api_package = 'org.onosproject.segmentrouting.web', diff --git a/apps/segmentrouting/web/pom.xml b/apps/segmentrouting/web/pom.xml index 8c3901bda8..e7345e985b 100644 --- a/apps/segmentrouting/web/pom.xml +++ b/apps/segmentrouting/web/pom.xml @@ -29,12 +29,12 @@ http://onosproject.org - Segment Routing REST Server + Segment Routing REST API /onos/segmentrouting 1.0 - Segment Routing Rest Server + Segment Routing REST APIr REST API for Segment Routing Application diff --git a/apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/SegmentRoutingWebApplication.java b/apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/SegmentRoutingWebApplication.java index 75646d1529..670403039e 100644 --- a/apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/SegmentRoutingWebApplication.java +++ b/apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/SegmentRoutingWebApplication.java @@ -21,11 +21,15 @@ import org.onlab.rest.AbstractWebApplication; import java.util.Set; /** - * Segment Routing Web application. + * Segment Routing REST API. */ public class SegmentRoutingWebApplication extends AbstractWebApplication { @Override public Set> getClasses() { - return getClasses(PseudowireWebResource.class, McastWebResource.class); + return getClasses( + PseudowireWebResource.class, + McastWebResource.class, + XconnectWebResource.class + ); } } diff --git a/apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/XconnectWebResource.java b/apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/XconnectWebResource.java new file mode 100644 index 0000000000..edaea9e1ac --- /dev/null +++ b/apps/segmentrouting/web/src/main/java/org/onosproject/segmentrouting/web/XconnectWebResource.java @@ -0,0 +1,109 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * 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.segmentrouting.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.onosproject.rest.AbstractWebResource; +import org.onosproject.segmentrouting.xconnect.api.XconnectDesc; +import org.onosproject.segmentrouting.xconnect.api.XconnectService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; + +import static org.onlab.util.Tools.readTreeFromStream; + +/** + * Query, create and remove Xconnects. + */ +@Path("xconnect") +public class XconnectWebResource extends AbstractWebResource { + private static final String XCONNECTS = "xconnects"; + private static Logger log = LoggerFactory.getLogger(XconnectWebResource.class); + + /** + * Gets all Xconnects. + * + * @return an array of xconnects + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getXconnects() { + XconnectService xconnectService = get(XconnectService.class); + Set xconnects = xconnectService.getXconnects(); + + ObjectNode result = encodeArray(XconnectDesc.class, XCONNECTS, xconnects); + return ok(result).build(); + } + + /** + * Create a new Xconnect. + * + * @param input JSON stream for xconnect to create + * @return 200 OK + * @throws IOException Throws IO exception + * @onos.rsModel XconnectCreate + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response addOrUpdateXconnect(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode json = readTreeFromStream(mapper, input); + XconnectDesc desc = codec(XconnectDesc.class).decode(json, this); + + if (desc.ports().size() != 2) { + throw new IllegalArgumentException("Ports should have only two items."); + } + + XconnectService xconnectService = get(XconnectService.class); + xconnectService.addOrUpdateXconnect(desc.key().deviceId(), desc.key().vlanId(), desc.ports()); + + return Response.ok().build(); + } + + + /** + * Delete an existing Xconnect. + * + * @param input JSON stream for xconnect to remove + * @return 204 NO CONTENT + * @throws IOException Throws IO exception + * @onos.rsModel XconnectDelete + */ + @DELETE + @Consumes(MediaType.APPLICATION_JSON) + public Response removeXconnect(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode json = readTreeFromStream(mapper, input); + XconnectDesc desc = codec(XconnectDesc.class).decode(json, this); + + XconnectService xconnectService = get(XconnectService.class); + xconnectService.removeXonnect(desc.key().deviceId(), desc.key().vlanId()); + + return Response.noContent().build(); + } +} diff --git a/apps/segmentrouting/web/src/main/resources/definitions/XconnectCreate.json b/apps/segmentrouting/web/src/main/resources/definitions/XconnectCreate.json new file mode 100644 index 0000000000..8d2099f75f --- /dev/null +++ b/apps/segmentrouting/web/src/main/resources/definitions/XconnectCreate.json @@ -0,0 +1,29 @@ +{ + "type": "object", + "title": "xconnect-creation", + "required": [ + "deviceId", + "vlanId", + "ports" + ], + "properties": { + "deviceId": { + "type": "string", + "example": "of:0000000000000201", + "description": "Device ID" + }, + "vlanId": { + "type": "string", + "example": "94", + "description": "VLAN ID" + }, + "ports": { + "type": "array", + "items": { + "type": "int8", + "description": "Port number" + }, + "example": [1, 2] + } + } +} \ No newline at end of file diff --git a/apps/segmentrouting/web/src/main/resources/definitions/XconnectDelete.json b/apps/segmentrouting/web/src/main/resources/definitions/XconnectDelete.json new file mode 100644 index 0000000000..fcd5f66110 --- /dev/null +++ b/apps/segmentrouting/web/src/main/resources/definitions/XconnectDelete.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "title": "xconnect-deletion", + "required": [ + "deviceId", + "vlanId" + ], + "properties": { + "deviceId": { + "type": "string", + "example": "of:0000000000000201", + "description": "Device ID" + }, + "vlanId": { + "type": "string", + "example": "94", + "description": "VLAN ID" + } + } +} \ No newline at end of file