blob: 8e51851168d6292873ca8b9d12cae0d9001119c7 [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 Haraszti9b485fb2016-12-26 23:11:15 -080017import argparse
18import os
Zsolt Harasztia133a452016-12-22 01:26:57 -080019import readline
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040020import sys
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080021from optparse import make_option
Zsolt Haraszti80175202016-12-24 00:17:51 -080022from time import sleep, time
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080023
Zsolt Harasztia133a452016-12-22 01:26:57 -080024import grpc
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080025import requests
26from cmd2 import Cmd, options
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040027from consul import Consul
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080028from google.protobuf.empty_pb2 import Empty
Zsolt Harasztia133a452016-12-22 01:26:57 -080029from simplejson import dumps
30
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080031from cli.device import DeviceCli
Nikolay Titov89004ec2017-06-19 18:22:42 -040032from cli.xpon import XponCli
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040033from cli.alarm_filters import AlarmFiltersCli
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080034from cli.logical_device import LogicalDeviceCli
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040035from cli.table import print_pb_list_as_table
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080036from voltha.core.flow_decomposer import *
Zsolt Harasztia133a452016-12-22 01:26:57 -080037from voltha.protos import third_party
38from voltha.protos import voltha_pb2
Zsolt Haraszti85f12852016-12-24 08:30:58 -080039from voltha.protos.openflow_13_pb2 import FlowTableUpdate, FlowGroupTableUpdate
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080040
Zsolt Harasztia133a452016-12-22 01:26:57 -080041_ = third_party
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040042from cli.utils import pb2dict
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -080043
44defs = dict(
45 # config=os.environ.get('CONFIG', './cli.yml'),
46 consul=os.environ.get('CONSUL', 'localhost:8500'),
47 voltha_grpc_endpoint=os.environ.get('VOLTHA_GRPC_ENDPOINT',
48 'localhost:50055'),
49 voltha_sim_rest_endpoint=os.environ.get('VOLTHA_SIM_REST_ENDPOINT',
50 'localhost:18880'),
khenaidoo108f05c2017-07-06 11:15:29 -040051 global_request=os.environ.get('GLOBAL_REQUEST', False)
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -080052)
53
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080054banner = """\
Zsolt Haraszti313c4be2016-12-27 11:06:53 -080055 _ _ _ ___ _ ___
56__ _____| | |_| |_ __ _ / __| | |_ _|
57\ V / _ \ | _| ' \/ _` | | (__| |__ | |
58 \_/\___/_|\__|_||_\__,_| \___|____|___|
Zsolt Haraszti80175202016-12-24 00:17:51 -080059(to exit type quit or hit Ctrl-D)
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080060"""
Zsolt Harasztia133a452016-12-22 01:26:57 -080061
Zsolt Harasztia133a452016-12-22 01:26:57 -080062
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040063class VolthaCli(Cmd):
Zsolt Harasztia133a452016-12-22 01:26:57 -080064 prompt = 'voltha'
65 history_file_name = '.voltha_cli_history'
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080066
67 # Settable CLI parameters
68 voltha_grpc = 'localhost:50055'
69 voltha_sim_rest = 'localhost:18880'
khenaidoo108f05c2017-07-06 11:15:29 -040070 global_request = False
Zsolt Harasztia133a452016-12-22 01:26:57 -080071 max_history_lines = 500
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080072 default_device_id = None
73 default_logical_device_id = None
Zsolt Harasztia133a452016-12-22 01:26:57 -080074
75 Cmd.settable.update(dict(
Zsolt Harasztid036b7e2016-12-23 15:36:01 -080076 voltha_grpc='Voltha GRPC endpoint in form of <host>:<port>',
77 voltha_sim_rest='Voltha simulation back door for testing in form '
78 'of <host>:<port>',
79 max_history_lines='Maximum number of history lines stored across '
80 'sessions',
81 default_device_id='Device id used when no device id is specified',
82 default_logical_device_id='Logical device id used when no device id '
83 'is specified',
Zsolt Harasztia133a452016-12-22 01:26:57 -080084 ))
85
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -080086 # cleanup of superfluous commands from cmd2
Zsolt Haraszti80175202016-12-24 00:17:51 -080087 del Cmd.do_cmdenvironment
Steve Crooks05f24522017-02-27 13:32:27 -050088 del Cmd.do_load
Zsolt Haraszti80175202016-12-24 00:17:51 -080089 del Cmd.do__relative_load
Zsolt Haraszti80175202016-12-24 00:17:51 -080090
khenaidoo108f05c2017-07-06 11:15:29 -040091 def __init__(self, voltha_grpc, voltha_sim_rest, global_request=False):
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -080092 VolthaCli.voltha_grpc = voltha_grpc
93 VolthaCli.voltha_sim_rest = voltha_sim_rest
khenaidoo108f05c2017-07-06 11:15:29 -040094 VolthaCli.global_request = global_request
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -080095 Cmd.__init__(self)
Zsolt Harasztia133a452016-12-22 01:26:57 -080096 self.prompt = '(' + self.colorize(
Zsolt Haraszti80175202016-12-24 00:17:51 -080097 self.colorize(self.prompt, 'blue'), 'bold') + ') '
Zsolt Harasztia133a452016-12-22 01:26:57 -080098 self.channel = None
khenaidoo108f05c2017-07-06 11:15:29 -040099 self.stub = None
Zsolt Haraszti80175202016-12-24 00:17:51 -0800100 self.device_ids_cache = None
101 self.device_ids_cache_ts = time()
102 self.logical_device_ids_cache = None
103 self.logical_device_ids_cache_ts = time()
Zsolt Harasztia133a452016-12-22 01:26:57 -0800104
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -0800105 # we override cmd2's method to avoid its optparse conflicting with our
106 # command line parsing
107 def cmdloop(self):
108 self._cmdloop()
109
Zsolt Harasztia133a452016-12-22 01:26:57 -0800110 def load_history(self):
111 """Load saved command history from local history file"""
112 try:
113 with file(self.history_file_name, 'r') as f:
114 for line in f.readlines():
115 stripped_line = line.strip()
116 self.history.append(stripped_line)
117 readline.add_history(stripped_line)
118 except IOError:
119 pass # ignore if file cannot be read
120
121 def save_history(self):
122 try:
Zsolt Haraszti80175202016-12-24 00:17:51 -0800123 with open(self.history_file_name, 'w') as f:
Zsolt Harasztia133a452016-12-22 01:26:57 -0800124 f.write('\n'.join(self.history[-self.max_history_lines:]))
Zsolt Haraszti80175202016-12-24 00:17:51 -0800125 except IOError as e:
126 self.perror('Could not save history in {}: {}'.format(
127 self.history_file_name, e))
Zsolt Harasztia133a452016-12-22 01:26:57 -0800128 else:
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -0800129 self.poutput('History saved as {}'.format(
Zsolt Haraszti80175202016-12-24 00:17:51 -0800130 self.history_file_name))
131
132 def perror(self, errmsg, statement=None):
133 # Touch it up to make sure error is prefixed and colored
134 Cmd.perror(self, self.colorize('***ERROR: ', 'red') + errmsg,
135 statement)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800136
137 def get_channel(self):
138 if self.channel is None:
139 self.channel = grpc.insecure_channel(self.voltha_grpc)
140 return self.channel
141
khenaidoo108f05c2017-07-06 11:15:29 -0400142 def get_stub(self):
143 if self.stub is None:
144 self.stub = \
145 voltha_pb2.VolthaGlobalServiceStub(self.get_channel()) \
146 if self.global_request else \
147 voltha_pb2.VolthaLocalServiceStub(self.get_channel())
148 return self.stub
149
Zsolt Haraszti80175202016-12-24 00:17:51 -0800150 # ~~~~~~~~~~~~~~~~~ ACTUAL COMMAND IMPLEMENTATIONS ~~~~~~~~~~~~~~~~~~~~~~~~
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800151
Zsolt Haraszti80175202016-12-24 00:17:51 -0800152 def do_reset_history(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800153 """Reset CLI history"""
154 while self.history:
155 self.history.pop()
156
Zsolt Haraszti80175202016-12-24 00:17:51 -0800157 def do_launch(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800158 """If Voltha is not running yet, launch it"""
Zsolt Haraszti80175202016-12-24 00:17:51 -0800159 raise NotImplementedError('not implemented yet')
Zsolt Harasztia133a452016-12-22 01:26:57 -0800160
Zsolt Haraszti80175202016-12-24 00:17:51 -0800161 def do_restart(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800162 """Launch Voltha, but if it is already running, terminate it first"""
163 pass
164
Zsolt Haraszti80175202016-12-24 00:17:51 -0800165 def do_adapters(self, line):
166 """List loaded adapter"""
khenaidoo108f05c2017-07-06 11:15:29 -0400167 stub = self.get_stub()
Zsolt Haraszti80175202016-12-24 00:17:51 -0800168 res = stub.ListAdapters(Empty())
Sergio Slobodrian6e9fb692017-03-17 14:46:33 -0400169 omit_fields = {'config.log_level', 'logical_device_ids'}
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800170 print_pb_list_as_table('Adapters:', res.items, omit_fields, self.poutput)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800171
172 def get_devices(self):
khenaidoo108f05c2017-07-06 11:15:29 -0400173 stub = self.get_stub()
Zsolt Harasztia133a452016-12-22 01:26:57 -0800174 res = stub.ListDevices(Empty())
Zsolt Haraszti80175202016-12-24 00:17:51 -0800175 return res.items
Zsolt Harasztia133a452016-12-22 01:26:57 -0800176
Zsolt Haraszti80175202016-12-24 00:17:51 -0800177 def get_logical_devices(self):
khenaidoo108f05c2017-07-06 11:15:29 -0400178 stub = self.get_stub()
Zsolt Haraszti80175202016-12-24 00:17:51 -0800179 res = stub.ListLogicalDevices(Empty())
180 return res.items
181
182 def do_devices(self, line):
183 """List devices registered in Voltha"""
184 devices = self.get_devices()
185 omit_fields = {
186 'adapter',
187 'vendor',
188 'model',
189 'hardware_version',
ggowdru236bd952017-06-20 20:32:55 -0700190 'images',
Zsolt Haraszti80175202016-12-24 00:17:51 -0800191 'firmware_version',
Sergio Slobodriana95f99b2017-03-21 10:22:47 -0400192 'serial_number'
Zsolt Haraszti80175202016-12-24 00:17:51 -0800193 }
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800194 print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800195
196 def do_logical_devices(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800197 """List logical devices in Voltha"""
khenaidoo108f05c2017-07-06 11:15:29 -0400198 stub = self.get_stub()
Zsolt Harasztia133a452016-12-22 01:26:57 -0800199 res = stub.ListLogicalDevices(Empty())
Zsolt Haraszti80175202016-12-24 00:17:51 -0800200 omit_fields = {
201 'desc.mfr_desc',
202 'desc.hw_desc',
203 'desc.sw_desc',
204 'desc.dp_desc',
205 'desc.serial_number',
Sergio Slobodriana95f99b2017-03-21 10:22:47 -0400206 'switch_features.capabilities'
Zsolt Haraszti80175202016-12-24 00:17:51 -0800207 }
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800208 print_pb_list_as_table('Logical devices:', res.items, omit_fields,
209 self.poutput)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800210
Zsolt Haraszti80175202016-12-24 00:17:51 -0800211 def do_device(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800212 """Enter device level command mode"""
Zsolt Haraszti80175202016-12-24 00:17:51 -0800213 device_id = line.strip() or self.default_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800214 if not device_id:
215 raise Exception('<device-id> parameter needed')
khenaidoo108f05c2017-07-06 11:15:29 -0400216 sub = DeviceCli(device_id, self.get_stub)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800217 sub.cmdloop()
218
Zsolt Haraszti80175202016-12-24 00:17:51 -0800219 def do_logical_device(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800220 """Enter logical device level command mode"""
Zsolt Haraszti80175202016-12-24 00:17:51 -0800221 logical_device_id = line.strip() or self.default_logical_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800222 if not logical_device_id:
223 raise Exception('<logical-device-id> parameter needed')
khenaidoo108f05c2017-07-06 11:15:29 -0400224 sub = LogicalDeviceCli(logical_device_id, self.get_stub)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800225 sub.cmdloop()
226
Zsolt Haraszti80175202016-12-24 00:17:51 -0800227 def device_ids(self, force_refresh=False):
228 if force_refresh or self.device_ids is None or \
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400229 (time() - self.device_ids_cache_ts) > 1:
Zsolt Haraszti80175202016-12-24 00:17:51 -0800230 self.device_ids_cache = [d.id for d in self.get_devices()]
231 self.device_ids_cache_ts = time()
232 return self.device_ids_cache
233
234 def logical_device_ids(self, force_refresh=False):
235 if force_refresh or self.logical_device_ids is None or \
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400236 (time() - self.logical_device_ids_cache_ts) > 1:
Zsolt Haraszti80175202016-12-24 00:17:51 -0800237 self.logical_device_ids_cache = [d.id for d
238 in self.get_logical_devices()]
239 self.logical_device_ids_cache_ts = time()
240 return self.logical_device_ids_cache
241
242 def complete_device(self, text, line, begidx, endidx):
243 if not text:
244 completions = self.device_ids()[:]
245 else:
246 completions = [d for d in self.device_ids() if d.startswith(text)]
247 return completions
248
249 def complete_logical_device(self, text, line, begidx, endidx):
250 if not text:
251 completions = self.logical_device_ids()[:]
252 else:
253 completions = [d for d in self.logical_device_ids()
254 if d.startswith(text)]
255 return completions
256
Nikolay Titov89004ec2017-06-19 18:22:42 -0400257 def do_xpon(self, line):
258 """xpon <optional> [device_ID] - Enter xpon level command mode"""
259 device_id = line.strip()
Nikolay Titov3f0c9dd2017-07-17 17:37:25 -0400260 if device_id:
261 stub = self.get_stub()
262 try:
263 res = stub.GetDevice(voltha_pb2.ID(id=device_id))
264 except Exception:
265 self.poutput(self.colorize('Error: ', 'red') + \
266 'No device id ' + self.colorize(device_id, 'blue') + \
267 ' is found')
268 return
269 sub = XponCli(self.get_channel, device_id)
Nikolay Titov89004ec2017-06-19 18:22:42 -0400270 sub.cmdloop()
271
Zsolt Haraszti80175202016-12-24 00:17:51 -0800272 def do_pdb(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800273 """Launch PDB debug prompt in CLI (for CLI development)"""
274 from pdb import set_trace
275 set_trace()
276
Zsolt Haraszti80175202016-12-24 00:17:51 -0800277 def do_health(self, line):
Zsolt Harasztia133a452016-12-22 01:26:57 -0800278 """Show connectivity status to Voltha status"""
279 stub = voltha_pb2.HealthServiceStub(self.get_channel())
280 res = stub.GetHealthStatus(Empty())
Zsolt Haraszti80175202016-12-24 00:17:51 -0800281 self.poutput(dumps(pb2dict(res), indent=4))
Zsolt Harasztia133a452016-12-22 01:26:57 -0800282
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800283 @options([
284 make_option('-t', '--device-type', action="store", dest='device_type',
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400285 help="Device type", default='simulated_olt'),
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800286 make_option('-m', '--mac-address', action='store', dest='mac_address',
287 default='00:0c:e2:31:40:00'),
288 make_option('-i', '--ip-address', action='store', dest='ip_address'),
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800289 make_option('-H', '--host_and_port', action='store',
290 dest='host_and_port'),
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800291 ])
Zsolt Haraszti80175202016-12-24 00:17:51 -0800292 def do_preprovision_olt(self, line, opts):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800293 """Preprovision a new OLT with given device type"""
khenaidoo108f05c2017-07-06 11:15:29 -0400294 stub = self.get_stub()
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800295 kw = dict(type=opts.device_type)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800296 if opts.host_and_port:
297 kw['host_and_port'] = opts.host_and_port
298 elif opts.ip_address:
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800299 kw['ipv4_address'] = opts.ip_address
300 elif opts.mac_address:
301 kw['mac_address'] = opts.mac_address
302 else:
303 raise Exception('Either IP address or Mac Address is needed')
Chip Boling90b224d2017-06-02 11:51:48 -0500304 # Pass any extra arguments past '--' to the device as custom arguments
305 kw['extra_args'] = line
306
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800307 device = voltha_pb2.Device(**kw)
308 device = stub.CreateDevice(device)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800309 self.poutput('success (device id = {})'.format(device.id))
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800310 self.default_device_id = device.id
Zsolt Harasztia133a452016-12-22 01:26:57 -0800311
Khen Nursimulud068d812017-03-06 11:44:18 -0500312 def do_enable(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800313 """
Khen Nursimulud068d812017-03-06 11:44:18 -0500314 Enable a device. If the <id> is not provided, it will be on the last
315 pre-provisioned device.
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800316 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800317 device_id = line or self.default_device_id
Khen Nursimulu29e75502017-03-07 17:26:50 -0500318 self.poutput('enabling {}'.format(device_id))
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400319 try:
khenaidoo108f05c2017-07-06 11:15:29 -0400320 stub = self.get_stub()
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400321 stub.EnableDevice(voltha_pb2.ID(id=device_id))
Zsolt Harasztia133a452016-12-22 01:26:57 -0800322
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400323 while True:
324 device = stub.GetDevice(voltha_pb2.ID(id=device_id))
325 # If this is an OLT then acquire logical device id
326 if device.oper_status == voltha_pb2.OperStatus.ACTIVE:
327 if device.type.endswith('_olt'):
328 assert device.parent_id
329 self.default_logical_device_id = device.parent_id
330 self.poutput('success (logical device id = {})'.format(
331 self.default_logical_device_id))
332 else:
333 self.poutput('success (device id = {})'.format(device.id))
334 break
335 self.poutput('waiting for device to be enabled...')
336 sleep(.5)
337 except Exception, e:
338 self.poutput('Error enabling {}. Error:{}'.format(device_id, e))
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800339
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800340 complete_activate_olt = complete_device
341
Khen Nursimulud068d812017-03-06 11:44:18 -0500342 def do_reboot(self, line):
343 """
344 Rebooting a device. ID of the device needs to be provided
345 """
346 device_id = line or self.default_device_id
347 self.poutput('rebooting {}'.format(device_id))
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400348 try:
khenaidoo108f05c2017-07-06 11:15:29 -0400349 stub = self.get_stub()
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400350 stub.RebootDevice(voltha_pb2.ID(id=device_id))
351 self.poutput('rebooted {}'.format(device_id))
352 except Exception, e:
353 self.poutput('Error rebooting {}. Error:{}'.format(device_id, e))
Khen Nursimulud068d812017-03-06 11:44:18 -0500354
sathishg5ae86222017-06-28 15:16:29 +0530355 def do_self_test(self, line):
356 """
357 Self Test a device. ID of the device needs to be provided
358 """
359 device_id = line or self.default_device_id
360 self.poutput('Self Testing {}'.format(device_id))
361 try:
khenaidoo108f05c2017-07-06 11:15:29 -0400362 stub = self.get_stub()
sathishg5ae86222017-06-28 15:16:29 +0530363 res = stub.SelfTest(voltha_pb2.ID(id=device_id))
364 self.poutput('Self Tested {}'.format(device_id))
365 self.poutput(dumps(pb2dict(res), indent=4))
366 except Exception, e:
367 self.poutput('Error in self test {}. Error:{}'.format(device_id, e))
368
Khen Nursimulud068d812017-03-06 11:44:18 -0500369 def do_delete(self, line):
370 """
371 Deleting a device. ID of the device needs to be provided
372 """
373 device_id = line or self.default_device_id
374 self.poutput('deleting {}'.format(device_id))
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400375 try:
khenaidoo108f05c2017-07-06 11:15:29 -0400376 stub = self.get_stub()
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400377 stub.DeleteDevice(voltha_pb2.ID(id=device_id))
378 self.poutput('deleted {}'.format(device_id))
379 except Exception, e:
380 self.poutput('Error deleting {}. Error:{}'.format(device_id, e))
Khen Nursimulud068d812017-03-06 11:44:18 -0500381
382 def do_disable(self, line):
383 """
384 Disable a device. ID of the device needs to be provided
385 """
386 device_id = line
387 self.poutput('disabling {}'.format(device_id))
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400388 try:
khenaidoo108f05c2017-07-06 11:15:29 -0400389 stub = self.get_stub()
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400390 stub.DisableDevice(voltha_pb2.ID(id=device_id))
Khen Nursimulud068d812017-03-06 11:44:18 -0500391
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400392 # Do device query and verify that the device admin status is
393 # DISABLED and Operational Status is unknown
394 device = stub.GetDevice(voltha_pb2.ID(id=device_id))
395 if device.oper_status == voltha_pb2.OperStatus.UNKNOWN and \
396 device.admin_state == voltha_pb2.AdminState.DISABLED:
397 self.poutput('disabled successfully {}'.format(device_id))
398 else:
399 self.poutput('disabling failed {}. Admin State:{} '
400 'Operation State: {}'.format(device_id,
401 device.admin_state,
402 device.oper_status))
403 except Exception, e:
404 self.poutput('Error disabling {}. Error:{}'.format(device_id, e))
Khen Nursimulud068d812017-03-06 11:44:18 -0500405
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800406 def do_test(self, line):
407 """Enter test mode, which makes a bunch on new commands available"""
khenaidoo108f05c2017-07-06 11:15:29 -0400408 sub = TestCli(self.history, self.voltha_grpc,
409 self.get_stub, self.voltha_sim_rest)
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800410 sub.cmdloop()
411
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400412 def do_alarm_filters(self, line):
khenaidoo108f05c2017-07-06 11:15:29 -0400413 sub = AlarmFiltersCli(self.get_stub)
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400414 sub.cmdloop()
415
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800416
417class TestCli(VolthaCli):
khenaidoo108f05c2017-07-06 11:15:29 -0400418 def __init__(self, history, voltha_grpc, get_stub, voltha_sim_rest):
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800419 VolthaCli.__init__(self, voltha_grpc, voltha_sim_rest)
420 self.history = history
khenaidoo108f05c2017-07-06 11:15:29 -0400421 self.get_stub = get_stub
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800422 self.prompt = '(' + self.colorize(self.colorize('test', 'cyan'),
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400423 'bold') + ') '
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800424
425 def get_device(self, device_id, depth=0):
khenaidoo108f05c2017-07-06 11:15:29 -0400426 stub = self.get_stub()
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800427 res = stub.GetDevice(voltha_pb2.ID(id=device_id),
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400428 metadata=(('get-depth', str(depth)),))
Zsolt Haraszti50cae7d2017-01-08 22:27:07 -0800429 return res
Zsolt Haraszti80175202016-12-24 00:17:51 -0800430
431 def do_arrive_onus(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800432 """
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800433 Simulate the arrival of ONUs (available only on simulated_olt)
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800434 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800435 device_id = line or self.default_device_id
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800436
437 # verify that device is of type simulated_olt
438 device = self.get_device(device_id)
439 assert device.type == 'simulated_olt', (
440 'Cannot use it on this device type (only on simulated_olt type)')
441
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800442 requests.get('http://{}/devices/{}/detect_onus'.format(
443 self.voltha_sim_rest, device_id
444 ))
445
Zsolt Haraszti80175202016-12-24 00:17:51 -0800446 complete_arrive_onus = VolthaCli.complete_device
447
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800448 def get_logical_ports(self, logical_device_id):
449 """
450 Return the NNI port number and the first usable UNI port of logical
451 device, and the vlan associated with the latter.
452 """
khenaidoo108f05c2017-07-06 11:15:29 -0400453 stub = self.get_stub()
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800454 ports = stub.ListLogicalDevicePorts(
455 voltha_pb2.ID(id=logical_device_id)).items
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800456 nni = None
457 unis = []
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800458 for port in ports:
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800459 if port.root_port:
460 assert nni is None, "There shall be only one root port"
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800461 nni = port.ofp_port.port_no
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800462 else:
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800463 uni = port.ofp_port.port_no
464 uni_device = self.get_device(port.device_id)
465 vlan = uni_device.vlan
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800466 unis.append((uni, vlan))
467
468 assert nni is not None, "No NNI port found"
469 assert unis, "Not a single UNI?"
470
471 return nni, unis
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800472
Zsolt Haraszti80175202016-12-24 00:17:51 -0800473 def do_install_eapol_flow(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800474 """
475 Install an EAPOL flow on the given logical device. If device is not
476 given, it will be applied to logical device of the last pre-provisioned
477 OLT device.
478 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800479 logical_device_id = line or self.default_logical_device_id
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800480
481 # gather NNI and UNI port IDs
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800482 nni_port_no, unis = self.get_logical_ports(logical_device_id)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800483
484 # construct and push flow rule
khenaidoo108f05c2017-07-06 11:15:29 -0400485 stub = self.get_stub()
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800486 for uni_port_no, _ in unis:
487 update = FlowTableUpdate(
488 id=logical_device_id,
489 flow_mod=mk_simple_flow_mod(
490 priority=2000,
491 match_fields=[in_port(uni_port_no), eth_type(0x888e)],
492 actions=[
493 # push_vlan(0x8100),
494 # set_field(vlan_vid(4096 + 4000)),
495 output(ofp.OFPP_CONTROLLER)
496 ]
497 )
498 )
499 res = stub.UpdateLogicalDeviceFlowTable(update)
500 self.poutput('success for uni {} ({})'.format(uni_port_no, res))
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800501
Zsolt Haraszti80175202016-12-24 00:17:51 -0800502 complete_install_eapol_flow = VolthaCli.complete_logical_device
503
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800504 def do_install_all_controller_bound_flows(self, line):
505 """
506 Install all flow rules for controller bound flows, including EAPOL,
507 IGMP and DHCP. If device is not given, it will be applied to logical
508 device of the last pre-provisioned OLT device.
509 """
510 logical_device_id = line or self.default_logical_device_id
511
512 # gather NNI and UNI port IDs
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800513 nni_port_no, unis = self.get_logical_ports(logical_device_id)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800514
515 # construct and push flow rules
khenaidoo108f05c2017-07-06 11:15:29 -0400516 stub = self.get_stub()
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800517
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800518 for uni_port_no, _ in unis:
519 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
520 id=logical_device_id,
521 flow_mod=mk_simple_flow_mod(
522 priority=2000,
523 match_fields=[
524 in_port(uni_port_no),
525 eth_type(0x888e)
526 ],
527 actions=[output(ofp.OFPP_CONTROLLER)]
528 )
529 ))
530 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
531 id=logical_device_id,
532 flow_mod=mk_simple_flow_mod(
533 priority=1000,
534 match_fields=[
535 in_port(uni_port_no),
536 eth_type(0x800),
537 ip_proto(2)
538 ],
539 actions=[output(ofp.OFPP_CONTROLLER)]
540 )
541 ))
542 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
543 id=logical_device_id,
544 flow_mod=mk_simple_flow_mod(
545 priority=1000,
546 match_fields=[
547 in_port(uni_port_no),
548 eth_type(0x800),
549 ip_proto(17),
550 udp_dst(67)
551 ],
552 actions=[output(ofp.OFPP_CONTROLLER)]
553 )
554 ))
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800555 self.poutput('success')
556
557 complete_install_all_controller_bound_flows = \
558 VolthaCli.complete_logical_device
559
560 def do_install_all_sample_flows(self, line):
561 """
562 Install all flows that are representative of the virtualized access
563 scenario in a PON network.
564 """
565 logical_device_id = line or self.default_logical_device_id
566
567 # gather NNI and UNI port IDs
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800568 nni_port_no, unis = self.get_logical_ports(logical_device_id)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800569
570 # construct and push flow rules
khenaidoo108f05c2017-07-06 11:15:29 -0400571 stub = self.get_stub()
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800572
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800573 for uni_port_no, c_vid in unis:
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800574 # Controller-bound flows
575 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
576 id=logical_device_id,
577 flow_mod=mk_simple_flow_mod(
578 priority=2000,
579 match_fields=[in_port(uni_port_no), eth_type(0x888e)],
580 actions=[
581 # push_vlan(0x8100),
582 # set_field(vlan_vid(4096 + 4000)),
583 output(ofp.OFPP_CONTROLLER)
584 ]
585 )
586 ))
587 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
588 id=logical_device_id,
589 flow_mod=mk_simple_flow_mod(
590 priority=1000,
591 match_fields=[eth_type(0x800), ip_proto(2)],
592 actions=[output(ofp.OFPP_CONTROLLER)]
593 )
594 ))
595 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
596 id=logical_device_id,
597 flow_mod=mk_simple_flow_mod(
598 priority=1000,
599 match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
600 actions=[output(ofp.OFPP_CONTROLLER)]
601 )
602 ))
603
604 # Unicast flows:
605 # Downstream flow 1
606 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
607 id=logical_device_id,
608 flow_mod=mk_simple_flow_mod(
609 priority=500,
610 match_fields=[
611 in_port(nni_port_no),
612 vlan_vid(4096 + 1000),
613 metadata(c_vid) # here to mimic an ONOS artifact
614 ],
615 actions=[pop_vlan()],
616 next_table_id=1
617 )
618 ))
619 # Downstream flow 2
620 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
621 id=logical_device_id,
622 flow_mod=mk_simple_flow_mod(
623 priority=500,
624 table_id=1,
625 match_fields=[in_port(nni_port_no), vlan_vid(4096 + c_vid)],
626 actions=[set_field(vlan_vid(4096 + 0)), output(uni_port_no)]
627 )
628 ))
629 # Upstream flow 1 for 0-tagged case
630 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
631 id=logical_device_id,
632 flow_mod=mk_simple_flow_mod(
633 priority=500,
634 match_fields=[in_port(uni_port_no), vlan_vid(4096 + 0)],
635 actions=[set_field(vlan_vid(4096 + c_vid))],
636 next_table_id=1
637 )
638 ))
639 # Upstream flow 1 for untagged case
640 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
641 id=logical_device_id,
642 flow_mod=mk_simple_flow_mod(
643 priority=500,
644 match_fields=[in_port(uni_port_no), vlan_vid(0)],
645 actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + c_vid))],
646 next_table_id=1
647 )
648 ))
649 # Upstream flow 2 for s-tag
650 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
651 id=logical_device_id,
652 flow_mod=mk_simple_flow_mod(
653 priority=500,
654 table_id=1,
655 match_fields=[in_port(uni_port_no), vlan_vid(4096 + c_vid)],
656 actions=[
657 push_vlan(0x8100),
658 set_field(vlan_vid(4096 + 1000)),
659 output(nni_port_no)
660 ]
661 )
662 ))
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800663
664 # Push a few multicast flows
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800665 # 1st with one bucket for our uni 0
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800666 stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
667 id=logical_device_id,
668 group_mod=mk_multicast_group_mod(
669 group_id=1,
670 buckets=[
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800671 ofp.ofp_bucket(actions=[
672 pop_vlan(),
673 output(unis[0][0])
674 ])
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800675 ]
676 )
677 ))
678 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
679 id=logical_device_id,
680 flow_mod=mk_simple_flow_mod(
681 priority=1000,
682 match_fields=[
683 in_port(nni_port_no),
684 eth_type(0x800),
685 vlan_vid(4096 + 140),
686 ipv4_dst(0xe4010101)
687 ],
688 actions=[group(1)]
689 )
690 ))
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800691
692 # 2nd with one bucket for uni 0 and 1
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800693 stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
694 id=logical_device_id,
695 group_mod=mk_multicast_group_mod(
696 group_id=2,
697 buckets=[
Nathan Knuth6b7b6ff2017-02-12 03:30:48 -0800698 ofp.ofp_bucket(actions=[pop_vlan(), output(unis[0][0])])
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400699 # ofp.ofp_bucket(actions=[pop_vlan(), output(unis[1][0])])
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800700 ]
701 )
702 ))
703 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
704 id=logical_device_id,
705 flow_mod=mk_simple_flow_mod(
706 priority=1000,
707 match_fields=[
708 in_port(nni_port_no),
709 eth_type(0x800),
710 vlan_vid(4096 + 140),
711 ipv4_dst(0xe4020202)
712 ],
713 actions=[group(2)]
714 )
715 ))
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800716
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800717 # 3rd with empty bucket
718 stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
719 id=logical_device_id,
720 group_mod=mk_multicast_group_mod(
721 group_id=3,
722 buckets=[]
723 )
724 ))
725 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
726 id=logical_device_id,
727 flow_mod=mk_simple_flow_mod(
728 priority=1000,
729 match_fields=[
730 in_port(nni_port_no),
731 eth_type(0x800),
732 vlan_vid(4096 + 140),
733 ipv4_dst(0xe4030303)
734 ],
735 actions=[group(3)]
736 )
737 ))
738
739 self.poutput('success')
740
741 complete_install_all_sample_flows = VolthaCli.complete_logical_device
742
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600743 def do_install_dhcp_flows(self, line):
744 """
745 Install all dhcp flows that are representative of the virtualized access
746 scenario in a PON network.
747 """
748 logical_device_id = line or self.default_logical_device_id
749
750 # gather NNI and UNI port IDs
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800751 nni_port_no, unis = self.get_logical_ports(logical_device_id)
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600752
753 # construct and push flow rules
khenaidoo108f05c2017-07-06 11:15:29 -0400754 stub = self.get_stub()
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600755
756 # Controller-bound flows
Zsolt Harasztib9a5f752017-02-11 06:07:08 -0800757 for uni_port_no, _ in unis:
758 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
759 id=logical_device_id,
760 flow_mod=mk_simple_flow_mod(
761 priority=1000,
762 match_fields=[
763 in_port(uni_port_no),
764 eth_type(0x800),
765 ip_proto(17),
766 udp_dst(67)
767 ],
768 actions=[output(ofp.OFPP_CONTROLLER)]
769 )
770 ))
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600771
772 self.poutput('success')
773
774 complete_install_dhcp_flows = VolthaCli.complete_logical_device
775
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800776 def do_delete_all_flows(self, line):
777 """
778 Remove all flows and flow groups from given logical device
779 """
780 logical_device_id = line or self.default_logical_device_id
khenaidoo108f05c2017-07-06 11:15:29 -0400781 stub = self.get_stub()
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800782 stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
783 id=logical_device_id,
784 flow_mod=ofp.ofp_flow_mod(
785 command=ofp.OFPFC_DELETE,
786 table_id=ofp.OFPTT_ALL,
787 cookie_mask=0,
788 out_port=ofp.OFPP_ANY,
789 out_group=ofp.OFPG_ANY
790 )
791 ))
792 stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
793 id=logical_device_id,
794 group_mod=ofp.ofp_group_mod(
795 command=ofp.OFPGC_DELETE,
796 group_id=ofp.OFPG_ALL
797 )
798 ))
799 self.poutput('success')
800
801 complete_delete_all_flows = VolthaCli.complete_logical_device
802
Zsolt Haraszti80175202016-12-24 00:17:51 -0800803 def do_send_simulated_upstream_eapol(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800804 """
805 Send an EAPOL upstream from a simulated OLT
806 """
Zsolt Haraszti80175202016-12-24 00:17:51 -0800807 device_id = line or self.default_device_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800808 requests.get('http://{}/devices/{}/test_eapol_in'.format(
809 self.voltha_sim_rest, device_id
810 ))
811
Zsolt Haraszti80175202016-12-24 00:17:51 -0800812 complete_send_simulated_upstream_eapol = VolthaCli.complete_device
813
814 def do_inject_eapol_start(self, line):
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800815 """
816 Send out an an EAPOL start message into the given Unix interface
817 """
818 pass
Zsolt Harasztia133a452016-12-22 01:26:57 -0800819
820
821if __name__ == '__main__':
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -0800822
823 parser = argparse.ArgumentParser()
824
825 _help = '<hostname>:<port> to consul agent (default: %s)' % defs['consul']
826 parser.add_argument(
827 '-C', '--consul', action='store', default=defs['consul'], help=_help)
828
829 _help = 'Lookup Voltha endpoints based on service entries in Consul'
830 parser.add_argument(
831 '-L', '--lookup', action='store_true', help=_help)
832
khenaidoo108f05c2017-07-06 11:15:29 -0400833 _help = 'All requests to the Voltha gRPC service are global'
834 parser.add_argument(
835 '-G', '--global_request', action='store_true', help=_help)
836
Zsolt Haraszti9b485fb2016-12-26 23:11:15 -0800837 _help = '<hostname>:<port> of Voltha gRPC service (default={})'.format(
838 defs['voltha_grpc_endpoint'])
839 parser.add_argument('-g', '--grpc-endpoint', action='store',
840 default=defs['voltha_grpc_endpoint'], help=_help)
841
842 _help = '<hostname>:<port> of Voltha simulated adapter backend for ' \
843 'testing (default={})'.format(
844 defs['voltha_sim_rest_endpoint'])
845 parser.add_argument('-s', '--sim-rest-endpoint', action='store',
846 default=defs['voltha_sim_rest_endpoint'], help=_help)
847
848 args = parser.parse_args()
849
850 if args.lookup:
851 host = args.consul.split(':')[0].strip()
852 port = int(args.consul.split(':')[1].strip())
853 consul = Consul(host=host, port=port)
854
855 _, services = consul.catalog.service('voltha-grpc')
856 if not services:
857 print('No voltha-grpc service registered in consul; exiting')
858 sys.exit(1)
859 args.grpc_endpoint = '{}:{}'.format(services[0]['ServiceAddress'],
860 services[0]['ServicePort'])
861
862 _, services = consul.catalog.service('voltha-sim-rest')
863 if not services:
864 print('No voltha-sim-rest service registered in consul; exiting')
865 sys.exit(1)
866 args.sim_rest_endpoint = '{}:{}'.format(services[0]['ServiceAddress'],
867 services[0]['ServicePort'])
868
khenaidoo108f05c2017-07-06 11:15:29 -0400869 c = VolthaCli(args.grpc_endpoint, args.sim_rest_endpoint,
870 args.global_request)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800871 c.poutput(banner)
Zsolt Harasztia133a452016-12-22 01:26:57 -0800872 c.load_history()
873 c.cmdloop()
874 c.save_history()