/* * Copyright 2017-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.routing; import com.google.common.collect.Sets; import org.onosproject.incubator.net.intf.Interface; import org.onosproject.incubator.net.intf.InterfaceEvent; import org.onosproject.incubator.net.intf.InterfaceListener; import org.onosproject.incubator.net.intf.InterfaceService; import org.onosproject.net.DeviceId; import org.onosproject.net.device.DeviceService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkNotNull; /** * Manages the configuration and provisioning of a single-device router. * It maintains which interfaces are part of the router when the configuration * changes, and handles the provisioning/unprovisioning of interfaces when they * are added/removed. */ public class Router { private final Logger log = LoggerFactory.getLogger(getClass()); private final Consumer<InterfaceProvisionRequest> provisioner; private final Consumer<InterfaceProvisionRequest> unprovisioner; private RouterInfo info; private Set<Interface> provisioned = new HashSet<>(); private InterfaceService interfaceService; private InterfaceListener listener = new InternalInterfaceListener(); private DeviceService deviceService; private AsyncDeviceFetcher asyncDeviceFetcher; /** * Creates a new router interface manager. * * @param info router configuration information * @param interfaceService interface service * @param deviceService device service * @param provisioner consumer that will provision new interfaces * @param unprovisioner consumer that will unprovision old interfaces * @param forceUnprovision force unprovision when the device goes offline */ public Router(RouterInfo info, InterfaceService interfaceService, DeviceService deviceService, Consumer<InterfaceProvisionRequest> provisioner, Consumer<InterfaceProvisionRequest> unprovisioner, boolean forceUnprovision) { this.info = checkNotNull(info); this.provisioner = checkNotNull(provisioner); this.unprovisioner = checkNotNull(unprovisioner); this.interfaceService = checkNotNull(interfaceService); this.deviceService = checkNotNull(deviceService); this.asyncDeviceFetcher = AsyncDeviceFetcher.create(deviceService); if (forceUnprovision) { asyncDeviceFetcher.registerCallback(info.deviceId(), this::provision, this::forceUnprovision); } else { asyncDeviceFetcher.registerCallback(info.deviceId(), this::provision, null); } interfaceService.addListener(listener); } /** * Cleans up the router and unprovisions all interfaces. */ public void cleanup() { asyncDeviceFetcher.shutdown(); interfaceService.removeListener(listener); unprovision(); } /** * Retrieves the router configuration information. * * @return router configuration information */ public RouterInfo info() { return info; } /** * Changes the router configuration. * * @param newConfig new configuration * @param forceUnprovision true if we want to force unprovision the device when it goes offline */ public void changeConfiguration(RouterInfo newConfig, boolean forceUnprovision) { if (forceUnprovision) { asyncDeviceFetcher.registerCallback(info.deviceId(), this::provision, this::forceUnprovision); } else { asyncDeviceFetcher.registerCallback(info.deviceId(), this::provision, null); } Set<String> oldConfiguredInterfaces = info.interfaces(); info = newConfig; Set<String> newConfiguredInterfaces = info.interfaces(); if (newConfiguredInterfaces.isEmpty() && !oldConfiguredInterfaces.isEmpty()) { // Reverted to using all interfaces. Provision interfaces that // weren't previously in the configured list getInterfacesForDevice(info.deviceId()) .filter(intf -> !oldConfiguredInterfaces.contains(intf.name())) .forEach(this::provision); } else if (!newConfiguredInterfaces.isEmpty() && oldConfiguredInterfaces.isEmpty()) { // Began using an interface list. Unprovision interfaces that // are not in the new interface list. getInterfacesForDevice(info.deviceId()) .filter(intf -> !newConfiguredInterfaces.contains(intf.name())) .forEach(this::unprovision); } else { // The existing interface list was changed. Set<String> toUnprovision = Sets.difference(oldConfiguredInterfaces, newConfiguredInterfaces); Set<String> toProvision = Sets.difference(newConfiguredInterfaces, oldConfiguredInterfaces); toUnprovision.forEach(name -> getInterfacesForDevice(info.deviceId()) .filter(intf -> intf.name().equals(name)) .findFirst() .ifPresent(this::unprovision) ); toProvision.forEach(name -> getInterfacesForDevice(info.deviceId()) .filter(intf -> intf.name().equals(name)) .findFirst() .ifPresent(this::provision) ); } } private void provision() { getInterfacesForDevice(info.deviceId()) .forEach(this::provision); } private void unprovision() { getInterfacesForDevice(info.deviceId()) .forEach(this::unprovision); } private void forceUnprovision() { getInterfacesForDevice(info.deviceId()) .forEach(this::forceUnprovision); } private void provision(Interface intf) { if (!provisioned.contains(intf) && deviceAvailable(intf) && shouldProvision(intf)) { log.info("Provisioning interface {}", intf); provisioner.accept(InterfaceProvisionRequest.of(info, intf)); provisioned.add(intf); } } private void unprovision(Interface intf) { if (provisioned.contains(intf) && deviceAvailable(intf) && shouldProvision(intf)) { log.info("Unprovisioning interface {}", intf); unprovisioner.accept(InterfaceProvisionRequest.of(info, intf)); provisioned.remove(intf); } } private void forceUnprovision(Interface intf) { // Skip availability check when force unprovisioning an interface if (provisioned.contains(intf) && shouldProvision(intf)) { log.info("Unprovisioning interface {}", intf); unprovisioner.accept(InterfaceProvisionRequest.of(info, intf)); provisioned.remove(intf); } } private boolean deviceAvailable(Interface intf) { return deviceService.isAvailable(intf.connectPoint().deviceId()); } private boolean shouldProvision(Interface intf) { return info.interfaces().isEmpty() || info.interfaces().contains(intf.name()); } private Stream<Interface> getInterfacesForDevice(DeviceId deviceId) { return interfaceService.getInterfaces().stream() .filter(intf -> intf.connectPoint().deviceId().equals(deviceId)); } private class InternalInterfaceListener implements InterfaceListener { @Override public void event(InterfaceEvent event) { Interface intf = event.subject(); switch (event.type()) { case INTERFACE_ADDED: provision(intf); break; case INTERFACE_UPDATED: // TODO break; case INTERFACE_REMOVED: unprovision(intf); break; default: break; } } } }