blob: a5fc042cbfafb42bf30982223e8cc4bfa48d22f4 [file] [log] [blame]
/*
* 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) {
//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());
}
}
}