blob: 7b4b241fb9d0d13280b5f8ed24a8133b5efd6052 [file] [log] [blame]
Zsolt Harasztia133a452016-12-22 01:26:57 -08001#!/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 Haraszti80175202016-12-24 00:17:51 -080017
Zsolt Harasztia133a452016-12-22 01:26:57 -080018import readline
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080019from optparse import make_option
Zsolt Haraszti80175202016-12-24 00:17:51 -080020from time import sleep, time
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080021
Zsolt Harasztia133a452016-12-22 01:26:57 -080022import grpc
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080023import requests
24from cmd2 import Cmd, options
25from google.protobuf.empty_pb2 import Empty
Zsolt Harasztia133a452016-12-22 01:26:57 -080026from simplejson import dumps
27
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080028from cli.device import DeviceCli
29from cli.logical_device import LogicalDeviceCli
Zsolt Haraszti80175202016-12-24 00:17:51 -080030from cli.table import TablePrinter, print_pb_table
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080031from voltha.core.flow_decomposer import *
Zsolt Harasztia133a452016-12-22 01:26:57 -080032from voltha.protos import third_party
33from voltha.protos import voltha_pb2
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080034from voltha.protos.openflow_13_pb2 import FlowTableUpdate
35
Zsolt Harasztia133a452016-12-22 01:26:57 -080036_ = third_party
Zsolt Haraszti80175202016-12-24 00:17:51 -080037from cli.utils import pb2dict, dict2line
Zsolt Harasztia133a452016-12-22 01:26:57 -080038
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080039banner = """\
Zsolt Haraszti80175202016-12-24 00:17:51 -080040 _ _ _ _ _
41__ _____| | |_| |_ __ _ __| (_)
42\ V / _ \ | _| ' \/ _` | / _| | |
43 \_/\___/_|\__|_||_\__,_| \__|_|_|
44(to exit type quit or hit Ctrl-D)
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080045"""
Zsolt Harasztia133a452016-12-22 01:26:57 -080046
47class VolthaCli(Cmd):
48
49 prompt = 'voltha'
50 history_file_name = '.voltha_cli_history'
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080051
52 # Settable CLI parameters
53 voltha_grpc = 'localhost:50055'
54 voltha_sim_rest = 'localhost:18880'
Zsolt Harasztia133a452016-12-22 01:26:57 -080055 max_history_lines = 500
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080056 default_device_id = None
57 default_logical_device_id = None
Zsolt Harasztia133a452016-12-22 01:26:57 -080058
59 Cmd.settable.update(dict(
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080060 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 Harasztia133a452016-12-22 01:26:57 -080068 ))
69
Zsolt Haraszti80175202016-12-24 00:17:51 -080070 # cleanup of superflous commands from cmd2
71 del Cmd.do_cmdenvironment
72 # del Cmd.do_eof
73 del Cmd.do_exit
74 del Cmd.do_q
75 del Cmd.do_hi
76 del Cmd.do_l
77 del Cmd.do_li
78 del Cmd.do_r
79 del Cmd.do__load
80 del Cmd.do__relative_load
81 Cmd.do_edit = Cmd.do_ed
82
83
Zsolt Harasztia133a452016-12-22 01:26:57 -080084 def __init__(self, *args, **kw):
Zsolt Haraszti80175202016-12-24 00:17:51 -080085
Zsolt Harasztia133a452016-12-22 01:26:57 -080086 Cmd.__init__(self, *args, **kw)
87 self.prompt = '(' + self.colorize(
Zsolt Haraszti80175202016-12-24 00:17:51 -080088 self.colorize(self.prompt, 'blue'), 'bold') + ') '
Zsolt Harasztia133a452016-12-22 01:26:57 -080089 self.channel = None
Zsolt Haraszti80175202016-12-24 00:17:51 -080090 self.device_ids_cache = None
91 self.device_ids_cache_ts = time()
92 self.logical_device_ids_cache = None
93 self.logical_device_ids_cache_ts = time()
Zsolt Harasztia133a452016-12-22 01:26:57 -080094
95 def load_history(self):
96 """Load saved command history from local history file"""
97 try:
98 with file(self.history_file_name, 'r') as f:
99 for line in f.readlines():
100 stripped_line = line.strip()
101 self.history.append(stripped_line)
102 readline.add_history(stripped_line)
103 except IOError:
104 pass # ignore if file cannot be read
105
106 def save_history(self):
107 try:
Zsolt Haraszti80175202016-12-24 00:17:51 -0800108 with open(self.history_file_name, 'w') as f:
Zsolt Harasztia133a452016-12-22 01:26:57 -0800109 f.write('\n'.join(self.history[-self.max_history_lines:]))
Zsolt Haraszti80175202016-12-24 00:17:51 -0800110 except IOError as e:
111 self.perror('Could not save history in {}: {}'.format(
112 self.history_file_name, e))
Zsolt Harasztia133a452016-12-22 01:26:57 -0800113 else:
Zsolt Haraszti80175202016-12-24 00:17:51 -0800114 self.perror('History saved as {}'.format(
115 self.history_file_name))
116
117 def perror(self, errmsg, statement=None):
118 # Touch it up to make sure error is prefixed and colored
119 Cmd.perror(self, self.colorize('***ERROR: ', 'red') + errmsg,
120 statement)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800121
122 def get_channel(self):
123 if self.channel is None:
124 self.channel = grpc.insecure_channel(self.voltha_grpc)
125 return self.channel
126
Zsolt Haraszti80175202016-12-24 00:17:51 -0800127 # ~~~~~~~~~~~~~~~~~ ACTUAL COMMAND IMPLEMENTATIONS ~~~~~~~~~~~~~~~~~~~~~~~~
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800128
Zsolt Haraszti80175202016-12-24 00:17:51 -0800129 def do_reset_history(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800130 """Reset CLI history"""
131 while self.history:
132 self.history.pop()
133
Zsolt Haraszti80175202016-12-24 00:17:51 -0800134 def do_launch(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800135 """If Voltha is not running yet, launch it"""
Zsolt Haraszti80175202016-12-24 00:17:51 -0800136 raise NotImplementedError('not implemented yet')
Zsolt Harasztia133a452016-12-22 01:26:57 -0800137
Zsolt Haraszti80175202016-12-24 00:17:51 -0800138 def do_restart(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800139 """Launch Voltha, but if it is already running, terminate it first"""
140 pass
141
Zsolt Haraszti80175202016-12-24 00:17:51 -0800142 def do_adapters(self, line):
143 """List loaded adapter"""
144 stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
145 res = stub.ListAdapters(Empty())
146 omit_fields = {}
147 print_pb_table('Adapters:', res.items, omit_fields, self.poutput)
148
149 def get_devices(self):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800150 stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
151 res = stub.ListDevices(Empty())
Zsolt Haraszti80175202016-12-24 00:17:51 -0800152 return res.items
Zsolt Harasztia133a452016-12-22 01:26:57 -0800153
Zsolt Haraszti80175202016-12-24 00:17:51 -0800154 def get_logical_devices(self):
155 stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
156 res = stub.ListLogicalDevices(Empty())
157 return res.items
158
159 def do_devices(self, line):
160 """List devices registered in Voltha"""
161 devices = self.get_devices()
162 omit_fields = {
163 'adapter',
164 'vendor',
165 'model',
166 'hardware_version',
167 'software_version',
168 'firmware_version',
169 'serial_number'
170 }
171 print_pb_table('Devices:', devices, omit_fields, self.poutput)
172
173 def do_logical_devices(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800174 """List logical devices in Voltha"""
175 stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
176 res = stub.ListLogicalDevices(Empty())
Zsolt Haraszti80175202016-12-24 00:17:51 -0800177 omit_fields = {
178 'desc.mfr_desc',
179 'desc.hw_desc',
180 'desc.sw_desc',
181 'desc.dp_desc',
182 'desc.serial_number',
183 'switch_features.capabilities'
184 }
185 print_pb_table('Logical devices:', res.items, omit_fields,
186 self.poutput)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800187
Zsolt Haraszti80175202016-12-24 00:17:51 -0800188 def do_device(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800189 """Enter device level command mode"""
Zsolt Haraszti80175202016-12-24 00:17:51 -0800190 device_id = line.strip() or self.default_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800191 if not device_id:
192 raise Exception('<device-id> parameter needed')
193 sub = DeviceCli(self.get_channel, device_id)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800194 sub.cmdloop()
195
Zsolt Haraszti80175202016-12-24 00:17:51 -0800196 def do_logical_device(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800197 """Enter logical device level command mode"""
Zsolt Haraszti80175202016-12-24 00:17:51 -0800198 logical_device_id = line.strip() or self.default_logical_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800199 if not logical_device_id:
200 raise Exception('<logical-device-id> parameter needed')
201 sub = LogicalDeviceCli(self.get_channel, logical_device_id)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800202 sub.cmdloop()
203
Zsolt Haraszti80175202016-12-24 00:17:51 -0800204 def device_ids(self, force_refresh=False):
205 if force_refresh or self.device_ids is None or \
206 (time() - self.device_ids_cache_ts) > 1:
207 self.device_ids_cache = [d.id for d in self.get_devices()]
208 self.device_ids_cache_ts = time()
209 return self.device_ids_cache
210
211 def logical_device_ids(self, force_refresh=False):
212 if force_refresh or self.logical_device_ids is None or \
213 (time() - self.logical_device_ids_cache_ts) > 1:
214 self.logical_device_ids_cache = [d.id for d
215 in self.get_logical_devices()]
216 self.logical_device_ids_cache_ts = time()
217 return self.logical_device_ids_cache
218
219 def complete_device(self, text, line, begidx, endidx):
220 if not text:
221 completions = self.device_ids()[:]
222 else:
223 completions = [d for d in self.device_ids() if d.startswith(text)]
224 return completions
225
226 def complete_logical_device(self, text, line, begidx, endidx):
227 if not text:
228 completions = self.logical_device_ids()[:]
229 else:
230 completions = [d for d in self.logical_device_ids()
231 if d.startswith(text)]
232 return completions
233
234 def do_pdb(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800235 """Launch PDB debug prompt in CLI (for CLI development)"""
236 from pdb import set_trace
237 set_trace()
238
Zsolt Haraszti80175202016-12-24 00:17:51 -0800239 def do_health(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800240 """Show connectivity status to Voltha status"""
241 stub = voltha_pb2.HealthServiceStub(self.get_channel())
242 res = stub.GetHealthStatus(Empty())
Zsolt Haraszti80175202016-12-24 00:17:51 -0800243 self.poutput(dumps(pb2dict(res), indent=4))
Zsolt Harasztia133a452016-12-22 01:26:57 -0800244
Zsolt Haraszti80175202016-12-24 00:17:51 -0800245 def do_test(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800246 """Enter test mode, which makes a bunch on new commands available"""
247 sub = TestCli(self.history, self.get_channel)
248 sub.cmdloop()
Zsolt Harasztia133a452016-12-22 01:26:57 -0800249
Zsolt Harasztia133a452016-12-22 01:26:57 -0800250
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800251class TestCli(VolthaCli):
252
253 def __init__(self, history, get_channel):
254 VolthaCli.__init__(self)
255 self.history = history
Zsolt Harasztia133a452016-12-22 01:26:57 -0800256 self.get_channel = get_channel
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800257 self.prompt = '(' + self.colorize(self.colorize('test', 'cyan'),
Zsolt Harasztia133a452016-12-22 01:26:57 -0800258 'bold') + ') '
259
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800260 @options([
261 make_option('-t', '--device-type', action="store", dest='device_type',
262 help="Device type", default='simulated_olt'),
263 make_option('-m', '--mac-address', action='store', dest='mac_address',
264 default='00:0c:e2:31:40:00'),
265 make_option('-i', '--ip-address', action='store', dest='ip_address'),
266 ])
Zsolt Haraszti80175202016-12-24 00:17:51 -0800267 def do_preprovision_olt(self, line, opts):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800268 """Preprovision a new OLT with given device type"""
Zsolt Harasztia133a452016-12-22 01:26:57 -0800269 stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800270 kw = dict(type=opts.device_type)
271 if opts.ip_address:
272 kw['ipv4_address'] = opts.ip_address
273 elif opts.mac_address:
274 kw['mac_address'] = opts.mac_address
275 else:
276 raise Exception('Either IP address or Mac Address is needed')
277 device = voltha_pb2.Device(**kw)
278 device = stub.CreateDevice(device)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800279 self.poutput('success (device id = {})'.format(device.id))
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800280 self.default_device_id = device.id
Zsolt Harasztia133a452016-12-22 01:26:57 -0800281
Zsolt Haraszti80175202016-12-24 00:17:51 -0800282 def do_activate_olt(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800283 """
284 Activate an OLT. If the <id> is not provided, it will be on the last
285 pre-provisioned OLT.
286 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800287 device_id = line or self.default_device_id
288 self.poutput('activating {}'.format(device_id))
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800289 stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
290 stub.ActivateDevice(voltha_pb2.ID(id=device_id))
Zsolt Harasztia133a452016-12-22 01:26:57 -0800291
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800292 # try to acquire logical device id
293 while True:
294 device = stub.GetDevice(voltha_pb2.ID(id=device_id))
295 if device.oper_status == voltha_pb2.OperStatus.ACTIVE:
296 assert device.parent_id
297 self.default_logical_device_id = device.parent_id
298 break
Zsolt Haraszti80175202016-12-24 00:17:51 -0800299 self.poutput('waiting for device to be activated...')
300 sleep(.5)
301 self.poutput('success (logical device id = {})'.format(
302 self.default_logical_device_id))
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800303
Zsolt Haraszti80175202016-12-24 00:17:51 -0800304 complete_activate_olt = VolthaCli.complete_device
305
306 def do_arrive_onus(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800307 """
308 Simulate the arrival of ONUs
309 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800310 device_id = line or self.default_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800311 requests.get('http://{}/devices/{}/detect_onus'.format(
312 self.voltha_sim_rest, device_id
313 ))
314
Zsolt Haraszti80175202016-12-24 00:17:51 -0800315 complete_arrive_onus = VolthaCli.complete_device
316
317 def do_install_eapol_flow(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800318 """
319 Install an EAPOL flow on the given logical device. If device is not
320 given, it will be applied to logical device of the last pre-provisioned
321 OLT device.
322 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800323 logical_device_id = line or self.default_logical_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800324 update = FlowTableUpdate(
325 id=logical_device_id,
326 flow_mod = mk_simple_flow_mod(
327 priority=2000,
Zsolt Haraszti80175202016-12-24 00:17:51 -0800328 match_fields=[in_port(241), eth_type(0x888e)],
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800329 actions=[
330 push_vlan(0x8100),
331 set_field(vlan_vid(4096 + 4000)),
332 output(ofp.OFPP_CONTROLLER)
333 ]
334 )
Zsolt Harasztia133a452016-12-22 01:26:57 -0800335 )
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800336 stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
337 res = stub.UpdateLogicalDeviceFlowTable(update)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800338 self.poutput('success ({})'.format(res))
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800339
Zsolt Haraszti80175202016-12-24 00:17:51 -0800340 complete_install_eapol_flow = VolthaCli.complete_logical_device
341
342 def do_send_simulated_upstream_eapol(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800343 """
344 Send an EAPOL upstream from a simulated OLT
345 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800346 device_id = line or self.default_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800347 requests.get('http://{}/devices/{}/test_eapol_in'.format(
348 self.voltha_sim_rest, device_id
349 ))
350
Zsolt Haraszti80175202016-12-24 00:17:51 -0800351 complete_send_simulated_upstream_eapol = VolthaCli.complete_device
352
353 def do_inject_eapol_start(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800354 """
355 Send out an an EAPOL start message into the given Unix interface
356 """
357 pass
Zsolt Harasztia133a452016-12-22 01:26:57 -0800358
359
360if __name__ == '__main__':
361 c = VolthaCli()
Zsolt Haraszti80175202016-12-24 00:17:51 -0800362 c.poutput(banner)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800363 c.load_history()
364 c.cmdloop()
365 c.save_history()