Michael Enrico | 584eebd | 2021-07-22 08:04:13 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2016-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 | |
| 17 | package org.onosproject.drivers.polatis.netconf; |
| 18 | |
| 19 | import org.onosproject.net.ConnectPoint; |
| 20 | import org.onosproject.net.Device; |
| 21 | import org.onosproject.net.DeviceId; |
| 22 | import org.onosproject.net.Link; |
| 23 | import org.onosproject.net.device.PortDescription; |
| 24 | import org.onosproject.net.PortNumber; |
| 25 | import org.onosproject.net.Port; |
| 26 | import org.onosproject.net.DefaultAnnotations; |
| 27 | import org.onosproject.net.behaviour.LinkDiscovery; |
| 28 | import org.onosproject.net.device.DeviceService; |
| 29 | import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| 30 | import org.onosproject.net.driver.DriverHandler; |
| 31 | import org.onosproject.net.link.DefaultLinkDescription; |
| 32 | import org.onosproject.net.link.LinkDescription; |
| 33 | import org.onosproject.netconf.NetconfController; |
| 34 | import org.onosproject.netconf.NetconfSession; |
| 35 | import org.slf4j.Logger; |
| 36 | |
| 37 | import java.util.Set; |
| 38 | import java.util.HashSet; |
| 39 | import java.util.List; |
| 40 | |
| 41 | import com.fasterxml.jackson.databind.ObjectMapper; |
| 42 | import com.fasterxml.jackson.databind.JsonNode; |
| 43 | import com.fasterxml.jackson.core.JsonProcessingException; |
| 44 | |
| 45 | import static com.google.common.base.Preconditions.checkNotNull; |
| 46 | import static org.slf4j.LoggerFactory.getLogger; |
| 47 | |
| 48 | import static org.onosproject.drivers.polatis.netconf.PolatisUtility.*; |
| 49 | |
| 50 | import static org.onosproject.drivers.polatis.netconf.PolatisNetconfUtility.netconfGet; |
| 51 | import static org.onosproject.drivers.polatis.netconf.PolatisNetconfUtility.KEY_PORTPEER; |
| 52 | import static org.onosproject.drivers.polatis.netconf.PolatisNetconfUtility.KEY_PORTDIR; |
| 53 | |
| 54 | |
| 55 | /** |
| 56 | * Reads peer-port fields from a Polatis switch, parses them and returns a set of LinkDescriptions. |
| 57 | */ |
| 58 | public class PolatisLinkDiscovery extends AbstractHandlerBehaviour implements LinkDiscovery { |
| 59 | |
| 60 | private final Logger log = getLogger(getClass()); |
| 61 | |
| 62 | private static final String JSON_PEERPORT_PEER_KEY = "peer"; |
| 63 | private static final String JSON_PEERPORT_PEER_DEVICEID_KEY = "id"; |
| 64 | private static final String JSON_PEERPORT_PEER_PORTID_KEY = "port"; |
| 65 | private static final String SCHEME_NAME = "linkdiscovery"; |
| 66 | |
| 67 | /** |
| 68 | * Constructor just used to initiate logging when LinkDiscovery behaviour is invoked. |
| 69 | */ |
| 70 | public PolatisLinkDiscovery() { |
| 71 | log.debug("Running PolatisLinkDiscovery handler"); |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Returns the set of LinkDescriptions originating from a Polatis switch. |
| 76 | * <p> |
| 77 | * This is the callback required by the LinkDiscovery behaviour. |
| 78 | * @return Set of outbound unidirectional links as LinkDescriptions |
| 79 | */ |
| 80 | @Override |
| 81 | public Set<LinkDescription> getLinks() { |
| 82 | Set<LinkDescription> links = new HashSet<>(); |
| 83 | DeviceId deviceID = handler().data().deviceId(); |
| 84 | log.debug("*** Checking peer-port fields on device {}", deviceID.toString()); |
| 85 | NetconfController controller = checkNotNull(handler().get(NetconfController.class)); |
| 86 | if (controller == null || controller.getDevicesMap() == null |
| 87 | || controller.getDevicesMap().get(deviceID) == null) { |
| 88 | log.warn("NETCONF session to device {} not yet established, will try again...", deviceID); |
| 89 | return links; |
| 90 | } |
| 91 | DeviceService deviceService = checkNotNull(handler().get(DeviceService.class)); |
| 92 | Device device = deviceService.getDevice(deviceID); |
| 93 | int numInputPorts = Integer.parseInt(device.annotations().value(KEY_INPUTPORTS)); |
| 94 | int numOutputPorts = Integer.parseInt(device.annotations().value(KEY_OUTPUTPORTS)); |
| 95 | log.trace("Talking to device " + handler().data().deviceId().toString()); |
| 96 | String reply = netconfGet(handler(), getPortsFilter()); |
| 97 | // Get port details from switch as PortDescription objects |
| 98 | List<PortDescription> ports = parsePorts(reply, numInputPorts, numOutputPorts); |
| 99 | int numPeerPortEntries = 0; |
| 100 | int numPortsScanned = 0; |
| 101 | ObjectMapper mapper = new ObjectMapper(); |
| 102 | for (PortDescription port : ports) { |
| 103 | numPortsScanned++; |
| 104 | if (deviceService.getPort(new ConnectPoint(deviceID, port.portNumber())).isEnabled()) { |
| 105 | String peerPortData = port.annotations().value(KEY_PORTPEER); |
| 106 | if (!peerPortData.equals("")) { |
| 107 | numPeerPortEntries++; |
| 108 | if (peerPortData.charAt(0) == '{') { |
| 109 | ConnectPoint nearEndCP = new ConnectPoint(deviceID, port.portNumber()); |
| 110 | ConnectPoint farEndCP = null; |
| 111 | try { |
| 112 | farEndCP = parsePeerportDataForCP(mapper.readTree(peerPortData)); |
| 113 | } catch (JsonProcessingException jpe) { |
| 114 | log.debug("Error processing peer-port JSON: {}", jpe.toString()); |
| 115 | } |
| 116 | if (farEndCP != null) { |
| 117 | log.trace("Found ref on port {} to peer ConnectPoint: {}", port.portNumber(), |
| 118 | farEndCP.toString()); |
| 119 | if (checkPeer(nearEndCP, farEndCP, this.handler(), true)) { |
| 120 | log.trace("Peer {} checks out", farEndCP.toString()); |
| 121 | // now add link to Set<LinkDescription> |
| 122 | DefaultAnnotations annotations = DefaultAnnotations.builder() |
| 123 | .set(KEY_LINKBIDIR, VALUE_FALSE) |
| 124 | .set(KEY_LINKALLOWED, VALUE_TRUE) |
| 125 | .build(); |
| 126 | ConnectPoint aEndCP = nearEndCP; |
| 127 | ConnectPoint bEndCP = farEndCP; |
| 128 | // reverse direction of unidirectional link if near-end port is INPUT |
| 129 | if (port.annotations().value(KEY_PORTDIR).equals(VALUE_INPUT)) { |
| 130 | aEndCP = farEndCP; |
| 131 | bEndCP = nearEndCP; |
| 132 | } |
| 133 | LinkDescription newLinkDesc = new DefaultLinkDescription(aEndCP, bEndCP, |
| 134 | Link.Type.OPTICAL, true, annotations); |
| 135 | links.add(newLinkDesc); |
| 136 | log.debug("Adding link {}", newLinkDesc); |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | log.debug("Scanned {} ports, {} had peer-port entries, {} {} valid", numPortsScanned, numPeerPortEntries, |
| 144 | links.size(), links.size() == 1 ? "is" : "are"); |
| 145 | log.trace("Links found on this iteration: {}", links); |
| 146 | return links; |
| 147 | } |
| 148 | |
| 149 | private ConnectPoint parsePeerportDataForCP(JsonNode peerData) { |
| 150 | DeviceId idTo = DeviceId.NONE; |
| 151 | PortNumber portNumTo = PortNumber.portNumber(0L); |
| 152 | JsonNode peer = peerData.get(JSON_PEERPORT_PEER_KEY); |
| 153 | if (peer != null) { |
| 154 | log.trace("Found peer element: {}", peer.toString()); |
| 155 | JsonNode devIDNode = peer.get(JSON_PEERPORT_PEER_DEVICEID_KEY); |
| 156 | if (devIDNode != null) { |
| 157 | String devID = peer.get(JSON_PEERPORT_PEER_DEVICEID_KEY).asText(); |
| 158 | log.trace("Found devID: {}", devID); |
| 159 | idTo = DeviceId.deviceId(devID); |
| 160 | JsonNode portNode = peer.get(JSON_PEERPORT_PEER_PORTID_KEY); |
| 161 | if (portNode != null) { |
| 162 | portNumTo = PortNumber.portNumber(portNode.asInt()); |
| 163 | if (portNumTo.toLong() != 0) { |
| 164 | log.trace("Found legal peer JSON element: {}={}, {}={}", JSON_PEERPORT_PEER_DEVICEID_KEY, |
| 165 | idTo.toString(), JSON_PEERPORT_PEER_PORTID_KEY, portNumTo.toString()); |
| 166 | } else { |
| 167 | log.trace("Malformed peer-port JSON: non-numerical or zero port value"); |
| 168 | } |
| 169 | } else { |
| 170 | log.trace("Malformed peer-port JSON: unable to find \"{}\" key in {}", |
| 171 | JSON_PEERPORT_PEER_PORTID_KEY, peer.toString()); |
| 172 | } |
| 173 | } else { |
| 174 | log.trace("Malformed peer-port JSON: unable to find \"{}\" key in {}", |
| 175 | JSON_PEERPORT_PEER_DEVICEID_KEY, peer.toString()); |
| 176 | } |
| 177 | } else { |
| 178 | log.trace("Malformed peer-port JSON: unable to find \"{}\" key in {}", JSON_PEERPORT_PEER_KEY, |
| 179 | peerData.toString()); |
| 180 | } |
| 181 | return (idTo.equals(DeviceId.NONE) || portNumTo.toLong() == 0) ? null : new ConnectPoint(idTo, portNumTo); |
| 182 | } |
| 183 | |
| 184 | private boolean checkPeer(ConnectPoint nearEndCP, ConnectPoint peerCP, DriverHandler handler, boolean direct) { |
| 185 | // check peerCP exists and is available (either via device service or direct from device) |
| 186 | DeviceId peerDeviceID = peerCP.deviceId(); |
| 187 | boolean result = false; |
| 188 | DeviceService deviceService = checkNotNull(handler.get(DeviceService.class)); |
| 189 | if (deviceService.isAvailable(peerDeviceID)) { |
| 190 | log.trace("Peer device {} exists", peerDeviceID.toString()); |
| 191 | Device device = deviceService.getDevice(peerDeviceID); |
| 192 | int numInputPorts = Integer.parseInt(device.annotations().value(KEY_INPUTPORTS)); |
| 193 | int numOutputPorts = Integer.parseInt(device.annotations().value(KEY_OUTPUTPORTS)); |
| 194 | List<Port> ports = deviceService.getPorts(peerDeviceID); |
| 195 | PortNumber farEndPortNum = peerCP.port(); |
| 196 | Port port = deviceService.getPort(peerCP); |
| 197 | if (port != null) { |
| 198 | if (port.isEnabled()) { |
| 199 | log.trace("Peer port {} exists", port.number().toLong()); |
| 200 | // check far end peer-port entry (use device service or retrieve direct from switch) |
| 201 | Port peerPort = deviceService.getPort(peerDeviceID, farEndPortNum); |
| 202 | String farEndPortPeerportData = peerPort.annotations().value(KEY_PORTPEER); |
| 203 | if (direct) { |
| 204 | log.trace("Checking device {} DIRECT", handler.data().deviceId()); |
| 205 | //A bit of a cludge it seems but temporarily open a new NETCONF session to far-end device |
| 206 | NetconfController controller = checkNotNull(handler.get(NetconfController.class)); |
| 207 | NetconfSession farEndDeviceSession = controller.getDevicesMap().get(peerDeviceID).getSession(); |
| 208 | String reply = netconfGet(farEndDeviceSession, getPortFilter(farEndPortNum)); |
| 209 | PortDescription peerPortDescDirect = parsePorts(reply, numInputPorts, numOutputPorts).get(0); |
| 210 | log.trace("peerPortDesc from device: " + peerPortDescDirect.toString()); |
| 211 | String farEndPortPeerportDataDirect = peerPortDescDirect.annotations().value(KEY_PORTPEER); |
| 212 | farEndPortPeerportData = farEndPortPeerportDataDirect; |
| 213 | } |
| 214 | if (!farEndPortPeerportData.equals("")) { |
| 215 | if (farEndPortPeerportData.charAt(0) == '{') { |
| 216 | log.trace("Far-end peer-port value:" + farEndPortPeerportData); |
| 217 | ObjectMapper mapper = new ObjectMapper(); |
| 218 | ConnectPoint checkNearEndCP = null; |
| 219 | try { |
| 220 | checkNearEndCP = parsePeerportDataForCP(mapper.readTree(farEndPortPeerportData)); |
| 221 | } catch (JsonProcessingException jpe) { |
| 222 | log.trace("Error processing peer-port JSON: {}", jpe.toString()); |
| 223 | } |
| 224 | if (nearEndCP.equals(checkNearEndCP)) { |
| 225 | log.trace("Reciprocal peer port entries match: nearEnd={}, farEnd={}", nearEndCP, |
| 226 | checkNearEndCP); |
| 227 | result = true; |
| 228 | } else { |
| 229 | log.trace("Peer-port entry for far-end port ({}) does not match near-end " + |
| 230 | "port number ({})", checkNearEndCP, nearEndCP); |
| 231 | } |
| 232 | } |
| 233 | } else { |
| 234 | log.trace("Null peer-port entry for far-end port ({})", peerCP); |
| 235 | } |
| 236 | } else { |
| 237 | log.trace("Peer port {} is DISABLED", port); |
| 238 | } |
| 239 | } else { |
| 240 | log.trace("Peer port {} does not exist", port); |
| 241 | } |
| 242 | } else { |
| 243 | log.trace("Far end device does not exist or is not available"); |
| 244 | } |
| 245 | return result; |
| 246 | } |
| 247 | } |