| /* |
| * Copyright 2014-2015 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.rest.resources; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| |
| import org.onlab.packet.ChassisId; |
| import org.onlab.packet.IpAddress; |
| import org.onlab.packet.MacAddress; |
| import org.onlab.packet.VlanId; |
| import org.onlab.util.Frequency; |
| import org.onlab.util.Spectrum; |
| import org.onosproject.net.AnnotationKeys; |
| import org.onosproject.net.ChannelSpacing; |
| import org.onosproject.net.ConnectPoint; |
| import org.onosproject.net.DefaultAnnotations; |
| import org.onosproject.net.Device; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.GridType; |
| import org.onosproject.net.Host; |
| import org.onosproject.net.HostId; |
| import org.onosproject.net.HostLocation; |
| import org.onosproject.net.Link; |
| import org.onosproject.net.MastershipRole; |
| import org.onosproject.net.OchPort; |
| import org.onosproject.net.OchSignal; |
| import org.onosproject.net.OduCltPort; |
| import org.onosproject.net.OduSignalType; |
| import org.onosproject.net.OmsPort; |
| import org.onosproject.net.Port; |
| import org.onosproject.net.PortNumber; |
| import org.onosproject.net.SparseAnnotations; |
| import org.onosproject.net.device.DefaultDeviceDescription; |
| import org.onosproject.net.device.DefaultPortDescription; |
| import org.onosproject.net.device.DeviceDescription; |
| import org.onosproject.net.device.DeviceEvent; |
| import org.onosproject.net.device.DeviceListener; |
| import org.onosproject.net.device.DeviceProvider; |
| import org.onosproject.net.device.DeviceProviderRegistry; |
| import org.onosproject.net.device.DeviceProviderService; |
| import org.onosproject.net.device.DeviceService; |
| import org.onosproject.net.device.OchPortDescription; |
| import org.onosproject.net.device.OduCltPortDescription; |
| import org.onosproject.net.device.OmsPortDescription; |
| import org.onosproject.net.device.PortDescription; |
| import org.onosproject.net.host.DefaultHostDescription; |
| import org.onosproject.net.host.HostProvider; |
| import org.onosproject.net.host.HostProviderRegistry; |
| import org.onosproject.net.host.HostProviderService; |
| import org.onosproject.net.link.DefaultLinkDescription; |
| import org.onosproject.net.link.LinkProvider; |
| import org.onosproject.net.link.LinkProviderRegistry; |
| import org.onosproject.net.link.LinkProviderService; |
| import org.onosproject.net.provider.ProviderId; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.onosproject.net.DeviceId.deviceId; |
| import static org.onosproject.net.PortNumber.portNumber; |
| import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED; |
| import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED; |
| |
| /** |
| * Provider of devices and links parsed from a JSON configuration structure. |
| * |
| * @deprecated in 1.5.0 (Falcon) |
| */ |
| @Deprecated |
| class ConfigProvider implements DeviceProvider, LinkProvider, HostProvider { |
| |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private static final ProviderId PID = |
| new ProviderId("cfg", "org.onosproject.rest", true); |
| |
| private static final String UNKNOWN = "unknown"; |
| |
| // C-band has 4.4 THz (4,400 GHz) total bandwidth |
| private static final Frequency TOTAL = Frequency.ofGHz(4_400); |
| |
| private CountDownLatch deviceLatch; |
| |
| private final JsonNode cfg; |
| private final DeviceService deviceService; |
| |
| private final DeviceProviderRegistry deviceProviderRegistry; |
| private final LinkProviderRegistry linkProviderRegistry; |
| private final HostProviderRegistry hostProviderRegistry; |
| |
| private DeviceProviderService deviceProviderService; |
| private LinkProviderService linkProviderService; |
| private HostProviderService hostProviderService; |
| |
| private DeviceListener deviceEventCounter = new DeviceEventCounter(); |
| private List<ConnectPoint> connectPoints = Lists.newArrayList(); |
| private Map<ConnectPoint, PortDescription> descriptions = Maps.newHashMap(); |
| |
| /** |
| * Creates a new configuration provider. |
| * |
| * @param cfg JSON configuration |
| * @param deviceService device service |
| * @param deviceProviderRegistry device provider registry |
| * @param linkProviderRegistry link provider registry |
| * @param hostProviderRegistry host provider registry |
| */ |
| ConfigProvider(JsonNode cfg, |
| DeviceService deviceService, |
| DeviceProviderRegistry deviceProviderRegistry, |
| LinkProviderRegistry linkProviderRegistry, |
| HostProviderRegistry hostProviderRegistry) { |
| this.cfg = checkNotNull(cfg, "Configuration cannot be null"); |
| this.deviceService = checkNotNull(deviceService, "Device service cannot be null"); |
| this.deviceProviderRegistry = checkNotNull(deviceProviderRegistry, "Device provider registry cannot be null"); |
| this.linkProviderRegistry = checkNotNull(linkProviderRegistry, "Link provider registry cannot be null"); |
| this.hostProviderRegistry = checkNotNull(hostProviderRegistry, "Host provider registry cannot be null"); |
| } |
| |
| /** |
| * Parses the given JSON and provides links as configured. |
| */ |
| void parse() { |
| try { |
| register(); |
| parseDevices(); |
| parseLinks(); |
| parseHosts(); |
| addMissingPorts(); |
| } finally { |
| unregister(); |
| } |
| } |
| |
| private void register() { |
| deviceProviderService = deviceProviderRegistry.register(this); |
| linkProviderService = linkProviderRegistry.register(this); |
| hostProviderService = hostProviderRegistry.register(this); |
| } |
| |
| private void unregister() { |
| deviceProviderRegistry.unregister(this); |
| linkProviderRegistry.unregister(this); |
| hostProviderRegistry.unregister(this); |
| } |
| |
| // Parses the given JSON and provides devices. |
| private void parseDevices() { |
| try { |
| JsonNode nodes = cfg.get("devices"); |
| if (nodes != null) { |
| prepareForDeviceEvents(nodes.size()); |
| for (JsonNode node : nodes) { |
| parseDevice(node); |
| |
| // FIXME: hack to make sure device attributes take |
| // This will be fixed when GossipDeviceStore uses ECM |
| parseDevice(node); |
| } |
| } |
| } finally { |
| waitForDeviceEvents(); |
| } |
| } |
| |
| // Parses the given node with device data and supplies the device. |
| private void parseDevice(JsonNode node) { |
| URI uri = URI.create(get(node, "uri")); |
| Device.Type type = Device.Type.valueOf(get(node, "type", "SWITCH")); |
| String mfr = get(node, "mfr", UNKNOWN); |
| String hw = get(node, "hw", UNKNOWN); |
| String sw = get(node, "sw", UNKNOWN); |
| String serial = get(node, "serial", UNKNOWN); |
| ChassisId cid = new ChassisId(get(node, "mac", "000000000000")); |
| SparseAnnotations annotations = annotations(node.get("annotations")); |
| |
| DeviceDescription desc = |
| new DefaultDeviceDescription(uri, type, mfr, hw, sw, serial, |
| cid, annotations); |
| DeviceId deviceId = deviceId(uri); |
| deviceProviderService.deviceConnected(deviceId, desc); |
| |
| JsonNode ports = node.get("ports"); |
| if (ports != null) { |
| parsePorts(deviceId, ports); |
| } |
| } |
| |
| // Parses the given node with list of device ports. |
| private void parsePorts(DeviceId deviceId, JsonNode nodes) { |
| List<PortDescription> ports = new ArrayList<>(); |
| for (JsonNode node : nodes) { |
| ports.add(parsePort(deviceId, node)); |
| } |
| deviceProviderService.updatePorts(deviceId, ports); |
| } |
| |
| // Parses the given node with port information. |
| private PortDescription parsePort(DeviceId deviceId, JsonNode node) { |
| Port.Type type = Port.Type.valueOf(node.path("type").asText("COPPER")); |
| // TL1-based ports have a name |
| PortNumber port = null; |
| if (node.has("name")) { |
| for (Port p : deviceService.getPorts(deviceId)) { |
| if (p.number().name().equals(node.get("name").asText())) { |
| port = p.number(); |
| break; |
| } |
| } |
| } else { |
| port = portNumber(node.path("port").asLong(0)); |
| } |
| |
| if (port == null) { |
| log.error("Cannot find port given in node {}", node); |
| return null; |
| } |
| |
| String portName = Strings.emptyToNull(port.name()); |
| SparseAnnotations annotations = null; |
| if (portName != null) { |
| annotations = DefaultAnnotations.builder() |
| .set(AnnotationKeys.PORT_NAME, portName).build(); |
| } |
| switch (type) { |
| case COPPER: |
| return new DefaultPortDescription(port, node.path("enabled").asBoolean(true), |
| type, node.path("speed").asLong(1_000), |
| annotations); |
| case FIBER: |
| // Currently, assume OMS when FIBER. Provide sane defaults. |
| annotations = annotations(node.get("annotations")); |
| return new OmsPortDescription(port, node.path("enabled").asBoolean(true), |
| Spectrum.CENTER_FREQUENCY, Spectrum.CENTER_FREQUENCY.add(TOTAL), |
| Frequency.ofGHz(100), annotations); |
| case ODUCLT: |
| annotations = annotations(node.get("annotations")); |
| OduCltPort oduCltPort = (OduCltPort) deviceService.getPort(deviceId, port); |
| return new OduCltPortDescription(port, node.path("enabled").asBoolean(true), |
| oduCltPort.signalType(), annotations); |
| case OCH: |
| annotations = annotations(node.get("annotations")); |
| OchPort ochPort = (OchPort) deviceService.getPort(deviceId, port); |
| return new OchPortDescription(port, node.path("enabled").asBoolean(true), |
| ochPort.signalType(), ochPort.isTunable(), |
| ochPort.lambda(), annotations); |
| case OMS: |
| annotations = annotations(node.get("annotations")); |
| OmsPort omsPort = (OmsPort) deviceService.getPort(deviceId, port); |
| return new OmsPortDescription(port, node.path("enabled").asBoolean(true), |
| omsPort.minFrequency(), omsPort.maxFrequency(), omsPort.grid(), annotations); |
| default: |
| log.warn("{}: Unsupported Port Type"); |
| } |
| return new DefaultPortDescription(port, node.path("enabled").asBoolean(true), |
| type, node.path("speed").asLong(1_000), |
| annotations); |
| } |
| |
| // Parses the given JSON and provides links as configured. |
| private void parseLinks() { |
| JsonNode nodes = cfg.get("links"); |
| if (nodes != null) { |
| for (JsonNode node : nodes) { |
| parseLink(node, false); |
| if (!node.has("halfplex")) { |
| parseLink(node, true); |
| } |
| } |
| } |
| } |
| |
| // Parses the given node with link data and supplies the link. |
| private void parseLink(JsonNode node, boolean reverse) { |
| ConnectPoint src = connectPoint(get(node, "src")); |
| ConnectPoint dst = connectPoint(get(node, "dst")); |
| Link.Type type = Link.Type.valueOf(get(node, "type", "DIRECT")); |
| SparseAnnotations annotations = annotations(node.get("annotations")); |
| // take annotations to update optical ports with correct attributes. |
| updatePorts(src, dst, annotations); |
| DefaultLinkDescription desc = reverse ? |
| new DefaultLinkDescription(dst, src, type, annotations) : |
| new DefaultLinkDescription(src, dst, type, annotations); |
| linkProviderService.linkDetected(desc); |
| |
| connectPoints.add(src); |
| connectPoints.add(dst); |
| } |
| |
| private void updatePorts(ConnectPoint src, ConnectPoint dst, SparseAnnotations annotations) { |
| final String linkType = annotations.value("optical.type"); |
| if ("cross-connect".equals(linkType)) { |
| String value = annotations.value("bandwidth").trim(); |
| try { |
| double bw = Double.parseDouble(value); |
| updateOchPort(bw, src, dst); |
| } catch (NumberFormatException e) { |
| log.warn("Invalid bandwidth ({}), can't configure port(s)", value); |
| return; |
| } |
| } else if ("WDM".equals(linkType)) { |
| String value = annotations.value("optical.waves").trim(); |
| try { |
| int numChls = Integer.parseInt(value); |
| updateOmsPorts(numChls, src, dst); |
| } catch (NumberFormatException e) { |
| log.warn("Invalid channel ({}), can't configure port(s)", value); |
| return; |
| } |
| } |
| } |
| |
| // uses 'bandwidth' annotation to determine the channel spacing. |
| private void updateOchPort(double bw, ConnectPoint srcCp, ConnectPoint dstCp) { |
| Device src = deviceService.getDevice(srcCp.deviceId()); |
| Device dst = deviceService.getDevice(dstCp.deviceId()); |
| // bandwidth in MHz (assuming Hz - linc is not clear if that or Mb). |
| Frequency spacing = Frequency.ofMHz(bw); |
| // channel bandwidth is smaller than smallest standard channel spacing. |
| ChannelSpacing chsp = null; |
| if (spacing.compareTo(ChannelSpacing.CHL_6P25GHZ.frequency()) <= 0) { |
| chsp = ChannelSpacing.CHL_6P25GHZ; |
| } |
| for (int i = 1; i < ChannelSpacing.values().length; i++) { |
| Frequency val = ChannelSpacing.values()[i].frequency(); |
| // pick the next highest or equal channel interval. |
| if (val.isLessThan(spacing)) { |
| chsp = ChannelSpacing.values()[i - 1]; |
| break; |
| } |
| } |
| if (chsp == null) { |
| log.warn("Invalid channel spacing ({}), can't configure port(s)", spacing); |
| return; |
| } |
| OchSignal signal = new OchSignal(GridType.DWDM, chsp, 1, 1); |
| if (src.type() == Device.Type.ROADM) { |
| PortDescription portDesc = new OchPortDescription(srcCp.port(), true, |
| OduSignalType.ODU4, true, signal); |
| descriptions.put(srcCp, portDesc); |
| deviceProviderService.portStatusChanged(srcCp.deviceId(), portDesc); |
| } |
| if (dst.type() == Device.Type.ROADM) { |
| PortDescription portDesc = new OchPortDescription(dstCp.port(), true, |
| OduSignalType.ODU4, true, signal); |
| descriptions.put(dstCp, portDesc); |
| deviceProviderService.portStatusChanged(dstCp.deviceId(), portDesc); |
| } |
| } |
| |
| private void updateOmsPorts(int numChls, ConnectPoint srcCp, ConnectPoint dstCp) { |
| // round down to largest slot that allows numChl channels to fit into C band range |
| ChannelSpacing chl = null; |
| Frequency perChl = TOTAL.floorDivision(numChls); |
| for (int i = 0; i < ChannelSpacing.values().length; i++) { |
| Frequency val = ChannelSpacing.values()[i].frequency(); |
| if (val.isLessThan(perChl)) { |
| chl = ChannelSpacing.values()[i]; |
| break; |
| } |
| } |
| if (chl == null) { |
| chl = ChannelSpacing.CHL_6P25GHZ; |
| } |
| |
| // if true, there was less channels than can be tightly packed. |
| Frequency grid = chl.frequency(); |
| // say Linc's 1st slot starts at CENTER and goes up from there. |
| Frequency min = Spectrum.CENTER_FREQUENCY.add(grid); |
| Frequency max = Spectrum.CENTER_FREQUENCY.add(grid.multiply(numChls)); |
| |
| PortDescription srcPortDesc = new OmsPortDescription(srcCp.port(), true, min, max, grid); |
| PortDescription dstPortDesc = new OmsPortDescription(dstCp.port(), true, min, max, grid); |
| descriptions.put(srcCp, srcPortDesc); |
| descriptions.put(dstCp, dstPortDesc); |
| deviceProviderService.portStatusChanged(srcCp.deviceId(), srcPortDesc); |
| deviceProviderService.portStatusChanged(dstCp.deviceId(), dstPortDesc); |
| } |
| |
| // Parses the given JSON and provides hosts as configured. |
| private void parseHosts() { |
| try { |
| JsonNode nodes = cfg.get("hosts"); |
| if (nodes != null) { |
| for (JsonNode node : nodes) { |
| parseHost(node); |
| |
| // FIXME: hack to make sure host attributes take |
| // This will be fixed when GossipHostStore uses ECM |
| parseHost(node); |
| } |
| } |
| } finally { |
| hostProviderRegistry.unregister(this); |
| } |
| } |
| |
| // Parses the given node with host data and supplies the host. |
| private void parseHost(JsonNode node) { |
| MacAddress mac = MacAddress.valueOf(get(node, "mac")); |
| VlanId vlanId = VlanId.vlanId((short) node.get("vlan").asInt(VlanId.UNTAGGED)); |
| HostId hostId = HostId.hostId(mac, vlanId); |
| SparseAnnotations annotations = annotations(node.get("annotations")); |
| HostLocation location = new HostLocation(connectPoint(get(node, "location")), 0); |
| |
| String[] ipStrings = get(node, "ip", "").split(","); |
| Set<IpAddress> ips = new HashSet<>(); |
| for (String ip : ipStrings) { |
| ips.add(IpAddress.valueOf(ip.trim())); |
| } |
| |
| DefaultHostDescription desc = |
| new DefaultHostDescription(mac, vlanId, location, ips, annotations); |
| hostProviderService.hostDetected(hostId, desc); |
| |
| connectPoints.add(location); |
| } |
| |
| // Adds any missing device ports for configured links and host locations. |
| private void addMissingPorts() { |
| deviceService.getDevices().forEach(this::addMissingPorts); |
| } |
| |
| // Adds any missing device ports. |
| private void addMissingPorts(Device device) { |
| try { |
| List<Port> ports = deviceService.getPorts(device.id()); |
| Set<ConnectPoint> existing = ports.stream() |
| .map(p -> new ConnectPoint(device.id(), p.number())) |
| .collect(Collectors.toSet()); |
| Set<ConnectPoint> missing = connectPoints.stream() |
| .filter(cp -> cp.deviceId().equals(device.id())) |
| .filter(cp -> !existing.contains(cp)) |
| .collect(Collectors.toSet()); |
| |
| if (!missing.isEmpty()) { |
| List<PortDescription> newPorts = Stream.concat( |
| ports.stream().map(this::description), |
| missing.stream().map(this::description) |
| ).collect(Collectors.toList()); |
| deviceProviderService.updatePorts(device.id(), newPorts); |
| } |
| } catch (IllegalArgumentException e) { |
| log.warn("Error pushing ports: {}", e.getMessage()); |
| } |
| } |
| |
| // Creates a port description from the specified port. |
| private PortDescription description(Port p) { |
| switch (p.type()) { |
| case OMS: |
| OmsPort op = (OmsPort) p; |
| return new OmsPortDescription( |
| op.number(), op.isEnabled(), op.minFrequency(), op.maxFrequency(), op.grid()); |
| case OCH: |
| OchPort ochp = (OchPort) p; |
| return new OchPortDescription( |
| ochp.number(), ochp.isEnabled(), ochp.signalType(), ochp.isTunable(), ochp.lambda()); |
| case ODUCLT: |
| OduCltPort odup = (OduCltPort) p; |
| return new OduCltPortDescription( |
| odup.number(), odup.isEnabled(), odup.signalType()); |
| default: |
| return new DefaultPortDescription(p.number(), p.isEnabled(), p.type(), p.portSpeed()); |
| } |
| } |
| |
| // Creates a port description from the specified connection point if none created earlier. |
| private PortDescription description(ConnectPoint cp) { |
| PortDescription saved = descriptions.get(cp); |
| if (saved != null) { |
| return saved; |
| } |
| Port p = deviceService.getPort(cp.deviceId(), cp.port()); |
| if (p == null) { |
| return new DefaultPortDescription(cp.port(), true); |
| } |
| return description(p); |
| } |
| |
| // Produces set of annotations from the given JSON node. |
| private SparseAnnotations annotations(JsonNode node) { |
| if (node == null) { |
| return DefaultAnnotations.EMPTY; |
| } |
| |
| DefaultAnnotations.Builder builder = DefaultAnnotations.builder(); |
| Iterator<String> it = node.fieldNames(); |
| while (it.hasNext()) { |
| String k = it.next(); |
| builder.set(k, node.get(k).asText()); |
| } |
| return builder.build(); |
| } |
| |
| // Produces a connection point from the specified uri/port text. |
| private ConnectPoint connectPoint(String text) { |
| int i = text.lastIndexOf("/"); |
| String portName = text.substring(i + 1); |
| DeviceId deviceId = deviceId(text.substring(0, i)); |
| |
| for (Port port : deviceService.getPorts(deviceId)) { |
| PortNumber pn = port.number(); |
| if (pn.name().equals(portName)) { |
| return new ConnectPoint(deviceId, pn); |
| } |
| } |
| |
| long portNum; |
| try { |
| portNum = Long.parseLong(portName); |
| } catch (NumberFormatException e) { |
| portNum = 0; |
| } |
| |
| return new ConnectPoint(deviceId, portNumber(portNum, portName)); |
| } |
| |
| // Returns string form of the named property in the given JSON object. |
| private String get(JsonNode node, String name) { |
| return node.path(name).asText(); |
| } |
| |
| // Returns string form of the named property in the given JSON object. |
| private String get(JsonNode node, String name, String defaultValue) { |
| return node.path(name).asText(defaultValue); |
| } |
| |
| @Override |
| public void roleChanged(DeviceId device, MastershipRole newRole) { |
| deviceProviderService.receivedRoleReply(device, newRole, newRole); |
| } |
| |
| @Override |
| public void triggerProbe(DeviceId deviceId) { |
| } |
| |
| @Override |
| public void triggerProbe(Host host) { |
| } |
| |
| @Override |
| public ProviderId id() { |
| return PID; |
| } |
| |
| @Override |
| public boolean isReachable(DeviceId device) { |
| return true; |
| } |
| |
| /** |
| * Prepares to count device added/available/removed events. |
| * |
| * @param count number of events to count |
| */ |
| protected void prepareForDeviceEvents(int count) { |
| deviceLatch = new CountDownLatch(count); |
| deviceService.addListener(deviceEventCounter); |
| } |
| |
| /** |
| * Waits for all expected device added/available/removed events. |
| */ |
| protected void waitForDeviceEvents() { |
| try { |
| deviceLatch.await(2, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| log.warn("Device events did not arrive in time"); |
| } |
| deviceService.removeListener(deviceEventCounter); |
| } |
| |
| // Counts down number of device added/available/removed events. |
| private class DeviceEventCounter implements DeviceListener { |
| @Override |
| public void event(DeviceEvent event) { |
| DeviceEvent.Type type = event.type(); |
| if (type == DEVICE_ADDED || type == DEVICE_AVAILABILITY_CHANGED) { |
| deviceLatch.countDown(); |
| } |
| } |
| } |
| |
| } |