blob: d24a4b6e760cbb7191d89ef33f8e5464f0bebeb8 [file] [log] [blame]
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.routescale;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onosproject.app.ApplicationService;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.MastershipRole;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostAdminService;
import org.onosproject.net.link.LinkService;
import org.onosproject.routeservice.Route;
import org.onosproject.routeservice.RouteAdminService;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Dictionary;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onlab.util.Tools.get;
@Component(immediate = true, service = ScaleTestManager.class)
public class ScaleTestManager {
private Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected ApplicationService applicationService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected HostAdminService hostAdminService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected LinkService linkService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected RouteAdminService routeAdminService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected ComponentConfigService componentConfigService;
//@Property(name = "flowCount", intValue = 0,
// label = "Number of flows to be maintained in the system")
private int flowCount = 0;
//@Property(name = "routeCount", intValue = 0,
// label = "Number of routes to be maintained in the system")
private int routeCount = 0;
private final Random random = new Random(System.currentTimeMillis());
private ApplicationId appId;
private long macBase = System.currentTimeMillis();
@Activate
protected void activate() {
appId = applicationService.getId("org.onosproject.routescale");
componentConfigService.registerProperties(getClass());
log.info("Started");
}
@Deactivate
protected void deactivate() {
componentConfigService.unregisterProperties(getClass(), false);
log.info("Stopped");
}
@Modified
public void modified(ComponentContext context) {
if (context == null) {
return;
}
Dictionary<?, ?> properties = context.getProperties();
try {
String s = get(properties, "flowCount");
flowCount = isNullOrEmpty(s) ? flowCount : Integer.parseInt(s.trim());
s = get(properties, "routeCount");
routeCount = isNullOrEmpty(s) ? routeCount : Integer.parseInt(s.trim());
log.info("Reconfigured; flowCount={}; routeCount={}", flowCount, routeCount);
adjustFlows();
adjustRoutes();
} catch (NumberFormatException | ClassCastException e) {
log.warn("Misconfigured", e);
}
}
private void adjustFlows() {
int deviceCount = deviceService.getAvailableDeviceCount();
if (deviceCount == 0) {
return;
}
int flowsPerDevice = flowCount / deviceCount;
for (Device device : deviceService.getAvailableDevices()) {
DeviceId id = device.id();
if (deviceService.getRole(id) != MastershipRole.MASTER ||
flowsPerDevice == 0) {
continue;
}
int currentFlowCount = flowRuleService.getFlowRuleCount(id);
if (flowsPerDevice > currentFlowCount) {
addMoreFlows(flowsPerDevice, device, id, currentFlowCount);
} else if (flowsPerDevice < currentFlowCount) {
removeExcessFlows(flowsPerDevice, id, currentFlowCount);
}
}
}
private void addMoreFlows(int flowsPerDevice, Device device, DeviceId id,
int currentFlowCount) {
int c = flowsPerDevice - currentFlowCount;
log.info("Adding {} flows for device {}", c, id);
List<PortNumber> ports = devicePorts(device);
FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
for (int i = 0; i < c; i++) {
FlowRule.Builder frb = DefaultFlowRule.builder();
frb.fromApp(appId).makePermanent().withPriority((currentFlowCount + i) % FlowRule.MAX_PRIORITY);
TrafficSelector.Builder tsb = DefaultTrafficSelector.builder();
TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
tsb.matchEthType(Ethernet.TYPE_IPV4);
tsb.matchEthDst(randomMac());
ttb.setEthDst(randomMac()).setEthSrc(randomMac());
ttb.setOutput(randomPort(ports));
frb.withSelector(tsb.build()).withTreatment(ttb.build());
ops.add(frb.forDevice(id).build());
}
flowRuleService.apply(ops.build());
}
private void removeExcessFlows(int flowsPerDevice, DeviceId id,
int currentFlowCount) {
FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
int c = currentFlowCount - flowsPerDevice;
log.info("Removing {} flows from device {}", c, id);
for (FlowEntry e : flowRuleService.getFlowEntries(id)) {
if (Objects.equals(e.appId(), appId.id()) && c > 0) {
ops.remove(e);
c--;
} else if (c == 0) {
break;
}
}
flowRuleService.apply(ops.build());
}
private void adjustRoutes() {
int currentRouteCount =
routeAdminService.getRouteTables().parallelStream()
.mapToInt(t -> routeAdminService.getRoutes(t).size()).sum();
if (currentRouteCount < routeCount) {
createRoutes(routeCount - currentRouteCount);
} else if (currentRouteCount > routeCount) {
removeRoutes(currentRouteCount - routeCount);
}
}
// Returns a list of ports on the given device that have either links or
// hosts connected to them.
private List<PortNumber> devicePorts(Device device) {
DeviceId id = device.id();
ImmutableList.Builder<PortNumber> ports = ImmutableList.builder();
linkService.getDeviceEgressLinks(id).forEach(l -> ports.add(l.src().port()));
hostAdminService.getConnectedHosts(id)
.forEach(h -> h.locations().stream()
.filter(l -> Objects.equals(id, l.elementId()))
.findFirst()
.ifPresent(l -> ports.add(l.port())));
return ports.build();
}
// Creates the specified number of random routes. Such routes are generated
// using random IP prefices with next hop being an IP address of a randomly
// chosen hosts.
private void createRoutes(int routeCount) {
List<Host> hosts = ImmutableList.copyOf(hostAdminService.getHosts());
ImmutableSet.Builder<Route> routes = ImmutableSet.builder();
for (int i = 0; i < routeCount; i++) {
IpPrefix prefix = randomIp().toIpPrefix();
IpAddress nextHop = randomIp(hosts);
routes.add(new Route(Route.Source.STATIC, prefix, nextHop));
}
routeAdminService.update(routes.build());
}
// Removes the specified number of routes chosen at random.
private void removeRoutes(int routeCount) {
log.warn("Not implemented yet");
}
// Generates a random IP address.
private IpAddress randomIp() {
byte[] bytes = new byte[4];
random.nextBytes(bytes);
return IpAddress.valueOf(IpAddress.Version.INET, bytes, 0);
}
// Generates a random MAC address.
private MacAddress randomMac() {
return MacAddress.valueOf(macBase++);
}
// Returns IP address of a host randomly chosen from the specified list.
private IpAddress randomIp(List<Host> hosts) {
Host host = hosts.get(random.nextInt(hosts.size()));
return host.ipAddresses().iterator().next();
}
// Returns port number randomly chosen from the given list of port numbers.
private PortNumber randomPort(List<PortNumber> ports) {
return ports.get(random.nextInt(ports.size()));
}
}