blob: 36c22d37e90cc46ecad24dd602fa3b6ca49bc1f6 [file] [log] [blame]
Seyeon Jeong357bcec2020-02-28 01:17:34 -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.t3.cli;
18
19import com.fasterxml.jackson.core.type.TypeReference;
20import com.fasterxml.jackson.databind.JsonNode;
21import com.fasterxml.jackson.databind.node.ArrayNode;
22import com.fasterxml.jackson.databind.node.ObjectNode;
23import com.google.common.collect.Lists;
24import org.apache.karaf.shell.api.action.Argument;
25import org.apache.karaf.shell.api.action.Command;
26import org.apache.karaf.shell.api.action.Completion;
27import org.apache.karaf.shell.api.action.lifecycle.Service;
28import org.onlab.packet.MacAddress;
29import org.onlab.packet.VlanId;
30import org.onosproject.cli.AbstractShellCommand;
31import org.onosproject.cli.PlaceholderCompleter;
32import org.onosproject.cluster.NodeId;
33import org.onosproject.mcast.api.McastRoute;
34import org.onosproject.mcast.api.McastRouteData;
35import org.onosproject.net.ConnectPoint;
36import org.onosproject.net.Device;
37import org.onosproject.net.DeviceId;
38import org.onosproject.net.Host;
39import org.onosproject.net.HostId;
40import org.onosproject.net.Link;
41import org.onosproject.net.Port;
42import org.onosproject.net.PortNumber;
43import org.onosproject.net.config.Config;
44import org.onosproject.net.config.basics.InterfaceConfig;
45import org.onosproject.net.flow.FlowEntry;
46import org.onosproject.net.flow.instructions.Instruction;
47import org.onosproject.net.group.Group;
48import org.onosproject.routeservice.ResolvedRoute;
49import org.onosproject.routeservice.Route;
50import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
51import org.onosproject.t3.api.DeviceNib;
52import org.onosproject.t3.api.DriverNib;
53import org.onosproject.t3.api.EdgePortNib;
54import org.onosproject.t3.api.FlowNib;
55import org.onosproject.t3.api.GroupNib;
56import org.onosproject.t3.api.HostNib;
57import org.onosproject.t3.api.LinkNib;
58import org.onosproject.t3.api.MastershipNib;
59import org.onosproject.t3.api.MulticastRouteNib;
60import org.onosproject.t3.api.NetworkConfigNib;
61import org.onosproject.t3.api.RouteNib;
62import org.onosproject.t3.api.TroubleshootService;
63import org.slf4j.Logger;
64
65import java.io.File;
66import java.io.FileInputStream;
67import java.io.IOException;
68import java.io.InputStream;
69import java.util.ArrayList;
70import java.util.HashMap;
71import java.util.HashSet;
72import java.util.List;
73import java.util.Map;
74import java.util.Set;
75
76import static org.slf4j.LoggerFactory.getLogger;
77
78/**
79 * Reads network states from JSON files of onos-diagnostics
80 * and sets them to corresponding Network Information Bases (NIBs).
81 */
82@Service
83@Command(scope = "onos", name = "t3-load-file",
84 description = "Command to create a snapshot (cache) of network states called Network Information Bases (NIBs) "
85 + "from onos-diagnostics dump files")
86public class TroubleshootLoadFileCommand extends AbstractShellCommand {
87
88 private static final Logger log = getLogger(TroubleshootLoadFileCommand.class);
89
90 public static final String ERROR_NULL = "Some NIBs are not ready to trace. " +
91 "Make sure t3-troubleshoot-load-file is done correctly";
92
93 @Argument(index = 0, name = "rootDir", description = "Specify the location of the directory " +
94 "where the dump files of a given instance have been extracted (e.g. /tmp/onos-diags/127.0.0.1)",
95 required = true, multiValued = false)
96 @Completion(PlaceholderCompleter.class)
97 String rootDir;
98
99 @Override
100 protected void doExecute() {
101
102 if (!rootDir.endsWith("/")) {
103 rootDir = rootDir + "/";
104 }
105 print("Load target files in: %s", rootDir);
106
107 try {
108 // fills each NIB (singleton) instance with the contents of the corresponding dump file
109 // the file names are defined in the onos-diagnostics script
110 createFlowNib(rootDir + "flows.json");
111 createGroupNib(rootDir + "groups.json");
112 createLinkNib(rootDir + "links.json");
113 createHostNib(rootDir + "hosts.json");
114 createDeviceNib(rootDir + "ports.json");
115 createDriverNib(rootDir + "device-drivers.json");
116 createMastershipNib(rootDir + "masters.json");
117 createEdgePortNib(rootDir + "edge-ports.json");
118 createRouteNib(rootDir + "routes.json");
119 createNetworkConfigNib(rootDir + "netcfg.json");
120 createMulticastRouteNib(rootDir + "mcast-host-show.json");
121 } catch (IOException e) {
122 print("Error in creating NIB: %s", e.getMessage());
123 log.error("Nib creation error", e);
124 return;
125 }
126
127 TroubleshootService service = get(TroubleshootService.class);
128 service.applyNibs();
129 if (service.checkNibsUnavailable()) {
130 print(ERROR_NULL);
131 return;
132 }
133 }
134
135 /**
136 * Fetches multicast route-related information and creates the multicast route NIB.
137 *
138 * @param fileName absolute path of JSON file to read
139 */
140 private void createMulticastRouteNib(String fileName) throws IOException {
141 InputStream stream = new FileInputStream(new File(fileName));
142 JsonNode jsonTree = mapper().readTree(stream);
143 Map<McastRoute, McastRouteData> mcastRoutes = new HashMap<>();
144
145 // note: the parsing structure depends on McastShowHostCommand
146 jsonTree.forEach(mcastRouteNode -> {
147 // use McastHostRouteCodec to decode McastRoute
148 McastRoute mcastRoute = codec(McastRoute.class)
149 .decode((ObjectNode) mcastRouteNode, this);
150 // create McastRouteData that stores sources and sinks of McastRoute
151 McastRouteData mcastRouteData = McastRouteData.empty();
152 if (mcastRouteNode.get("sources") != null) {
153 JsonNode sourcesNode = mcastRouteNode.get("sources");
154 sourcesNode.fields().forEachRemaining(sourceEntry -> {
155 HostId hostId = HostId.hostId(sourceEntry.getKey());
156 Set<ConnectPoint> sources = mapper().convertValue(
157 sourceEntry.getValue(), new TypeReference<Set<ConnectPoint>>() { });
158 mcastRouteData.addSources(hostId, sources);
159 });
160 }
161 if (mcastRouteNode.get("sinks") != null) {
162 JsonNode sinksNode = mcastRouteNode.get("sinks");
163 sinksNode.fields().forEachRemaining(sinkEntry -> {
164 HostId hostId = HostId.hostId(sinkEntry.getKey());
165 Set<ConnectPoint> sinks = mapper().convertValue(
166 sinkEntry.getValue(), new TypeReference<Set<ConnectPoint>>() { });
167 mcastRouteData.addSinks(hostId, sinks);
168 });
169 }
170 mcastRoutes.put(mcastRoute, mcastRouteData);
171 });
172
173 MulticastRouteNib mcastRouteNib = MulticastRouteNib.getInstance();
174 mcastRouteNib.setMcastRoutes(mcastRoutes);
175 print("the number of mcast routes: %d", mcastRouteNib.getMcastRoutes().size());
176
177 stream.close();
178 }
179
180 /**
181 * Fetches network config-related information and creates the network config NIB.
182 *
183 * @param fileName absolute path of JSON file to read
184 */
185 private void createNetworkConfigNib(String fileName) throws IOException {
186 InputStream stream = new FileInputStream(new File(fileName));
187 JsonNode jsonTree = mapper().readTree(stream);
188 Map<String, Config> portConfigMap = new HashMap<>();
189 Map<String, Config> deviceConfigMap = new HashMap<>();
190
191 // note: the parsing structure depends on NetworkConfigCommand
192 // TODO: improve the code quality by referring to target json
193 jsonTree.fields().forEachRemaining(e -> {
194 if (e.getKey().equals("ports")) {
195 JsonNode portConfigsNode = e.getValue();
196 portConfigsNode.fields().forEachRemaining(portConfigEntry -> {
197 String key = portConfigEntry.getKey();
198 InterfaceConfig config = new InterfaceConfig();
199 config.init(ConnectPoint.fromString(key), "interfaces",
200 portConfigEntry.getValue().get("interfaces"), mapper(), null);
201 portConfigMap.put(key, config);
202 });
203 } else if (e.getKey().equals("devices")) {
204 JsonNode deviceConfigsNode = e.getValue();
205 deviceConfigsNode.fields().forEachRemaining(deviceConfigEntry -> {
206 String key = deviceConfigEntry.getKey();
207 SegmentRoutingDeviceConfig config = new SegmentRoutingDeviceConfig();
208 config.init(DeviceId.deviceId(key), "segmentrouting",
209 deviceConfigEntry.getValue().get("segmentrouting"), mapper(), null);
210 deviceConfigMap.put(key, config);
211 });
212 } else {
213 log.warn("Given configuration subject {} is not supported", e.getKey());
214 }
215 });
216
217 NetworkConfigNib networkConfigNib = NetworkConfigNib.getInstance();
218 networkConfigNib.setPortConfigMap(portConfigMap);
219 networkConfigNib.setDeviceConfigMap(deviceConfigMap);
220 print("the number of network configurations: %d",
221 networkConfigNib.getPortConfigMap().size() + networkConfigNib.getDeviceConfigMap().size());
222
223 stream.close();
224 }
225
226 /**
227 * Fetches route-related information and creates the route NIB.
228 *
229 * @param fileName absolute path of JSON file to read
230 */
231 private void createRouteNib(String fileName) throws IOException {
232 InputStream stream = new FileInputStream(new File(fileName));
233 JsonNode jsonTree = mapper().readTree(stream);
234 Set<ResolvedRoute> routes = new HashSet<>();
235
236 // note: the parsing structure depends on RoutesListCommand
237 jsonTree.fields().forEachRemaining(e -> {
238 ArrayNode routesNode = (ArrayNode) e.getValue();
239 routesNode.forEach(routeNode -> {
240 Route route = codec(Route.class).decode((ObjectNode) routeNode, this);
241 // parse optional fields needed for ResolvedRoute
242 MacAddress nextHopMac = (null == routeNode.get("nextHopMac")) ?
243 null : MacAddress.valueOf(routeNode.get("nextHopMac").asText());
244 VlanId nextHopVlan = (null == routeNode.get("nextHopVlan")) ?
245 null : VlanId.vlanId(routeNode.get("nextHopVlan").asText());
246 routes.add(new ResolvedRoute(route, nextHopMac, nextHopVlan));
247 });
248 });
249
250 RouteNib routeNib = RouteNib.getInstance();
251 routeNib.setRoutes(routes);
252 print("the number of routes: %d", routeNib.getRoutes().size());
253
254 stream.close();
255 }
256
257 /**
258 * Fetches edge port-related information and creates the edge port NIB.
259 *
260 * @param fileName absolute path of JSON file to read
261 */
262 private void createEdgePortNib(String fileName) throws IOException {
263 InputStream stream = new FileInputStream(new File(fileName));
264 JsonNode jsonTree = mapper().readTree(stream);
265 Map<DeviceId, Set<ConnectPoint>> edgePorts = new HashMap<>();
266
267 // note: the parsing structure depends on EdgePortsListCommand
268 jsonTree.forEach(jsonNode -> {
269 DeviceId deviceId = DeviceId.deviceId(jsonNode.fieldNames().next());
270 PortNumber portNumber = PortNumber.portNumber(
271 jsonNode.get(deviceId.toString()).asText());
272 if (!edgePorts.containsKey(deviceId)) {
273 edgePorts.put(deviceId, new HashSet<>());
274 }
275 edgePorts.get(deviceId).add(new ConnectPoint(deviceId, portNumber));
276 });
277
278 EdgePortNib edgePortNib = EdgePortNib.getInstance();
279 edgePortNib.setEdgePorts(edgePorts);
280 print("the number of edge ports: %d", edgePortNib.getEdgePorts().size());
281
282 stream.close();
283 }
284
285 /**
286 * Fetches mastership-related information and creates the mastership NIB.
287 *
288 * @param fileName absolute path of JSON file to read
289 */
290 private void createMastershipNib(String fileName) throws IOException {
291 InputStream stream = new FileInputStream(new File(fileName));
292 JsonNode jsonTree = mapper().readTree(stream);
293 Map<DeviceId, NodeId> deviceMasterMap = new HashMap<>();
294
295 // note: the parsing structure depends on MastersListCommand
296 jsonTree.forEach(jsonNode -> {
297 ArrayNode devicesNode = ((ArrayNode) jsonNode.get("devices"));
298 devicesNode.forEach(deviceNode -> {
299 // a device is connected to only one master node at a time
300 deviceMasterMap.put(
301 DeviceId.deviceId(deviceNode.asText()),
302 NodeId.nodeId(jsonNode.get("id").asText()));
303 });
304 });
305
306 MastershipNib mastershipNib = MastershipNib.getInstance();
307 mastershipNib.setDeviceMasterMap(deviceMasterMap);
308 print("the number of device-node mappings: %d", mastershipNib.getDeviceMasterMap().size());
309
310 stream.close();
311 }
312
313 /**
314 * Fetches driver-related information and creates the driver NIB.
315 *
316 * @param fileName absolute path of JSON file to read
317 */
318 private void createDriverNib(String fileName) throws IOException {
319 InputStream stream = new FileInputStream(new File(fileName));
320 JsonNode jsonTree = mapper().readTree(stream);
321 Map<DeviceId, String> deviceDriverMap = new HashMap<>();
322
323 // note: the parsing structure depends on DeviceDriversCommand
324 jsonTree.fields().forEachRemaining(e -> {
325 deviceDriverMap.put(DeviceId.deviceId(e.getKey()), e.getValue().asText());
326 });
327
328 DriverNib driverNib = DriverNib.getInstance();
329 driverNib.setDeviceDriverMap(deviceDriverMap);
330 print("the number of device-driver mappings: %d", deviceDriverMap.size());
331
332 stream.close();
333 }
334
335 /**
336 * Fetches device-related information and creates the device NIB.
337 *
338 * @param fileName absolute path of JSON file to read
339 */
340 private void createDeviceNib(String fileName) throws IOException {
341 InputStream stream = new FileInputStream(new File(fileName));
342 JsonNode jsonTree = mapper().readTree(stream);
343 Map<Device, Set<Port>> devicePortMap = new HashMap<>();
344
345 // note: the parsing structure depends on DevicePortsListCommand
346 jsonTree.forEach(jsonNode -> {
347 Device device = codec(Device.class).decode(
348 (ObjectNode) jsonNode.get("device"), this);
349 Set<Port> ports = new HashSet<>(codec(Port.class).decode(
350 (ArrayNode) jsonNode.get("ports"), this));
351 devicePortMap.put(device, ports);
352 });
353
354 DeviceNib deviceNib = DeviceNib.getInstance();
355 deviceNib.setDevicePortMap(devicePortMap);
356 print("the number of devices: %d", deviceNib.getDevicePortMap().size());
357
358 stream.close();
359 }
360
361 /**
362 * Fetches host-related information and creates the host NIB.
363 *
364 * @param fileName absolute path of JSON file to read
365 */
366 private void createHostNib(String fileName) throws IOException {
367 InputStream stream = new FileInputStream(new File(fileName));
368 JsonNode jsonTree = mapper().readTree(stream);
369 Set<Host> hosts = new HashSet<>();
370
371 // note: the parsing structure depends on HostsListCommand
372 hosts.addAll(codec(Host.class).decode((ArrayNode) jsonTree, this));
373
374 HostNib hostNib = HostNib.getInstance();
375 hostNib.setHosts(hosts);
376 print("the number of hosts: %d", hostNib.getHosts().size());
377
378 stream.close();
379 }
380
381 /**
382 * Fetches link-related information and creates the link NIB.
383 *
384 * @param fileName absolute path of JSON file to read
385 */
386 private void createLinkNib(String fileName) throws IOException {
387 InputStream stream = new FileInputStream(new File(fileName));
388 JsonNode jsonTree = mapper().readTree(stream);
389 Set<Link> links = new HashSet<>();
390
391 // note: the parsing structure depends on LinksListCommand
392 links.addAll(codec(Link.class).decode((ArrayNode) jsonTree, this));
393
394 LinkNib linkNib = LinkNib.getInstance();
395 linkNib.setLinks(links);
396 print("the number of links: %d", linkNib.getLinks().size());
397
398 stream.close();
399 }
400
401 /**
402 * Fetches group-related information and creates the group NIB.
403 *
404 * @param fileName absolute path of JSON file to read
405 */
406 private void createGroupNib(String fileName) throws IOException {
407 InputStream stream = new FileInputStream(new File(fileName));
408 JsonNode jsonTree = mapper().readTree(stream);
409 Set<Group> groups = new HashSet<>();
410
411 // note: the parsing structure depends on GroupsListCommand
412 groups.addAll(codec(Group.class).decode((ArrayNode) jsonTree, this));
413
414 GroupNib groupNib = GroupNib.getInstance();
415 groupNib.setGroups(groups);
416 print("the number of groups: %d", groupNib.getGroups().size());
417
418 stream.close();
419 }
420
421 /**
422 * Fetches flow-related information and creates the flow NIB.
423 *
424 * @param fileName absolute path of JSON file to read
425 */
426 private void createFlowNib(String fileName) throws IOException {
427 InputStream stream = new FileInputStream(new File(fileName));
428 JsonNode jsonTree = mapper().readTree(stream);
429 Set<FlowEntry> flows = new HashSet<>();
430
431 List<ObjectNode> flowNodeList = new ArrayList<>();
432 jsonTree.forEach(jsonNode -> {
433 ArrayNode flowArrayNode = (ArrayNode) jsonNode.get("flows");
434 Lists.newArrayList(flowArrayNode.iterator())
435 .forEach(flowNode -> flowNodeList.add((ObjectNode) flowNode));
436 });
437
438 // TODO: future plan for the new APIs of the flow rule service that returns raw flows or normalized flows
439 flowNodeList.forEach(flowNode -> {
440 FlowEntry flow;
441 try {
442 flow = codec(FlowEntry.class).decode(flowNode, this);
443 } catch (IllegalArgumentException e) {
444 log.warn("T3 in offline mode ignores reading extension fields of this flow to avoid decoding error");
445 ObjectNode extensionRemoved = removeExtension(flowNode);
446 flow = codec(FlowEntry.class).decode(extensionRemoved, this);
447 }
448 flows.add(flow);
449 });
450
451 FlowNib flowNib = FlowNib.getInstance();
452 flowNib.setFlows(flows);
453 print("the number of flows: %d", flowNib.getFlows().size());
454
455 stream.close();
456 }
457
458 /**
459 * Remove JSON nodes for extension instructions of a flow.
460 * This effectively allows T3 in offline mode to ignore extension fields of flows to avoid "device not found" error.
461 * See decodeExtension() in {@link org.onosproject.codec.impl.DecodeInstructionCodecHelper}.
462 *
463 * @param flowNode the json node representing a flow
464 * @return json node with removed extensions
465 */
466 private ObjectNode removeExtension(ObjectNode flowNode) {
467
468 // TODO: decoding extension instructions of offline (dumped) flows is not supported by T3 for now
469 ArrayNode extensionRemoved = mapper().createArrayNode();
470 ArrayNode instructionArrayNode = (ArrayNode) flowNode.get("treatment").get("instructions");
471 instructionArrayNode.forEach(instrNode -> {
472 String instrType = instrNode.get("type").asText();
473 if (!instrType.equals(Instruction.Type.EXTENSION.name())) {
474 extensionRemoved.add(instrNode);
475 }
476 });
477 ((ObjectNode) flowNode.get("treatment")).replace("instructions", extensionRemoved);
478
479 return flowNode;
480 }
481
482}