blob: 17ea3b995b1bd6bacf45103de2dbc1fc1da76e1f [file] [log] [blame]
/*
* Copyright 2015-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.nil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.device.DeviceProviderService;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.link.DefaultLinkDescription;
import org.onosproject.net.link.LinkDescription;
import org.onosproject.net.link.LinkProviderService;
import org.onosproject.net.link.LinkService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.delay;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.Link.Type.DIRECT;
import static org.onosproject.net.MastershipRole.MASTER;
import static org.onosproject.provider.nil.TopologySimulator.description;
/**
* Drives topology mutations at a specified rate of events per second.
*/
class TopologyMutationDriver implements Runnable {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final int WAIT_DELAY = 2_000;
private static final int MAX_DOWN_LINKS = 5;
private final Random random = new Random();
private volatile boolean stopped = true;
private double mutationRate;
private int millis, nanos;
private LinkService linkService;
private DeviceService deviceService;
private LinkProviderService linkProviderService;
private DeviceProviderService deviceProviderService;
private TopologySimulator simulator;
private List<LinkDescription> activeLinks;
private List<LinkDescription> inactiveLinks;
private final ExecutorService executor =
newSingleThreadScheduledExecutor(groupedThreads("onos/null", "topo-mutator", log));
private Map<DeviceId, Set<Link>> savedLinks = Maps.newConcurrentMap();
/**
* Starts the mutation process.
*
* @param mutationRate link events per second
* @param linkService link service
* @param deviceService device service
* @param linkProviderService link provider service
* @param deviceProviderService device provider service
* @param simulator topology simulator
*/
void start(double mutationRate,
LinkService linkService, DeviceService deviceService,
LinkProviderService linkProviderService,
DeviceProviderService deviceProviderService,
TopologySimulator simulator) {
savedLinks.clear();
stopped = false;
this.linkService = linkService;
this.deviceService = deviceService;
this.linkProviderService = linkProviderService;
this.deviceProviderService = deviceProviderService;
this.simulator = simulator;
activeLinks = reduceLinks();
inactiveLinks = Lists.newArrayList();
adjustRate(mutationRate);
executor.execute(this);
}
/**
* Adjusts the topology mutation rate.
*
* @param mutationRate new topology mutation rate
*/
void adjustRate(double mutationRate) {
this.mutationRate = mutationRate;
if (mutationRate > 0) {
this.millis = (int) (1_000 / mutationRate / 2);
this.nanos = (int) (1_000_000 / mutationRate / 2) % 1_000_000;
} else {
this.millis = 0;
this.nanos = 0;
}
log.info("Settings: millis={}, nanos={}", millis, nanos);
}
/**
* Stops the mutation process.
*/
void stop() {
stopped = true;
}
/**
* Severs the link between the specified end-points in both directions.
*
* @param one link endpoint
* @param two link endpoint
*/
void severLink(ConnectPoint one, ConnectPoint two) {
LinkDescription link = new DefaultLinkDescription(one, two, DIRECT);
linkProviderService.linkVanished(link);
linkProviderService.linkVanished(reverse(link));
}
/**
* Repairs the link between the specified end-points in both directions.
*
* @param one link endpoint
* @param two link endpoint
*/
void repairLink(ConnectPoint one, ConnectPoint two) {
LinkDescription link = new DefaultLinkDescription(one, two, DIRECT);
linkProviderService.linkDetected(link);
linkProviderService.linkDetected(reverse(link));
}
/**
* Fails the specified device.
*
* @param deviceId device identifier
*/
void failDevice(DeviceId deviceId) {
savedLinks.put(deviceId, linkService.getDeviceLinks(deviceId));
deviceProviderService.deviceDisconnected(deviceId);
}
/**
* Repairs the specified device.
*
* @param deviceId device identifier
*/
void repairDevice(DeviceId deviceId) {
int chassisId = Integer.parseInt(deviceId.uri().getSchemeSpecificPart());
simulator.createDevice(deviceId, chassisId);
Set<Link> links = savedLinks.remove(deviceId);
if (links != null) {
links.forEach(l -> linkProviderService
.linkDetected(new DefaultLinkDescription(l.src(), l.dst(), DIRECT)));
}
}
/**
* Returns whether the given device is considered reachable or not.
*
* @param deviceId device identifier
* @return true if device is reachable
*/
boolean isReachable(DeviceId deviceId) {
return !savedLinks.containsKey(deviceId);
}
@Override
public void run() {
delay(WAIT_DELAY);
while (!stopped) {
if (mutationRate > 0 && inactiveLinks.isEmpty()) {
primeInactiveLinks();
} else if (mutationRate <= 0 && !inactiveLinks.isEmpty()) {
repairInactiveLinks();
} else if (inactiveLinks.isEmpty()) {
delay(WAIT_DELAY);
} else {
activeLinks.add(repairLink());
pause();
inactiveLinks.add(severLink());
pause();
}
}
}
// Primes the inactive links with a few random links.
private void primeInactiveLinks() {
for (int i = 0, n = Math.min(MAX_DOWN_LINKS, activeLinks.size()); i < n; i++) {
inactiveLinks.add(severLink());
}
}
// Repairs all inactive links.
private void repairInactiveLinks() {
while (!inactiveLinks.isEmpty()) {
repairLink();
}
}
// Picks a random active link and severs it.
private LinkDescription severLink() {
LinkDescription link = getRandomLink(activeLinks);
linkProviderService.linkVanished(link);
linkProviderService.linkVanished(reverse(link));
return link;
}
// Picks a random inactive link and repairs it.
private LinkDescription repairLink() {
LinkDescription link = getRandomLink(inactiveLinks);
linkProviderService.linkDetected(link);
linkProviderService.linkDetected(reverse(link));
return link;
}
// Produces a reverse of the specified link.
private LinkDescription reverse(LinkDescription link) {
return new DefaultLinkDescription(link.dst(), link.src(), link.type());
}
// Returns a random link from the specified list of links.
private LinkDescription getRandomLink(List<LinkDescription> links) {
return links.remove(random.nextInt(links.size()));
}
// Reduces the given list of links to just a single link in each original pair.
private List<LinkDescription> reduceLinks() {
List<LinkDescription> links = Lists.newArrayList();
linkService.getLinks().forEach(link -> links.add(description(link)));
return links.stream()
.filter(this::isOurLink)
.filter(this::isRightDirection)
.collect(Collectors.toList());
}
// Returns true if the specified link is ours.
private boolean isOurLink(LinkDescription linkDescription) {
return deviceService.getRole(linkDescription.src().deviceId()) == MASTER;
}
// Returns true if the link source is greater than the link destination.
private boolean isRightDirection(LinkDescription link) {
return link.src().deviceId().toString().compareTo(link.dst().deviceId().toString()) > 0;
}
// Pauses the current thread for the pre-computed time of millis & nanos.
private void pause() {
delay(millis, nanos);
}
}