blob: b64aaeacbf2fa9c72932c6b4a6b6e1d003730870 [file] [log] [blame]
/*
* Copyright 2020-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.drivers.odtn.openconfig;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Streams;
import gnmi.Gnmi;
import org.onosproject.drivers.gnmi.OpenConfigGnmiDeviceDescriptionDiscovery;
import org.onosproject.gnmi.api.GnmiUtils.GnmiPathBuilder;
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.ChannelSpacing;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.Device;
import org.onosproject.net.OchSignal;
import org.onosproject.net.OduSignalType;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DefaultDeviceDescription;
import org.onosproject.net.device.DeviceDescription;
import org.onosproject.net.device.PortDescription;
import org.onosproject.net.optical.device.OchPortHelper;
import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.onosproject.gnmi.api.GnmiUtils.pathToString;
/**
* A ODTN device discovery behaviour based on gNMI and OpenConfig model.
*
* This behavior is based on the origin gNMI OpenConfig device description discovery
* with additional logic to discover optical ports for this device.
*
* To find all optical port name and info, it queries all components with path:
* /components/component[name=*]
* And it uses components with type "OPTICAL_CHANNEL" to find optical ports
*
*/
public class GnmiTerminalDeviceDiscovery
extends OpenConfigGnmiDeviceDescriptionDiscovery
implements OdtnDeviceDescriptionDiscovery {
private static final Logger log = LoggerFactory.getLogger(GnmiTerminalDeviceDiscovery.class);
private static final String COMPONENT_TYPE_PATH_TEMPLATE =
"/components/component[name=%s]/state/type";
private static final String LINE_PORT_PATH_TEMPLATE =
"/components/component[name=%s]/optical-channel/config/line-port";
@Override
public DeviceDescription discoverDeviceDetails() {
return new DefaultDeviceDescription(super.discoverDeviceDetails(),
Device.Type.TERMINAL_DEVICE);
}
@Override
public List<PortDescription> discoverPortDetails() {
if (!setupBehaviour("discoverPortDetails()")) {
return Collections.emptyList();
}
// Get all components
Gnmi.Path path = GnmiPathBuilder.newBuilder()
.addElem("components")
.addElem("component").withKeyValue("name", "*")
.build();
Gnmi.GetRequest req = Gnmi.GetRequest.newBuilder()
.addPath(path)
.setEncoding(Gnmi.Encoding.PROTO)
.build();
Gnmi.GetResponse resp;
try {
resp = client.get(req).get();
} catch (ExecutionException | InterruptedException e) {
log.warn("unable to get components via gNMI: {}", e.getMessage());
return Collections.emptyList();
}
Multimap<String, Gnmi.Update> componentUpdates = HashMultimap.create();
resp.getNotificationList().stream()
.map(Gnmi.Notification::getUpdateList)
.flatMap(List::stream)
.forEach(u -> {
// Get component name
// /components/component[name=?]
Gnmi.Path p = u.getPath();
if (p.getElemCount() < 2) {
// Invalid path
return;
}
String name = p.getElem(1)
.getKeyOrDefault("name", null);
// Collect gNMI updates for the component.
// name -> a set of gNMI updates
if (name != null) {
componentUpdates.put(name, u);
}
});
Stream<PortDescription> normalPorts = super.discoverPortDetails().stream();
Stream<PortDescription> opticalPorts = componentUpdates.keySet().stream()
.map(name -> convertComponentToOdtnPortDesc(name, componentUpdates.get(name)))
.filter(Objects::nonNull);
return Streams.concat(normalPorts, opticalPorts)
.collect(Collectors.toList());
}
/**
* Converts gNMI updates to ODTN port description.
*
* Paths we expected per optical port component:
* /components/component/state/type
* /components/component/optical-channel/config/line-port
*
* @param name component name
* @param updates gNMI updates
* @return port description, null if it is not a valid component config/state
*/
private PortDescription
convertComponentToOdtnPortDesc(String name, Collection<Gnmi.Update> updates) {
Map<String, Gnmi.TypedValue> pathValue = Maps.newHashMap();
updates.forEach(u -> pathValue.put(pathToString(u.getPath()), u.getVal()));
String componentTypePathStr =
String.format(COMPONENT_TYPE_PATH_TEMPLATE, name);
Gnmi.TypedValue componentType =
pathValue.get(componentTypePathStr);
if (componentType == null ||
!componentType.getStringVal().equals("OPTICAL_CHANNEL")) {
// Ignore the component which is not a optical channel type.
return null;
}
Map<String, String> annotations = Maps.newHashMap();
annotations.put(OC_NAME, name);
annotations.put(OC_TYPE, componentType.getStringVal());
String linePortPathStr =
String.format(LINE_PORT_PATH_TEMPLATE, name);
Gnmi.TypedValue linePort = pathValue.get(linePortPathStr);
// Invalid optical port
if (linePort == null) {
return null;
}
// According to CassiniTerminalDevice class, we expected to received a string with
// this format: port-[port id].
// And we use "port id" from the string as the port number.
// However, if we can't get port id from line port value, we will use
// hash number of the port name. (According to TerminalDeviceDiscovery class)
String linePortString = linePort.getStringVal();
long portId = name.hashCode();
if (linePortString.contains("-") && !linePortString.endsWith("-")) {
try {
portId = Long.parseUnsignedLong(linePortString.split("-")[1]);
} catch (NumberFormatException e) {
log.warn("Invalid line port string: {}, use {}", linePortString, portId);
}
}
annotations.put(AnnotationKeys.PORT_NAME, linePortString);
annotations.putIfAbsent(PORT_TYPE,
OdtnDeviceDescriptionDiscovery.OdtnPortType.LINE.value());
annotations.putIfAbsent(ONOS_PORT_INDEX, Long.toString(portId));
annotations.putIfAbsent(CONNECTION_ID, "connection-" + portId);
OchSignal signalId = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1);
return OchPortHelper.ochPortDescription(
PortNumber.portNumber(portId, name),
true,
OduSignalType.ODU4, // TODO: discover type via gNMI if possible
true,
signalId,
DefaultAnnotations.builder().putAll(annotations).build());
}
}