blob: 58b99d697c42531e15844f2722c71b7627e61bf1 [file] [log] [blame]
Seyeon Jeong83e79862020-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;
Seyeon Jeong5018bdd2020-02-28 01:17:34 -080061import org.onosproject.t3.api.NibProfile;
Seyeon Jeong83e79862020-02-28 01:17:34 -080062import org.onosproject.t3.api.RouteNib;
Seyeon Jeong83e79862020-02-28 01:17:34 -080063import 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/**
Seyeon Jeong5018bdd2020-02-28 01:17:34 -080079 * T3 CLI command to load the NIB with dump files that represent snapshots of the network states.
Seyeon Jeong83e79862020-02-28 01:17:34 -080080 */
81@Service
82@Command(scope = "onos", name = "t3-load-file",
Seyeon Jeong5018bdd2020-02-28 01:17:34 -080083 description = "Load the NIB with onos-diagnostics dump files")
84public class TroubleshootLoadFileCommand
85 extends AbstractShellCommand implements NibLoader {
Seyeon Jeong83e79862020-02-28 01:17:34 -080086
87 private static final Logger log = getLogger(TroubleshootLoadFileCommand.class);
88
Seyeon Jeong83e79862020-02-28 01:17:34 -080089 @Argument(index = 0, name = "rootDir", description = "Specify the location of the directory " +
90 "where the dump files of a given instance have been extracted (e.g. /tmp/onos-diags/127.0.0.1)",
91 required = true, multiValued = false)
92 @Completion(PlaceholderCompleter.class)
93 String rootDir;
94
95 @Override
96 protected void doExecute() {
97
98 if (!rootDir.endsWith("/")) {
99 rootDir = rootDir + "/";
100 }
101 print("Load target files in: %s", rootDir);
102
103 try {
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800104 // names of files to read are defined in the onos-diagnostics script
105 loadFlowNib();
106 loadGroupNib();
107 loadLinkNib();
108 loadHostNib();
109 loadDeviceNib();
110 loadDriverNib();
111 loadMastershipNib();
112 loadEdgePortNib();
113 loadRouteNib();
114 loadNetworkConfigNib();
115 loadMulticastRouteNib();
116
Seyeon Jeong83e79862020-02-28 01:17:34 -0800117 } catch (IOException e) {
118 print("Error in creating NIB: %s", e.getMessage());
119 log.error("Nib creation error", e);
120 return;
121 }
122
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800123 // ensured no errors in file loading. so make them available officially
124 Lists.newArrayList(FlowNib.getInstance(), GroupNib.getInstance(), LinkNib.getInstance(),
125 HostNib.getInstance(), DeviceNib.getInstance(), DriverNib.getInstance(),
126 MastershipNib.getInstance(), EdgePortNib.getInstance(), RouteNib.getInstance(),
127 NetworkConfigNib.getInstance(), MulticastRouteNib.getInstance())
128 .forEach(nib -> {
129 // specify creation time and source which the NIB is filled with
130 nib.setProfile(new NibProfile(System.currentTimeMillis(), NibProfile.SourceType.FILE));
131 NibProfile profile = nib.getProfile();
132 print(String.format(
133 nib.getClass().getSimpleName() + " created %s from %s",
134 profile.date(), profile.sourceType()));
135 });
Seyeon Jeong83e79862020-02-28 01:17:34 -0800136 }
137
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800138 @Override
139 public void loadFlowNib() throws IOException {
140 InputStream stream = new FileInputStream(new File(rootDir + "flows.json"));
Seyeon Jeong83e79862020-02-28 01:17:34 -0800141 JsonNode jsonTree = mapper().readTree(stream);
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800142 Set<FlowEntry> flows = new HashSet<>();
Seyeon Jeong83e79862020-02-28 01:17:34 -0800143
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800144 List<ObjectNode> flowNodeList = new ArrayList<>();
145 jsonTree.forEach(jsonNode -> {
146 ArrayNode flowArrayNode = (ArrayNode) jsonNode.get("flows");
147 Lists.newArrayList(flowArrayNode.iterator())
148 .forEach(flowNode -> flowNodeList.add((ObjectNode) flowNode));
Seyeon Jeong83e79862020-02-28 01:17:34 -0800149 });
150
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800151 // TODO: future plan for the new APIs of the flow rule service that returns raw flows or normalized flows
152 flowNodeList.forEach(flowNode -> {
153 FlowEntry flow;
154 try {
155 flow = codec(FlowEntry.class).decode(flowNode, this);
156 } catch (IllegalArgumentException e) {
157 log.warn("T3 in offline mode ignores reading extension fields of this flow to avoid decoding error");
158 ObjectNode extensionRemoved = removeExtension(flowNode);
159 flow = codec(FlowEntry.class).decode(extensionRemoved, this);
160 }
161 flows.add(flow);
162 });
163
164 FlowNib flowNib = FlowNib.getInstance();
165 flowNib.setFlows(flows);
Seyeon Jeong83e79862020-02-28 01:17:34 -0800166
167 stream.close();
168 }
169
170 /**
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800171 * Remove JSON nodes for extension instructions of a flow.
172 * This effectively allows T3 in offline mode to ignore extension fields of flows to avoid "device not found" error.
173 * See decodeExtension() in {@link org.onosproject.codec.impl.DecodeInstructionCodecHelper}.
Seyeon Jeong83e79862020-02-28 01:17:34 -0800174 *
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800175 * @param flowNode the json node representing a flow
176 * @return json node with removed extensions
Seyeon Jeong83e79862020-02-28 01:17:34 -0800177 */
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800178 private ObjectNode removeExtension(ObjectNode flowNode) {
179
180 // TODO: decoding extension instructions of offline (dumped) flows is not supported by T3 for now
181 ArrayNode extensionRemoved = mapper().createArrayNode();
182 ArrayNode instructionArrayNode = (ArrayNode) flowNode.get("treatment").get("instructions");
183 instructionArrayNode.forEach(instrNode -> {
184 String instrType = instrNode.get("type").asText();
185 if (!instrType.equals(Instruction.Type.EXTENSION.name())) {
186 extensionRemoved.add(instrNode);
187 }
188 });
189 ((ObjectNode) flowNode.get("treatment")).replace("instructions", extensionRemoved);
190
191 return flowNode;
192 }
193
194 @Override
195 public void loadGroupNib() throws IOException {
196 InputStream stream = new FileInputStream(new File(rootDir + "groups.json"));
197 JsonNode jsonTree = mapper().readTree(stream);
198 Set<Group> groups = new HashSet<>();
199
200 // note: the parsing structure depends on GroupsListCommand
201 groups.addAll(codec(Group.class).decode((ArrayNode) jsonTree, this));
202
203 GroupNib groupNib = GroupNib.getInstance();
204 groupNib.setGroups(groups);
205
206 stream.close();
207 }
208
209 @Override
210 public void loadLinkNib() throws IOException {
211 InputStream stream = new FileInputStream(new File(rootDir + "links.json"));
212 JsonNode jsonTree = mapper().readTree(stream);
213 Set<Link> links = new HashSet<>();
214
215 // note: the parsing structure depends on LinksListCommand
216 links.addAll(codec(Link.class).decode((ArrayNode) jsonTree, this));
217
218 LinkNib linkNib = LinkNib.getInstance();
219 linkNib.setLinks(links);
220
221 stream.close();
222 }
223
224 @Override
225 public void loadHostNib() throws IOException {
226 InputStream stream = new FileInputStream(new File(rootDir + "hosts.json"));
227 JsonNode jsonTree = mapper().readTree(stream);
228 Set<Host> hosts = new HashSet<>();
229
230 // note: the parsing structure depends on HostsListCommand
231 hosts.addAll(codec(Host.class).decode((ArrayNode) jsonTree, this));
232
233 HostNib hostNib = HostNib.getInstance();
234 hostNib.setHosts(hosts);
235
236 stream.close();
237 }
238
239 @Override
240 public void loadDeviceNib() throws IOException {
241 InputStream stream = new FileInputStream(new File(rootDir + "ports.json"));
242 JsonNode jsonTree = mapper().readTree(stream);
243 Map<Device, Set<Port>> devicePortMap = new HashMap<>();
244
245 // note: the parsing structure depends on DevicePortsListCommand
246 jsonTree.forEach(jsonNode -> {
247 Device device = codec(Device.class).decode(
248 (ObjectNode) jsonNode.get("device"), this);
249 Set<Port> ports = new HashSet<>(codec(Port.class).decode(
250 (ArrayNode) jsonNode.get("ports"), this));
251 devicePortMap.put(device, ports);
252 });
253
254 DeviceNib deviceNib = DeviceNib.getInstance();
255 deviceNib.setDevicePortMap(devicePortMap);
256
257 stream.close();
258 }
259
260 @Override
261 public void loadDriverNib() throws IOException {
262 InputStream stream = new FileInputStream(new File(rootDir + "device-drivers.json"));
263 JsonNode jsonTree = mapper().readTree(stream);
264 Map<DeviceId, String> deviceDriverMap = new HashMap<>();
265
266 // note: the parsing structure depends on DeviceDriversCommand
267 jsonTree.fields().forEachRemaining(e -> {
268 deviceDriverMap.put(DeviceId.deviceId(e.getKey()), e.getValue().asText());
269 });
270
271 DriverNib driverNib = DriverNib.getInstance();
272 driverNib.setDeviceDriverMap(deviceDriverMap);
273
274 stream.close();
275 }
276
277 @Override
278 public void loadMastershipNib() throws IOException {
279 InputStream stream = new FileInputStream(new File(rootDir + "masters.json"));
280 JsonNode jsonTree = mapper().readTree(stream);
281 Map<DeviceId, NodeId> deviceMasterMap = new HashMap<>();
282
283 // note: the parsing structure depends on MastersListCommand
284 jsonTree.forEach(jsonNode -> {
285 ArrayNode devicesNode = ((ArrayNode) jsonNode.get("devices"));
286 devicesNode.forEach(deviceNode -> {
287 // a device is connected to only one master node at a time
288 deviceMasterMap.put(
289 DeviceId.deviceId(deviceNode.asText()),
290 NodeId.nodeId(jsonNode.get("id").asText()));
291 });
292 });
293
294 MastershipNib mastershipNib = MastershipNib.getInstance();
295 mastershipNib.setDeviceMasterMap(deviceMasterMap);
296
297 stream.close();
298 }
299
300 @Override
301 public void loadEdgePortNib() throws IOException {
302 InputStream stream = new FileInputStream(new File(rootDir + "edge-ports.json"));
303 JsonNode jsonTree = mapper().readTree(stream);
304 Map<DeviceId, Set<ConnectPoint>> edgePorts = new HashMap<>();
305
306 // note: the parsing structure depends on EdgePortsListCommand
307 jsonTree.forEach(jsonNode -> {
308 DeviceId deviceId = DeviceId.deviceId(jsonNode.fieldNames().next());
309 PortNumber portNumber = PortNumber.portNumber(
310 jsonNode.get(deviceId.toString()).asText());
311 if (!edgePorts.containsKey(deviceId)) {
312 edgePorts.put(deviceId, new HashSet<>());
313 }
314 edgePorts.get(deviceId).add(new ConnectPoint(deviceId, portNumber));
315 });
316
317 EdgePortNib edgePortNib = EdgePortNib.getInstance();
318 edgePortNib.setEdgePorts(edgePorts);
319
320 stream.close();
321 }
322
323 @Override
324 public void loadRouteNib() throws IOException {
325 InputStream stream = new FileInputStream(new File(rootDir + "routes.json"));
326 JsonNode jsonTree = mapper().readTree(stream);
327 Set<ResolvedRoute> routes = new HashSet<>();
328
329 // note: the parsing structure depends on RoutesListCommand
330 jsonTree.fields().forEachRemaining(e -> {
331 ArrayNode routesNode = (ArrayNode) e.getValue();
332 routesNode.forEach(routeNode -> {
333 Route route = codec(Route.class).decode((ObjectNode) routeNode, this);
334 // parse optional fields needed for ResolvedRoute
335 MacAddress nextHopMac = (null == routeNode.get("nextHopMac")) ?
336 null : MacAddress.valueOf(routeNode.get("nextHopMac").asText());
337 VlanId nextHopVlan = (null == routeNode.get("nextHopVlan")) ?
338 null : VlanId.vlanId(routeNode.get("nextHopVlan").asText());
339 routes.add(new ResolvedRoute(route, nextHopMac, nextHopVlan));
340 });
341 });
342
343 RouteNib routeNib = RouteNib.getInstance();
344 routeNib.setRoutes(routes);
345
346 stream.close();
347 }
348
349 @Override
350 public void loadNetworkConfigNib() throws IOException {
351 InputStream stream = new FileInputStream(new File(rootDir + "netcfg.json"));
Seyeon Jeong83e79862020-02-28 01:17:34 -0800352 JsonNode jsonTree = mapper().readTree(stream);
353 Map<String, Config> portConfigMap = new HashMap<>();
354 Map<String, Config> deviceConfigMap = new HashMap<>();
355
356 // note: the parsing structure depends on NetworkConfigCommand
357 // TODO: improve the code quality by referring to target json
358 jsonTree.fields().forEachRemaining(e -> {
359 if (e.getKey().equals("ports")) {
360 JsonNode portConfigsNode = e.getValue();
361 portConfigsNode.fields().forEachRemaining(portConfigEntry -> {
362 String key = portConfigEntry.getKey();
363 InterfaceConfig config = new InterfaceConfig();
364 config.init(ConnectPoint.fromString(key), "interfaces",
365 portConfigEntry.getValue().get("interfaces"), mapper(), null);
366 portConfigMap.put(key, config);
367 });
368 } else if (e.getKey().equals("devices")) {
369 JsonNode deviceConfigsNode = e.getValue();
370 deviceConfigsNode.fields().forEachRemaining(deviceConfigEntry -> {
371 String key = deviceConfigEntry.getKey();
372 SegmentRoutingDeviceConfig config = new SegmentRoutingDeviceConfig();
373 config.init(DeviceId.deviceId(key), "segmentrouting",
374 deviceConfigEntry.getValue().get("segmentrouting"), mapper(), null);
375 deviceConfigMap.put(key, config);
376 });
377 } else {
378 log.warn("Given configuration subject {} is not supported", e.getKey());
379 }
380 });
381
382 NetworkConfigNib networkConfigNib = NetworkConfigNib.getInstance();
383 networkConfigNib.setPortConfigMap(portConfigMap);
384 networkConfigNib.setDeviceConfigMap(deviceConfigMap);
Seyeon Jeong83e79862020-02-28 01:17:34 -0800385
386 stream.close();
387 }
388
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800389 @Override
390 public void loadMulticastRouteNib() throws IOException {
391 InputStream stream = new FileInputStream(new File(rootDir + "mcast-host-show.json"));
Seyeon Jeong83e79862020-02-28 01:17:34 -0800392 JsonNode jsonTree = mapper().readTree(stream);
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800393 Map<McastRoute, McastRouteData> mcastRoutes = new HashMap<>();
Seyeon Jeong83e79862020-02-28 01:17:34 -0800394
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800395 // note: the parsing structure depends on McastShowHostCommand
396 jsonTree.forEach(mcastRouteNode -> {
397 // use McastHostRouteCodec to decode McastRoute
398 McastRoute mcastRoute = codec(McastRoute.class)
399 .decode((ObjectNode) mcastRouteNode, this);
400 // create McastRouteData that stores sources and sinks of McastRoute
401 McastRouteData mcastRouteData = McastRouteData.empty();
402 if (mcastRouteNode.get("sources") != null) {
403 JsonNode sourcesNode = mcastRouteNode.get("sources");
404 sourcesNode.fields().forEachRemaining(sourceEntry -> {
405 HostId hostId = HostId.hostId(sourceEntry.getKey());
406 Set<ConnectPoint> sources = mapper().convertValue(
407 sourceEntry.getValue(), new TypeReference<Set<ConnectPoint>>() { });
408 mcastRouteData.addSources(hostId, sources);
409 });
Seyeon Jeong83e79862020-02-28 01:17:34 -0800410 }
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800411 if (mcastRouteNode.get("sinks") != null) {
412 JsonNode sinksNode = mcastRouteNode.get("sinks");
413 sinksNode.fields().forEachRemaining(sinkEntry -> {
414 HostId hostId = HostId.hostId(sinkEntry.getKey());
415 Set<ConnectPoint> sinks = mapper().convertValue(
416 sinkEntry.getValue(), new TypeReference<Set<ConnectPoint>>() { });
417 mcastRouteData.addSinks(hostId, sinks);
418 });
Seyeon Jeong83e79862020-02-28 01:17:34 -0800419 }
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800420 mcastRoutes.put(mcastRoute, mcastRouteData);
Seyeon Jeong83e79862020-02-28 01:17:34 -0800421 });
422
Seyeon Jeong5018bdd2020-02-28 01:17:34 -0800423 MulticastRouteNib mcastRouteNib = MulticastRouteNib.getInstance();
424 mcastRouteNib.setMcastRoutes(mcastRoutes);
Seyeon Jeong83e79862020-02-28 01:17:34 -0800425
426 stream.close();
427 }
428
Seyeon Jeong83e79862020-02-28 01:17:34 -0800429}