/* * 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.segmentrouting; 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.MacAddress; import org.onlab.packet.VlanId; import org.onlab.packet.IPv4; import org.onlab.packet.Ip4Address; import org.onlab.packet.Ip4Prefix; import org.onlab.packet.IpAddress; import org.onlab.packet.IpPrefix; import org.onlab.util.KryoNamespace; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; import org.onosproject.event.Event; import org.onosproject.net.ConnectPoint; import org.onosproject.net.PortNumber; import org.onosproject.net.config.ConfigFactory; import org.onosproject.net.config.NetworkConfigEvent; import org.onosproject.net.config.NetworkConfigRegistry; import org.onosproject.net.config.NetworkConfigListener; import org.onosproject.net.config.basics.SubjectFactories; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flowobjective.DefaultForwardingObjective; import org.onosproject.net.flowobjective.ForwardingObjective; import org.onosproject.net.flowobjective.Objective; import org.onosproject.net.flowobjective.ObjectiveContext; import org.onosproject.net.flowobjective.ObjectiveError; import org.onosproject.net.host.HostEvent; import org.onosproject.net.host.HostListener; import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException; import org.onosproject.segmentrouting.config.DeviceConfiguration; import org.onosproject.segmentrouting.config.SegmentRoutingConfig; import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler; import org.onosproject.segmentrouting.grouphandler.NeighborSet; import org.onosproject.segmentrouting.grouphandler.NeighborSetNextObjectiveStoreKey; import org.onosproject.mastership.MastershipService; import org.onosproject.net.Device; import org.onosproject.net.DeviceId; import org.onosproject.net.Link; import org.onosproject.net.Port; import org.onosproject.net.device.DeviceEvent; import org.onosproject.net.device.DeviceListener; import org.onosproject.net.device.DeviceService; import org.onosproject.net.flowobjective.FlowObjectiveService; import org.onosproject.net.host.HostService; import org.onosproject.net.intent.IntentService; import org.onosproject.net.link.LinkEvent; import org.onosproject.net.link.LinkListener; import org.onosproject.net.link.LinkService; import org.onosproject.net.packet.InboundPacket; import org.onosproject.net.packet.PacketContext; import org.onosproject.net.packet.PacketProcessor; import org.onosproject.net.packet.PacketService; import org.onosproject.net.topology.TopologyService; import org.onosproject.segmentrouting.grouphandler.SubnetNextObjectiveStoreKey; import org.onosproject.store.service.EventuallyConsistentMap; import org.onosproject.store.service.EventuallyConsistentMapBuilder; import org.onosproject.store.service.StorageService; import org.onosproject.store.service.WallClockTimestamp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @SuppressWarnings("ALL") @Service @Component(immediate = true) public class SegmentRoutingManager implements SegmentRoutingService { private static Logger log = LoggerFactory .getLogger(SegmentRoutingManager.class); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CoreService coreService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected TopologyService topologyService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected PacketService packetService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentService intentService; @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 LinkService linkService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected MastershipService mastershipService; protected ArpHandler arpHandler = null; protected IcmpHandler icmpHandler = null; protected IpHandler ipHandler = null; protected RoutingRulePopulator routingRulePopulator = null; protected ApplicationId appId; protected DeviceConfiguration deviceConfiguration = null; private DefaultRoutingHandler defaultRoutingHandler = null; private TunnelHandler tunnelHandler = null; private PolicyHandler policyHandler = null; private InternalPacketProcessor processor = null; private InternalLinkListener linkListener = null; private InternalDeviceListener deviceListener = null; private InternalEventHandler eventHandler = new InternalEventHandler(); private ScheduledExecutorService executorService = Executors .newScheduledThreadPool(1); private static ScheduledFuture eventHandlerFuture = null; private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue(); private Map groupHandlerMap = new ConcurrentHashMap(); // Per device next objective ID store with (device id + neighbor set) as key private EventuallyConsistentMap nsNextObjStore = null; private EventuallyConsistentMap subnetNextObjStore = null; private EventuallyConsistentMap tunnelStore = null; private EventuallyConsistentMap policyStore = null; // Per device, per-subnet assigned-vlans store, with (device id + subnet // IPv4 prefix) as key private EventuallyConsistentMap subnetVidStore = null; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected StorageService storageService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected NetworkConfigRegistry cfgService; private final InternalConfigListener cfgListener = new InternalConfigListener(this); private final ConfigFactory cfgFactory = new ConfigFactory(SubjectFactories.DEVICE_SUBJECT_FACTORY, SegmentRoutingConfig.class, "segmentrouting") { @Override public SegmentRoutingConfig createConfig() { return new SegmentRoutingConfig(); } }; private final HostListener hostListener = new InternalHostListener(); private Object threadSchedulerLock = new Object(); private static int numOfEventsQueued = 0; private static int numOfEventsExecuted = 0; private static int numOfHandlerExecution = 0; private static int numOfHandlerScheduled = 0; private KryoNamespace.Builder kryoBuilder = null; private static final short ASSIGNED_VLAN_START = 4093; public static final short ASSIGNED_VLAN_NO_SUBNET = 4094; @Activate protected void activate() { appId = coreService .registerApplication("org.onosproject.segmentrouting"); kryoBuilder = new KryoNamespace.Builder() .register(NeighborSetNextObjectiveStoreKey.class, SubnetNextObjectiveStoreKey.class, SubnetAssignedVidStoreKey.class, NeighborSet.class, DeviceId.class, URI.class, WallClockTimestamp.class, org.onosproject.cluster.NodeId.class, HashSet.class, Tunnel.class, DefaultTunnel.class, Policy.class, TunnelPolicy.class, Policy.Type.class, VlanId.class, Ip4Address.class, Ip4Prefix.class, IpAddress.Version.class, ConnectPoint.class ); log.debug("Creating EC map nsnextobjectivestore"); EventuallyConsistentMapBuilder nsNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder(); nsNextObjStore = nsNextObjMapBuilder .withName("nsnextobjectivestore") .withSerializer(kryoBuilder) .withTimestampProvider((k, v) -> new WallClockTimestamp()) .build(); log.trace("Current size {}", nsNextObjStore.size()); log.debug("Creating EC map subnetnextobjectivestore"); EventuallyConsistentMapBuilder subnetNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder(); subnetNextObjStore = subnetNextObjMapBuilder .withName("subnetnextobjectivestore") .withSerializer(kryoBuilder) .withTimestampProvider((k, v) -> new WallClockTimestamp()) .build(); EventuallyConsistentMapBuilder tunnelMapBuilder = storageService.eventuallyConsistentMapBuilder(); tunnelStore = tunnelMapBuilder .withName("tunnelstore") .withSerializer(kryoBuilder) .withTimestampProvider((k, v) -> new WallClockTimestamp()) .build(); EventuallyConsistentMapBuilder policyMapBuilder = storageService.eventuallyConsistentMapBuilder(); policyStore = policyMapBuilder .withName("policystore") .withSerializer(kryoBuilder) .withTimestampProvider((k, v) -> new WallClockTimestamp()) .build(); EventuallyConsistentMapBuilder subnetVidStoreMapBuilder = storageService.eventuallyConsistentMapBuilder(); subnetVidStore = subnetVidStoreMapBuilder .withName("subnetvidstore") .withSerializer(kryoBuilder) .withTimestampProvider((k, v) -> new WallClockTimestamp()) .build(); cfgService.addListener(cfgListener); cfgService.registerConfigFactory(cfgFactory); hostService.addListener(hostListener); processor = new InternalPacketProcessor(); linkListener = new InternalLinkListener(); deviceListener = new InternalDeviceListener(); packetService.addProcessor(processor, PacketProcessor.director(2)); linkService.addListener(linkListener); deviceService.addListener(deviceListener); cfgListener.configureNetwork(); log.info("Started"); } @Deactivate protected void deactivate() { cfgService.removeListener(cfgListener); cfgService.unregisterConfigFactory(cfgFactory); packetService.removeProcessor(processor); linkService.removeListener(linkListener); deviceService.removeListener(deviceListener); processor = null; linkListener = null; deviceService = null; groupHandlerMap.clear(); log.info("Stopped"); } @Override public List getTunnels() { return tunnelHandler.getTunnels(); } @Override public TunnelHandler.Result createTunnel(Tunnel tunnel) { return tunnelHandler.createTunnel(tunnel); } @Override public TunnelHandler.Result removeTunnel(Tunnel tunnel) { for (Policy policy: policyHandler.getPolicies()) { if (policy.type() == Policy.Type.TUNNEL_FLOW) { TunnelPolicy tunnelPolicy = (TunnelPolicy) policy; if (tunnelPolicy.tunnelId().equals(tunnel.id())) { log.warn("Cannot remove the tunnel used by a policy"); return TunnelHandler.Result.TUNNEL_IN_USE; } } } return tunnelHandler.removeTunnel(tunnel); } @Override public PolicyHandler.Result removePolicy(Policy policy) { return policyHandler.removePolicy(policy); } @Override public PolicyHandler.Result createPolicy(Policy policy) { return policyHandler.createPolicy(policy); } @Override public List getPolicies() { return policyHandler.getPolicies(); } /** * Returns the tunnel object with the tunnel ID. * * @param tunnelId Tunnel ID * @return Tunnel reference */ public Tunnel getTunnel(String tunnelId) { return tunnelHandler.getTunnel(tunnelId); } /** * Returns the vlan-id assigned to the subnet configured for a device. * If no vlan-id has been assigned, a new one is assigned out of a pool of ids, * if and only if this controller instance is the master for the device. *

* USAGE: The assigned vlans are meant to be applied to untagged packets on those * switches/pipelines that need this functionality. These vids are meant * to be used internally within a switch, and thus need to be unique only * on a switch level. Note that packets never go out on the wire with these * vlans. Currently, vlan ids are assigned from value 4093 down. * Vlan id 4094 expected to be used for all ports that are not assigned subnets. * Vlan id 4095 is reserved and unused. Only a single vlan id is assigned * per subnet. * XXX This method should avoid any vlans configured on the ports, but * currently the app works only on untagged packets and as a result * ignores any vlan configuration. * * @param deviceId switch dpid * @param subnet IPv4 prefix for which assigned vlan is desired * @return VlanId assigned for the subnet on the device, or * null if no vlan assignment was found and this instance is not * the master for the device. */ public VlanId getSubnetAssignedVlanId(DeviceId deviceId, Ip4Prefix subnet) { VlanId assignedVid = subnetVidStore.get(new SubnetAssignedVidStoreKey( deviceId, subnet)); if (assignedVid != null) { log.debug("Query for subnet:{} on device:{} returned assigned-vlan " + "{}", subnet, deviceId, assignedVid); return assignedVid; } //check mastership for the right to assign a vlan if (!mastershipService.isLocalMaster(deviceId)) { log.warn("This controller instance is not the master for device {}. " + "Cannot assign vlan-id for subnet {}", deviceId, subnet); return null; } // vlan assignment is expensive but done only once Set configuredSubnets = deviceConfiguration.getSubnets(deviceId); Set assignedVlans = new HashSet<>(); Set unassignedSubnets = new HashSet<>(); for (Ip4Prefix sub : configuredSubnets) { VlanId v = subnetVidStore.get(new SubnetAssignedVidStoreKey(deviceId, sub)); if (v != null) { assignedVlans.add(v.toShort()); } else { unassignedSubnets.add(sub); } } short nextAssignedVlan = ASSIGNED_VLAN_START; if (!assignedVlans.isEmpty()) { nextAssignedVlan = (short) (Collections.min(assignedVlans) - 1); } for (Ip4Prefix unsub : unassignedSubnets) { subnetVidStore.put(new SubnetAssignedVidStoreKey(deviceId, unsub), VlanId.vlanId(nextAssignedVlan--)); log.info("Assigned vlan: {} to subnet: {} on device: {}", nextAssignedVlan + 1, unsub, deviceId); } return subnetVidStore.get(new SubnetAssignedVidStoreKey(deviceId, subnet)); } /** * Returns the next objective ID for the given NeighborSet. * If the nextObjective does not exist, a new one is created and * it's id is returned. * TODO move the side-effect creation of a Next Objective into a new method * * @param deviceId Device ID * @param ns NegighborSet * @param meta metadata passed into the creation of a Next Objective * @return next objective ID or -1 if an error was encountered during the * creation of the nextObjective */ public int getNextObjectiveId(DeviceId deviceId, NeighborSet ns, TrafficSelector meta) { if (groupHandlerMap.get(deviceId) != null) { log.trace("getNextObjectiveId query in device {}", deviceId); return groupHandlerMap .get(deviceId).getNextObjectiveId(ns, meta); } else { log.warn("getNextObjectiveId query in device {} not found", deviceId); return -1; } } /** * Returns the next objective ID for the Subnet given. If the nextObjectiveID does not exist, * a new one is created and returned. * * @param deviceId Device ID * @param prefix Subnet * @return next objective ID */ public int getSubnetNextObjectiveId(DeviceId deviceId, IpPrefix prefix) { if (groupHandlerMap.get(deviceId) != null) { log.trace("getSubnetNextObjectiveId query in device {}", deviceId); return groupHandlerMap .get(deviceId).getSubnetNextObjectiveId(prefix); } else { log.warn("getSubnetNextObjectiveId query in device {} not found", deviceId); return -1; } } private class InternalPacketProcessor implements PacketProcessor { @Override public void process(PacketContext context) { if (context.isHandled()) { return; } InboundPacket pkt = context.inPacket(); Ethernet ethernet = pkt.parsed(); if (ethernet.getEtherType() == Ethernet.TYPE_ARP) { arpHandler.processPacketIn(pkt); } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV4) { IPv4 ipPacket = (IPv4) ethernet.getPayload(); ipHandler.addToPacketBuffer(ipPacket); if (ipPacket.getProtocol() == IPv4.PROTOCOL_ICMP) { icmpHandler.processPacketIn(pkt); } else { ipHandler.processPacketIn(pkt); } } } } private class InternalLinkListener implements LinkListener { @Override public void event(LinkEvent event) { if (event.type() == LinkEvent.Type.LINK_ADDED || event.type() == LinkEvent.Type.LINK_REMOVED) { log.debug("Event {} received from Link Service", event.type()); scheduleEventHandlerIfNotScheduled(event); } } } private class InternalDeviceListener implements DeviceListener { @Override public void event(DeviceEvent event) { switch (event.type()) { case DEVICE_ADDED: case PORT_REMOVED: case DEVICE_UPDATED: case DEVICE_AVAILABILITY_CHANGED: log.debug("Event {} received from Device Service", event.type()); scheduleEventHandlerIfNotScheduled(event); break; default: } } } private void scheduleEventHandlerIfNotScheduled(Event event) { synchronized (threadSchedulerLock) { eventQueue.add(event); numOfEventsQueued++; if ((numOfHandlerScheduled - numOfHandlerExecution) == 0) { //No pending scheduled event handling threads. So start a new one. eventHandlerFuture = executorService .schedule(eventHandler, 100, TimeUnit.MILLISECONDS); numOfHandlerScheduled++; } log.trace("numOfEventsQueued {}, numOfEventHanlderScheduled {}", numOfEventsQueued, numOfHandlerScheduled); } } private class InternalEventHandler implements Runnable { @Override public void run() { try { while (true) { Event event = null; synchronized (threadSchedulerLock) { if (!eventQueue.isEmpty()) { event = eventQueue.poll(); numOfEventsExecuted++; } else { numOfHandlerExecution++; log.debug("numOfHandlerExecution {} numOfEventsExecuted {}", numOfHandlerExecution, numOfEventsExecuted); break; } } if (event.type() == LinkEvent.Type.LINK_ADDED) { processLinkAdded((Link) event.subject()); } else if (event.type() == LinkEvent.Type.LINK_REMOVED) { processLinkRemoved((Link) event.subject()); } else if (event.type() == DeviceEvent.Type.DEVICE_ADDED || event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED || event.type() == DeviceEvent.Type.DEVICE_UPDATED) { if (deviceService.isAvailable(((Device) event.subject()).id())) { log.info("Processing device event {} for available device {}", event.type(), ((Device) event.subject()).id()); processDeviceAdded((Device) event.subject()); } } else if (event.type() == DeviceEvent.Type.PORT_REMOVED) { processPortRemoved((Device) event.subject(), ((DeviceEvent) event).port()); } else { log.warn("Unhandled event type: {}", event.type()); } } } catch (Exception e) { log.error("SegmentRouting event handler " + "thread thrown an exception: {}", e); } } } private void processLinkAdded(Link link) { log.debug("A new link {} was added", link.toString()); if (!deviceConfiguration.isConfigured(link.src().deviceId())) { log.warn("Source device of this link is not configured."); return; } //Irrespective whether the local is a MASTER or not for this device, //create group handler instance and push default TTP flow rules. //Because in a multi-instance setup, instances can initiate //groups for any devices. Also the default TTP rules are needed //to be pushed before inserting any IP table entries for any device DefaultGroupHandler groupHandler = groupHandlerMap.get(link.src() .deviceId()); if (groupHandler != null) { groupHandler.linkUp(link, mastershipService.isLocalMaster( link.src().deviceId())); } else { Device device = deviceService.getDevice(link.src().deviceId()); if (device != null) { log.warn("processLinkAdded: Link Added " + "Notification without Device Added " + "event, still handling it"); processDeviceAdded(device); groupHandler = groupHandlerMap.get(link.src() .deviceId()); groupHandler.linkUp(link, mastershipService.isLocalMaster(device.id())); } } log.trace("Starting optimized route population process"); defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(null); //log.trace("processLinkAdded: re-starting route population process"); //defaultRoutingHandler.startPopulationProcess(); } private void processLinkRemoved(Link link) { log.debug("A link {} was removed", link.toString()); DefaultGroupHandler groupHandler = groupHandlerMap.get(link.src().deviceId()); if (groupHandler != null) { groupHandler.portDown(link.src().port()); } log.trace("Starting optimized route population process"); defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(link); //log.trace("processLinkRemoved: re-starting route population process"); //defaultRoutingHandler.startPopulationProcess(); } private void processDeviceAdded(Device device) { log.debug("A new device with ID {} was added", device.id()); if (deviceConfiguration == null || !deviceConfiguration.isConfigured(device.id())) { log.warn("Device configuration uploading. Device {} will be " + "processed after config completes.", device.id()); return; } // Irrespective of whether the local is a MASTER or not for this device, // we need to create a SR-group-handler instance. This is because in a // multi-instance setup, any instance can initiate forwarding/next-objectives // for any switch (even if this instance is a SLAVE or not even connected // to the switch). To handle this, a default-group-handler instance is necessary // per switch. if (groupHandlerMap.get(device.id()) == null) { DefaultGroupHandler groupHandler; try { groupHandler = DefaultGroupHandler. createGroupHandler(device.id(), appId, deviceConfiguration, linkService, flowObjectiveService, nsNextObjStore, subnetNextObjStore); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting processDeviceAdded."); return; } groupHandlerMap.put(device.id(), groupHandler); // Also, in some cases, drivers may need extra // information to process rules (eg. Router IP/MAC); and so, we send // port addressing rules to the driver as well irrespective of whether // this instance is the master or not. defaultRoutingHandler.populatePortAddressingRules(device.id()); } if (mastershipService.isLocalMaster(device.id())) { DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id()); groupHandler.createGroupsFromSubnetConfig(); routingRulePopulator.populateSubnetBroadcastRule(device.id()); } } private void processPortRemoved(Device device, Port port) { log.debug("Port {} was removed", port.toString()); DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id()); if (groupHandler != null) { groupHandler.portDown(port.number()); } } private class InternalConfigListener implements NetworkConfigListener { SegmentRoutingManager segmentRoutingManager; public InternalConfigListener(SegmentRoutingManager srMgr) { this.segmentRoutingManager = srMgr; } public void configureNetwork() { deviceConfiguration = new DeviceConfiguration(segmentRoutingManager.cfgService); arpHandler = new ArpHandler(segmentRoutingManager); icmpHandler = new IcmpHandler(segmentRoutingManager); ipHandler = new IpHandler(segmentRoutingManager); routingRulePopulator = new RoutingRulePopulator(segmentRoutingManager); defaultRoutingHandler = new DefaultRoutingHandler(segmentRoutingManager); tunnelHandler = new TunnelHandler(linkService, deviceConfiguration, groupHandlerMap, tunnelStore); policyHandler = new PolicyHandler(appId, deviceConfiguration, flowObjectiveService, tunnelHandler, policyStore); for (Device device : deviceService.getDevices()) { // Irrespective of whether the local is a MASTER or not for this device, // we need to create a SR-group-handler instance. This is because in a // multi-instance setup, any instance can initiate forwarding/next-objectives // for any switch (even if this instance is a SLAVE or not even connected // to the switch). To handle this, a default-group-handler instance is necessary // per switch. if (groupHandlerMap.get(device.id()) == null) { DefaultGroupHandler groupHandler; try { groupHandler = DefaultGroupHandler. createGroupHandler(device.id(), appId, deviceConfiguration, linkService, flowObjectiveService, nsNextObjStore, subnetNextObjStore); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting configureNetwork."); return; } groupHandlerMap.put(device.id(), groupHandler); // Also, in some cases, drivers may need extra // information to process rules (eg. Router IP/MAC); and so, we send // port addressing rules to the driver as well, irrespective of whether // this instance is the master or not. defaultRoutingHandler.populatePortAddressingRules(device.id()); } if (mastershipService.isLocalMaster(device.id())) { DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id()); groupHandler.createGroupsFromSubnetConfig(); routingRulePopulator.populateSubnetBroadcastRule(device.id()); } } defaultRoutingHandler.startPopulationProcess(); } @Override public void event(NetworkConfigEvent event) { if (event.configClass().equals(SegmentRoutingConfig.class)) { if (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED) { log.info("Network configuration added."); configureNetwork(); } if (event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) { log.info("Network configuration updated."); // TODO support dynamic configuration } } } } private class InternalHostListener implements HostListener { private ForwardingObjective.Builder getForwardingObjectiveBuilder( MacAddress mac, VlanId vlanId, PortNumber port) { TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); sbuilder.matchEthDst(mac); sbuilder.matchVlanId(vlanId); TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); // TODO Move popVlan from flow action to group action tbuilder.immediate().popVlan(); tbuilder.immediate().setOutput(port); return DefaultForwardingObjective.builder() .withFlag(ForwardingObjective.Flag.SPECIFIC) .withSelector(sbuilder.build()) .withTreatment(tbuilder.build()) .withPriority(100) .fromApp(appId) .makePermanent(); } private void processHostAddedEvent(HostEvent event) { MacAddress mac = event.subject().mac(); VlanId vlanId = event.subject().vlan(); DeviceId deviceId = event.subject().location().deviceId(); PortNumber port = event.subject().location().port(); Set ips = event.subject().ipAddresses(); log.debug("Host {}/{} is added at {}:{}", mac, vlanId, deviceId, port); // TODO Move bridging table population to a separate class // Populate bridging table entry ForwardingObjective.Builder fob = getForwardingObjectiveBuilder(mac, vlanId, port); flowObjectiveService.forward(deviceId, fob.add( new BridgingTableObjectiveContext(mac, vlanId) )); // Populate IP table entry ips.forEach(ip -> { if (ip.isIp4()) { routingRulePopulator.populateIpRuleForHost( deviceId, ip.getIp4Address(), mac, port); } }); } private void processHostRemoveEvent(HostEvent event) { MacAddress mac = event.subject().mac(); VlanId vlanId = event.subject().vlan(); DeviceId deviceId = event.subject().location().deviceId(); PortNumber port = event.subject().location().port(); Set ips = event.subject().ipAddresses(); log.debug("Host {}/{} is removed from {}:{}", mac, vlanId, deviceId, port); // Revoke bridging table entry ForwardingObjective.Builder fob = getForwardingObjectiveBuilder(mac, vlanId, port); flowObjectiveService.forward(deviceId, fob.remove( new BridgingTableObjectiveContext(mac, vlanId) )); // Revoke IP table entry ips.forEach(ip -> { if (ip.isIp4()) { routingRulePopulator.revokeIpRuleForHost( deviceId, ip.getIp4Address(), mac, port); } }); } private void processHostMovedEvent(HostEvent event) { MacAddress mac = event.subject().mac(); VlanId vlanId = event.subject().vlan(); DeviceId prevDeviceId = event.prevSubject().location().deviceId(); PortNumber prevPort = event.prevSubject().location().port(); Set prevIps = event.prevSubject().ipAddresses(); DeviceId newDeviceId = event.subject().location().deviceId(); PortNumber newPort = event.subject().location().port(); Set newIps = event.subject().ipAddresses(); log.debug("Host {}/{} is moved from {}:{} to {}:{}", mac, vlanId, prevDeviceId, prevPort, newDeviceId, newPort); // Revoke previous bridging table entry ForwardingObjective.Builder prevFob = getForwardingObjectiveBuilder(mac, vlanId, prevPort); flowObjectiveService.forward(prevDeviceId, prevFob.remove( new BridgingTableObjectiveContext(mac, vlanId) )); // Revoke previous IP table entry prevIps.forEach(ip -> { if (ip.isIp4()) { routingRulePopulator.revokeIpRuleForHost( prevDeviceId, ip.getIp4Address(), mac, prevPort); } }); // Populate new bridging table entry ForwardingObjective.Builder newFob = getForwardingObjectiveBuilder(mac, vlanId, prevPort); flowObjectiveService.forward(newDeviceId, newFob.add( new BridgingTableObjectiveContext(mac, vlanId) )); // Populate new IP table entry newIps.forEach(ip -> { if (ip.isIp4()) { routingRulePopulator.populateIpRuleForHost( newDeviceId, ip.getIp4Address(), mac, newPort); } }); } private void processHostUpdatedEvent(HostEvent event) { MacAddress mac = event.subject().mac(); VlanId vlanId = event.subject().vlan(); DeviceId prevDeviceId = event.prevSubject().location().deviceId(); PortNumber prevPort = event.prevSubject().location().port(); Set prevIps = event.prevSubject().ipAddresses(); DeviceId newDeviceId = event.subject().location().deviceId(); PortNumber newPort = event.subject().location().port(); Set newIps = event.subject().ipAddresses(); log.debug("Host {}/{} is updated", mac, vlanId); // Revoke previous IP table entry prevIps.forEach(ip -> { if (ip.isIp4()) { routingRulePopulator.revokeIpRuleForHost( prevDeviceId, ip.getIp4Address(), mac, prevPort); } }); // Populate new IP table entry newIps.forEach(ip -> { if (ip.isIp4()) { routingRulePopulator.populateIpRuleForHost( newDeviceId, ip.getIp4Address(), mac, newPort); } }); } @Override public void event(HostEvent event) { // Do not proceed without mastership DeviceId deviceId = event.subject().location().deviceId(); if (!mastershipService.isLocalMaster(deviceId)) { return; } switch (event.type()) { case HOST_ADDED: processHostAddedEvent(event); break; case HOST_MOVED: processHostMovedEvent(event); break; case HOST_REMOVED: processHostRemoveEvent(event); break; case HOST_UPDATED: processHostUpdatedEvent(event); break; default: log.warn("Unsupported host event type: {}", event.type()); break; } } } private static class BridgingTableObjectiveContext implements ObjectiveContext { final MacAddress mac; final VlanId vlanId; BridgingTableObjectiveContext(MacAddress mac, VlanId vlanId) { this.mac = mac; this.vlanId = vlanId; } @Override public void onSuccess(Objective objective) { if (objective.op() == Objective.Operation.ADD) { log.debug("Successfully populate bridging table entry for {}/{}", mac, vlanId); } else { log.debug("Successfully revoke bridging table entry for {}/{}", mac, vlanId); } } @Override public void onError(Objective objective, ObjectiveError error) { if (objective.op() == Objective.Operation.ADD) { log.debug("Fail to populate bridging table entry for {}/{}. {}", mac, vlanId, error); } else { log.debug("Fail to revoke bridging table entry for {}/{}. {}", mac, vlanId, error); } } } }