def runcommand_heat(self, workload, template, files, parameters): """Run workload on stack deployed by heat. Workload can be either file or resource: {"file": "/path/to/file.sh"} {"resource": ["package.module", "workload.py"]} Also it should contain "username" key. Given file will be uploaded to `gate_node` and started. This script should print `key` `value` pairs separated by colon. These pairs will be presented in results. Gate node should be accessible via ssh with keypair `key_name`, so heat template should accept parameter `key_name`. :param workload: workload to run :param template: path to heat template file :param files: additional template files :param parameters: parameters for heat template """ keypair = self.context["user"]["keypair"] parameters["key_name"] = keypair["name"] network = self.context["tenant"]["networks"][0] parameters["router_id"] = network["router_id"] self.stack = heat.main.Stack(self, self.task, template, files=files, parameters=parameters) self.stack.create() for output in self.stack.stack.outputs: if output["output_key"] == "gate_node": ip = output["output_value"] break ssh = sshutils.SSH(workload["username"], ip, pkey=keypair["private"]) ssh.wait() script = workload.get("resource") if script: script = pkgutil.get_data(*script) else: script = open(workload["file"]).read() ssh.execute("cat > /tmp/.rally-workload", stdin=script) ssh.execute("chmod +x /tmp/.rally-workload") with atomic.ActionTimer(self, "runcommand_heat.workload"): status, out, err = ssh.execute( "/tmp/.rally-workload", stdin=json.dumps(self.stack.stack.outputs)) rows = [] for line in out.splitlines(): row = line.split(":") if len(row) != 2: raise exceptions.ScriptError("Invalid data '%s'" % line) rows.append(row) if not rows: raise exceptions.ScriptError("No data returned") self.add_output( complete={"title": "Workload summary", "description": "Data generated by workload", "chart_plugin": "Table", "data": { "cols": ["key", "value"], "rows": rows}} )
def boot_runcommand(self, image, flavor, script, interpreter, username, password=None, volume_args=None, floating_network=None, port=22, force_delete=False, **kwargs): """Boot a server, run a script that outputs JSON. A server will be deleted by cleanup. Example Script in samples/tasks/support/instance_dd_test.sh :param image: glance image name to use for the vm :param flavor: VM flavor name :param script: script to run on server, must output JSON mapping metric names to values (see the sample script below) :param interpreter: server's interpreter to run the script :param username: ssh username on server, str :param password: Password on SSH authentication :param volume_args: volume args for booting server from volume :param floating_network: external network name, for floating ip :param port: ssh port for SSH connection :param force_delete: whether to use force_delete for servers :param **kwargs: extra arguments for booting the server :returns: dictionary with keys `data' and `errors': data: dict, JSON output from the script errors: str, raw data from the script's stderr stream """ if volume_args: volume = self._create_volume(volume_args["size"], imageRef=None) kwargs["block_device_mapping"] = {"vdrally": "%s:::1" % volume.id} server, fip = self._boot_server_with_fip( image, flavor, floating_network=floating_network, key_name=self.context["user"]["keypair"]["name"], **kwargs) code, out, err = self._run_command(fip["ip"], port, username, password, interpreter, script) if code: raise exceptions.ScriptError( "Error running script %(script)s." "Error %(code)s: %(error)s" % { "script": script, "code": code, "error": err}) try: data = json.loads(out) except ValueError as e: raise exceptions.ScriptError( "Script %(script)s has not output valid JSON: " "%(error)s" % {"script": script, "error": str(e)}) return {"data": data, "errors": err}
def _customize_image(self, server, fip, user): code, out, err = vm_utils.VMScenario(self.context)._run_command( fip["ip"], self.config["port"], self.config["username"], self.config.get("password"), command=self.config["command"], pkey=user["keypair"]["private"]) if code: raise exceptions.ScriptError( message="Command `%(command)s' execution failed," " code %(code)d:\n" "STDOUT:\n============================\n" "%(out)s\n" "STDERR:\n============================\n" "%(err)s\n" "============================\n" % { "command": self.config["command"], "code": code, "out": out, "err": err }) return code, out, err
def failover(self, host, command, port=22, username="", password="", key_filename=None, pkey=None): """Trigger failover at host :param host: :param command: :return: """ if key_filename: key_filename = path.expanduser(key_filename) LOG.info("Host: %s. Injecting Failover %s" % (host, command)) try: code, out, err = _run_command(self, server_ip=host, port=port, username=username, password=password, key_filename=key_filename, pkey=pkey, command=command ) if code and code > 0: raise exceptions.ScriptError( "Error running command %(command)s. " "Error %(code)s: %(error)s" % { "command": command, "code": code, "error": err}) except exceptions.SSHTimeout: LOG.debug("SSH session of disruptor command timeouted, continue...") pass
def _run_command_over_ssh(self, ssh, interpreter, script, is_file=True): """Run command inside an instance. This is a separate function so that only script execution is timed. :param ssh: A SSHClient instance. :param interpreter: The interpreter that will be used to execute the script. :param script: Path to the script file or its content in a StringIO. :param is_file: if True, script represent a path, else, script contains an inline script. :returns: tuple (exit_status, stdout, stderr) """ if not is_file: stdin = script elif isinstance(script, six.string_types): stdin = open(script, "rb") elif isinstance(script, six.moves.StringIO): stdin = script else: raise exceptions.ScriptError( "Either file path or StringIO expected, given %s" % type(script).__name__) return ssh.execute(interpreter, stdin=stdin)
def runcommand_heat_local(self, workload, template, parameters): """Run workload on stack deployed by heat. Workload can be either file or resource: {"file": "/path/to/file.sh"} {"resource": ["package.module", "workload.py"]} The workload will be run from the same host that is running rally :param workload: workload to run :param template: path to heat template file :param files: additional template files :param parameters: parameters for heat template """ self.stack = heat.main.Stack(self, self.task, template, files=files, parameters=parameters) self.stack.create() script = workload.get("resource") if script: script = pkgutil.get_data(*script) else: script = open(workload["file"]).read() with atomic.ActionTimer(self, "runcommand_heat_local.workload"): p = subprocess.Popen( script, stdin=json.dumps(self.stack.stack.outputs)) out, err = p.communicate() rows = [] for line in out.splitlines(): row = line.split(":") if len(row) != 2: raise exceptions.ScriptError("Invalid data '%s'" % line) rows.append(row) if not rows: raise exceptions.ScriptError("No data returned") self.add_output( complete={"title": "Workload summary", "description": "Data generated by workload", "chart_plugin": "Table", "data": { "cols": ["key", "value"], "rows": rows}} )
def boot_runcommand_delete(self, image, flavor, username, password=None, script=None, interpreter=None, command=None, volume_args=None, floating_network=None, port=22, use_floating_ip=True, force_delete=False, wait_for_ping=True, max_log_length=None, **kwargs): """Boot a server, run a script that outputs JSON, delete the server. Example Script in samples/tasks/support/instance_dd_test.sh :param image: glance image name to use for the vm :param flavor: VM flavor name :param username: ssh username on server, str :param password: Password on SSH authentication :param script: DEPRECATED. Use `command' instead. Script to run on server, must output JSON mapping metric names to values (see the sample script below) :param interpreter: DEPRECATED. Use `command' instead. server's interpreter to run the script :param command: Command-specifying dictionary that either specifies remote command path via `remote_path' (can be uploaded from a local file specified by `local_path`), an inline script via `script_inline' or a local script file path using `script_file'. Both `script_file' and `local_path' are checked to be accessible by the `file_exists' validator code. The `script_inline' and `script_file' both require an `interpreter' value to specify the interpreter script should be run with. Note that any of `interpreter' and `remote_path' can be an array prefixed with environment variables and suffixed with args for the `interpreter' command. `remote_path's last component must be a path to a command to execute (also upload destination if a `local_path' is given). Uploading an interpreter is possible but requires that `remote_path' and `interpreter' path do match. Examples:: # Run a `local_script.pl' file sending it to a remote # Perl interpreter command = { "script_file": "local_script.pl", "interpreter": "/usr/bin/perl" } # Run an inline script sending it to a remote interpreter command = { "script_inline": "echo 'Hello, World!'", "interpreter": "/bin/sh" } # Run a remote command command = { "remote_path": "/bin/false" } # Copy a local command and run it command = { "remote_path": "/usr/local/bin/fio", "local_path": "/home/foobar/myfiodir/bin/fio" } # Copy a local command and run it with environment variable command = { "remote_path": ["HOME=/root", "/usr/local/bin/fio"], "local_path": "/home/foobar/myfiodir/bin/fio" } # Run an inline script sending it to a remote interpreter command = { "script_inline": "echo \"Hello, ${NAME:-World}\"", "interpreter": ["NAME=Earth", "/bin/sh"] } # Run an inline script sending it to an uploaded remote # interpreter command = { "script_inline": "echo \"Hello, ${NAME:-World}\"", "interpreter": ["NAME=Earth", "/tmp/sh"], "remote_path": "/tmp/sh", "local_path": "/home/user/work/cve/sh-1.0/bin/sh" } :param volume_args: volume args for booting server from volume :param floating_network: external network name, for floating ip :param port: ssh port for SSH connection :param use_floating_ip: bool, floating or fixed IP for SSH connection :param force_delete: whether to use force_delete for servers :param wait_for_ping: whether to check connectivity on server creation :param **kwargs: extra arguments for booting the server :param max_log_length: The number of tail nova console-log lines user would like to retrieve :returns: dictionary with keys `data' and `errors': data: dict, JSON output from the script errors: str, raw data from the script's stderr stream """ if command is None and script and interpreter: command = {"script_file": script, "interpreter": interpreter} if volume_args: volume = self._create_volume(volume_args["size"], imageRef=None) kwargs["block_device_mapping"] = {"vdrally": "%s:::1" % volume.id} server, fip = self._boot_server_with_fip( image, flavor, use_floating_ip=use_floating_ip, floating_network=floating_network, key_name=self.context["user"]["keypair"]["name"], **kwargs) try: if wait_for_ping: self._wait_for_ping(fip["ip"]) code, out, err = self._run_command( fip["ip"], port, username, password, command=command) if code: raise exceptions.ScriptError( "Error running command %(command)s. " "Error %(code)s: %(error)s" % { "command": command, "code": code, "error": err}) try: data = json.loads(out) except ValueError as e: raise exceptions.ScriptError( "Command %(command)s has not output valid JSON: %(error)s." " Output: %(output)s" % { "command": command, "error": str(e), "output": out}) except (exceptions.TimeoutException, exceptions.SSHTimeout): console_logs = self._get_server_console_output(server, max_log_length) LOG.debug("VM console logs:\n%s", console_logs) raise finally: self._delete_server_with_fip(server, fip, force_delete=force_delete) return {"data": data, "errors": err}
def boot_runcommand_delete(self, image, flavor, script, interpreter, username, fixed_network="private", floating_network="public", ip_version=4, port=22, use_floatingip=True, **kwargs): """Boot server, run a script that outputs JSON, delete server. :param script: script to run on the server, must output JSON mapping metric names to values. See sample script below. :param interpreter: The shell interpreter to use when running script :param username: User to SSH to instance as :param fixed_network: Network where instance is part of :param floating_network: External network used to get floating ip from :param ip_version: Version of ip protocol to use for connection :param port: Port to use for SSH connection :param use_floatingip: Whether to associate a floating ip for connection :returns: Dictionary containing two keys, data and errors. Data is JSON data output by the script. Errors is raw data from the script's standard error stream. Example Script in doc/samples/tasks/support/instance_dd_test.sh """ server = None floating_ip = None try: server = self._boot_server( self._generate_random_name("rally_novaserver_"), image, flavor, key_name='rally_ssh_key', **kwargs) self.check_network(server, fixed_network) fixed_ip = [ ip for ip in server.addresses[fixed_network] if ip["version"] == ip_version ][0]["addr"] if use_floatingip: floating_ip = self._create_floating_ip(floating_network) self._associate_floating_ip(server, floating_ip) server_ip = floating_ip.ip else: server_ip = fixed_ip code, out, err = self.run_command(server_ip, port, username, interpreter, script) if code: raise exceptions.ScriptError("Error running script %(script)s." "Error %(code)s: %(error)s" % { "script": script, "code": code, "error": err }) try: out = json.loads(out) except ValueError as e: raise exceptions.ScriptError( "Script %(script)s did not output valid JSON: %(error)s" % { "script": script, "error": str(e) }) # Always try to free resources finally: if use_floatingip: self._release_server_floating_ip(server, floating_ip) if server: self._delete_server(server) return {"data": out, "errors": err}
def run(self, flavor, username, password=None, image=None, command=None, volume_args=None, floating_network=None, port=22, use_floating_ip=True, force_delete=False, wait_for_ping=True, max_log_length=None, **kwargs): """Boot a server, run script specified in command and delete server. :param image: glance image name to use for the vm. Optional in case of specified "image_command_customizer" context :param flavor: VM flavor name :param username: ssh username on server, str :param password: Password on SSH authentication :param command: Command-specifying dictionary that either specifies remote command path via `remote_path' (can be uploaded from a local file specified by `local_path`), an inline script via `script_inline' or a local script file path using `script_file'. Both `script_file' and `local_path' are checked to be accessible by the `file_exists' validator code. The `script_inline' and `script_file' both require an `interpreter' value to specify the interpreter script should be run with. Note that any of `interpreter' and `remote_path' can be an array prefixed with environment variables and suffixed with args for the `interpreter' command. `remote_path's last component must be a path to a command to execute (also upload destination if a `local_path' is given). Uploading an interpreter is possible but requires that `remote_path' and `interpreter' path do match. Examples: .. code-block:: python # Run a `local_script.pl' file sending it to a remote # Perl interpreter command = { "script_file": "local_script.pl", "interpreter": "/usr/bin/perl" } # Run an inline script sending it to a remote interpreter command = { "script_inline": "echo 'Hello, World!'", "interpreter": "/bin/sh" } # Run a remote command command = { "remote_path": "/bin/false" } # Copy a local command and run it command = { "remote_path": "/usr/local/bin/fio", "local_path": "/home/foobar/myfiodir/bin/fio" } # Copy a local command and run it with environment variable command = { "remote_path": ["HOME=/root", "/usr/local/bin/fio"], "local_path": "/home/foobar/myfiodir/bin/fio" } # Run an inline script sending it to a remote interpreter command = { "script_inline": "echo \"Hello, ${NAME:-World}\"", "interpreter": ["NAME=Earth", "/bin/sh"] } # Run an inline script sending it to an uploaded remote # interpreter command = { "script_inline": "echo \"Hello, ${NAME:-World}\"", "interpreter": ["NAME=Earth", "/tmp/sh"], "remote_path": "/tmp/sh", "local_path": "/home/user/work/cve/sh-1.0/bin/sh" } :param volume_args: volume args for booting server from volume :param floating_network: external network name, for floating ip :param port: ssh port for SSH connection :param use_floating_ip: bool, floating or fixed IP for SSH connection :param force_delete: whether to use force_delete for servers :param wait_for_ping: whether to check connectivity on server creation :param **kwargs: extra arguments for booting the server :param max_log_length: The number of tail nova console-log lines user would like to retrieve :returns: dictionary with keys `data' and `errors': data: dict, JSON output from the script errors: str, raw data from the script's stderr stream """ if volume_args: volume = self.cinder.create_volume(volume_args["size"], imageRef=None) kwargs["block_device_mapping"] = {"vdrally": "%s:::1" % volume.id} if not image: image = self.context["tenant"]["custom_image"]["id"] server, fip = self._boot_server_with_fip( image, flavor, use_floating_ip=use_floating_ip, floating_network=floating_network, key_name=self.context["user"]["keypair"]["name"], **kwargs) try: if wait_for_ping: self._wait_for_ping(fip["ip"]) code, out, err = self._run_command(fip["ip"], port, username, password, command=command) text_area_output = ["StdErr: %s" % (err or "(none)"), "StdOut:"] if code: raise exceptions.ScriptError( "Error running command %(command)s. " "Error %(code)s: %(error)s" % { "command": command, "code": code, "error": err }) # Let's try to load output data try: data = json.loads(out) # 'echo 42' produces very json-compatible result # - check it here if not isinstance(data, dict): raise ValueError except ValueError: # It's not a JSON, probably it's 'script_inline' result data = [] except (exceptions.TimeoutException, exceptions.SSHTimeout): console_logs = self._get_server_console_output( server, max_log_length) LOG.debug("VM console logs:\n%s", console_logs) raise finally: self._delete_server_with_fip(server, fip, force_delete=force_delete) if isinstance(data, dict) and set(data) == {"additive", "complete"}: for chart_type, charts in data.items(): for chart in charts: self.add_output(**{chart_type: chart}) else: # it's a dict with several unknown lines text_area_output.extend(out.split("\n")) self.add_output( complete={ "title": "Script Output", "chart_plugin": "TextArea", "data": text_area_output })
def run(self, image, flavor, username, password=None, floating_network=None, port=22, use_floating_ip=True, force_delete=False, max_log_length=None, **kwargs): """Try to resolve hostname from VM against existing designate DNS. - requires zone context with set_zone_in_network parameter > zones: > set_zone_in_network: True - designate IP should be in default dns_nameservers list for new networks or it can be specified in a network context > network: > dns_nameservers: > - 8.8.8.8 > - 192.168.210.45 :param image: glance image name to use for the vm :param flavor: VM flavor name :param username: ssh username on server :param password: Password on SSH authentication :param floating_network: external network name, for floating ip :param port: ssh port for SSH connection :param use_floating_ip: bool, floating or fixed IP for SSH connection :param force_delete: whether to use force_delete for servers :param max_log_length: The number of tail nova console-log lines user would like to retrieve :param kwargs: optional args """ zone = self.context["tenant"]["zones"][0]["name"] server, fip = self._boot_server_with_fip( image, flavor, use_floating_ip=use_floating_ip, floating_network=floating_network, key_name=self.context["user"]["keypair"]["name"], **kwargs) script = f"cloud-init status -w; systemd-resolve --status; "\ f"dig $(hostname).{zone}" command = {"script_inline": script, "interpreter": "/bin/bash"} try: rally_utils.wait_for_status( server, ready_statuses=["ACTIVE"], update_resource=rally_utils.get_from_manager(), ) code, out, err = self._run_command(fip["ip"], port, username, password, command=command) if code: raise exceptions.ScriptError( "Error running command %(command)s. " "Error %(code)s: %(error)s" % { "command": command, "code": code, "error": err }) else: if not re.findall(".*ANSWER SECTION.*", out, re.MULTILINE): raise exceptions.ScriptError( f"Error running {script}. " f"Error: Missing ANSWER section in the output {out}") except (exceptions.TimeoutException, exceptions.SSHTimeout): console_logs = self._get_server_console_output( server, max_log_length) LOG.debug("VM console logs:\n%s" % console_logs) raise finally: self._delete_server_with_fip(server, fip, force_delete=force_delete) self.add_output( complete={ "title": "Script StdOut", "chart_plugin": "TextArea", "data": str(out).split("\n") }) if err: self.add_output( complete={ "title": "Script StdErr", "chart_plugin": "TextArea", "data": err.split("\n") })
def run(self, image, flavor, username, password=None, command=None, volume_args=None, floating_network=None, port=22, use_floating_ip=True, force_delete=False, wait_for_ping=True, max_log_length=None, **kwargs): """Boot a server, run script specified in command and delete server. :param image: glance image name to use for the vm :param flavor: VM flavor name :param username: ssh username on server, str :param password: Password on SSH authentication :param command: Command-specifying dictionary that either specifies remote command path via `remote_path' (can be uploaded from a local file specified by `local_path`), an inline script via `script_inline' or a local script file path using `script_file'. Both `script_file' and `local_path' are checked to be accessible by the `file_exists' validator code. The `script_inline' and `script_file' both require an `interpreter' value to specify the interpreter script should be run with. Note that any of `interpreter' and `remote_path' can be an array prefixed with environment variables and suffixed with args for the `interpreter' command. `remote_path's last component must be a path to a command to execute (also upload destination if a `local_path' is given). Uploading an interpreter is possible but requires that `remote_path' and `interpreter' path do match. Examples:: # Run a `local_script.pl' file sending it to a remote # Perl interpreter command = { "script_file": "local_script.pl", "interpreter": "/usr/bin/perl" } # Run an inline script sending it to a remote interpreter command = { "script_inline": "echo 'Hello, World!'", "interpreter": "/bin/sh" } # Run a remote command command = { "remote_path": "/bin/false" } # Copy a local command and run it command = { "remote_path": "/usr/local/bin/fio", "local_path": "/home/foobar/myfiodir/bin/fio" } # Copy a local command and run it with environment variable command = { "remote_path": ["HOME=/root", "/usr/local/bin/fio"], "local_path": "/home/foobar/myfiodir/bin/fio" } # Run an inline script sending it to a remote interpreter command = { "script_inline": "echo \"Hello, ${NAME:-World}\"", "interpreter": ["NAME=Earth", "/bin/sh"] } # Run an inline script sending it to an uploaded remote # interpreter command = { "script_inline": "echo \"Hello, ${NAME:-World}\"", "interpreter": ["NAME=Earth", "/tmp/sh"], "remote_path": "/tmp/sh", "local_path": "/home/user/work/cve/sh-1.0/bin/sh" } :param volume_args: volume args for booting server from volume :param floating_network: external network name, for floating ip :param port: ssh port for SSH connection :param use_floating_ip: bool, floating or fixed IP for SSH connection :param force_delete: whether to use force_delete for servers :param wait_for_ping: whether to check connectivity on server creation :param **kwargs: extra arguments for booting the server :param max_log_length: The number of tail nova console-log lines user would like to retrieve :returns: dictionary with keys `data' and `errors': data: dict, JSON output from the script errors: str, raw data from the script's stderr stream """ if volume_args: volume = self._create_volume(volume_args["size"], imageRef=None) kwargs["block_device_mapping"] = {"vdrally": "%s:::1" % volume.id} server, fip = self._boot_server_with_fip( image, flavor, use_floating_ip=use_floating_ip, floating_network=floating_network, key_name=self.context["user"]["keypair"]["name"], **kwargs) try: if wait_for_ping: self._wait_for_ping(fip["ip"]) code, out, err = self._run_command( fip["ip"], port, username, password, command=command) if code: raise exceptions.ScriptError( "Error running command %(command)s. " "Error %(code)s: %(error)s" % { "command": command, "code": code, "error": err}) try: data = json.loads(out) except ValueError as e: raise exceptions.ScriptError( "Command %(command)s has not output valid JSON: %(error)s." " Output: %(output)s" % { "command": command, "error": str(e), "output": out}) except (exceptions.TimeoutException, exceptions.SSHTimeout): console_logs = self._get_server_console_output(server, max_log_length) LOG.debug("VM console logs:\n%s", console_logs) raise finally: self._delete_server_with_fip(server, fip, force_delete=force_delete) if type(data) != dict: raise exceptions.ScriptError( "Command has returned data in unexpected format.\n" "Expected format: {" "\"additive\": [{chart data}, {chart data}, ...], " "\"complete\": [{chart data}, {chart data}, ...]}\n" "Actual data: %s" % data) if set(data) - {"additive", "complete"}: LOG.warning( "Deprecated since Rally release 0.4.1: command has " "returned data in format {\"key\": <value>, ...}\n" "Expected format: {" "\"additive\": [{chart data}, {chart data}, ...], " "\"complete\": [{chart data}, {chart data}, ...]}") output = None try: output = [[str(k), float(v)] for k, v in data.items()] except (TypeError, ValueError): raise exceptions.ScriptError( "Command has returned data in unexpected format.\n" "Expected format: {key1: <number>, " "key2: <number>, ...}.\n" "Actual data: %s" % data) if output: self.add_output(additive={"title": "Command output", "chart_plugin": "Lines", "data": output}) else: for chart_type, charts in data.items(): for chart in charts: self.add_output(**{chart_type: chart})
def run(self, image, flavor, username, size=1, password=None, floating_network=None, port=22, use_floating_ip=True, force_delete=False, max_log_length=None, **kwargs): """Create a share and access it from a VM. - create NFS share - launch VM - authorize VM's fip to access the share - mount share iside the VM - write to share - delete VM - delete share :param size: share size in GB, should be greater than 0 :param image: glance image name to use for the vm :param flavor: VM flavor name :param username: ssh username on server :param password: Password on SSH authentication :param floating_network: external network name, for floating ip :param port: ssh port for SSH connection :param use_floating_ip: bool, floating or fixed IP for SSH connection :param force_delete: whether to use force_delete for servers :param max_log_length: The number of tail nova console-log lines user would like to retrieve :param kwargs: optional args to create a share or a VM """ share_proto = "nfs" share = self._create_share(share_proto=share_proto, size=size, **kwargs) location = self._export_location(share) server, fip = self._boot_server_with_fip( image, flavor, use_floating_ip=use_floating_ip, floating_network=floating_network, key_name=self.context["user"]["keypair"]["name"], userdata="#cloud-config\npackages:\n - nfs-common", **kwargs) self._allow_access_share(share, "ip", fip["ip"], "rw") mount_opt = "-t nfs -o nfsvers=4.1,proto=tcp" script = f"cloud-init status -w;" \ f"sudo mount {mount_opt} {location[0]} /mnt || exit 1;" \ f"sudo touch /mnt/testfile || exit 2" command = {"script_inline": script, "interpreter": "/bin/bash"} try: rally_utils.wait_for_status( server, ready_statuses=["ACTIVE"], update_resource=rally_utils.get_from_manager(), ) code, out, err = self._run_command(fip["ip"], port, username, password, command=command) if code: raise exceptions.ScriptError( "Error running command %(command)s. " "Error %(code)s: %(error)s" % { "command": command, "code": code, "error": err }) except (exceptions.TimeoutException, exceptions.SSHTimeout): console_logs = self._get_server_console_output( server, max_log_length) LOG.debug("VM console logs:\n%s" % console_logs) raise finally: self._delete_server_with_fip(server, fip, force_delete=force_delete) self._delete_share(share) self.add_output( complete={ "title": "Script StdOut", "chart_plugin": "TextArea", "data": str(out).split("\n") }) if err: self.add_output( complete={ "title": "Script StdErr", "chart_plugin": "TextArea", "data": err.split("\n") })
def run(self, image, flavor, username, password=None, floating_net=None, port=22, use_floating_ip=True, description=None, admin_state=True, listeners=None, flavor_id=None, provider=None, vip_qos_policy_id=None, **kwargs): """Create a loadbalancer in front of two apache servers. :param image: The image to boot from :param flavor: Flavor used to boot instance :param username: ssh username on server :param password: Password on SSH authentication :param floating_net: external network name, for floating ip :param port: ssh port for SSH connection :param use_floating_ip: bool, floating or fixed IP for SSH connection :param description: Human-readable description of the loadbalancer :param admin_state: The administrative state of the loadbalancer, which is up(true) or down(false) :param listeners: The associated listener id, if any :param flavor_id: The ID of the flavor :param provider: Provider name for the loadbalancer :param vip_qos_policy_id: The ID of the QoS policy :param kwargs: Optional additional arguments for instance creation - Create a loadbalancer - Create a listener for protocal HTTP on port 80 - Create a pool using ROUND_ROBIN and protocal HTTP - Create two VMs, installing apache2 from cloud-init - Wait for loadbalancer and server resources to become ACTIVE - Create a loadbalancer member for each VM - Assign a floating IP to the loadbalancer port - Verify that a connection can be established to port 80 at the FIP """ project_id = self.context["tenant"]["id"] network = self.context["tenant"]["networks"][0] subnet = network["subnets"][0] security_group_create_args = {"name": self.generate_random_name()} security_group = self.clients("neutron").create_security_group( {"security_group": security_group_create_args}) sg_id = security_group["security_group"]["id"] security_group_rule_args = { "security_group_id": sg_id, "direction": "ingress", "port_range_max": 80, "port_range_min": 80, "protocol": "tcp", "remote_ip_prefix": "0.0.0.0/0", } self.clients("neutron").create_security_group_rule( {"security_group_rule": security_group_rule_args}) fnet = {"id": floating_net} lp_fip = network_wrapper.wrap(self.clients, self).create_floating_ip( ext_network=fnet, tenant_id=project_id) lb = self.octavia.load_balancer_create( subnet_id=subnet, description=description, admin_state=admin_state, project_id=project_id, listeners=listeners, flavor_id=flavor_id, provider=provider, vip_qos_policy_id=vip_qos_policy_id ) LOG.info("Waiting for Octavia loadbalancer to become ready.") self.octavia.wait_for_loadbalancer_prov_status(lb) lb_id = lb["id"] largs = { "protocol": "HTTP", "protocol_port": 80, "loadbalancer_id": lb_id, } listener = self.octavia.listener_create(json={"listener": largs}) l1 = listener["listener"] self.octavia.wait_for_listener_prov_status(l1) pool = self.octavia.pool_create( lb_id=lb_id, protocol="HTTP", lb_algorithm="ROUND_ROBIN", listener_id=l1["id"], project_id=project_id, ) # Assign the FIP to the loadbalancer fip_update_dict = {"port_id": lb["vip_port_id"]} self.clients("neutron").update_floatingip( lp_fip["id"], {"floatingip": fip_update_dict}) # Place a couple http servers behind the loadbalancer for s in range(2): server, fip = self._boot_server_with_fip( image, flavor, use_floating_ip=use_floating_ip, floating_net=floating_net, key_name=self.context["user"]["keypair"]["name"], userdata="#cloud-config\npackages:\n - apache2", **kwargs) server_address = server.addresses[network["name"]][0]["addr"] margs = { "address": server_address, "protocol_port": 80, } self.octavia.member_create( pool["id"], json={"member": margs}) # Allow http access to the server self._add_server_secgroups(server, sg_id) LOG.info("Added server with IP %s as a member to pool: %s" % (server_address, pool["id"])) # It takes some time for the server to become ready. # Be sure the server network is available before proceeding. script = "cloud-init status -w || exit 1" command = { "script_inline": script, "interpreter": "/bin/bash" } LOG.info("Waiting for server instance to become ready.") rally_utils.wait_for_status( server, ready_statuses=["ACTIVE"], update_resource=rally_utils.get_from_manager(), ) code, out, err = self._run_command( fip["ip"], port, username, password, command=command) if code: raise exceptions.ScriptError( "Error running command %(command)s. " "Error %(code)s: %(error)s" % { "command": command, "code": code, "error": err}) # Verifiy the http servers can be reached on the loadbalancer FIP. response = urlopen("http://%s/" % lp_fip["ip"]) r_code = response.getcode() if r_code != 200: raise exceptions.RallyException( "Received unexpected error when connecting to %s: %d" % (lp_fip["ip"], r_code)) LOG.info("Successfully connected to Octavia loadbalancer at http://%s/" % lp_fip["ip"])