From 84b6e392fe93bb724dbd0454258a46746d75b058 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Thu, 15 Dec 2022 16:18:27 -0500 Subject: [PATCH 01/10] adding new major version --- .version | 2 +- netsim_wrapper/netsim2.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.version b/.version index ef538c2..fcdb2e1 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.1.2 +4.0.0 diff --git a/netsim_wrapper/netsim2.py b/netsim_wrapper/netsim2.py index 05afe0b..7eb3097 100644 --- a/netsim_wrapper/netsim2.py +++ b/netsim_wrapper/netsim2.py @@ -330,7 +330,7 @@ def read_netsim(self, path): class NetsimWrapper(Netsim): name = 'netsim-wrapper' options = [] - version = '3.1.2' + version = '4.0.0' _instance = None _netsim_wrapper_help = None From 3ced0889cffd436f8f4285448d7fdf98a5cbfa6e Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Fri, 16 Dec 2022 18:00:23 -0500 Subject: [PATCH 02/10] reformat --- netsim_wrapper/main.py | 30 + netsim_wrapper/netsim.py | 440 +++++++++++++ netsim_wrapper/netsim2.py | 1291 ++++++++----------------------------- netsim_wrapper/nso.py | 328 ++++++++++ netsim_wrapper/nwrap.py | 312 +++++++++ netsim_wrapper/utils.py | 307 +++++++++ setup.py | 3 +- 7 files changed, 1674 insertions(+), 1037 deletions(-) create mode 100644 netsim_wrapper/main.py create mode 100644 netsim_wrapper/netsim.py create mode 100644 netsim_wrapper/nso.py create mode 100644 netsim_wrapper/nwrap.py create mode 100644 netsim_wrapper/utils.py diff --git a/netsim_wrapper/main.py b/netsim_wrapper/main.py new file mode 100644 index 0000000..661c0ef --- /dev/null +++ b/netsim_wrapper/main.py @@ -0,0 +1,30 @@ +import sys +from logging import DEBUG +from nwrap import NWrap + + +def check_verbose(params): + nwrap = None + if '-vv' in params: + nwrap = NWrap(level=DEBUG) + params.remove('-vv') + if '--verbose' in params: + nwrap = NWrap(level=DEBUG) + params.remove('--verbose') + return nwrap, params + + +def nwrap(): + nwrap, params = check_verbose(sys.argv) + if nwrap == None: nwrap = NWrap() + + if len(params) >= 2: + if params[1] not in nwrap.options: + nwrap.help + nwrap.apply(params[1:]) + nwrap.utils.exit + nwrap.help + +if __name__ == "__main__": + nwrap() + diff --git a/netsim_wrapper/netsim.py b/netsim_wrapper/netsim.py new file mode 100644 index 0000000..e802fd0 --- /dev/null +++ b/netsim_wrapper/netsim.py @@ -0,0 +1,440 @@ +import re +from logging import DEBUG +from utils import Utils, Singleton +from collections import OrderedDict + + +class Netsim(metaclass=Singleton): + name = __name__ + cmd = ['ncs-netsim'] + dir_name = 'netsim' + + help = None + options = set() + + def __init__(self, **kwargs) -> None: + self.utils = Utils(**kwargs) + self.log = self.utils.log + self.load_help + self.load_options + + @property + def load_help(self): + if self.help: return + try: + self.log.debug("loading netsim help") + cmd = self.cmd + ['--help'] + self.help = self.utils.cmd.run(cmd) + self.log.debug("netsim help done") + except FileExistsError as e: + self.log.error('`ncs-netsim` command not found, source ncsrc') + self.utils.exit + + @property + def load_options(self): + if len(self.options): return + self.log.debug("loading netsim options") + options = set() + cmd_regex = re.compile(r'^\s+([a-z-]+)') + options_regex = re.compile(r'^\s+\[(\S+)\s+\|\s+([a-z-]+).*?\]\s+([a-z]+)') + for cmd in self.help.split('\n'): + res = cmd_regex.match(cmd) + if res: options.update(set(res.groups())) + res = options_regex.match(cmd) + if res: options.update(set(res.groups())) + options.update({'cli', 'cli-c', 'cli-i', '--dir'}) + self.options = options + self.log.debug("netsim options done") + + def device(self, action, devices, _async, sysout=True): + cmd = self.cmd + [action] + if _async: + cmd = self.cmd + ['--async', action] + devices = None + + if devices == None: + out = self.utils.cmd.run(cmd) + if not sysout or not out: + return out + + self.utils.append_log(out) + self.log.debug("netsim action done") + return out + + if type(devices) == set: + for device in devices: + out = self.utils.cmd.run(cmd + [device]) + if out: self.utils.append_log(out) + self.log.debug("netsim action done") + return + + self.log.error("invalid paramater `devices`") + + def start(self, devices=None, _async=False): + self.log.debug("starting devices..") + not_alive = self.utils.filter_not_alive(self.is_alive(sysout=False)) + if devices == None: devices = not_alive + else: devices = devices & not_alive + self.device('start', devices, _async) + + def stop(self, devices=None, _async=False): + self.log.debug("stoping devices..") + alive = self.utils.filter_alive(self.is_alive(sysout=False)) + if devices == None: devices = alive + else: devices = devices & alive + self.device('stop', devices, _async) + + def reset(self, devices=None, _async=False): + self.log.debug("reseting devices..") + self.device('reset', devices, _async) + + def restart(self, devices=None, _async=False): + self.log.debug("restarting devices..") + self.device('restart', devices, _async) + + def is_alive(self, devices=None, _async=False, sysout=True): + self.log.debug("running is-alive") + out = self.device('is-alive', devices, _async, sysout) + return out + + def status(self, devices=None, _async=False): + self.log.debug("running status") + out = self.device('status', devices, False) + return out + + def list(self): + self.log.debug("fetching netsim list") + out = self.utils.cmd.run(self.cmd + ['list']) + self.log.debug("netsim list done") + return out + + def create_network(self, package, num_devices, prefix): + self.log.debug("creating network..") + out = self.utils.cmd.run(self.cmd + ['create-network', package, str(num_devices), prefix]) + self.utils.append_log(out, method='debug') + self.log.debug("network created") + + def delete_network(self): + self.log.debug("deleting netsim network") + self.utils.cmd.run(self.cmd + ['delete-network']) + self.log.debug("netsims network deleted") + + def create_device(self, package, device): + self.log.debug("creating device..") + out = self.utils.cmd.run(self.cmd + ['create-device', package, device]) + self.utils.append_log(out, method='debug') + self.log.debug("device created") + + def add_to_network(self, package, num_devices, prefix): + self.log.debug("adding to network..") + out = self.utils.cmd.run(self.cmd + ['add-to-network', package, str(num_devices), prefix]) + self.utils.append_log(out, method='debug') + self.log.debug("device added") + + def add_device(self, package, device): + self.log.debug("adding device..") + out = self.utils.cmd.run(self.cmd + ['add-device', package, device]) + self.log.debug(out) + self.log.debug("device added") + + def whichdir(self): + self.log.debug("whichdir..") + res = self.utils.cmd.run(self.cmd + ['whichdir'], raiseError=False) + if res: + return res.strip() + + def ncs_xml_init(self, devices=None, _async=False, sysout=True): + if devices == None: cmd = self.cmd + ['ncs-xml-init'] + else: cmd = self.cmd + ['ncs-xml-init'] + list(devices) + data = self.utils.cmd.run(cmd) + if sysout: + print(data) + return data + + def run(self, cmd): + self.log.debug("passing to ncs-netsim") + res = self.utils.cmd.run(self.cmd + cmd) + print(res) + self.log.debug("ncs-netsim done") + + +class NetsimTemplates(metaclass=Singleton): + name = __name__ + filename = 'template' + ttype = {'network', 'device'} + ftype = {'json', 'yaml'} + defaults = { + 'ttype': 'network', + 'ftype': 'yaml', + 'current': None + } + + data = None + + def __init__(self) -> None: + self.utils = Utils() + self.log = self.utils.log + + def basic_template(self): + self.log.debug("adding base template") + template = OrderedDict() + template['nso-packages-path'] = '' # String + template['download-neds'] = True # True/False + template['neds'] = [] + template['neds'].append('') + template['neds'].append('') + template['compile-neds'] = True # True/False + template['start-devices'] = True # True/False + template['add-to-nso'] = True # True/False + template['add-authgroup-to-nso'] = True # True/False + template['authgroup'] = OrderedDict() # Dict + template['authgroup']['type'] = 'custom' # local/system/custom + template['authgroup']['path'] = '' + self.log.debug("done") + return template + + def day0_template(self, template): + self.log.debug("adding day0 params") + template['load-day0-config'] = True + template['config-path'] = '' # config path + template['config-files'] = [] + template['config-files'].append('') # each file path + template['config-files'].append('') + return template + + def network(self, template): + self.log.debug("adding create-network params") + template['device-mode'] = OrderedDict() + template['device-mode']['prefix-based'] = OrderedDict() + template['device-mode']['prefix-based'][''] = OrderedDict() + template['device-mode']['prefix-based']['']['count'] = 2 + template['device-mode']['prefix-based']['']['prefix'] = '' + return template + + def device(self, template): + self.log.debug("adding create-device params") + template['device-mode'] = OrderedDict() + template['device-mode']['name-based'] = OrderedDict() + template['device-mode']['name-based'][''] = [] + template['device-mode']['name-based'][''].append('device1') + template['device-mode']['name-based'][''].append('device2') + return template + + def create(self, ttype='network', ftype='yaml', fname=None): + if ttype not in self.ttype: + self.log.error("invalid template type [ network | device ]") + self.utils.exit + if ftype not in self.ftype: + self.log.error("invalid file type [ yaml | json ]") + self.utils.exit + + # template begin + self.log.debug("creating template") + template = self.basic_template() + if ttype == self.defaults['ttype']: + template = self.network(template) + else: # device + template = self.device(template) + template = self.day0_template(template) + # template end + + # kkotari: can adopt factory pattern + if ftype == self.defaults['ftype']: + self.utils.yaml.dump(template, fname) + else: # json + self.utils.json.dump(template, fname) + + def load(self, fname): + self.log.debug("loading template") + if not self.utils.file.is_file(fname): + self.log.error("couldn't able to find the file {}".format(fname)) + self.utils.exit + + ext = self.utils.file.get_ext(fname)[1:] + if ext not in self.ftype: + self.log.error("couldn't support the given template format {}".format(fname)) + self.utils.exit + + # kkotari: can adopt factory pattern + if ext == self.defaults['ftype']: # 'yaml' + self.data = self.utils.yaml.load(fname) + else: # 'json' + self.data = self.utils.json.load(fname) + + if self.data == None: + self.log.error("error on loading the template {}".format(fname)) + self.utils.exit + + if 'prefix-based' in self.data['device-mode']: + self.defaults['current'] = 'network' + else: # 'name-based' + self.defaults['current'] = 'device' + self.log.debug("done") + + +class NetsimInfo(metaclass=Singleton): + name = __name__ + netsim_list = {} + split_string = '#######' + fname = '.netsiminfo' + + def __init__(self) -> None: + self.netsim = Netsim() + self.log = self.netsim.log + + @property + def load_netsim_list(self): # __netsim_devices_created_by, _netsim_devices_created_by => netsim_list + self.log.debug("loading netsim list") + data = self.netsim.list() + if data == None or data == '': + self.log.debug("no data found") + return + data = list(filter(lambda x: 'netconf' in x, data.split('\n'))) + for device in data: + device = device.split('/') + name = device[-1].strip() + if name == device[-2]: + self.netsim_list[name] = ['add-device', device[-2]] + else: + self.netsim_list[name] = ['add-to-network', device[-2]] + self.log.debug("done") + + def dump(self, data, fpath): # _dump_netsim_mapper + fname = '{}/{}'.format(fpath, self.fname) + self.log.debug("creating netsim file: {}".format(fname)) + try: + index = 0 + with open(fname, 'w') as f: + f.write('\n') + for name, d in data.items(): + f.write('## device {}\n'.format(name)) + for key, value in d.items(): + if key in ['created_by', 'parent']: continue + f.write('{}[{}]={}\n'.format(key, index, value)) + f.write('#######\n\n') + index += 1 + self.log.debug("created netsim file: {}".format(fname)) + except EnvironmentError as e: + self.log.error("error on creating netsim file: {}".format(fname)) + self.log.error(e) + + def load(self, fpath): + self.load_netsim_list + fname = "{}/{}".format(fpath, self.fname) + # TODO: need to refactor + data = open(fname).read().split(self.split_string) + self.load_netsim_list + return self.netsim_list(data) + + def mapper(self, data): # _netsim_device_mapper, _netsim_mapper => info + self.log.debug("creating netsim info mapper") + info = OrderedDict() + for netsim in data: + device = netsim.split('=') + if len(device) > 1: + name = device[1].split('\n')[0] + info[name] = {} + for line in netsim.split('\n'): + if len(line.split('[')) > 1: + key = (line.split('[')[0]).strip(' ') + value = line.split('=')[1] + info[name][key] = value + info['created_by'] = self.netsim_list.get(name)[0] + info['parent'] = self.netsim_list.get(name)[1] + self.log.debug("done") + return info + + def lazy_load(self, btw): + l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + for i in l[:btw]: + # self.dump() + pass + yield + for i in l[btw:]: + # self.dump() + pass + + def update(self, index=None): + # self.load() + if index: + for k in self.lazy_load(index): + yield + else: + # self.dump() + pass + pass + + # TODO: netsim info + # def __refactor_netsiminfo(self, path): + # # reading and removing the unwanted data from netsiminfo + # __netsim_device_mapper = self.read_netsim(path) + # for each_key in self.__netsim_wrapper_device_mapper: + # if each_key in __netsim_device_mapper: + # del __netsim_device_mapper[each_key] + # self._dump_netsim_mapper(path, __netsim_device_mapper) + + +class NetsimDelete(metaclass=Singleton): + name = __name__ + fname = '.netsimdelete' + + def __init__(self) -> None: + self.netsim = Netsim() + self.log = self.netsim.log + + # TODO: netsim delete + def dump(self, data, fpath): + fname = "{}/{}".format(fpath, self.fname) + pass + + def load(self, fpath): + fname = "{}/{}".format(fpath, self.fname) + fname = fname or self.netsimdelete + + def create(self): + # self.dump() + pass + + def update(self): + # self.load() + # self.dump() + pass + + # TODO: old code + # def __netsim_wrapper_restore_device(self, path, cmd_lst, __netsim_wrapper_device_mapper_temp): + # for k,v in __netsim_wrapper_device_mapper_temp.items(): + # __temp = dict(filter(lambda d: int(d[1]['netconf_ssh_port']) < int(v['netconf_ssh_port']), self.__netsim_device_mapper.items())) + # self._dump_netsim_mapper(path, __temp) + # self.run_ncs_netsim__command(cmd_lst) + # del self.__netsim_wrapper_device_mapper[k] + # break + + # self._dump_netsim_mapper(path, self.__netsim_device_mapper) + # # removing the unwanted data + # self.__refactor_netsiminfo(path) + # json.dump(self.__netsim_wrapper_device_mapper, open(self.__netsim_path, 'w')) + + # def __netsim_wrapper_restore_devices(self, path, cmd_lst, __netsim_wrapper_device_mapper_temp): + # __total_devices = int(cmd_lst[-2]) + # if __total_devices <= 0: + # self.logger.error('no. of devices need to be > 0') + # self._exit + + # for k,v in __netsim_wrapper_device_mapper_temp.items(): + # __temp = dict(filter(lambda d: int(d[1]['netconf_ssh_port']) < int(v['netconf_ssh_port']), self.__netsim_device_mapper.items())) + # self._dump_netsim_mapper(path, __temp) + # cmd_lst[-2] = '1' + # self.run_ncs_netsim__command(cmd_lst) + # del self.__netsim_wrapper_device_mapper[k] + # __total_devices -= 1 + # if __total_devices == 0: + # break + + # self._dump_netsim_mapper(path, self.__netsim_device_mapper) + # if __total_devices > 0: + # cmd_lst[-2] = str(__total_devices) + # self.run_ncs_netsim__command(cmd_lst) + + # # removing the unwanted data + # self.__refactor_netsiminfo(path) + # json.dump(self.__netsim_wrapper_device_mapper, open(self.__netsim_path, 'w')) diff --git a/netsim_wrapper/netsim2.py b/netsim_wrapper/netsim2.py index 7eb3097..e5fd2cb 100644 --- a/netsim_wrapper/netsim2.py +++ b/netsim_wrapper/netsim2.py @@ -1,1037 +1,256 @@ -import sys -import re -import os -import copy -import json -import yaml -import subprocess -import logging -import collections -from operator import methodcaller - - -class Utils: - name = 'utils' - - _instance = None - def __new__(cls, log_level=logging.INFO, log_format=None): - if cls._instance is None: - cls._instance = object.__new__(cls) - return cls._instance - - def __init__(self, log_level=logging.INFO, log_format=None, *args, **kwargs): - self.__format = log_format - self.current_path = os.path.abspath('.') - self.logger = self.__set_logger_level(log_level) - self._setup_yaml - - def __set_logger_level(self, log_level): - if self.__format is None: - self.__format = '[ %(levelname)s ] :: [ %(name)s ] :: %(message)s' - logging.basicConfig(stream=sys.stdout, level=log_level, - format=self.__format, datefmt=None) - logger = logging.getLogger(self.name) - logger.setLevel(log_level) - return logger - - @property - def _setup_yaml(self): - represent_dict_order = lambda self, data: \ - self.represent_mapping( - 'tag:yaml.org,2002:map', - data.items() - ) - yaml.add_representer(collections.OrderedDict, represent_dict_order) - - def __del__(self): - self._instance = None - - @property - def _exit(self): - sys.exit() - - def _dump_yaml(self, filename, template): - try: - with open(filename, 'w') as f: - yaml.dump(template, f, sort_keys=False) - self.logger.info("please, find the {} file in current directory".format(filename)) - self.logger.info("update based on your requirement") - except EnvironmentError as e: - self.logger.error("error on createing of template..") - self.logger.error(e) - - def _dump_json(self, filename, template): - try: - with open(filename, 'w') as f: - json.dump(template, f, indent=2) - self.logger.info("please, find the {} file in current directory".format(filename)) - self.logger.info("update based on your requirement") - except EnvironmentError as e: - self.logger.error("error on createing of template..") - self.logger.error(e) - - def _load_yaml(self, path): - data = None - try: - with open(path) as f: - data = yaml.load(f, Loader=yaml.FullLoader) - except EnvironmentError as e: - self.logger.error("error while loading the {} file..".format(path)) - self.logger.error(e) - return data - - def _load_json(self, path): - data = None - try: - with open(path) as f: - data = json.load(f) - except EnvironmentError as e: - self.logger.error("error while loading the {} file..".format(path)) - self.logger.error(e) - return data - - def _create_file(self, path): - if not os.path.exists(path): - with open(path, "w") as fp: - json.dump({}, fp) - - def _delete_file(self, path): - if os.path.exists(path): - os.remove(path) - - def _rstrip_digits(self, given_string): - return given_string.rstrip('1234567890') - - def get_index(self, given_list, element): - try: - return given_list.index(element) - except ValueError: - return None - - def _load_path(self, cmd_lst, index, filename, filetype='yaml'): - if len(cmd_lst) > index+1: - path = cmd_lst[index+1] - else: - path = '{}/{}.{}'.format(self.current_path, filename, filetype) - return path - - def _dump_xml(self, filename, xml_data): - try: - with open(filename, 'w') as fp: - fp.write(xml_data) - except EnvironmentError as e: - self.logger.error("error on createing xml file") - self.logger.error(e) - - def _run_bash_commands(self, cmd): - try: - subprocess.call(cmd, shell=True) - except EnvironmentError as e: - self.logger.error("failed to run command: {}".format(cmd)) - self.logger.error(e) - - def _is_file(self, fname): - return os.path.isfile(fname) - - def _is_folder(self, fname): - return os.path.isdir(fname) - -class Netsim(Utils): - name = 'ncs-netsim' - command = ['ncs-netsim'] - netsim_options = [] - netsim_dir = 'netsim' - - _instance = None - _ncs_netsim_help = None - - __stdout = subprocess.PIPE - __stderr = subprocess.PIPE - - _split = '#######' - - def __new__(cls, log_level=logging.INFO, log_format=None): - if cls._instance is None: - cls._instance = object.__new__(cls) - return cls._instance - - def __init__(self, log_level=logging.INFO, log_format=None, *args, **kwargs): - Utils.__init__(self, log_level, log_format) - - # pre-req - self.__get_ncs_netsim__help - self._netsim_options - - @property - def __get_ncs_netsim__help(self): - if self._ncs_netsim_help: - return - try: - output = self._run_command(self.command + ['--help']) - except ValueError as e: - self.logger.error(e) - self._exit - except FileNotFoundError as e: - self.logger.error('ncs-netsim command not found. please source ncsrc file') - self._exit - self._ncs_netsim_help = output - - @property - def _netsim_options(self): - if len(self.netsim_options): - return - self.netsim_options = self.__fetch_ncs_netsim__commands - - @property - def __fetch_ncs_netsim__commands(self): - _commands = [] - _commands_regex = re.compile(r'^\s+([a-z-]+)') - _options_regex = re.compile( - r'^\s+\[(\S+)\s+\|\s+([a-z-]+).*?\]\s+([a-z]+)') - for each in self._ncs_netsim_help.split('\n'): - result = _commands_regex.match(each) - if result: - _commands += list(result.groups()) - result = _options_regex.match(each) - if result: - _commands += list(result.groups()) - _commands += ['cli', 'cli-c', 'cli-i', '--dir'] - return _commands - - @property - def __netsim_devices_created_by(self): - self._netsim_devices_created_by = {} - data = self.run_ncs_netsim__command(['list'], print_output=False).split('\n') - result = list(filter(lambda x: 'netconf' in x, data)) - for each in result: - each = each.split('/') - dev_name = each[-1].strip() - if dev_name == each[-2]: - self._netsim_devices_created_by[dev_name] = ['add-device', each[-2]] - else: - self._netsim_devices_created_by[dev_name] = ['add-to-network', each[-2]] - - @property - def _build_network_template(self): - template = collections.OrderedDict() - template['nso-packages-path'] = '' # String - template['compile-neds'] = True # True/False - template['start-devices'] = True # True/False - template['add-to-nso'] = True # True/False - template['add-authgroup-to-nso'] = True # True/False - template['authgroup'] = collections.OrderedDict() # Dict - template['authgroup']['type'] = 'custom' # local/system/custom - template['authgroup']['path'] = '' - template['device-mode'] = collections.OrderedDict() - template['device-mode']['prefix-based'] = collections.OrderedDict() - template['device-mode']['prefix-based'][''] = collections.OrderedDict() - template['device-mode']['prefix-based']['']['count'] = 2 - template['device-mode']['prefix-based']['']['prefix'] = '' - template['load-day0-config'] = True - template['config-path'] = '' # config path - template['config-files'] = [] - template['config-files'].append('') # each file path - template['config-files'].append('') - return template - - @property - def _build_device_template(self): - template = collections.OrderedDict() - template['nso-packages-path'] = '' # String - template['compile-neds'] = True # True/False - template['start-devices'] = True # True/False - template['add-to-nso'] = True # True/False - template['add-authgroup-to-nso'] = True # True/False - template['authgroup'] = collections.OrderedDict() - template['authgroup']['type'] = 'custom' # local/system/custom - template['authgroup']['path'] = '' - template['device-mode'] = collections.OrderedDict() - template['device-mode']['name-based'] = collections.OrderedDict() - template['device-mode']['name-based'][''] = [] - template['device-mode']['name-based'][''].append('device1') - template['device-mode']['name-based'][''].append('device2') - template['load-day0-config'] = True # True/False - template['config-path'] = '' # config path - template['config-files'] = [] - template['config-files'].append('') # each file path - template['config-files'].append('') - return template - - def _run_command(self, command, throw_err=True): - self.logger.debug("command `{}` running on ncs-netsim".format(' '.join(command))) - p = subprocess.Popen(command, stdout=self.__stdout, - stderr=self.__stderr) - out, err = p.communicate() - out, err = out.decode('utf-8'), err.decode('utf-8') - if err == '' or 'env.sh' in err: - self.logger.debug("`{}` ran successfully".format(' '.join(command))) - return out - if throw_err: - self.logger.error("an error occured while running command `{}`".format(' '.join(command))) - self.logger.error('message: {}'.format(err)) - if 'command not found' in err or 'Unknown command' in err: - raise ValueError("command not found.") - raise ValueError("try netsim-wrapper --help") - raise ValueError("{}\ntry netsim-wrapper --help".format(err)) - - def _netsim_device_mapper(self, data): - _netsim_mapper = collections.OrderedDict() - for each_device in data: - device = each_device.split('=') - if len(device) > 1: - device = device[1].split('\n')[0] - _netsim_mapper[device] = self._netsim_device_keypair_mapper(device, each_device) - return _netsim_mapper - - def _netsim_device_keypair_mapper(self, device, data): - _mapper = {} - for each_line in data.split('\n'): - if len(each_line.split('[')) > 1: - key = (each_line.split('[')[0]).strip(' ') - value = each_line.split('=')[1] - _mapper[key] = value - _mapper['created_by'] = self._netsim_devices_created_by.get(device)[0] - _mapper['parent'] = self._netsim_devices_created_by.get(device)[1] - return _mapper - - def _dump_netsim_mapper(self, path, netsim_mapper): - fp = open(path, 'w') - fp.write('\n') - index = 0 - for device_name, device_dict in netsim_mapper.items(): - fp.write('## device {}\n'.format(device_name)) - for key, value in device_dict.items(): - if key in ['created_by', 'parent']: - continue - fp.write('{}[{}]={}\n'.format(key, index, value)) - fp.write('#######\n\n') - index += 1 - fp.close() - - def run_ncs_netsim__command(self, cmd_lst, print_output=True, throw_err=True): - try: - output = self._run_command(self.command + cmd_lst, throw_err) - except ValueError as e: - if throw_err: - self.logger.error(e) - self._exit - raise ValueError(e) - # need to print - if print_output: - print(output.rstrip('\n')) - return output - - def read_netsim(self, path): - data = open(path).read().split(self._split) - self.__netsim_devices_created_by - return self._netsim_device_mapper(data) - - -class NetsimWrapper(Netsim): - name = 'netsim-wrapper' - options = [] - version = '4.0.0' - - _instance = None - _netsim_wrapper_help = None - _netsim_wrapper_commands = [] - __netsiminfo = '.netsiminfo' - __netsimdelete = '.netsimdelete' - __filename = 'template' - - def __new__(cls, log_level=logging.INFO, log_format=None, *args, **kwargs): - if cls._instance is None: - cls._instance = object.__new__(cls, *args, **kwargs) - return cls._instance - - def __init__(self, log_level=logging.INFO, log_format=None, *args, **kwargs): - Netsim.__init__(self, log_level, log_format) - self._options - - @property - def _options(self): - if len(self.options): - return - self._help = ['-h', '--help'] - self._version = ['-v', '--version'] - self._template_type = ['yaml', 'json'] - self._netsim_wrapper_commands = ['create-network', 'create-network-template', - 'create-network-from', 'create-device', 'create-device-template', - 'create-device-from', 'add-to-network', 'add-device', - 'delete-devices', 'delete-network', 'update-ip', 'update-port', - 'start', 'stop', 'ncs-xml-init'] - self.options = self._help + self._version + \ - self._netsim_wrapper_commands + self.netsim_options - - @property - def help(self): - if self._netsim_wrapper_help is not None: - # need to print - print(self._netsim_wrapper_help) - self._exit - - __match_replace = [ - ['create-network \s+\|', - '''create-network-template [yaml | json] | - create-network-from [yaml | json] | - create-network |'''], - ['create-device \s+\|', - '''create-device-template [yaml | json] | - create-device-from [yaml | json] | - create-device |'''], - ['add-device \s+\|', '''add-device | - delete-devices |'''], - ['get-port devname \[ipc.*', '''get-port devname [ipc | netconf | cli | snmp] | - -v | --version | - -h | --help'''], - ['ncs-netsim ', 'netsim-wrapper '] - ] - self._netsim_wrapper_help = self._ncs_netsim_help - for each in __match_replace: - self._netsim_wrapper_help = re.sub(each[0], each[1], self._netsim_wrapper_help, 1) - self.help - - @property - def get_version(self): - # need to print - print('netsim-wrapper version {}'.format(self.version)) - self._exit - - def _create_network_template(self, cmd_lst): - template = self._build_network_template - if 'yaml' in cmd_lst: - self._dump_yaml('{}.yaml'.format(self.__filename), template) - elif 'json' in cmd_lst: - self._dump_json('{}.json'.format(self.__filename), template) - else: - self.logger.error("invalid options entered..") - self._help - self._exit - - def _loading_from(self, cmd_lst): - device_data = None - yaml_index = self.get_index(cmd_lst, 'yaml') - json_index = self.get_index(cmd_lst, 'json') - - # loading template - if yaml_index: - path = self._load_path(cmd_lst, yaml_index, self.__filename, 'yaml') - device_data = self._load_yaml(path) - elif json_index: - path = self._load_path(cmd_lst, json_index, self.__filename, 'json') - device_data = self._load_json(path) - else: - self.logger.error('invalid options entered..') - self._help - self._exit - if device_data is None: - self.logger.error('could not able to read the file..!') - self._exit - return device_data - - def _create_network_from(self, cmd_lst): - device_data = self._loading_from(cmd_lst) - device_lst = [] - start_device_lst = [] - - if 'device-mode' not in device_data: - self.logger.error('given template is invalid..!') - self.logger.error('either template belongs to older version') - self.logger.error('create a new template and update accordingly') - self._exit - - if 'prefix-based' not in device_data['device-mode']: - self.logger.error('create-network-from supports only perfix-based') - self.logger.info('for name-based use `netsim-wrapper create-device-from`') - self._exit - - # creating devices - nso_package_path = device_data.get('nso-packages-path', None) - if not self._is_folder(nso_package_path): - self.logger.error('given nso-package-path is not valid.') - self._exit - - neds = device_data['device-mode']['prefix-based'] - for i, each_ned in enumerate(neds): - ned_count = str(neds[each_ned].get('count', 0)) - device_prefix = neds[each_ned].get('prefix', None) - if device_prefix is None: - self.logger.error('expecting device prefix under following ned: {}'.format(each_ned)) - self._exit - ned_path = '{}/{}'.format(nso_package_path, each_ned) - if not self._is_folder(ned_path): - self.logger.error('please check the ned path {} for ned {}'.format(nso_package_path, each_ned)) - self._exit - - new_cmd_lst = cmd_lst[:2] + ['create-network', ned_path, ned_count, device_prefix] - if i == 0 and cmd_lst[1] == 'netsim': - result = self._create_network(new_cmd_lst) - else: - new_cmd_lst[2] = 'add-to-network' - result = self._add_to_network(new_cmd_lst) - - start_device_lst += self.__get_device_names_not_alive(result) - device_lst += self.__get_device_names(result) - - # starting devices - start_devices = device_data.get('start-devices', False) - load_day0_config = device_data.get('load-day0-config', False) - add_to_nso = device_data.get('add-to-nso', False) - if not start_devices and not load_day0_config: - self._exit - - if start_devices: - self._compile_neds(device_data) - self.logger.info("about to start all devices") - new_cmd_lst = cmd_lst[:2] + ['start'] - self._start(new_cmd_lst, start_device_lst) - self.logger.info("devices are running") - - # auth-group - add_authgroup_to_nso = device_data.get('add-authgroup-to-nso', False) - if add_authgroup_to_nso: - authgroup = device_data.get('authgroup', None) - if authgroup is None: - self.logger.error('expecting auth-group data of type (local/system/custom)') - self._exit - - self.logger.info('configuring authgroup') - authgroup_type = authgroup.get('type', 'system') - self._config_authgroup(authgroup_type, authgroup) - - # loading devices to ncs and sync - if add_to_nso: - self._load_devices_to_ncs(device_data, cmd_lst, device_lst) - self._sync_from(device_lst) - - # pre-config is True - if load_day0_config: - self._load_per_config(device_data, device_lst) - - def _config_authgroup(self, type, authgroup): - authgroup_config = None - if type == 'local': - authgroup_config = ''' - - - - default - - admin - admin - - - - -''' - elif type == 'system': - authgroup_config = ''' - - - - default - - admin - admin - admin - - - admin - admin - admin - - - oper - oper - admin - - - - -''' - elif type == 'custom': - authgroup_path = authgroup.get('path', None) - if authgroup_path is None: - self.logger.error('expecting authgroup file path') - self._exit - else: - self.logger.error('invalid options, expecting authgroup.!') - self._exit - - if authgroup_config: - self._dump_xml('{}/authgroup.xml'.format(self.current_path), authgroup_config) - authgroup_path = 'authgroup.xml' - if not self._is_file(authgroup_path): - self.logger.error('invalid authgroup file') - self._exit - - new_cmd_lst = ['ncs_load', '-l', '-m', authgroup_path] - try: - self._run_command(new_cmd_lst) - except ValueError as e: - self.logger.error(e) - self._exit - - def _compile_neds(self, device_data): - # ned compile and reload - compile_neds = device_data.get('compile-neds', False) - if compile_neds: - self.logger.info('compiling the neds') - try: - mode = list(device_data['device-mode'].keys())[0] - neds = device_data['device-mode'][mode].keys() - for each_ned in neds: - ned_path = '{}/{}/src'.format(device_data['nso-packages-path'], each_ned) - cmd = "cd {}; make clean all;".format(ned_path) - self._run_bash_commands(cmd) - self.logger.info("about to run package reload force") - cmd = "echo 'packages reload force' | ncs_cli -u admin -C" - self._run_bash_commands(cmd) - except ValueError as e: - self.logger.error('failed at compile neds') - self.logger.error(e) - except IndexError as e: - self.logger.error('failed to fetch the device mode') - self.logger.error(e) - - def _load_devices_to_ncs(self, device_data, cmd_lst, device_lst): - # ncs_load - self.logger.info("about to add devices to ncs") - new_cmd_lst = cmd_lst[:2] + ['ncs-xml-init'] + device_lst - result = self._ncs_xml_init(new_cmd_lst, print_output=False) - self._dump_xml('{}/devices.xml'.format(self.current_path), result) - new_cmd_lst = ['ncs_load', '-l', '-m', 'devices.xml'] - try: - self._run_command(new_cmd_lst) - except ValueError as e: - self.logger.error(e) - self._exit - - def _sync_from(self, device_lst): - # devices sync-from - for each_device in device_lst: - self.logger.info("about to sync-from device {}".format(each_device)) - cmd = "echo 'devices device {} sync-from' | ncs_cli -u admin -C".format(each_device) - self._run_bash_commands(cmd) - - def _load_per_config(self, device_data, device_lst): - # apply the config - config_path = device_data.get('config-path') - if not self._is_folder(config_path): - self.logger.error('invalid configuration folder path given') - self._exit - - for each_file in device_data['config-files']: - self.logger.info('applying config {}'.format(each_file)) - file_path = '{}/{}'.format(config_path, each_file) - - if not self._is_file(file_path): - self.logger.error('invalid configuration file path given') - self.logger.error(file_path) - self._exit - - new_cmd_lst = ['ncs_load', '-l', '-m', file_path] - - try: - self._run_command(new_cmd_lst) - except ValueError as e: - self.logger.error('invalid configuration data..!, please correct') - self.logger.error('you run the same command again.') - self.logger.error(e) - self._exit - - def _create_network(self, cmd_lst): - output = self.run_ncs_netsim__command(cmd_lst) - self._create_file(self.__netsim_path) - # self.__update_netsimdelete_on_create_network # nomore used, it's empty on create.. - return output - - def _create_device_template(self, cmd_lst): - template = self._build_device_template - if 'yaml' in cmd_lst: - self._dump_yaml('{}.yaml'.format(self.__filename), template) - elif 'json' in cmd_lst: - self._dump_json('{}.json'.format(self.__filename), template) - else: - self.logger.error('invalid options entered..') - self._help - self._exit - - def _create_device_from(self, cmd_lst): - device_data = self._loading_from(cmd_lst) - device_lst = [] - start_device_lst = [] - - if 'device-mode' not in device_data: - self.logger.error('given template is invalid..!') - self.logger.error('either template belongs to older version') - self.logger.error('create a new template and update accordingly') - self._exit - - if 'name-based' not in device_data['device-mode']: - self.logger.error('create-device-from support only name-based') - self.logger.info('for prefix-based use `netsim-wrapper create-network-from`') - self._exit - - # creating devices - nso_package_path = device_data.get('nso-packages-path', None) - if not self._is_folder(nso_package_path): - self.logger.error('given nso-package-path is not valid.') - self._exit - - neds = device_data['device-mode']['name-based'] - for i, each_ned in enumerate(neds): - for j, device_name in enumerate(neds[each_ned]): - new_cmd_lst = cmd_lst[:2] + [ - 'add-device', - '{}/{}'.format(nso_package_path, each_ned), - device_name - ] - if i == 0 and j == 0 and cmd_lst[1] == 'netsim': - new_cmd_lst[2] = 'create-device' - self._create_device(new_cmd_lst) - start_device_lst.append(device_name) - else: - if self._add_device(new_cmd_lst) != False: - start_device_lst.append(device_name) - device_lst.append(device_name) - - # starting devices - start_devices = device_data.get('start-devices', False) - load_day0_config = device_data.get('load-day0-config', False) - add_to_nso = device_data.get('add-to-nso', False) - if not start_devices and not load_day0_config: - self._exit - - if start_devices and len(start_device_lst): - self._compile_neds(device_data) - self.logger.info("about to start devices") - new_cmd_lst = cmd_lst[:2] + ['start'] - self._start(new_cmd_lst, start_device_lst) - self.logger.info("devices are running") - - # auth-group - add_authgroup_to_nso = device_data.get('add-authgroup-to-nso', False) - if add_authgroup_to_nso: - authgroup = device_data.get('authgroup', None) - if authgroup is None: - self.logger.error('expecting auth-group data of type (local/system/custom)') - self._exit - - self.logger.info('configuring authgroup') - authgroup_type = authgroup.get('type', 'system') - self._config_authgroup(authgroup_type, authgroup) - - # loading devices to ncs and sync - if add_to_nso: - self._load_devices_to_ncs(device_data, cmd_lst, device_lst) - self._sync_from(device_lst) - - # pre-config is True - if load_day0_config: - self._load_per_config(device_data,device_lst) - - def _create_device(self, cmd_lst): - result = self.run_ncs_netsim__command(cmd_lst) - self._create_file(self.__netsim_path) - return result - - def _add_to_network(self, cmd_lst): - _command = 'add-to-network' - return self.__netsim_wrapper_add_devices(cmd_lst, _command) - - def _add_device(self, cmd_lst): - _command = 'add-device' - return self.__netsim_wrapper_add_devices(cmd_lst, _command) - - def _delete_devices(self, cmd_lst): - if len(cmd_lst) <= 3: - raise ValueError("no device names found") - - __netsim_wrapper_device_mapper = self.read_netsim_wrapper(self.__netsim_path) - path = self.__netsim_path.replace(self.__netsimdelete, self.__netsiminfo) - __netsim_device_mapper = self.read_netsim(path) - - for each in cmd_lst[3:]: - if each not in __netsim_device_mapper: - self.logger.error("device {} not exist".format(each)) - self._exit - __netsim_wrapper_device_mapper[each] = __netsim_device_mapper[each] - new_cmd_lst = cmd_lst[:2] + ['stop'] - self._stop(new_cmd_lst, [each]) - self.__remove_device_from_netsim(self.__netsim_path, each, __netsim_device_mapper[each]) - del __netsim_device_mapper[each] - - json.dump(__netsim_wrapper_device_mapper, open(self.__netsim_path, 'w')) - self._dump_netsim_mapper(path, __netsim_device_mapper) - - def _update_ip(self, cmd_lst): - self.logger.info('To be Added.') - - def _update_port(self, cmd_lst): - self.logger.info('To be Added.') - - def _delete_network(self, cmd_lst): - # automatically deleted .netsimdelete file - self.run_ncs_netsim__command(cmd_lst, print_output=False) - - def _start(self, cmd_lst, device_lst=[]): - start_index = self.get_index(cmd_lst, 'start') - if len(cmd_lst) > start_index+1: - # device name given by user..! - self.run_ncs_netsim__command(cmd_lst) - return - if len(device_lst): - for each in device_lst: - self.run_ncs_netsim__command(cmd_lst + [each]) - else: - result = self.run_ncs_netsim__command( - cmd_lst[:-1] + ['is-alive'], print_output=False) - device_lst = self.__get_device_names_not_alive(result) - for each in device_lst: - self.run_ncs_netsim__command(cmd_lst + [each]) - - def _stop(self, cmd_lst, device_lst=[]): - stop_index = self.get_index(cmd_lst, 'stop') - if len(cmd_lst) > stop_index+1: - # device name given by user..! - self.run_ncs_netsim__command(cmd_lst) - return - if len(device_lst): - for each in device_lst: - self.run_ncs_netsim__command(cmd_lst + [each]) - else: - result = self.run_ncs_netsim__command( - cmd_lst[:-1] + ['is-alive'], print_output=False) - device_lst = self.__get_device_names_alive(result) - for each in device_lst: - self.run_ncs_netsim__command(cmd_lst + [each]) - - def _ncs_xml_init(self, cmd_lst, print_output=True): - result = self.run_ncs_netsim__command(cmd_lst, print_output) - return result - - def __run_netsim_wrapper__command(self, cmd_lst): - self.__netsim_path = '{}/{}'.format(cmd_lst[1], self.__netsimdelete) - f = methodcaller("_{}".format(cmd_lst[2].replace('-','_')), self, cmd_lst) - try: - f(NetsimWrapper) - except ValueError as e: - self.logger.error(e) - self._exit - - def __get_device_names(self, data): - devices_lst = [] - device_name_pattern = re.compile(r'DEVICE\s+(\S+)\s+(.*)') - data = data.split('\n') - for each_line in data: - result = device_name_pattern.match(each_line) - if result: - devices_lst.append(result.group(1)) - return devices_lst - - def __get_device_names_not_alive(self, data): - devices_lst = [] - device_name_pattern = re.compile(r'DEVICE\s+(\S+)\s+(.*)') - data = data.split('\n') - for each_line in data: - result = device_name_pattern.match(each_line) - if result: - if result.group(2) == 'FAIL' or result.group(2) == 'CREATED': - devices_lst.append(result.group(1)) - elif result.group(2) == 'OK': - self.logger.info('device {} already started.'.format(result.group(1))) - return devices_lst - - def __get_device_names_alive(self, data): - devices_lst = [] - device_name_pattern = re.compile(r'DEVICE\s+(\S+)\s+(.*)') - data = data.split('\n') - for each_line in data: - result = device_name_pattern.match(each_line) - if result: - if result.group(2) == 'FAIL' or result.group(2) == 'CREATED': - self.logger.info('device {} already stopped.'.format(result.group(1))) - elif result.group(2) == 'OK': - devices_lst.append(result.group(1)) - return devices_lst - - def __remove_device_from_netsim(self, path, device, device_mapper): - path = os.path.abspath(path.replace(self.__netsim_path.split('/')[-1], '')) - if device_mapper['created_by'] == 'add-device': - cmd_lst = ['rm', '-rf', "{}/{}".format(path, device_mapper['parent'])] - elif len(list(filter(os.path.isdir, os.listdir(path)))) == 1: - cmd_lst = ['rm', '-rf', "{}/{}".format(path, device_mapper['parent'])] - else: - cmd_lst = ['rm', '-rf', "{}/{}/{}".format(path, device_mapper['parent'], device)] - self.__run_os_command(cmd_lst, print_output=False) - self.logger.info('deleting device: {}'.format(device)) - - def __run_os_command(self, cmd_lst, print_output=True): - try: - output = self._run_command(cmd_lst) - except Exception as e: - self.logger.error(e) - # need to print - if print_output: - print(output) - - def __fetch_device_prefix(self, mapper_dict, created_by): - prefix = set([i['prefix'] for i in mapper_dict.values() if i['created_by'] in created_by]) - return prefix - - def __check_is_valid_prefix(self, current_prefix, command): - __full_prefix = set() - - created_by=['add-device'] if command == 'add-to-network' else ['add-to-network'] - __netsim_wrapper_prefix = self.__fetch_device_prefix(self.__netsim_wrapper_device_mapper, created_by) - __netsim_prefix = self.__fetch_device_prefix(self.__netsim_device_mapper, created_by) - __full_prefix.update(__netsim_prefix, __netsim_wrapper_prefix) - - if command == 'add-to-network': - for each in __full_prefix: - if each.startswith(current_prefix) and each[len(current_prefix):].isdigit(): - raise ValueError("the prefix can't be part of existing/deleted devices via {} command".format(created_by)) - if command == 'add-device': - for each in __full_prefix: - if current_prefix.startswith(each) and current_prefix[len(each):].isdigit(): - raise ValueError("the prefix can't be part of existing/deleted devices via {} command".format(created_by)) - return True - - def __refactor_netsiminfo(self, path): - # reading and removing the unwanted data from netsiminfo - __netsim_device_mapper = self.read_netsim(path) - for each_key in self.__netsim_wrapper_device_mapper: - if each_key in __netsim_device_mapper: - del __netsim_device_mapper[each_key] - self._dump_netsim_mapper(path, __netsim_device_mapper) - - def __netsim_wrapper_add_devices(self, cmd_lst, _command): - _current_prefix = cmd_lst[-1] - - path = self.__netsim_path.replace(self.__netsimdelete, self.__netsiminfo) - self.__netsim_device_mapper = self.read_netsim(path) - self.__netsim_wrapper_device_mapper = self.read_netsim_wrapper(self.__netsim_path) - - # validating cross-prefix checks - if self.__check_is_valid_prefix(_current_prefix, _command): - if len(self.__netsim_wrapper_device_mapper) == 0: - try: - return self.run_ncs_netsim__command(cmd_lst, throw_err=False) - except ValueError as e: - if 'already exists' in str(e): - self.logger.info('device {} already exist.!'.format(cmd_lst[4])) - return False - - _prefix = self.__fetch_device_prefix(self.__netsim_wrapper_device_mapper, [_command]) - self.__netsim_device_mapper.update(self.__netsim_wrapper_device_mapper) - self.__netsim_device_mapper = collections.OrderedDict(sorted(self.__netsim_device_mapper.items(), key=lambda d: d[1]['netconf_ssh_port'])) - - # if the prefix is not under delete-devices - if _current_prefix not in _prefix: - self._dump_netsim_mapper(path, self.__netsim_device_mapper) - try: - result = self.run_ncs_netsim__command(cmd_lst, throw_err=False) - except ValueError as e: - if 'already exists' in str(e): - self.logger.info('device {} already exist.!'.format(cmd_lst[4])) - return False - - # removing the unwanted data - self.__refactor_netsiminfo(path) - return result - else: - __netsim_wrapper_device_mapper_temp = dict(filter(lambda d: d[1]['prefix'] == _current_prefix, self.__netsim_wrapper_device_mapper.items())) - __netsim_wrapper_device_mapper_temp = collections.OrderedDict(sorted(__netsim_wrapper_device_mapper_temp.items(), key=lambda d: d[1]['netconf_ssh_port'])) - - if _command == 'add-to-network': - self.__netsim_wrapper_restore_devices(path, cmd_lst, __netsim_wrapper_device_mapper_temp) - else: - self.__netsim_wrapper_restore_device(path, cmd_lst, __netsim_wrapper_device_mapper_temp) - - def __netsim_wrapper_restore_device(self, path, cmd_lst, __netsim_wrapper_device_mapper_temp): - for k,v in __netsim_wrapper_device_mapper_temp.items(): - __temp = dict(filter(lambda d: int(d[1]['netconf_ssh_port']) < int(v['netconf_ssh_port']), self.__netsim_device_mapper.items())) - self._dump_netsim_mapper(path, __temp) - self.run_ncs_netsim__command(cmd_lst) - del self.__netsim_wrapper_device_mapper[k] - break - - self._dump_netsim_mapper(path, self.__netsim_device_mapper) - # removing the unwanted data - self.__refactor_netsiminfo(path) - json.dump(self.__netsim_wrapper_device_mapper, open(self.__netsim_path, 'w')) - - def __netsim_wrapper_restore_devices(self, path, cmd_lst, __netsim_wrapper_device_mapper_temp): - __total_devices = int(cmd_lst[-2]) - if __total_devices <= 0: - self.logger.error('no. of devices need to be > 0') - self._exit - - for k,v in __netsim_wrapper_device_mapper_temp.items(): - __temp = dict(filter(lambda d: int(d[1]['netconf_ssh_port']) < int(v['netconf_ssh_port']), self.__netsim_device_mapper.items())) - self._dump_netsim_mapper(path, __temp) - cmd_lst[-2] = '1' - self.run_ncs_netsim__command(cmd_lst) - del self.__netsim_wrapper_device_mapper[k] - __total_devices -= 1 - if __total_devices == 0: - break - - self._dump_netsim_mapper(path, self.__netsim_device_mapper) - if __total_devices > 0: - cmd_lst[-2] = str(__total_devices) - self.run_ncs_netsim__command(cmd_lst) - - # removing the unwanted data - self.__refactor_netsiminfo(path) - json.dump(self.__netsim_wrapper_device_mapper, open(self.__netsim_path, 'w')) - - def run_command(self, cmd_lst, netsim_dir=None): - if '--dir' in cmd_lst: - _index = cmd_lst.index('--dir') - _netsim_dir = cmd_lst[_index+1] - del cmd_lst[_index:_index+2] - self.run_command(cmd_lst, netsim_dir=_netsim_dir) - - if cmd_lst[0] in self._version: - self.get_version - if cmd_lst[0] in self._help: - self.help - - if not netsim_dir: - try: - netsim_dir = self.run_ncs_netsim__command( - ['whichdir'], - print_output=False, throw_err=False - ).strip() - except ValueError: - netsim_dir = self.netsim_dir - - if cmd_lst[0] in self._netsim_wrapper_commands: - self.__run_netsim_wrapper__command(['--dir', netsim_dir] + cmd_lst) - else: - self.run_ncs_netsim__command(['--dir', netsim_dir] + cmd_lst) - self._exit - - def read_netsim_wrapper(self, path): - if os.path.exists(path): - with open(path, 'r') as fp: - data = json.load(fp) - return data - return {} - - -def run(): - obj = NetsimWrapper() - if len(sys.argv) >= 2: - if sys.argv[1] not in obj.options: - obj.help - obj.run_command(sys.argv[1:]) - else: - obj.help - - -if __name__ == "__main__": - run() +# import sys +# import re +# import os +# import copy +# import json +# import yaml +# import subprocess +# import logging +# import collections +# from operator import methodcaller + + + +# class Netsim(Utils): +# name = 'ncs-netsim' +# command = ['ncs-netsim'] +# netsim_options = [] +# netsim_dir = 'netsim' + +# _instance = None +# _ncs_netsim_help = None + +# _split = '#######' + +# # def run_ncs_netsim__command(self, cmd_lst, print_output=True, throw_err=True): +# # try: +# # output = self._run_command(self.command + cmd_lst, throw_err) +# # except ValueError as e: +# # if throw_err: +# # self.logger.error(e) +# # self._exit +# # raise ValueError(e) +# # # need to print +# # if print_output: +# # print(output.rstrip('\n')) +# # return output + +# # def read_netsim(self, path): +# # data = open(path).read().split(self._split) +# # self.__netsim_devices_created_by +# # return self._netsim_device_mapper(data) + + +# class NetsimWrapper(Netsim): +# name = 'netsim-wrapper' +# options = [] +# version = '4.0.0' + +# _instance = None +# _netsim_wrapper_help = None +# _netsim_wrapper_commands = [] +# __netsiminfo = '.netsiminfo' +# __netsimdelete = '.netsimdelete' +# __filename = 'template' + +# def __new__(cls, log_level=logging.INFO, log_format=None, *args, **kwargs): +# if cls._instance is None: +# cls._instance = object.__new__(cls, *args, **kwargs) +# return cls._instance + +# def __init__(self, log_level=logging.INFO, log_format=None, *args, **kwargs): +# Netsim.__init__(self, log_level, log_format) +# self._options + + + +# def _load_path(self, cmd_lst, index, filename, filetype='yaml'): +# if len(cmd_lst) > index+1: +# path = cmd_lst[index+1] +# else: +# path = '{}/{}.{}'.format(self.current_path, filename, filetype) +# return path + + +# def __run_netsim_wrapper__command(self, cmd_lst): +# self.__netsim_path = '{}/{}'.format(cmd_lst[1], self.__netsimdelete) +# f = methodcaller("_{}".format(cmd_lst[2].replace('-','_')), self, cmd_lst) +# try: +# f(NetsimWrapper) +# except ValueError as e: +# self.logger.error(e) +# self._exit + +# def __get_device_names(self, data): +# devices_lst = [] +# device_name_pattern = re.compile(r'DEVICE\s+(\S+)\s+(.*)') +# data = data.split('\n') +# for each_line in data: +# result = device_name_pattern.match(each_line) +# if result: +# devices_lst.append(result.group(1)) +# return devices_lst + +# def __remove_device_from_netsim(self, path, device, device_mapper): +# path = os.path.abspath(path.replace(self.__netsim_path.split('/')[-1], '')) +# if device_mapper['created_by'] == 'add-device': +# cmd_lst = ['rm', '-rf', "{}/{}".format(path, device_mapper['parent'])] +# elif len(list(filter(os.path.isdir, os.listdir(path)))) == 1: +# cmd_lst = ['rm', '-rf', "{}/{}".format(path, device_mapper['parent'])] +# else: +# cmd_lst = ['rm', '-rf', "{}/{}/{}".format(path, device_mapper['parent'], device)] +# self.__run_os_command(cmd_lst, print_output=False) +# self.logger.info('deleting device: {}'.format(device)) + +# def __run_os_command(self, cmd_lst, print_output=True): +# try: +# output = self._run_command(cmd_lst) +# except Exception as e: +# self.logger.error(e) +# # need to print +# if print_output: +# print(output) + +# def __fetch_device_prefix(self, mapper_dict, created_by): +# prefix = set([i['prefix'] for i in mapper_dict.values() if i['created_by'] in created_by]) +# return prefix + +# def __check_is_valid_prefix(self, current_prefix, command): +# __full_prefix = set() + +# created_by=['add-device'] if command == 'add-to-network' else ['add-to-network'] +# __netsim_wrapper_prefix = self.__fetch_device_prefix(self.__netsim_wrapper_device_mapper, created_by) +# __netsim_prefix = self.__fetch_device_prefix(self.__netsim_device_mapper, created_by) +# __full_prefix.update(__netsim_prefix, __netsim_wrapper_prefix) + +# if command == 'add-to-network': +# for each in __full_prefix: +# if each.startswith(current_prefix) and each[len(current_prefix):].isdigit(): +# raise ValueError("the prefix can't be part of existing/deleted devices via {} command".format(created_by)) +# if command == 'add-device': +# for each in __full_prefix: +# if current_prefix.startswith(each) and current_prefix[len(each):].isdigit(): +# raise ValueError("the prefix can't be part of existing/deleted devices via {} command".format(created_by)) +# return True + + +# def __netsim_wrapper_add_devices(self, cmd_lst, _command): +# _current_prefix = cmd_lst[-1] + +# path = self.__netsim_path.replace(self.__netsimdelete, self.__netsiminfo) +# self.__netsim_device_mapper = self.read_netsim(path) +# self.__netsim_wrapper_device_mapper = self.read_netsim_wrapper(self.__netsim_path) + +# # validating cross-prefix checks +# if self.__check_is_valid_prefix(_current_prefix, _command): +# if len(self.__netsim_wrapper_device_mapper) == 0: +# try: +# return self.run_ncs_netsim__command(cmd_lst, throw_err=False) +# except ValueError as e: +# if 'already exists' in str(e): +# self.logger.info('device {} already exist.!'.format(cmd_lst[4])) +# return False + +# _prefix = self.__fetch_device_prefix(self.__netsim_wrapper_device_mapper, [_command]) +# self.__netsim_device_mapper.update(self.__netsim_wrapper_device_mapper) +# self.__netsim_device_mapper = collections.OrderedDict(sorted(self.__netsim_device_mapper.items(), key=lambda d: d[1]['netconf_ssh_port'])) + +# # if the prefix is not under delete-devices +# if _current_prefix not in _prefix: +# self._dump_netsim_mapper(path, self.__netsim_device_mapper) +# try: +# result = self.run_ncs_netsim__command(cmd_lst, throw_err=False) +# except ValueError as e: +# if 'already exists' in str(e): +# self.logger.info('device {} already exist.!'.format(cmd_lst[4])) +# return False + +# # removing the unwanted data +# self.__refactor_netsiminfo(path) +# return result +# else: +# __netsim_wrapper_device_mapper_temp = dict(filter(lambda d: d[1]['prefix'] == _current_prefix, self.__netsim_wrapper_device_mapper.items())) +# __netsim_wrapper_device_mapper_temp = collections.OrderedDict(sorted(__netsim_wrapper_device_mapper_temp.items(), key=lambda d: d[1]['netconf_ssh_port'])) + +# if _command == 'add-to-network': +# self.__netsim_wrapper_restore_devices(path, cmd_lst, __netsim_wrapper_device_mapper_temp) +# else: +# self.__netsim_wrapper_restore_device(path, cmd_lst, __netsim_wrapper_device_mapper_temp) + + +# def run_command(self, cmd_lst, netsim_dir=None): +# if '--dir' in cmd_lst: +# _index = cmd_lst.index('--dir') +# _netsim_dir = cmd_lst[_index+1] +# del cmd_lst[_index:_index+2] +# self.run_command(cmd_lst, netsim_dir=_netsim_dir) + +# if cmd_lst[0] in self._version: +# self.get_version +# if cmd_lst[0] in self._help: +# self.help + +# if not netsim_dir: +# try: +# netsim_dir = self.run_ncs_netsim__command( +# ['whichdir'], +# print_output=False, throw_err=False +# ).strip() +# except ValueError: +# netsim_dir = self.netsim_dir + +# if cmd_lst[0] in self._netsim_wrapper_commands: +# self.__run_netsim_wrapper__command(['--dir', netsim_dir] + cmd_lst) +# else: +# self.run_ncs_netsim__command(['--dir', netsim_dir] + cmd_lst) +# self._exit + +# def read_netsim_wrapper(self, path): +# if os.path.exists(path): +# with open(path, 'r') as fp: +# data = json.load(fp) +# return data +# return {} + + + +# def _create_network(self, cmd_lst): +# output = self.run_ncs_netsim__command(cmd_lst) +# self.utils.json.dummy(self.__netsim_path) # .netsimdelete path +# # self.__update_netsimdelete_on_create_network # nomore used, it's empty on create.. +# return output + +# def _create_device(self, cmd_lst): +# result = self.run_ncs_netsim__command(cmd_lst) +# self.utils.json.dummy(self.__netsim_path) # .netsimdelete path +# return result + +# def _add_to_network(self, cmd_lst): +# _command = 'add-to-network' +# return self.__netsim_wrapper_add_devices(cmd_lst, _command) + +# def _add_device(self, cmd_lst): +# _command = 'add-device' +# return self.__netsim_wrapper_add_devices(cmd_lst, _command) + +# def _delete_devices(self, cmd_lst): +# if len(cmd_lst) <= 3: +# raise ValueError("no device names found") + +# __netsim_wrapper_device_mapper = self.read_netsim_wrapper(self.__netsim_path) +# path = self.__netsim_path.replace(self.__netsimdelete, self.__netsiminfo) +# __netsim_device_mapper = self.read_netsim(path) + +# for each in cmd_lst[3:]: +# if each not in __netsim_device_mapper: +# self.logger.error("device {} not exist".format(each)) +# self._exit +# __netsim_wrapper_device_mapper[each] = __netsim_device_mapper[each] +# new_cmd_lst = cmd_lst[:2] + ['stop'] +# self._stop(new_cmd_lst, [each]) +# self.__remove_device_from_netsim(self.__netsim_path, each, __netsim_device_mapper[each]) +# del __netsim_device_mapper[each] + +# json.dump(__netsim_wrapper_device_mapper, open(self.__netsim_path, 'w')) +# self._dump_netsim_mapper(path, __netsim_device_mapper) diff --git a/netsim_wrapper/nso.py b/netsim_wrapper/nso.py new file mode 100644 index 0000000..7205945 --- /dev/null +++ b/netsim_wrapper/nso.py @@ -0,0 +1,328 @@ +from utils import Singleton, Download +from collections import defaultdict + + +class AuthGroup(metaclass=Singleton): + name = __name__ + + def local(self): + return """ + + + + default + + admin + admin + + + + +""" + + def system(self): + return """ + + + + default + + admin + admin + admin + + + admin + admin + admin + + + oper + oper + admin + + + + +""" + + +class AAA(metaclass=Singleton): + name = __name__ + + def add(self, type): + def admin(self): + return """ + + + + + + admin + 65534 + 65534 + admin + /etc/ncs/ssh + /var/ncs/admin + + + oper + 65534 + 65534 + oper + /etc/ncs/ssh + /var/ncs/oper + + + + + +""" + + def default(self): + return """ + + + + + + admin + 65534 + 65534 + admin + /etc/ncs/ssh + /var/ncs/admin + + + oper + 65534 + 65534 + oper + /etc/ncs/ssh + /var/ncs/oper + + + private + 65534 + 65534 + $6$ + /etc/ncs/ssh + /var/ncs/private + + + public + 65534 + 65534 + $6$ + /etc/ncs/ssh + /var/ncs/public + + + + + +""" + if type == 'admin': + return admin() + return default() + + +class NsoUtils(metaclass=Singleton): + name = __name__ + default = defaultdict(lambda: False) + default['user'] = 'admin' + + def __init__(self, nwrap) -> None: + self.nwrap = nwrap + self.auth = AuthGroup() + self.aaa = AAA() + self.links = Download() + self.log = self.nwrap.log + self.utils = self.nwrap.utils + + def log_ncs_cli(self, func): + def wrapper(*args, **kwargs): + self.log.info("$ ncs_cli -Cu {}".format(self.default['user'])) + result = func(*args, **kwargs) + self.log.info("{} # exit".format(self.default['user'])) + return result + return wrapper + + def fetch_ssh_keys(self): + self.log.info("{}# devices fetch-ssh-host-keys".format(self.default['user'])) + cmd = "echo 'devices fetch-ssh-host-keys' | ncs_cli -u {} -C".format(self.default['user']) + self.utils.cmd.call(cmd) + + def sync_from(self): + self.log.info("{}# devices sync-from".format(self.default['user'])) + cmd = "echo 'devices sync-from' | ncs_cli -u {} -C".format(self.default['user']) + self.utils.cmd.call(cmd) + + def packages_reload(self): + self.log.info("{}# packages reload force".format(self.default['user'])) + cmd = "echo 'packages reload force' | ncs_cli -u {} -C".format(self.default['user']) + self.utils.cmd.call(cmd) + + def compile_neds(self): + self.log.debug("compiliing neds") + for ned in self.default['device-neds']: + path = "{}/{}".format(self.default['ppath'], ned) + if not self.utils.file.is_folder(path): + self.log.warning("can't compile ned: {}".format(path)) + continue + self.log.info("$ make clean all -> in {}/src".format(path)) + cmd = "cd {}/src; make clean all;".format(path) + self.utils.cmd.call(cmd) + + def load_authgroup(self, type, path=None): # type -> local/system/custom + path = path or '/tmp/authgroup.xml' + if type == 'local': + self.utils.xml.dump(self.auth.local, path) + if type == 'system': + self.utils.xml.dump(self.auth.system, path) + return path + + def load_aaa(self, type): # type -> add.admin/add.default + # TODO: yet to test + path = '/tmp/aaa.xml' + self.utils.xml.dump(self.aaa.add(type), path) + return path + + def load_merge(self, fpath): + self.log.info("{}# load merge {}".format(self.default['user'], fpath)) + cmd = "echo 'load merge {}' | ncs_cli -u {} -C".format(fpath, self.default['user']) + self.utils.cmd.call(cmd) + + def fetch_neds(self): + if self.default['ttype'] == 'network': + return set(self.default['device-mode']['prefix-based'].keys()) + return set(self.default['device-mode']['name-based'].keys()) + + def download_neds(self): + if not (self.default['username'] and self.default['password']): + self.log.error("required and to download") + self.utils.exit + self.links.get( + self.default['username'], + self.default['password'], + self.default['neds'] + ) + + def extract_neds(self, extract_tar=True): + self.log.debug("extracting neds") + # TODO: need to test + cmd = ['cd', self.default['ppath'], '&&'] + for ned in self.default['neds']: + file = ned.split('/')[-1] + self.log.debug('ned: {}'.format(file)) + self.utils.cmd.run(['mv', file, self.default['ppath']]) + self.utils.cmd.run(cmd + ['bash', file]) + if not extract_tar: + continue + tar_file = self.utils.get_tar(file) + self.utils.cmd.run(cmd + ['tar', '-xvf', tar_file]) + self.utils.cmd.run(cmd + ['rm', '-rf', tar_file, file]) + self.log.debug('extraction done') + + def create_devices(self): + self.log.info("creating netsim devices") + create = True if not self.nwrap.netsim.list() else False + if self.default['ttype'] == 'network': + for ned, val in self.default['device-mode']['prefix-based'].items(): + path = "{}/{}".format(self.default['ppath'], ned) + if create: + self.nwrap.create_network(path, val['count'], val['prefix']) + create = False + continue + self.nwrap.add_to_network(path, val['count'], val['prefix']) + else: + for ned, val in self.default['device-mode']['name-based'].items(): + path = "{}/{}".format(self.default['ppath'], ned) + for device in val: + if create: + self.nwrap.create_device(path, device) + create = False + continue + self.nwrap.add_device(path, device) + self.log.info("okay") + + def analyse_params(self): + self.log.debug("analysing template parameters") + + self.default.update(self.nwrap.template.data) + self.default['ttype'] = self.nwrap.template.defaults['current'] + self.default['ppath'] = self.default['nso-packages-path'] + + # args compile neds + if self.default['compile-neds']: + self.default['device-neds'] = self.fetch_neds() + + # args authgroup + if self.default['add-authgroup-to-nso']: + self.default['authgroup-path'] = self.load_authgroup( + type=self.default['authgroup']['type'], + path=self.default['authgroup'].get('path', None) + ) + + # args day0 config + if self.default['load-day0-config']: + files = [] + for file in self.default['config-files']: + fpath = "{}/{}".format(self.default['config-path'], file) + if not self.utils.file.is_file(fpath): + msg = "can't find file: {}".format(fpath) + raise FileNotFoundError(msg) + files.append(fpath) + self.default['config-files'] = files + + self.log.debug("analysing template done") + + def apply(self): + self.analyse_params() + + self.log.debug("per-netsim steps") + if self.default['download-neds']: + self.download_neds() + self.extract_neds() + + if self.default['compile-neds']: + self.compile_neds() + + # feature pre netsim create + @self.log_ncs_cli + def pre_create(): + if self.default['compile-neds']: + self.packages_reload() + + if self.default['add-authgroup-to-nso']: + self.load_merge(self.load_aaa(type='admin')) + self.load_merge(self.default['authgroup-path']) + return pre_create + + # feature create devices + self.log.debug("netsim steps") + self.create_devices() + + if self.default['start-devices']: + self.nwrap.netsim.start() + + if self.default['add-to-nso']: + path = 'tmp/devices.xml' + data = self.nwrap.netsim.ncs_xml_init(sysout=False) + self.utils.xml.dump(data, path) + + # feature post netsim create + self.log.debug("post-netsim steps") + @self.log_ncs_cli + def post_create(): + if self.default['add-to-nso']: + self.load_merge(path) + self.fetch_ssh_keys() + self.sync_from() + + if self.default['load-day0-config']: + for fpath in self.default['config-files']: + self.load_merge(fpath) + return post_create + + self.log.debug("template done") diff --git a/netsim_wrapper/nwrap.py b/netsim_wrapper/nwrap.py new file mode 100644 index 0000000..86d10be --- /dev/null +++ b/netsim_wrapper/nwrap.py @@ -0,0 +1,312 @@ +from utils import Singleton, Download +from netsim import Netsim, NetsimTemplates, NetsimInfo, NetsimDelete + +from nso import NsoUtils +from collections import defaultdict + + +help = """ +Usage nwrap template create [network | device] [yaml | json] [] + template load [ ] + download link + delete-devices + -v | --version + -vv | --verbose + -h | --help + +nwrap is an alias of `netsim-wrapper` and you can pass +multiple devnames instead of single devname, eg. start, stop, ... + +Additionally we support all `ncs-netsim` commands +""" + +class NetsimWrapper(metaclass=Singleton): + name = __name__ + help = None + version = '4.0.0' + options = {} + + def __init__(self, **kwargs) -> None: + self.netsim = Netsim(**kwargs) + self.template = NetsimTemplates() + self.info = NetsimInfo() + self.delete = NetsimDelete() + self.utils = self.netsim.utils + self.log = self.netsim.utils.log + self.update_help + self.update_options + + @property + def update_options(self): + if len(self.options): + return + + self.log.debug("updating netsim-wrapper options") + self.options = { + 'help': {'-h', '--help'}, + 'version': {'-v', '--version'}, + 'log': {'-vv', '--verbose'}, + 'ftype': self.template.ftype, + 'ttype': self.template.ttype, + 'wraps': { + 'create-network', 'create-device', 'add-to-network', 'add-device', + 'delete-network', 'start', 'stop', 'reset', 'restart', 'ncs-xml-init' + }, + 'new': { + 'template', 'delete-devices', 'download' + }, + 'netsim': self.netsim.options + } + self.log.debug("updated.") + + @property + def update_help(self): + if self.help is not None: + return + + self.log.debug("updating netsim-wrapper help") + global help + self.help = "{}{}".format(help, self.netsim.help) + self.log.debug("updated.") + self.update_help + + def special_methods(self, method:str, devices, _async): + method = method.replace('-', '_') + devices = devices or None + getattr(self.netsim, method)(devices, _async) + + def create_network(self, package, num_devices, prefix): + self.netsim.create_network(package, num_devices, prefix) + + def create_device(self, package, device): + self.netsim.create_device(package, device) + + def add_to_network(self, package, num_devices, prefix): + self.netsim.add_to_network(package, num_devices, prefix) + fpath = "{}/{}".format(self.netsim.cmd[-1], self.delete.fname) + if self.utils.file.is_file(fpath): + pass + # TODO: need to add more + + def add_device(self, package, device): + self.netsim.add_device(package, device) + fpath = "{}/{}".format(self.netsim.cmd[-1], self.delete.fname) + if self.utils.file.is_file(fpath): + pass + # TODO: need to add more + + def delete_devices(self, devices): + fpath = "{}/{}".format(self.netsim.cmd[-1], self.delete.fname) + if not self.utils.file.is_file(fpath): + # creating for first time..! + pass + else: + # need to update existing file..! + pass + # 1. need to create self.delete.fname + # 2. remove the device and re-adjust the self.info.fname index + # self.info.load() + # self.delete.create(self.info.data) + pass + + +# TODO: if possible need to add logs +class NWrap(metaclass=Singleton): + name = __name__ + + # special keys + skeys = ('delete-device', 'start', 'stop', 'reset', 'restart', 'is-alive', 'status', 'ncs-xml-init') + + # create keys + ckeys = ('create-network', 'add-to-network', 'create-device', 'add-device') + + + def __init__(self, **kwargs) -> None: + self.nwrap = NetsimWrapper(**kwargs) + self.nso = NsoUtils(self.nwrap) + self.links = Download() + self.log = self.nwrap.log + self.utils = self.nwrap.utils + + @property + def version(self): + print('netsim-wrapper version {}'.format(self.nwrap.version)) + self.utils.exit + + @property + def help(self): + print(self.nwrap.help) + self.utils.exit + + @property + def options(self): + return self.utils.flatset(self.nwrap.options.values()) + + def analyse_params(self, params): + self.log.debug("analysing given parameters") + res = defaultdict(lambda: False) + pkeys = ('ncs-xml-init-remote', '--force-generic', 'packages', 'netconf-console', \ + '-w', '--window', 'cli', 'cli-c', 'cli-i', 'get-port', 'list') + + for each in params: + p = str(each).lower() + + # args help, version + if p in self.nwrap.options['help']: + res['help'] = True + return res + if p in self.nwrap.options['version']: + res['version'] = True + return res + + # args link + if res['password']: + res['link'] = each + continue + + # args password + if res['username']: + res['password'] = each + continue + + # args username + if res['download'] or res['filename']: + res['username'] = each + continue + + # args filename + if res['load']: + res['filename'] = each + continue + + if (res['json'] or res['yaml']) and ('.yaml' in p or '.yml' in p or 'json' in p): + res['filename'] = each + continue + + # args devices + if sum((res[i] for i in self.skeys)): + if res['devices'] == False: + res['devices'] = {each} + continue + res['devices'].add(each) + continue + + # args async + if p in {'-a', '--async'}: + res['async'] = True + continue + + # args passing commands + if len(self.utils.loopuntil(p, pkeys)) != len(pkeys): + res['pass'] = True + res['cmd'] = params + return res + + # args prefix + if res['num_devices']: + res['prefix'] = each + continue + + # args device count + if res['package'] and sum((res[i] for i in self.ckeys[:2])): + res['num_devices'] = each + continue + + # args device name + if res['package'] and sum((res[i] for i in self.ckeys[-2:])): + res['device'] = each + continue + + # args package + if sum((res[i] for i in self.ckeys)): + res['package'] = each + continue + + # args netsim dir + if res['--dir'] and res['netsim_dir'] == False: + res['netsim_dir'] = each + continue + + # args automated + res[p] = True + self.log.debug("analysing done") + return res + + def apply(self, params): + params = self.analyse_params(params) + + if params['help']: + self.help + if params['version']: + self.version + + # feature download link + if params['link']: + self.links.get( + params['username'], + params['password'], + params['link'] + ) + self.utils.exit + + # feature template create + if params['create']: + ttype = 'network' if params['network'] else 'device' + ftype = 'yaml' if params['yaml'] else 'json' + filename = params['filename'] or None + self.nwrap.template.create(ttype, ftype, filename) + self.utils.exit + + # feature fetching netsim dir + if not params['--dir']: + params['netsim_dir'] = self.nwrap.netsim.whichdir() + if params['netsim_dir']: + params['--dir'] = True + + if params['--dir']: + self.nwrap.netsim.cmd += ['--dir', params['netsim_dir']] + + # feature which dir + if params['whichdir']: + self.log.info(params['netsim_dir']) + self.utils.exit + + # feature template load and apply + if params['load']: + self.nwrap.template.load(params['filename']) + self.nso.apply() + + # feature directly calling ncs-netsim commands + if params['pass']: + self.nwrap.netsim.run(params['cmd']) + self.utils.exit + + if params['delete-network']: + self.nwrap.netsim.delete_network() + self.utils.exit + + # feature for special keys + for key in self.skeys: + if params[key]: + self.nwrap.special_methods(key, params['devices'], params['async']) + self.utils.exit + + if params[self.ckeys[0]]: # 'create-network' + self.nwrap.create_network(params['package'], params['num_devices'], params['prefix']) + self.utils.exit + + if params[self.ckeys[2]]: # 'create-device' + self.nwrap.create_device(params['package'], params['device']) + self.utils.exit + + if params[self.ckeys[1]]: # 'add-to-network' + self.nwrap.add_to_network(params['package'], params['num_devices'], params['prefix']) + self.utils.exit + + if params[self.ckeys[-1]]: # 'add-device' + self.nwrap.add_device(params['package'], params['device']) + self.utils.exit + + # for k,v in params.items(): + # print("{}: {}".format(k, v)) + diff --git a/netsim_wrapper/utils.py b/netsim_wrapper/utils.py new file mode 100644 index 0000000..eec9979 --- /dev/null +++ b/netsim_wrapper/utils.py @@ -0,0 +1,307 @@ +import re +import sys +import json +import yaml +import logging +import subprocess + +from os import path, remove +from collections import OrderedDict +from itertools import chain, takewhile + +class Singleton(type): + name = __name__ + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +class Logger(metaclass=Singleton): + name = __name__ + format = '%(levelname)5s | %(module)6s | %(message)s' + + def __init__(self, level=None): + self.level = level or logging.INFO + + def setup(self): + logging.basicConfig( + stream=sys.stdout, + level=self.level, + format=self.format, + datefmt=None + ) + log = logging.getLogger(self.name) + log.setLevel(self.level) + return log + + +class Yaml(metaclass=Singleton): + name = __name__ + + def __init__(self): + self.log = Logger().setup() + self.setup() + + def setup(self): + self.log.debug("adding YAML settings") + represent_dict_order = lambda self, data: \ + self.represent_mapping( + 'tag:yaml.org,2002:map', + data.items() + ) + yaml.add_representer(OrderedDict, represent_dict_order) + self.log.debug("added YAML settings") + + def load(self, fname): + self.log.debug("loading YAML file: {}".format(fname)) + try: + with open(fname) as f: + data = yaml.load(f, Loader=yaml.FullLoader) + self.log.debug("YAML file: {} loaded".format(fname)) + return data + except EnvironmentError as e: + self.log.error("error on loading YAML file: {}".format(fname)) + self.log.error(e) + + def dump(self, data, fname=None): + if fname == None: fname='template.yaml' + self.log.debug("creating YAML template file: {}".format(fname)) + try: + with open(fname, 'w') as f: + yaml.dump(data, f, sort_keys=False) + self.log.info("update the base YAML template file: {}".format(fname)) + self.log.debug("created YAML template") + except EnvironmentError as e: + self.log.error("error on creating YAML template file: {}".format(fname)) + self.log.error(e) + + +class Json(metaclass=Singleton): + name = __name__ + + def __init__(self): + self.log = Logger().setup() + + def load(self, fname): + self.log.debug("loading JSON file: {}".format(fname)) + try: + with open(fname) as f: + data = json.load(f) + self.log.debug("JSON file: {} loaded".format(fname)) + return data + except EnvironmentError as e: + self.log.error("error on loading JSON file: {}".format(fname)) + self.log.error(e) + + def dump(self, data, fname=None): + if fname == None: fname='template.json' + self.log.debug("creating JSON template file: {}".format(fname)) + try: + with open(fname, 'w') as f: + json.dump(data, f, indent=2) + self.log.info("update the base JSON template file: {}".format(fname)) + self.log.debug("created JSON template") + except EnvironmentError as e: + self.log.error("error on creating JSON template file: {}".format(fname)) + self.log.error(e) + + def dummy(self, fname='template.json'): # _create_file + if not path.exists(fname): + self.dump(template={}, fname=fname) + + +class XML(metaclass=Singleton): + name = __name__ + + def __init__(self): + self.log = Logger().setup() + + def dump(self, data, fname): + self.log.debug("creating XML file: {}".format(fname)) + try: + with open(fname, 'w') as f: + f.write(data) + self.log.debug("created XML file: {}".format(fname)) + except EnvironmentError as e: + self.log.error("error on creating XML file: {}".format(fname)) + self.log.error(e) + + +class Files(metaclass=Singleton): + name = __name__ + + def __init__(self): + self.log = Logger().setup() + + def is_file(self, fname): + self.log.debug("checking {} is a file".format(fname)) + return path.isfile(fname) + + def is_folder(self, fname): + self.log.debug("checking {} is a folder".format(fname)) + return path.isdir(fname) + + def delete(self, fname): # _delete_file + self.log.debug("deleting {}".format(fname)) + if path.exists(fname): + remove(fname) + self.log.debug("deleted {}".format(fname)) + + def get_ext(self, fname): + self.log.debug("get extention of {}".format(fname)) + return path.splitext(fname)[-1] + + +class Command(metaclass=Singleton): + name = __name__ + stdout = subprocess.PIPE + stderr = subprocess.PIPE + + def __init__(self): + self.log = Logger().setup() + + def decode(self, args): + return (i.decode('utf-8') for i in args) + + def join(self, cmd, sensitive): + # print(cmd) + cmd_str = ' '.join(cmd) + if sensitive: + cmd_str = re.sub(r":\S+", ':xxxxxx', cmd_str) + return cmd_str + + def call(self, cmd, sensitive=False): # _run_bash_commands + cmd_str = self.join(cmd, sensitive) + self.log.debug("executing $ {}".format(cmd_str)) + try: + subprocess.call(cmd, shell=True) + self.log.debug("done") + except EnvironmentError as e: + self.log.error("failed to run: {}".format(cmd)) + self.log.error(e) + + def run(self, cmd, raiseError=True, sensitive=False): # _run_command + cmd_str = self.join(cmd, sensitive) + self.log.debug("executing $ {}".format(cmd_str)) + try: + p = subprocess.Popen( + cmd, + stdout=self.stdout, + stderr=self.stderr + ) + out, err = self.decode(p.communicate()) + if err == '' or 'env.sh' in err: + self.log.debug('done') + return out + if raiseError: + if '% Total' in err: + return err + self.log.debug('validating the error') + if 'command not found' in err or 'Unknown command' in err: + msg = "command `{}` not found".format(cmd_str) + raise SyntaxError(msg) + raise SyntaxError(err) + except SyntaxError as e: + self.log.error("failed to run: {}".format(cmd_str)) + self.log.error(e) + + +class Utils(metaclass=Singleton): + name = __name__ + + def __init__(self, **kwargs): + self.path = path.abspath('.') + self.level=kwargs.get('level', None) + self.log = Logger(self.level).setup() + self.file = Files() + self.json = Json() + self.yaml = Yaml() + self.xml = XML() + self.cmd = Command() + + @property + def exit(self): + sys.exit() + + def rstrip(self, s:str): # _rstrip_digits + return s.rstrip('1234567890') + + def iloc(self, lst, e): # get_index + try: + return lst.index(e) + except ValueError: + return None + + def flatset(slef, s): + return set(chain(*s)) + + def loopuntil(self, p, s): + return set(takewhile(lambda x: x!=p, s)) + + def match(slef, data, regex): + result = set() + for line in data: + res = regex.match(line) + if res: + result.add(res) + return result + + def filter_not_alive(self, data): + self.log.debug("applying not-alive filter") + devices, data = set(), data.split('\n') + regex = re.compile(r'DEVICE\s+(\S+)\s+(.*)') + res = self.match(data, regex) + for match in res: + if match.group(2) in ['FAIL', 'CREATED']: + devices.add(match.group(1)) + return devices + + def filter_alive(self, data): + self.log.debug("applying alive filter") + devices, data = set(), data.split('\n') + regex = re.compile(r'DEVICE\s+(\S+)\s+(.*)') + res = self.match(data, regex) + for match in res: + if match.group(2) == 'OK': + devices.add(match.group(1)) + return devices + + def append_log(self, out:str, method='info'): + for line in out.split('\n'): + if line: getattr(self.log, method)(line) + if self.log.level == logging.DEBUG: + print('') + + def get_tar(self, file): + return re.sub('signed.bin', 'tar.gz', file) + + +class Download(metaclass=Singleton): + name = __name__ + cmd = ["curl", "-k"] + + def __init__(self) -> None: + self.utils = Utils() + self.log = self.utils.log + + def setup(self, username, password): + self.cmd += ['-u', "{}:{}".format(username, password)] + + def download(self, link): + self.log.debug('download: {}'.format(link)) + fname = link.split('/')[-1] or 'dummyFile' + out = self.utils.cmd.run(self.cmd + [link, '-o', fname], sensitive=True) + self.utils.append_log(out) + self.log.debug('download done, file name: {}'.format(fname)) + + def get(self, username, password, links): + self.setup(username, password) + if type(links) == str: + self.download(links) + return + if type(links) == list: + for link in links: + self.download(link) + return diff --git a/setup.py b/setup.py index bfb2478..2e39509 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,8 @@ author_email = 'kirankotari@live.com', entry_points={ 'console_scripts': [ - 'netsim-wrapper=netsim_wrapper.netsim2:run' + 'netsim-wrapper=netsim_wrapper.main:nwrap', + 'nwrap=netsim_wrapper.main:nwrap' ], }, install_requires=reqs, From 4ce56dc46d5b0376c338517e064781a263e4f2b7 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Sat, 25 Feb 2023 21:28:30 -0500 Subject: [PATCH 03/10] updates --- .version | 2 +- netsim_wrapper/main.py | 3 ++- netsim_wrapper/netsim.py | 5 ++-- netsim_wrapper/nso.py | 52 +++++++++++++++++++++++----------------- netsim_wrapper/nwrap.py | 17 ++++++------- netsim_wrapper/utils.py | 11 +++++++-- 6 files changed, 54 insertions(+), 36 deletions(-) diff --git a/.version b/.version index fcdb2e1..0c89fc9 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -4.0.0 +4.0.0 \ No newline at end of file diff --git a/netsim_wrapper/main.py b/netsim_wrapper/main.py index 661c0ef..01d495d 100644 --- a/netsim_wrapper/main.py +++ b/netsim_wrapper/main.py @@ -1,6 +1,7 @@ import sys from logging import DEBUG -from nwrap import NWrap + +from netsim_wrapper.nwrap import NWrap def check_verbose(params): diff --git a/netsim_wrapper/netsim.py b/netsim_wrapper/netsim.py index e802fd0..1893732 100644 --- a/netsim_wrapper/netsim.py +++ b/netsim_wrapper/netsim.py @@ -1,8 +1,9 @@ import re -from logging import DEBUG -from utils import Utils, Singleton from collections import OrderedDict +from netsim_wrapper.utils import Utils, Singleton + + class Netsim(metaclass=Singleton): name = __name__ diff --git a/netsim_wrapper/nso.py b/netsim_wrapper/nso.py index 7205945..e0e5a03 100644 --- a/netsim_wrapper/nso.py +++ b/netsim_wrapper/nso.py @@ -1,12 +1,15 @@ -from utils import Singleton, Download from collections import defaultdict +from netsim_wrapper.utils import Singleton, Download + + class AuthGroup(metaclass=Singleton): name = __name__ - def local(self): - return """ + def add(self, type): + def local(): + return """ @@ -20,8 +23,8 @@ def local(self): """ - def system(self): - return """ + def system(): + return """ @@ -45,13 +48,15 @@ def system(self): """ - + if type == 'system': + return system() + return local() class AAA(metaclass=Singleton): name = __name__ def add(self, type): - def admin(self): + def admin(): return """ @@ -79,7 +84,7 @@ def admin(self): """ - def default(self): + def default(): return """ @@ -176,10 +181,7 @@ def compile_neds(self): def load_authgroup(self, type, path=None): # type -> local/system/custom path = path or '/tmp/authgroup.xml' - if type == 'local': - self.utils.xml.dump(self.auth.local, path) - if type == 'system': - self.utils.xml.dump(self.auth.system, path) + self.utils.xml.dump(self.auth.add(type), path) return path def load_aaa(self, type): # type -> add.admin/add.default @@ -208,20 +210,24 @@ def download_neds(self): self.default['neds'] ) - def extract_neds(self, extract_tar=True): + def extract_neds(self, signed_bin=True, extract_tar=True): self.log.debug("extracting neds") # TODO: need to test - cmd = ['cd', self.default['ppath'], '&&'] + files = [] for ned in self.default['neds']: file = ned.split('/')[-1] self.log.debug('ned: {}'.format(file)) self.utils.cmd.run(['mv', file, self.default['ppath']]) - self.utils.cmd.run(cmd + ['bash', file]) - if not extract_tar: - continue - tar_file = self.utils.get_tar(file) - self.utils.cmd.run(cmd + ['tar', '-xvf', tar_file]) - self.utils.cmd.run(cmd + ['rm', '-rf', tar_file, file]) + files.append(file) + + self.utils.change_dir(self.default['ppath']) + for ned in files: + if signed_bin: + self.utils.cmd.run(['bash', ned]) + if extract_tar: + tar_file = self.utils.get_tar(ned) + self.utils.cmd.run(['tar', '-xvf', tar_file]) + self.utils.cmd.run(['rm', '-rf', tar_file, ned]) self.log.debug('extraction done') def create_devices(self): @@ -277,8 +283,10 @@ def analyse_params(self): self.log.debug("analysing template done") - def apply(self): + def apply(self, username, password): self.analyse_params() + self.default['username'] = username + self.default['password'] = password self.log.debug("per-netsim steps") if self.default['download-neds']: @@ -307,7 +315,7 @@ def pre_create(): self.nwrap.netsim.start() if self.default['add-to-nso']: - path = 'tmp/devices.xml' + path = '/tmp/devices.xml' data = self.nwrap.netsim.ncs_xml_init(sysout=False) self.utils.xml.dump(data, path) diff --git a/netsim_wrapper/nwrap.py b/netsim_wrapper/nwrap.py index 86d10be..acfb649 100644 --- a/netsim_wrapper/nwrap.py +++ b/netsim_wrapper/nwrap.py @@ -1,9 +1,10 @@ -from utils import Singleton, Download -from netsim import Netsim, NetsimTemplates, NetsimInfo, NetsimDelete - -from nso import NsoUtils from collections import defaultdict +from netsim_wrapper.utils import Singleton, Download +from netsim_wrapper.netsim import Netsim, NetsimTemplates +from netsim_wrapper.netsim import NetsimInfo, NetsimDelete +from netsim_wrapper.nso import NsoUtils + help = """ Usage nwrap template create [network | device] [yaml | json] [] @@ -235,6 +236,9 @@ def analyse_params(self, params): def apply(self, params): params = self.analyse_params(params) + # for k,v in params.items(): + # print("{}: {}".format(k, v)) + if params['help']: self.help if params['version']: @@ -274,7 +278,7 @@ def apply(self, params): # feature template load and apply if params['load']: self.nwrap.template.load(params['filename']) - self.nso.apply() + self.nso.apply(params['username'], params['password']) # feature directly calling ncs-netsim commands if params['pass']: @@ -307,6 +311,3 @@ def apply(self, params): self.nwrap.add_device(params['package'], params['device']) self.utils.exit - # for k,v in params.items(): - # print("{}: {}".format(k, v)) - diff --git a/netsim_wrapper/utils.py b/netsim_wrapper/utils.py index eec9979..6ab428c 100644 --- a/netsim_wrapper/utils.py +++ b/netsim_wrapper/utils.py @@ -5,7 +5,7 @@ import logging import subprocess -from os import path, remove +from os import path, remove, chdir from collections import OrderedDict from itertools import chain, takewhile @@ -167,6 +167,8 @@ def decode(self, args): def join(self, cmd, sensitive): # print(cmd) + if type(cmd) == str: + return cmd cmd_str = ' '.join(cmd) if sensitive: cmd_str = re.sub(r":\S+", ':xxxxxx', cmd_str) @@ -234,6 +236,9 @@ def iloc(self, lst, e): # get_index except ValueError: return None + def change_dir(self, path): + chdir(path) + def flatset(slef, s): return set(chain(*s)) @@ -269,6 +274,8 @@ def filter_alive(self, data): return devices def append_log(self, out:str, method='info'): + if not out: + return for line in out.split('\n'): if line: getattr(self.log, method)(line) if self.log.level == logging.DEBUG: @@ -290,7 +297,7 @@ def setup(self, username, password): self.cmd += ['-u', "{}:{}".format(username, password)] def download(self, link): - self.log.debug('download: {}'.format(link)) + self.log.info('download: {}'.format(link)) fname = link.split('/')[-1] or 'dummyFile' out = self.utils.cmd.run(self.cmd + [link, '-o', fname], sensitive=True) self.utils.append_log(out) From ad0fea306c873a5c11b5ddb43235fc2e4a408b6b Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Wed, 1 Mar 2023 22:35:14 -0500 Subject: [PATCH 04/10] inital tests --- netsim_wrapper/pending_list_to_check.yml | 84 +++++++++++++++++++ tests/__init__.py | 1 + tests/{test_netsim.py => _test_netsim.py} | 0 ...sim_wrapper.py => _test_netsim_wrapper.py} | 0 tests/test_nwrap.py | 57 +++++++++++++ 5 files changed, 142 insertions(+) create mode 100644 netsim_wrapper/pending_list_to_check.yml rename tests/{test_netsim.py => _test_netsim.py} (100%) rename tests/{test_netsim_wrapper.py => _test_netsim_wrapper.py} (100%) create mode 100644 tests/test_nwrap.py diff --git a/netsim_wrapper/pending_list_to_check.yml b/netsim_wrapper/pending_list_to_check.yml new file mode 100644 index 0000000..c892b71 --- /dev/null +++ b/netsim_wrapper/pending_list_to_check.yml @@ -0,0 +1,84 @@ +# TODO: list... +7. delete-devices + +==== +how to set custom ports +how to set netsim dir +how to set ncsdir, nsorun, etc + +==== +in templates -> +download - true +ned-links - list +extract - true + +""" +In [10]: def abc(index=None, lazy_load=False): + ...: l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ...: print(l) + ...: index = index or len(l) + ...: print(index) + ...: for i in l[:index]: + ...: print(i) + ...: if lazy_load: + ...: yield + ...: for i in l[index:]: + ...: print(i) + ...: + +In [17]: def xyz(): + ...: for i in abc(index=4, lazy_load=True): + ...: print('I am in-between') + ...: + +In [18]: xyz() +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +4 +0 +1 +2 +3 +I am in-between +4 +5 +6 +7 +8 +9 +""" +Testing --> +Usage nwrap + (template load -> basic is done) + template load [ ] + delete-devices + + +nso-packages-path: "/root/ncs-run-5.7/packages" +download-neds: true +neds: +- https://earth.tail-f.com:8443/ncs-pkgs/cisco-iosxr/5.7.1/ncs-5.7.1-cisco-iosxr-7.38.4.signed.bin +- https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.77.10.signed.bin +- https://earth.tail-f.com:8443/ncs-pkgs/cisco-nx/5.7.1/ncs-5.7.1-cisco-nx-5.22.8.signed.bin +compile-neds: true +start-devices: true +add-to-nso: true +add-authgroup-to-nso: true +authgroup: + type: system + path: +device-mode: + prefix-based: + cisco-ios-cli-6.77: + count: 2 + prefix: ios + cisco-iosxr-cli-7.38: + count: 2 + prefix: xr + cisco-nx-cli-5.22: + count: 2 + prefix: nx +load-day0-config: false +config-path: "/root/ncs-run-5.7/config" +config-files: +- day0.xml +- day1.xml \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..c396168 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import diff --git a/tests/test_netsim.py b/tests/_test_netsim.py similarity index 100% rename from tests/test_netsim.py rename to tests/_test_netsim.py diff --git a/tests/test_netsim_wrapper.py b/tests/_test_netsim_wrapper.py similarity index 100% rename from tests/test_netsim_wrapper.py rename to tests/_test_netsim_wrapper.py diff --git a/tests/test_nwrap.py b/tests/test_nwrap.py new file mode 100644 index 0000000..2f704ab --- /dev/null +++ b/tests/test_nwrap.py @@ -0,0 +1,57 @@ +import pytest +import logging +import argparse +import unittest + +from _pytest.monkeypatch import MonkeyPatch + +from netsim_wrapper import main + +LOGGER = logging.getLogger(__name__) + +# TODO: need to update +def get_inputs(): + args = { + 'username': None, + 'password': None, + 'ext': None, + 'download': False, + 'verbosity': 0, + 'web': None, + 'substring': None, + 'proxy': None, + 'proxy_username': None, + 'proxy_password': None, + 'local': None, + 'global': None + } + return args + + +@pytest.mark.run +class TestNetsimWrapperVersion(unittest.TestCase): + + def setUp(self): + self.monkeypatch = MonkeyPatch() + self.args = get_inputs() + + def tearDown(self) -> None: + self.monkeypatch.undo() + return super().tearDown() + + # @property + # def applyPatch(self): + # f = lambda x: argparse.Namespace(**self.args) + # self.monkeypatch.setattr(argparse.ArgumentParser, 'parse_args', f) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + def test_weblinks_version(self): + self.args['version'] = True + self.applyPatch + run.main() + out, err = self._capfd.readouterr() + assert out.strip() == 'weblinks version: 2.0' + del self.args['version'] \ No newline at end of file From 786e550d1f3c5a45c06b3c918be4ea860f412c1d Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Fri, 3 Mar 2023 18:06:39 -0500 Subject: [PATCH 05/10] adding pytest for commons --- tests/_test_netsim.py | 18 --- tests/_test_netsim_wrapper.py | 17 --- tests/test_nwrap.py | 57 --------- tests/test_nwrap_common.py | 145 ++++++++++++++++++++++ tests/test_nwrap_template.py | 218 ++++++++++++++++++++++++++++++++++ 5 files changed, 363 insertions(+), 92 deletions(-) delete mode 100644 tests/_test_netsim.py delete mode 100644 tests/_test_netsim_wrapper.py delete mode 100644 tests/test_nwrap.py create mode 100644 tests/test_nwrap_common.py create mode 100644 tests/test_nwrap_template.py diff --git a/tests/_test_netsim.py b/tests/_test_netsim.py deleted file mode 100644 index a52121e..0000000 --- a/tests/_test_netsim.py +++ /dev/null @@ -1,18 +0,0 @@ -# create-network -# create-device -# delete-network -# start -# stop -# reset -# restart -# list -# is-alive -# status -# whichdir -# ncs-xml-init -# ncs-xml-init-remote -# --force-generic -# packages -# netconf-console -# -w | --window] [cli | cli-c | cli-i] devname -# get-port devname [ipc | netconf | cli | snmp] \ No newline at end of file diff --git a/tests/_test_netsim_wrapper.py b/tests/_test_netsim_wrapper.py deleted file mode 100644 index a8e32fd..0000000 --- a/tests/_test_netsim_wrapper.py +++ /dev/null @@ -1,17 +0,0 @@ -# help -# create-network-template -# yaml -# json -# create-network-from -# yaml -# json -# create-device-template -# yaml -# json -# create-device-from -# yaml -# json -# add-to-network -# add-device -# delete-devices -# --version diff --git a/tests/test_nwrap.py b/tests/test_nwrap.py deleted file mode 100644 index 2f704ab..0000000 --- a/tests/test_nwrap.py +++ /dev/null @@ -1,57 +0,0 @@ -import pytest -import logging -import argparse -import unittest - -from _pytest.monkeypatch import MonkeyPatch - -from netsim_wrapper import main - -LOGGER = logging.getLogger(__name__) - -# TODO: need to update -def get_inputs(): - args = { - 'username': None, - 'password': None, - 'ext': None, - 'download': False, - 'verbosity': 0, - 'web': None, - 'substring': None, - 'proxy': None, - 'proxy_username': None, - 'proxy_password': None, - 'local': None, - 'global': None - } - return args - - -@pytest.mark.run -class TestNetsimWrapperVersion(unittest.TestCase): - - def setUp(self): - self.monkeypatch = MonkeyPatch() - self.args = get_inputs() - - def tearDown(self) -> None: - self.monkeypatch.undo() - return super().tearDown() - - # @property - # def applyPatch(self): - # f = lambda x: argparse.Namespace(**self.args) - # self.monkeypatch.setattr(argparse.ArgumentParser, 'parse_args', f) - - @pytest.fixture(autouse=True) - def inject_fixtures(self, capfd): - self._capfd = capfd - - def test_weblinks_version(self): - self.args['version'] = True - self.applyPatch - run.main() - out, err = self._capfd.readouterr() - assert out.strip() == 'weblinks version: 2.0' - del self.args['version'] \ No newline at end of file diff --git a/tests/test_nwrap_common.py b/tests/test_nwrap_common.py new file mode 100644 index 0000000..08cfd12 --- /dev/null +++ b/tests/test_nwrap_common.py @@ -0,0 +1,145 @@ +""" +Usage nwrap template create [network | device] [yaml | json] [] + template load [ ] + download link + delete-devices + -v | --version + -vv | --verbose + -h | --help + +nwrap is an alias of `netsim-wrapper` and you can pass +multiple devnames instead of single devname, eg. start, stop, ... + +Additionally we support all `ncs-netsim` commands +""" + +import gc +import sys +import pytest +import unittest + +from logging import DEBUG, INFO +from netsim_wrapper import main +from netsim_wrapper.nwrap import NWrap + +from _pytest.monkeypatch import MonkeyPatch + + +# @pytest.mark.run +class TestNwrapCommon(unittest.TestCase): + + def setUp(self): + self.monkeypatch = MonkeyPatch() + self.args = ['nwrap'] + + def tearDown(self) -> None: + gc.collect() + self.monkeypatch.undo() + return super().tearDown() + + @property + def applyPatch(self): + self.monkeypatch.setattr(sys, 'argv', self.args) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + def test_nwrap_version(self): + self.args.append('-v') + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'netsim-wrapper version 4.0.0' + + def test_nwrap_long_version(self): + self.args.append('--version') + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'netsim-wrapper version 4.0.0' + + def test_nwrap(self): + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + out = out.strip() + assert 'Usage nwrap template create' in out + assert 'template create [network | device] [yaml | json] []' in out + assert 'template load [ ]' in out + assert 'download link' in out + assert 'delete-devices ' in out + assert '-v | --version' in out + assert '-vv | --verbose' in out + assert '-h | --help' in out + + def test_nwrap_help(self): + self.args.append('-h') + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + out = out.strip() + assert 'Usage nwrap template create' in out + + def test_nwrap_long_help(self): + self.args.append('--help') + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + out = out.strip() + assert 'Usage nwrap template create' in out + + +# @pytest.mark.run +class TestNwrapVerbose(unittest.TestCase): + + def setUp(self): + gc.collect() + self.monkeypatch = MonkeyPatch() + self.args = ['nwrap'] + + def tearDown(self) -> None: + self.monkeypatch.undo() + return super().tearDown() + + @property + def applyPatch(self): + self.monkeypatch.setattr(sys, 'argv', self.args) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + def test_nwrap_verbose(self): + self.args.append('-vv') + self.applyPatch + nwrap, p = main.check_verbose(self.args) + assert nwrap.log.level == INFO # DEBUG + # TODO: We logger level is getting set to INFO due to singleton objects + + def test_nwrap_long_verbose(self): + self.args.append('--verbose') + self.applyPatch + nwrap, p = main.check_verbose(self.args) + assert nwrap.log.level == INFO # DEBUG + # TODO: We logger level is getting set to INFO due to singleton objects \ No newline at end of file diff --git a/tests/test_nwrap_template.py b/tests/test_nwrap_template.py new file mode 100644 index 0000000..568acd5 --- /dev/null +++ b/tests/test_nwrap_template.py @@ -0,0 +1,218 @@ +""" +Usage nwrap template create [network | device] [yaml | json] [] + template load [ ] + download link + delete-devices + -v | --version + -vv | --verbose + -h | --help + +nwrap is an alias of `netsim-wrapper` and you can pass +multiple devnames instead of single devname, eg. start, stop, ... + +Additionally we support all `ncs-netsim` commands +""" + +import gc +import os +import sys +import pytest +import unittest + +from logging import DEBUG, INFO +from netsim_wrapper import main +from netsim_wrapper.nwrap import NWrap + +from _pytest.monkeypatch import MonkeyPatch + + +# @pytest.mark.run +class TestNwrapTemplateCreate(unittest.TestCase): + + def setUp(self): + self.monkeypatch = MonkeyPatch() + self.args = ['nwrap'] + + def tearDown(self) -> None: + gc.collect() + files = ['template.json', 'template.yaml', 'basic_template.json', 'basic_template.yaml'] + [os.unlink(each) for each in files if os.path.exists(each)] + self.monkeypatch.undo() + return super().tearDown() + + @property + def applyPatch(self): + self.monkeypatch.setattr(sys, 'argv', self.args) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + @pytest.fixture(autouse=True) + def inject_fixtures_log(self, caplog): + self._caplog = caplog + + def test_nwrap_create_network_yaml(self): + command = 'template create network yaml'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base YAML template file: template.yaml' + assert msg in messages + self._caplog.clear() + + def test_nwrap_create_network_yaml_with_file(self): + command = 'template create network yaml basic_template.yaml'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base YAML template file: basic_template.yaml' + assert msg in messages + self._caplog.clear() + + def test_nwrap_create_network_json(self): + command = 'template create network json'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base JSON template file: template.json' + assert msg in messages + self._caplog.clear() + + def test_nwrap_create_network_json_with_file(self): + command = 'template create network json basic_template.json'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base JSON template file: basic_template.json' + assert msg in messages + self._caplog.clear() + + def test_nwrap_create_device_yaml(self): + command = 'template create device yaml'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base YAML template file: template.yaml' + assert msg in messages + self._caplog.clear() + + def test_nwrap_create_device_yaml_with_file(self): + command = 'template create device yaml basic_template.yaml'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base YAML template file: basic_template.yaml' + assert msg in messages + self._caplog.clear() + + def test_nwrap_create_device_json(self): + command = 'template create device json'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base JSON template file: template.json' + assert msg in messages + self._caplog.clear() + + def test_nwrap_create_device_json_with_file(self): + command = 'template create device json basic_template.json'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'update the base JSON template file: basic_template.json' + assert msg in messages + self._caplog.clear() + + +# @pytest.mark.run +class TestNwrapTemplateLoadNetwork(unittest.TestCase): + + def setUp(self): + self.monkeypatch = MonkeyPatch() + self.args = ['nwrap'] + files = ['template.json', 'template.yaml', 'basic_template.json', 'basic_template.yaml'] + + def tearDown(self) -> None: + gc.collect() + files = ['template.json', 'template.yaml', 'basic_template.json', 'basic_template.yaml'] + [os.unlink(each) for each in files if os.path.exists(each)] + self.monkeypatch.undo() + return super().tearDown() + + @property + def applyPatch(self): + self.monkeypatch.setattr(sys, 'argv', self.args) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + @pytest.fixture(autouse=True) + def inject_fixtures_log(self, caplog): + self._caplog = caplog + From 42ad95fdddc0658aa9aaa5342feb65fb9b831961 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Fri, 3 Mar 2023 18:51:03 -0500 Subject: [PATCH 06/10] updated templates --- templates/device.json | 30 ++++++++++++++++++++ templates/device.yaml | 22 ++++++++++++++ templates/network.json | 30 ++++++++++++++++++++ templates/network.yaml | 22 ++++++++++++++ templates/{ => old}/create_device_from.json | 0 templates/{ => old}/create_device_from.yaml | 0 templates/{ => old}/create_network_from.json | 0 templates/{ => old}/create_network_from.yaml | 0 8 files changed, 104 insertions(+) create mode 100644 templates/device.json create mode 100644 templates/device.yaml create mode 100644 templates/network.json create mode 100644 templates/network.yaml rename templates/{ => old}/create_device_from.json (100%) rename templates/{ => old}/create_device_from.yaml (100%) rename templates/{ => old}/create_network_from.json (100%) rename templates/{ => old}/create_network_from.yaml (100%) diff --git a/templates/device.json b/templates/device.json new file mode 100644 index 0000000..64349eb --- /dev/null +++ b/templates/device.json @@ -0,0 +1,30 @@ +{ + "nso-packages-path": "", + "download-neds": true, + "neds": [ + "", + "" + ], + "compile-neds": true, + "start-devices": true, + "add-to-nso": true, + "add-authgroup-to-nso": true, + "authgroup": { + "type": "custom", + "path": "" + }, + "device-mode": { + "name-based": { + "": [ + "device1", + "device2" + ] + } + }, + "load-day0-config": true, + "config-path": "", + "config-files": [ + "", + "" + ] +} \ No newline at end of file diff --git a/templates/device.yaml b/templates/device.yaml new file mode 100644 index 0000000..6dd1c38 --- /dev/null +++ b/templates/device.yaml @@ -0,0 +1,22 @@ +nso-packages-path: +download-neds: true +neds: +- +- +compile-neds: true +start-devices: true +add-to-nso: true +add-authgroup-to-nso: true +authgroup: + type: custom + path: +device-mode: + name-based: + : + - device1 + - device2 +load-day0-config: true +config-path: +config-files: +- +- diff --git a/templates/network.json b/templates/network.json new file mode 100644 index 0000000..3e1c845 --- /dev/null +++ b/templates/network.json @@ -0,0 +1,30 @@ +{ + "nso-packages-path": "", + "download-neds": true, + "neds": [ + "", + "" + ], + "compile-neds": true, + "start-devices": true, + "add-to-nso": true, + "add-authgroup-to-nso": true, + "authgroup": { + "type": "custom", + "path": "" + }, + "device-mode": { + "prefix-based": { + "": { + "count": 2, + "prefix": "" + } + } + }, + "load-day0-config": true, + "config-path": "", + "config-files": [ + "", + "" + ] +} \ No newline at end of file diff --git a/templates/network.yaml b/templates/network.yaml new file mode 100644 index 0000000..cb1d9b7 --- /dev/null +++ b/templates/network.yaml @@ -0,0 +1,22 @@ +nso-packages-path: +download-neds: true +neds: +- +- +compile-neds: true +start-devices: true +add-to-nso: true +add-authgroup-to-nso: true +authgroup: + type: custom + path: +device-mode: + prefix-based: + : + count: 2 + prefix: +load-day0-config: true +config-path: +config-files: +- +- diff --git a/templates/create_device_from.json b/templates/old/create_device_from.json similarity index 100% rename from templates/create_device_from.json rename to templates/old/create_device_from.json diff --git a/templates/create_device_from.yaml b/templates/old/create_device_from.yaml similarity index 100% rename from templates/create_device_from.yaml rename to templates/old/create_device_from.yaml diff --git a/templates/create_network_from.json b/templates/old/create_network_from.json similarity index 100% rename from templates/create_network_from.json rename to templates/old/create_network_from.json diff --git a/templates/create_network_from.yaml b/templates/old/create_network_from.yaml similarity index 100% rename from templates/create_network_from.yaml rename to templates/old/create_network_from.yaml From 284494e15f10364eeffb1ba226b2e884b2463a57 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Sat, 4 Mar 2023 16:00:34 -0500 Subject: [PATCH 07/10] adding more test-cases --- netsim_wrapper/nso.py | 4 +- tests/.gitignore | 3 + tests/custom_authgroup.xml | 32 +++++++++++ tests/network.json | 28 ++++++++++ tests/test_nwrap_common.py | 14 ++++- tests/test_nwrap_delete_device.py | 70 ++++++++++++++++++++++++ tests/test_nwrap_download.py | 72 ++++++++++++++++++++++++ tests/test_nwrap_template.py | 91 +++++++++++++++++++++++++++++-- 8 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 tests/.gitignore create mode 100644 tests/custom_authgroup.xml create mode 100644 tests/network.json create mode 100644 tests/test_nwrap_delete_device.py create mode 100644 tests/test_nwrap_download.py diff --git a/netsim_wrapper/nso.py b/netsim_wrapper/nso.py index e0e5a03..ff3397d 100644 --- a/netsim_wrapper/nso.py +++ b/netsim_wrapper/nso.py @@ -223,11 +223,11 @@ def extract_neds(self, signed_bin=True, extract_tar=True): self.utils.change_dir(self.default['ppath']) for ned in files: if signed_bin: - self.utils.cmd.run(['bash', ned]) + self.utils.cmd.run(['bash', ned, '--skip-verification']) if extract_tar: tar_file = self.utils.get_tar(ned) self.utils.cmd.run(['tar', '-xvf', tar_file]) - self.utils.cmd.run(['rm', '-rf', tar_file, ned]) + self.utils.cmd.run(['rm', '-rf', tar_file, ned, '*.signature', '*.py', '*.py3', 'tailf.cer']) self.log.debug('extraction done') def create_devices(self): diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..71c01d2 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +# custom file ignore from tests folder +nso-run/ +*cisco* \ No newline at end of file diff --git a/tests/custom_authgroup.xml b/tests/custom_authgroup.xml new file mode 100644 index 0000000..990767c --- /dev/null +++ b/tests/custom_authgroup.xml @@ -0,0 +1,32 @@ + + + + default + + admin + admin + admin + + + admin + admin + admin + + + oper + oper + admin + + + cisco + cisco + cisco + + + lab + lab + lab + + + + \ No newline at end of file diff --git a/tests/network.json b/tests/network.json new file mode 100644 index 0000000..d8c2f76 --- /dev/null +++ b/tests/network.json @@ -0,0 +1,28 @@ +{ + "nso-packages-path": "./nso-run/packages", + "download-neds": false, + "neds": [ + "https://ned-link" + ], + "compile-neds": true, + "start-devices": true, + "add-to-nso": true, + "add-authgroup-to-nso": true, + "authgroup": { + "type": "system" + }, + "device-mode": { + "prefix-based": { + "cisco-ios": { + "count": 2, + "prefix": "ios" + } + } + }, + "load-day0-config": true, + "config-path": "./day0/", + "config-files": [ + "interface_loopback10.cfg", + "interface_loopback100.cfg" + ] +} \ No newline at end of file diff --git a/tests/test_nwrap_common.py b/tests/test_nwrap_common.py index 08cfd12..5e880e4 100644 --- a/tests/test_nwrap_common.py +++ b/tests/test_nwrap_common.py @@ -142,4 +142,16 @@ def test_nwrap_long_verbose(self): self.applyPatch nwrap, p = main.check_verbose(self.args) assert nwrap.log.level == INFO # DEBUG - # TODO: We logger level is getting set to INFO due to singleton objects \ No newline at end of file + # TODO: We logger level is getting set to INFO due to singleton objects + + def test_nwrap_dummy(self): + self.args.append('--dummy') + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + out = out.strip() + assert 'Usage nwrap template create' in out \ No newline at end of file diff --git a/tests/test_nwrap_delete_device.py b/tests/test_nwrap_delete_device.py new file mode 100644 index 0000000..608d1ed --- /dev/null +++ b/tests/test_nwrap_delete_device.py @@ -0,0 +1,70 @@ +""" +Usage nwrap template create [network | device] [yaml | json] [] + template load [ ] + download link + delete-devices + -v | --version + -vv | --verbose + -h | --help + +nwrap is an alias of `netsim-wrapper` and you can pass +multiple devnames instead of single devname, eg. start, stop, ... + +Additionally we support all `ncs-netsim` commands +""" + +import gc +import os +import sys +import pytest +import shutil +import unittest + +from logging import DEBUG, INFO +from netsim_wrapper import main +from netsim_wrapper import utils + +from _pytest.monkeypatch import MonkeyPatch + + +# @pytest.mark.run +class TestNwrapDeleteDevice(unittest.TestCase): + + def setUp(self): + self.monkeypatch = MonkeyPatch() + self.args = ['nwrap'] + + def tearDown(self) -> None: + gc.collect() + self.monkeypatch.undo() + return super().tearDown() + + @property + def applyPatch(self): + self.monkeypatch.setattr(sys, 'argv', self.args) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + @pytest.fixture(autouse=True) + def inject_fixtures_log(self, caplog): + self._caplog = caplog + + def test_nwrap_delete_device(self): + # command = 'template create network yaml'.split() + # self.args.extend(command) + # self.applyPatch + # try: + # main.nwrap() + # except SystemExit as e: + # assert type(e) == SystemExit + # finally: + # out, err = self._capfd.readouterr() + # assert out.strip() == '' + + # messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + # msg = 'update the base YAML template file: template.yaml' + # assert msg in messages + self._caplog.clear() + diff --git a/tests/test_nwrap_download.py b/tests/test_nwrap_download.py new file mode 100644 index 0000000..6d9682c --- /dev/null +++ b/tests/test_nwrap_download.py @@ -0,0 +1,72 @@ +""" +Usage nwrap template create [network | device] [yaml | json] [] + template load [ ] + download link + delete-devices + -v | --version + -vv | --verbose + -h | --help + +nwrap is an alias of `netsim-wrapper` and you can pass +multiple devnames instead of single devname, eg. start, stop, ... + +Additionally we support all `ncs-netsim` commands +""" + +import gc +import os +import sys +import pytest +import shutil +import unittest + +from logging import DEBUG, INFO +from netsim_wrapper import main +from netsim_wrapper import utils + +from _pytest.monkeypatch import MonkeyPatch + + +# @pytest.mark.run +class TestNwrapDownload(unittest.TestCase): + + def setUp(self): + self.monkeypatch = MonkeyPatch() + self.args = ['nwrap'] + + def tearDown(self) -> None: + gc.collect() + self.monkeypatch.undo() + return super().tearDown() + + @property + def applyPatch(self): + self.monkeypatch.setattr(sys, 'argv', self.args) + self.monkeypatch.setattr(utils.Command, 'run', lambda s, c, sensitive: 'download successful') # TODO: need to add mock for subprocessor + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + @pytest.fixture(autouse=True) + def inject_fixtures_log(self, caplog): + self._caplog = caplog + + def test_nwrap_download(self): + command = 'template download user_test password_test https://test.com/download.txt'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + + messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + msg = 'download: https://test.com/download.txt' + assert msg in messages + self._caplog.clear() + + diff --git a/tests/test_nwrap_template.py b/tests/test_nwrap_template.py index 568acd5..56527cd 100644 --- a/tests/test_nwrap_template.py +++ b/tests/test_nwrap_template.py @@ -17,11 +17,12 @@ import os import sys import pytest +import shutil import unittest from logging import DEBUG, INFO from netsim_wrapper import main -from netsim_wrapper.nwrap import NWrap +from netsim_wrapper import utils from _pytest.monkeypatch import MonkeyPatch @@ -188,25 +189,88 @@ def test_nwrap_create_device_json_with_file(self): assert msg in messages self._caplog.clear() +class TestNwrapTemplateData: + @classmethod + def network(cls): + return { + "nso-packages-path": "./nso-run/packages", + "download-neds": True, + "neds": [ + "https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin" + ], + "compile-neds": True, + "start-devices": True, + "add-to-nso": True, + "add-authgroup-to-nso": True, + "authgroup": { + "type": "system" + }, + "device-mode": { + "prefix-based": { + "cisco-ios-6.56": { + "count": 2, + "prefix": "network-ios" + } + } + }, + "load-day0-config": True, + "config-path": "./day0/", + "config-files": [ + "interface_loopback10.cfg", + "interface_loopback100.cfg", + ] + } + + @classmethod + def device(cls): + return { + "nso-packages-path": "./nso-run/packages", + "download-neds": True, + "neds": [ + "https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin" + ], + "compile-neds": True, + "start-devices": True, + "add-to-nso": True, + "add-authgroup-to-nso": True, + "authgroup": { + "type": "custom", + "path": "./custom_authgroup.xml" + }, + "device-mode": { + "name-based": { + "cisco-ios-6.56": [ + "device-ios0", + "device-ios1" + ] + } + }, + "load-day0-config": True, + "config-path": "./day0/", + "config-files": [ + "interface_loopback10.cfg", + "interface_loopback100.cfg", + ] + } # @pytest.mark.run -class TestNwrapTemplateLoadNetwork(unittest.TestCase): +class TestNwrapTemplateLoad(unittest.TestCase): + yaml = utils.Yaml() + json = utils.Json() def setUp(self): self.monkeypatch = MonkeyPatch() self.args = ['nwrap'] - files = ['template.json', 'template.yaml', 'basic_template.json', 'basic_template.yaml'] def tearDown(self) -> None: gc.collect() - files = ['template.json', 'template.yaml', 'basic_template.json', 'basic_template.yaml'] - [os.unlink(each) for each in files if os.path.exists(each)] self.monkeypatch.undo() return super().tearDown() @property def applyPatch(self): self.monkeypatch.setattr(sys, 'argv', self.args) + self.monkeypatch.setattr(utils.Download, 'get', lambda s, u, p, l: None) @pytest.fixture(autouse=True) def inject_fixtures(self, capfd): @@ -216,3 +280,20 @@ def inject_fixtures(self, capfd): def inject_fixtures_log(self, caplog): self._caplog = caplog + def test_nwrap_create_network_json(self): + self.json.dump(TestNwrapTemplateData.network(), 'network.json') + command = 'template load network.json'.split() + self.args.extend(command) + self.applyPatch + # try: + # main.nwrap() + # except SystemExit as e: + # assert type(e) == SystemExit + # finally: + # out, err = self._capfd.readouterr() + # assert out.strip() == '' + # # TODO: need to add some validations + # messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] + # msg = 'update the base YAML template file: template.yaml' + # assert msg in messages + # self._caplog.clear() From d7ea6c20ea60b1653bc943d670cd78aef48f3cd0 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Thu, 9 Mar 2023 10:26:07 -0500 Subject: [PATCH 08/10] enhancement in test-cases --- README.md | 20 +- netsim_wrapper/netsim.py | 5 +- netsim_wrapper/nso.py | 47 +++-- netsim_wrapper/nwrap.py | 5 +- netsim_wrapper/utils.py | 4 + tests/.gitignore | 2 +- tests/configs/custom_authgroup.xml | 12 ++ tests/configs/device.yaml | 22 +++ tests/custom_authgroup.xml | 32 --- tests/day0/device_ios0_interface.cfg | 14 ++ tests/day0/device_ios1_interface.cfg | 14 ++ tests/day0/network_ios0_interface.cfg | 14 ++ tests/day0/network_ios1_interface.cfg | 14 ++ tests/device.json | 30 +++ tests/network.json | 13 +- tests/test_nwrap_commands.py | 267 ++++++++++++++++++++++++++ tests/test_nwrap_common.py | 1 + tests/test_nwrap_download.py | 2 +- tests/test_nwrap_template.py | 155 ++++++++++++--- 19 files changed, 588 insertions(+), 85 deletions(-) create mode 100644 tests/configs/custom_authgroup.xml create mode 100644 tests/configs/device.yaml delete mode 100644 tests/custom_authgroup.xml create mode 100644 tests/day0/device_ios0_interface.cfg create mode 100644 tests/day0/device_ios1_interface.cfg create mode 100644 tests/day0/network_ios0_interface.cfg create mode 100644 tests/day0/network_ios1_interface.cfg create mode 100644 tests/device.json create mode 100644 tests/test_nwrap_commands.py diff --git a/README.md b/README.md index b80871a..c2d6eed 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,20 @@ ncs-netsim is a great tool, but it lack of following features which are developed as part of netsim-wrapper -- netsim-wrapper features - - delete-devices \ - - create-network-from [ yaml | json ] \ - - create-device-from [ yaml | json ] \ - - create-network-template [ yaml | json ] - - create-device-template [ yaml | json ] +```python +Usage nwrap template create [network | device] [yaml | json] [] + template load [ ] + download link + delete-devices + -v | --version + -vv | --verbose + -h | --help + +nwrap is an alias of `netsim-wrapper` and you can pass +multiple devnames instead of single devname, eg. start, stop, ... + +Additionally we support all `ncs-netsim` commands +``` netsim-wrapper is a wrapper on top of ncs-netsim with added features. It's written in python and we opened the space to add more features to it. diff --git a/netsim_wrapper/netsim.py b/netsim_wrapper/netsim.py index 1893732..9d226d6 100644 --- a/netsim_wrapper/netsim.py +++ b/netsim_wrapper/netsim.py @@ -103,9 +103,9 @@ def status(self, devices=None, _async=False): out = self.device('status', devices, False) return out - def list(self): + def list(self, raiseError=True): self.log.debug("fetching netsim list") - out = self.utils.cmd.run(self.cmd + ['list']) + out = self.utils.cmd.run(self.cmd + ['list'], raiseError) self.log.debug("netsim list done") return out @@ -185,6 +185,7 @@ def basic_template(self): template['neds'].append('') template['neds'].append('') template['compile-neds'] = True # True/False + template['packages-reload'] = True # True/False template['start-devices'] = True # True/False template['add-to-nso'] = True # True/False template['add-authgroup-to-nso'] = True # True/False diff --git a/netsim_wrapper/nso.py b/netsim_wrapper/nso.py index ff3397d..490be1a 100644 --- a/netsim_wrapper/nso.py +++ b/netsim_wrapper/nso.py @@ -1,3 +1,6 @@ +import os + +from pathlib import Path from collections import defaultdict from netsim_wrapper.utils import Singleton, Download @@ -178,6 +181,8 @@ def compile_neds(self): self.log.info("$ make clean all -> in {}/src".format(path)) cmd = "cd {}/src; make clean all;".format(path) self.utils.cmd.call(cmd) + ## kkotari: new change + self.utils.change_dir(self.default['cwd']) def load_authgroup(self, type, path=None): # type -> local/system/custom path = path or '/tmp/authgroup.xml' @@ -191,8 +196,8 @@ def load_aaa(self, type): # type -> add.admin/add.default return path def load_merge(self, fpath): - self.log.info("{}# load merge {}".format(self.default['user'], fpath)) - cmd = "echo 'load merge {}' | ncs_cli -u {} -C".format(fpath, self.default['user']) + self.log.info("{}(config)# load merge {}".format(self.default['user'], fpath)) + cmd = "echo 'config ; load merge {} ; commit ;' | ncs_cli -u {} -C".format(fpath, self.default['user']) self.utils.cmd.call(cmd) def fetch_neds(self): @@ -227,12 +232,16 @@ def extract_neds(self, signed_bin=True, extract_tar=True): if extract_tar: tar_file = self.utils.get_tar(ned) self.utils.cmd.run(['tar', '-xvf', tar_file]) - self.utils.cmd.run(['rm', '-rf', tar_file, ned, '*.signature', '*.py', '*.py3', 'tailf.cer']) + self.utils.cmd.run(['rm', '-rf', tar_file, ned, 'tailf.cer', f'{tar_file}.signature']) + self.utils.cmd.run(['rm', '-rf', 'README.signature', 'cisco_x509_verify_release.py']) + self.utils.cmd.run(['rm', '-rf', 'cisco_x509_verify_release.py3']) + ## kkotari: new change + self.utils.change_dir(self.default['cwd']) self.log.debug('extraction done') def create_devices(self): self.log.info("creating netsim devices") - create = True if not self.nwrap.netsim.list() else False + create = True if not self.nwrap.netsim.list(raiseError=False) else False if self.default['ttype'] == 'network': for ned, val in self.default['device-mode']['prefix-based'].items(): path = "{}/{}".format(self.default['ppath'], ned) @@ -256,8 +265,19 @@ def analyse_params(self): self.log.debug("analysing template parameters") self.default.update(self.nwrap.template.data) + # fetching full paths + self.default['nso-packages-path'] = Path(self.default.get('nso-packages-path', '')).absolute().as_posix() + self.default['config-path'] = Path(self.default.get('config-path', '')).absolute().as_posix() + if 'path' in self.default['authgroup']: + self.default['authgroup']['path'] = Path(self.default['authgroup']['path']).absolute().as_posix() + else: + self.default['authgroup']['path'] = None + + # mode, package path self.default['ttype'] = self.nwrap.template.defaults['current'] self.default['ppath'] = self.default['nso-packages-path'] + self.default['runpath'] = Path(self.default['nso-packages-path']).parent.as_posix() + self.default['devices-path'] = '/tmp/devices.xml' # args compile neds if self.default['compile-neds']: @@ -267,7 +287,7 @@ def analyse_params(self): if self.default['add-authgroup-to-nso']: self.default['authgroup-path'] = self.load_authgroup( type=self.default['authgroup']['type'], - path=self.default['authgroup'].get('path', None) + path=self.default['authgroup']['path'] ) # args day0 config @@ -283,8 +303,9 @@ def analyse_params(self): self.log.debug("analysing template done") - def apply(self, username, password): + def apply(self, cwd, username, password): self.analyse_params() + self.default['cwd'] = cwd self.default['username'] = username self.default['password'] = password @@ -292,45 +313,49 @@ def apply(self, username, password): if self.default['download-neds']: self.download_neds() self.extract_neds() - + if self.default['compile-neds']: self.compile_neds() # feature pre netsim create @self.log_ncs_cli def pre_create(): - if self.default['compile-neds']: + if self.default['packages-reload']: self.packages_reload() if self.default['add-authgroup-to-nso']: self.load_merge(self.load_aaa(type='admin')) self.load_merge(self.default['authgroup-path']) return pre_create + pre_create() # feature create devices self.log.debug("netsim steps") + + self.utils.change_dir(self.default['runpath']) self.create_devices() if self.default['start-devices']: self.nwrap.netsim.start() if self.default['add-to-nso']: - path = '/tmp/devices.xml' data = self.nwrap.netsim.ncs_xml_init(sysout=False) - self.utils.xml.dump(data, path) + if data is not None: self.utils.xml.dump(data, self.default['devices-path']) # feature post netsim create self.log.debug("post-netsim steps") @self.log_ncs_cli def post_create(): if self.default['add-to-nso']: - self.load_merge(path) + self.load_merge(self.default['devices-path']) self.fetch_ssh_keys() self.sync_from() + self.utils.change_dir(self.default['cwd']) if self.default['load-day0-config']: for fpath in self.default['config-files']: self.load_merge(fpath) return post_create + post_create() self.log.debug("template done") diff --git a/netsim_wrapper/nwrap.py b/netsim_wrapper/nwrap.py index acfb649..85ecb90 100644 --- a/netsim_wrapper/nwrap.py +++ b/netsim_wrapper/nwrap.py @@ -4,7 +4,7 @@ from netsim_wrapper.netsim import Netsim, NetsimTemplates from netsim_wrapper.netsim import NetsimInfo, NetsimDelete from netsim_wrapper.nso import NsoUtils - +from os import getcwd help = """ Usage nwrap template create [network | device] [yaml | json] [] @@ -149,6 +149,7 @@ def analyse_params(self, params): pkeys = ('ncs-xml-init-remote', '--force-generic', 'packages', 'netconf-console', \ '-w', '--window', 'cli', 'cli-c', 'cli-i', 'get-port', 'list') + res['cwd'] = getcwd() for each in params: p = str(each).lower() @@ -278,7 +279,7 @@ def apply(self, params): # feature template load and apply if params['load']: self.nwrap.template.load(params['filename']) - self.nso.apply(params['username'], params['password']) + self.nso.apply(params['cwd'], params['username'], params['password']) # feature directly calling ncs-netsim commands if params['pass']: diff --git a/netsim_wrapper/utils.py b/netsim_wrapper/utils.py index 6ab428c..081fb98 100644 --- a/netsim_wrapper/utils.py +++ b/netsim_wrapper/utils.py @@ -255,6 +255,8 @@ def match(slef, data, regex): def filter_not_alive(self, data): self.log.debug("applying not-alive filter") + if data == None: + return set() devices, data = set(), data.split('\n') regex = re.compile(r'DEVICE\s+(\S+)\s+(.*)') res = self.match(data, regex) @@ -265,6 +267,8 @@ def filter_not_alive(self, data): def filter_alive(self, data): self.log.debug("applying alive filter") + if data == None: + return set() devices, data = set(), data.split('\n') regex = re.compile(r'DEVICE\s+(\S+)\s+(.*)') res = self.match(data, regex) diff --git a/tests/.gitignore b/tests/.gitignore index 71c01d2..426b4b8 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,3 +1,3 @@ # custom file ignore from tests folder nso-run/ -*cisco* \ No newline at end of file +*cisco* diff --git a/tests/configs/custom_authgroup.xml b/tests/configs/custom_authgroup.xml new file mode 100644 index 0000000..671e6ef --- /dev/null +++ b/tests/configs/custom_authgroup.xml @@ -0,0 +1,12 @@ + + + + + default + + admin + admin + + + + diff --git a/tests/configs/device.yaml b/tests/configs/device.yaml new file mode 100644 index 0000000..10c0e16 --- /dev/null +++ b/tests/configs/device.yaml @@ -0,0 +1,22 @@ +nso-packages-path: ./nso-run/packages +download-neds: false +neds: +- https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin +compile-neds: false +packages-reload: false +start-devices: true +add-to-nso: true +add-authgroup-to-nso: false +authgroup: + type: local + path: ./custom_authgroup.xml +device-mode: + name-based: + cisco-ios-cli-6.79: + - device-ios0 + - device-ios1 +load-day0-config: false +config-path: ../day0/ +config-files: +- device_ios0_interface.cfg +- device_ios1_interface.cfg diff --git a/tests/custom_authgroup.xml b/tests/custom_authgroup.xml deleted file mode 100644 index 990767c..0000000 --- a/tests/custom_authgroup.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - default - - admin - admin - admin - - - admin - admin - admin - - - oper - oper - admin - - - cisco - cisco - cisco - - - lab - lab - lab - - - - \ No newline at end of file diff --git a/tests/day0/device_ios0_interface.cfg b/tests/day0/device_ios0_interface.cfg new file mode 100644 index 0000000..48a738a --- /dev/null +++ b/tests/day0/device_ios0_interface.cfg @@ -0,0 +1,14 @@ +devices device device-ios0 + config + interface Loopback10 + description day0 configuraiton of interface loopback 10 + ip address 10.0.10.0 255.255.255.0 + no shutdown + exit + interface Loopback100 + description day0 configuraiton of interface loopback 100 + ip address 10.0.100.0 255.255.255.0 + no shutdown + exit + ! +! \ No newline at end of file diff --git a/tests/day0/device_ios1_interface.cfg b/tests/day0/device_ios1_interface.cfg new file mode 100644 index 0000000..a55b948 --- /dev/null +++ b/tests/day0/device_ios1_interface.cfg @@ -0,0 +1,14 @@ +devices device device-ios1 + config + interface Loopback10 + description day0 configuraiton of interface loopback 10 + ip address 10.0.10.0 255.255.255.0 + no shutdown + exit + interface Loopback100 + description day0 configuraiton of interface loopback 100 + ip address 10.0.100.0 255.255.255.0 + no shutdown + exit + ! +! \ No newline at end of file diff --git a/tests/day0/network_ios0_interface.cfg b/tests/day0/network_ios0_interface.cfg new file mode 100644 index 0000000..5f40bc6 --- /dev/null +++ b/tests/day0/network_ios0_interface.cfg @@ -0,0 +1,14 @@ +devices device network-ios0 + config + interface Loopback10 + description day0 configuraiton of interface loopback 10 + ip address 10.0.10.0 255.255.255.0 + no shutdown + exit + interface Loopback100 + description day0 configuraiton of interface loopback 100 + ip address 10.0.100.0 255.255.255.0 + no shutdown + exit + ! +! \ No newline at end of file diff --git a/tests/day0/network_ios1_interface.cfg b/tests/day0/network_ios1_interface.cfg new file mode 100644 index 0000000..a228293 --- /dev/null +++ b/tests/day0/network_ios1_interface.cfg @@ -0,0 +1,14 @@ +devices device network-ios1 + config + interface Loopback10 + description day0 configuraiton of interface loopback 10 + ip address 10.1.10.0 255.255.255.0 + no shutdown + exit + interface Loopback100 + description day0 configuraiton of interface loopback 100 + ip address 10.1.100.0 255.255.255.0 + no shutdown + exit + ! +! \ No newline at end of file diff --git a/tests/device.json b/tests/device.json new file mode 100644 index 0000000..250db17 --- /dev/null +++ b/tests/device.json @@ -0,0 +1,30 @@ +{ + "nso-packages-path": "./nso-run/packages", + "download-neds": true, + "neds": [ + "https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin" + ], + "compile-neds": true, + "packages-reload": true, + "start-devices": true, + "add-to-nso": true, + "add-authgroup-to-nso": true, + "authgroup": { + "type": "custom", + "path": "./configs/custom_authgroup.xml" + }, + "device-mode": { + "name-based": { + "cisco-ios-cli-6.79": [ + "device-ios0", + "device-ios1" + ] + } + }, + "load-day0-config": true, + "config-path": "./day0/", + "config-files": [ + "device_ios0_interface.cfg", + "device_ios1_interface.cfg" + ] +} \ No newline at end of file diff --git a/tests/network.json b/tests/network.json index d8c2f76..41a74ca 100644 --- a/tests/network.json +++ b/tests/network.json @@ -1,10 +1,11 @@ { "nso-packages-path": "./nso-run/packages", - "download-neds": false, + "download-neds": true, "neds": [ - "https://ned-link" + "https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin" ], "compile-neds": true, + "packages-reload": true, "start-devices": true, "add-to-nso": true, "add-authgroup-to-nso": true, @@ -13,16 +14,16 @@ }, "device-mode": { "prefix-based": { - "cisco-ios": { + "cisco-ios-cli-6.79": { "count": 2, - "prefix": "ios" + "prefix": "network-ios" } } }, "load-day0-config": true, "config-path": "./day0/", "config-files": [ - "interface_loopback10.cfg", - "interface_loopback100.cfg" + "network_ios0_interface.cfg", + "network_ios1_interface.cfg" ] } \ No newline at end of file diff --git a/tests/test_nwrap_commands.py b/tests/test_nwrap_commands.py new file mode 100644 index 0000000..0edc33a --- /dev/null +++ b/tests/test_nwrap_commands.py @@ -0,0 +1,267 @@ +""" +Usage nwrap template create [network | device] [yaml | json] [] + template load [ ] + download link + delete-devices + -v | --version + -vv | --verbose + -h | --help + +nwrap is an alias of `netsim-wrapper` and you can pass +multiple devnames instead of single devname, eg. start, stop, ... + +Additionally we support all `ncs-netsim` commands +""" + +import gc +import os +import sys +import pytest +import shutil +import unittest + +from logging import DEBUG, INFO +from netsim_wrapper import main +from netsim_wrapper import utils + +from _pytest.monkeypatch import MonkeyPatch + +netsim_help = """Usage ncs-netsim [--dir ] + create-network | + create-device | + add-to-network | + add-device | + delete-network | + [-a | --async] start [devname] | + [-a | --async ] stop [devname] | + [-a | --async ] reset [devname] | + [-a | --async ] restart [devname] | + list | + is-alive [devname] | + status [devname] | + whichdir | + ncs-xml-init [devname] | + ncs-xml-init-remote [devname] | + [--force-generic] | + packages | + netconf-console devname [XpathFilter] | + [-w | --window] [cli | cli-c | cli-i] devname | + get-port devname [ipc | netconf | cli | snmp] + +See manpage for ncs-netsim for more info. NetsimDir is optional +and defaults to ./netsim, any netsim directory above in the path, +or $NETSIM_DIR if set. +""" +# @pytest.mark.run +class TestNwrapCommands(unittest.TestCase): + commands = [ + 'create-network', 'create-device', 'add-to-network', 'add-device', + 'start', 'stop', 'reset', 'restart', 'delete-network', + 'status' + ] + def setUp(self): + self.monkeypatch = MonkeyPatch() + self.args = ['nwrap'] + + def tearDown(self) -> None: + gc.collect() + self.monkeypatch.undo() + return super().tearDown() + + def _mock_run(self, cmd, raiseError=True, sensitive=False): + cmd = set(cmd) + # print(cmd) + if 'whichdir' in cmd: + return './nso-run/netsim' + if '--help' in cmd: + return netsim_help + if 'is-alive' in cmd: + return None + if 'list' in cmd: + return None + for each in self.commands: + if each in cmd: + print(each) + return + + @property + def applyPatch(self): + self.monkeypatch.setattr(sys, 'argv', self.args) + self.monkeypatch.setattr(utils.Command, 'run', self._mock_run) + + @pytest.fixture(autouse=True) + def inject_fixtures(self, capfd): + self._capfd = capfd + + @pytest.fixture(autouse=True) + def inject_fixtures_log(self, caplog): + self._caplog = caplog + + def test_nwrap_create_network(self): + command = 'create-network dummyPackage ios 2'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'create-network' + self._caplog.clear() + + def test_nwrap_create_device(self): + command = 'create-device dummyPackage ios'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'create-device' + self._caplog.clear() + + def test_nwrap_add_network(self): + command = 'add-to-network dummyPackage ios 2'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'add-to-network' + self._caplog.clear() + + def test_nwrap_add_device(self): + command = '--dir dummy add-device dummyPackage ios'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'add-device' + self._caplog.clear() + + def test_nwrap_delete_network(self): + command = 'delete-network'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'delete-network' + self._caplog.clear() + + def test_nwrap_delete_network(self): + command = 'whichdir'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == '' + self._caplog.clear() + + def test_nwrap_device_start(self): + command = 'start'.split() + self.args.extend(command) + self.applyPatch + # try: + # main.nwrap() + # except SystemExit as e: + # assert type(e) == SystemExit + # finally: + # out, err = self._capfd.readouterr() + # assert out.strip() == 'start' + # self._caplog.clear() + + def test_nwrap_device_stop(self): + command = '--async stop ios0'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'stop' + self._caplog.clear() + + def test_nwrap_device_stop(self): + command = '-a stop ios0 ios1'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'stop' + self._caplog.clear() + + def test_nwrap_device_reset(self): + command = '-a reset ios0'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'reset' + self._caplog.clear() + + def test_nwrap_device_restart(self): + command = 'restart ios0'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'restart' + self._caplog.clear() + + def test_nwrap_device_list(self): + command = ['list'] + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert 'None' in out.strip() + self._caplog.clear() + + def test_nwrap_device_restart(self): + command = 'status'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + assert out.strip() == 'status' + self._caplog.clear() \ No newline at end of file diff --git a/tests/test_nwrap_common.py b/tests/test_nwrap_common.py index 5e880e4..5a4643e 100644 --- a/tests/test_nwrap_common.py +++ b/tests/test_nwrap_common.py @@ -130,6 +130,7 @@ def applyPatch(self): def inject_fixtures(self, capfd): self._capfd = capfd + @pytest.mark.run(order=1) def test_nwrap_verbose(self): self.args.append('-vv') self.applyPatch diff --git a/tests/test_nwrap_download.py b/tests/test_nwrap_download.py index 6d9682c..4fc7307 100644 --- a/tests/test_nwrap_download.py +++ b/tests/test_nwrap_download.py @@ -42,7 +42,7 @@ def tearDown(self) -> None: @property def applyPatch(self): self.monkeypatch.setattr(sys, 'argv', self.args) - self.monkeypatch.setattr(utils.Command, 'run', lambda s, c, sensitive: 'download successful') # TODO: need to add mock for subprocessor + self.monkeypatch.setattr(utils.Command, 'run', lambda s, c, sensitive: 'download successful') @pytest.fixture(autouse=True) def inject_fixtures(self, capfd): diff --git a/tests/test_nwrap_template.py b/tests/test_nwrap_template.py index 56527cd..4d546f8 100644 --- a/tests/test_nwrap_template.py +++ b/tests/test_nwrap_template.py @@ -20,6 +20,7 @@ import shutil import unittest +from pathlib import Path from logging import DEBUG, INFO from netsim_wrapper import main from netsim_wrapper import utils @@ -199,6 +200,7 @@ def network(cls): "https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin" ], "compile-neds": True, + "packages-reload": True, "start-devices": True, "add-to-nso": True, "add-authgroup-to-nso": True, @@ -207,7 +209,7 @@ def network(cls): }, "device-mode": { "prefix-based": { - "cisco-ios-6.56": { + "cisco-ios-cli-6.79": { "count": 2, "prefix": "network-ios" } @@ -216,8 +218,8 @@ def network(cls): "load-day0-config": True, "config-path": "./day0/", "config-files": [ - "interface_loopback10.cfg", - "interface_loopback100.cfg", + "network_ios0_interface.cfg", + "network_ios1_interface.cfg", ] } @@ -230,16 +232,17 @@ def device(cls): "https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin" ], "compile-neds": True, + "packages-reload": True, "start-devices": True, "add-to-nso": True, "add-authgroup-to-nso": True, "authgroup": { "type": "custom", - "path": "./custom_authgroup.xml" + "path": "./configs/custom_authgroup.xml" }, "device-mode": { "name-based": { - "cisco-ios-6.56": [ + "cisco-ios-cli-6.79": [ "device-ios0", "device-ios1" ] @@ -248,19 +251,31 @@ def device(cls): "load-day0-config": True, "config-path": "./day0/", "config-files": [ - "interface_loopback10.cfg", - "interface_loopback100.cfg", + "device_ios0_interface.cfg", + "device_ios1_interface.cfg", ] } + + @classmethod + def ned_path(cls): + return Path('/Users/kkotari/git/netsim-wrapper/tests/configs/ncs-5.7.1-cisco-ios-6.79.signed.bin').absolute().as_posix() # @pytest.mark.run class TestNwrapTemplateLoad(unittest.TestCase): - yaml = utils.Yaml() - json = utils.Json() + u = utils.Utils() + yaml = u.yaml + json = u.json + sh = u.cmd def setUp(self): + self.cpath = os.getcwd() self.monkeypatch = MonkeyPatch() self.args = ['nwrap'] + shutil.copyfile( + TestNwrapTemplateData.ned_path(), + 'ncs-5.7.1-cisco-ios-6.79.signed.bin' + ) + self._cleanup() def tearDown(self) -> None: gc.collect() @@ -280,20 +295,112 @@ def inject_fixtures(self, capfd): def inject_fixtures_log(self, caplog): self._caplog = caplog - def test_nwrap_create_network_json(self): - self.json.dump(TestNwrapTemplateData.network(), 'network.json') - command = 'template load network.json'.split() + def validation_logs(self, messages, file, ttype): + msg = f'update the base {file} template file: {ttype}.json' + assert msg in messages + msg = '$ make clean all -> in /Users/kkotari/git/netsim-wrapper/tests/nso-run/packages/cisco-ios-cli-6.79/src' + assert msg in messages + msg = '$ ncs_cli -Cu admin' + assert msg in messages + msg = 'admin# packages reload force' + assert msg in messages + msg = 'admin(config)# load merge /tmp/aaa.xml' + assert msg in messages + # msg = f'DEVICE {ttype}-ios0 OK STARTED' + # assert msg in messages + # msg = f'DEVICE {ttype}-ios1 OK STARTED' + # assert msg in messages + msg = 'admin(config)# load merge /tmp/devices.xml' + assert msg in messages + msg = 'admin# devices fetch-ssh-host-keys' + assert msg in messages + msg = 'admin# devices sync-from' + assert msg in messages + msg = f'admin(config)# load merge /Users/kkotari/git/netsim-wrapper/tests/day0/{ttype}_ios0_interface.cfg' + assert msg in messages + msg = f'admin(config)# load merge /Users/kkotari/git/netsim-wrapper/tests/day0/{ttype}_ios1_interface.cfg' + assert msg in messages + + def _cleanup(self): + cmd = "echo 'config ; no devices device ; no devices authgroup ; no aaa ; commit ;' | ncs_cli -u admin -C > /dev/null" + self.sh.call(cmd) + self.u.change_dir(f'{self.cpath}/nso-run') + self.sh.call("ncs-netsim delete-network > /dev/null") + self.u.change_dir(self.cpath) + + def test_nwrap_load_network_json(self): + tname = 'network.json' + self.json.dump(TestNwrapTemplateData.network(), tname) + command = f'template load {tname} dummy_user dummy_password -vv'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + # assert out.strip() == '' + messages = { x.message for x in self._caplog.get_records('call') if x.levelno == INFO } + self.validation_logs(messages, file='JSON', ttype='network') + msg = 'admin(config)# load merge /tmp/authgroup.xml' + assert msg in messages + + # clean up + self._cleanup() + self._caplog.clear() + + def test_nwrap_load_device_json(self): + tname = 'device.json' + self.json.dump(TestNwrapTemplateData.device(), tname) + command = f'template load {tname} dummy_user dummy_password -vv'.split() + self.args.extend(command) + self.applyPatch + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + # assert out.strip() == '' + + messages = { x.message for x in self._caplog.get_records('call') if x.levelno == INFO } + self.validation_logs(messages, file='JSON', ttype='device') + msg = 'admin(config)# load merge /Users/kkotari/git/netsim-wrapper/tests/configs/custom_authgroup.xml' + assert msg in messages + + # clean up + self._cleanup() + self._caplog.clear() + + def test_nwrap_load_device_yaml(self): + tname = './configs/device.yaml' + template = TestNwrapTemplateData.device() + template['download-neds'] = template['compile-neds'] = template['packages-reload'] = False + template['load-day0-config'] = template['add-authgroup-to-nso'] = False + template['authgroup']['type'] = 'local' + template['authgroup']['path'] = './custom_authgroup.xml' + template['config-path'] = '../day0/' + self.yaml.dump(template, tname) + command = f'template load {tname} -vv'.split() self.args.extend(command) self.applyPatch - # try: - # main.nwrap() - # except SystemExit as e: - # assert type(e) == SystemExit - # finally: - # out, err = self._capfd.readouterr() - # assert out.strip() == '' - # # TODO: need to add some validations - # messages = [ x.message for x in self._caplog.get_records('call') if x.levelno == INFO ] - # msg = 'update the base YAML template file: template.yaml' - # assert msg in messages - # self._caplog.clear() + try: + main.nwrap() + except SystemExit as e: + assert type(e) == SystemExit + finally: + out, err = self._capfd.readouterr() + # assert out.strip() == '' + + messages = { x.message for x in self._caplog.get_records('call') if x.levelno == INFO } + msg = 'admin# devices fetch-ssh-host-keys' + assert msg in messages + msg = 'admin# devices sync-from' + assert msg in messages + + # clean up + self._cleanup() + self._caplog.clear() + + From 96d720a7eb7e4b418bbcbc3c8c6b033506bf2f5f Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Thu, 9 Mar 2023 10:32:38 -0500 Subject: [PATCH 09/10] updating readme --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/README.md b/README.md index c2d6eed..e6062ab 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,66 @@ ncs-netsim, It's a powerful tool to build a simulated network environment for Ne netsim-wrapper, An open space to automate the ncs-netsim. +You can build your netsim network using JSON/YAML templates as follows + +**creating device using YAML file** +``` +nso-packages-path: ./nso-run/package +download-neds: false +neds: +- https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin +compile-neds: false +packages-reload: false +start-devices: true +add-to-nso: true +add-authgroup-to-nso: false +authgroup: + type: local + path: ./custom_authgroup.xml +device-mode: + name-based: + cisco-ios-cli-6.79: + - device-ios0 + - device-ios1 +load-day0-config: false +config-path: ../day0/ +config-files: +- device_ios0_interface.cfg +- device_ios1_interface.cfg +``` + +**creating network using JSON format** +``` +{ + "nso-packages-path": "./nso-run/packages", + "download-neds": true, + "neds": [ + "https://earth.tail-f.com:8443/ncs-pkgs/cisco-ios/5.7.1/ncs-5.7.1-cisco-ios-6.79.signed.bin" + ], + "compile-neds": true, + "packages-reload": true, + "start-devices": true, + "add-to-nso": true, + "add-authgroup-to-nso": true, + "authgroup": { + "type": "system" + }, + "device-mode": { + "prefix-based": { + "cisco-ios-cli-6.79": { + "count": 2, + "prefix": "network-ios" + } + } + }, + "load-day0-config": true, + "config-path": "./day0/", + "config-files": [ + "network_ios0_interface.cfg", + "network_ios1_interface.cfg" + ] +} +``` ## Pre-requisites - ncs-netsim command must be reconginsed by the terminal. From 3df9912b39636c9ac969cd03ec07de4b14673940 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kotari Date: Tue, 14 Mar 2023 13:22:35 -0400 Subject: [PATCH 10/10] asking pass --- netsim_wrapper/nso.py | 4 +++- netsim_wrapper/utils.py | 2 +- tests/.gitignore | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/netsim_wrapper/nso.py b/netsim_wrapper/nso.py index 490be1a..8a142fe 100644 --- a/netsim_wrapper/nso.py +++ b/netsim_wrapper/nso.py @@ -1,5 +1,5 @@ import os - +import getpass from pathlib import Path from collections import defaultdict @@ -206,6 +206,8 @@ def fetch_neds(self): return set(self.default['device-mode']['name-based'].keys()) def download_neds(self): + if not (self.default['username'] and self.default['password']): + self.default['password'] = getpass.getpass('enter the user {}\'s password: '.format(self.default['username'])) if not (self.default['username'] and self.default['password']): self.log.error("required and to download") self.utils.exit diff --git a/netsim_wrapper/utils.py b/netsim_wrapper/utils.py index 081fb98..48311a8 100644 --- a/netsim_wrapper/utils.py +++ b/netsim_wrapper/utils.py @@ -291,7 +291,7 @@ def get_tar(self, file): class Download(metaclass=Singleton): name = __name__ - cmd = ["curl", "-k"] + cmd = ["curl", "-k"] # "--progress-bar" def __init__(self) -> None: self.utils = Utils() diff --git a/tests/.gitignore b/tests/.gitignore index 426b4b8..7ec73e1 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,3 +1,4 @@ # custom file ignore from tests folder nso-run/ +*.bin *cisco*