blob: b64aaeacbf2fa9c72932c6b4a6b6e1d003730870 [file] [log] [blame]
Yi Tseng9619d802020-03-19 23:28:31 +08001/*
2 * Copyright 2020-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.drivers.odtn.openconfig;
18
19import com.google.common.collect.HashMultimap;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Multimap;
22import com.google.common.collect.Streams;
23import gnmi.Gnmi;
24import org.onosproject.drivers.gnmi.OpenConfigGnmiDeviceDescriptionDiscovery;
25import org.onosproject.gnmi.api.GnmiUtils.GnmiPathBuilder;
26import org.onosproject.net.AnnotationKeys;
27import org.onosproject.net.ChannelSpacing;
28import org.onosproject.net.DefaultAnnotations;
29import org.onosproject.net.Device;
30import org.onosproject.net.OchSignal;
31import org.onosproject.net.OduSignalType;
32import org.onosproject.net.PortNumber;
33import org.onosproject.net.device.DefaultDeviceDescription;
34import org.onosproject.net.device.DeviceDescription;
35import org.onosproject.net.device.PortDescription;
36import org.onosproject.net.optical.device.OchPortHelper;
37import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
38import org.slf4j.Logger;
39import org.slf4j.LoggerFactory;
40
41import java.util.Collection;
42import java.util.Collections;
43import java.util.List;
44import java.util.Map;
45import java.util.Objects;
46import java.util.concurrent.ExecutionException;
47import java.util.stream.Collectors;
48import java.util.stream.Stream;
49
50import static org.onosproject.gnmi.api.GnmiUtils.pathToString;
51
52/**
53 * A ODTN device discovery behaviour based on gNMI and OpenConfig model.
54 *
55 * This behavior is based on the origin gNMI OpenConfig device description discovery
56 * with additional logic to discover optical ports for this device.
57 *
58 * To find all optical port name and info, it queries all components with path:
59 * /components/component[name=*]
60 * And it uses components with type "OPTICAL_CHANNEL" to find optical ports
61 *
62 */
63public class GnmiTerminalDeviceDiscovery
64 extends OpenConfigGnmiDeviceDescriptionDiscovery
65 implements OdtnDeviceDescriptionDiscovery {
66
67 private static final Logger log = LoggerFactory.getLogger(GnmiTerminalDeviceDiscovery.class);
68 private static final String COMPONENT_TYPE_PATH_TEMPLATE =
69 "/components/component[name=%s]/state/type";
70 private static final String LINE_PORT_PATH_TEMPLATE =
71 "/components/component[name=%s]/optical-channel/config/line-port";
72
73 @Override
74 public DeviceDescription discoverDeviceDetails() {
75 return new DefaultDeviceDescription(super.discoverDeviceDetails(),
76 Device.Type.TERMINAL_DEVICE);
77 }
78
79 @Override
80 public List<PortDescription> discoverPortDetails() {
81 if (!setupBehaviour("discoverPortDetails()")) {
82 return Collections.emptyList();
83 }
84
85 // Get all components
86 Gnmi.Path path = GnmiPathBuilder.newBuilder()
87 .addElem("components")
88 .addElem("component").withKeyValue("name", "*")
89 .build();
90
91 Gnmi.GetRequest req = Gnmi.GetRequest.newBuilder()
92 .addPath(path)
93 .setEncoding(Gnmi.Encoding.PROTO)
94 .build();
95 Gnmi.GetResponse resp;
96 try {
97 resp = client.get(req).get();
98 } catch (ExecutionException | InterruptedException e) {
99 log.warn("unable to get components via gNMI: {}", e.getMessage());
100 return Collections.emptyList();
101 }
102
103 Multimap<String, Gnmi.Update> componentUpdates = HashMultimap.create();
104 resp.getNotificationList().stream()
105 .map(Gnmi.Notification::getUpdateList)
106 .flatMap(List::stream)
107 .forEach(u -> {
108 // Get component name
109 // /components/component[name=?]
110 Gnmi.Path p = u.getPath();
111 if (p.getElemCount() < 2) {
112 // Invalid path
113 return;
114 }
115 String name = p.getElem(1)
116 .getKeyOrDefault("name", null);
117
118 // Collect gNMI updates for the component.
119 // name -> a set of gNMI updates
120 if (name != null) {
121 componentUpdates.put(name, u);
122 }
123 });
124
125 Stream<PortDescription> normalPorts = super.discoverPortDetails().stream();
126 Stream<PortDescription> opticalPorts = componentUpdates.keySet().stream()
127 .map(name -> convertComponentToOdtnPortDesc(name, componentUpdates.get(name)))
128 .filter(Objects::nonNull);
129 return Streams.concat(normalPorts, opticalPorts)
130 .collect(Collectors.toList());
131 }
132
133 /**
134 * Converts gNMI updates to ODTN port description.
135 *
136 * Paths we expected per optical port component:
137 * /components/component/state/type
138 * /components/component/optical-channel/config/line-port
139 *
140 * @param name component name
141 * @param updates gNMI updates
142 * @return port description, null if it is not a valid component config/state
143 */
144 private PortDescription
145 convertComponentToOdtnPortDesc(String name, Collection<Gnmi.Update> updates) {
146 Map<String, Gnmi.TypedValue> pathValue = Maps.newHashMap();
147 updates.forEach(u -> pathValue.put(pathToString(u.getPath()), u.getVal()));
148
149 String componentTypePathStr =
150 String.format(COMPONENT_TYPE_PATH_TEMPLATE, name);
151 Gnmi.TypedValue componentType =
152 pathValue.get(componentTypePathStr);
153
154 if (componentType == null ||
155 !componentType.getStringVal().equals("OPTICAL_CHANNEL")) {
156 // Ignore the component which is not a optical channel type.
157 return null;
158 }
159
160 Map<String, String> annotations = Maps.newHashMap();
161 annotations.put(OC_NAME, name);
162 annotations.put(OC_TYPE, componentType.getStringVal());
163
164 String linePortPathStr =
165 String.format(LINE_PORT_PATH_TEMPLATE, name);
166 Gnmi.TypedValue linePort = pathValue.get(linePortPathStr);
167
168 // Invalid optical port
169 if (linePort == null) {
170 return null;
171 }
172
173 // According to CassiniTerminalDevice class, we expected to received a string with
174 // this format: port-[port id].
175 // And we use "port id" from the string as the port number.
176 // However, if we can't get port id from line port value, we will use
177 // hash number of the port name. (According to TerminalDeviceDiscovery class)
178 String linePortString = linePort.getStringVal();
179 long portId = name.hashCode();
180 if (linePortString.contains("-") && !linePortString.endsWith("-")) {
181 try {
182 portId = Long.parseUnsignedLong(linePortString.split("-")[1]);
183 } catch (NumberFormatException e) {
184 log.warn("Invalid line port string: {}, use {}", linePortString, portId);
185 }
186 }
187
188 annotations.put(AnnotationKeys.PORT_NAME, linePortString);
189 annotations.putIfAbsent(PORT_TYPE,
190 OdtnDeviceDescriptionDiscovery.OdtnPortType.LINE.value());
191 annotations.putIfAbsent(ONOS_PORT_INDEX, Long.toString(portId));
192 annotations.putIfAbsent(CONNECTION_ID, "connection-" + portId);
193
194 OchSignal signalId = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1);
195 return OchPortHelper.ochPortDescription(
196 PortNumber.portNumber(portId, name),
197 true,
198 OduSignalType.ODU4, // TODO: discover type via gNMI if possible
199 true,
200 signalId,
201 DefaultAnnotations.builder().putAll(annotations).build());
202 }
203}