| /* |
| * 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.provider.linkdiscovery.impl; |
| |
| 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.Modified; |
| import org.apache.felix.scr.annotations.Property; |
| import org.apache.felix.scr.annotations.Reference; |
| import org.apache.felix.scr.annotations.ReferenceCardinality; |
| import org.onosproject.cfg.ComponentConfigService; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.core.CoreService; |
| import org.onosproject.mastership.MastershipService; |
| import org.onosproject.net.AnnotationKeys; |
| import org.onosproject.net.DefaultAnnotations; |
| import org.onosproject.net.Device; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.Link; |
| import org.onosproject.net.behaviour.LinkDiscovery; |
| import org.onosproject.net.device.DeviceEvent; |
| import org.onosproject.net.device.DeviceListener; |
| import org.onosproject.net.device.DeviceService; |
| import org.onosproject.net.link.DefaultLinkDescription; |
| import org.onosproject.net.link.LinkDescription; |
| import org.onosproject.net.link.LinkProvider; |
| import org.onosproject.net.link.LinkProviderRegistry; |
| import org.onosproject.net.link.LinkProviderService; |
| import org.onosproject.net.link.LinkService; |
| import org.onosproject.net.provider.AbstractProvider; |
| import org.onosproject.net.provider.ProviderId; |
| import org.osgi.service.component.ComponentContext; |
| import org.slf4j.Logger; |
| |
| import java.util.Dictionary; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.ScheduledFuture; |
| import java.util.stream.Collectors; |
| |
| import static com.google.common.base.Strings.isNullOrEmpty; |
| import static java.util.concurrent.Executors.newScheduledThreadPool; |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| import static org.onlab.util.Tools.get; |
| import static org.onlab.util.Tools.groupedThreads; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Link provider capable of polling the environment using the device driver |
| * {@link LinkDiscovery} behaviour. |
| */ |
| @Component(immediate = true) |
| public class LinkDiscoveryProvider extends AbstractProvider |
| implements LinkProvider { |
| |
| protected static final String APP_NAME = "org.onosproject.linkdiscovery"; |
| protected static final String SCHEME_NAME = "linkdiscovery"; |
| private static final String LINK_PROVIDER_PACKAGE = "org.onosproject.provider.linkdiscovery"; |
| private final Logger log = getLogger(getClass()); |
| private static final int DEFAULT_POLL_DELAY_SECONDS = 20; |
| @Property(name = "linkPollDelaySeconds", intValue = DEFAULT_POLL_DELAY_SECONDS, |
| label = "Initial delay (in seconds) for polling link discovery") |
| protected static int linkPollDelaySeconds = DEFAULT_POLL_DELAY_SECONDS; |
| private static final int DEFAULT_POLL_FREQUENCY_SECONDS = 10; |
| @Property(name = "linkPollFrequencySeconds", intValue = DEFAULT_POLL_FREQUENCY_SECONDS, |
| label = "Frequency (in seconds) for polling link discovery") |
| protected static int linkPollFrequencySeconds = DEFAULT_POLL_FREQUENCY_SECONDS; |
| |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected LinkProviderRegistry providerRegistry; |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected CoreService coreService; |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected LinkService linkService; |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected MastershipService mastershipService; |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected DeviceService deviceService; |
| protected ScheduledExecutorService executor = |
| newScheduledThreadPool(2, groupedThreads("onos/netconf-link", |
| "discovery-%d")); |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected ComponentConfigService cfgService; |
| |
| protected LinkProviderService providerService; |
| private InternalDeviceListener deviceListener = new InternalDeviceListener(); |
| private ApplicationId appId; |
| private ScheduledFuture<?> scheduledTask; |
| |
| /** |
| * Creates a provider with the supplied identifier. |
| */ |
| public LinkDiscoveryProvider() { |
| super(new ProviderId(SCHEME_NAME, LINK_PROVIDER_PACKAGE)); |
| } |
| |
| @Activate |
| public void activate(ComponentContext context) { |
| providerService = providerRegistry.register(this); |
| appId = coreService.registerApplication(APP_NAME); |
| deviceService.addListener(deviceListener); |
| cfgService.registerProperties(getClass()); |
| |
| if (context == null) { |
| linkPollFrequencySeconds = DEFAULT_POLL_FREQUENCY_SECONDS; |
| log.info("No component configuration"); |
| } else { |
| Dictionary<?, ?> properties = context.getProperties(); |
| linkPollFrequencySeconds = |
| getNewPollFrequency(properties, linkPollFrequencySeconds); |
| } |
| scheduledTask = schedulePolling(); |
| log.info("Started"); |
| } |
| |
| @Deactivate |
| public void deactivate() { |
| cfgService.unregisterProperties(getClass(), false); |
| deviceService.removeListener(deviceListener); |
| providerRegistry.unregister(this); |
| providerService = null; |
| scheduledTask.cancel(true); |
| executor.shutdown(); |
| log.info("Stopped"); |
| } |
| |
| @Modified |
| public void modified(ComponentContext context) { |
| if (context == null) { |
| log.info("No component configuration"); |
| return; |
| } else { |
| Dictionary<?, ?> properties = context.getProperties(); |
| |
| int newPollFrequency = getNewPollFrequency(properties, linkPollFrequencySeconds); |
| int newPollDelay = getNewPollDealy(properties, linkPollDelaySeconds); |
| if (newPollFrequency != linkPollFrequencySeconds || |
| newPollDelay != linkPollDelaySeconds) { |
| linkPollFrequencySeconds = newPollFrequency; |
| linkPollDelaySeconds = newPollDelay; |
| //stops the old scheduled task |
| scheduledTask.cancel(true); |
| //schedules new task at the new polling rate |
| scheduledTask = schedulePolling(); |
| } |
| } |
| log.info("Modified"); |
| } |
| |
| private int getNewPollFrequency(Dictionary<?, ?> properties, int pollFrequency) { |
| int newPollFrequency; |
| try { |
| String s = get(properties, "linkPollFrequencySeconds"); |
| newPollFrequency = isNullOrEmpty(s) ? pollFrequency : Integer.parseInt(s.trim()); |
| } catch (NumberFormatException | ClassCastException e) { |
| newPollFrequency = DEFAULT_POLL_FREQUENCY_SECONDS; |
| } |
| return newPollFrequency; |
| } |
| |
| private int getNewPollDealy(Dictionary<?, ?> properties, int pollDelay) { |
| int newPollFrequency; |
| try { |
| String s = get(properties, "linkPollDelaySeconds"); |
| newPollFrequency = isNullOrEmpty(s) ? pollDelay : Integer.parseInt(s.trim()); |
| } catch (NumberFormatException | ClassCastException e) { |
| newPollFrequency = DEFAULT_POLL_DELAY_SECONDS; |
| } |
| return newPollFrequency; |
| } |
| |
| private ScheduledFuture schedulePolling() { |
| return executor.scheduleAtFixedRate(this::discoverLinksTasks, |
| linkPollDelaySeconds, |
| linkPollFrequencySeconds, |
| SECONDS); |
| } |
| |
| private void discoverLinksTasks() { |
| deviceService.getAvailableDevices().forEach(device -> { |
| if (isSupported(device)) { |
| evaluateLinks(device.id(), device.as(LinkDiscovery.class).getLinks()); |
| } |
| }); |
| } |
| |
| private void evaluateLinks(DeviceId deviceId, Set<LinkDescription> discoveredLinksDesc) { |
| if (discoveredLinksDesc == null) { |
| return; |
| } |
| |
| //The provider will get only existing links related to LinkDiscovery |
| Set<Link> storedLinks = linkService.getDeviceEgressLinks(deviceId) |
| .stream() |
| .filter(link -> { |
| String value = link.annotations().value(AnnotationKeys.PROTOCOL); |
| if (value != null && value.equals(SCHEME_NAME.toUpperCase())) { |
| return true; |
| } |
| return false; |
| }) |
| .collect(Collectors.toSet()); |
| |
| //Convert Link to LinkDescription for comparison |
| Set<LinkDescription> storedLinkDescs = new HashSet<>(); |
| storedLinks.forEach(link -> storedLinkDescs |
| .add(new DefaultLinkDescription( |
| link.src(), link.dst(), link.type(), link.isExpected(), |
| DefaultAnnotations.builder().putAll(link.annotations()).build()))); |
| log.debug("Current stored links provider related {}", storedLinks); |
| |
| //Add the correct annotation for comparison |
| Set<LinkDescription> discoveredLinkDescsAnn = new HashSet<>(); |
| |
| discoveredLinksDesc.forEach(linkDesc -> discoveredLinkDescsAnn |
| .add(new DefaultLinkDescription( |
| linkDesc.src(), linkDesc.dst(), linkDesc.type(), false, |
| DefaultAnnotations.builder().putAll(linkDesc.annotations()) |
| .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase()) |
| .build()))); |
| |
| Set<LinkDescription> linkDescsToBeRemoved = new HashSet<>(storedLinkDescs); |
| linkDescsToBeRemoved.removeAll(discoveredLinkDescsAnn); |
| log.debug("Links to be removed {}", linkDescsToBeRemoved); |
| linkDescsToBeRemoved.forEach(linkDesc -> |
| providerService.linkVanished(linkDesc)); |
| |
| Set<LinkDescription> linksToBeAdded = new HashSet<>(discoveredLinkDescsAnn); |
| linksToBeAdded.removeAll(storedLinkDescs); |
| log.debug("Links to be added {}", linksToBeAdded); |
| linksToBeAdded.forEach(linkDesc -> providerService.linkDetected(linkDesc) |
| ); |
| } |
| |
| protected boolean isSupported(Device device) { |
| boolean supported = mastershipService.isLocalMaster(device.id()) |
| && device.is(LinkDiscovery.class); |
| if (!supported) { |
| log.debug("Device {} does not support LinkDiscovery", device); |
| } |
| return supported; |
| } |
| |
| /** |
| * Listener for core device events. |
| */ |
| private class InternalDeviceListener implements DeviceListener { |
| @Override |
| public void event(DeviceEvent event) { |
| if ((event.type() == DeviceEvent.Type.DEVICE_ADDED)) { |
| executor.execute(() -> event.subject().as(LinkDiscovery.class).getLinks() |
| .forEach(linkDesc -> { |
| providerService.linkDetected(new DefaultLinkDescription( |
| linkDesc.src(), linkDesc.dst(), linkDesc.type(), false, |
| DefaultAnnotations.builder() |
| .putAll(linkDesc.annotations()) |
| .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase()) |
| .build())); |
| })); |
| } |
| } |
| |
| @Override |
| public boolean isRelevant(DeviceEvent event) { |
| return isSupported(event.subject()); |
| } |
| } |
| } |