blob: 105840e87a4ad5ddf1b7e23de3837960d3e6508c [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.Set;
import java.util.stream.Collectors;
/**
* Arranges access network according to roles assigned to devices and hosts.
*/
public class AccessNetworkLayout extends LayoutAlgorithm {
private static final double COMPUTE_Y = -400.0;
private static final double SERVICE_Y = -200.0;
private static final double SPINE_Y = 0.0;
private static final double AGGREGATION_Y = +200.0;
private static final double ACCESS_Y = +400.0;
private static final double HOSTS_Y = +700.0;
private static final double GATEWAY_X = 900.0;
private static final int HOSTS_PER_ROW = 6;
private static final int COMPUTE_PER_ROW = 12;
private static final double ROW_GAP = 70;
private static final double COMPUTE_ROW_GAP = -120;
private static final double COL_GAP = 54;
private static final double COMPUTE_OFFSET = 400.0;
private static final double GATEWAY_GAP = 200.0;
private static final double GATEWAY_OFFSET = -200.0;
private int spine, aggregation, accessLeaf, serviceLeaf, gateway;
@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()), SPINE_Y));
}
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()), SERVICE_Y);
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 ? -GATEWAY_X : GATEWAY_X,
c(gateway++, gwHosts.size(), GATEWAY_GAP, GATEWAY_OFFSET));
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 ? -COMPUTE_OFFSET : COMPUTE_OFFSET,
COMPUTE_Y, COMPUTE_PER_ROW, COMPUTE_ROW_GAP,
serviceLeaf <= 2 ? -COL_GAP : COL_GAP);
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()), AGGREGATION_Y);
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);
place(leafId, x, ACCESS_Y);
placed.add(leafId);
placeHostBlock(hostService.getConnectedHosts(leafId).stream()
.map(Host::id)
.sorted(Comparators.ELEMENT_ID_COMPARATOR)
.collect(Collectors.toList()), x, HOSTS_Y,
HOSTS_PER_ROW, ROW_GAP, COL_GAP);
}
}