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