Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | import logging |
Bartek Grzybowski | f2b1483 | 2020-05-25 00:47:35 -0700 | [diff] [blame] | 3 | from subprocess import run, CalledProcessError |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 4 | import argparse |
| 5 | import ipaddress |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 6 | from sys import exit |
Bartek Grzybowski | 9ab2f40 | 2020-06-12 12:20:48 +0200 | [diff] [blame] | 7 | from os import chdir, getcwd, path, popen, kill, getuid, stat, mkdir, getlogin |
Bartek Grzybowski | 138b199 | 2020-06-08 14:25:44 +0200 | [diff] [blame] | 8 | from shutil import copytree, rmtree, move |
Bartek Grzybowski | 626f286 | 2020-06-04 09:55:26 +0200 | [diff] [blame] | 9 | from json import loads, dumps |
Bartek Grzybowski | 700b607 | 2020-06-09 12:41:08 +0200 | [diff] [blame] | 10 | from yaml import load, SafeLoader, dump |
Bartek Grzybowski | a71704c | 2020-05-19 04:47:09 -0700 | [diff] [blame] | 11 | from glob import glob |
Bartek Grzybowski | 1fbf854 | 2020-06-10 15:16:23 +0200 | [diff] [blame] | 12 | from time import strftime, tzname, daylight |
Bartek Grzybowski | 110badf | 2020-06-01 12:31:42 +0200 | [diff] [blame] | 13 | from docker import from_env |
Bartek Grzybowski | 626f286 | 2020-06-04 09:55:26 +0200 | [diff] [blame] | 14 | from requests import get, codes, post |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 15 | from requests.exceptions import MissingSchema, InvalidSchema, InvalidURL, ConnectionError, ConnectTimeout |
| 16 | |
| 17 | def validate_url(url): |
| 18 | '''Helper function to perform --urlves input param validation''' |
| 19 | logger = logging.getLogger("urllib3") |
| 20 | logger.setLevel(logging.WARNING) |
| 21 | try: |
| 22 | get(url, timeout=0.001) |
| 23 | except (MissingSchema, InvalidSchema, InvalidURL): |
| 24 | raise argparse.ArgumentTypeError(f'{url} is not a valid URL') |
| 25 | except (ConnectionError, ConnectTimeout): |
| 26 | pass |
| 27 | return url |
| 28 | |
| 29 | def validate_ip(ip): |
| 30 | '''Helper function to validate input param is a vaild IP address''' |
| 31 | try: |
| 32 | ip_valid = ipaddress.ip_address(ip) |
| 33 | except ValueError: |
| 34 | raise argparse.ArgumentTypeError(f'{ip} is not a valid IP address') |
| 35 | else: |
| 36 | return ip_valid |
| 37 | |
| 38 | def get_parser(): |
| 39 | '''Process input arguments''' |
| 40 | |
| 41 | parser = argparse.ArgumentParser() |
| 42 | subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand') |
Bartek Grzybowski | d203820 | 2020-05-15 00:57:25 -0700 | [diff] [blame] | 43 | # Build command parser |
| 44 | subparsers.add_parser('build', help='Build simulator image') |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 45 | # Bootstrap command parser |
| 46 | parser_bootstrap = subparsers.add_parser('bootstrap', help='Bootstrap the system') |
| 47 | parser_bootstrap.add_argument('--count', help='Instance count to bootstrap', type=int, metavar='INT', default=1) |
| 48 | parser_bootstrap.add_argument('--urlves', help='URL of the VES collector', type=validate_url, metavar='URL', required=True) |
| 49 | parser_bootstrap.add_argument('--ipfileserver', help='Visible IP of the file server (SFTP/FTPS) to be included in the VES event', |
| 50 | type=validate_ip, metavar='IP', required=True) |
| 51 | parser_bootstrap.add_argument('--typefileserver', help='Type of the file server (SFTP/FTPS) to be included in the VES event', |
| 52 | type=str, choices=['sftp', 'ftps'], required=True) |
| 53 | parser_bootstrap.add_argument('--ipstart', help='IP address range beginning', type=validate_ip, metavar='IP', required=True) |
| 54 | # Start command parser |
| 55 | parser_start = subparsers.add_parser('start', help='Start instances') |
Bartek Grzybowski | 5717b5e | 2020-05-19 04:50:51 -0700 | [diff] [blame] | 56 | parser_start.add_argument('--count', help='Instance count to start', type=int, metavar='INT', default=0) |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 57 | # Stop command parser |
| 58 | parser_stop = subparsers.add_parser('stop', help='Stop instances') |
Bartek Grzybowski | 5717b5e | 2020-05-19 04:50:51 -0700 | [diff] [blame] | 59 | parser_stop.add_argument('--count', help='Instance count to stop', type=int, metavar='INT', default=0) |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 60 | # Trigger command parser |
| 61 | parser_trigger = subparsers.add_parser('trigger', help='Trigger one single VES event from each simulator') |
Bartek Grzybowski | 5717b5e | 2020-05-19 04:50:51 -0700 | [diff] [blame] | 62 | parser_trigger.add_argument('--count', help='Instance count to trigger', type=int, metavar='INT', default=0) |
Bartek Grzybowski | a3737ae | 2020-06-05 15:07:56 +0200 | [diff] [blame] | 63 | # Stop-simulator command parser |
| 64 | parser_stopsimulator = subparsers.add_parser('stop_simulator', help='Stop sending PNF registration messages') |
| 65 | parser_stopsimulator.add_argument('--count', help='Instance count to stop', type=int, metavar='INT', default=0) |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 66 | # Trigger-custom command parser |
Bartek Grzybowski | 950cbad | 2020-05-11 06:00:00 -0700 | [diff] [blame] | 67 | parser_triggerstart = subparsers.add_parser('trigger_custom', help='Trigger one single VES event from specific simulators') |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 68 | parser_triggerstart.add_argument('--triggerstart', help='First simulator id to trigger', type=int, |
| 69 | metavar='INT', required=True) |
| 70 | parser_triggerstart.add_argument('--triggerend', help='Last simulator id to trigger', type=int, |
| 71 | metavar='INT', required=True) |
| 72 | # Status command parser |
| 73 | parser_status = subparsers.add_parser('status', help='Status') |
Bartek Grzybowski | 5717b5e | 2020-05-19 04:50:51 -0700 | [diff] [blame] | 74 | parser_status.add_argument('--count', help='Instance count to show status for', type=int, metavar='INT', default=0) |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 75 | # Clean command parser |
| 76 | subparsers.add_parser('clean', help='Clean work-dirs') |
| 77 | # General options parser |
| 78 | parser.add_argument('--verbose', help='Verbosity level', choices=['info', 'debug'], |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 79 | type=str, default='info') |
Bartek Grzybowski | c85faa4 | 2020-05-11 05:38:08 -0700 | [diff] [blame] | 80 | return parser |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 81 | |
Bartek Grzybowski | a89abf8 | 2020-05-18 07:30:11 -0700 | [diff] [blame] | 82 | class MassPnfSim: |
Bartek Grzybowski | bb8249e | 2020-05-18 04:11:15 -0700 | [diff] [blame] | 83 | |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 84 | log_lvl = logging.INFO |
Bartek Grzybowski | 1fbf854 | 2020-06-10 15:16:23 +0200 | [diff] [blame] | 85 | sim_compose_template = 'docker-compose-template.yml' |
Bartek Grzybowski | 9ab2f40 | 2020-06-12 12:20:48 +0200 | [diff] [blame] | 86 | sim_vsftpd_template = 'config/vsftpd_ssl-TEMPLATE.conf' |
| 87 | sim_vsftpd_config = 'config/vsftpd_ssl.conf' |
Bartek Grzybowski | 07b7d00 | 2020-05-28 07:40:02 -0700 | [diff] [blame] | 88 | sim_config = 'config/config.yml' |
Bartek Grzybowski | 626f286 | 2020-06-04 09:55:26 +0200 | [diff] [blame] | 89 | sim_msg_config = 'config/config.json' |
Bartek Grzybowski | 572f644 | 2020-06-01 12:38:49 +0200 | [diff] [blame] | 90 | sim_port = 5000 |
| 91 | sim_base_url = 'http://{}:' + str(sim_port) + '/simulator' |
Bartek Grzybowski | 626f286 | 2020-06-04 09:55:26 +0200 | [diff] [blame] | 92 | sim_start_url = sim_base_url + '/start' |
| 93 | sim_status_url = sim_base_url + '/status' |
Bartek Grzybowski | a3737ae | 2020-06-05 15:07:56 +0200 | [diff] [blame] | 94 | sim_stop_url = sim_base_url + '/stop' |
Bartek Grzybowski | 572f644 | 2020-06-01 12:38:49 +0200 | [diff] [blame] | 95 | sim_container_name = 'pnf-simulator' |
Bartek Grzybowski | c17ed78 | 2020-06-03 11:46:51 +0200 | [diff] [blame] | 96 | rop_script_name = 'ROP_file_creator.sh' |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 97 | |
| 98 | def __init__(self, args): |
| 99 | self.args = args |
| 100 | self.logger = logging.getLogger(__name__) |
| 101 | self.logger.setLevel(self.log_lvl) |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 102 | self.sim_dirname_pattern = "pnf-sim-lw-" |
Bartek Grzybowski | 5422700 | 2020-05-15 07:38:04 -0700 | [diff] [blame] | 103 | self.mvn_build_cmd = 'mvn clean package docker:build -Dcheckstyle.skip' |
Bartek Grzybowski | 572f644 | 2020-06-01 12:38:49 +0200 | [diff] [blame] | 104 | self.docker_compose_status_cmd = 'docker-compose ps' |
Bartek Grzybowski | a71704c | 2020-05-19 04:47:09 -0700 | [diff] [blame] | 105 | self.existing_sim_instances = self._enum_sim_instances() |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 106 | |
Bartek Grzybowski | 5717b5e | 2020-05-19 04:50:51 -0700 | [diff] [blame] | 107 | # Validate 'trigger_custom' subcommand options |
| 108 | if self.args.subcommand == 'trigger_custom': |
| 109 | if (self.args.triggerend + 1) > self.existing_sim_instances: |
| 110 | self.logger.error('--triggerend value greater than existing instance count.') |
| 111 | exit(1) |
| 112 | |
| 113 | # Validate --count option for subcommands that support it |
Bartek Grzybowski | a3737ae | 2020-06-05 15:07:56 +0200 | [diff] [blame] | 114 | if self.args.subcommand in ['start', 'stop', 'trigger', 'status', 'stop_simulator']: |
Bartek Grzybowski | 5717b5e | 2020-05-19 04:50:51 -0700 | [diff] [blame] | 115 | if self.args.count > self.existing_sim_instances: |
| 116 | self.logger.error('--count value greater that existing instance count') |
| 117 | exit(1) |
| 118 | if not self.existing_sim_instances: |
| 119 | self.logger.error('No bootstrapped instance found') |
| 120 | exit(1) |
| 121 | |
| 122 | # Validate 'bootstrap' subcommand |
| 123 | if (self.args.subcommand == 'bootstrap') and self.existing_sim_instances: |
| 124 | self.logger.error('Bootstrapped instances detected, not overwiriting, clean first') |
| 125 | exit(1) |
| 126 | |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 127 | def _run_cmd(self, cmd, dir_context='.'): |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 128 | old_pwd = getcwd() |
| 129 | try: |
| 130 | chdir(dir_context) |
Bartek Grzybowski | d0f2a7d | 2020-06-08 12:13:57 +0200 | [diff] [blame] | 131 | self.logger.debug(f'_run_cmd: Current direcotry: {getcwd()}') |
| 132 | self.logger.debug(f'_run_cmd: Command string: {cmd}') |
Bartek Grzybowski | f2b1483 | 2020-05-25 00:47:35 -0700 | [diff] [blame] | 133 | run(cmd, check=True, shell=True) |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 134 | chdir(old_pwd) |
| 135 | except FileNotFoundError: |
| 136 | self.logger.error(f"Directory {dir_context} not found") |
Bartek Grzybowski | f2b1483 | 2020-05-25 00:47:35 -0700 | [diff] [blame] | 137 | except CalledProcessError as e: |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 138 | exit(e.returncode) |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 139 | |
Bartek Grzybowski | a71704c | 2020-05-19 04:47:09 -0700 | [diff] [blame] | 140 | def _enum_sim_instances(self): |
| 141 | '''Helper method that returns bootstraped simulator instances count''' |
| 142 | return len(glob(f"{self.sim_dirname_pattern}[0-9]*")) |
| 143 | |
Bartek Grzybowski | 07b7d00 | 2020-05-28 07:40:02 -0700 | [diff] [blame] | 144 | def _get_sim_instance_data(self, instance_id): |
| 145 | '''Helper method that returns specific instance data''' |
| 146 | oldpwd = getcwd() |
| 147 | chdir(f"{self.sim_dirname_pattern}{instance_id}") |
| 148 | with open(self.sim_config) as cfg: |
| 149 | yml = load(cfg, Loader=SafeLoader) |
| 150 | chdir(oldpwd) |
| 151 | return yml['ippnfsim'] |
| 152 | |
Bartek Grzybowski | 110badf | 2020-06-01 12:31:42 +0200 | [diff] [blame] | 153 | def _get_docker_containers(self): |
| 154 | '''Returns a list containing 'name' attribute of running docker containers''' |
| 155 | dc = from_env() |
| 156 | containers = [] |
| 157 | for container in dc.containers.list(): |
| 158 | containers.append(container.attrs['Name'][1:]) |
| 159 | return containers |
| 160 | |
Bartek Grzybowski | 61ed366 | 2020-06-02 10:25:11 +0200 | [diff] [blame] | 161 | def _get_iter_range(self): |
| 162 | '''Helper routine to get the iteration range |
| 163 | for the lifecycle commands''' |
Bartek Grzybowski | 33a15ff | 2020-06-04 12:18:41 +0200 | [diff] [blame] | 164 | if hasattr(self.args, 'count'): |
| 165 | if not self.args.count: |
| 166 | return [self.existing_sim_instances] |
| 167 | else: |
| 168 | return [self.args.count] |
| 169 | elif hasattr(self.args, 'triggerstart'): |
| 170 | return [self.args.triggerstart, self.args.triggerend + 1] |
Bartek Grzybowski | 61ed366 | 2020-06-02 10:25:11 +0200 | [diff] [blame] | 171 | else: |
Bartek Grzybowski | 33a15ff | 2020-06-04 12:18:41 +0200 | [diff] [blame] | 172 | return [self.existing_sim_instances] |
Bartek Grzybowski | 61ed366 | 2020-06-02 10:25:11 +0200 | [diff] [blame] | 173 | |
Bartek Grzybowski | 138b199 | 2020-06-08 14:25:44 +0200 | [diff] [blame] | 174 | def _archive_logs(self, sim_dir): |
| 175 | '''Helper function to archive simulator logs or create the log dir''' |
| 176 | old_pwd = getcwd() |
| 177 | try: |
| 178 | chdir(sim_dir) |
| 179 | if path.isdir('logs'): |
| 180 | arch_dir = f"logs/archive_{strftime('%Y-%m-%d_%T')}" |
| 181 | mkdir(arch_dir) |
| 182 | self.logger.debug(f'Created {arch_dir}') |
| 183 | # Collect file list to move |
| 184 | self.logger.debug('Archiving log files') |
| 185 | for fpattern in ['*.log', '*.xml']: |
| 186 | for f in glob('logs/' + fpattern): |
| 187 | # Move files from list to arch dir |
| 188 | move(f, arch_dir) |
| 189 | self.logger.debug(f'Moving {f} to {arch_dir}') |
| 190 | else: |
| 191 | mkdir('logs') |
| 192 | self.logger.debug("Logs dir didn't exist, created") |
| 193 | chdir(old_pwd) |
| 194 | except FileNotFoundError: |
| 195 | self.logger.error(f"Directory {sim_dir} not found") |
| 196 | |
Bartek Grzybowski | 700b607 | 2020-06-09 12:41:08 +0200 | [diff] [blame] | 197 | def _generate_pnf_sim_config(self, i, port_sftp, port_ftps, pnf_sim_ip): |
| 198 | '''Writes a yaml formatted configuration file for Java simulator app''' |
| 199 | yml = {} |
| 200 | yml['urlves'] = self.args.urlves |
| 201 | yml['urlsftp'] = f'sftp://onap:pano@{self.args.ipfileserver}:{port_sftp}' |
| 202 | yml['urlftps'] = f'ftps://onap:pano@{self.args.ipfileserver}:{port_ftps}' |
| 203 | yml['ippnfsim'] = pnf_sim_ip |
| 204 | yml['typefileserver'] = self.args.typefileserver |
| 205 | self.logger.debug(f'Generated simulator config:\n{dump(yml)}') |
| 206 | with open(f'{self.sim_dirname_pattern}{i}/{self.sim_config}', 'w') as fout: |
| 207 | fout.write(dump(yml)) |
| 208 | |
Bartek Grzybowski | e2f20d0 | 2020-06-12 10:45:42 +0200 | [diff] [blame] | 209 | def _generate_config_file(self, source, dest, **kwargs): |
| 210 | '''Helper private method to generate a file based on a template''' |
Bartek Grzybowski | 1fbf854 | 2020-06-10 15:16:23 +0200 | [diff] [blame] | 211 | old_pwd = getcwd() |
| 212 | chdir(self.sim_dirname_pattern + str(kwargs['I'])) |
Bartek Grzybowski | e2f20d0 | 2020-06-12 10:45:42 +0200 | [diff] [blame] | 213 | # Read the template file |
| 214 | with open(source, 'r') as f: |
Bartek Grzybowski | 1fbf854 | 2020-06-10 15:16:23 +0200 | [diff] [blame] | 215 | template = f.read() |
| 216 | # Replace all occurences of env like variable with it's |
| 217 | # relevant value from a corresponding key form kwargs |
| 218 | for (k,v) in kwargs.items(): |
| 219 | template = template.replace('${' + k + '}', str(v)) |
Bartek Grzybowski | e2f20d0 | 2020-06-12 10:45:42 +0200 | [diff] [blame] | 220 | with open(dest, 'w') as f: |
Bartek Grzybowski | 1fbf854 | 2020-06-10 15:16:23 +0200 | [diff] [blame] | 221 | f.write(template) |
| 222 | chdir(old_pwd) |
| 223 | |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 224 | def bootstrap(self): |
| 225 | self.logger.info("Bootstrapping PNF instances") |
| 226 | |
| 227 | start_port = 2000 |
| 228 | ftps_pasv_port_start = 8000 |
| 229 | ftps_pasv_port_num_of_ports = 10 |
| 230 | |
| 231 | ftps_pasv_port_end = ftps_pasv_port_start + ftps_pasv_port_num_of_ports |
| 232 | |
| 233 | for i in range(self.args.count): |
| 234 | self.logger.info(f"PNF simulator instance: {i}") |
| 235 | |
| 236 | # The IP ranges are in distance of 16 compared to each other. |
| 237 | # This is matching the /28 subnet mask used in the dockerfile inside. |
| 238 | instance_ip_offset = i * 16 |
| 239 | ip_properties = [ |
| 240 | 'subnet', |
| 241 | 'gw', |
| 242 | 'PnfSim', |
| 243 | 'ftps', |
| 244 | 'sftp' |
| 245 | ] |
| 246 | |
| 247 | ip_offset = 0 |
| 248 | ip = {} |
| 249 | for prop in ip_properties: |
| 250 | ip.update({prop: str(self.args.ipstart + ip_offset + instance_ip_offset)}) |
| 251 | ip_offset += 1 |
| 252 | |
| 253 | self.logger.debug(f'Instance #{i} properties:\n {dumps(ip, indent=4)}') |
| 254 | |
| 255 | PortSftp = start_port + 1 |
| 256 | PortFtps = start_port + 2 |
| 257 | start_port += 2 |
| 258 | |
Bartek Grzybowski | be79e4d | 2020-05-14 07:38:54 -0700 | [diff] [blame] | 259 | self.logger.info(f'\tCreating {self.sim_dirname_pattern}{i}') |
Bartek Grzybowski | 5717b5e | 2020-05-19 04:50:51 -0700 | [diff] [blame] | 260 | copytree('pnf-sim-lightweight', f'{self.sim_dirname_pattern}{i}') |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 261 | |
| 262 | composercmd = " ".join([ |
| 263 | "./simulator.sh compose", |
| 264 | ip['gw'], |
| 265 | ip['subnet'], |
| 266 | str(i), |
| 267 | self.args.urlves, |
| 268 | ip['PnfSim'], |
| 269 | str(self.args.ipfileserver), |
| 270 | self.args.typefileserver, |
| 271 | str(PortSftp), |
| 272 | str(PortFtps), |
| 273 | ip['ftps'], |
| 274 | ip['sftp'], |
| 275 | str(ftps_pasv_port_start), |
| 276 | str(ftps_pasv_port_end) |
| 277 | ]) |
| 278 | self.logger.debug(f"Script cmdline: {composercmd}") |
Bartek Grzybowski | be79e4d | 2020-05-14 07:38:54 -0700 | [diff] [blame] | 279 | self.logger.info(f"\tCreating instance #{i} configuration ") |
| 280 | self._run_cmd(composercmd, f"{self.sim_dirname_pattern}{i}") |
Bartek Grzybowski | 1fbf854 | 2020-06-10 15:16:23 +0200 | [diff] [blame] | 281 | self._generate_pnf_sim_config(i, PortSftp, PortFtps, ip['PnfSim']) |
Bartek Grzybowski | e2f20d0 | 2020-06-12 10:45:42 +0200 | [diff] [blame] | 282 | # generate docker-compose for the simulator instance |
| 283 | self._generate_config_file(self.sim_compose_template, 'docker-compose.yml', |
| 284 | IPGW = ip['gw'], IPSUBNET = ip['subnet'], |
| 285 | I = i, IPPNFSIM = ip['PnfSim'], |
| 286 | PORTSFTP = str(PortSftp), |
| 287 | PORTFTPS = str(PortFtps), |
| 288 | IPFTPS = ip['ftps'], IPSFTP = ip['sftp'], |
| 289 | FTPS_PASV_MIN = str(ftps_pasv_port_start), |
| 290 | FTPS_PASV_MAX = str(ftps_pasv_port_end), |
| 291 | TIMEZONE = tzname[daylight]) |
Bartek Grzybowski | 9ab2f40 | 2020-06-12 12:20:48 +0200 | [diff] [blame] | 292 | # generate vsftpd config file for the simulator instance |
| 293 | self._generate_config_file(self.sim_vsftpd_template, self.sim_vsftpd_config, |
| 294 | I = i, USER = getlogin(), |
| 295 | FTPS_PASV_MIN = str(ftps_pasv_port_start), |
| 296 | FTPS_PASV_MAX = str(ftps_pasv_port_end), |
| 297 | IPFILESERVER = str(self.args.ipfileserver)) |
Bartek Grzybowski | 624c115 | 2020-06-15 13:14:45 +0200 | [diff] [blame^] | 298 | # Run the 3GPP measurements file generator |
| 299 | self._run_cmd(f'./ROP_file_creator.sh {i} &', f"{self.sim_dirname_pattern}{i}") |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 300 | |
| 301 | ftps_pasv_port_start += ftps_pasv_port_num_of_ports + 1 |
| 302 | ftps_pasv_port_end += ftps_pasv_port_num_of_ports + 1 |
| 303 | |
Bartek Grzybowski | 81287bc | 2020-06-08 14:29:29 +0200 | [diff] [blame] | 304 | # ugly hack to chown vsftpd config file to root |
| 305 | if getuid(): |
Bartek Grzybowski | 9ab2f40 | 2020-06-12 12:20:48 +0200 | [diff] [blame] | 306 | self._run_cmd(f'sudo chown root {self.sim_vsftpd_config}', f'{self.sim_dirname_pattern}{i}') |
| 307 | self.logger.debug(f"vsftpd config file owner UID: {stat(self.sim_dirname_pattern + str(i) + '/' + self.sim_vsftpd_config).st_uid}") |
Bartek Grzybowski | 81287bc | 2020-06-08 14:29:29 +0200 | [diff] [blame] | 308 | |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 309 | self.logger.info(f'Done setting up instance #{i}') |
| 310 | |
Bartek Grzybowski | d203820 | 2020-05-15 00:57:25 -0700 | [diff] [blame] | 311 | def build(self): |
| 312 | self.logger.info("Building simulator image") |
Bartek Grzybowski | 5422700 | 2020-05-15 07:38:04 -0700 | [diff] [blame] | 313 | if path.isfile('pnf-sim-lightweight/pom.xml'): |
| 314 | self._run_cmd(self.mvn_build_cmd, 'pnf-sim-lightweight') |
| 315 | else: |
| 316 | self.logger.error('POM file was not found, Maven cannot run') |
| 317 | exit(1) |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 318 | |
| 319 | def clean(self): |
Bartek Grzybowski | b61465f | 2020-05-14 05:52:40 -0700 | [diff] [blame] | 320 | self.logger.info('Cleaning simulators workdirs') |
Bartek Grzybowski | f2b1483 | 2020-05-25 00:47:35 -0700 | [diff] [blame] | 321 | for sim_id in range(self.existing_sim_instances): |
| 322 | rmtree(f"{self.sim_dirname_pattern}{sim_id}") |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 323 | |
| 324 | def start(self): |
Bartek Grzybowski | 09b5a6f | 2020-06-08 14:39:01 +0200 | [diff] [blame] | 325 | for i in range(*self._get_iter_range()): |
| 326 | # If container is not running |
| 327 | if f"{self.sim_container_name}-{i}" not in self._get_docker_containers(): |
| 328 | self.logger.info(f'Starting {self.sim_dirname_pattern}{i} instance:') |
| 329 | self.logger.info(f' PNF-Sim IP: {self._get_sim_instance_data(i)}') |
| 330 | #Move logs to archive |
| 331 | self._archive_logs(self.sim_dirname_pattern + str(i)) |
| 332 | self.logger.info(' Starting simulator containers using netconf model specified in config/netconf.env') |
| 333 | self._run_cmd('docker-compose up -d', self.sim_dirname_pattern + str(i)) |
| 334 | else: |
| 335 | self.logger.warning(f'Instance {self.sim_dirname_pattern}{i} containers are already up') |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 336 | |
| 337 | def status(self): |
Bartek Grzybowski | 61ed366 | 2020-06-02 10:25:11 +0200 | [diff] [blame] | 338 | for i in range(*self._get_iter_range()): |
Bartek Grzybowski | 572f644 | 2020-06-01 12:38:49 +0200 | [diff] [blame] | 339 | self.logger.info(f'Getting {self.sim_dirname_pattern}{i} instance status:') |
| 340 | if f"{self.sim_container_name}-{i}" in self._get_docker_containers(): |
| 341 | try: |
| 342 | sim_ip = self._get_sim_instance_data(i) |
| 343 | self.logger.info(f' PNF-Sim IP: {sim_ip}') |
| 344 | self._run_cmd(self.docker_compose_status_cmd, f"{self.sim_dirname_pattern}{i}") |
Bartek Grzybowski | 626f286 | 2020-06-04 09:55:26 +0200 | [diff] [blame] | 345 | sim_response = get('{}'.format(self.sim_status_url).format(sim_ip)) |
Bartek Grzybowski | 572f644 | 2020-06-01 12:38:49 +0200 | [diff] [blame] | 346 | if sim_response.status_code == codes.ok: |
| 347 | self.logger.info(sim_response.text) |
| 348 | else: |
| 349 | self.logger.error(f'Simulator request returned http code {sim_response.status_code}') |
| 350 | except KeyError: |
| 351 | self.logger.error(f'Unable to get sim instance IP from {self.sim_config}') |
| 352 | else: |
| 353 | self.logger.info(' Simulator containers are down') |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 354 | |
| 355 | def stop(self): |
Bartek Grzybowski | c17ed78 | 2020-06-03 11:46:51 +0200 | [diff] [blame] | 356 | for i in range(*self._get_iter_range()): |
| 357 | self.logger.info(f'Stopping {self.sim_dirname_pattern}{i} instance:') |
| 358 | self.logger.info(f' PNF-Sim IP: {self._get_sim_instance_data(i)}') |
| 359 | # attempt killing ROP script |
| 360 | rop_pid = [] |
| 361 | for ps_line in iter(popen(f'ps --no-headers -C {self.rop_script_name} -o pid,cmd').readline, ''): |
| 362 | # try getting ROP script pid |
| 363 | try: |
| 364 | ps_line_arr = ps_line.split() |
| 365 | assert self.rop_script_name in ps_line_arr[2] |
| 366 | assert ps_line_arr[3] == str(i) |
| 367 | rop_pid = ps_line_arr[0] |
| 368 | except AssertionError: |
| 369 | pass |
| 370 | else: |
| 371 | # get rop script childs, kill ROP script and all childs |
| 372 | childs = popen(f'pgrep -P {rop_pid}').read().split() |
| 373 | for pid in [rop_pid] + childs: |
| 374 | kill(int(pid), 15) |
| 375 | self.logger.info(f' ROP_file_creator.sh {i} successfully killed') |
| 376 | if not rop_pid: |
| 377 | # no process found |
| 378 | self.logger.warning(f' ROP_file_creator.sh {i} already not running') |
| 379 | # try tearing down docker-compose application |
| 380 | if f"{self.sim_container_name}-{i}" in self._get_docker_containers(): |
| 381 | self._run_cmd('docker-compose down', self.sim_dirname_pattern + str(i)) |
| 382 | self._run_cmd('docker-compose rm', self.sim_dirname_pattern + str(i)) |
| 383 | else: |
| 384 | self.logger.warning(" Simulator containers are already down") |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 385 | |
| 386 | def trigger(self): |
| 387 | self.logger.info("Triggering VES sending:") |
Bartek Grzybowski | 626f286 | 2020-06-04 09:55:26 +0200 | [diff] [blame] | 388 | for i in range(*self._get_iter_range()): |
| 389 | sim_ip = self._get_sim_instance_data(i) |
| 390 | self.logger.info(f'Triggering {self.sim_dirname_pattern}{i} instance:') |
| 391 | self.logger.info(f' PNF-Sim IP: {sim_ip}') |
| 392 | # setup req headers |
| 393 | req_headers = { |
| 394 | "Content-Type": "application/json", |
| 395 | "X-ONAP-RequestID": "123", |
| 396 | "X-InvocationID": "456" |
| 397 | } |
| 398 | self.logger.debug(f' Request headers: {req_headers}') |
| 399 | try: |
| 400 | # get payload for the request |
| 401 | with open(f'{self.sim_dirname_pattern}{i}/{self.sim_msg_config}') as data: |
| 402 | json_data = loads(data.read()) |
| 403 | self.logger.debug(f' JSON payload for the simulator:\n{json_data}') |
| 404 | # make a http request to the simulator |
| 405 | sim_response = post('{}'.format(self.sim_start_url).format(sim_ip), headers=req_headers, json=json_data) |
| 406 | if sim_response.status_code == codes.ok: |
| 407 | self.logger.info(' Simulator response: ' + sim_response.text) |
| 408 | else: |
| 409 | self.logger.warning(' Simulator response ' + sim_response.text) |
| 410 | except TypeError: |
| 411 | self.logger.error(f' Could not load JSON data from {self.sim_dirname_pattern}{i}/{self.sim_msg_config}') |
Bartek Grzybowski | feb93cd | 2020-05-11 04:51:37 -0700 | [diff] [blame] | 412 | |
Bartek Grzybowski | 33a15ff | 2020-06-04 12:18:41 +0200 | [diff] [blame] | 413 | # Make the 'trigger_custom' an alias to the 'trigger' method |
| 414 | trigger_custom = trigger |
Bartek Grzybowski | a3737ae | 2020-06-05 15:07:56 +0200 | [diff] [blame] | 415 | |
| 416 | def stop_simulator(self): |
| 417 | self.logger.info("Stopping sending PNF registration messages:") |
| 418 | for i in range(*self._get_iter_range()): |
| 419 | sim_ip = self._get_sim_instance_data(i) |
| 420 | self.logger.info(f'Stopping {self.sim_dirname_pattern}{i} instance:') |
| 421 | self.logger.info(f' PNF-Sim IP: {sim_ip}') |
| 422 | sim_response = post('{}'.format(self.sim_stop_url).format(sim_ip)) |
| 423 | if sim_response.status_code == codes.ok: |
| 424 | self.logger.info(' Simulator response: ' + sim_response.text) |
| 425 | else: |
| 426 | self.logger.warning(' Simulator response ' + sim_response.text) |