Castor app - Needs final review and merge
Change-Id: Ieb32596216ac848e9661c0785427bfe96fb958c6
diff --git a/apps/castor/src/main/java/org/onosproject/castor/ConnectivityManager.java b/apps/castor/src/main/java/org/onosproject/castor/ConnectivityManager.java
new file mode 100644
index 0000000..bd55d0d
--- /dev/null
+++ b/apps/castor/src/main/java/org/onosproject/castor/ConnectivityManager.java
@@ -0,0 +1,478 @@
+/*
+ * 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.castor;
+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.IPv6;
+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.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+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.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Manages the connectivity requirements between peers.
+ */
+@Component(immediate = true, enabled = true)
+@Service
+public class ConnectivityManager implements ConnectivityManagerService {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected IntentSynchronizationService intentSynchronizer;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CastorStore castorStore;
+
+ private static final int PRIORITY_OFFSET = 1000;
+ private static final int FLOW_PRIORITY = 500;
+ private static final String SUFFIX_DST = "dst";
+ private static final String SUFFIX_SRC = "src";
+ private static final String SUFFIX_ICMP = "icmp";
+
+ private static final Logger log = LoggerFactory.getLogger(ConnectivityManager.class);
+
+ private static final short BGP_PORT = 179;
+
+ private ApplicationId appId;
+
+ @Activate
+ public void activate() {
+ appId = coreService.getAppId(Castor.CASTOR_APP);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ }
+
+ /**
+ * Inputs the Route Servers.
+ */
+ @Override
+ public void start(Peer server) {
+ //routeServers.add(server);
+ //allPeers.add(server);
+ castorStore.storePeer(server);
+ castorStore.storeServer(server);
+ }
+
+ /**
+ * Stops the peer connectivity manager.
+ */
+ public void stop() {};
+
+ /**
+ * Sets up paths to establish connectivity between all internal.
+ * BGP speakers and external BGP peers.
+ */
+ @Override
+ public void setUpConnectivity(Peer peer) {
+
+ if (!castorStore.getCustomers().contains(peer)) {
+ castorStore.storePeer(peer);
+ castorStore.storeCustomer(peer);
+ }
+
+ for (Peer routeServer : castorStore.getServers()) {
+ log.debug("Start to set up BGP paths for BGP peer and Route Server"
+ + peer + "&" + routeServer);
+
+ buildSpeakerIntents(routeServer, peer).forEach(i -> {
+ castorStore.storePeerIntent(i.key(), i);
+ intentSynchronizer.submit(i);
+ });
+ }
+ }
+
+ private Collection<PointToPointIntent> buildSpeakerIntents(Peer speaker, Peer peer) {
+ List<PointToPointIntent> intents = new ArrayList<>();
+
+ IpAddress peerAddress = IpAddress.valueOf(peer.getIpAddress());
+ IpAddress speakerAddress = IpAddress.valueOf(speaker.getIpAddress());
+
+ checkNotNull(peerAddress);
+
+ intents.addAll(buildIntents(ConnectPoint.deviceConnectPoint(speaker.getPort()), speakerAddress,
+ ConnectPoint.deviceConnectPoint(peer.getPort()), peerAddress));
+
+ return intents;
+ }
+
+ /**
+ * Builds the required intents between the two pairs of connect points and
+ * IP addresses.
+ *
+ * @param portOne the first connect point
+ * @param ipOne the first IP address
+ * @param portTwo the second connect point
+ * @param ipTwo the second IP address
+ * @return the intents to install
+ */
+ private Collection<PointToPointIntent> buildIntents(ConnectPoint portOne,
+ IpAddress ipOne,
+ ConnectPoint portTwo,
+ IpAddress ipTwo) {
+
+ List<PointToPointIntent> intents = new ArrayList<>();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+ TrafficSelector selector;
+ Key key;
+
+ byte tcpProtocol;
+ byte icmpProtocol;
+
+ if (ipOne.isIp4()) {
+ tcpProtocol = IPv4.PROTOCOL_TCP;
+ icmpProtocol = IPv4.PROTOCOL_ICMP;
+ } else {
+ tcpProtocol = IPv6.PROTOCOL_TCP;
+ icmpProtocol = IPv6.PROTOCOL_ICMP6;
+ }
+
+ // Path from BGP speaker to BGP peer matching source TCP port 179
+ selector = buildSelector(tcpProtocol,
+ ipOne,
+ ipTwo,
+ BGP_PORT,
+ null);
+
+ key = buildKey(ipOne, ipTwo, SUFFIX_SRC);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portOne)
+ .egressPoint(portTwo)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // Path from BGP peer to BGP speaker matching destination TCP port 179
+ selector = buildSelector(tcpProtocol,
+ ipTwo,
+ ipOne,
+ null,
+ BGP_PORT);
+
+ key = buildKey(ipTwo, ipOne, SUFFIX_DST);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portTwo)
+ .egressPoint(portOne)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // ICMP path from BGP speaker to BGP peer
+ selector = buildSelector(icmpProtocol,
+ ipOne,
+ ipTwo,
+ null,
+ null);
+
+ key = buildKey(ipOne, ipTwo, SUFFIX_ICMP);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portOne)
+ .egressPoint(portTwo)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ // ICMP path from BGP peer to BGP speaker
+ selector = buildSelector(icmpProtocol,
+ ipTwo,
+ ipOne,
+ null,
+ null);
+
+ key = buildKey(ipTwo, ipOne, SUFFIX_ICMP);
+
+ intents.add(PointToPointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector)
+ .treatment(treatment)
+ .ingressPoint(portTwo)
+ .egressPoint(portOne)
+ .priority(PRIORITY_OFFSET)
+ .build());
+
+ return intents;
+ }
+
+ /**
+ * Builds a traffic selector based on the set of input parameters.
+ *
+ * @param ipProto IP protocol
+ * @param srcIp source IP address
+ * @param dstIp destination IP address
+ * @param srcTcpPort source TCP port, or null if shouldn't be set
+ * @param dstTcpPort destination TCP port, or null if shouldn't be set
+ * @return the new traffic selector
+ */
+ private TrafficSelector buildSelector(byte ipProto, IpAddress srcIp,
+ IpAddress dstIp, Short srcTcpPort,
+ Short dstTcpPort) {
+ TrafficSelector.Builder builder = DefaultTrafficSelector.builder().matchIPProtocol(ipProto);
+
+ if (dstIp.isIp4()) {
+ builder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET_MASK_LENGTH))
+ .matchIPDst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET_MASK_LENGTH));
+ } else {
+ builder.matchEthType(Ethernet.TYPE_IPV6)
+ .matchIPv6Src(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET6_MASK_LENGTH))
+ .matchIPv6Dst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET6_MASK_LENGTH));
+ }
+
+ if (srcTcpPort != null) {
+ builder.matchTcpSrc(TpPort.tpPort(srcTcpPort));
+ }
+
+ if (dstTcpPort != null) {
+ builder.matchTcpDst(TpPort.tpPort(dstTcpPort));
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Builds an intent Key for a point-to-point intent based off the source
+ * and destination IP address, as well as a suffix String to distinguish
+ * between different types of intents between the same source and
+ * destination.
+ *
+ * @param srcIp source IP address
+ * @param dstIp destination IP address
+ * @param suffix suffix string
+ * @return intent key
+ */
+ private Key buildKey(IpAddress srcIp, IpAddress dstIp, String suffix) {
+ String keyString = new StringBuilder()
+ .append(srcIp.toString())
+ .append("-")
+ .append(dstIp.toString())
+ .append("-")
+ .append(suffix)
+ .toString();
+
+ return Key.of(keyString, appId);
+ }
+
+ @Override
+ public void setUpL2(Peer peer) {
+
+ // First update all the previous existing intents. Update ingress points.
+
+ if (!castorStore.getLayer2Intents().isEmpty()) {
+ updateExistingL2Intents(peer);
+ }
+
+ Set<ConnectPoint> ingressPorts = new HashSet<>();
+ ConnectPoint egressPort = ConnectPoint.deviceConnectPoint(peer.getPort());
+
+ for (Peer inPeer : castorStore.getAllPeers()) {
+ if (!inPeer.getName().equals(peer.getName())) {
+ ingressPorts.add(ConnectPoint.deviceConnectPoint(inPeer.getPort()));
+ }
+ }
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ MacAddress macAddress = castorStore.getAddressMap().get(IpAddress.valueOf(peer.getIpAddress()));
+ selector.matchEthDst(macAddress);
+ TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+ Key key = Key.of(peer.getIpAddress(), appId);
+
+ MultiPointToSinglePointIntent intent = MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector.build())
+ .treatment(treatment)
+ .ingressPoints(ingressPorts)
+ .egressPoint(egressPort)
+ .priority(FLOW_PRIORITY)
+ .build();
+ intentSynchronizer.submit(intent);
+ castorStore.storeLayer2Intent(peer.getIpAddress(), intent);
+ castorStore.removeCustomer(peer);
+ peer.setL2(true);
+ castorStore.storeCustomer(peer);
+ }
+
+ /**
+ * Updates the existing layer 2 flows. Whenever a new Peer is added, it is also
+ * added as the ingress point to the existing layer two flows.
+ *
+ * @param peer The Peer being added.
+ */
+ private void updateExistingL2Intents(Peer peer) {
+
+ Collection<MultiPointToSinglePointIntent> oldIntents = castorStore.getLayer2Intents().values();
+
+ for (MultiPointToSinglePointIntent oldIntent : oldIntents) {
+
+ Set<ConnectPoint> ingressPoints = oldIntent.ingressPoints();
+ ConnectPoint egressPoint = oldIntent.egressPoint();
+ if (ingressPoints.add(ConnectPoint.deviceConnectPoint(peer.getPort()))) {
+
+ MultiPointToSinglePointIntent updatedMp2pIntent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(oldIntent.key())
+ .selector(oldIntent.selector())
+ .treatment(oldIntent.treatment())
+ .ingressPoints(ingressPoints)
+ .egressPoint(egressPoint)
+ .priority(oldIntent.priority())
+ .build();
+
+ //layer2Intents.put(peer.getIpAddress(), updatedMp2pIntent);
+ castorStore.storeLayer2Intent(peer.getIpAddress(), updatedMp2pIntent);
+ intentSynchronizer.submit(updatedMp2pIntent);
+ }
+ }
+ }
+
+ @Override
+ public void deletePeer(Peer peer) {
+
+ if (castorStore.getCustomers().contains(peer)) {
+
+ deletel3(peer);
+
+ for (Peer customer : castorStore.getCustomers()) {
+ if (customer.getIpAddress().equals(peer.getIpAddress()) && customer.getl2Status()) {
+ deleteL2(customer);
+ updateL2AfterDeletion(customer);
+ }
+ }
+ castorStore.removeCustomer(peer);
+ }
+ }
+
+ /**
+ * Delete all the flows between the Peer being deleted and the Route Servers
+ * to kill the BGP sessions. It uses the keys to retrive the previous intents
+ * and withdraw them.
+ *
+ * @param peer The Peer being deleted.
+ */
+ private void deletel3(Peer peer) {
+
+ List<Key> keys = new LinkedList<>();
+ IpAddress ipTwo = IpAddress.valueOf(peer.getIpAddress());
+
+ for (Peer server : castorStore.getServers()) {
+ IpAddress ipOne = IpAddress.valueOf(server.getIpAddress());
+ keys.add(buildKey(ipOne, ipTwo, SUFFIX_SRC));
+ keys.add(buildKey(ipTwo, ipOne, SUFFIX_DST));
+ keys.add(buildKey(ipOne, ipTwo, SUFFIX_ICMP));
+ keys.add(buildKey(ipTwo, ipOne, SUFFIX_ICMP));
+ }
+ for (Key keyDel : keys) {
+
+ PointToPointIntent intent = castorStore.getPeerIntents().get(keyDel);
+ intentSynchronizer.withdraw(intent);
+ castorStore.removePeerIntent(keyDel);
+ }
+ }
+
+ /**
+ * Deletes the layer two flows for the peer being deleted.
+ *
+ * @param peer The Peer being deleted.
+ */
+ private void deleteL2(Peer peer) {
+ intentSynchronizer.withdraw(castorStore.getLayer2Intents().get(peer.getIpAddress()));
+ castorStore.removeLayer2Intent(peer.getIpAddress());
+ }
+
+ /**
+ * Updates all the layer 2 flows after successful deletion of a Peer.
+ * The Peer being deleted is removed from the ingress points of all
+ * other flows.
+ *
+ * @param peer The Peer being deleted.
+ */
+ private void updateL2AfterDeletion(Peer peer) {
+ Collection<MultiPointToSinglePointIntent> oldIntents = castorStore.getLayer2Intents().values();
+ Map<String, MultiPointToSinglePointIntent> intents = new HashMap<>();
+
+ for (MultiPointToSinglePointIntent oldIntent : oldIntents) {
+
+ Set<ConnectPoint> ingressPoints = oldIntent.ingressPoints();
+ ConnectPoint egressPoint = oldIntent.egressPoint();
+ if (ingressPoints.remove(ConnectPoint.deviceConnectPoint(peer.getPort()))) {
+
+ MultiPointToSinglePointIntent updatedMp2pIntent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(oldIntent.key())
+ .selector(oldIntent.selector())
+ .treatment(oldIntent.treatment())
+ .ingressPoints(ingressPoints)
+ .egressPoint(egressPoint)
+ .priority(oldIntent.priority())
+ .build();
+
+ intents.put(peer.getIpAddress(), updatedMp2pIntent);
+ intentSynchronizer.submit(updatedMp2pIntent);
+ }
+ }
+ for (String key : intents.keySet()) {
+ castorStore.storeLayer2Intent(key, intents.get(key));
+ }
+ }
+}