ONOS-5182 Simplified OpenStack networking application structure
Change-Id: Ic7941f2c9a2febec4f24745278c4c305a3937097
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFloatingIpManager.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFloatingIpManager.java
new file mode 100644
index 0000000..602b0f7
--- /dev/null
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFloatingIpManager.java
@@ -0,0 +1,404 @@
+/*
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+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.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.host.HostService;
+import org.onosproject.openstackinterface.OpenstackFloatingIP;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackFloatingIpService;
+import org.onosproject.openstacknode.OpenstackNode;
+import org.onosproject.openstacknode.OpenstackNodeEvent;
+import org.onosproject.openstacknode.OpenstackNodeListener;
+import org.onosproject.openstacknode.OpenstackNodeService;
+import org.onosproject.scalablegateway.api.ScalableGatewayService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+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.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.*;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+
+
+@Service
+@Component(immediate = true)
+public class OpenstackFloatingIpManager extends AbstractVmHandler implements OpenstackFloatingIpService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowObjectiveService flowObjectiveService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService nodeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ScalableGatewayService gatewayService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackInterfaceService openstackService;
+
+ private static final String NOT_ASSOCIATED = "null";
+ private static final KryoNamespace.Builder FLOATING_IP_SERIALIZER =
+ KryoNamespace.newBuilder().register(KryoNamespaces.API);
+
+ private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+ private final InternalNodeListener nodeListener = new InternalNodeListener();
+ private ConsistentMap<IpAddress, Host> floatingIpMap;
+
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate() {
+ super.activate();
+ appId = coreService.registerApplication(ROUTING_APP_ID);
+ nodeService.addListener(nodeListener);
+ floatingIpMap = storageService.<IpAddress, Host>consistentMapBuilder()
+ .withSerializer(Serializer.using(FLOATING_IP_SERIALIZER.build()))
+ .withName("openstackrouting-floatingip")
+ .withApplicationId(appId)
+ .build();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ nodeService.removeListener(nodeListener);
+ log.info("Stopped");
+ }
+
+ @Override
+ protected void hostDetected(Host host) {
+ IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
+ Optional<OpenstackFloatingIP> floatingIp = openstackService.floatingIps().stream()
+ .filter(fip -> fip.fixedIpAddress() != null && fip.fixedIpAddress().equals(hostIp))
+ .findFirst();
+ if (floatingIp.isPresent()) {
+ eventExecutor.execute(() -> associateFloatingIp(floatingIp.get()));
+ }
+ }
+
+ @Override
+ protected void hostRemoved(Host host) {
+ IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
+ Optional<OpenstackFloatingIP> floatingIp = openstackService.floatingIps().stream()
+ .filter(fip -> fip.fixedIpAddress() != null && fip.fixedIpAddress().equals(hostIp))
+ .findFirst();
+ if (floatingIp.isPresent()) {
+ eventExecutor.execute(() -> disassociateFloatingIp(floatingIp.get()));
+ }
+ }
+
+ @Override
+ public void reinstallVmFlow(Host host) {
+ if (host == null) {
+ hostService.getHosts().forEach(h -> {
+ hostDetected(h);
+ log.info("Re-Install data plane flow of virtual machine {}", h);
+ });
+ } else {
+ hostDetected(host);
+ log.info("Re-Install data plane flow of virtual machine {}", host);
+ }
+ }
+
+ @Override
+ public void purgeVmFlow(Host host) {
+ if (host == null) {
+ hostService.getHosts().forEach(h -> {
+ hostRemoved(h);
+ log.info("Purge data plane flow of virtual machine {}", h);
+ });
+ } else {
+ hostRemoved(host);
+ log.info("Purge data plane flow of virtual machine {}", host);
+ }
+ }
+
+ @Override
+ public void createFloatingIp(OpenstackFloatingIP floatingIp) {
+ }
+
+ @Override
+ public void updateFloatingIp(OpenstackFloatingIP floatingIp) {
+ if (Strings.isNullOrEmpty(floatingIp.portId()) ||
+ floatingIp.portId().equals(NOT_ASSOCIATED)) {
+ eventExecutor.execute(() -> disassociateFloatingIp(floatingIp));
+ } else {
+ eventExecutor.execute(() -> associateFloatingIp(floatingIp));
+ }
+ }
+
+ @Override
+ public void deleteFloatingIp(String floatingIpId) {
+ }
+
+ private void associateFloatingIp(OpenstackFloatingIP floatingIp) {
+ Optional<Host> associatedVm = Tools.stream(hostService.getHosts())
+ .filter(host -> Objects.equals(
+ host.annotations().value(PORT_ID),
+ floatingIp.portId()))
+ .findAny();
+ if (!associatedVm.isPresent()) {
+ log.warn("Failed to associate floating IP({}) to port:{}",
+ floatingIp.floatingIpAddress(),
+ floatingIp.portId());
+ return;
+ }
+
+ floatingIpMap.put(floatingIp.floatingIpAddress(), associatedVm.get());
+ populateFloatingIpRules(floatingIp.floatingIpAddress(), associatedVm.get());
+
+ log.info("Associated floating IP {} to fixed IP {}",
+ floatingIp.floatingIpAddress(), floatingIp.fixedIpAddress());
+ }
+
+ private void disassociateFloatingIp(OpenstackFloatingIP floatingIp) {
+ Versioned<Host> associatedVm = floatingIpMap.remove(floatingIp.floatingIpAddress());
+ if (associatedVm == null) {
+ log.warn("Failed to disassociate floating IP({})",
+ floatingIp.floatingIpAddress());
+ // No VM is actually associated with the floating IP, do nothing
+ return;
+ }
+
+ removeFloatingIpRules(floatingIp.floatingIpAddress(), associatedVm.value());
+ log.info("Disassociated floating IP {} from fixed IP {}",
+ floatingIp.floatingIpAddress(),
+ associatedVm.value().ipAddresses());
+ }
+
+ private void populateFloatingIpRules(IpAddress floatingIp, Host associatedVm) {
+ populateFloatingIpIncomingRules(floatingIp, associatedVm);
+ populateFloatingIpOutgoingRules(floatingIp, associatedVm);
+ }
+
+ private void removeFloatingIpRules(IpAddress floatingIp, Host associatedVm) {
+ Optional<IpAddress> fixedIp = associatedVm.ipAddresses().stream().findFirst();
+ if (!fixedIp.isPresent()) {
+ log.warn("Failed to remove floating IP({}) from {}",
+ floatingIp, associatedVm);
+ return;
+ }
+
+ TrafficSelector.Builder sOutgoingBuilder = DefaultTrafficSelector.builder();
+ TrafficSelector.Builder sIncomingBuilder = DefaultTrafficSelector.builder();
+
+ sOutgoingBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+ .matchIPSrc(fixedIp.get().toIpPrefix());
+
+ sIncomingBuilder.matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(floatingIp.toIpPrefix());
+
+ gatewayService.getGatewayDeviceIds().forEach(deviceId -> {
+ TrafficSelector.Builder sForTrafficFromVmBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(floatingIp.toIpPrefix())
+ .matchInPort(nodeService.tunnelPort(deviceId).get());
+
+ RulePopulatorUtil.setRule(
+ flowObjectiveService,
+ appId,
+ deviceId,
+ sOutgoingBuilder.build(),
+ DefaultTrafficTreatment.builder().build(),
+ ForwardingObjective.Flag.VERSATILE,
+ FLOATING_RULE_PRIORITY, false);
+
+ RulePopulatorUtil.setRule(
+ flowObjectiveService,
+ appId,
+ deviceId,
+ sIncomingBuilder.build(),
+ DefaultTrafficTreatment.builder().build(),
+ ForwardingObjective.Flag.VERSATILE,
+ FLOATING_RULE_PRIORITY, false);
+
+ RulePopulatorUtil.setRule(
+ flowObjectiveService,
+ appId,
+ deviceId,
+ sForTrafficFromVmBuilder.build(),
+ DefaultTrafficTreatment.builder().build(),
+ ForwardingObjective.Flag.VERSATILE,
+ FLOATING_RULE_FOR_TRAFFIC_FROM_VM_PRIORITY, false);
+ });
+ }
+
+ private void populateFloatingIpIncomingRules(IpAddress floatingIp, Host associatedVm) {
+ DeviceId cnodeId = associatedVm.location().deviceId();
+ Optional<IpAddress> dataIp = nodeService.dataIp(cnodeId);
+ Optional<IpAddress> fixedIp = associatedVm.ipAddresses().stream().findFirst();
+
+ if (!fixedIp.isPresent() || !dataIp.isPresent()) {
+ log.warn("Failed to associate floating IP({})", floatingIp);
+ return;
+ }
+
+ TrafficSelector selectorForTrafficFromExternal = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(floatingIp.toIpPrefix())
+ .build();
+
+ gatewayService.getGatewayDeviceIds().forEach(gnodeId -> {
+ TrafficTreatment treatmentForTrafficFromExternal = DefaultTrafficTreatment.builder()
+ .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+ .setEthDst(associatedVm.mac())
+ .setIpDst(associatedVm.ipAddresses().stream().findFirst().get())
+ .setTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+ .extension(buildExtension(deviceService, gnodeId, dataIp.get().getIp4Address()),
+ gnodeId)
+ .setOutput(nodeService.tunnelPort(gnodeId).get())
+ .build();
+
+ ForwardingObjective forwardingObjectiveForTrafficFromExternal = DefaultForwardingObjective.builder()
+ .withSelector(selectorForTrafficFromExternal)
+ .withTreatment(treatmentForTrafficFromExternal)
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(FLOATING_RULE_PRIORITY)
+ .fromApp(appId)
+ .add();
+
+ flowObjectiveService.forward(gnodeId, forwardingObjectiveForTrafficFromExternal);
+
+
+ TrafficSelector selectorForTrafficFromVm = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(floatingIp.toIpPrefix())
+ .matchInPort(nodeService.tunnelPort(gnodeId).get())
+ .build();
+
+ TrafficTreatment treatmentForTrafficFromVm = DefaultTrafficTreatment.builder()
+ .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+ .setEthDst(associatedVm.mac())
+ .setIpDst(associatedVm.ipAddresses().stream().findFirst().get())
+ .setTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+ .extension(buildExtension(deviceService, gnodeId, dataIp.get().getIp4Address()),
+ gnodeId)
+ .setOutput(PortNumber.IN_PORT)
+ .build();
+
+ ForwardingObjective forwardingObjectiveForTrafficFromVm = DefaultForwardingObjective.builder()
+ .withSelector(selectorForTrafficFromVm)
+ .withTreatment(treatmentForTrafficFromVm)
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(FLOATING_RULE_FOR_TRAFFIC_FROM_VM_PRIORITY)
+ .fromApp(appId)
+ .add();
+
+ flowObjectiveService.forward(gnodeId, forwardingObjectiveForTrafficFromVm);
+
+ });
+ }
+
+ private void populateFloatingIpOutgoingRules(IpAddress floatingIp, Host associatedVm) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchTunnelId(Long.valueOf(associatedVm.annotations().value(VXLAN_ID)))
+ .matchIPSrc(associatedVm.ipAddresses().stream().findFirst().get().toIpPrefix())
+ .build();
+
+ gatewayService.getGatewayDeviceIds().forEach(gnodeId -> {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setIpSrc(floatingIp)
+ .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+ .setEthDst(Constants.DEFAULT_EXTERNAL_ROUTER_MAC)
+ .setOutput(gatewayService.getUplinkPort(gnodeId))
+ .build();
+
+ ForwardingObjective fo = DefaultForwardingObjective.builder()
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withPriority(FLOATING_RULE_PRIORITY)
+ .fromApp(appId)
+ .add();
+
+ flowObjectiveService.forward(gnodeId, fo);
+ });
+ }
+
+ // TODO consider the case that port with associated floating IP is attached to a VM
+
+ private class InternalNodeListener implements OpenstackNodeListener {
+
+ @Override
+ public void event(OpenstackNodeEvent event) {
+ OpenstackNode node = event.node();
+
+ switch (event.type()) {
+ case COMPLETE:
+ reinstallVmFlow(null);
+ break;
+ case INIT:
+ case DEVICE_CREATED:
+ case INCOMPLETE:
+ default:
+ break;
+ }
+ }
+ }
+}