gyewan.an | eeb4cf5 | 2018-12-24 11:47:57 +0900 | [diff] [blame] | 1 | /* |
sdn | 5d65d11 | 2019-01-11 18:45:26 +0900 | [diff] [blame^] | 2 | * Copyright 2019-present Open Networking Foundation |
gyewan.an | eeb4cf5 | 2018-12-24 11:47:57 +0900 | [diff] [blame] | 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.cisco.rest; |
| 18 | |
| 19 | import com.fasterxml.jackson.core.JsonParseException; |
| 20 | import com.fasterxml.jackson.core.JsonProcessingException; |
| 21 | import com.fasterxml.jackson.databind.JsonMappingException; |
| 22 | import com.fasterxml.jackson.databind.JsonNode; |
| 23 | import com.fasterxml.jackson.databind.ObjectMapper; |
| 24 | import com.fasterxml.jackson.databind.node.JsonNodeType; |
| 25 | import com.google.common.collect.Lists; |
| 26 | import org.onlab.packet.ChassisId; |
| 27 | import org.onlab.packet.MacAddress; |
| 28 | import org.onosproject.net.AnnotationKeys; |
| 29 | import org.onosproject.net.DefaultAnnotations; |
| 30 | import org.onosproject.net.Device; |
| 31 | import org.onosproject.net.DeviceId; |
| 32 | import org.onosproject.net.Port; |
| 33 | import org.onosproject.net.PortNumber; |
| 34 | import org.onosproject.net.device.DefaultDeviceDescription; |
| 35 | import org.onosproject.net.device.DefaultPortDescription; |
| 36 | import org.onosproject.net.device.DeviceDescription; |
| 37 | import org.onosproject.net.device.DeviceDescriptionDiscovery; |
| 38 | import org.onosproject.net.device.DeviceService; |
| 39 | import org.onosproject.net.device.PortDescription; |
| 40 | import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| 41 | import org.slf4j.Logger; |
| 42 | |
| 43 | import java.io.IOException; |
| 44 | import java.util.ArrayList; |
| 45 | import java.util.Iterator; |
| 46 | import java.util.List; |
| 47 | import java.util.Optional; |
| 48 | import java.util.stream.StreamSupport; |
| 49 | |
| 50 | import static com.google.common.base.Preconditions.checkNotNull; |
| 51 | import static org.slf4j.LoggerFactory.getLogger; |
| 52 | |
| 53 | /** |
| 54 | * Discovers the device, ports information from a Cisco Nexus 9K REST device. |
| 55 | */ |
| 56 | public class DeviceDescriptionDiscoveryCisco9kImpl extends AbstractHandlerBehaviour |
| 57 | implements DeviceDescriptionDiscovery { |
| 58 | private final Logger log = getLogger(getClass()); |
| 59 | |
| 60 | private static final String SHOW_INTERFACES_CMD = "show interface"; |
| 61 | private static final String SHOW_VERSION_CMD = "show version"; |
| 62 | private static final String SHOW_MODULE_CMD = "show module"; |
| 63 | |
| 64 | private static final String JSON_BODY = "body"; |
| 65 | private static final String JSON_RESULT = "result"; |
| 66 | private static final String JSON_INTERFACE = "interface"; |
| 67 | private static final String JSON_ROW_INTERFACE = "ROW_interface"; |
| 68 | private static final String JSON_ROW_MODULE = "ROW_modinfo"; |
| 69 | private static final String JSON_ROW_MODULE_MAC = "ROW_modmacinfo"; |
| 70 | |
| 71 | private static final String MANUFACTURER = "manufacturer"; |
| 72 | private static final String CHASSIS_ID = "chassis_id"; |
| 73 | private static final String CISCO_SERIAL_BOARD = "proc_board_id"; |
| 74 | private static final String KICKSTART_VER = "kickstart_ver_str"; |
| 75 | private static final String INTERFACE_STATE = "state"; |
| 76 | private static final String INTERFACE_ADMIN_STATE = "admin_state"; |
| 77 | private static final String INTERFACE_ENABLED = "enabled"; |
| 78 | private static final String INTERFACE_DISABLED = "disabled"; |
| 79 | private static final String STATE_UP = "up"; |
| 80 | private static final String MODULE_MODEL = "model"; |
| 81 | private static final String MODULE_INTERFACE = "modinf"; |
| 82 | private static final String MODULE_MAC = "modmac"; |
| 83 | private static final String MODULE_SERIAL = "serialnum"; |
| 84 | |
| 85 | private static final String INTERFACE_ETHERNET = "Ethernet"; |
| 86 | private static final String INTERFACE_PORTCHANNEL = "port-channel"; |
| 87 | private static final String INTERFACE_BW = "eth_bw"; |
| 88 | private static final String INTERFACE_MAC = "eth_bia_addr"; |
| 89 | private static final String BREAKOUT = "breakout"; |
| 90 | private static final String UNKNOWN = "unknown"; |
| 91 | |
| 92 | private static final String MODULE_ANNOTATION_FORMAT = "%s:%s:%s"; |
| 93 | private static final String MODULE_BRACKET_FORMAT = "[%s]"; |
| 94 | |
| 95 | @Override |
| 96 | public DeviceDescription discoverDeviceDetails() { |
| 97 | DeviceId deviceId = handler().data().deviceId(); |
| 98 | |
| 99 | ArrayList<String> cmd = new ArrayList<>(); |
| 100 | cmd.add(SHOW_VERSION_CMD); |
| 101 | cmd.add(SHOW_MODULE_CMD); |
| 102 | |
| 103 | String response = NxApiRequest.postClis(handler(), cmd); |
| 104 | |
| 105 | String mrf = UNKNOWN; |
| 106 | String hwVer = UNKNOWN; |
| 107 | String swVer = UNKNOWN; |
| 108 | String serialNum = UNKNOWN; |
| 109 | String module = UNKNOWN; |
| 110 | |
| 111 | try { |
| 112 | ObjectMapper om = new ObjectMapper(); |
| 113 | JsonNode json = om.readTree(response); |
| 114 | |
| 115 | JsonNode body = json.at("/0/body"); |
| 116 | if (!body.isMissingNode()) { |
| 117 | mrf = body.at("/" + MANUFACTURER).asText(); |
| 118 | hwVer = body.at("/" + CHASSIS_ID).asText(); |
| 119 | swVer = body.at("/" + KICKSTART_VER).asText(); |
| 120 | serialNum = body.at("/" + CISCO_SERIAL_BOARD).asText(); |
| 121 | } |
| 122 | |
| 123 | |
| 124 | JsonNode modInfo = json.at("/1/" + JSON_ROW_MODULE); |
| 125 | JsonNode modMacInfo = json.at("/1/" + JSON_ROW_MODULE_MAC); |
| 126 | |
| 127 | if (!modInfo.isMissingNode()) { |
| 128 | List<String> modulesAnn = prepareModuleAnnotation(modInfo, modMacInfo); |
| 129 | module = String.format(MODULE_BRACKET_FORMAT, String.join(",", modulesAnn)); |
| 130 | } |
| 131 | } catch (JsonParseException e) { |
| 132 | log.error("Failed to parse Json", e); |
| 133 | } catch (JsonMappingException e) { |
| 134 | log.error("Failed to map Json", e); |
| 135 | } catch (JsonProcessingException e) { |
| 136 | log.error("Failed to processing Json", e); |
| 137 | } catch (IOException e) { |
| 138 | log.error("Failed to retrieve Device Information", e); |
| 139 | } |
| 140 | DefaultAnnotations.Builder annotations = DefaultAnnotations.builder(); |
| 141 | |
| 142 | DeviceService deviceService = checkNotNull(handler().get(DeviceService.class)); |
| 143 | Device device = deviceService.getDevice(deviceId); |
| 144 | if (device != null) { |
| 145 | annotations.putAll(device.annotations()); |
| 146 | } |
| 147 | |
| 148 | return new DefaultDeviceDescription(deviceId.uri(), Device.Type.SWITCH, |
| 149 | mrf, hwVer, swVer, serialNum, |
| 150 | new ChassisId(), annotations.build()); |
| 151 | } |
| 152 | |
| 153 | @Override |
| 154 | public List<PortDescription> discoverPortDetails() { |
| 155 | List<PortDescription> ports = Lists.newArrayList(); |
| 156 | |
| 157 | try { |
| 158 | String response; |
| 159 | try { |
| 160 | response = NxApiRequest.postCli(handler(), SHOW_INTERFACES_CMD); |
| 161 | } catch (NullPointerException e) { |
| 162 | log.error("Failed to perform {} command on the device {}", |
| 163 | SHOW_INTERFACES_CMD, handler().data().deviceId()); |
| 164 | return ports; |
| 165 | } |
| 166 | |
| 167 | ObjectMapper om = new ObjectMapper(); |
| 168 | JsonNode json = om.readTree(response); |
| 169 | JsonNode res = json.get(JSON_RESULT); |
| 170 | JsonNode interfaces = res.findValue(JSON_ROW_INTERFACE); |
| 171 | Iterator<JsonNode> iter = interfaces.elements(); |
| 172 | Integer ifCount = 1; |
| 173 | while (iter.hasNext()) { |
| 174 | JsonNode ifs = iter.next(); |
| 175 | String ifName = ifs.get(JSON_INTERFACE).asText(); |
| 176 | |
| 177 | if (isPortValid(ifName)) { |
| 178 | Port.Type portType = Port.Type.VIRTUAL; |
| 179 | long portSpeed = ifs.get(INTERFACE_BW).asLong() / 1000; //Mbps |
| 180 | String portMac = ifs.get(INTERFACE_MAC).asText(); |
| 181 | MacAddress mac = MacAddress.valueOf( |
| 182 | portMac.replace(".", "").replaceAll("(.{2})", "$1:").trim().substring(0, 17)); |
| 183 | boolean state = STATE_UP.equals(ifs.get(INTERFACE_STATE).asText()); |
| 184 | String adminState = STATE_UP.equals(ifs.get(INTERFACE_ADMIN_STATE).asText()) |
| 185 | ? INTERFACE_ENABLED : INTERFACE_DISABLED; |
| 186 | |
| 187 | DefaultAnnotations.Builder annotations = DefaultAnnotations.builder() |
| 188 | .set(AnnotationKeys.PORT_NAME, ifName) |
| 189 | .set(AnnotationKeys.PORT_MAC, mac.toString()) |
| 190 | .set(AnnotationKeys.ADMIN_STATE, adminState); |
| 191 | |
| 192 | if (isValidPhysicalPort(ifName)) { |
| 193 | String interfaceNumber = ifName.replace(INTERFACE_ETHERNET, ""); |
| 194 | String[] interfaceLocation = interfaceNumber.split("/"); |
| 195 | portType = Port.Type.FIBER; |
| 196 | |
| 197 | if (interfaceLocation.length == 3) { |
| 198 | String breakout = ifName.substring(0, ifName.lastIndexOf("/")); |
| 199 | annotations.set(BREAKOUT, breakout); |
| 200 | } |
| 201 | } |
| 202 | PortDescription desc = DefaultPortDescription.builder() |
| 203 | .withPortNumber(PortNumber.portNumber(ifCount)) |
| 204 | .isEnabled(state) |
| 205 | .type(portType).portSpeed(portSpeed).annotations(annotations.build()) |
| 206 | .build(); |
| 207 | ports.add(desc); |
| 208 | ifCount++; |
| 209 | } |
| 210 | } |
| 211 | } catch (Exception e) { |
| 212 | log.error("Exception occurred because of ", e); |
| 213 | } |
| 214 | |
| 215 | return ports; |
| 216 | } |
| 217 | |
| 218 | private boolean isValidPhysicalPort(String portName) { |
| 219 | return portName.startsWith(INTERFACE_ETHERNET); |
| 220 | } |
| 221 | |
| 222 | private boolean isValidVirtualPort(String portName) { |
| 223 | return portName.startsWith(INTERFACE_PORTCHANNEL); |
| 224 | } |
| 225 | |
| 226 | private boolean isPortValid(String portName) { |
| 227 | return isValidPhysicalPort(portName) || isValidVirtualPort(portName); |
| 228 | } |
| 229 | |
| 230 | private List<String> prepareModuleAnnotation(JsonNode modules, JsonNode macs) { |
| 231 | List<String> modulesInfo = new ArrayList<>(); |
| 232 | if (modules.getNodeType() == JsonNodeType.ARRAY) { |
| 233 | modules.forEach(module -> modulesInfo.add(getModuleInfo(module, macs))); |
| 234 | } else if (modules.getNodeType() == JsonNodeType.OBJECT) { |
| 235 | modulesInfo.add(getModuleInfo(modules, macs)); |
| 236 | } |
| 237 | return modulesInfo; |
| 238 | } |
| 239 | |
| 240 | private String getModuleInfo(JsonNode module, JsonNode moduleMac) { |
| 241 | int moduleId = module.get(MODULE_INTERFACE).asInt(); |
| 242 | String moduleModel = module.get(MODULE_MODEL).asText(); |
| 243 | String moduleSerial = getModuleSerial(moduleId, moduleMac); |
| 244 | return String.format(MODULE_ANNOTATION_FORMAT, moduleId, moduleModel, moduleSerial); |
| 245 | } |
| 246 | |
| 247 | private String getModuleSerial(int moduleId, JsonNode macs) { |
| 248 | if (macs.getNodeType() == JsonNodeType.ARRAY) { |
| 249 | Optional<JsonNode> serial = StreamSupport.stream(macs.spliterator(), false) |
| 250 | .filter(mac -> mac.get(MODULE_MAC).asInt() == moduleId) |
| 251 | .findAny(); |
| 252 | if (serial.isPresent()) { |
| 253 | return serial.get().get(MODULE_SERIAL).asText(); |
| 254 | } |
| 255 | } else if (macs.getNodeType() == JsonNodeType.OBJECT) { |
| 256 | if (macs.get(MODULE_MAC).asInt() == moduleId) { |
| 257 | return macs.get(MODULE_SERIAL).asText(); |
| 258 | } |
| 259 | } |
| 260 | return UNKNOWN; |
| 261 | } |
| 262 | |
| 263 | } |