blob: 484185f217a5585035024bea63e0f42481d160e4 [file] [log] [blame]
khenaidoofdbad6e2018-11-06 22:26:38 -05001#!/usr/bin/env python
2#
3# Copyright 2016 the original author or authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""
19Device level CLI commands
20"""
21from optparse import make_option
22from cmd2 import Cmd, options
23from simplejson import dumps
24
25from table import print_pb_as_table, print_pb_list_as_table
26from utils import print_flows, pb2dict, enum2name
khenaidoofdbad6e2018-11-06 22:26:38 -050027
Arun Aroraed4b7602019-04-02 18:42:37 +000028from voltha_protos import voltha_pb2, common_pb2
khenaidoofdbad6e2018-11-06 22:26:38 -050029import sys
30import json
31from google.protobuf.json_format import MessageToDict
32
33# Since proto3 won't send fields that are set to 0/false/"" any object that
34# might have those values set in them needs to be replicated here such that the
35# fields can be adequately
36
37
38class DeviceCli(Cmd):
39
40 def __init__(self, device_id, get_stub):
41 Cmd.__init__(self)
42 self.get_stub = get_stub
43 self.device_id = device_id
44 self.prompt = '(' + self.colorize(
45 self.colorize('device {}'.format(device_id), 'red'), 'bold') + ') '
46 self.pm_config_last = None
47 self.pm_config_dirty = False
48
49 def cmdloop(self):
50 self._cmdloop()
51
52 def get_device(self, depth=0):
53 stub = self.get_stub()
54 res = stub.GetDevice(voltha_pb2.ID(id=self.device_id),
55 metadata=(('get-depth', str(depth)), ))
56 return res
57
58 do_exit = Cmd.do_quit
59
60 def do_quit(self, line):
61 if self.pm_config_dirty:
62 self.poutput("Uncommited changes for " + \
63 self.colorize(
64 self.colorize("perf_config,", "blue"),
65 "bold") + " please either " + self.colorize(
66 self.colorize("commit", "blue"), "bold") + \
67 " or " + self.colorize(
68 self.colorize("reset", "blue"), "bold") + \
69 " your changes using " + \
70 self.colorize(
71 self.colorize("perf_config", "blue"), "bold"))
72 return False
73 else:
74 return self._STOP_AND_EXIT
75
76 def do_show(self, line):
77 """Show detailed device information"""
78 print_pb_as_table('Device {}'.format(self.device_id),
79 self.get_device(depth=-1))
80
81 def do_ports(self, line):
82 """Show ports of device"""
83 device = self.get_device(depth=-1)
84 omit_fields = {
85 }
86 print_pb_list_as_table('Device ports:', device.ports,
87 omit_fields, self.poutput)
88
89 def complete_perf_config(self, text, line, begidx, endidx):
90 sub_cmds = {"show", "set", "commit", "reset"}
91 sub_opts = {"-f", "-e", "-d", "-o"}
92 # Help the interpreter complete the paramters.
93 completions = []
94 if not self.pm_config_last:
95 device = self.get_device(depth=-1)
96 self.pm_config_last = device.pm_configs
97 m_names = [d.name for d in self.pm_config_last.metrics]
98 cur_cmd = line.strip().split(" ")
99 try:
100 if not text and len(cur_cmd) == 1:
101 completions = ("show", "set", "commit", "reset")
102 elif len(cur_cmd) == 2:
103 if "set" == cur_cmd[1]:
104 completions = [d for d in sub_opts]
105 else:
106 completions = [d for d in sub_cmds if d.startswith(text)]
107 elif len(cur_cmd) > 2 and cur_cmd[1] == "set":
108 if cur_cmd[len(cur_cmd)-1] == "-":
109 completions = [list(d)[1] for d in sub_opts]
110 elif cur_cmd[len(cur_cmd)-1] == "-f":
111 completions = ("\255","Please enter a sampling frequency in 10ths of a second")
112 elif cur_cmd[len(cur_cmd)-2] == "-f":
113 completions = [d for d in sub_opts]
114 elif cur_cmd[len(cur_cmd)-1] in {"-e","-d","-o"}:
115 if self.pm_config_last.grouped:
116 pass
117 else:
118 completions = [d.name for d in self.pm_config_last.metrics]
119 elif cur_cmd[len(cur_cmd)-2] in {"-e","-d"}:
120 if text and text not in m_names:
121 completions = [d for d in m_names if d.startswith(text)]
122 else:
123 completions = [d for d in sub_opts]
124 elif cur_cmd[len(cur_cmd)-2] == "-o":
125 if cur_cmd[len(cur_cmd)-1] in [d.name for d in self.pm_config_last.metrics]:
126 completions = ("\255","Please enter a sampling frequency in 10ths of a second")
127 else:
128 completions = [d for d in m_names if d.startswith(text)]
129 elif cur_cmd[len(cur_cmd)-3] == "-o":
130 completions = [d for d in sub_opts]
131 except:
132 e = sys.exc_info()
133 print(e)
134 return completions
135
136
137 def help_perf_config(self):
138 self.poutput(
139'''
140perf_config [show | set | commit | reset] [-f <default frequency>] [{-e <metric/group
141 name>}] [{-d <metric/group name>}] [{-o <metric/group name> <override
142 frequency>}]
143
144show: displays the performance configuration of the device
145set: changes the parameters specified with -e, -d, and -o
146reset: reverts any changes made since the last commit
147commit: commits any changes made which applies them to the device.
148
149-e: enable collection of the specified metric, more than one -e may be
150 specified.
151-d: disable collection of the specified metric, more than on -d may be
152 specified.
153-o: override the collection frequency of the specified metric, more than one -o
154 may be specified. Note that -o isn't valid unless
155 frequency_override is set to True for the device.
156
157Changes made by set are held locally until a commit or reset command is issued.
158A commit command will write the configuration to the device and it takes effect
159immediately. The reset command will undo any changes since the start of the
160device session.
161
162If grouped is true then the -d, -e and -o commands refer to groups and not
163individual metrics.
164'''
165 )
166
167 @options([
168 make_option('-f', '--default_freq', action="store", dest='default_freq',
169 type='long', default=None),
170 make_option('-e', '--enable', action='append', dest='enable',
171 default=None),
172 make_option('-d', '--disable', action='append', dest='disable',
173 default=None),
174 make_option('-o', '--override', action='append', dest='override',
175 nargs=2, default=None, type='string'),
176 ])
177 def do_perf_config(self, line, opts):
178 """Show and set the performance monitoring configuration of the device"""
179
180 device = self.get_device(depth=-1)
181 if not self.pm_config_last:
182 self.pm_config_last = device.pm_configs
183
184 # Ensure that a valid sub-command was provided
185 if line.strip() not in {"set", "show", "commit", "reset", ""}:
186 self.poutput(self.colorize('Error: ', 'red') +
187 self.colorize(self.colorize(line.strip(), 'blue'),
188 'bold') + ' is not recognized')
189 return
190
191 # Ensure no options are provided when requesting to view the config
192 if line.strip() == "show" or line.strip() == "":
193 if opts.default_freq or opts.enable or opts.disable:
194 self.poutput(opts.disable)
195 self.poutput(self.colorize('Error: ', 'red') + 'use ' +
196 self.colorize(self.colorize('"set"', 'blue'),
197 'bold') + ' to change settings')
198 return
199
200 if line.strip() == "set": # Set the supplied values
201 metric_list = set()
202 if opts.enable is not None:
203 metric_list |= {metric for metric in opts.enable}
204 if opts.disable is not None:
205 metric_list |= {metric for metric in opts.disable}
206 if opts.override is not None:
207 metric_list |= {metric for metric, _ in opts.override}
208
209 # The default frequency
210 if opts.default_freq:
211 self.pm_config_last.default_freq = opts.default_freq
212 self.pm_config_dirty = True
213
214 # Field or group visibility
215 if self.pm_config_last.grouped:
216 for g in self.pm_config_last.groups:
217 if opts.enable:
218 if g.group_name in opts.enable:
219 g.enabled = True
220 self.pm_config_dirty = True
221 metric_list.discard(g.group_name)
222 for g in self.pm_config_last.groups:
223 if opts.disable:
224 if g.group_name in opts.disable:
225 g.enabled = False
226 self.pm_config_dirty = True
227 metric_list.discard(g.group_name)
228 else:
229 for m in self.pm_config_last.metrics:
230 if opts.enable:
231 if m.name in opts.enable:
232 m.enabled = True
233 self.pm_config_dirty = True
234 metric_list.discard(m.name)
235 for m in self.pm_config_last.metrics:
236 if opts.disable:
237 if m.name in opts.disable:
238 m.enabled = False
239 self.pm_config_dirty = True
240 metric_list.discard(m.name)
241
242 # Frequency overrides.
243 if opts.override:
244 if self.pm_config_last.freq_override:
245 oo = dict()
246 for o in opts.override:
247 oo[o[0]] = o[1]
248 if self.pm_config_last.grouped:
249 for g in self.pm_config_last.groups:
250 if g.group_name in oo:
251 try:
252 g.group_freq = int(oo[g.group_name])
253 except ValueError:
254 self.poutput(self.colorize('Warning: ',
255 'yellow') +
256 self.colorize(oo[g.group_name],
257 'blue') +
258 " is not an integer... ignored")
259 del oo[g.group_name]
260 self.pm_config_dirty = True
261 metric_list.discard(g.group_name)
262 else:
263 for m in self.pm_config_last.metrics:
264 if m.name in oo:
265 try:
266 m.sample_freq = int(oo[m.name])
267 except ValueError:
268 self.poutput(self.colorize('Warning: ',
269 'yellow') +
270 self.colorize(oo[m.name],
271 'blue') +
272 " is not an integer... ignored")
273 del oo[m.name]
274 self.pm_config_dirty = True
275 metric_list.discard(m.name)
276
277 # If there's anything left the input was typoed
278 if self.pm_config_last.grouped:
279 field = 'group'
280 else:
281 field = 'metric'
282 for o in oo:
283 self.poutput(self.colorize('Warning: ', 'yellow') +
284 'the parameter' + ' ' +
285 self.colorize(o, 'blue') + ' is not ' +
286 'a ' + field + ' name... ignored')
287 if oo:
288 return
289
290 else: # Frequency overrides not enabled
291 self.poutput(self.colorize('Error: ', 'red') +
292 'Individual overrides are only ' +
293 'supported if ' +
294 self.colorize('freq_override', 'blue') +
295 ' is set to ' + self.colorize('True', 'blue'))
296 return
297
298 if len(metric_list):
299 metric_name_list = ", ".join(str(metric) for metric in metric_list)
300 self.poutput(self.colorize('Error: ', 'red') +
301 'Metric/Metric Group{} '.format('s' if len(metric_list) > 1 else '') +
302 self.colorize(metric_name_list, 'blue') +
303 ' {} not found'.format('were' if len(metric_list) > 1 else 'was'))
304 return
305
306 self.poutput("Success")
307 return
308
309 elif line.strip() == "commit" and self.pm_config_dirty:
310 stub = self.get_stub()
311 stub.UpdateDevicePmConfigs(self.pm_config_last)
312 self.pm_config_last = self.get_device(depth=-1).pm_configs
313 self.pm_config_dirty = False
314
315 elif line.strip() == "reset" and self.pm_config_dirty:
316 self.pm_config_last = self.get_device(depth=-1).pm_configs
317 self.pm_config_dirty = False
318
319 omit_fields = {'groups', 'metrics', 'id'}
320 print_pb_as_table('PM Config:', self.pm_config_last, omit_fields,
321 self.poutput,show_nulls=True)
322 if self.pm_config_last.grouped:
323 #self.poutput("Supported metric groups:")
324 for g in self.pm_config_last.groups:
325 if self.pm_config_last.freq_override:
326 omit_fields = {'metrics'}
327 else:
328 omit_fields = {'group_freq','metrics'}
329 print_pb_as_table('', g, omit_fields, self.poutput,
330 show_nulls=True)
331 if g.enabled:
332 state = 'enabled'
333 else:
334 state = 'disabled'
335 print_pb_list_as_table(
336 'Metric group {} is {}'.format(g.group_name,state),
337 g.metrics, {'enabled', 'sample_freq'}, self.poutput,
338 dividers=100, show_nulls=True)
339 else:
340 if self.pm_config_last.freq_override:
341 omit_fields = {}
342 else:
343 omit_fields = {'sample_freq'}
344 print_pb_list_as_table('Supported metrics:', self.pm_config_last.metrics,
345 omit_fields, self.poutput, dividers=100,
346 show_nulls=True)
347
348 def do_flows(self, line):
349 """Show flow table for device"""
350 device = pb2dict(self.get_device(-1))
351 print_flows(
352 'Device',
353 self.device_id,
354 type=device['type'],
355 flows=device['flows']['items'],
356 groups=device['flow_groups']['items']
357 )
358
359 def do_images(self, line):
360 """Show software images on the device"""
361 device = self.get_device(depth=-1)
362 omit_fields = {}
363 print_pb_list_as_table('Software Images:', device.images.image,
364 omit_fields, self.poutput, show_nulls=True)
365
366 @options([
367 make_option('-u', '--url', action='store', dest='url',
368 help="URL to get sw image"),
369 make_option('-n', '--name', action='store', dest='name',
370 help="Image name"),
371 make_option('-c', '--crc', action='store', dest='crc',
372 help="CRC code to verify with", default=0),
373 make_option('-v', '--version', action='store', dest='version',
374 help="Image version", default=0),
375 ])
376 def do_img_dnld_request(self, line, opts):
377 """
378 Request image download to a device
379 """
380 device = self.get_device(depth=-1)
381 self.poutput('device_id {}'.format(device.id))
382 self.poutput('name {}'.format(opts.name))
383 self.poutput('url {}'.format(opts.url))
384 self.poutput('crc {}'.format(opts.crc))
385 self.poutput('version {}'.format(opts.version))
386 try:
387 device_id = device.id
388 if device_id and opts.name and opts.url:
389 kw = dict(id=device_id)
390 kw['name'] = opts.name
391 kw['url'] = opts.url
392 else:
393 self.poutput('Device ID and URL are needed')
394 raise Exception('Device ID and URL are needed')
395 except Exception as e:
396 self.poutput('Error request img dnld {}. Error:{}'.format(device_id, e))
397 return
398 kw['crc'] = long(opts.crc)
399 kw['image_version'] = opts.version
400 response = None
401 try:
402 request = voltha_pb2.ImageDownload(**kw)
403 stub = self.get_stub()
404 response = stub.DownloadImage(request)
405 except Exception as e:
406 self.poutput('Error download image {}. Error:{}'.format(kw['id'], e))
407 return
408 name = enum2name(common_pb2.OperationResp,
409 'OperationReturnCode', response.code)
410 self.poutput('response: {}'.format(name))
411 self.poutput('{}'.format(response))
412
413 @options([
414 make_option('-n', '--name', action='store', dest='name',
415 help="Image name"),
416 ])
417 def do_img_dnld_status(self, line, opts):
418 """
419 Get a image download status
420 """
421 device = self.get_device(depth=-1)
422 self.poutput('device_id {}'.format(device.id))
423 self.poutput('name {}'.format(opts.name))
424 try:
425 device_id = device.id
426 if device_id and opts.name:
427 kw = dict(id=device_id)
428 kw['name'] = opts.name
429 else:
430 self.poutput('Device ID, Image Name are needed')
431 raise Exception('Device ID, Image Name are needed')
432 except Exception as e:
433 self.poutput('Error get img dnld status {}. Error:{}'.format(device_id, e))
434 return
435 status = None
436 try:
437 img_dnld = voltha_pb2.ImageDownload(**kw)
438 stub = self.get_stub()
439 status = stub.GetImageDownloadStatus(img_dnld)
440 except Exception as e:
441 self.poutput('Error get img dnld status {}. Error:{}'.format(device_id, e))
442 return
443 fields_to_omit = {
444 'crc',
445 'local_dir',
446 }
447 try:
448 print_pb_as_table('ImageDownload Status:', status, fields_to_omit, self.poutput)
449 except Exception, e:
450 self.poutput('Error {}. Error:{}'.format(device_id, e))
451
452 def do_img_dnld_list(self, line):
453 """
454 List all image download records for a given device
455 """
456 device = self.get_device(depth=-1)
457 device_id = device.id
458 self.poutput('Get all img dnld records {}'.format(device_id))
459 try:
460 stub = self.get_stub()
461 img_dnlds = stub.ListImageDownloads(voltha_pb2.ID(id=device_id))
462 except Exception, e:
463 self.poutput('Error list img dnlds {}. Error:{}'.format(device_id, e))
464 return
465 fields_to_omit = {
466 'crc',
467 'local_dir',
468 }
469 try:
470 print_pb_list_as_table('ImageDownloads:', img_dnlds.items, fields_to_omit, self.poutput)
471 except Exception, e:
472 self.poutput('Error {}. Error:{}'.format(device_id, e))
473
474
475 @options([
476 make_option('-n', '--name', action='store', dest='name',
477 help="Image name"),
478 ])
479 def do_img_dnld_cancel(self, line, opts):
480 """
481 Cancel a requested image download
482 """
483 device = self.get_device(depth=-1)
484 self.poutput('device_id {}'.format(device.id))
485 self.poutput('name {}'.format(opts.name))
486 device_id = device.id
487 try:
488 if device_id and opts.name:
489 kw = dict(id=device_id)
490 kw['name'] = opts.name
491 else:
492 self.poutput('Device ID, Image Name are needed')
493 raise Exception('Device ID, Image Name are needed')
494 except Exception as e:
495 self.poutput('Error cancel sw dnld {}. Error:{}'.format(device_id, e))
496 return
497 response = None
498 try:
499 img_dnld = voltha_pb2.ImageDownload(**kw)
500 stub = self.get_stub()
501 img_dnld = stub.GetImageDownload(img_dnld)
502 response = stub.CancelImageDownload(img_dnld)
503 except Exception as e:
504 self.poutput('Error cancel sw dnld {}. Error:{}'.format(device_id, e))
505 return
506 name = enum2name(common_pb2.OperationResp,
507 'OperationReturnCode', response.code)
508 self.poutput('response: {}'.format(name))
509 self.poutput('{}'.format(response))
510
511 @options([
512 make_option('-n', '--name', action='store', dest='name',
513 help="Image name"),
514 make_option('-s', '--save', action='store', dest='save_config',
515 help="Save Config", default="True"),
516 make_option('-d', '--dir', action='store', dest='local_dir',
517 help="Image on device location"),
518 ])
519 def do_img_activate(self, line, opts):
520 """
521 Activate an image update on device
522 """
523 device = self.get_device(depth=-1)
524 device_id = device.id
525 try:
526 if device_id and opts.name and opts.local_dir:
527 kw = dict(id=device_id)
528 kw['name'] = opts.name
529 kw['local_dir'] = opts.local_dir
530 else:
531 self.poutput('Device ID, Image Name, and Location are needed')
532 raise Exception('Device ID, Image Name, and Location are needed')
533 except Exception as e:
534 self.poutput('Error activate image {}. Error:{}'.format(device_id, e))
535 return
536 kw['save_config'] = json.loads(opts.save_config.lower())
537 self.poutput('activate image update {} {} {} {}'.format( \
538 kw['id'], kw['name'],
539 kw['local_dir'], kw['save_config']))
540 response = None
541 try:
542 img_dnld = voltha_pb2.ImageDownload(**kw)
543 stub = self.get_stub()
544 img_dnld = stub.GetImageDownload(img_dnld)
545 response = stub.ActivateImageUpdate(img_dnld)
546 except Exception as e:
547 self.poutput('Error activate image {}. Error:{}'.format(kw['id'], e))
548 return
549 name = enum2name(common_pb2.OperationResp,
550 'OperationReturnCode', response.code)
551 self.poutput('response: {}'.format(name))
552 self.poutput('{}'.format(response))
553
554 @options([
555 make_option('-n', '--name', action='store', dest='name',
556 help="Image name"),
557 make_option('-s', '--save', action='store', dest='save_config',
558 help="Save Config", default="True"),
559 make_option('-d', '--dir', action='store', dest='local_dir',
560 help="Image on device location"),
561 ])
562 def do_img_revert(self, line, opts):
563 """
564 Revert an image update on device
565 """
566 device = self.get_device(depth=-1)
567 device_id = device.id
568 try:
569 if device_id and opts.name and opts.local_dir:
570 kw = dict(id=device_id)
571 kw['name'] = opts.name
572 kw['local_dir'] = opts.local_dir
573 else:
574 self.poutput('Device ID, Image Name, and Location are needed')
575 raise Exception('Device ID, Image Name, and Location are needed')
576 except Exception as e:
577 self.poutput('Error revert image {}. Error:{}'.format(device_id, e))
578 return
579 kw['save_config'] = json.loads(opts.save_config.lower())
580 self.poutput('revert image update {} {} {} {}'.format( \
581 kw['id'], kw['name'],
582 kw['local_dir'], kw['save_config']))
583 response = None
584 try:
585 img_dnld = voltha_pb2.ImageDownload(**kw)
586 stub = self.get_stub()
587 img_dnld = stub.GetImageDownload(img_dnld)
588 response = stub.RevertImageUpdate(img_dnld)
589 except Exception as e:
590 self.poutput('Error revert image {}. Error:{}'.format(kw['id'], e))
591 return
592 name = enum2name(common_pb2.OperationResp,
593 'OperationReturnCode', response.code)
594 self.poutput('response: {}'.format(name))
595 self.poutput('{}'.format(response))