Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 1 | #!/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 | # |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 17 | import readline |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 18 | import sys |
| 19 | from optparse import make_option |
| 20 | from time import sleep |
| 21 | |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 22 | import grpc |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 23 | import requests |
| 24 | from cmd2 import Cmd, options |
| 25 | from google.protobuf.empty_pb2 import Empty |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 26 | from simplejson import dumps |
| 27 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 28 | from cli.device import DeviceCli |
| 29 | from cli.logical_device import LogicalDeviceCli |
| 30 | from voltha.core.flow_decomposer import * |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 31 | from voltha.protos import third_party |
| 32 | from voltha.protos import voltha_pb2 |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 33 | from voltha.protos.openflow_13_pb2 import FlowTableUpdate |
| 34 | |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 35 | _ = third_party |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 36 | from cli.utils import pb2dict |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 37 | |
| 38 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 39 | banner = """\ |
| 40 | _ _ _ _ _ |
| 41 | __ _____| | |_| |_ __ _ __| (_) |
| 42 | \ V / _ \ | _| ' \/ _` | / _| | | |
| 43 | \_/\___/_|\__|_||_\__,_| \__|_|_| |
| 44 | (to exit type q, exit or quit or hit Ctrl-D) |
| 45 | """ |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 46 | |
| 47 | class VolthaCli(Cmd): |
| 48 | |
| 49 | prompt = 'voltha' |
| 50 | history_file_name = '.voltha_cli_history' |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 51 | |
| 52 | # Settable CLI parameters |
| 53 | voltha_grpc = 'localhost:50055' |
| 54 | voltha_sim_rest = 'localhost:18880' |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 55 | max_history_lines = 500 |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 56 | default_device_id = None |
| 57 | default_logical_device_id = None |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 58 | |
| 59 | Cmd.settable.update(dict( |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 60 | voltha_grpc='Voltha GRPC endpoint in form of <host>:<port>', |
| 61 | voltha_sim_rest='Voltha simulation back door for testing in form ' |
| 62 | 'of <host>:<port>', |
| 63 | max_history_lines='Maximum number of history lines stored across ' |
| 64 | 'sessions', |
| 65 | default_device_id='Device id used when no device id is specified', |
| 66 | default_logical_device_id='Logical device id used when no device id ' |
| 67 | 'is specified', |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 68 | )) |
| 69 | |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 70 | def __init__(self, *args, **kw): |
| 71 | Cmd.__init__(self, *args, **kw) |
| 72 | self.prompt = '(' + self.colorize( |
| 73 | self.colorize(self.prompt, 'red'), 'bold') + ') ' |
| 74 | self.channel = None |
| 75 | |
| 76 | def load_history(self): |
| 77 | """Load saved command history from local history file""" |
| 78 | try: |
| 79 | with file(self.history_file_name, 'r') as f: |
| 80 | for line in f.readlines(): |
| 81 | stripped_line = line.strip() |
| 82 | self.history.append(stripped_line) |
| 83 | readline.add_history(stripped_line) |
| 84 | except IOError: |
| 85 | pass # ignore if file cannot be read |
| 86 | |
| 87 | def save_history(self): |
| 88 | try: |
| 89 | with file(self.history_file_name, 'w') as f: |
| 90 | f.write('\n'.join(self.history[-self.max_history_lines:])) |
| 91 | except IOError, e: |
| 92 | print >> sys.stderr, 'Could not save history in {}: {}'.format( |
| 93 | self.history_file_name, e.msg) |
| 94 | else: |
| 95 | print >> sys.stderr, 'History saved as {}'.format( |
| 96 | self.history_file_name) |
| 97 | |
| 98 | def get_channel(self): |
| 99 | if self.channel is None: |
| 100 | self.channel = grpc.insecure_channel(self.voltha_grpc) |
| 101 | return self.channel |
| 102 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 103 | def preloop(self): |
| 104 | self.poutput(banner) |
| 105 | |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 106 | def do_reset_history(self, arg): |
| 107 | """Reset CLI history""" |
| 108 | while self.history: |
| 109 | self.history.pop() |
| 110 | |
| 111 | def do_launch(self, arg): |
| 112 | """If Voltha is not running yet, launch it""" |
| 113 | pass |
| 114 | |
| 115 | def do_restart(self, arg): |
| 116 | """Launch Voltha, but if it is already running, terminate it first""" |
| 117 | pass |
| 118 | |
| 119 | def do_devices(self, arg): |
| 120 | """List devices registered in Voltha""" |
| 121 | stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel()) |
| 122 | res = stub.ListDevices(Empty()) |
| 123 | for device in res.items: |
| 124 | print self.colorize('# ====== device {}'.format(device.id), 'blue') |
| 125 | print dumps(pb2dict(device), indent=4, sort_keys=True) |
| 126 | |
| 127 | def do_logical_devices(self, arg): |
| 128 | """List logical devices in Voltha""" |
| 129 | stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel()) |
| 130 | res = stub.ListLogicalDevices(Empty()) |
| 131 | for logical_device in res.items: |
| 132 | print self.colorize('# ====== logical device {}'.format( |
| 133 | logical_device.id), 'blue') |
| 134 | print dumps(pb2dict(logical_device), indent=4, sort_keys=True) |
| 135 | |
| 136 | def do_device(self, arg): |
| 137 | """Enter device level command mode""" |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 138 | device_id = arg or self.default_device_id |
| 139 | if not device_id: |
| 140 | raise Exception('<device-id> parameter needed') |
| 141 | sub = DeviceCli(self.get_channel, device_id) |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 142 | sub.cmdloop() |
| 143 | |
| 144 | def do_logical_device(self, arg): |
| 145 | """Enter logical device level command mode""" |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 146 | logical_device_id = arg or self.default_logical_device_id |
| 147 | if not logical_device_id: |
| 148 | raise Exception('<logical-device-id> parameter needed') |
| 149 | sub = LogicalDeviceCli(self.get_channel, logical_device_id) |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 150 | sub.cmdloop() |
| 151 | |
| 152 | def do_debug(self, arg): |
| 153 | """Launch PDB debug prompt in CLI (for CLI development)""" |
| 154 | from pdb import set_trace |
| 155 | set_trace() |
| 156 | |
| 157 | def do_health(self, arg): |
| 158 | """Show connectivity status to Voltha status""" |
| 159 | stub = voltha_pb2.HealthServiceStub(self.get_channel()) |
| 160 | res = stub.GetHealthStatus(Empty()) |
| 161 | print dumps(pb2dict(res), indent=4) |
| 162 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 163 | def do_test(self, arg): |
| 164 | """Enter test mode, which makes a bunch on new commands available""" |
| 165 | sub = TestCli(self.history, self.get_channel) |
| 166 | sub.cmdloop() |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 167 | |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 168 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 169 | class TestCli(VolthaCli): |
| 170 | |
| 171 | def __init__(self, history, get_channel): |
| 172 | VolthaCli.__init__(self) |
| 173 | self.history = history |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 174 | self.get_channel = get_channel |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 175 | self.prompt = '(' + self.colorize(self.colorize('test', 'cyan'), |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 176 | 'bold') + ') ' |
| 177 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 178 | @options([ |
| 179 | make_option('-t', '--device-type', action="store", dest='device_type', |
| 180 | help="Device type", default='simulated_olt'), |
| 181 | make_option('-m', '--mac-address', action='store', dest='mac_address', |
| 182 | default='00:0c:e2:31:40:00'), |
| 183 | make_option('-i', '--ip-address', action='store', dest='ip_address'), |
| 184 | ]) |
| 185 | def do_preprovision_olt(self, arg, opts): |
| 186 | """Preprovision a new OLT with given device type""" |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 187 | stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel()) |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 188 | kw = dict(type=opts.device_type) |
| 189 | if opts.ip_address: |
| 190 | kw['ipv4_address'] = opts.ip_address |
| 191 | elif opts.mac_address: |
| 192 | kw['mac_address'] = opts.mac_address |
| 193 | else: |
| 194 | raise Exception('Either IP address or Mac Address is needed') |
| 195 | device = voltha_pb2.Device(**kw) |
| 196 | device = stub.CreateDevice(device) |
| 197 | print 'success (device id = {})'.format(device.id) |
| 198 | self.default_device_id = device.id |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 199 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 200 | def do_activate_olt(self, arg): |
| 201 | """ |
| 202 | Activate an OLT. If the <id> is not provided, it will be on the last |
| 203 | pre-provisioned OLT. |
| 204 | """ |
| 205 | device_id = arg or self.default_device_id |
| 206 | print 'activating', device_id |
| 207 | stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel()) |
| 208 | stub.ActivateDevice(voltha_pb2.ID(id=device_id)) |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 209 | |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 210 | # try to acquire logical device id |
| 211 | while True: |
| 212 | device = stub.GetDevice(voltha_pb2.ID(id=device_id)) |
| 213 | if device.oper_status == voltha_pb2.OperStatus.ACTIVE: |
| 214 | assert device.parent_id |
| 215 | self.default_logical_device_id = device.parent_id |
| 216 | break |
| 217 | print 'waiting for device to be activated...' |
| 218 | sleep(1) |
| 219 | print 'success (logical device id = {})'.format( |
| 220 | self.default_logical_device_id) |
| 221 | |
| 222 | def do_arrive_onus(self, arg): |
| 223 | """ |
| 224 | Simulate the arrival of ONUs |
| 225 | """ |
| 226 | device_id = arg or self.default_device_id |
| 227 | requests.get('http://{}/devices/{}/detect_onus'.format( |
| 228 | self.voltha_sim_rest, device_id |
| 229 | )) |
| 230 | |
| 231 | def do_install_eapol_flow(self, arg): |
| 232 | """ |
| 233 | Install an EAPOL flow on the given logical device. If device is not |
| 234 | given, it will be applied to logical device of the last pre-provisioned |
| 235 | OLT device. |
| 236 | """ |
| 237 | logical_device_id = arg or self.default_logical_device_id |
| 238 | update = FlowTableUpdate( |
| 239 | id=logical_device_id, |
| 240 | flow_mod = mk_simple_flow_mod( |
| 241 | priority=2000, |
| 242 | match_fields=[in_port(101), eth_type(0x888e)], |
| 243 | actions=[ |
| 244 | push_vlan(0x8100), |
| 245 | set_field(vlan_vid(4096 + 4000)), |
| 246 | output(ofp.OFPP_CONTROLLER) |
| 247 | ] |
| 248 | ) |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 249 | ) |
Zsolt Haraszti | d036b7e | 2016-12-23 15:36:01 -0800 | [diff] [blame^] | 250 | stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel()) |
| 251 | res = stub.UpdateLogicalDeviceFlowTable(update) |
| 252 | print 'success', res |
| 253 | |
| 254 | def do_send_simulated_upstream_eapol(self, arg): |
| 255 | """ |
| 256 | Send an EAPOL upstream from a simulated OLT |
| 257 | """ |
| 258 | device_id = arg or self.default_device_id |
| 259 | requests.get('http://{}/devices/{}/test_eapol_in'.format( |
| 260 | self.voltha_sim_rest, device_id |
| 261 | )) |
| 262 | |
| 263 | def do_inject_eapol_start(self, arg): |
| 264 | """ |
| 265 | Send out an an EAPOL start message into the given Unix interface |
| 266 | """ |
| 267 | pass |
Zsolt Haraszti | a133a45 | 2016-12-22 01:26:57 -0800 | [diff] [blame] | 268 | |
| 269 | |
| 270 | if __name__ == '__main__': |
| 271 | c = VolthaCli() |
| 272 | c.load_history() |
| 273 | c.cmdloop() |
| 274 | c.save_history() |