blob: 9f4f39d9f92f42cc2ef45a9ec8863c954f9b6466 [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.layout;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.utils.Comparators;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Arranges access network according to roles assigned to devices and hosts.
*/
public class AccessNetworkLayout extends LayoutAlgorithm {
private double computeY = -350.0;
private double serviceY = -200.0;
private double spineY = 0.0;
private double aggregationY = +200.0;
private double accessY = +400.0;
private double hostsY = +550.0;
private double gatewayX = 900.0;
private double rowGap = 70;
private double computeRowGap = -120;
private double colGap = 54;
private double computeOffset = 800.0;
private double gatewayGap = 200.0;
private double gatewayOffset = -200.0;
private double serviceGap = 800;
private int computePerRow = 25;
private double spinesGap = 800;
private double aggregationGap = 400;
private double accessGap = 400;
private int hostsPerRow = 6;
private int spine, aggregation, accessLeaf, serviceLeaf, gateway;
/**
* Creates the network layout using default layout options.
*/
public AccessNetworkLayout() {
}
/**
* Creates the network layout using the specified layout property overrides.
*
* @param custom overrides of the default layout properties
*/
public AccessNetworkLayout(Map<String, Object> custom) {
computeY = (double) custom.getOrDefault("computeY", computeY);
serviceY = (double) custom.getOrDefault("serviceY", serviceY);
spineY = (double) custom.getOrDefault("spineY", spineY);
aggregationY = (double) custom.getOrDefault("aggregationY", aggregationY);
accessY = (double) custom.getOrDefault("accessY", accessY);
hostsY = (double) custom.getOrDefault("hostsY", hostsY);
gatewayX = (double) custom.getOrDefault("gatewayX", gatewayX);
rowGap = (double) custom.getOrDefault("rowGap", rowGap);
computeRowGap = (double) custom.getOrDefault("computeRowGap", computeRowGap);
colGap = (double) custom.getOrDefault("colGap", colGap);
computeOffset = (double) custom.getOrDefault("computeOffset", computeOffset);
gatewayGap = (double) custom.getOrDefault("gatewayGap", gatewayGap);
gatewayOffset = (double) custom.getOrDefault("gatewayOffset", gatewayOffset);
serviceGap = (double) custom.getOrDefault("serviceGap", serviceGap);
computePerRow = (int) custom.getOrDefault("computePerRow", computePerRow);
spinesGap = (double) custom.getOrDefault("spinesGap", spinesGap);
aggregationGap = (double) custom.getOrDefault("aggregationGap", aggregationGap);
accessGap = (double) custom.getOrDefault("accessGap", accessGap);
hostsPerRow = (int) custom.getOrDefault("hostsPerRow", hostsPerRow);
}
@Override
protected boolean classify(Device device) {
if (!super.classify(device)) {
String role;
// Does the device have any hosts attached? If not, it's a spine
if (hostService.getConnectedHosts(device.id()).isEmpty()) {
// Does the device have any aggregate links to other devices?
Multiset<DeviceId> destinations = HashMultiset.create();
linkService.getDeviceEgressLinks(device.id()).stream()
.map(l -> l.dst().deviceId()).forEach(destinations::add);
// If yes, it's the main spine; otherwise it's an aggregate spine
role = destinations.entrySet().stream().anyMatch(e -> e.getCount() > 1) ?
SPINE : AGGREGATION;
} else {
// Does the device have any multi-home hosts attached?
// If yes, it's a service leaf; otherwise it's an access leaf
role = hostService.getConnectedHosts(device.id()).stream()
.map(Host::locations).anyMatch(s -> s.size() > 1) ?
LEAF : ACCESS;
}
deviceCategories.put(role, device.id());
}
return true;
}
@Override
protected boolean classify(Host host) {
if (!super.classify(host)) {
// Is the host attached to an access leaf?
// If so, it's an access host; otherwise it's a service host or gateway
String role = host.locations().stream().map(ConnectPoint::deviceId)
.anyMatch(d -> deviceCategories.get(ACCESS)
.contains(deviceService.getDevice(d).id())) ?
ACCESS : COMPUTE;
hostCategories.put(role, host.id());
}
return true;
}
@Override
public void apply() {
placeSpines();
placeServiceLeavesAndHosts();
placeAccessLeavesAndHosts();
}
private void placeSpines() {
spine = 1;
List<DeviceId> spines = deviceCategories.get(SPINE);
spines.stream().sorted(Comparators.ELEMENT_ID_COMPARATOR)
.forEach(d -> place(d, c(spine++, spines.size(), spinesGap), spineY));
}
private void placeServiceLeavesAndHosts() {
List<DeviceId> leaves = deviceCategories.get(LEAF);
List<HostId> computes = hostCategories.get(COMPUTE);
List<HostId> gateways = hostCategories.get(GATEWAY);
Set<HostId> placed = Sets.newHashSet();
serviceLeaf = 1;
leaves.stream().sorted(Comparators.ELEMENT_ID_COMPARATOR).forEach(id -> {
gateway = 1;
place(id, c(serviceLeaf++, leaves.size(), serviceGap), serviceY);
List<HostId> gwHosts = hostService.getConnectedHosts(id).stream()
.map(Host::id)
.filter(gateways::contains)
.filter(hid -> !placed.contains(hid))
.sorted(Comparators.ELEMENT_ID_COMPARATOR)
.collect(Collectors.toList());
gwHosts.forEach(hid -> {
place(hid, serviceLeaf <= 2 ? -gatewayX : gatewayX,
c(gateway++, gwHosts.size(), gatewayGap, gatewayOffset));
placed.add(hid);
});
List<HostId> hosts = hostService.getConnectedHosts(id).stream()
.map(Host::id)
.filter(computes::contains)
.filter(hid -> !placed.contains(hid))
.sorted(Comparators.ELEMENT_ID_COMPARATOR)
.collect(Collectors.toList());
placeHostBlock(hosts, serviceLeaf <= 2 ? -computeOffset : computeOffset,
computeY, computePerRow, computeRowGap,
serviceLeaf <= 2 ? -colGap : colGap);
placed.addAll(hosts);
});
}
private void placeAccessLeavesAndHosts() {
List<DeviceId> spines = deviceCategories.get(AGGREGATION);
List<DeviceId> leaves = deviceCategories.get(ACCESS);
Set<DeviceId> placed = Sets.newHashSet();
aggregation = 1;
accessLeaf = 1;
if (spines.isEmpty()) {
leaves.stream().sorted(Comparators.ELEMENT_ID_COMPARATOR)
.forEach(lid -> placeAccessLeafAndHosts(lid, leaves.size(), placed));
} else {
spines.stream().sorted(Comparators.ELEMENT_ID_COMPARATOR).forEach(id -> {
place(id, c(aggregation++, spines.size(), aggregationGap), aggregationY);
linkService.getDeviceEgressLinks(id).stream()
.map(l -> l.dst().deviceId())
.filter(leaves::contains)
.filter(lid -> !placed.contains(lid))
.sorted(Comparators.ELEMENT_ID_COMPARATOR)
.forEach(lid -> placeAccessLeafAndHosts(lid, leaves.size(), placed));
});
}
}
private void placeAccessLeafAndHosts(DeviceId leafId, int leafCount, Set<DeviceId> placed) {
double x = c(accessLeaf++, leafCount, accessGap);
place(leafId, x, accessY);
placed.add(leafId);
placeHostBlock(hostService.getConnectedHosts(leafId).stream()
.map(Host::id)
.sorted(Comparators.ELEMENT_ID_COMPARATOR)
.collect(Collectors.toList()), x, hostsY,
hostsPerRow, rowGap, colGap);
}
}