Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018-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 | package org.onosproject.drivers.odtn; |
| 17 | |
| 18 | import static com.google.common.base.Preconditions.checkNotNull; |
| 19 | import static org.slf4j.LoggerFactory.getLogger; |
| 20 | |
| 21 | import java.io.IOException; |
| 22 | import java.util.HashMap; |
| 23 | import java.util.List; |
| 24 | import java.util.Map; |
| 25 | import java.util.Objects; |
| 26 | import java.util.Optional; |
harjak | b0518c4 | 2019-07-01 11:42:48 +0200 | [diff] [blame] | 27 | import java.util.concurrent.atomic.AtomicInteger; |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 28 | import java.util.stream.Collectors; |
| 29 | |
| 30 | import org.apache.commons.configuration.ConfigurationException; |
| 31 | import org.apache.commons.configuration.HierarchicalConfiguration; |
| 32 | import org.apache.commons.configuration.XMLConfiguration; |
| 33 | import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; |
Andrea Campanella | 77e9b33 | 2018-11-29 13:33:19 -0800 | [diff] [blame] | 34 | import org.onlab.packet.ChassisId; |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 35 | import org.onosproject.net.DefaultAnnotations; |
Andrea Campanella | 77e9b33 | 2018-11-29 13:33:19 -0800 | [diff] [blame] | 36 | import org.onosproject.net.Device; |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 37 | import org.onosproject.net.DeviceId; |
| 38 | import org.onosproject.net.Port.Type; |
| 39 | import org.onosproject.net.PortNumber; |
Andrea Campanella | 77e9b33 | 2018-11-29 13:33:19 -0800 | [diff] [blame] | 40 | import org.onosproject.net.device.DefaultDeviceDescription; |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 41 | import org.onosproject.net.device.DefaultPortDescription; |
| 42 | import org.onosproject.net.device.DefaultPortDescription.Builder; |
| 43 | import org.onosproject.net.device.DeviceDescription; |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 44 | import org.onosproject.net.device.PortDescription; |
| 45 | import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| 46 | import org.onosproject.netconf.NetconfController; |
| 47 | import org.onosproject.netconf.NetconfDevice; |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 48 | import org.onosproject.netconf.NetconfSession; |
| 49 | import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery; |
| 50 | import org.slf4j.Logger; |
| 51 | |
| 52 | import com.google.common.annotations.VisibleForTesting; |
| 53 | import com.google.common.collect.ImmutableList; |
| 54 | import com.google.common.io.CharSource; |
| 55 | |
| 56 | /** |
| 57 | * OpenConfig based device and port discovery. |
| 58 | */ |
| 59 | public class OpenConfigDeviceDiscovery |
| 60 | extends AbstractHandlerBehaviour |
Andrea Campanella | 77e9b33 | 2018-11-29 13:33:19 -0800 | [diff] [blame] | 61 | implements OdtnDeviceDescriptionDiscovery { |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 62 | |
| 63 | private static final Logger log = getLogger(OpenConfigDeviceDiscovery.class); |
| 64 | |
harjak | b0518c4 | 2019-07-01 11:42:48 +0200 | [diff] [blame] | 65 | private static final AtomicInteger COUNTER = new AtomicInteger(); |
| 66 | |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 67 | @Override |
| 68 | public DeviceDescription discoverDeviceDetails() { |
Andrea Campanella | 77e9b33 | 2018-11-29 13:33:19 -0800 | [diff] [blame] | 69 | return new DefaultDeviceDescription(handler().data().deviceId().uri(), |
Andrea Campanella | 1c24fb9 | 2018-12-20 16:43:59 +0100 | [diff] [blame] | 70 | Device.Type.TERMINAL_DEVICE, "unknown", "unknown", |
Andrea Campanella | 77e9b33 | 2018-11-29 13:33:19 -0800 | [diff] [blame] | 71 | "unknown", "unknown", new ChassisId()); |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 72 | } |
| 73 | |
| 74 | @Override |
| 75 | public List<PortDescription> discoverPortDetails() { |
| 76 | try { |
| 77 | return discoverPorts(); |
| 78 | } catch (Exception e) { |
| 79 | log.error("Error discovering port details on {}", data().deviceId(), e); |
| 80 | return ImmutableList.of(); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | private List<PortDescription> discoverPorts() throws ConfigurationException, IOException { |
| 85 | DeviceId did = data().deviceId(); |
| 86 | NetconfSession ns = Optional.ofNullable(handler().get(NetconfController.class)) |
| 87 | .map(c -> c.getNetconfDevice(did)) |
| 88 | .map(NetconfDevice::getSession) |
| 89 | .orElseThrow(() -> new IllegalStateException("No NetconfSession found for " + did)); |
| 90 | |
| 91 | // TODO convert this method into non-blocking form? |
| 92 | |
Yuta HIGUCHI | f733373 | 2018-05-14 16:07:37 -0700 | [diff] [blame] | 93 | String reply = ns.asyncGet() |
| 94 | .join().toString(); |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 95 | |
Yuta HIGUCHI | f733373 | 2018-05-14 16:07:37 -0700 | [diff] [blame] | 96 | // workaround until asyncGet().join() start failing exceptionally |
| 97 | String data = null; |
| 98 | if (reply.startsWith("<data")) { |
| 99 | data = reply; |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 100 | } |
| 101 | |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 102 | if (data == null) { |
| 103 | log.error("No valid response found from {}:\n{}", did, reply); |
| 104 | return ImmutableList.of(); |
| 105 | } |
| 106 | |
| 107 | XMLConfiguration cfg = new XMLConfiguration(); |
| 108 | cfg.load(CharSource.wrap(data).openStream()); |
| 109 | |
| 110 | return discoverPorts(cfg); |
| 111 | } |
| 112 | |
| 113 | /** |
| 114 | * Parses port information from OpenConfig XML configuration. |
| 115 | * |
| 116 | * @param cfg tree where the root node is {@literal <data>} |
| 117 | * @return List of ports |
| 118 | */ |
| 119 | @VisibleForTesting |
| 120 | protected List<PortDescription> discoverPorts(XMLConfiguration cfg) { |
| 121 | // If we want to use XPath |
| 122 | cfg.setExpressionEngine(new XPathExpressionEngine()); |
| 123 | |
| 124 | // converting components into PortDescription. |
| 125 | List<HierarchicalConfiguration> components = cfg.configurationsAt("components/component"); |
| 126 | return components.stream() |
| 127 | .map(this::toPortDescription) |
| 128 | .filter(Objects::nonNull) |
| 129 | .collect(Collectors.toList()); |
| 130 | } |
| 131 | |
| 132 | // wrapper to make parsing exception safe |
| 133 | private PortDescription toPortDescription(HierarchicalConfiguration component) { |
| 134 | try { |
| 135 | return toPortDescriptionInternal(component); |
| 136 | } catch (Exception e) { |
| 137 | log.error("Unexpected exception parsing component {} on {}", |
| 138 | component.getString("name"), |
| 139 | data().deviceId(), e); |
| 140 | return null; |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Converts Component subtree to PortDescription. |
| 146 | * |
| 147 | * @param component subtree to parse |
| 148 | * @return PortDescription or null if component is not an ONOS Port |
| 149 | */ |
| 150 | private PortDescription toPortDescriptionInternal(HierarchicalConfiguration component) { |
| 151 | |
| 152 | // to access other part of <data> tree: |
| 153 | //log.warn("parent data Node: {}", |
| 154 | // ((SubnodeConfiguration) component).getParent().getRootNode().getName()); |
| 155 | |
| 156 | Map<String, String> props = new HashMap<>(); |
| 157 | |
| 158 | String name = component.getString("name"); |
| 159 | String type = component.getString("state/type"); |
Yuta HIGUCHI | 318809a | 2018-05-25 11:27:35 -0700 | [diff] [blame] | 160 | checkNotNull(name, "name not found"); |
| 161 | checkNotNull(type, "state/type not found"); |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 162 | props.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name); |
| 163 | props.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type); |
| 164 | |
| 165 | component.configurationsAt("properties/property").forEach(prop -> { |
| 166 | String pName = prop.getString("name"); |
| 167 | String pValue = prop.getString("config/value"); |
| 168 | props.put(pName, pValue); |
| 169 | }); |
| 170 | |
harjak | b0518c4 | 2019-07-01 11:42:48 +0200 | [diff] [blame] | 171 | PortNumber number = null; |
| 172 | |
Yuta HIGUCHI | 44e3a61 | 2018-05-11 15:03:39 -0700 | [diff] [blame] | 173 | if (!props.containsKey(ONOS_PORT_INDEX)) { |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 174 | log.info("DEBUG: Component {} does not include onos-index, skipping", name); |
| 175 | // ODTN: port must have onos-index property |
harjak | b0518c4 | 2019-07-01 11:42:48 +0200 | [diff] [blame] | 176 | number = PortNumber.portNumber(COUNTER.getAndIncrement(), name); |
| 177 | } else { |
| 178 | number = PortNumber.portNumber(Long.parseLong(props.get(ONOS_PORT_INDEX)), name); |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 179 | } |
| 180 | |
| 181 | Builder builder = DefaultPortDescription.builder(); |
harjak | b0518c4 | 2019-07-01 11:42:48 +0200 | [diff] [blame] | 182 | builder.withPortNumber(number); |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 183 | |
| 184 | switch (type) { |
harjak | b0518c4 | 2019-07-01 11:42:48 +0200 | [diff] [blame] | 185 | case "oc-platform-types:PORT": case "PORT": |
| 186 | |
| 187 | |
| 188 | case "oc-opt-types:OPTICAL_CHANNEL": case "OPTICAL CHANNEL": |
hiroki | ec18d3a | 2018-05-16 15:27:37 -0700 | [diff] [blame] | 189 | // TODO assign appropriate port type & annotations at some point |
| 190 | // for now we just need a Port with annotations |
| 191 | builder.type(Type.OCH); |
| 192 | |
Yuta HIGUCHI | e4702af | 2018-05-18 17:17:34 -0700 | [diff] [blame] | 193 | props.putIfAbsent(PORT_TYPE, OdtnPortType.LINE.value()); |
| 194 | |
hiroki | ec18d3a | 2018-05-16 15:27:37 -0700 | [diff] [blame] | 195 | // Just a heuristics to deal with simple transponder |
| 196 | // if the device declare odtn-connection-id, just use them |
| 197 | // if not assign same value to relevant ports types |
| 198 | props.putIfAbsent(CONNECTION_ID, "the-only-one"); |
| 199 | break; |
Yuta HIGUCHI | e4702af | 2018-05-18 17:17:34 -0700 | [diff] [blame] | 200 | |
harjak | b0518c4 | 2019-07-01 11:42:48 +0200 | [diff] [blame] | 201 | case "oc-platform-types:TRANSCEIVER": case "TRANSCEIVER": |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 202 | // TODO assign appropriate port type & annotations at some point |
| 203 | // for now we just need a Port with annotations |
| 204 | builder.type(Type.PACKET); |
Yuta HIGUCHI | 4b11fab | 2018-05-15 13:03:29 -0700 | [diff] [blame] | 205 | |
Yuta HIGUCHI | e4702af | 2018-05-18 17:17:34 -0700 | [diff] [blame] | 206 | props.putIfAbsent(PORT_TYPE, OdtnPortType.CLIENT.value()); |
| 207 | |
Yuta HIGUCHI | 4b11fab | 2018-05-15 13:03:29 -0700 | [diff] [blame] | 208 | // Just a heuristics to deal with simple transponder |
| 209 | // if the device declare odtn-connection-id, just use them |
| 210 | // if not assign same value to relevant ports types |
| 211 | props.putIfAbsent(CONNECTION_ID, "the-only-one"); |
Yuta HIGUCHI | 8c6e194 | 2018-04-05 13:40:51 -0700 | [diff] [blame] | 212 | break; |
| 213 | |
| 214 | default: |
| 215 | log.info("DEBUG: Unknown component type {}", type); |
| 216 | return null; |
| 217 | } |
| 218 | |
| 219 | builder.annotations(DefaultAnnotations.builder().putAll(props).build()); |
| 220 | |
| 221 | return builder.build(); |
| 222 | |
| 223 | } |
| 224 | |
| 225 | } |