10068695 | e340ec9 | 2019-10-11 07:03:00 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019-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.zte; |
| 17 | |
| 18 | import com.google.common.collect.ImmutableList; |
| 19 | import com.google.common.io.CharSource; |
| 20 | import org.apache.commons.configuration.HierarchicalConfiguration; |
| 21 | import org.apache.commons.configuration.XMLConfiguration; |
| 22 | import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; |
| 23 | import org.onlab.packet.ChassisId; |
| 24 | import org.onosproject.drivers.utilities.XmlConfigParser; |
| 25 | import org.onosproject.net.ChannelSpacing; |
| 26 | import org.onosproject.net.DefaultAnnotations; |
| 27 | import org.onosproject.net.Device; |
| 28 | import org.onosproject.net.DeviceId; |
| 29 | import org.onosproject.net.OchSignal; |
| 30 | import org.onosproject.net.OduSignalType; |
| 31 | import org.onosproject.net.Port.Type; |
| 32 | import org.onosproject.net.PortNumber; |
| 33 | import org.onosproject.net.device.DefaultDeviceDescription; |
| 34 | import org.onosproject.net.device.DefaultPortDescription; |
| 35 | import org.onosproject.net.device.DefaultPortDescription.Builder; |
| 36 | import org.onosproject.net.device.DeviceDescription; |
| 37 | import org.onosproject.net.device.DeviceDescriptionDiscovery; |
| 38 | import org.onosproject.net.device.PortDescription; |
| 39 | import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| 40 | import org.onosproject.net.optical.device.OchPortHelper; |
| 41 | import org.onosproject.netconf.NetconfController; |
| 42 | import org.onosproject.netconf.NetconfException; |
| 43 | import org.onosproject.netconf.NetconfSession; |
| 44 | import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery; |
| 45 | import org.onosproject.net.AnnotationKeys; |
| 46 | import org.slf4j.Logger; |
| 47 | |
| 48 | import java.util.HashMap; |
| 49 | import java.util.List; |
| 50 | import java.util.Map; |
| 51 | import java.util.Objects; |
| 52 | import java.util.stream.Collectors; |
| 53 | |
| 54 | import static org.slf4j.LoggerFactory.getLogger; |
| 55 | |
| 56 | public class ZteDeviceDiscoveryImpl |
| 57 | extends AbstractHandlerBehaviour |
| 58 | implements OdtnDeviceDescriptionDiscovery, DeviceDescriptionDiscovery { |
| 59 | private final Logger log = getLogger(getClass()); |
| 60 | |
| 61 | @Override |
| 62 | public DeviceDescription discoverDeviceDetails() { |
| 63 | DeviceId deviceId = handler().data().deviceId(); |
| 64 | log.info("Discovering ZTE device {}", deviceId); |
| 65 | |
| 66 | NetconfController controller = handler().get(NetconfController.class); |
| 67 | NetconfSession session = controller.getDevicesMap().get(deviceId).getSession(); |
| 68 | |
| 69 | String hwVersion = "ZTE hw"; |
| 70 | String swVersion = "ZTE sw"; |
| 71 | String serialNumber = "000000000000"; |
| 72 | |
| 73 | try { |
| 74 | String reply = session.requestSync(buildDeviceInfoRequest()); |
| 75 | XMLConfiguration cfg = (XMLConfiguration) XmlConfigParser.loadXmlString(getDataOfRpcReply(reply)); |
| 76 | hwVersion = cfg.getString("components.component.state.hardware-version"); |
| 77 | swVersion = cfg.getString("components.component.state.software-version"); |
| 78 | serialNumber = cfg.getString("components.component.state.serial-no"); |
| 79 | } catch (NetconfException e) { |
| 80 | log.error("ZTE device discovery error.", e); |
| 81 | } |
| 82 | |
| 83 | return new DefaultDeviceDescription(deviceId.uri(), |
| 84 | Device.Type.OTN, |
| 85 | "ZTE", |
| 86 | hwVersion, |
| 87 | swVersion, |
| 88 | serialNumber, |
| 89 | new ChassisId(1)); |
| 90 | } |
| 91 | |
| 92 | private String buildDeviceInfoRequest() { |
| 93 | StringBuilder rpc = new StringBuilder(); |
| 94 | rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"); |
| 95 | rpc.append("<get>"); |
| 96 | rpc.append("<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">"); |
| 97 | rpc.append("<components xmlns=\"http://openconfig.net/yang/platform\">"); |
| 98 | rpc.append("<component>"); |
| 99 | rpc.append("<name>CHASSIS-1-1</name>"); |
| 100 | rpc.append("<state></state>"); |
| 101 | rpc.append("</component>"); |
| 102 | rpc.append("</components>"); |
| 103 | rpc.append("</filter>"); |
| 104 | rpc.append("</get>"); |
| 105 | rpc.append("</rpc>"); |
| 106 | |
| 107 | return rpc.toString(); |
| 108 | } |
| 109 | |
| 110 | private String getDataOfRpcReply(String rpcReply) { |
| 111 | String data = null; |
| 112 | int begin = rpcReply.indexOf("<data>"); |
| 113 | int end = rpcReply.lastIndexOf("</data>"); |
| 114 | if (begin != -1 && end != -1) { |
| 115 | data = (String) rpcReply.subSequence(begin, end + "</data>".length()); |
| 116 | } else { |
| 117 | data = rpcReply; |
| 118 | } |
| 119 | return data; |
| 120 | } |
| 121 | |
| 122 | @Override |
| 123 | public List<PortDescription> discoverPortDetails() { |
| 124 | DeviceId deviceId = handler().data().deviceId(); |
| 125 | log.info("Discovering ZTE device ports {}", deviceId); |
| 126 | |
| 127 | NetconfController controller = handler().get(NetconfController.class); |
| 128 | NetconfSession session = controller.getDevicesMap().get(deviceId).getSession(); |
| 129 | XMLConfiguration cfg = new XMLConfiguration(); |
| 130 | try { |
| 131 | String reply = session.requestSync(buildPortDetailRequest(), 30); |
| 132 | String data = getDataOfRpcReply(reply); |
| 133 | if (data == null) { |
| 134 | log.error("No valid response found from {}:\n{}", deviceId, reply); |
| 135 | return ImmutableList.of(); |
| 136 | } |
| 137 | cfg.load(CharSource.wrap(data).openStream()); |
| 138 | return discoverPorts(cfg); |
| 139 | } catch (Exception e) { |
| 140 | log.error("ZTE device port discovery error.", e); |
| 141 | } |
| 142 | |
| 143 | return ImmutableList.of(); |
| 144 | } |
| 145 | |
| 146 | private String buildPortDetailRequest() { |
| 147 | StringBuilder rpc = new StringBuilder(); |
| 148 | rpc.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"); |
| 149 | rpc.append("<get>"); |
| 150 | rpc.append("<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">"); |
| 151 | rpc.append("<components xmlns=\"http://openconfig.net/yang/platform\">"); |
| 152 | rpc.append("</components>"); |
| 153 | rpc.append("</filter>"); |
| 154 | rpc.append("</get>"); |
| 155 | rpc.append("</rpc>"); |
| 156 | |
| 157 | return rpc.toString(); |
| 158 | } |
| 159 | |
| 160 | private List<PortDescription> discoverPorts(XMLConfiguration cfg) { |
| 161 | cfg.setExpressionEngine(new XPathExpressionEngine()); |
| 162 | List<HierarchicalConfiguration> components = cfg.configurationsAt("components/component"); |
| 163 | return components.stream() |
| 164 | .filter(this::isPortComponent) |
| 165 | .map(this::toPortDescriptionInternal) |
| 166 | .filter(Objects::nonNull) |
| 167 | .collect(Collectors.toList()); |
| 168 | } |
| 169 | |
| 170 | private boolean isPortComponent(HierarchicalConfiguration component) { |
| 171 | String name = component.getString("name"); |
| 172 | String type = component.getString("state/type"); |
| 173 | |
| 174 | return name != null && name.startsWith("PORT") && type != null |
| 175 | && type.equals("openconfig-platform-types:PORT"); |
| 176 | } |
| 177 | |
| 178 | private PortDescription toPortDescriptionInternal(HierarchicalConfiguration component) { |
| 179 | Map<String, String> annotations = new HashMap<>(); |
| 180 | String name = component.getString("name"); |
| 181 | String type = component.getString("state/type"); |
| 182 | |
| 183 | annotations.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name); |
| 184 | annotations.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type); |
| 185 | annotations.putIfAbsent(AnnotationKeys.PORT_NAME, name); |
| 186 | |
| 187 | // PORT-1-4-C1 |
| 188 | String[] textStr = name.split("-"); |
| 189 | |
| 190 | // use different value of portNumber on the same equipment |
| 191 | String portComponentIndex = textStr[textStr.length - 1]; |
| 192 | int slotIndex = Integer.parseInt(textStr[2]); |
| 193 | int slotPortIndex = Integer.parseInt(portComponentIndex.substring(1)); |
| 194 | int portNumber = slotIndex * 10 + slotPortIndex; |
| 195 | |
| 196 | annotations.putIfAbsent(ONOS_PORT_INDEX, portComponentIndex); |
| 197 | annotations.putIfAbsent(CONNECTION_ID, "connection:" + Integer.parseInt(portComponentIndex.substring(1))); |
| 198 | |
| 199 | if (portComponentIndex.charAt(0) == 'L') { |
| 200 | // line |
| 201 | annotations.putIfAbsent(PORT_TYPE, OdtnPortType.LINE.value()); |
| 202 | OchSignal signalId = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1); |
| 203 | return OchPortHelper.ochPortDescription( |
| 204 | PortNumber.portNumber(portNumber + 100L), |
| 205 | true, |
| 206 | OduSignalType.ODUC2, |
| 207 | true, |
| 208 | signalId, |
| 209 | DefaultAnnotations.builder().putAll(annotations).build()); |
| 210 | } else if (portComponentIndex.charAt(0) == 'C') { |
| 211 | // client |
| 212 | annotations.putIfAbsent(PORT_TYPE, OdtnPortType.CLIENT.value()); |
| 213 | Builder builder = DefaultPortDescription.builder(); |
| 214 | builder.withPortNumber(PortNumber.portNumber(portNumber)) |
| 215 | .isEnabled(true) |
| 216 | .portSpeed(100000L) |
| 217 | .type(Type.PACKET) |
| 218 | .annotations(DefaultAnnotations.builder().putAll(annotations).build()); |
| 219 | |
| 220 | return builder.build(); |
| 221 | } |
| 222 | |
| 223 | return null; |
| 224 | } |
| 225 | } |