7 from os import chdir, getcwd, path
8 from shutil import copytree
11 from requests import get
12 from requests.exceptions import MissingSchema, InvalidSchema, InvalidURL, ConnectionError, ConnectTimeout
14 def validate_url(url):
15 '''Helper function to perform --urlves input param validation'''
16 logger = logging.getLogger("urllib3")
17 logger.setLevel(logging.WARNING)
19 get(url, timeout=0.001)
20 except (MissingSchema, InvalidSchema, InvalidURL):
21 raise argparse.ArgumentTypeError(f'{url} is not a valid URL')
22 except (ConnectionError, ConnectTimeout):
27 '''Helper function to validate input param is a vaild IP address'''
29 ip_valid = ipaddress.ip_address(ip)
31 raise argparse.ArgumentTypeError(f'{ip} is not a valid IP address')
36 '''Process input arguments'''
38 parser = argparse.ArgumentParser()
39 subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
40 # Build command parser
41 subparsers.add_parser('build', help='Build simulator image')
42 # Bootstrap command parser
43 parser_bootstrap = subparsers.add_parser('bootstrap', help='Bootstrap the system')
44 parser_bootstrap.add_argument('--count', help='Instance count to bootstrap', type=int, metavar='INT', default=1)
45 parser_bootstrap.add_argument('--urlves', help='URL of the VES collector', type=validate_url, metavar='URL', required=True)
46 parser_bootstrap.add_argument('--ipfileserver', help='Visible IP of the file server (SFTP/FTPS) to be included in the VES event',
47 type=validate_ip, metavar='IP', required=True)
48 parser_bootstrap.add_argument('--typefileserver', help='Type of the file server (SFTP/FTPS) to be included in the VES event',
49 type=str, choices=['sftp', 'ftps'], required=True)
50 parser_bootstrap.add_argument('--ipstart', help='IP address range beginning', type=validate_ip, metavar='IP', required=True)
51 # Start command parser
52 parser_start = subparsers.add_parser('start', help='Start instances')
53 parser_start.add_argument('--count', help='Instance count to start', type=int, metavar='INT', default=1)
55 parser_stop = subparsers.add_parser('stop', help='Stop instances')
56 parser_stop.add_argument('--count', help='Instance count to stop', type=int, metavar='INT', default=1)
57 # Trigger command parser
58 parser_trigger = subparsers.add_parser('trigger', help='Trigger one single VES event from each simulator')
59 parser_trigger.add_argument('--count', help='Instance count to trigger', type=int, metavar='INT', default=1)
60 # Trigger-custom command parser
61 parser_triggerstart = subparsers.add_parser('trigger_custom', help='Trigger one single VES event from specific simulators')
62 parser_triggerstart.add_argument('--triggerstart', help='First simulator id to trigger', type=int,
63 metavar='INT', required=True)
64 parser_triggerstart.add_argument('--triggerend', help='Last simulator id to trigger', type=int,
65 metavar='INT', required=True)
66 # Status command parser
67 parser_status = subparsers.add_parser('status', help='Status')
68 parser_status.add_argument('--count', help='Instance count to show status for', type=int, metavar='INT', default=1)
69 # Clean command parser
70 subparsers.add_parser('clean', help='Clean work-dirs')
71 # General options parser
72 parser.add_argument('--verbose', help='Verbosity level', choices=['info', 'debug'],
73 type=str, default='info')
78 # MassPnfSim class actions decorator
79 class _MassPnfSim_Decorators:
82 def do_action(action_string, cmd):
83 def action_decorator(method):
84 def action_wrap(self):
86 # Append instance # if action is 'stop'
87 if method.__name__ == 'stop':
89 # Alter looping range if action is 'tigger_custom'
90 if method.__name__ == 'trigger_custom':
91 iter_range = [self.args.triggerstart, self.args.triggerend+1]
93 iter_range = [self.args.count]
95 for i in range(*iter_range):
96 self.logger.info(f'{action_string} {self.sim_dirname_pattern}{i} instance:')
97 self._run_cmd(cmd_local.format(i), f"{self.sim_dirname_pattern}{i}")
99 return action_decorator
101 log_lvl = logging.INFO
103 def __init__(self, args):
105 self.logger = logging.getLogger(__name__)
106 self.logger.setLevel(self.log_lvl)
107 self.sim_dirname_pattern = "pnf-sim-lw-"
108 self.mvn_build_cmd = 'mvn clean package docker:build -Dcheckstyle.skip'
109 self.existing_sim_instances = self._enum_sim_instances()
111 def _run_cmd(self, cmd, dir_context='.'):
112 if self.args.verbose == 'debug':
117 subprocess.run(cmd, check=True, shell=True)
119 except FileNotFoundError:
120 self.logger.error(f"Directory {dir_context} not found")
121 except subprocess.CalledProcessError as e:
124 def _enum_sim_instances(self):
125 '''Helper method that returns bootstraped simulator instances count'''
126 return len(glob(f"{self.sim_dirname_pattern}[0-9]*"))
129 self.logger.info("Bootstrapping PNF instances")
132 ftps_pasv_port_start = 8000
133 ftps_pasv_port_num_of_ports = 10
135 ftps_pasv_port_end = ftps_pasv_port_start + ftps_pasv_port_num_of_ports
137 for i in range(self.args.count):
138 self.logger.info(f"PNF simulator instance: {i}")
140 # The IP ranges are in distance of 16 compared to each other.
141 # This is matching the /28 subnet mask used in the dockerfile inside.
142 instance_ip_offset = i * 16
153 for prop in ip_properties:
154 ip.update({prop: str(self.args.ipstart + ip_offset + instance_ip_offset)})
157 self.logger.debug(f'Instance #{i} properties:\n {dumps(ip, indent=4)}')
159 PortSftp = start_port + 1
160 PortFtps = start_port + 2
163 self.logger.info(f'\tCreating {self.sim_dirname_pattern}{i}')
165 copytree('pnf-sim-lightweight', f'{self.sim_dirname_pattern}{i}')
166 except FileExistsError:
167 self.logger.error(f'Directory {self.sim_dirname_pattern}{i} already exists, cannot overwrite.')
170 composercmd = " ".join([
171 "./simulator.sh compose",
177 str(self.args.ipfileserver),
178 self.args.typefileserver,
183 str(ftps_pasv_port_start),
184 str(ftps_pasv_port_end)
186 self.logger.debug(f"Script cmdline: {composercmd}")
187 self.logger.info(f"\tCreating instance #{i} configuration ")
188 self._run_cmd(composercmd, f"{self.sim_dirname_pattern}{i}")
190 ftps_pasv_port_start += ftps_pasv_port_num_of_ports + 1
191 ftps_pasv_port_end += ftps_pasv_port_num_of_ports + 1
193 self.logger.info(f'Done setting up instance #{i}')
196 self.logger.info("Building simulator image")
197 if path.isfile('pnf-sim-lightweight/pom.xml'):
198 self._run_cmd(self.mvn_build_cmd, 'pnf-sim-lightweight')
200 self.logger.error('POM file was not found, Maven cannot run')
204 self.logger.info('Cleaning simulators workdirs')
205 self._run_cmd(f"rm -rf {self.sim_dirname_pattern}*")
207 @_MassPnfSim_Decorators.do_action('Starting', './simulator.sh start')
211 @_MassPnfSim_Decorators.do_action('Getting', './simulator.sh status')
215 @_MassPnfSim_Decorators.do_action('Stopping', './simulator.sh stop')
219 @_MassPnfSim_Decorators.do_action('Triggering', './simulator.sh trigger-simulator')
221 self.logger.info("Triggering VES sending:")
223 @_MassPnfSim_Decorators.do_action('Triggering', './simulator.sh trigger-simulator')
224 def trigger_custom(self):
225 self.logger.info("Triggering VES sending by a range of simulators:")