Beispiel #1
0
    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}}
        )
Beispiel #2
0
    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}
Beispiel #3
0
    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
Beispiel #4
0
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
Beispiel #5
0
    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)
Beispiel #6
0
        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}}
            )
Beispiel #7
0
    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}
Beispiel #8
0
    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}
Beispiel #9
0
    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
                })
Beispiel #10
0
    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")
                })
Beispiel #11
0
    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})
Beispiel #12
0
    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")
                })
Beispiel #13
0
    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"])