Chip Boling | 69abce8 | 2018-06-18 09:56:23 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright 2018 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 | """ |
| 19 | OpenOMCI level CLI commands |
| 20 | """ |
| 21 | from optparse import make_option |
| 22 | from cmd2 import Cmd, options |
| 23 | from datetime import datetime |
| 24 | from google.protobuf.empty_pb2 import Empty |
| 25 | from cli.table import print_pb_list_as_table |
| 26 | from voltha.protos import third_party |
| 27 | from voltha.protos import voltha_pb2 |
| 28 | from voltha.protos.omci_mib_db_pb2 import MibDeviceData, MibClassData, \ |
| 29 | MibInstanceData |
| 30 | from os import linesep |
| 31 | |
| 32 | _ = third_party |
| 33 | |
| 34 | |
| 35 | class OmciCli(Cmd): |
| 36 | CREATED_KEY = 'created' |
| 37 | MODIFIED_KEY = 'modified' |
| 38 | MDS_KEY = 'mib_data_sync' |
| 39 | LAST_SYNC_KEY = 'last_mib_sync' |
| 40 | VERSION_KEY = 'version' |
| 41 | DEVICE_ID_KEY = 'device_id' |
| 42 | CLASS_ID_KEY = 'class_id' |
| 43 | INSTANCE_ID_KEY = 'instance_id' |
| 44 | ATTRIBUTES_KEY = 'attributes' |
| 45 | TIME_FORMAT = '%Y%m%d-%H%M%S.%f' |
| 46 | ME_KEY = 'managed_entities' |
| 47 | MSG_TYPE_KEY = 'message_types' |
| 48 | |
| 49 | MSG_TYPE_TO_NAME = { |
| 50 | 4: 'Create', |
| 51 | 5: 'Create Complete', |
| 52 | 6: 'Delete', |
| 53 | 8: 'Set', |
| 54 | 9: 'Get', |
| 55 | 10: 'Get Complete', |
| 56 | 11: 'Get All Alarms', |
| 57 | 12: 'Get All Alarms Next', |
| 58 | 13: 'Mib Upload', |
| 59 | 14: 'Mib Upload Next', |
| 60 | 15: 'Mib Reset', |
| 61 | 16: 'Alarm Notification', |
| 62 | 17: 'Attribute Value Change', |
| 63 | 18: 'Test', |
| 64 | 19: 'Start Software Download', |
| 65 | 20: 'Download Section', |
| 66 | 21: 'End Software Download', |
| 67 | 22: 'Activate Software', |
| 68 | 23: 'Commit Software', |
| 69 | 24: 'Synchronize Time', |
| 70 | 25: 'Reboot', |
| 71 | 26: 'Get Next', |
| 72 | 27: 'Test Result', |
| 73 | 28: 'Get Current Data', |
| 74 | 29: 'Set Table' |
| 75 | } |
| 76 | |
| 77 | def __init__(self, device_id, get_stub): |
| 78 | Cmd.__init__(self) |
| 79 | self.get_stub = get_stub |
| 80 | self.device_id = device_id |
| 81 | self.prompt = '(' + self.colorize( |
| 82 | self.colorize('omci {}'.format(device_id), 'green'), |
| 83 | 'bold') + ') ' |
| 84 | |
| 85 | def cmdloop(self, intro=None): |
| 86 | self._cmdloop() |
| 87 | |
| 88 | do_exit = Cmd.do_quit |
| 89 | |
| 90 | def do_quit(self, line): |
| 91 | return self._STOP_AND_EXIT |
| 92 | |
| 93 | def get_device_mib(self, device_id, depth=-1): |
| 94 | stub = self.get_stub() |
| 95 | |
| 96 | try: |
| 97 | res = stub.GetMibDeviceData(voltha_pb2.ID(id=device_id), |
| 98 | metadata=(('get-depth', str(depth)), )) |
| 99 | except Exception as e: |
| 100 | pass |
| 101 | |
| 102 | return res |
| 103 | |
| 104 | def help_show_mib(self): |
| 105 | self.poutput('show_mib [-d <device-id>] [-c <class-id> [-i <instance-id>]]' + |
| 106 | linesep + '-d: <device-id> ONU Device ID' + |
| 107 | linesep + '-c: <class-id> Managed Entity Class ID' + |
| 108 | linesep + '-i: <instance-id> ME Instance ID') |
| 109 | |
| 110 | @options([ |
| 111 | make_option('-d', '--device-id', action="store", dest='device_id', type='string', |
| 112 | help='ONU Device ID', default=None), |
| 113 | make_option('-c', '--class-id', action="store", dest='class_id', |
| 114 | type='int', help='Managed Entity Class ID', default=None), |
| 115 | make_option('-i', '--instance-id', action="store", dest='instance_id', |
| 116 | type='int', help='ME Instance ID', default=None) |
| 117 | ]) |
| 118 | def do_show_mib(self, _line, opts): |
| 119 | """ |
| 120 | Show OMCI MIB Database Information |
| 121 | """ |
| 122 | device_id = opts.device_id or self.device_id |
| 123 | |
| 124 | if opts.class_id is not None and not 1 <= opts.class_id <= 0xFFFF: |
| 125 | self.poutput(self.colorize('Error: ', 'red') + |
| 126 | self.colorize('Class ID must be 1..65535', 'blue')) |
| 127 | return |
| 128 | |
| 129 | if opts.instance_id is not None and opts.class_id is None: |
| 130 | self.poutput(self.colorize('Error: ', 'red') + |
| 131 | self.colorize('Class ID required if specifying an Instance ID', |
| 132 | 'blue')) |
| 133 | return |
| 134 | |
| 135 | if opts.instance_id is not None and not 0 <= opts.instance_id <= 0xFFFF: |
| 136 | self.poutput(self.colorize('Error: ', 'red') + |
| 137 | self.colorize('Instance ID must be 0..65535', 'blue')) |
| 138 | return |
| 139 | |
| 140 | try: |
| 141 | mib_db = self.get_device_mib(device_id, depth=-1) |
| 142 | |
| 143 | except Exception: # UnboundLocalError if Device ID not found in DB |
| 144 | self.poutput(self.colorize('Failed to get MIB database for ONU {}' |
| 145 | .format(device_id), 'red')) |
| 146 | return |
| 147 | |
| 148 | mib = self._device_to_dict(mib_db) |
| 149 | |
| 150 | self.poutput('OpenOMCI MIB Database for ONU {}'.format(device_id)) |
| 151 | |
| 152 | if opts.class_id is None and opts.instance_id is None: |
| 153 | self.poutput('Version : {}'.format(mib[OmciCli.VERSION_KEY])) |
| 154 | self.poutput('Created : {}'.format(mib[OmciCli.CREATED_KEY])) |
| 155 | self.poutput('Last In-Sync Time : {}'.format(mib[OmciCli.LAST_SYNC_KEY])) |
| 156 | self.poutput('MIB Data Sync Value: {}'.format(mib[OmciCli.MDS_KEY])) |
| 157 | |
| 158 | class_ids = [k for k in mib.iterkeys() |
| 159 | if isinstance(k, int) and |
| 160 | (opts.class_id is None or opts.class_id == k)] |
| 161 | class_ids.sort() |
| 162 | |
| 163 | if len(class_ids) == 0 and opts.class_id is not None: |
| 164 | self.poutput(self.colorize('Class ID {} not found in MIB Database' |
| 165 | .format(opts.class_id), 'red')) |
| 166 | return |
| 167 | |
| 168 | for cls_id in class_ids: |
| 169 | class_data = mib[cls_id] |
| 170 | self.poutput(' ----------------------------------------------') |
Chip Boling | 07912c3 | 2018-09-28 14:13:31 -0500 | [diff] [blame] | 171 | self.poutput(' Class ID : {0} - ({0:#x}): {1}'. |
| 172 | format(cls_id, mib[OmciCli.ME_KEY].get(cls_id, 'Unknown'))) |
Chip Boling | 69abce8 | 2018-06-18 09:56:23 -0500 | [diff] [blame] | 173 | inst_ids = [k for k in class_data.iterkeys() |
| 174 | if isinstance(k, int) and |
| 175 | (opts.instance_id is None or opts.instance_id == k)] |
| 176 | inst_ids.sort() |
| 177 | |
| 178 | if len(inst_ids) == 0 and opts.instance_id is not None: |
| 179 | self.poutput(self.colorize('Instance ID {} of Class ID {} not ' + |
| 180 | 'found in MIB Database'. |
| 181 | format(opts.instance_id, opts.class_id), |
| 182 | 'red')) |
| 183 | return |
| 184 | |
| 185 | for inst_id in inst_ids: |
| 186 | inst_data = class_data[inst_id] |
| 187 | self.poutput(' Instance ID: {0} - ({0:#x})'.format(inst_id)) |
| 188 | self.poutput(' Created : {}'.format(inst_data[OmciCli.CREATED_KEY])) |
| 189 | self.poutput(' Modified : {}'.format(inst_data[OmciCli.MODIFIED_KEY])) |
| 190 | |
| 191 | attributes = inst_data[OmciCli.ATTRIBUTES_KEY] |
| 192 | attr_names = attributes.keys() |
Chip Boling | 07912c3 | 2018-09-28 14:13:31 -0500 | [diff] [blame] | 193 | if len(attr_names): |
| 194 | attr_names.sort() |
| 195 | max_len = max([len(attr) for attr in attr_names]) |
Chip Boling | 69abce8 | 2018-06-18 09:56:23 -0500 | [diff] [blame] | 196 | |
Chip Boling | 07912c3 | 2018-09-28 14:13:31 -0500 | [diff] [blame] | 197 | for attr in attr_names: |
| 198 | name = self._cleanup_attribute_name(attr).ljust(max_len) |
| 199 | value = attributes[attr] |
| 200 | try: |
| 201 | ivalue = int(value) |
| 202 | self.poutput(' {0}: {1} - ({1:#x})'.format(name, ivalue)) |
Chip Boling | 69abce8 | 2018-06-18 09:56:23 -0500 | [diff] [blame] | 203 | |
Chip Boling | 07912c3 | 2018-09-28 14:13:31 -0500 | [diff] [blame] | 204 | except ValueError: |
| 205 | self.poutput(' {}: {}'.format(name, value)) |
Chip Boling | 69abce8 | 2018-06-18 09:56:23 -0500 | [diff] [blame] | 206 | |
Chip Boling | 07912c3 | 2018-09-28 14:13:31 -0500 | [diff] [blame] | 207 | if inst_id is not inst_ids[-1]: |
| 208 | self.poutput(linesep) |
Chip Boling | 69abce8 | 2018-06-18 09:56:23 -0500 | [diff] [blame] | 209 | |
| 210 | def _cleanup_attribute_name(self, attr): |
| 211 | """Change underscore to space and capitalize first character""" |
| 212 | return ' '.join([v[0].upper() + v[1:] for v in attr.split('_')]) |
| 213 | |
| 214 | def _instance_to_dict(self, instance): |
| 215 | if not isinstance(instance, MibInstanceData): |
| 216 | raise TypeError('{} is not of type MibInstanceData'.format(type(instance))) |
| 217 | |
| 218 | data = { |
| 219 | OmciCli.INSTANCE_ID_KEY: instance.instance_id, |
| 220 | OmciCli.CREATED_KEY: self._string_to_time(instance.created), |
| 221 | OmciCli.MODIFIED_KEY: self._string_to_time(instance.modified), |
| 222 | OmciCli.ATTRIBUTES_KEY: dict() |
| 223 | } |
| 224 | for attribute in instance.attributes: |
| 225 | data[OmciCli.ATTRIBUTES_KEY][attribute.name] = str(attribute.value) |
| 226 | |
| 227 | return data |
| 228 | |
| 229 | def _class_to_dict(self, val): |
| 230 | if not isinstance(val, MibClassData): |
| 231 | raise TypeError('{} is not of type MibClassData'.format(type(val))) |
| 232 | |
| 233 | data = { |
| 234 | OmciCli.CLASS_ID_KEY: val.class_id, |
| 235 | } |
| 236 | for instance in val.instances: |
| 237 | data[instance.instance_id] = self._instance_to_dict(instance) |
| 238 | return data |
| 239 | |
| 240 | def _device_to_dict(self, val): |
| 241 | if not isinstance(val, MibDeviceData): |
| 242 | raise TypeError('{} is not of type MibDeviceData'.format(type(val))) |
| 243 | |
| 244 | data = { |
| 245 | OmciCli.DEVICE_ID_KEY: val.device_id, |
| 246 | OmciCli.CREATED_KEY: self._string_to_time(val.created), |
| 247 | OmciCli.LAST_SYNC_KEY: self._string_to_time(val.last_sync_time), |
| 248 | OmciCli.MDS_KEY: val.mib_data_sync, |
| 249 | OmciCli.VERSION_KEY: val.version, |
| 250 | OmciCli.ME_KEY: dict(), |
| 251 | OmciCli.MSG_TYPE_KEY: set() |
| 252 | } |
| 253 | for class_data in val.classes: |
| 254 | data[class_data.class_id] = self._class_to_dict(class_data) |
| 255 | |
| 256 | for managed_entity in val.managed_entities: |
| 257 | data[OmciCli.ME_KEY][managed_entity.class_id] = managed_entity.name |
| 258 | |
| 259 | for msg_type in val.message_types: |
| 260 | data[OmciCli.MSG_TYPE_KEY].add(msg_type.message_type) |
| 261 | |
| 262 | return data |
| 263 | |
| 264 | def _string_to_time(self, time): |
| 265 | return datetime.strptime(time, OmciCli.TIME_FORMAT) if len(time) else None |
| 266 | |
| 267 | def help_show_me(self): |
| 268 | self.poutput('show_me [-d <device-id>]' + |
| 269 | linesep + '-d: <device-id> ONU Device ID') |
| 270 | |
| 271 | @options([ |
| 272 | make_option('-d', '--device-id', action="store", dest='device_id', type='string', |
| 273 | help='ONU Device ID', default=None), |
| 274 | ]) |
| 275 | def do_show_me(self, _line, opts): |
| 276 | """ Show supported OMCI Managed Entities""" |
| 277 | |
| 278 | device_id = opts.device_id or self.device_id |
| 279 | |
| 280 | try: |
| 281 | mib_db = self.get_device_mib(device_id, depth=1) |
| 282 | mib = self._device_to_dict(mib_db) |
| 283 | |
| 284 | except Exception: # UnboundLocalError if Device ID not found in DB |
| 285 | self.poutput(self.colorize('Failed to get supported ME information for ONU {}' |
| 286 | .format(device_id), 'red')) |
| 287 | return |
| 288 | |
| 289 | class_ids = [class_id for class_id in mib[OmciCli.ME_KEY].keys()] |
| 290 | class_ids.sort() |
| 291 | |
| 292 | self.poutput('Supported Managed Entities for ONU {}'.format(device_id)) |
| 293 | for class_id in class_ids: |
| 294 | self.poutput(' {0} - ({0:#x}): {1}'.format(class_id, |
| 295 | mib[OmciCli.ME_KEY][class_id])) |
| 296 | |
| 297 | def help_show_msg_types(self): |
| 298 | self.poutput('show_msg_types [-d <device-id>]' + |
| 299 | linesep + '-d: <device-id> ONU Device ID') |
| 300 | |
| 301 | @options([ |
| 302 | make_option('-d', '--device-id', action="store", dest='device_id', type='string', |
| 303 | help='ONU Device ID', default=None), |
| 304 | ]) |
| 305 | def do_show_msg_types(self, _line, opts): |
| 306 | """ Show supported OMCI Message Types""" |
| 307 | device_id = opts.device_id or self.device_id |
| 308 | |
| 309 | try: |
| 310 | mib_db = self.get_device_mib(device_id, depth=1) |
| 311 | mib = self._device_to_dict(mib_db) |
| 312 | |
| 313 | except Exception: # UnboundLocalError if Device ID not found in DB |
| 314 | self.poutput(self.colorize('Failed to get supported Message Types for ONU {}' |
| 315 | .format(device_id), 'red')) |
| 316 | return |
| 317 | |
| 318 | msg_types = [msg_type for msg_type in mib[OmciCli.MSG_TYPE_KEY]] |
| 319 | msg_types.sort() |
| 320 | |
| 321 | self.poutput('Supported Message Types for ONU {}'.format(device_id)) |
| 322 | for msg_type in msg_types: |
| 323 | self.poutput(' {0} - ({0:#x}): {1}'. |
| 324 | format(msg_type, |
| 325 | OmciCli.MSG_TYPE_TO_NAME.get(msg_type, 'Unknown'))) |
| 326 | |
| 327 | def get_devices(self): |
| 328 | stub = self.get_stub() |
| 329 | res = stub.ListDevices(Empty()) |
| 330 | return res.items |
| 331 | |
| 332 | def do_devices(self, line): |
| 333 | """List devices registered in Voltha reduced for OMCI menu""" |
| 334 | devices = self.get_devices() |
| 335 | omit_fields = { |
| 336 | 'adapter', |
| 337 | 'model', |
| 338 | 'hardware_version', |
| 339 | 'images', |
| 340 | 'firmware_version', |
| 341 | 'serial_number', |
| 342 | 'vlan', |
| 343 | 'root', |
| 344 | 'extra_args', |
| 345 | 'proxy_address', |
| 346 | } |
| 347 | print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput) |
| 348 | |
| 349 | def help_devices(self): |
| 350 | self.poutput('TODO: Provide some help') |
| 351 | |
| 352 | def poutput(self, msg): |
| 353 | """Convenient shortcut for self.stdout.write(); adds newline if necessary.""" |
| 354 | if msg: |
| 355 | self.stdout.write(msg) |
| 356 | if msg[-1] != '\n': |
| 357 | self.stdout.write('\n') |