Commit 5281fe9e authored by Likun Zhang's avatar Likun Zhang
Browse files

1. Add unittests and help information for cmdctl and bindctl.

2. Add login idle timeout for cmdctl, default idle time is 1200 seconds.
3. Refactor some code for cmdctl.

git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@961 e5f2f494-b856-4b98-b285-d166d9295462
parent 1c2ee20f
...@@ -172,8 +172,9 @@ AC_CONFIG_FILES([Makefile ...@@ -172,8 +172,9 @@ AC_CONFIG_FILES([Makefile
]) ])
AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
src/bin/cfgmgr/run_b10-cfgmgr.sh src/bin/cfgmgr/run_b10-cfgmgr.sh
src/bin/cmdctl/b10-cmdctl.py src/bin/cmdctl/cmdctl.py
src/bin/cmdctl/run_b10-cmdctl.sh src/bin/cmdctl/run_b10-cmdctl.sh
src/bin/cmdctl/unittest/cmdctl_test
src/bin/bind10/bind10.py src/bin/bind10/bind10.py
src/bin/bind10/bind10_test src/bin/bind10/bind10_test
src/bin/bind10/run_bind10.sh src/bin/bind10/run_bind10.sh
...@@ -190,6 +191,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py ...@@ -190,6 +191,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
chmod +x src/bin/cfgmgr/run_b10-cfgmgr.sh chmod +x src/bin/cfgmgr/run_b10-cfgmgr.sh
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/bind10/run_bind10.sh chmod +x src/bin/bind10/run_bind10.sh
chmod +x src/bin/cmdctl/unittest/cmdctl_test
chmod +x src/bin/bindctl/unittest/bindctl_test
chmod +x src/bin/bindctl/bindctl chmod +x src/bin/bindctl/bindctl
chmod +x src/bin/msgq/run_msgq.sh chmod +x src/bin/msgq/run_msgq.sh
chmod +x src/bin/msgq/msgq_test chmod +x src/bin/msgq/msgq_test
......
1. Refactor the code for bindctl. 1. Refactor the code for bindctl.
2. Update man page for bindctl provided by jreed. 2. Update man page for bindctl provided by jreed.
3. Add more unit tests. 3. Add more unit tests.
4. Need Review:
bindcmd.py:
apply_config_cmd()
_validate_cmd()
complete()
cmdparse.py:
_parse_params
moduleinfo.py:
get_param_name_by_position
...@@ -61,47 +61,52 @@ class BindCmdInterpreter(Cmd): ...@@ -61,47 +61,52 @@ class BindCmdInterpreter(Cmd):
self.modules = OrderedDict() self.modules = OrderedDict()
self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl")) self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl"))
self.server_port = server_port self.server_port = server_port
self.connect_to_cmd_ctrld() self._connect_to_cmd_ctrld()
self.session_id = self._get_session_id() self.session_id = self._get_session_id()
def connect_to_cmd_ctrld(self): def _connect_to_cmd_ctrld(self):
'''Connect to cmdctl in SSL context. '''
try: try:
self.conn = http.client.HTTPSConnection(self.server_port, cert_file='bindctl.pem') self.conn = http.client.HTTPSConnection(self.server_port, cert_file='bindctl.pem')
except Exception as e: except Exception as e:
print(e) print(e, "can't connect to %s, please make sure cmd-ctrld is running" %
print("can't connect to %s, please make sure cmd-ctrld is running" % self.server_port) self.server_port)
def _get_session_id(self): def _get_session_id(self):
'''Generate one session id for the connection. '''
rand = os.urandom(16) rand = os.urandom(16)
now = time.time() now = time.time()
ip = socket.gethostbyname(socket.gethostname()) ip = socket.gethostbyname(socket.gethostname())
session_id = sha1(("%s%s%s" %(rand, now, ip)).encode()) session_id = sha1(("%s%s%s" %(rand, now, ip)).encode())
session_id = session_id.hexdigest() digest = session_id.hexdigest()
return session_id return digest
def run(self): def run(self):
'''Parse commands inputted from user and send them to cmdctl. '''
try: try:
ret = self.login() if not self.login_to_cmdctl():
if not ret:
return False return False
# Get all module information from cmd-ctrld # Get all module information from cmd-ctrld
self.config_data = isc.config.UIModuleCCSession(self) self.config_data = isc.config.UIModuleCCSession(self)
self.update_commands() self._update_commands()
self.cmdloop() self.cmdloop()
except KeyboardInterrupt: except KeyboardInterrupt:
return True return True
def login(self): def login_to_cmdctl(self):
'''Login to cmdctl with the username and password inputted
from user. After login sucessfully, the username and password
will be saved in 'default_user.csv', when login next time,
username and password saved in 'default_user.csv' will be used
first.
'''
csvfile = None csvfile = None
bsuccess = False bsuccess = False
try: try:
csvfile = open('default_user.csv') csvfile = open('default_user.csv')
users = csv.reader(csvfile) users = csv.reader(csvfile)
for row in users: for row in users:
if (len(row) < 2):
continue
param = {'username': row[0], 'password' : row[1]} param = {'username': row[0], 'password' : row[1]}
response = self.send_POST('/login', param) response = self.send_POST('/login', param)
data = response.read().decode() data = response.read().decode()
...@@ -120,10 +125,13 @@ class BindCmdInterpreter(Cmd): ...@@ -120,10 +125,13 @@ class BindCmdInterpreter(Cmd):
return True return True
count = 0 count = 0
csvfile = None
print("[TEMP MESSAGE]: username :root password :bind10") print("[TEMP MESSAGE]: username :root password :bind10")
while count < 3: while True:
count = count + 1 count = count + 1
if count > 3:
print("Too many authentication failures")
return False
username = input("Username:") username = input("Username:")
passwd = getpass.getpass() passwd = getpass.getpass()
param = {'username': username, 'password' : passwd} param = {'username': username, 'password' : passwd}
...@@ -135,26 +143,23 @@ class BindCmdInterpreter(Cmd): ...@@ -135,26 +143,23 @@ class BindCmdInterpreter(Cmd):
csvfile = open('default_user.csv', 'w') csvfile = open('default_user.csv', 'w')
writer = csv.writer(csvfile) writer = csv.writer(csvfile)
writer.writerow([username, passwd]) writer.writerow([username, passwd])
bsuccess = True csvfile.close()
break return True
if count == 3:
print("Too many authentication failures")
break
if csvfile:
csvfile.close()
return bsuccess
def update_commands(self): def _update_commands(self):
'''Get all commands of modules. '''
cmd_spec = self.send_GET('/command_spec') cmd_spec = self.send_GET('/command_spec')
if (len(cmd_spec) == 0): if not cmd_spec:
print('can\'t get any command specification') return
for module_name in cmd_spec.keys(): for module_name in cmd_spec.keys():
if cmd_spec[module_name]: self._prepare_module_commands(module_name, cmd_spec[module_name])
self.prepare_module_commands(module_name, cmd_spec[module_name])
def send_GET(self, url, body = None): def send_GET(self, url, body = None):
'''Send GET request to cmdctl, session id is send with the name
'cookie' in header.
'''
headers = {"cookie" : self.session_id} headers = {"cookie" : self.session_id}
self.conn.request('GET', url, body, headers) self.conn.request('GET', url, body, headers)
res = self.conn.getresponse() res = self.conn.getresponse()
...@@ -162,11 +167,12 @@ class BindCmdInterpreter(Cmd): ...@@ -162,11 +167,12 @@ class BindCmdInterpreter(Cmd):
if reply_msg: if reply_msg:
return json.loads(reply_msg.decode()) return json.loads(reply_msg.decode())
else: else:
return None return {}
def send_POST(self, url, post_param = None): def send_POST(self, url, post_param = None):
''' '''Send GET request to cmdctl, session id is send with the name
'cookie' in header.
Format: /module_name/command_name Format: /module_name/command_name
parameters of command is encoded as a map parameters of command is encoded as a map
''' '''
...@@ -183,13 +189,12 @@ class BindCmdInterpreter(Cmd): ...@@ -183,13 +189,12 @@ class BindCmdInterpreter(Cmd):
self.prompt = self.location + self.prompt_end self.prompt = self.location + self.prompt_end
return stop return stop
def prepare_module_commands(self, module_name, module_commands): def _prepare_module_commands(self, module_name, module_commands):
module = ModuleInfo(name = module_name, module = ModuleInfo(name = module_name,
desc = "same here") desc = "same here")
for command in module_commands: for command in module_commands:
cmd = CommandInfo(name = command["command_name"], cmd = CommandInfo(name = command["command_name"],
desc = command["command_description"], desc = command["command_description"])
need_inst_param = False)
for arg in command["command_args"]: for arg in command["command_args"]:
param = ParamInfo(name = arg["item_name"], param = ParamInfo(name = arg["item_name"],
type = arg["item_type"], type = arg["item_type"],
...@@ -200,7 +205,7 @@ class BindCmdInterpreter(Cmd): ...@@ -200,7 +205,7 @@ class BindCmdInterpreter(Cmd):
module.add_command(cmd) module.add_command(cmd)
self.add_module_info(module) self.add_module_info(module)
def validate_cmd(self, cmd): def _validate_cmd(self, cmd):
if not cmd.module in self.modules: if not cmd.module in self.modules:
raise CmdUnknownModuleSyntaxError(cmd.module) raise CmdUnknownModuleSyntaxError(cmd.module)
...@@ -225,7 +230,6 @@ class BindCmdInterpreter(Cmd): ...@@ -225,7 +230,6 @@ class BindCmdInterpreter(Cmd):
list(params.keys())[0]) list(params.keys())[0])
elif params: elif params:
param_name = None param_name = None
index = 0
param_count = len(params) param_count = len(params)
for name in params: for name in params:
# either the name of the parameter must be known, or # either the name of the parameter must be known, or
...@@ -250,18 +254,17 @@ class BindCmdInterpreter(Cmd): ...@@ -250,18 +254,17 @@ class BindCmdInterpreter(Cmd):
raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, cmd.params[name]) raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, cmd.params[name])
else: else:
# replace the numbered items by named items # replace the numbered items by named items
param_name = command_info.get_param_name_by_position(name+1, index, param_count) param_name = command_info.get_param_name_by_position(name+1, param_count)
cmd.params[param_name] = cmd.params[name] cmd.params[param_name] = cmd.params[name]
del cmd.params[name] del cmd.params[name]
elif not name in all_params: elif not name in all_params:
raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, name) raise CmdUnknownParamSyntaxError(cmd.module, cmd.command, name)
param_nr = 0 param_nr = 0
for name in manda_params: for name in manda_params:
if not name in params and not param_nr in params: if not name in params and not param_nr in params:
raise CmdMissParamSyntaxError(cmd.module, cmd.command, name) raise CmdMissParamSyntaxError(cmd.module, cmd.command, name)
param_nr += 1
param_nr += 1 param_nr += 1
def _handle_cmd(self, cmd): def _handle_cmd(self, cmd):
...@@ -385,7 +388,7 @@ class BindCmdInterpreter(Cmd): ...@@ -385,7 +388,7 @@ class BindCmdInterpreter(Cmd):
def _parse_cmd(self, line): def _parse_cmd(self, line):
try: try:
cmd = BindCmdParse(line) cmd = BindCmdParse(line)
self.validate_cmd(cmd) self._validate_cmd(cmd)
self._handle_cmd(cmd) self._handle_cmd(cmd)
except BindCtlException as e: except BindCtlException as e:
print("Error! ", e) print("Error! ", e)
...@@ -497,5 +500,3 @@ class BindCmdInterpreter(Cmd): ...@@ -497,5 +500,3 @@ class BindCmdInterpreter(Cmd):
print("received reply:", data) print("received reply:", data)
...@@ -18,69 +18,97 @@ from moduleinfo import * ...@@ -18,69 +18,97 @@ from moduleinfo import *
from bindcmd import * from bindcmd import *
import isc import isc
import pprint import pprint
from optparse import OptionParser, OptionValueError
__version__ = 'Bindctl'
def prepare_config_commands(tool): def prepare_config_commands(tool):
module = ModuleInfo(name = "config", desc = "Configuration commands") module = ModuleInfo(name = "config", desc = "Configuration commands")
cmd = CommandInfo(name = "show", desc = "Show configuration", need_inst_param = False) cmd = CommandInfo(name = "show", desc = "Show configuration")
param = ParamInfo(name = "identifier", type = "string", optional=True) param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param) cmd.add_param(param)
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "add", desc = "Add entry to configuration list", need_inst_param = False) cmd = CommandInfo(name = "add", desc = "Add entry to configuration list")
param = ParamInfo(name = "identifier", type = "string", optional=True) param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param) cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=False) param = ParamInfo(name = "value", type = "string", optional=False)
cmd.add_param(param) cmd.add_param(param)
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list", need_inst_param = False) cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
param = ParamInfo(name = "identifier", type = "string", optional=True) param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param) cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=False) param = ParamInfo(name = "value", type = "string", optional=False)
cmd.add_param(param) cmd.add_param(param)
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "set", desc = "Set a configuration value", need_inst_param = False) cmd = CommandInfo(name = "set", desc = "Set a configuration value")
param = ParamInfo(name = "identifier", type = "string", optional=True) param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param) cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=False) param = ParamInfo(name = "value", type = "string", optional=False)
cmd.add_param(param) cmd.add_param(param)
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "unset", desc = "Unset a configuration value", need_inst_param = False) cmd = CommandInfo(name = "unset", desc = "Unset a configuration value")
param = ParamInfo(name = "identifier", type = "string", optional=False) param = ParamInfo(name = "identifier", type = "string", optional=False)
cmd.add_param(param) cmd.add_param(param)
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "diff", desc = "Show all local changes", need_inst_param = False) cmd = CommandInfo(name = "diff", desc = "Show all local changes")
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "revert", desc = "Revert all local changes", need_inst_param = False) cmd = CommandInfo(name = "revert", desc = "Revert all local changes")
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "commit", desc = "Commit all local changes", need_inst_param = False) cmd = CommandInfo(name = "commit", desc = "Commit all local changes")
module.add_command(cmd) module.add_command(cmd)
cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part", need_inst_param = False) cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part")
param = ParamInfo(name = "identifier", type="string", optional=False) param = ParamInfo(name = "identifier", type="string", optional=False)
cmd.add_param(param) cmd.add_param(param)
module.add_command(cmd) module.add_command(cmd)
tool.add_module_info(module) tool.add_module_info(module)
def check_port(option, opt_str, value, parser):
if (value < 0) or (value > 65535):
raise OptionValueError('%s requires a port number (0-65535)' % opt_str)
parser.values.port = value
def check_addr(option, opt_str, value, parser):
ipstr = value
ip_family = socket.AF_INET
if (ipstr.find(':') != -1):
ip_family = socket.AF_INET6
try:
socket.inet_pton(ip_family, ipstr)
except:
raise OptionValueError("%s invalid ip address" % ipstr)
parser.values.addr = value
def set_bindctl_options(parser):
parser.add_option('-p', '--port', dest = 'port', type = 'int',
action = 'callback', callback=check_port,
default = '8080', help = 'port for cmdctl of bind10')
parser.add_option('-a', '--address', dest = 'addr', type = 'string',
action = 'callback', callback=check_addr,
default = '127.0.0.1', help = 'IP address for cmdctl of bind10')
if __name__ == '__main__': if __name__ == '__main__':
tool = BindCmdInterpreter("localhost:8080") try:
prepare_config_commands(tool) parser = OptionParser(version = __version__)
tool.run() set_bindctl_options(parser)
# TODO: put below back, was removed to see errors (options, args) = parser.parse_args()
#if __name__ == '__main__': server_addr = options.addr + ':' + str(options.port)
#try: tool = BindCmdInterpreter(server_addr)
#tool = BindCmdInterpreter("localhost:8080") prepare_config_commands(tool)
#prepare_config_commands(tool) tool.run()
#tool.run() except Exception as e:
#except Exception as e: print(e, "\nFailed to connect with b10-cmdctl module, is it running?")
#print(e)
#print("Failed to connect with b10-cmdctl module, is it running?")
...@@ -34,10 +34,10 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str) ...@@ -34,10 +34,10 @@ PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)
NAME_PATTERN = re.compile("^\s*(?P<name>[\w]+)(?P<blank>\s*)(?P<others>.*)$") NAME_PATTERN = re.compile("^\s*(?P<name>[\w]+)(?P<blank>\s*)(?P<others>.*)$")
class BindCmdParse: class BindCmdParse:
""" This class will parse the command line user input into three parts: """ This class will parse the command line usr input into three part
module name, command, parameters. module name, command, parameters
The first two parts are strings and parameter is one hash. the first two parts are strings and parameter is one hash,
The parameter part is optional. parameters part is optional
Example: zone reload, zone_name=example.com Example: zone reload, zone_name=example.com
module == zone module == zone
...@@ -52,6 +52,7 @@ class BindCmdParse: ...@@ -52,6 +52,7 @@ class BindCmdParse:
self._parse_cmd(cmd) self._parse_cmd(cmd)
def _parse_cmd(self, text_str): def _parse_cmd(self, text_str):
'''Parse command line. '''
# Get module name # Get module name
groups = NAME_PATTERN.match(text_str) groups = NAME_PATTERN.match(text_str)
if not groups: if not groups:
......
...@@ -51,10 +51,8 @@ class CommandInfo: ...@@ -51,10 +51,8 @@ class CommandInfo:
more parameters more parameters
""" """
def __init__(self, name, desc = "", need_inst_param = True): def __init__(self, name, desc = ""):
self.name = name self.name = name
# Wether command needs parameter "instance_name"
self.need_inst_param = need_inst_param
self.desc = desc self.desc = desc
self.params = OrderedDict() self.params = OrderedDict()
# Set default parameter "help" # Set default parameter "help"
...@@ -91,7 +89,7 @@ class CommandInfo: ...@@ -91,7 +89,7 @@ class CommandInfo:
return [name for name in all_names return [name for name in all_names
if not self.params[name].is_optional] if not self.params[name].is_optional]
def get_param_name_by_position(self, pos, index, param_count): def get_param_name_by_position(self, pos, param_count):
# count mandatories back from the last # count mandatories back from the last
# from the last mandatory; see the number of mandatories before it # from the last mandatory; see the number of mandatories before it
# and compare that to the number of positional arguments left to do # and compare that to the number of positional arguments left to do
...@@ -101,7 +99,9 @@ class CommandInfo: ...@@ -101,7 +99,9 @@ class CommandInfo:
# (can this be done in all cases? this is certainly not the most efficient method; # (can this be done in all cases? this is certainly not the most efficient method;
# one way to make the whole of this more consistent is to always set mandatories first, but # one way to make the whole of this more consistent is to always set mandatories first, but
# that would make some commands less nice to use ("config set value location" instead of "config set location value") # that would make some commands less nice to use ("config set value location" instead of "config set location value")
if type(pos) == int: if type(pos) != int:
raise KeyError(str(pos) + " is not an integer")
else:
if param_count == len(self.params) - 1: if param_count == len(self.params) - 1:
i = 0 i = 0
for k in self.params.keys(): for k in self.params.keys():
...@@ -131,14 +131,9 @@ class CommandInfo: ...@@ -131,14 +131,9 @@ class CommandInfo:
raise KeyError(str(pos) + " out of range") raise KeyError(str(pos) + " out of range")
else: else:
raise KeyError("Too many parameters") raise KeyError("Too many parameters")
else:
raise KeyError(str(pos) + " is not an integer")
def need_instance_param(self):
return self.need_inst_param
def command_help(self, inst_name, inst_type, inst_desc): def command_help(self):
print("Command ", self) print("Command ", self)
print("\t\thelp (Get help for command)") print("\t\thelp (Get help for command)")
...@@ -166,65 +161,39 @@ class CommandInfo: ...@@ -166,65 +161,39 @@ class CommandInfo:
class ModuleInfo: class ModuleInfo:
"""Define the information of one module, include module name, """Define the information of one module, include module name,
module supporting commands, instance name and the value type of instance name module supporting commands.
""" """
def __init__(self, name, inst_name = "", inst_type = STRING_TYPE, def __init__(self, name, desc = ""):
inst_desc = "", desc = ""):
self.name = name self.name = name
self.inst_name = inst_name
self.inst_type = inst_type
self.inst_desc = inst_desc
self.desc = desc