Scott Baker | 9828eb8 | 2018-09-13 14:13:34 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2017-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 | """ |
| 17 | Alarm-generator is a process that sends a stream of simulate_alarm requrests to VOLTHA. It will automatically pick |
| 18 | between a variety of ONU-related alarms. The number of alarms per second is configurable (for example, 0.01 = one |
| 19 | alarm per hundred seconds), and the duration of the alarm from RAISE to CLEAR is also configurable. |
| 20 | |
| 21 | By default, if not device is specified, then VOLTHA will be queried for the first device. |
| 22 | |
| 23 | Example: |
| 24 | # Generate 1 request per second, each request a duration of 2 seconds. Use whatever OLT device id exists in |
| 25 | # VOLTHA. Use intf_id =1234 and onu_device_id=5678 |
| 26 | main.py -C consul:8500 -g voltha:50555 -G -r 1 -u 2 -i 1234 -o 5678 |
| 27 | |
| 28 | # Generate 1 request per second, each request a duration of 2 seconds. Use whatever OLT and ONUs exist in |
| 29 | # VOLTHA. |
| 30 | main.py -C consul:8500 -g voltha:50555 -G -r 1 -u 2 |
| 31 | |
| 32 | """ |
| 33 | import argparse |
| 34 | import copy |
| 35 | import functools |
| 36 | import os |
| 37 | import random |
| 38 | import structlog |
| 39 | import sys |
| 40 | import time |
| 41 | from threading import Timer |
| 42 | |
| 43 | import grpc |
| 44 | from consul import Consul |
| 45 | from simplejson import dumps |
| 46 | |
| 47 | from google.protobuf.empty_pb2 import Empty |
| 48 | from voltha.protos import third_party |
Scott Baker | d865fa2 | 2018-11-07 11:45:28 -0800 | [diff] [blame] | 49 | from voltha.protos import voltha_pb2, voltha_pb2_grpc, common_pb2 |
Scott Baker | 9828eb8 | 2018-09-13 14:13:34 -0700 | [diff] [blame] | 50 | |
| 51 | defs = dict( |
| 52 | # config=os.environ.get('CONFIG', './cli.yml'), |
| 53 | consul=os.environ.get('CONSUL', 'localhost:8500'), |
| 54 | voltha_grpc_endpoint=os.environ.get('VOLTHA_GRPC_ENDPOINT', |
| 55 | 'localhost:50055'), |
| 56 | global_request=os.environ.get('GLOBAL_REQUEST', False), |
| 57 | device_id = None, |
| 58 | intf_id = "1234", |
| 59 | onu_id = None, |
| 60 | rate = 0.1, |
| 61 | duration = 1, |
| 62 | ) |
| 63 | |
| 64 | def enum2name(msg_obj, enum_type, enum_value): |
| 65 | descriptor = msg_obj.DESCRIPTOR.enum_types_by_name[enum_type] |
| 66 | name = descriptor.values_by_number[enum_value].name |
| 67 | return name |
| 68 | |
| 69 | banner = """\ |
| 70 | _ _ _ |
| 71 | __ _____| | |_| |_ __ _ |
| 72 | \ V / _ \ | _| ' \/ _` | Alarm Generator |
| 73 | \_/\___/_|\__|_||_\__,_| |
| 74 | """ |
| 75 | |
| 76 | class VOLTHAClient(object): |
| 77 | def __init__(self, voltha_grpc, global_request=False): |
| 78 | self.voltha_grpc = voltha_grpc |
| 79 | self.global_request = global_request |
| 80 | self.channel = None |
| 81 | self.stub = None |
| 82 | self.log = structlog.get_logger() |
| 83 | self.stdout = sys.stdout |
| 84 | |
| 85 | def get_channel(self): |
| 86 | if self.channel is None: |
| 87 | self.channel = grpc.insecure_channel(self.voltha_grpc) |
| 88 | return self.channel |
| 89 | |
| 90 | def get_stub(self): |
| 91 | if self.stub is None: |
| 92 | self.stub = \ |
Scott Baker | d865fa2 | 2018-11-07 11:45:28 -0800 | [diff] [blame] | 93 | voltha_pb2_grpc.VolthaGlobalServiceStub(self.get_channel()) \ |
Scott Baker | 9828eb8 | 2018-09-13 14:13:34 -0700 | [diff] [blame] | 94 | if self.global_request else \ |
Scott Baker | d865fa2 | 2018-11-07 11:45:28 -0800 | [diff] [blame] | 95 | voltha_pb2_grpc.VolthaLocalServiceStub(self.get_channel()) |
Scott Baker | 9828eb8 | 2018-09-13 14:13:34 -0700 | [diff] [blame] | 96 | return self.stub |
| 97 | |
| 98 | def get_first_olt_device_id(self): |
| 99 | """ Query VOLTHA and pick some olt device that we can use for alarms. """ |
| 100 | |
| 101 | stub = self.get_stub() |
| 102 | logical_devices = stub.ListLogicalDevices(Empty()) |
| 103 | logical_device_ids = [x.id for x in logical_devices.items] |
| 104 | |
| 105 | devices = stub.ListDevices(Empty()) |
| 106 | |
| 107 | for device in devices.items: |
| 108 | # If the parent_id is notempty and no a logical_device then it must not be an OLT. |
| 109 | if (device.parent_id) and (device.parent_id not in logical_device_ids): |
| 110 | print device.parent_id, logical_device_ids |
| 111 | continue |
| 112 | |
| 113 | return device. id |
| 114 | |
| 115 | raise Exception("No OLT devices in VOLTHA to choose from") |
| 116 | |
| 117 | def get_onus(self, olt_id): |
| 118 | """ Query VOLTHA to get a list of ONUs attached to a particular OLT """ |
| 119 | |
| 120 | onus = [] |
| 121 | |
| 122 | stub = self.get_stub() |
| 123 | devices = stub.ListDevices(Empty()) |
| 124 | for device in devices.items: |
| 125 | if device.parent_id != olt_id: |
| 126 | continue |
| 127 | if device.parent_port_no is None: |
| 128 | continue |
| 129 | |
| 130 | onus.append({"id": olt_id, |
| 131 | "onu_device_id": device.id, |
| 132 | "intf_id": str(device.parent_port_no & 0x0F)}) |
| 133 | |
| 134 | return onus |
| 135 | |
| 136 | |
| 137 | class AlarmGenerator(VOLTHAClient): |
| 138 | def __init__(self, voltha_grpc, global_request, onus=[], rate=0.1, duration=1): |
| 139 | super(AlarmGenerator, self).__init__(voltha_grpc, global_request) |
| 140 | self.onus = onus |
| 141 | self.rate = rate |
| 142 | self.duration = duration |
| 143 | self.raised_alarms = [] |
| 144 | |
| 145 | def pick_alarm(self): |
| 146 | eligible_indicators = ["dying_gasp", "onu_los", "onu_lopc_miss", "onu_lopc_mic", "onu_lob"] |
| 147 | while True: |
| 148 | onu = random.choice(self.onus) |
| 149 | indicator = random.choice(eligible_indicators) |
| 150 | |
| 151 | alarm = copy.copy(onu) |
| 152 | alarm["indicator"] = indicator |
| 153 | |
| 154 | # Make sure we don't already have this alarm raised on this ONU. If so, the loop around and try to pick |
| 155 | # some other alarm. If all possible alarms are currently raised, eventually we'll keep looping until some |
| 156 | # alarm is free. |
| 157 | if alarm in self.raised_alarms: |
| 158 | time.sleep(0.01) # Avoid tying up the CPU if all alarms are raised for an extended time. |
| 159 | continue |
| 160 | |
| 161 | return alarm |
| 162 | |
| 163 | def clear_alarm(self, kw): |
| 164 | if kw in self.raised_alarms: |
| 165 | self.raised_alarms.remove(kw) |
| 166 | |
| 167 | kw["operation"] = voltha_pb2.SimulateAlarmRequest.CLEAR |
| 168 | |
| 169 | response = None |
| 170 | try: |
| 171 | simulate_alarm = voltha_pb2.SimulateAlarmRequest(**kw) |
| 172 | stub = self.get_stub() |
| 173 | response = stub.SimulateAlarm(simulate_alarm) |
| 174 | except Exception as e: |
| 175 | self.log.error('Error simulate alarm {}. Error:{}'.format(kw['id'], e), kw=kw) |
| 176 | return |
| 177 | name = enum2name(common_pb2.OperationResp, |
| 178 | 'OperationReturnCode', response.code) |
| 179 | self.log.info("Cleared Alarm", alarm=kw, response=name) |
| 180 | |
| 181 | def generate_alarm(self): |
| 182 | kw = self.pick_alarm() |
| 183 | |
| 184 | response = None |
| 185 | try: |
| 186 | simulate_alarm = voltha_pb2.SimulateAlarmRequest(**kw) |
| 187 | stub = self.get_stub() |
| 188 | response = stub.SimulateAlarm(simulate_alarm) |
| 189 | except Exception as e: |
| 190 | self.log.error('Error simulate alarm {}. Error:{}'.format(kw['id'], e), kw=kw) |
| 191 | return |
| 192 | name = enum2name(common_pb2.OperationResp, |
| 193 | 'OperationReturnCode', response.code) |
| 194 | self.log.info("Generated Alarm", alarm=kw, response=name) |
| 195 | |
| 196 | self.raised_alarms.append(kw) |
| 197 | |
| 198 | Timer(self.duration, functools.partial(self.clear_alarm, kw)).start() |
| 199 | |
| 200 | def run(self): |
| 201 | while True: |
| 202 | time.sleep(1/self.rate) |
| 203 | self.generate_alarm() |
| 204 | |
| 205 | |
| 206 | def main(): |
| 207 | |
| 208 | parser = argparse.ArgumentParser() |
| 209 | |
| 210 | _help = '<hostname>:<port> to consul agent (default: %s)' % defs['consul'] |
| 211 | parser.add_argument( |
| 212 | '-C', '--consul', action='store', default=defs['consul'], help=_help) |
| 213 | |
| 214 | _help = 'Lookup Voltha endpoints based on service entries in Consul' |
| 215 | parser.add_argument( |
| 216 | '-L', '--lookup', action='store_true', help=_help) |
| 217 | |
| 218 | _help = 'All requests to the Voltha gRPC service are global' |
| 219 | parser.add_argument( |
| 220 | '-G', '--global_request', action='store_true', help=_help) |
| 221 | |
| 222 | _help = '<hostname>:<port> of Voltha gRPC service (default={})'.format( |
| 223 | defs['voltha_grpc_endpoint']) |
| 224 | parser.add_argument('-g', '--grpc-endpoint', action='store', |
| 225 | default=defs['voltha_grpc_endpoint'], help=_help) |
| 226 | |
| 227 | _help = 'device id, if None will query VOLTHA for the first device (default %s)' % defs['device_id'] |
| 228 | parser.add_argument('-d', '--device_id', action='store', |
| 229 | default=defs['device_id'], help=_help) |
| 230 | |
| 231 | _help = 'onu id, if None will query VOLTHA for a set of ONUs (default: %s)' % defs['onu_id'] |
| 232 | parser.add_argument('-o', '--onu_id', action='store', |
| 233 | default=defs['onu_id'], help=_help) |
| 234 | |
| 235 | _help = 'intf id (default: %s)' % defs['intf_id'] |
| 236 | parser.add_argument('-i', '--intf_id', action='store', |
| 237 | default=defs['intf_id'], help=_help) |
| 238 | |
| 239 | _help = 'rate in alarms per second (default: %s)' % defs['rate'] |
| 240 | parser.add_argument('-r', '--rate', action='store', type=float, |
| 241 | default=defs['rate'], help=_help) |
| 242 | |
| 243 | _help = 'duration between raise and clear in seconds (default: %s)' % defs['duration'] |
| 244 | parser.add_argument('-u', '--duration', action='store', type=int, |
| 245 | default=defs['duration'], help=_help) |
| 246 | |
| 247 | args = parser.parse_args() |
| 248 | |
| 249 | if args.lookup: |
| 250 | host = args.consul.split(':')[0].strip() |
| 251 | port = int(args.consul.split(':')[1].strip()) |
| 252 | consul = Consul(host=host, port=port) |
| 253 | |
| 254 | _, services = consul.catalog.service('voltha-grpc') |
| 255 | if not services: |
| 256 | print('No voltha-grpc service registered in consul; exiting') |
| 257 | sys.exit(1) |
| 258 | args.grpc_endpoint = '{}:{}'.format(services[0]['ServiceAddress'], |
| 259 | services[0]['ServicePort']) |
| 260 | |
| 261 | _, services = consul.catalog.service('voltha-sim-rest') |
| 262 | if not services: |
| 263 | print('No voltha-sim-rest service registered in consul; exiting') |
| 264 | sys.exit(1) |
| 265 | args.sim_rest_endpoint = '{}:{}'.format(services[0]['ServiceAddress'], |
| 266 | services[0]['ServicePort']) |
| 267 | |
| 268 | device_id = args.device_id |
| 269 | if not device_id: |
| 270 | device_id = VOLTHAClient(args.grpc_endpoint, args.global_request).get_first_olt_device_id() |
| 271 | |
| 272 | if args.onu_id: |
| 273 | onus = [{"id": device_id, |
| 274 | "onu_device_id": args.onu_id, |
| 275 | "intf_id": args.intf_id}] |
| 276 | else: |
| 277 | onus = VOLTHAClient(args.grpc_endpoint, args.global_request).get_onus(device_id) |
| 278 | if not onus: |
| 279 | raise Exception("Found no valid ONUs in VOLTHA on olt %s. Perhaps use -i and -o options?" % device_id) |
| 280 | |
| 281 | print banner |
| 282 | |
| 283 | g = AlarmGenerator(args.grpc_endpoint, |
| 284 | args.global_request, |
| 285 | onus = onus, |
| 286 | rate=args.rate, |
| 287 | duration=args.duration) |
| 288 | g.run() |
| 289 | |
| 290 | if __name__ == "__main__": |
| 291 | main() |