Esempio n. 1
0
 def __init__(self):
     """ Initialization. """
     self._http_connection = None
     self._http_cache = ConnectionCache()
     self._amqp_connection = None
     self._amqp_cache = ConnectionCache()
     self._channel = None
Esempio n. 2
0
    def __init__(self):
        """
        initial
        """

        self._cache = ConnectionCache()
        self.dict_alias = {}
 def __init__(self, libraryComponents=[]):
     """
     DatabaseLib could be extened through parameter libraryComponents
     """
     self._connections = ConnectionCache()
     self._sessions = {}
     super(DatabaseLib, self).__init__(libraryComponents)
    def __init__(self):
        """Library initialization.
        Robot Framework ConnectionCache() class is prepared for working with concurrent connections."""

        self.bi = BuiltIn()
        self._connection = None
        self._cache = ConnectionCache()
        self._connections = []
Esempio n. 5
0
    def __init__(self) -> None:
        """Library initialization.
        Robot Framework ConnectionCache() class is prepared for working with concurrent connections."""

        self.bi = BuiltIn()
        self._connection: Optional[ZookeeperConnection] = None
        self._cache = ConnectionCache()
        self._connections: List[ZookeeperConnection] = []
Esempio n. 6
0
 def __init__(self, heartbeat=10, timeout=5):
     self.amqp_addr = ""
     self.amqp_connection = None
     self.amqp_channel = None
     self.exchange = ""
     self.routing_key = ""
     self.queue = ""
     # self.amqp_heartbeat = heartbeat
     self.amqp_timeout = timeout
     self._cache = ConnectionCache()
    def __init__(self, pool: str = 'default') -> None:
        """
        Library initialization.\n
        Robot Framework ConnectionCache() class is prepared for working with concurrent connections.

        *Args*:\n
            _pool_: name for connection pool.
        """
        self._connection: Optional[RequestConnection] = None
        self.headers: Dict[str, str] = {}
        self._cache = ConnectionCache()
        self.pool = pool
Esempio n. 8
0
 def __init__(self, transport="https", host='localhost',
              username="******", password="******", port="443", alias=None):
     """Defaults may be changed by specifying when importing the library:
     | *** Setting ***
     | Library AristaLibrary
     | Library AristaLirary | username="******" | password="******"
     """
     self.host = host
     self.transport = transport
     self.port = port
     self.username = username
     self.password = password
     self.alias = None
     self.connections = dict()
     self._connection = ConnectionCache()
Esempio n. 9
0
class SessionKeywords(LibraryComponent):
    def __init__(self, state):
        LibraryComponent.__init__(self, state)
        self._cache = ConnectionCache('No sessions.')

    @keyword('Create Session With Keys')
    def create_session_with_keys(self, region, access_key, secret_key):
        """Takes Region as an argument and creates as session with your access key
        and secret key stored at ~/.aws/credentials.
        Will throw error if not configured.

        Examples:
        | Create Session With Keys | us-west-1 | access key | secret key |
        """
        self.rb_logger.info("Creating Session: %s" % region)
        session = boto3.Session(aws_access_key_id=access_key,
                                aws_secret_access_key=secret_key,
                                region_name=region)
        print(session)
        self._cache.register(session, alias=region)
        self.state.session = session
        return session

    @keyword('Create Session With Profile')
    def create_session_with_profile(self, region, profile):
        """Takes Region as an argument and creates as session with your profile
         stored at ~/.aws/config. Will throw error if not configured

        Examples:
        | Create Session With Profile | us-west-1 |  profile name |
        """
        self.rb_logger.info(f"Creating Session: {region}, {profile}")
        session = boto3.Session(profile_name=profile, region_name=region)
        self._cache.register(session, alias=region)
        self.state.session = session
        return session

    @keyword('Delete Session')
    def delete_session(self, region, profile=None):
        """Removes session.
        Arguments:
        - ``region``: A case and space insensitive string to identify the session.
                     (Default ``region``)
        Examples:
        | Delete Session | REGION |
        """
        self._cache.switch(region)
        index = self._cache.current_index
        self._cache.current = self._cache._no_current
        self._cache._connections[index - 1] = None
        self._cache._aliases.pop(region)

    @keyword('Delete All Sessions')
    def delete_all_sessions(self):
        """ Delete All Sessions """
        self._cache.empty_cache()
 def __init__(self):
     self._cache = ConnectionCache(no_current_msg='No current client')
     self._imports = []
     self._logger = logger
     self._global_timeout = False
     try:  # exception if Robot is not running
         BuiltIn().set_global_variable("${SUDS_NULL}", null())
     except:
         pass
Esempio n. 11
0
 def __init__(self):
     self._cache = ConnectionCache(no_current_msg='No current client')
     self._imports = []
     self._logger = logger
     self._global_timeout = True
     self._external_options = weakref.WeakKeyDictionary()
     try:  # exception if Robot is not running
         BuiltIn().set_global_variable("${SUDS_NULL}", null())
     except:
         pass
Esempio n. 12
0
 def __init__(self):
     self._cache = ConnectionCache(no_current_msg='No current client')
     self._imports = []
     self._logger = logger
     self._global_timeout = True
     self._external_options = weakref.WeakKeyDictionary()
     try:
         part = urllib2.__version__.split('.', 1)
         n = float('.'.join(part))
         if n >= 2.6:
             self._global_timeout = False
     except Exception, e:
         raise e
         self._logger.warn("Failed to get urllib2's version")
         self._logger.debug(traceback.format_exc())
Esempio n. 13
0
 def __init__(self, developer_email="*****@*****.**",
              timeout=DEFAULT_TIMEOUT,
              newline=DEFAULT_NEWLINE,
              prompt=DEFAULT_PROMPT,
              loglevel=DEFAULT_LOGLEVEL,
              term_type=DEFAULT_TERM_TYPE,
              width=DEFAULT_TERM_WIDTH,
              height=DEFAULT_TERM_HEIGHT,
              path_separator=DEFAULT_PATH_SEPARATOR,
             encoding=DEFAULT_ENCODING):       
     self.developer_email=developer_email
     self._connections = ConnectionCache()
     self._config = _DefaultConfiguration(
     timeout or self.DEFAULT_TIMEOUT,
     newline or self.DEFAULT_NEWLINE,
     prompt or self.DEFAULT_PROMPT,
     loglevel or self.DEFAULT_LOGLEVEL,
     term_type or self.DEFAULT_TERM_TYPE,
     width or self.DEFAULT_TERM_WIDTH,
     height or self.DEFAULT_TERM_HEIGHT,
     path_separator or self.DEFAULT_PATH_SEPARATOR,
     encoding or self.DEFAULT_ENCODING
     )
     print "init ok\n"
Esempio n. 14
0
    def __init__(self, timeout=3, newline='LF', prompt=None,
                 loglevel='INFO'):
        """SSH Library allows some import time configuration.

        `timeout`, `newline` and `prompt` set default values for new
        connections opened with `Open Connection`. The default values may
        later be changed using `Set Default Configuration` and settings
        of a single connection with `Set Client Configuration`.

        `loglevel` sets the default log level used to log return values of
        `Read Until` variants. It can also be later changed using `Set
        Default Configuration`.

        Examples:
        | Library | SSHLibrary | # use default values |
        | Library | SSHLibrary | timeout=10 | prompt=> |
        """
        self._cache = ConnectionCache()
        self._config = DefaultConfig(timeout, newline, prompt, loglevel)
Esempio n. 15
0
 def __init__(self):
     self._cache = ConnectionCache(no_current_msg='No current client')
     self._imports = []
     self._logger = logger
     self._global_timeout = True
     self._external_options = weakref.WeakKeyDictionary()
     try:
         part = urllib.request.__version__.split('.', 1)
         n = float('.'.join(part))
         if n >= 2.6:
             self._global_timeout = False
     except Exception as e:
         raise e
         self._logger.warn("Failed to get urllib2's version")
         self._logger.debug(traceback.format_exc())
     try:  # exception if Robot is not running
         BuiltIn().set_global_variable("${SUDS_NULL}", null())
     except:
         pass
class OracleDB(object):
    """
    Библиотека для работы с базой данных Oracle.
    
    == Зависимости ==
    | cx_Oracle | http://cx-oracle.sourceforge.net | version > 3.0 |
    | robot framework | http://robotframework.org |
    """
    
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self):
        self._connection = None
        self._cache = ConnectionCache()    # использование кеша Robot Framework для одновременной работы с несколькими соединениями
        
    def connect_to_oracle (self, dbName, dbUserName, dbPassword, alias=None):
        """
        Подключение к Oracle.
        
        *Args:*\n
        _dbName_ - имя базы данных;\n
        _dbUserName_ - имя пользователя;\n
        _dbPassword_ - пароль пользователя;\n
        _alias_ - псевдоним соединения;\n
        
        *Returns:*\n
        Индекс текущего соединения.
        
        *Example:*\n
        | Connect To Oracle  |  rb60db  |  bis  |  password |
        """

        try:
            logger.debug ('Connecting using : dbName=%s, dbUserName=%s, dbPassword=%s ' % (dbName, dbUserName, dbPassword))
            connection_string = '%s/%s@%s' % (dbUserName,dbPassword,dbName)
            self._connection=cx_Oracle.connect(connection_string)
            return self._cache.register(self._connection, alias)
        except cx_Oracle.DatabaseError,info:
            raise Exception ("Logon to oracle  Error:",str(info))
Esempio n. 17
0
 def __init__(self):
     self._processes = ConnectionCache('No active process.')
     self._results = {}
Esempio n. 18
0
class Process(object):
    """Robot Framework test library for running processes.

    This library utilizes Python's
    [http://docs.python.org/2/library/subprocess.html|subprocess]
    module and its
    [http://docs.python.org/2/library/subprocess.html#subprocess.Popen|Popen]
    class.

    The library has following main usages:

    - Running processes in system and waiting for their completion using
      `Run Process` keyword.
    - Starting processes on background using `Start Process`.
    - Waiting started process to complete using `Wait For Process` or
      stopping them with `Terminate Process` or `Terminate All Processes`.

    This library is new in Robot Framework 2.8.

    == Table of contents ==

    - `Specifying command and arguments`
    - `Process configuration`
    - `Active process`
    - `Result object`
    - `Boolean arguments`
    - `Example`
    - `Shortcuts`
    - `Keywords`

    = Specifying command and arguments =

    Both `Run Process` and `Start Process` accept the command to execute and
    all arguments passed to the command as separate arguments. This makes usage
    convenient and also allows these keywords to automatically escape possible
    spaces and other special characters in commands and arguments. Notice that
    if a command accepts options that themselves accept values, these options
    and their values must be given as separate arguments.

    When `running processes in shell`, it is also possible to give the whole
    command to execute as a single string. The command can then contain
    multiple commands to be run together. When using this approach, the caller
    is responsible on escaping.

    Examples:
    | `Run Process` | ${tools}${/}prog.py | argument | second arg with spaces |
    | `Run Process` | java | -jar | ${jars}${/}example.jar | --option | value |
    | `Run Process` | prog.py "one arg" && tool.sh | shell=yes | cwd=${tools} |

    Starting from Robot Framework 2.8.6, possible non-string arguments are
    converted to strings automatically.

    = Process configuration =

    `Run Process` and `Start Process` keywords can be configured using
    optional ``**configuration`` keyword arguments. Configuration arguments
    must be given after other arguments passed to these keywords and must
    use syntax like ``name=value``. Available configuration arguments are
    listed below and discussed further in sections afterwards.

    |  = Name =  |                  = Explanation =                      |
    | shell      | Specifies whether to run the command in shell or not. |
    | cwd        | Specifies the working directory.                      |
    | env        | Specifies environment variables given to the process. |
    | env:<name> | Overrides the named environment variable(s) only.     |
    | stdout     | Path of a file where to write standard output.        |
    | stderr     | Path of a file where to write standard error.         |
    | output_encoding | Encoding to use when reading command outputs.    |
    | alias      | Alias given to the process.                           |

    Note that because ``**configuration`` is passed using ``name=value`` syntax,
    possible equal signs in other arguments passed to `Run Process` and
    `Start Process` must be escaped with a backslash like ``name\\=value``.
    See `Run Process` for an example.

    == Running processes in shell ==

    The ``shell`` argument specifies whether to run the process in a shell or
    not. By default shell is not used, which means that shell specific commands,
    like ``copy`` and ``dir`` on Windows, are not available. You can, however,
    run shell scripts and batch files without using a shell.

    Giving the ``shell`` argument any non-false value, such as ``shell=True``,
    changes the program to be executed in a shell. It allows using the shell
    capabilities, but can also make the process invocation operating system
    dependent. Having a shell between the actually started process and this
    library can also interfere communication with the process such as stopping
    it and reading its outputs. Because of these problems, it is recommended
    to use the shell only when absolutely necessary.

    When using a shell it is possible to give the whole command to execute
    as a single string. See `Specifying command and arguments` section for
    examples and more details in general.

    == Current working directory ==

    By default the child process will be executed in the same directory
    as the parent process, the process running tests, is executed. This
    can be changed by giving an alternative location using the ``cwd`` argument.
    Forward slashes in the given path are automatically converted to
    backslashes on Windows.

    `Standard output and error streams`, when redirected to files,
    are also relative to the current working directory possibly set using
    the ``cwd`` argument.

    Example:
    | `Run Process` | prog.exe | cwd=${ROOT}/directory | stdout=stdout.txt |

    == Environment variables ==

    By default the child process will get a copy of the parent process's
    environment variables. The ``env`` argument can be used to give the
    child a custom environment as a Python dictionary. If there is a need
    to specify only certain environment variable, it is possible to use the
    ``env:<name>=<value>`` format to set or override only that named variables.
    It is also possible to use these two approaches together.

    Examples:
    | `Run Process` | program | env=${environ} |
    | `Run Process` | program | env:http_proxy=10.144.1.10:8080 | env:PATH=%{PATH}${:}${PROGDIR} |
    | `Run Process` | program | env=${environ} | env:EXTRA=value |

    == Standard output and error streams ==

    By default processes are run so that their standard output and standard
    error streams are kept in the memory. This works fine normally,
    but if there is a lot of output, the output buffers may get full and
    the program can hang. Additionally on Jython, everything written to
    these in-memory buffers can be lost if the process is terminated.

    To avoid the above mentioned problems, it is possible to use ``stdout``
    and ``stderr`` arguments to specify files on the file system where to
    redirect the outputs. This can also be useful if other processes or
    other keywords need to read or manipulate the outputs somehow.

    Given ``stdout`` and ``stderr`` paths are relative to the `current working
    directory`. Forward slashes in the given paths are automatically converted
    to backslashes on Windows.

    As a special feature, it is possible to redirect the standard error to
    the standard output by using ``stderr=STDOUT``.

    Regardless are outputs redirected to files or not, they are accessible
    through the `result object` returned when the process ends. Commands are
    expected to write outputs using the console encoding, but `output encoding`
    can be configured using the ``output_encoding`` argument if needed.

    Examples:
    | ${result} = | `Run Process` | program | stdout=${TEMPDIR}/stdout.txt | stderr=${TEMPDIR}/stderr.txt |
    | `Log Many`  | stdout: ${result.stdout} | stderr: ${result.stderr} |
    | ${result} = | `Run Process` | program | stderr=STDOUT |
    | `Log`       | all output: ${result.stdout} |

    Note that the created output files are not automatically removed after
    the test run. The user is responsible to remove them if needed.

    == Output encoding ==

    Executed commands are, by default, expected to write outputs to the
    `standard output and error streams` using the encoding used by the
    system console. If the command uses some other encoding, that can be
    configured using the ``output_encoding`` argument. This is especially
    useful on Windows where the console uses a different encoding than rest
    of the system, and many commands use the general system encoding instead
    of the console encoding.

    The value used with the ``output_encoding`` argument must be a valid
    encoding and must match the encoding actually used by the command. As a
    convenience, it is possible to use strings ``CONSOLE`` and ``SYSTEM``
    to specify that the console or system encoding is used, respectively.
    If produced outputs use different encoding then configured, values got
    through the `result object` will be invalid.

    Examples:
    | `Start Process` | program | output_encoding=UTF-8 |
    | `Run Process`   | program | stdout=${path} | output_encoding=SYSTEM |

    The support to set output encoding is new in Robot Framework 3.0.

    == Alias ==

    A custom name given to the process that can be used when selecting the
    `active process`.

    Examples:
    | `Start Process` | program | alias=example |
    | `Run Process`   | python  | -c | print 'hello' | alias=hello |

    = Active process =

    The test library keeps record which of the started processes is currently
    active. By default it is latest process started with `Start Process`,
    but `Switch Process` can be used to select a different one. Using
    `Run Process` does not affect the active process.

    The keywords that operate on started processes will use the active process
    by default, but it is possible to explicitly select a different process
    using the ``handle`` argument. The handle can be the identifier returned by
    `Start Process` or an ``alias`` explicitly given to `Start Process` or
    `Run Process`.

    = Result object =

    `Run Process`, `Wait For Process` and `Terminate Process` keywords return a
    result object that contains information about the process execution as its
    attributes. The same result object, or some of its attributes, can also
    be get using `Get Process Result` keyword. Attributes available in the
    object are documented in the table below.

    | = Attribute = |             = Explanation =               |
    | rc            | Return code of the process as an integer. |
    | stdout        | Contents of the standard output stream.   |
    | stderr        | Contents of the standard error stream.    |
    | stdout_path   | Path where stdout was redirected or ``None`` if not redirected. |
    | stderr_path   | Path where stderr was redirected or ``None`` if not redirected. |

    Example:
    | ${result} =            | `Run Process`         | program               |
    | `Should Be Equal As Integers` | ${result.rc}   | 0                     |
    | `Should Match`         | ${result.stdout}      | Some t?xt*            |
    | `Should Be Empty`      | ${result.stderr}      |                       |
    | ${stdout} =            | `Get File`            | ${result.stdout_path} |
    | `Should Be Equal`      | ${stdout}             | ${result.stdout}      |
    | `File Should Be Empty` | ${result.stderr_path} |                       |

    = Boolean arguments =

    Some keywords accept arguments that are handled as Boolean values true or
    false. If such an argument is given as a string, it is considered false if
    it is either empty or case-insensitively equal to ``false`` or ``no``.
    Other strings are considered true regardless their value, and other
    argument types are tested using same
    [http://docs.python.org/2/library/stdtypes.html#truth-value-testing|rules
    as in Python].

    True examples:
    | `Terminate Process` | kill=True     | # Strings are generally true.    |
    | `Terminate Process` | kill=yes      | # Same as the above.             |
    | `Terminate Process` | kill=${TRUE}  | # Python ``True`` is true.       |
    | `Terminate Process` | kill=${42}    | # Numbers other than 0 are true. |

    False examples:
    | `Terminate Process` | kill=False    | # String ``false`` is false.   |
    | `Terminate Process` | kill=no       | # Also string ``no`` is false. |
    | `Terminate Process` | kill=${EMPTY} | # Empty string is false.       |
    | `Terminate Process` | kill=${FALSE} | # Python ``False`` is false.   |

    Note that prior to Robot Framework 2.8 all non-empty strings, including
    ``false``, were considered true. Additionally, ``no`` is considered false
    only in Robot Framework 2.9 and newer.

    = Example =

    | ***** Settings *****
    | Library           Process
    | Suite Teardown    `Terminate All Processes`    kill=True
    |
    | ***** Test Cases *****
    | Example
    |     `Start Process`    program    arg1    arg2    alias=First
    |     ${handle} =    `Start Process`    command.sh arg | command2.sh    shell=True    cwd=/path
    |     ${result} =    `Run Process`    ${CURDIR}/script.py
    |     `Should Not Contain`    ${result.stdout}    FAIL
    |     `Terminate Process`    ${handle}
    |     ${result} =    `Wait For Process`    First
    |     `Should Be Equal As Integers`    ${result.rc}    0
    """
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = get_version()
    TERMINATE_TIMEOUT = 30
    KILL_TIMEOUT = 10

    def __init__(self):
        self._processes = ConnectionCache('No active process.')
        self._results = {}

    def run_process(self, command, *arguments, **configuration):
        """Runs a process and waits for it to complete.

        ``command`` and ``*arguments`` specify the command to execute and
        arguments passed to it. See `Specifying command and arguments` for
        more details.

        ``**configuration`` contains additional configuration related to
        starting processes and waiting for them to finish. See `Process
        configuration` for more details about configuration related to starting
        processes. Configuration related to waiting for processes consists of
        ``timeout`` and ``on_timeout`` arguments that have same semantics as
        with `Wait For Process` keyword. By default there is no timeout, and
        if timeout is defined the default action on timeout is ``terminate``.

        Returns a `result object` containing information about the execution.

        Note that possible equal signs in ``*arguments`` must be escaped
        with a backslash (e.g. ``name\\=value``) to avoid them to be passed in
        as ``**configuration``.

        Examples:
        | ${result} = | Run Process | python | -c | print 'Hello, world!' |
        | Should Be Equal | ${result.stdout} | Hello, world! |
        | ${result} = | Run Process | ${command} | stderr=STDOUT | timeout=10s |
        | ${result} = | Run Process | ${command} | timeout=1min | on_timeout=continue |
        | ${result} = | Run Process | java -Dname\\=value Example | shell=True | cwd=${EXAMPLE} |

        This keyword does not change the `active process`.

        ``timeout`` and ``on_timeout`` arguments are new in Robot Framework
        2.8.4.
        """
        current = self._processes.current
        timeout = configuration.pop('timeout', None)
        on_timeout = configuration.pop('on_timeout', 'terminate')
        try:
            handle = self.start_process(command, *arguments, **configuration)
            return self.wait_for_process(handle, timeout, on_timeout)
        finally:
            self._processes.current = current

    def start_process(self, command, *arguments, **configuration):
        """Starts a new process on background.

        See `Specifying command and arguments` and `Process configuration`
        for more information about the arguments, and `Run Process` keyword
        for related examples.

        Makes the started process new `active process`. Returns an identifier
        that can be used as a handle to activate the started process if needed.

        Starting from Robot Framework 2.8.5, processes are started so that
        they create a new process group. This allows sending signals to and
        terminating also possible child processes. This is not supported by
        Jython in general nor by Python versions prior to 2.7 on Windows.
        """
        conf = ProcessConfiguration(**configuration)
        command = conf.get_command(command, list(arguments))
        self._log_start(command, conf)
        process = subprocess.Popen(command, **conf.popen_config)
        self._results[process] = ExecutionResult(process, **conf.result_config)
        return self._processes.register(process, alias=conf.alias)

    def _log_start(self, command, config):
        if is_list_like(command):
            command = self.join_command_line(command)
        logger.info(u'Starting process:\n%s' % system_decode(command))
        logger.debug(u'Process configuration:\n%s' % config)

    def is_process_running(self, handle=None):
        """Checks is the process running or not.

        If ``handle`` is not given, uses the current `active process`.

        Returns ``True`` if the process is still running and ``False`` otherwise.
        """
        return self._processes[handle].poll() is None

    def process_should_be_running(self,
                                  handle=None,
                                  error_message='Process is not running.'):
        """Verifies that the process is running.

        If ``handle`` is not given, uses the current `active process`.

        Fails if the process has stopped.
        """
        if not self.is_process_running(handle):
            raise AssertionError(error_message)

    def process_should_be_stopped(self,
                                  handle=None,
                                  error_message='Process is running.'):
        """Verifies that the process is not running.

        If ``handle`` is not given, uses the current `active process`.

        Fails if the process is still running.
        """
        if self.is_process_running(handle):
            raise AssertionError(error_message)

    def wait_for_process(self,
                         handle=None,
                         timeout=None,
                         on_timeout='continue'):
        """Waits for the process to complete or to reach the given timeout.

        The process to wait for must have been started earlier with
        `Start Process`. If ``handle`` is not given, uses the current
        `active process`.

        ``timeout`` defines the maximum time to wait for the process. It can be
        given in
        [http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#time-format|
        various time formats] supported by Robot Framework, for example, ``42``,
        ``42 s``, or ``1 minute 30 seconds``.

        ``on_timeout`` defines what to do if the timeout occurs. Possible values
        and corresponding actions are explained in the table below. Notice
        that reaching the timeout never fails the test.

        | = Value = |               = Action =               |
        | continue  | The process is left running (default). |
        | terminate | The process is gracefully terminated.  |
        | kill      | The process is forcefully stopped.     |

        See `Terminate Process` keyword for more details how processes are
        terminated and killed.

        If the process ends before the timeout or it is terminated or killed,
        this keyword returns a `result object` containing information about
        the execution. If the process is left running, Python ``None`` is
        returned instead.

        Examples:
        | # Process ends cleanly      |                  |                  |
        | ${result} =                 | Wait For Process | example          |
        | Process Should Be Stopped   | example          |                  |
        | Should Be Equal As Integers | ${result.rc}     | 0                |
        | # Process does not end      |                  |                  |
        | ${result} =                 | Wait For Process | timeout=42 secs  |
        | Process Should Be Running   |                  |                  |
        | Should Be Equal             | ${result}        | ${NONE}          |
        | # Kill non-ending process   |                  |                  |
        | ${result} =                 | Wait For Process | timeout=1min 30s | on_timeout=kill |
        | Process Should Be Stopped   |                  |                  |
        | Should Be Equal As Integers | ${result.rc}     | -9               |

        ``timeout`` and ``on_timeout`` are new in Robot Framework 2.8.2.
        """
        process = self._processes[handle]
        logger.info('Waiting for process to complete.')
        if timeout:
            timeout = timestr_to_secs(timeout)
            if not self._process_is_stopped(process, timeout):
                logger.info('Process did not complete in %s.' %
                            secs_to_timestr(timeout))
                return self._manage_process_timeout(handle, on_timeout.lower())
        return self._wait(process)

    def _manage_process_timeout(self, handle, on_timeout):
        if on_timeout == 'terminate':
            return self.terminate_process(handle)
        elif on_timeout == 'kill':
            return self.terminate_process(handle, kill=True)
        else:
            logger.info('Leaving process intact.')
            return None

    def _wait(self, process):
        result = self._results[process]
        result.rc = process.wait() or 0
        result.close_streams()
        logger.info('Process completed.')
        return result

    def terminate_process(self, handle=None, kill=False):
        """Stops the process gracefully or forcefully.

        If ``handle`` is not given, uses the current `active process`.

        By default first tries to stop the process gracefully. If the process
        does not stop in 30 seconds, or ``kill`` argument is given a true value,
        (see `Boolean arguments`) kills the process forcefully. Stops also all
        the child processes of the originally started process.

        Waits for the process to stop after terminating it. Returns a `result
        object` containing information about the execution similarly as `Wait
        For Process`.

        On Unix-like machines graceful termination is done using ``TERM (15)``
        signal and killing using ``KILL (9)``. Use `Send Signal To Process`
        instead if you just want to send either of these signals without
        waiting for the process to stop.

        On Windows graceful termination is done using ``CTRL_BREAK_EVENT``
        event and killing using Win32 API function ``TerminateProcess()``.

        Examples:
        | ${result} =                 | Terminate Process |     |
        | Should Be Equal As Integers | ${result.rc}      | -15 | # On Unixes |
        | Terminate Process           | myproc            | kill=true |

        Limitations:
        - Graceful termination is not supported on Windows by Jython nor by
          Python versions prior to 2.7. Process is killed instead.
        - Stopping the whole process group is not supported by Jython at all
          nor by Python versions prior to 2.7 on Windows.
        - On Windows forceful kill only stops the main process, not possible
          child processes.

        Automatically killing the process if termination fails as well as
        returning a result object are new features in Robot Framework 2.8.2.
        Terminating also possible child processes, including using
        ``CTRL_BREAK_EVENT`` on Windows, is new in Robot Framework 2.8.5.
        """
        process = self._processes[handle]
        if not hasattr(process, 'terminate'):
            raise RuntimeError('Terminating processes is not supported '
                               'by this Python version.')
        terminator = self._kill if is_truthy(kill) else self._terminate
        try:
            terminator(process)
        except OSError:
            if not self._process_is_stopped(process, self.KILL_TIMEOUT):
                raise
            logger.debug('Ignored OSError because process was stopped.')
        return self._wait(process)

    def _kill(self, process):
        logger.info('Forcefully killing process.')
        if hasattr(os, 'killpg'):
            os.killpg(process.pid, signal_module.SIGKILL)
        else:
            process.kill()
        if not self._process_is_stopped(process, self.KILL_TIMEOUT):
            raise RuntimeError('Failed to kill process.')

    def _terminate(self, process):
        logger.info('Gracefully terminating process.')
        # Sends signal to the whole process group both on POSIX and on Windows
        # if supported by the interpreter.
        if hasattr(os, 'killpg'):
            os.killpg(process.pid, signal_module.SIGTERM)
        elif hasattr(signal_module, 'CTRL_BREAK_EVENT'):
            if IRONPYTHON:
                # https://ironpython.codeplex.com/workitem/35020
                ctypes.windll.kernel32.GenerateConsoleCtrlEvent(
                    signal_module.CTRL_BREAK_EVENT, process.pid)
            else:
                process.send_signal(signal_module.CTRL_BREAK_EVENT)
        else:
            process.terminate()
        if not self._process_is_stopped(process, self.TERMINATE_TIMEOUT):
            logger.info('Graceful termination failed.')
            self._kill(process)

    def terminate_all_processes(self, kill=False):
        """Terminates all still running processes started by this library.

        This keyword can be used in suite teardown or elsewhere to make
        sure that all processes are stopped,

        By default tries to terminate processes gracefully, but can be
        configured to forcefully kill them immediately. See `Terminate Process`
        that this keyword uses internally for more details.
        """
        for handle in range(1, len(self._processes) + 1):
            if self.is_process_running(handle):
                self.terminate_process(handle, kill=kill)
        self.__init__()

    def send_signal_to_process(self, signal, handle=None, group=False):
        """Sends the given ``signal`` to the specified process.

        If ``handle`` is not given, uses the current `active process`.

        Signal can be specified either as an integer as a signal name. In the
        latter case it is possible to give the name both with or without ``SIG``
        prefix, but names are case-sensitive. For example, all the examples
        below send signal ``INT (2)``:

        | Send Signal To Process | 2      |        | # Send to active process |
        | Send Signal To Process | INT    |        |                          |
        | Send Signal To Process | SIGINT | myproc | # Send to named process  |

        This keyword is only supported on Unix-like machines, not on Windows.
        What signals are supported depends on the system. For a list of
        existing signals on your system, see the Unix man pages related to
        signal handling (typically ``man signal`` or ``man 7 signal``).

        By default sends the signal only to the parent process, not to possible
        child processes started by it. Notice that when `running processes in
        shell`, the shell is the parent process and it depends on the system
        does the shell propagate the signal to the actual started process.

        To send the signal to the whole process group, ``group`` argument can
        be set to any true value (see `Boolean arguments`). This is not
        supported by Jython, however.

        New in Robot Framework 2.8.2. Support for ``group`` argument is new
        in Robot Framework 2.8.5.
        """
        if os.sep == '\\':
            raise RuntimeError('This keyword does not work on Windows.')
        process = self._processes[handle]
        signum = self._get_signal_number(signal)
        logger.info('Sending signal %s (%d).' % (signal, signum))
        if is_truthy(group) and hasattr(os, 'killpg'):
            os.killpg(process.pid, signum)
        elif hasattr(process, 'send_signal'):
            process.send_signal(signum)
        else:
            raise RuntimeError('Sending signals is not supported '
                               'by this Python version.')

    def _get_signal_number(self, int_or_name):
        try:
            return int(int_or_name)
        except ValueError:
            return self._convert_signal_name_to_number(int_or_name)

    def _convert_signal_name_to_number(self, name):
        try:
            return getattr(signal_module,
                           name if name.startswith('SIG') else 'SIG' + name)
        except AttributeError:
            raise RuntimeError("Unsupported signal '%s'." % name)

    def get_process_id(self, handle=None):
        """Returns the process ID (pid) of the process as an integer.

        If ``handle`` is not given, uses the current `active process`.

        Notice that the pid is not the same as the handle returned by
        `Start Process` that is used internally by this library.
        """
        return self._processes[handle].pid

    def get_process_object(self, handle=None):
        """Return the underlying ``subprocess.Popen`` object.

        If ``handle`` is not given, uses the current `active process`.
        """
        return self._processes[handle]

    def get_process_result(self,
                           handle=None,
                           rc=False,
                           stdout=False,
                           stderr=False,
                           stdout_path=False,
                           stderr_path=False):
        """Returns the specified `result object` or some of its attributes.

        The given ``handle`` specifies the process whose results should be
        returned. If no ``handle`` is given, results of the current `active
        process` are returned. In either case, the process must have been
        finishes before this keyword can be used. In practice this means
        that processes started with `Start Process` must be finished either
        with `Wait For Process` or `Terminate Process` before using this
        keyword.

        If no other arguments than the optional ``handle`` are given, a whole
        `result object` is returned. If one or more of the other arguments
        are given any true value, only the specified attributes of the
        `result object` are returned. These attributes are always returned
        in the same order as arguments are specified in the keyword signature.
        See `Boolean arguments` section for more details about true and false
        values.

        Examples:
        | Run Process           | python             | -c            | print 'Hello, world!' | alias=myproc |
        | # Get result object   |                    |               |
        | ${result} =           | Get Process Result | myproc        |
        | Should Be Equal       | ${result.rc}       | ${0}          |
        | Should Be Equal       | ${result.stdout}   | Hello, world! |
        | Should Be Empty       | ${result.stderr}   |               |
        | # Get one attribute   |                    |               |
        | ${stdout} =           | Get Process Result | myproc        | stdout=true |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | # Multiple attributes |                    |               |
        | ${stdout}             | ${stderr} =        | Get Process Result |  myproc | stdout=yes | stderr=yes |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | Should Be Empty       | ${stderr}          |               |

        Although getting results of a previously executed process can be handy
        in general, the main use case for this keyword is returning results
        over the remote library interface. The remote interface does not
        support returning the whole result object, but individual attributes
        can be returned without problems.

        New in Robot Framework 2.8.2.
        """
        result = self._results[self._processes[handle]]
        if result.rc is None:
            raise RuntimeError('Getting results of unfinished processes '
                               'is not supported.')
        attributes = self._get_result_attributes(result, rc, stdout, stderr,
                                                 stdout_path, stderr_path)
        if not attributes:
            return result
        elif len(attributes) == 1:
            return attributes[0]
        return attributes

    def _get_result_attributes(self, result, *includes):
        attributes = (result.rc, result.stdout, result.stderr,
                      result.stdout_path, result.stderr_path)
        includes = (is_truthy(incl) for incl in includes)
        return tuple(attr for attr, incl in zip(attributes, includes) if incl)

    def switch_process(self, handle):
        """Makes the specified process the current `active process`.

        The handle can be an identifier returned by `Start Process` or
        the ``alias`` given to it explicitly.

        Example:
        | Start Process  | prog1    | alias=process1 |
        | Start Process  | prog2    | alias=process2 |
        | # currently active process is process2 |
        | Switch Process | process1 |
        | # now active process is process1 |
        """
        self._processes.switch(handle)

    def _process_is_stopped(self, process, timeout):
        stopped = lambda: process.poll() is not None
        max_time = time.time() + timeout
        while time.time() <= max_time and not stopped():
            time.sleep(min(0.1, timeout))
        return stopped()

    def split_command_line(self, args, escaping=False):
        """Splits command line string into a list of arguments.

        String is split from spaces, but argument surrounded in quotes may
        contain spaces in them. If ``escaping`` is given a true value, then
        backslash is treated as an escape character. It can escape unquoted
        spaces, quotes inside quotes, and so on, but it also requires using
        double backslashes when using Windows paths.

        Examples:
        | @{cmd} = | Split Command Line | --option "value with spaces" |
        | Should Be True | $cmd == ['--option', 'value with spaces'] |

        New in Robot Framework 2.9.2.
        """
        return cmdline2list(args, escaping=escaping)

    def join_command_line(self, *args):
        """Joins arguments into one command line string.

        In resulting command line string arguments are delimited with a space,
        arguments containing spaces are surrounded with quotes, and possible
        quotes are escaped with a backslash.

        If this keyword is given only one argument and that is a list like
        object, then the values of that list are joined instead.

        Example:
        | ${cmd} = | Join Command Line | --option | value with spaces |
        | Should Be Equal | ${cmd} | --option "value with spaces" |

        New in Robot Framework 2.9.2.
        """
        if len(args) == 1 and is_list_like(args[0]):
            args = args[0]
        return subprocess.list2cmdline(args)
 def __init__(self) -> None:
     """ Initialization. """
     self._connection: Optional[Session] = None
     self._cache = ConnectionCache()
class PysphereLibrary(object):
    """Robot Framework test library for VMWare interaction

    The library has the following main usages:
    - Identifying available virtual machines on a vCenter or
      ESXi host
    - Starting and stopping VMs
    - Shutting down, rebooting VM guest OS
    - Checking VM status
    - Reverting VMs to a snapshot
    - Retrieving basic VM properties

    This library is essentially a wrapper around Pysphere
    http://code.google.com/p/pysphere/ adding connection
    caching consistent with other Robot Framework libraries.
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = VERSION

    def __init__(self):
        """
        """
        self._connections = ConnectionCache()

    def open_pysphere_connection(self, host, user, password, alias=None):
        """Opens a pysphere connection to the given `host`
        using the supplied `user` and `password`.

        The new connection is made active and any existing connections
        are left open in the background.

        This keyword returns the index of the new connection which
        can be used later to switch back to it. Indices start from `1`
        and are reset when `Close All Pysphere Connections` is called.

        An optional `alias` can be supplied for the connection and used
        for switching between connections. See `Switch Pysphere
        Connection` for details.

        Example:
        | ${index}= | Open Pysphere Connection | my.vcenter.server.com | username | password | alias=myserver |
        """
        server = VIServer()
        server.connect(host, user, password)
        connection_index = self._connections.register(server, alias)
        logger.info("Pysphere connection opened to host %s" % host)
        return connection_index

    def is_connected_to_pysphere(self):
        return self._connections.current.is_connected()

    def switch_pysphere_connection(self, index_or_alias):
        """Switches the active connection by index of alias.

        `index_or_alias` is either a connection index (an integer)
        or alias (a string). Index can be obtained by capturing
        the return value from `Open Pysphere Connection` and
        alias can be set as a named variable with the same method.

        Example:
        | ${my_connection}= | Open Pysphere Connection | myhost | myuser | mypassword |
        | Open Pysphere Connection | myotherhost | myuser | mypassword | alias=otherhost |
        | Switch Pysphere Connection | ${my_connection} |
        | Power On Vm | myvm |
        | Switch Pysphere Connection | otherhost |
        | Power On Vm | myothervm |
        """
        old_index = self._connections.current_index
        if index_or_alias is not None:
            self._connections.switch(index_or_alias)
            logger.info("Pysphere connection switched to %s" % index_or_alias)
        else:
            logger.info("No index or alias given, pysphere connection has not been switched.")

    def close_pysphere_connection(self):
        """Closes the current pysphere connection.

        No other connection is made active by this keyword. use
        `Switch Pysphere Connection` to switch to another connection.

        Example:
        | ${my_connection}= | Open Pysphere Connection | myhost | myuser | mypassword |
        | Power On Vm | myvm |
        | Close Pysphere Connection |

        """
        self._connections.current.disconnect()
        logger.info("Connection closed, there will no longer be a current pysphere connection.")
        self._connections.current = self._connections._no_current

    def close_all_pysphere_connections(self):
        """Closes all active pysphere connections.

        This keyword is appropriate for use in test or suite
        teardown. The assignment of connection indices resets
        after calling this keyword, and the next connection
        opened will be allocated index `1`.

        Example:
        | ${my_connection}= | Open Pysphere Connection | myhost | myuser | mypassword |
        | Open Pysphere Connection | myotherhost | myuser | mypassword | alias=otherhost |
        | Switch Pysphere Connection | ${myserver} |
        | Power On Vm | myvm |
        | Switch Pysphere Connection | otherhost |
        | Power On Vm | myothervm |
        | [Teardown] | Close All Pysphere Connections |
        """
        self._connections.close_all(closer_method='disconnect')
        logger.info("All pysphere connections closed.")

    def get_vm_names(self):
        """Returns a list of all registered VMs for the
        currently active connection.
        """
        return self._connections.current.get_registered_vms()

    def get_vm_properties(self, name):
        """Returns a dictionary of the properties
        associated with the named VM.
        """
        vm = self._get_vm(name)
        return vm.get_properties(from_cache=False)

    def power_on_vm(self, name):
        """Power on the vm if it is not
        already running. This method blocks
        until the operation is completed.
        """
        if not self.vm_is_powered_on(name):
            vm = self._get_vm(name)
            vm.power_on()
            logger.info("VM %s powered on." % name)
        else:
            logger.info("VM %s was already powered on." % name)

    def power_off_vm(self, name):
        """Power off the vm if it is not
        already powered off. This method blocks
        until the operation is completed.
        """
        if not self.vm_is_powered_off(name):
            vm = self._get_vm(name)
            vm.power_off()
            logger.info("VM %s was powered off." % name)
        else:
            logger.info("VM %s was already powered off." % name)

    def reset_vm(self, name):
        """Perform a reset on the VM. This
        method blocks until the operation is
        completed.
        """
        vm = self._get_vm(name)
        vm.reset()
        logger.info("VM %s reset." % name)

    def shutdown_vm_os(self, name):
        """Initiate a shutdown in the guest OS
        in the VM, returning immediately.
        """
        vm = self._get_vm(name)
        vm.shutdown_guest()
        logger.info("VM %s shutdown initiated." % name)

    def reboot_vm_os(self, name):
        """Initiate a reboot in the guest OS
        in the VM, returning immediately.
        """
        vm = self._get_vm(name)
        vm.reboot_guest()
        logger.info("VM %s reboot initiated." % name)

    def vm_is_powered_on(self, name):
        """Returns true if the VM is in the
        powered on state.
        """
        vm = self._get_vm(name)
        return vm.is_powered_on()

    def vm_is_powered_off(self, name):
        """Returns true if the VM is in the
        powered off state.
        """
        vm = self._get_vm(name)
        return vm.is_powered_off()

    def vm_wait_for_tools(self, name, timeout=15):
        """Returns true when the VM tools are running.  If the tools are not
        running within the specified timeout, a VIException is raised with
        the TIME_OUT FaultType.
        """
        vm = self._get_vm(name)
        return vm.wait_for_tools(timeout)

    def revert_vm_to_snapshot(self, name, snapshot_name=None):
        """Revert the named VM to a snapshot. If `snapshot_name`
        is supplied it is reverted to that snapshot, otherwise
        it is reverted to the current snapshot. This method
        blocks until the operation is completed.
        """
        vm = self._get_vm(name)
        if snapshot_name is None:
            vm.revert_to_snapshot()
            logger.info("VM %s reverted to current snapshot." % name)
        else:
            vm.revert_to_named_snapshot(snapshot_name)
            logger.info("VM %s reverted to snapshot %s." % name, snapshot_name)

    def _get_vm(self, name):
        connection = self._connections.current
        if isinstance(name, unicode):
            name = name.encode("utf8")
        return connection.get_vm_by_name(name)
Esempio n. 21
0
 def __init__(self):
     ConnectionCache.__init__(self, no_current_msg='No current application')
     self._closed = set()
Esempio n. 22
0
 def __init__(self):
     self._started_processes = ConnectionCache()
     self._logs = dict()
     self._tempdir = tempfile.mkdtemp(suffix="processlib")
Esempio n. 23
0
class Process(object):
    """Robot Framework test library for running processes.

    This library utilizes Python's
    [http://docs.python.org/2.7/library/subprocess.html|subprocess]
    module and its
    [http://docs.python.org/2.7/library/subprocess.html#subprocess.Popen|Popen]
    class.

    The library has following main usages:

    - Running processes in system and waiting for their completion using
      `Run Process` keyword.
    - Starting processes on background using `Start Process`.
    - Waiting started process to complete using `Wait For Process` or
      stopping them with `Terminate Process` or `Terminate All Processes`.

    This library is new in Robot Framework 2.8.

    == Table of contents ==

    - `Specifying command and arguments`
    - `Process configuration`
    - `Active process`
    - `Stopping processes`
    - `Result object`
    - `Using with OperatingSystem library`
    - `Example`

    = Specifying command and arguments =

    Both `Run Process` and `Start Process` accept the command to execute
    and all arguments passed to it as separate arguments. This is convenient
    to use and also allows these keywords to automatically escape possible
    spaces and other special characters in the command or arguments.

    When `running processes in shell`, it is also possible to give the
    whole command to execute as a single string. The command can then
    contain multiple commands, for example, connected with pipes. When
    using this approach the caller is responsible on escaping.

    Examples:
    | `Run Process` | ${progdir}${/}prog.py        | first arg | second         |
    | `Run Process` | script1.sh arg && script2.sh | shell=yes | cwd=${progdir} |

    = Process configuration =

    `Run Process` and `Start Process` keywords can be configured using
    optional `**configuration` keyword arguments. Available configuration
    arguments are listed below and discussed further in sections afterwards.

    | *Name*     | *Explanation*                                         |
    | shell      | Specifies whether to run the command in shell or not  |
    | cwd        | Specifies the working directory.                      |
    | env        | Specifies environment variables given to the process. |
    | env:<name> | Overrides the named environment variable(s) only.     |
    | stdout     | Path of a file where to write standard output.        |
    | stderr     | Path of a file where to write standard error.         |
    | alias      | Alias given to the process.                           |

    Configuration must be given after other arguments passed to these keywords
    and must use syntax `name=value`.

    == Running processes in shell ==

    The `shell` argument specifies whether to run the process in a shell or
    not. By default shell is not used, which means that shell specific
    commands, like `copy` and `dir` on Windows, are not available.

    Giving the `shell` argument any non-false value, such as `shell=True`,
    changes the program to be executed in a shell. It allows using the shell
    capabilities, but can also make the process invocation operating system
    dependent.

    When using a shell it is possible to give the whole command to execute
    as a single string. See `Specifying command and arguments` section for
    more details.

    == Current working directory ==

    By default the child process will be executed in the same directory
    as the parent process, the process running tests, is executed. This
    can be changed by giving an alternative location using the `cwd` argument.
    Forward slashes in the given path are automatically converted to
    backslashes on Windows.

    `Standard output and error streams`, when redirected to files,
    are also relative to the current working directory possibly set using
    the `cwd` argument.

    Example:
    | `Run Process` | prog.exe | cwd=${ROOT}/directory | stdout=stdout.txt |

    == Environment variables ==

    By default the child process will get a copy of the parent process's
    environment variables. The `env` argument can be used to give the
    child a custom environment as a Python dictionary. If there is a need
    to specify only certain environment variable, it is possible to use the
    `env:<name>` format to set or override only that named variables. It is
    also possible to use these two approaches together.

    Examples:
    | `Run Process` | program | env=${environ} |
    | `Run Process` | program | env:PATH=%{PATH}${:}${PROGRAM DIR} |
    | `Run Process` | program | env=${environ} | env:EXTRA=value   |

    == Standard output and error streams ==

    By default processes are run so that their standard output and standard
    error streams are kept in the memory. This works fine normally,
    but if there is a lot of output, the output buffers may get full and
    the program could hang.

    To avoid output buffers getting full, it is possible to use `stdout`
    and `stderr` arguments to specify files on the file system where to
    redirect the outputs. This can also be useful if other processes or
    other keywords need to read or manipulate the outputs somehow.

    Given `stdout` and `stderr` paths are relative to the `current working
    directory`. Forward slashes in the given paths are automatically converted
    to backslashes on Windows.

    As a special feature, it is possible to redirect the standard error to
    the standard output by using `stderr=STDOUT`.

    Regardless are outputs redirected to files or not, they are accessible
    through the `result object` returned when the process ends.

    Examples:
    | ${result} = | `Run Process` | program | stdout=${TEMPDIR}/stdout.txt | stderr=${TEMPDIR}/stderr.txt |
    | `Log Many`  | stdout: ${result.stdout} | stderr: ${result.stderr} |
    | ${result} = | `Run Process` | program | stderr=STDOUT |
    | `Log`       | all output: ${result.stdout} |

    *Note:* The created output files are not automatically removed after
    the test run. The user is responsible to remove them if needed.

    == Alias ==

    A custom name given to the process that can be used when selecting the
    `active process`.

    Example:
    | `Start Process` | program | alias=example |

    = Active process =

    The test library keeps record which of the started processes is currently
    active. By default it is latest process started with `Start Process`,
    but `Switch Process` can be used to select a different one.

    The keywords that operate on started processes will use the active process
    by default, but it is possible to explicitly select a different process
    using the `handle` argument. The handle can be the identifier returned by
    `Start Process` or an explicitly given `alias`.

    = Stopping processes =

    Started processed can be stopped using `Terminate Process` and
    `Terminate All Processes`. The former is used for stopping a selected
    process, and the latter to make sure all processes are stopped, for
    example, in a suite teardown.

    Both keywords use `subprocess`
    [http://docs.python.org/2.7/library/subprocess.html#subprocess.Popen.terminate|terminate()]
    method by default, but can be configured to use
    [http://docs.python.org/2.7/library/subprocess.html#subprocess.Popen.kill|kill()]
    instead.

    Because both `terminate()` and `kill()` methods were added to `subprocess`
    in Python 2.6, stopping processes does not work with Python or Jython 2.5.
    Unfortunately at least beta releases of Jython 2.7
    [http://bugs.jython.org/issue1898|do not seem to support it either].

    Examples:
    | `Terminate Process` | kill=True |
    | `Terminate All Processes` |

    = Result object =

    `Run Process` and `Wait For Process` keywords return a result object
    that contains information about the process execution as its attibutes.
    What is available is documented in the table below.

    | *Attribute* | *Explanation*                             |
    | rc          | Return code of the process as an integer. |
    | stdout      | Contents of the standard output stream.   |
    | stderr      | Contents of the standard error stream.    |
    | stdout_path | Path where stdout was redirected or `None` if not redirected. |
    | stderr_path | Path where stderr was redirected or `None` if not redirected. |

    Example:
    | ${result} =            | `Run Process`         | program               |
    | `Should Be Equal As Integers` | ${result.rc}   |                       |
    | `Should Match`         | ${result.stdout}      | Some t?xt*            |
    | `Should Be Empty`      | ${result.stderr}      |                       |
    | ${stdout} =            | `Get File`            | ${result.stdout_path} |
    | `File Should Be Empty` | ${result.stderr_path} |                       |
    | `Should Be Equal`      | ${result.stdout}      | ${stdout}             |

    = Using with OperatingSystem library =

    The OperatingSystem library also contains keywords for running processes.
    They are not as flexible as the keywords provided by this library, and
    thus not recommended to be used anymore. They may eventually even be
    deprecated.

    There is a name collision because both of these libraries have
    `Start Process` and `Switch Process` keywords. This is handled so that
    if both libraries are imported, the keywords in the Process library are
    used by default. If there is a need to use the OperatingSystem variants,
    it is possible to use `OperatingSystem.Start Process` syntax or use
    the `BuiltIn` keyword `Set Library Search Order` to change the priority.

    Other keywords in the OperatingSystem library can be used freely with
    keywords in the Process library.

    = Example =

    | ***** Settings *****
    | Library    Process
    | Suite Teardown    `Terminate All Processes`    kill=True
    |
    | ***** Test Cases *****
    | Example
    |     `Start Process`    program    arg1   arg2    alias=First
    |     ${handle} =    `Start Process`    command.sh arg | command2.sh   shell=True    cwd=/path
    |     ${result} =    `Run Process`    ${CURDIR}/script.py
    |     `Should Not Contain`    ${result.stdout}    FAIL
    |     `Terminate Process`    ${handle}
    |     ${result} =    `Wait For Process`    First
    |     `Should Be Equal As Integers`   ${result.rc}    0
    """
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = get_version()

    def __init__(self):
        self._processes = ConnectionCache('No active process.')
        self._results = {}

    def run_process(self, command, *arguments, **configuration):
        """Runs a process and waits for it to complete.

        See `Specifying command and arguments` and `Process configuration`
        for more information about the arguments.

        Returns a `result object` containing information about the execution.

        This command does not change the `active process`.
        """
        current = self._processes.current
        try:
            handle = self.start_process(command, *arguments, **configuration)
            return self.wait_for_process(handle)
        finally:
            self._processes.current = current

    def start_process(self, command, *arguments, **configuration):
        """Starts a new process on background.

        See `Specifying command and arguments` and `Process configuration`
        for more information about the arguments.

        Makes the started process new `active process`. Returns an identifier
        that can be used as a handle to active the started process if needed.
        """
        config = ProcessConfig(**configuration)
        executable_command = self._cmd(arguments, command, config.shell)
        logger.info('Starting process:\n%s' % executable_command)
        logger.debug('Process configuration:\n%s' % config)
        process = subprocess.Popen(executable_command,
                                   stdout=config.stdout_stream,
                                   stderr=config.stderr_stream,
                                   stdin=subprocess.PIPE,
                                   shell=config.shell,
                                   cwd=config.cwd,
                                   env=config.env,
                                   universal_newlines=True)
        self._results[process] = ExecutionResult(process,
                                                 config.stdout_stream,
                                                 config.stderr_stream)
        return self._processes.register(process, alias=config.alias)

    def _cmd(self, args, command, use_shell):
        command = [encode_to_system(item) for item in [command] + list(args)]
        if not use_shell:
            return command
        if args:
            return subprocess.list2cmdline(command)
        return command[0]

    def is_process_running(self, handle=None):
        """Checks is the process running or not.

        If `handle` is not given, uses the current `active process`.

        Returns `True` if the process is still running and `False` otherwise.
        """
        return self._processes[handle].poll() is None

    def process_should_be_running(self, handle=None,
                                  error_message='Process is not running.'):
        """Verifies that the process is running.

        If `handle` is not given, uses the current `active process`.

        Fails if the process has stopped.
        """
        if not self.is_process_running(handle):
            raise AssertionError(error_message)

    def process_should_be_stopped(self, handle=None,
                                  error_message='Process is running.'):
        """Verifies that the process is not running.

        If `handle` is not given, uses the current `active process`.

        Fails if the process is still running.
        """
        if self.is_process_running(handle):
            raise AssertionError(error_message)

    def wait_for_process(self, handle=None):
        """Waits for the process to complete.

        If `handle` is not given, uses the current `active process`.

        Returns a `result object` containing information about the execution.
        """
        process = self._processes[handle]
        result = self._results[process]
        logger.info('Waiting for process to complete.')
        result.rc = process.wait() or 0
        logger.info('Process completed.')
        return result

    def terminate_process(self, handle=None, kill=False):
        """Terminates the process.

        If `handle` is not given, uses the current `active process`.

        See `Stopping process` for more details.
        """
        process = self._processes[handle]
        try:
            terminator = process.kill if kill else process.terminate
        except AttributeError:
            raise RuntimeError('Terminating processes is not supported '
                               'by this interpreter version.')
        logger.info('Killing process.' if kill else 'Terminating process.')
        try:
            terminator()
        except OSError:
            if process.poll() is None:
                raise
            logger.debug('Ignored OSError because process was stopped.')

    def terminate_all_processes(self, kill=True):
        """Terminates all still running processes started by this library.

        See `Stopping processes` for more details.
        """
        for handle in range(1, len(self._processes) + 1):
            if self.is_process_running(handle):
                self.terminate_process(handle, kill=kill)
        self.__init__()

    def get_process_id(self, handle=None):
        """Returns the process ID (pid) of the process.

        If `handle` is not given, uses the current `active process`.

        Returns the pid assigned by the operating system as an integer.
        Note that with Jython, at least with the 2.5 version, the returned
        pid seems to always be `None`.

        The pid is not the same as the identifier returned by
        `Start Process` that is used internally by this library.
        """
        return self._processes[handle].pid

    def get_process_object(self, handle=None):
        """Return the underlying `subprocess.Popen`  object.

        If `handle` is not given, uses the current `active process`.
        """
        return self._processes[handle]

    def switch_process(self, handle):
        """Makes the specified process the current `active process`.

        The handle can be an identifier returned by `Start Process` or
        the `alias` given to it explicitly.

        Example:
        | `Start Process` | prog1 | alias=process1 |
        | `Start Process` | prog2 | alias=process2 |
        | # currently active process is process2 |
        | `Switch Process` | process1 |
        | # now active process is process 1 |
        """
        self._processes.switch(handle)
 def __init__(self):
     self._session=None
     self._cache=ConnectionCache('No sessions created')
Esempio n. 25
0
class SSHLibrary(DeprecatedSSHLibraryKeywords):
    """Robot Framework test library for SSH and SFTP.

    SSHLibrary works with both Python and Jython interpreters.

    To use SSHLibrary with Python, you must first install paramiko SSH
    implementation[1] and its dependencies.  For Jython, you must have jar
    distribution of Trilead SSH implementation[2] in the
    CLASSPATH during test execution

    | [1] http://www.lag.net/paramiko/
    | [2] http://www.trilead.com/Products/Trilead_SSH_for_Java/

    The library supports multiple connections to different hosts.

    A connection must always be opened using `Open Connection` before the
    other keywords work.

    For executing commands, there are two possibilities:

    1. `Execute Command` or `Start Command`. These keywords open a new session
    using the connection, possible state changes are not preserved.

    2. Keywords `Write` and `Read XXX` operate in an interactive shell, which
    means that changes to state are visible to next keywords. Note that in
    interactive mode, a prompt must be set before using any of the
    Write-keywords. Prompt can be set either on `library importing` or
    when a new connection is opened using `Open Connection`.
    """
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = __version__

    def __init__(self, timeout=3, newline='LF', prompt=None,
                 loglevel='INFO'):
        """SSH Library allows some import time configuration.

        `timeout`, `newline` and `prompt` set default values for new
        connections opened with `Open Connection`. The default values may
        later be changed using `Set Default Configuration` and settings
        of a single connection with `Set Client Configuration`.

        `loglevel` sets the default log level used to log return values of
        `Read Until` variants. It can also be later changed using `Set
        Default Configuration`.

        Examples:
        | Library | SSHLibrary | # use default values |
        | Library | SSHLibrary | timeout=10 | prompt=> |
        """
        self._cache = ConnectionCache()
        self._config = DefaultConfig(timeout, newline, prompt, loglevel)

    @property
    def ssh_client(self):
        return self._cache.current

    def set_default_configuration(self, timeout=None, newline=None,
                                  prompt=None, loglevel=None):
        """Update the default configuration values.

        Only parameters whose value is other than `None` are updated.

        Example:
            | Set Default Configuration | newline=CRLF | prompt=$ |
        """
        self._config.update(timeout=timeout, newline=newline, prompt=prompt,
                            loglevel=loglevel)

    def set_client_configuration(self, timeout=None, newline=None, prompt=None,
                                 term_type='vt100', width=80, height=24):
        """Update the client configuration values.

        Works on the currently selected connection. At least one connection
        must have been opened using `Open Connection`.

        Only parameters whose value is other than `None` are updated.

        Example:
            | Set Client Configuration | term_type=ansi | timeout=2 hours |
        """
        self.ssh_client.config.update(timeout=timeout, newline=newline,
                                      prompt=prompt, term_type=term_type,
                                      width=width, height=height)

    def open_connection(self, host, alias=None, port=22, timeout=None,
                        newline=None, prompt=None, term_type='vt100',
                        width=80, height=24):
        """Opens a new SSH connection to given `host` and `port`.

        Possible already opened connections are cached.

        Returns the index of this connection which can be used later to switch
        back to it. Indexing starts from 1 and is reset when `Close All`
        keyword is used.

        Optional `alias` is a name for the connection and it can be used for
        switching between connections similarly as the index. See `Switch
        Connection` for more details about that.

        If `timeout`, `newline` or `prompt` are not given, the default values
        set in `library importing` are used. See also `Set Default
        Configuration`.

        Starting from SSHLibrary 1.1, a shell session is also opened
        automatically by this keyword. `term_type` defines the terminal type
        for this shell, and `width` and `height` can be configured to control
        the virtual size of it.

        Client configuration options other than `host`, `port` and `alias`
        can be later updated using `Set Client Configuration`.

        Examples:
        | Open Connection | myhost.net |
        | Open Connection | yourhost.com | alias=2nd conn | port=23 |prompt=# |
        | Open Connection | myhost.net | term_type=ansi | width=40 |
        | ${id} =         | Open Connection | myhost.net |
        """
        timeout = timeout or self._config.timeout
        newline = newline or self._config.newline
        prompt = prompt or self._config.prompt
        client = SSHClient(host, alias, port, timeout, newline, prompt,
                           term_type, width, height)
        return self._cache.register(client, alias)

    def switch_connection(self, index_or_alias):
        """Switches between active connections using index or alias.

        Index is got from `Open Connection` and alias can be given to it.

        Returns the index of previous connection, which can be used to restore
        the connection later.

        Example:

        | Open Connection       | myhost.net   |          |
        | Login                 | john         | secret   |
        | Execute Command       | some command |          |
        | Open Connection       | yourhost.com | 2nd conn |
        | Login                 | root         | password |
        | Start Command         | another cmd  |          |
        | Switch Connection     | 1            | # index  |
        | Execute Command       | something    |          |
        | Switch Connection     | 2nd conn     | # alias  |
        | Read Command Output   |              |          |
        | Close All Connections |              |          |

        Above example expects that there was no other open connections when
        opening the first one because it used index '1' when switching to it
        later. If you aren't sure about that you can store the index into
        a variable as below.

        | ${id} =            | Open Connection | myhost.net |
        | # Do something ... |
        | Switch Connection  | ${id}           |            |
        """
        old_index = self._cache.current_index
        self._cache.switch(index_or_alias)
        return old_index

    def close_all_connections(self):
        """Closes all open connections and empties the connection cache.

        After this keyword indices returned by `Open Connection` start from 1.

        This keyword ought to be used in test or suite teardown to make sure
        all connections are closed.
        """
        self._cache.close_all()

    def get_connections(self):
        """Return information about opened connections.

        The return value is a list of objects that describe the connection.
        These objects have attributes that correspond to the argument names
        of `Open Connection`.

        Connection information is also logged.

        Example:
        | Open Connection | somehost  | prompt=>> |
        | Open Connection | otherhost | timeout=5 minutes |
        | ${conn1} | ${conn2}= | Get Connections |
        | Should Be Equal | ${conn1.host} | somehost |
        | Should Be Equal | ${conn2.timeout} | 5 minutes |
        """
        # TODO: could the ConnectionCache be enhanced to be iterable?
        configs = [c.config for c in self._cache._connections]
        for c in configs:
            self._log(str(c))
        return configs

    def enable_ssh_logging(self, logfile):
        """Enables logging of SSH protocol output to given `logfile`

        `logfile` can be relative or absolute path to a file that is writable
        by current user. In case that it already exists, it will be
        overwritten.

        Note that this keyword only works with Python, e.g. when executing the
        tests with `pybot`.
        """
        if SSHClient.enable_logging(logfile):
            self._log('SSH log is written to <a href="%s">file</a>.' % logfile,
                      'HTML')

    def close_connection(self):
        """Closes the currently active connection."""
        self.ssh_client.close()

    def login(self, username, password):
        """Logs in to SSH server with given user information.

        Reads and returns available output. If prompt is set, everything until
        the prompt is returned.

        Example:
        | Login | john | secret |
        """
        return self._login(self.ssh_client.login, username, password)

    def login_with_public_key(self, username, keyfile, password):
        """Logs into SSH server with using key-based authentication.

        `username` is the username on the remote system.
        `keyfile` is a path to a valid OpenSSH private key file.
        `password` is used to unlock `keyfile` if unlocking is required.

        Reads and returns available output. If prompt is set, everything until
        the prompt is returned.
        """
        return self._login(self.ssh_client.login_with_public_key, username,
                           keyfile, password)

    def _login(self, login_method, username, *args):
        self._info("Logging into '%s:%s' as '%s'."
                   % (self.ssh_client.host, self.ssh_client.port, username))
        try:
            return login_method(username, *args)
        except SSHClientException, e:
            raise RuntimeError(e)
class WinRMLibrary(object):
    """
    Robot Framework library for Windows Remote Management, based on pywinrm.
    
    == Enable Windows Remote Shell ==
    - [ http://support.microsoft.com/kb/555966 | KB-555966 ]
    - Execute on windows server:
     
     | winrm set winrm/config/client/auth @{Basic="true"}
     | winrm set winrm/config/service/auth @{Basic="true"}
     | winrm set winrm/config/service @{AllowUnencrypted="true"}

    == Dependence ==
    | pywinrm | https://pypi.python.org/pypi/pywinrm | 
    | robot framework | http://robotframework.org |
    """

    ROBOT_LIBRARY_SCOPE='GLOBAL'

    def __init__(self):
        self._session=None
        self._cache=ConnectionCache('No sessions created')

    def create_session (self, alias, hostname, login, password):
        """
        Create session with windows host. 
        
        Does not support domain authentification.
        
        *Args:*\n
        _alias_ - robot framework alias to identify the session\n
        _hostname_ -  windows hostname (not IP)\n
        _login_ - windows local login\n
        _password_ - windows local password
        
        *Returns:*\n
        Session index
        
        *Example:*\n
        | Create Session  |  server  |  windows-host |  Administrator  |  1234567890 |
        """

        logger.debug ('Connecting using : hostname=%s, login=%s, password=%s '%(hostname, login, password))
        self._session=winrm.Session(hostname, (login, password))
        return self._cache.register(self._session, alias)

    def run_cmd (self, alias, command, params=None):
        """
        Execute command on remote mashine.
        
        *Args:*\n
        _alias_ - robot framework alias to identify the session\n
        _command_ -  windows command\n
        _params_ - lists of command's parameters
        
        *Returns:*\n
        Result object with methods: status_code, std_out, std_err.
        
        *Example:*\n
        | ${params}=  | Create List  |  "/all" |
        | ${result}=  |  Run cmd  |  server  |  ipconfig  |  ${params} |
        | Log  |  ${result.status_code} |
        | Log  |  ${result.std_out} |
        | Log  |  ${result.std_err} |
        =>\n
        | 0 
        | Windows IP Configuration
        |    Host Name . . . . . . . . . . . . : WINDOWS-HOST
        |    Primary Dns Suffix  . . . . . . . :
        |    Node Type . . . . . . . . . . . . : Hybrid
        |    IP Routing Enabled. . . . . . . . : No
        |    WINS Proxy Enabled. . . . . . . . : No
        | 
        """

        if params is not None:
            log_cmd=command+' '+' '.join(params)
        else:
            log_cmd=command
        logger.info ('Run command on server with alias "%s": %s '%(alias, log_cmd))
        self._session=self._cache.switch(alias)
        result=self._session.run_cmd (command, params)
        return result

    def run_ps (self, alias, script):
        """
        Run power shell script on remote mashine.
        
        *Args:*\n
         _alias_ - robot framework alias to identify the session\n
         _script_ -  power shell script\n
        
        *Returns:*\n
         Result object with methods: status_code, std_out, std_err.
        
        *Example:*\n
        
        | ${result}=  |  Run ps  |  server  |  get-process iexplore|select -exp ws|measure-object -sum|select -exp Sum |
        | Log  |  ${result.status_code} |
        | Log  |  ${result.std_out} |
        | Log  |  ${result.std_err} |
        =>\n
        | 0                         
        | 56987648                         
        |              
        """

        logger.info ('Run power shell script on server with alias "%s": %s '%(alias, script))
        self._session=self._cache.switch(alias)
        result=self._session.run_ps (script)
        return result

    def delete_all_sessions(self):
        """ Removes all sessions with windows hosts"""

        self._cache.empty_cache()
 def __init__(self):
     self._connection = None
     self._cache = ConnectionCache()    # использование кеша Robot Framework для одновременной работы с несколькими соединениями
Esempio n. 28
0
class SOAP(object):

    def __init__(self):
        self._xml = XML()
        self._builtin = BuiltIn()
        self._cache = ConnectionCache()

    def create_soap_client(self, alias, wsdl=None, endpoint=None):
        """
        Creates SOAP client with specified alias.

        Arguments:
        | alias | client alias |
        | wsdl | path or url to service wsdl |
        | endpoint | url for service under test |

        Example usage:
        | Create Soap Client | client_alias | wsdl=path_to_wsdl${/}ws_example.wsdl | endpoint=http://localhost:8080/ |
        """
        session = Session()
        session.verify = False
        if wsdl:
            wsdl = self._get_wsdl(session, wsdl)
        client = SOAPClient(session, wsdl, endpoint)
        self._cache.register(client, alias)

    def call_soap_method(self, alias, name, message, replace=None, endpoint=None, expect_fault=False, xml=True):
        """
        Call SOAP method.

        Arguments:
        | alias | client alias |
        | name | method name |
        | message | path to SOAP message or SOAP message |
        | replace | dictionary of old_value: new_value pairs for replace in message |
        | endpoint | url for service under test, rewrites client endpoint |
        | expect_fault | if True, not raise exception when receive fault |
        | xml | return type, if True - return xml object (XML library format), False - string |

        Example usage:
        | ${replace} | Create Dictionary | _id_ | 64 |
        | ${response} | Call Soap Method | client_alias | getUserName | path_to_soap_message${/}example.xml | replace=${replace} | expect_fault=False | xml=True |
        | Element Should Exist | ${response} | .//{http://www.example.ru/example}requestResult |
        """
        client = self._cache.switch(alias)
        if endpoint:
            client.endpoint = endpoint
        try:
            ET.fromstring(message)
        except ET.ParseError:
            if not path.isfile(message):
                raise IOError("File not found or message is not well-formed" % message)
            message = open(message, mode="r", encoding="utf-8").read()
        if replace:
            for k, v in replace.items():
                message = message.replace(k, v)
        status_code, response = self._call(client, name, message)
        expect_fault = self._builtin.convert_to_boolean(expect_fault)
        is_fault = self._is_fault(status_code)
        if is_fault and not expect_fault:
            raise AssertionError("The server did not raise a fault")
        elif expect_fault and not is_fault:
            raise AssertionError("The server not raise a fault")
        if self._builtin.convert_to_boolean(xml):
            return self._xml.parse_xml(response, keep_clark_notation=True)
        else:
            return response

    @staticmethod
    def _get_wsdl(session, url_or_path):
        if not urlparse(url_or_path).scheme:
            if not path.isfile(url_or_path):
                raise IOError("File '%s' not found" % url_or_path)
            wsdl = open(url_or_path, mode="r", encoding="utf-8").read()
        else:
            response = session.get(url_or_path)
            if response.status_code != 200:
                raise ConnectionError("Server not found or resource '%s' is not available" % url_or_path)
            wsdl = response.content.decode("utf-8")
        return wsdl

    @staticmethod
    def _is_fault(status_code):
        if status_code == 200:
            return False
        else:
            return True

    def _get_soap_action(self, wsdl, name):
        root = self._xml.parse_xml(wsdl)
        xpath = ".//*[@name='%s']/operation" % name
        return self._xml.get_element_attribute(root, "soapAction", xpath=xpath)

    def _call(self, client, name, message):
        if client.wsdl:
            action = self._get_soap_action(client.wsdl, name)
        else:
            action = ""
        headers = {"Accept-Encoding": "gzip,deflate", "Content-Type": "text/xml;charset=UTF-8",
                   "Connection": "Keep-Alive", "SOAPAction": action}
        logger.info("Execution '%s' soap method" % name)
        response = client.session.post(client.endpoint, headers=headers, data=message.encode("utf-8"))
        return response.status_code, response.content.decode("utf-8")
class ZookeeperManager(object):
    """
    Robot Framework library for working with Apache Zookeeper.
    Based on Kazoo Python library.
    All errors described in "Returns" section are kazoo exceptions (kazoo.exceptions).

    == Dependencies ==
    | robot framework   | http://robotframework.org           |
    | kazoo             | https://github.com/python-zk/kazoo  |
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self):
        """Library initialization.
        Robot Framework ConnectionCache() class is prepared for working with concurrent connections."""

        self.bi = BuiltIn()
        self._connection = None
        self._cache = ConnectionCache()
        self._connections = []

    def connect_to_zookeeper(self,
                             hosts,
                             timeout=10,
                             alias=None,
                             zk_encoding='utf-8'):
        """
        Connection to Zookeeper

        *Args:*\n
            _hosts_ - comma-separated list of hosts to connect.\n
            _timeout_ - connection timeout in seconds. Default timeout is 10 seconds.\n
            _alias_ - alias of zookeeper connection. \n
            _zk_encoding_ - zookeeper encoding.\n

        *Returns:*\n
            Returns the index of the new connection. The connection is set as active.

        *Example:*\n
            | Connect To Zookeeper | host1:2181, host2 | 25 |
        """

        self._connection = ZookeeperConnection(hosts,
                                               timeout,
                                               zk_encoding=zk_encoding)
        self._connection.start()
        self._connections.append(self._connection)
        return self._cache.register(self._connection, alias)

    def disconnect_from_zookeeper(self):
        """
        Close current connection to Zookeeper

        *Example:*\n
            | Connect To Zookeeper | 127.0.0.1: 2181 |
            | Disconnect From Zookeeper |
        """

        self._connection.stop()
        self._connection.close()

    def switch_zookeeper_connection(self, index_or_alias):
        """
        Switch to another existing Zookeeper connection using its index or alias.\n

        The connection index is obtained on creating connection.
        Connection alias is optional and can be set at connecting to Zookeeper [#Connect To Zookeeper|Connect To Zookeeper].

        *Args:*\n
            _index_or_alias_ - index or alias of zookeeper connection. \n

        *Returns:*\n
            Index of previous connection.

        *Example:*\n
            | Connect To Zookeeper | host1:2181 | alias=zoo1 |
            | Switch Zookeeper Connection | zoo1 |
            | Get Value | /ps/config |
            | Connect To Zookeeper | host2:2181 | alias=zoo2 |
            | Switch Zookeeper Connection | zoo2 |
            | Get Value | /ps/config |
        """

        old_index = self._cache.current_index
        self._connection = self._cache.switch(index_or_alias)
        return old_index

    def close_all_zookeeper_connections(self):
        """
        Close all Zookeeper connections that were opened.
        After calling this keyword connection index returned by opening new connections [#Connect To Zookeeper |Connect To Zookeeper],
        starts from 1.
        You should not use [#Close all Zookeeper Connections | Close all Zookeeper Connections] and [#Disconnect From Zookeeper | Disconnect From Zookeeper]
        together.

        *Example:*\n
            | Connect To Zookeeper | 127.0.0.1: 2181 |
            | Close All Zookeeper Connections |
        """

        self._connection = self._cache.close_all(closer_method='stop')
        self._connection = self._cache.close_all(closer_method='close')

    def create_node(self, path, value='', force=False):
        """
        Create a node with a value.

        *Args:*\n
            _path_ - node path.\n
            _value_ - node value. Default is an empty string.\n
            _force_ - if TRUE parent path will be created. Default value is FALSE.\n

        *Raises:*\n
            _NodeExistError_ - node already exists.\n
            _NoNodeError_ - parent node doesn't exist.\n
            _NoChildrenForEphemeralsError_ - parent node is an ephemeral mode.\n
            _ZookeeperError_ - value is too large or server returns a non-zero error code.\n

        *Example:*\n
            | Create Node  |  /my/favorite/node  |  my_value |  ${TRUE} |
        """

        string_value = value.encode(self._connection.zk_encoding)
        self._connection.create(path, string_value, None, False, False, force)

    def delete_node(self, path, force=False):
        """
        Delete a node.

        *Args:*\n
            _path_ - node path.\n
            _force_ - if TRUE and node exists - recursively delete a node with all its children,
                      if TRUE and node doesn't exists - error won't be raised. Default value is FALSE.

        *Raises:*\n
            _NoNodeError_ - node doesn't exist.\n
            _NotEmptyError_ - node has children.\n
            _ZookeeperError_ - server returns a non-zero error code.
        """

        try:
            self._connection.delete(path, -1, force)
        except NoNodeError:
            if force:
                pass
            else:
                raise

    def exists(self, path):
        """
        Check if a node exists.

        *Args:*\n
            _path_ - node path.

        *Returns:*\n
            TRUE if a node exists, FALSE in other way.
        """

        node_stat = self._connection.exists(path)
        if node_stat is not None:
            # Node exists
            return True
        else:
            # Node doesn't exist
            return False

    def set_value(self, path, value, force=False):
        """
        Set the value of a node.

        *Args:*\n
            _path_ - node path.\n
            _value_ - new value.\n
            _force_ - if TRUE path will be created. Default value is FALSE.\n

        *Raises:*\n
            _NoNodeError - node doesn't exist.\n
            _ZookeeperError - value is too large or server returns non-zero error code.
        """

        string_value = value.encode(self._connection.zk_encoding)
        if force:
            self._connection.ensure_path(path)
        self._connection.set(path, string_value)

    def get_value(self, path):
        """
        Get the value of a node.

        *Args:*\n
            _path_ - node path.

        *Returns:*\n
            Value of a node.

        *Raises:*\n
            _NoNodeError_ - node doesn't exist.\n
            _ZookeeperError_ - server returns a non-zero error code.
        """

        value, _stat = self._connection.get(path)
        if PY3 and value is not None:
            return value.decode(self._connection.zk_encoding)
        return value

    def get_children(self, path):
        """
        Get a list of node's children.

        *Args:*\n
            _path_ - node path.

        *Returns:*\n
            List of node's children.

        *Raises:*\n
            _NoNodeError_ - node doesn't exist.\n
            _ZookeeperError_ - server returns a non-zero error code.
        """

        children = self._connection.get_children(path)
        return children

    def check_node_value_existence(self, path):
        """
        Check that value of node isn't empty.

        *Args:*\n
            _path_ - node path \n
        """
        value = self.get_value(path)
        if value in ("{}", "[]", "null"):
            raise Exception("Value of Zookeeper node {0} is equal {1}".format(
                path, value))
        elif not value:
            raise Exception(
                "Value of Zookeeper node {0} is empty".format(path))
        else:
            BuiltIn().log("Zookeeper node {0} value is exist: '{1}' ".format(
                path, value))

    def check_json_value(self, path, expr):
        """
        Check that value of node matches JSONSelect expression

        *Args:*\n
            _path_ - node path \n
            _expr_ - JSONSelect expression \n

        *Example:*\n
            | Connect To Zookeeper | 127.0.0.1: 2181 |
            | Check Json Value | /my/favorite/node | .color:val("blue") |
        """

        json_string = self.get_value(path)
        JsonValidator().element_should_exist(json_string, expr)

    def check_node_value(self, path, value):
        """
        Check that value of the node is equal to the expected value

        *Args:*\n
            _path_ - node path \n
            _value_ - expected value \n

        *Example:*\n
            | Connect To Zookeeper | 127.0.0.1: 2181 |
            | Check Node Value | /my/favorite/node | 12345 |
        """

        node_value = self.get_value(path)
        if node_value == value:
            BuiltIn().log(
                "Zookeeper node value is equal expected value: '{0}' ".format(
                    value))
        else:
            raise Exception(
                "Zookeeper node value is not equal expected value: '{0}' != '{1}' "
                .format(node_value, value))

    def safe_get_children(self, path):
        """
        Check that node exists and return its child nodes.

        *Args:*\n
            _path_ - node path \n

        *Returns:*\n
            List of node's children \n

        *Example:*\n
            | Connect To Zookeeper | 127.0.0.1: 2181 |
            | Safe Get Children | /my/favorite/node |
        """
        if self.exists(path):
            children = self.get_children(path)
            if children:
                return children
            raise Exception(
                'There is no child for node {} in zookeeper'.format(path))
        raise Exception('There is no node {} in zookeeper'.format(path))

    def safe_get_value(self, path):
        """
        Check that node exists and return its value if not empty or null.

        *Args:*\n
            _path_ - node path \n

        *Returns:*\n
            Value of the node \n

        *Example:*\n
            | Connect To Zookeeper | 127.0.0.1: 2181 |
            | Safe Get Value | /my/favorite/node |
        """
        if self.exists(path):
            self.check_node_value_existence(path)
            return self.get_value(path)
        raise Exception('There is no node {} in zookeeper'.format(path))

    def check_zookeeper_list_availability(self,
                                          zookeeper_list,
                                          default_encoding='utf-8'):
        """
        Iterate over zookeeper instances from the list and check if connection to each can be created,
         after that return a list with results.

        *Args:*\n
            _zookeeper_list_: list of dictionaries with Zookeeper instances(name, host, port, zk_encoding (optional)) \n
            _default_encoding_: default zookeeper encoding. Used when there is no zk_encoding for Zookeeper instance
                                in zookeeper_list \n

        *Returns:*\n
            List of dictionaries with connection results for each Zookeeper instances

        *Example:*\n
            | &{ZOOKEEPER_1} | Create Dictionary | name=Zookeeper_1 | host=127.0.0.1 | port=2181 | zk_encoding=utf-8 |
            | &{ZOOKEEPER_2} | Create Dictionary | name=Zookeeper_2 | host=127.0.0.1 | port=2182 |
            | @{ZOOKEEPER_LIST} | Create List | &{ZOOKEEPER_1} | &{ZOOKEEPER_2} |
            | Check Zookeeper List Availability | zookeeper_list=${ZOOKEEPER_LIST} |
        """
        results = list()
        for zookeeper in zookeeper_list:
            result = dict()
            zookeeper_host = '{}:{}'.format(zookeeper['host'],
                                            zookeeper['port'])
            result['test'] = '{} {}'.format(zookeeper['name'].upper(),
                                            zookeeper_host)
            try:
                self.connect_to_zookeeper(hosts=zookeeper_host,
                                          zk_encoding=zookeeper.get(
                                              'zk_encoding', default_encoding))
                result['success'] = True
            except Exception as ex:
                result['success'] = False
                result['reason'] = ex
            finally:
                self.close_all_zookeeper_connections()
                results.append(result)
        return results

    def check_zookeeper_nodes(self, zookeeper_nodes):
        """Iterate over a list with main zookeeper nodes, checking that each exists.

        *Args:*\n
            _zookeeper_nodes_ - list that contains zookeeper nodes \n

        *Example:*\n
            | Connect To Zookeeper | 127.0.0.1: 2181 |
            | Check Zookeeper Nodes | ['/my/favorite/node'] |

        """
        for node in zookeeper_nodes:
            node_exists = self.exists(node)
            message = "Node {0} doesn't exist.".format(node)
            self.bi.should_be_true(condition=node_exists, msg=message)
Esempio n. 30
0
class Process(object):
    """Robot Framework test library for running processes.

    This library utilizes Python's
    [http://docs.python.org/2/library/subprocess.html|subprocess]
    module and its
    [http://docs.python.org/2/library/subprocess.html#subprocess.Popen|Popen]
    class.

    The library has following main usages:

    - Running processes in system and waiting for their completion using
      `Run Process` keyword.
    - Starting processes on background using `Start Process`.
    - Waiting started process to complete using `Wait For Process` or
      stopping them with `Terminate Process` or `Terminate All Processes`.

    This library is new in Robot Framework 2.8.

    == Table of contents ==

    - `Specifying command and arguments`
    - `Process configuration`
    - `Active process`
    - `Result object`
    - `Boolean arguments`
    - `Using with OperatingSystem library`
    - `Example`
    - `Shortcuts`
    - `Keywords`

    = Specifying command and arguments =

    Both `Run Process` and `Start Process` accept the command to execute
    and all arguments passed to it as separate arguments. This is convenient
    to use and also allows these keywords to automatically escape possible
    spaces and other special characters in the command or arguments.

    When `running processes in shell`, it is also possible to give the
    whole command to execute as a single string. The command can then
    contain multiple commands, for example, connected with pipes. When
    using this approach the caller is responsible on escaping.

    Examples:
    | `Run Process` | ${progdir}${/}prog.py        | first arg | second         |
    | `Run Process` | script1.sh arg && script2.sh | shell=yes | cwd=${progdir} |

    Starting from Robot Framework 2.8.6, possible non-string arguments are
    converted to strings automatically.

    = Process configuration =

    `Run Process` and `Start Process` keywords can be configured using
    optional `**configuration` keyword arguments. Configuration arguments
    must be given after other arguments passed to these keywords and must
    use syntax like `name=value`. Available configuration arguments are
    listed below and discussed further in sections afterwards.

    |  = Name =  |                  = Explanation =                      |
    | shell      | Specifies whether to run the command in shell or not  |
    | cwd        | Specifies the working directory.                      |
    | env        | Specifies environment variables given to the process. |
    | env:<name> | Overrides the named environment variable(s) only.     |
    | stdout     | Path of a file where to write standard output.        |
    | stderr     | Path of a file where to write standard error.         |
    | alias      | Alias given to the process.                           |

    Note that because `**configuration` is passed using `name=value` syntax,
    possible equal signs in other arguments passed to `Run Process` and
    `Start Process` must be escaped with a backslash like `name\\=value`.
    See `Run Process` for an example.

    == Running processes in shell ==

    The `shell` argument specifies whether to run the process in a shell or
    not. By default shell is not used, which means that shell specific
    commands, like `copy` and `dir` on Windows, are not available.

    Giving the `shell` argument any non-false value, such as `shell=True`,
    changes the program to be executed in a shell. It allows using the shell
    capabilities, but can also make the process invocation operating system
    dependent.

    When using a shell it is possible to give the whole command to execute
    as a single string. See `Specifying command and arguments` section for
    examples and more details in general.

    == Current working directory ==

    By default the child process will be executed in the same directory
    as the parent process, the process running tests, is executed. This
    can be changed by giving an alternative location using the `cwd` argument.
    Forward slashes in the given path are automatically converted to
    backslashes on Windows.

    `Standard output and error streams`, when redirected to files,
    are also relative to the current working directory possibly set using
    the `cwd` argument.

    Example:
    | `Run Process` | prog.exe | cwd=${ROOT}/directory | stdout=stdout.txt |

    == Environment variables ==

    By default the child process will get a copy of the parent process's
    environment variables. The `env` argument can be used to give the
    child a custom environment as a Python dictionary. If there is a need
    to specify only certain environment variable, it is possible to use the
    `env:<name>=<value>` format to set or override only that named variables.
    It is also possible to use these two approaches together.

    Examples:
    | `Run Process` | program | env=${environ} |
    | `Run Process` | program | env:http_proxy=10.144.1.10:8080 | env:PATH=%{PATH}${:}${PROGDIR} |
    | `Run Process` | program | env=${environ} | env:EXTRA=value |

    == Standard output and error streams ==

    By default processes are run so that their standard output and standard
    error streams are kept in the memory. This works fine normally,
    but if there is a lot of output, the output buffers may get full and
    the program can hang.

    To avoid output buffers getting full, it is possible to use `stdout`
    and `stderr` arguments to specify files on the file system where to
    redirect the outputs. This can also be useful if other processes or
    other keywords need to read or manipulate the outputs somehow.

    Given `stdout` and `stderr` paths are relative to the `current working
    directory`. Forward slashes in the given paths are automatically converted
    to backslashes on Windows.

    As a special feature, it is possible to redirect the standard error to
    the standard output by using `stderr=STDOUT`.

    Regardless are outputs redirected to files or not, they are accessible
    through the `result object` returned when the process ends.

    Examples:
    | ${result} = | `Run Process` | program | stdout=${TEMPDIR}/stdout.txt | stderr=${TEMPDIR}/stderr.txt |
    | `Log Many`  | stdout: ${result.stdout} | stderr: ${result.stderr} |
    | ${result} = | `Run Process` | program | stderr=STDOUT |
    | `Log`       | all output: ${result.stdout} |

    Note that the created output files are not automatically removed after
    the test run. The user is responsible to remove them if needed.

    == Alias ==

    A custom name given to the process that can be used when selecting the
    `active process`.

    Examples:
    | `Start Process` | program | alias=example |
    | `Run Process`   | python  | -c | print 'hello' | alias=hello |

    = Active process =

    The test library keeps record which of the started processes is currently
    active. By default it is latest process started with `Start Process`,
    but `Switch Process` can be used to select a different one. Using
    `Run Process` does not affect the active process.

    The keywords that operate on started processes will use the active process
    by default, but it is possible to explicitly select a different process
    using the `handle` argument. The handle can be the identifier returned by
    `Start Process` or an `alias` explicitly given to `Start Process` or
    `Run Process`.

    = Result object =

    `Run Process`, `Wait For Process` and `Terminate Process` keywords return a
    result object that contains information about the process execution as its
    attributes. The same result object, or some of its attributes, can also
    be get using `Get Process Result` keyword. Attributes available in the
    object are documented in the table below.

    | = Attribute = |             = Explanation =               |
    | rc            | Return code of the process as an integer. |
    | stdout        | Contents of the standard output stream.   |
    | stderr        | Contents of the standard error stream.    |
    | stdout_path   | Path where stdout was redirected or `None` if not redirected. |
    | stderr_path   | Path where stderr was redirected or `None` if not redirected. |

    Example:
    | ${result} =            | `Run Process`         | program               |
    | `Should Be Equal As Integers` | ${result.rc}   | 0                     |
    | `Should Match`         | ${result.stdout}      | Some t?xt*            |
    | `Should Be Empty`      | ${result.stderr}      |                       |
    | ${stdout} =            | `Get File`            | ${result.stdout_path} |
    | `Should Be Equal`      | ${stdout}             | ${result.stdout}      |
    | `File Should Be Empty` | ${result.stderr_path} |                       |

    = Boolean arguments =

    Some keywords accept arguments that are handled as Boolean values.
    If such an argument is given as a string, it is considered false if it
    is either empty or case-insensitively equal to `false`. Other strings
    are considered true regardless what they contain, and other argument
    types are tested using same
    [http://docs.python.org/2/library/stdtypes.html#truth-value-testing|rules
    as in Python].

    True examples:
    | `Terminate Process` | kill=True     | # Strings are generally true.    |
    | `Terminate Process` | kill=yes      | # Same as above.                 |
    | `Terminate Process` | kill=${TRUE}  | # Python `True` is true.         |
    | `Terminate Process` | kill=${42}    | # Numbers other than 0 are true. |

    False examples:
    | `Terminate Process` | kill=False    | # String `False` is false.       |
    | `Terminate Process` | kill=${EMPTY} | # Empty string is false.         |
    | `Terminate Process` | kill=${FALSE} | # Python `False` is false.       |
    | `Terminate Process` | kill=${0}     | # Number 0 is false.             |

    Note that prior to Robot Framework 2.8 all non-empty strings, including
    `false`, were considered true.

    = Using with OperatingSystem library =

    The OperatingSystem library also contains keywords for running processes.
    They are not as flexible as the keywords provided by this library, and
    thus not recommended to be used anymore. They may eventually even be
    deprecated.

    There is a name collision because both of these libraries have
    `Start Process` and `Switch Process` keywords. This is handled so that
    if both libraries are imported, the keywords in the Process library are
    used by default. If there is a need to use the OperatingSystem variants,
    it is possible to use `OperatingSystem.Start Process` syntax or use
    the `BuiltIn` keyword `Set Library Search Order` to change the priority.

    Other keywords in the OperatingSystem library can be used freely with
    keywords in the Process library.

    = Example =

    | ***** Settings *****
    | Library    Process
    | Suite Teardown    `Terminate All Processes`    kill=True
    |
    | ***** Test Cases *****
    | Example
    |     `Start Process`    program    arg1   arg2    alias=First
    |     ${handle} =    `Start Process`    command.sh arg | command2.sh   shell=True    cwd=/path
    |     ${result} =    `Run Process`    ${CURDIR}/script.py
    |     `Should Not Contain`    ${result.stdout}    FAIL
    |     `Terminate Process`    ${handle}
    |     ${result} =    `Wait For Process`    First
    |     `Should Be Equal As Integers`   ${result.rc}    0
    """
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = get_version()
    TERMINATE_TIMEOUT = 30
    KILL_TIMEOUT = 10

    def __init__(self):
        self._processes = ConnectionCache('No active process.')
        self._results = {}

    def run_process(self, command, *arguments, **configuration):
        """Runs a process and waits for it to complete.

        `command` and `*arguments` specify the command to execute and arguments
        passed to it. See `Specifying command and arguments` for more details.

        `**configuration` contains additional configuration related to starting
        processes and waiting for them to finish. See `Process configuration`
        for more details about configuration related to starting processes.
        Configuration related to waiting for processes consists of `timeout`
        and `on_timeout` arguments that have same semantics as with `Wait
        For Process` keyword. By default there is no timeout, and if timeout
        is defined the default action on timeout is `terminate`.

        Returns a `result object` containing information about the execution.

        Note that possible equal signs in `*arguments` must be escaped
        with a backslash (e.g. `name\\=value`) to avoid them to be passed in
        as `**configuration`.

        Examples:
        | ${result} = | Run Process | python | -c | print 'Hello, world!' |
        | Should Be Equal | ${result.stdout} | Hello, world! |
        | ${result} = | Run Process | ${command} | stderr=STDOUT | timeout=10s |
        | ${result} = | Run Process | ${command} | timeout=1min | on_timeout=continue |
        | ${result} = | Run Process | java -Dname\\=value Example | shell=True | cwd=${EXAMPLE} |

        This command does not change the `active process`.
        `timeout` and `on_timeout` arguments are new in Robot Framework 2.8.4.
        """
        current = self._processes.current
        timeout = configuration.pop('timeout', None)
        on_timeout = configuration.pop('on_timeout', 'terminate')
        try:
            handle = self.start_process(command, *arguments, **configuration)
            return self.wait_for_process(handle, timeout, on_timeout)
        finally:
            self._processes.current = current

    def start_process(self, command, *arguments, **configuration):
        """Starts a new process on background.

        See `Specifying command and arguments` and `Process configuration`
        for more information about the arguments, and `Run Process` keyword
        for related examples.

        Makes the started process new `active process`. Returns an identifier
        that can be used as a handle to active the started process if needed.

        Starting from Robot Framework 2.8.5, processes are started so that
        they create a new process group. This allows sending signals to and
        terminating also possible child processes.
        """
        config = ProcessConfig(**configuration)
        executable_command = self._cmd(command, arguments, config.shell)
        logger.info('Starting process:\n%s' % executable_command)
        logger.debug('Process configuration:\n%s' % config)
        process = subprocess.Popen(executable_command, **config.full_config)
        self._results[process] = ExecutionResult(process,
                                                 config.stdout_stream,
                                                 config.stderr_stream)
        return self._processes.register(process, alias=config.alias)

    def _cmd(self, command, args, use_shell):
        command = [encode_to_system(item) for item in [command] + list(args)]
        if not use_shell:
            return command
        if args:
            return subprocess.list2cmdline(command)
        return command[0]

    def is_process_running(self, handle=None):
        """Checks is the process running or not.

        If `handle` is not given, uses the current `active process`.

        Returns `True` if the process is still running and `False` otherwise.
        """
        return self._processes[handle].poll() is None

    def process_should_be_running(self, handle=None,
                                  error_message='Process is not running.'):
        """Verifies that the process is running.

        If `handle` is not given, uses the current `active process`.

        Fails if the process has stopped.
        """
        if not self.is_process_running(handle):
            raise AssertionError(error_message)

    def process_should_be_stopped(self, handle=None,
                                  error_message='Process is running.'):
        """Verifies that the process is not running.

        If `handle` is not given, uses the current `active process`.

        Fails if the process is still running.
        """
        if self.is_process_running(handle):
            raise AssertionError(error_message)

    def wait_for_process(self, handle=None, timeout=None, on_timeout='continue'):
        """Waits for the process to complete or to reach the given timeout.

        The process to wait for must have been started earlier with
        `Start Process`. If `handle` is not given, uses the current
        `active process`.

        `timeout` defines the maximum time to wait for the process. It is
        interpreted according to Robot Framework User Guide Appendix
        `Time Format`, for example, '42', '42 s', or '1 minute 30 seconds'.

        `on_timeout` defines what to do if the timeout occurs. Possible values
        and corresponding actions are explained in the table below. Notice
        that reaching the timeout never fails the test.

        |  = Value =  |               = Action =               |
        | `continue`  | The process is left running (default). |
        | `terminate` | The process is gracefully terminated.  |
        | `kill`      | The process is forcefully stopped.     |

        See `Terminate Process` keyword for more details how processes are
        terminated and killed.

        If the process ends before the timeout or it is terminated or killed,
        this keyword returns a `result object` containing information about
        the execution. If the process is left running, Python `None` is
        returned instead.

        Examples:
        | # Process ends cleanly      |                  |                  |
        | ${result} =                 | Wait For Process | example          |
        | Process Should Be Stopped   | example          |                  |
        | Should Be Equal As Integers | ${result.rc}     | 0                |
        | # Process does not end      |                  |                  |
        | ${result} =                 | Wait For Process | timeout=42 secs  |
        | Process Should Be Running   |                  |                  |
        | Should Be Equal             | ${result}        | ${NONE}          |
        | # Kill non-ending process   |                  |                  |
        | ${result} =                 | Wait For Process | timeout=1min 30s | on_timeout=kill |
        | Process Should Be Stopped   |                  |                  |
        | Should Be Equal As Integers | ${result.rc}     | -9               |

        `timeout` and `on_timeout` are new in Robot Framework 2.8.2.
        """
        process = self._processes[handle]
        logger.info('Waiting for process to complete.')
        if timeout:
            timeout = timestr_to_secs(timeout)
            if not self._process_is_stopped(process, timeout):
                logger.info('Process did not complete in %s.'
                            % secs_to_timestr(timeout))
                return self._manage_process_timeout(handle, on_timeout.lower())
        return self._wait(process)

    def _manage_process_timeout(self, handle, on_timeout):
        if on_timeout == 'terminate':
            return self.terminate_process(handle)
        elif on_timeout == 'kill':
            return self.terminate_process(handle, kill=True)
        else:
            logger.info('Leaving process intact.')
            return None

    def _wait(self, process):
        result = self._results[process]
        result.rc = process.wait() or 0
        result.close_streams()
        logger.info('Process completed.')
        return result

    def terminate_process(self, handle=None, kill=False):
        """Stops the process gracefully or forcefully.

        If `handle` is not given, uses the current `active process`.

        Waits for the process to stop after terminating it. Returns
        a `result object` containing information about the execution
        similarly as `Wait For Process`.

        On Unix-like machines, by default, first tries to terminate the process
        group gracefully, but forcefully kills it if it does not stop in 30
        seconds. Kills the process group immediately if the `kill` argument is
        given any value considered true. See `Boolean arguments` section for
        more details about true and false values.

        Termination is done using `TERM (15)` signal and killing using
        `KILL (9)`. Use `Send Signal To Process` instead if you just want to
        send either of these signals without waiting for the process to stop.

        On Windows, by default, sends `CTRL_BREAK_EVENT` signal to the process
        group. If that does not stop the process in 30 seconds, or `kill`
        argument is given a true value, uses Win32 API function
        `TerminateProcess()` to kill the process forcefully. Note that
        `TerminateProcess()` does not kill possible child processes.

        | ${result} =                 | Terminate Process |     |
        | Should Be Equal As Integers | ${result.rc}      | -15 | # On Unixes |
        | Terminate Process           | myproc            | kill=true |

        *NOTE:* Stopping processes requires the
        [http://docs.python.org/2/library/subprocess.html|subprocess]
        module to have working `terminate` and `kill` functions. They were
        added in Python 2.6 and are thus missing from earlier versions.
        Unfortunately at least beta releases of Jython 2.7
        [http://bugs.jython.org/issue1898|do not seem to support them either].

        Automatically killing the process if termination fails as well as
        returning a result object are new features in Robot Framework 2.8.2.
        Terminating also possible child processes, including using
        `CTRL_BREAK_EVENT` on Windows, is new in Robot Framework 2.8.5.
        """
        process = self._processes[handle]
        if not hasattr(process, 'terminate'):
            raise RuntimeError('Terminating processes is not supported '
                               'by this Python version.')
        terminator = self._kill if is_true(kill) else self._terminate
        try:
            terminator(process)
        except OSError:
            if not self._process_is_stopped(process, self.KILL_TIMEOUT):
                raise
            logger.debug('Ignored OSError because process was stopped.')
        return self._wait(process)

    def _kill(self, process):
        logger.info('Forcefully killing process.')
        if hasattr(os, 'killpg'):
            os.killpg(process.pid, signal_module.SIGKILL)
        else:
            process.kill()
        if not self._process_is_stopped(process, self.KILL_TIMEOUT):
            raise RuntimeError('Failed to kill process.')

    def _terminate(self, process):
        logger.info('Gracefully terminating process.')
        # Sends signal to the whole process group both on POSIX and on Windows
        # if supported by the interpreter.
        if hasattr(os, 'killpg'):
            os.killpg(process.pid, signal_module.SIGTERM)
        elif hasattr(signal_module, 'CTRL_BREAK_EVENT'):
            if sys.platform == 'cli':
                # https://ironpython.codeplex.com/workitem/35020
                ctypes.windll.kernel32.GenerateConsoleCtrlEvent(
                    signal_module.CTRL_BREAK_EVENT, process.pid)
            else:
                process.send_signal(signal_module.CTRL_BREAK_EVENT)
        else:
            process.terminate()
        if not self._process_is_stopped(process, self.TERMINATE_TIMEOUT):
            logger.info('Graceful termination failed.')
            self._kill(process)

    def terminate_all_processes(self, kill=False):
        """Terminates all still running processes started by this library.

        This keyword can be used in suite teardown or elsewhere to make
        sure that all processes are stopped,

        By default tries to terminate processes gracefully, but can be
        configured to forcefully kill them immediately. See `Terminate Process`
        that this keyword uses internally for more details.
        """
        for handle in range(1, len(self._processes) + 1):
            if self.is_process_running(handle):
                self.terminate_process(handle, kill=kill)
        self.__init__()

    def send_signal_to_process(self, signal, handle=None, group=False):
        """Sends the given `signal` to the specified process.

        If `handle` is not given, uses the current `active process`.

        Signal can be specified either as an integer, or anything that can
        be converted to an integer, or as a signal name. In the latter case it
        is possible to give the name both with or without a `SIG` prefix,
        but names are case-sensitive. For example, all the examples below
        send signal `INT (2)`:

        | Send Signal To Process | 2      |        | # Send to active process |
        | Send Signal To Process | INT    |        |                          |
        | Send Signal To Process | SIGINT | myproc | # Send to named process  |

        What signals are supported depends on the system. For a list of
        existing signals on your system, see the Unix man pages related to
        signal handling (typically `man signal` or `man 7 signal`).

        By default sends the signal only to the parent process, not to possible
        child processes started by it. Notice that when `running processes in
        shell`, the shell is the parent process and it depends on the system
        does the shell propagate the signal to the actual started process.
        To send the signal to the whole process group, `group` argument can
        be set to any true value:

        | Send Signal To Process | TERM  | group=yes |

        If you are stopping a process, it is often easier and safer to use
        `Terminate Process` keyword instead.

        *NOTE:* Sending signals requires the
        [http://docs.python.org/2/library/subprocess.html|subprocess]
        module to have working `send_signal` function. It was added
        in Python 2.6 and are thus missing from earlier versions.
        How well it will work with forthcoming Jython 2.7 is unknown.

        New in Robot Framework 2.8.2. Support for `group` argument is new
        in Robot Framework 2.8.5.
        """
        if os.sep == '\\':
            raise RuntimeError('This keyword does not work on Windows.')
        process = self._processes[handle]
        signum = self._get_signal_number(signal)
        logger.info('Sending signal %s (%d).' % (signal, signum))
        if is_true(group) and hasattr(os, 'killpg'):
            os.killpg(process.pid, signum)
        elif hasattr(process, 'send_signal'):
            process.send_signal(signum)
        else:
            raise RuntimeError('Sending signals is not supported '
                               'by this Python version.')

    def _get_signal_number(self, int_or_name):
        try:
            return int(int_or_name)
        except ValueError:
            return self._convert_signal_name_to_number(int_or_name)

    def _convert_signal_name_to_number(self, name):
        try:
            return getattr(signal_module,
                           name if name.startswith('SIG') else 'SIG' + name)
        except AttributeError:
            raise RuntimeError("Unsupported signal '%s'." % name)

    def get_process_id(self, handle=None):
        """Returns the process ID (pid) of the process.

        If `handle` is not given, uses the current `active process`.

        Returns the pid assigned by the operating system as an integer.
        Note that with Jython, at least with the 2.5 version, the returned
        pid seems to always be `None`.

        The pid is not the same as the identifier returned by
        `Start Process` that is used internally by this library.
        """
        return self._processes[handle].pid

    def get_process_object(self, handle=None):
        """Return the underlying `subprocess.Popen`  object.

        If `handle` is not given, uses the current `active process`.
        """
        return self._processes[handle]

    def get_process_result(self, handle=None, rc=False, stdout=False,
                           stderr=False, stdout_path=False, stderr_path=False):
        """Returns the specified `result object` or some of its attributes.

        The given `handle` specifies the process whose results should be
        returned. If no `handle` is given, results of the current `active
        process` are returned. In either case, the process must have been
        finishes before this keyword can be used. In practice this means
        that processes started with `Start Process` must be finished either
        with `Wait For Process` or `Terminate Process` before using this
        keyword.

        If no other arguments than the optional `handle` are given, a whole
        `result object` is returned. If one or more of the other arguments
        are given any true value, only the specified attributes of the
        `result object` are returned. These attributes are always returned
        in the same order as arguments are specified in the keyword signature.
        See `Boolean arguments` section for more details about true and false
        values.

        Examples:
        | Run Process           | python             | -c            | print 'Hello, world!' | alias=myproc |
        | # Get result object   |                    |               |
        | ${result} =           | Get Process Result | myproc        |
        | Should Be Equal       | ${result.rc}       | ${0}          |
        | Should Be Equal       | ${result.stdout}   | Hello, world! |
        | Should Be Empty       | ${result.stderr}   |               |
        | # Get one attribute   |                    |               |
        | ${stdout} =           | Get Process Result | myproc        | stdout=true |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | # Multiple attributes |                    |               |
        | ${stdout}             | ${stderr} =        | Get Process Result |  myproc | stdout=yes | stderr=yes |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | Should Be Empty       | ${stderr}          |               |

        Although getting results of a previously executed process can be handy
        in general, the main use case for this keyword is returning results
        over the remote library interface. The remote interface does not
        support returning the whole result object, but individual attributes
        can be returned without problems.

        New in Robot Framework 2.8.2.
        """
        result = self._results[self._processes[handle]]
        if result.rc is None:
            raise RuntimeError('Getting results of unfinished processes '
                               'is not supported.')
        attributes = self._get_result_attributes(result, rc, stdout, stderr,
                                                 stdout_path, stderr_path)
        if not attributes:
            return result
        elif len(attributes) == 1:
            return attributes[0]
        return attributes

    def _get_result_attributes(self, result, *includes):
        attributes = (result.rc, result.stdout, result.stderr,
                      result.stdout_path, result.stderr_path)
        includes = (is_true(incl) for incl in includes)
        return tuple(attr for attr, incl in zip(attributes, includes) if incl)

    def switch_process(self, handle):
        """Makes the specified process the current `active process`.

        The handle can be an identifier returned by `Start Process` or
        the `alias` given to it explicitly.

        Example:
        | Start Process  | prog1    | alias=process1 |
        | Start Process  | prog2    | alias=process2 |
        | # currently active process is process2 |
        | Switch Process | process1 |
        | # now active process is process1 |
        """
        self._processes.switch(handle)

    def _process_is_stopped(self, process, timeout):
        max_time = time.time() + timeout
        while time.time() <= max_time:
            if process.poll() is not None:
                return True
            time.sleep(0.1)
        return False
class RabbitMqManager(object):
    """
    Библиотека для управления сервером RabbitMq.
    
    Реализована на основе:
    - [ http://hg.rabbitmq.com/rabbitmq-management/raw-file/3646dee55e02/priv/www-api/help.html | RabbitMQ Management HTTP API ]
    - [ https://github.com/rabbitmq/rabbitmq-management/blob/master/bin/rabbitmqadmin | rabbitmqadmin ]
    
    == Зависимости ==
    | robot framework | http://robotframework.org |
    
    == Example ==
    | *Settings* | *Value* |
    | Library    |       RabbitMqManager |
    | Library     |      Collections |
    
    | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* | *Argument* | *Argument* |
    | Simple |
    |    | Connect To Rabbitmq | my_host_name | 15672 | guest | guest | alias=rmq |
    |    | ${overview}= | Overview |
    |    | Log Dictionary | ${overview} |
    |    | Close All Rabbitmq Connections |
    """

    ROBOT_LIBRARY_SCOPE='GLOBAL'

    def __init__(self):
        self._connection=None
        self.headers=None
        self._cache=ConnectionCache()

    def connect_to_rabbitmq (self, host, port, username = '******', password = '******', timeout = 15, alias = None):
        """
        Подключение к серверу RabbitMq.
        
        *Args:*\n
        _host_ - имя сервера;\n
        _port_ - номер порта;\n
        _username_ - имя пользователя;\n
        _password_ - пароль пользователя;\n
        _timeout_ - время ожидания соединения;\n
        _alias_ - псевдоним соединения;\n 
        
        *Returns:*\n
        Индекс текущего соединения.
        
        *Raises:*\n
        socket.error в том случае, если невозможно создать соединение.
        
        *Example:*\n
        | Connect To Rabbitmq | my_host_name | 15672 | guest | guest | alias=rmq |
        """

        port=int (port)
        timeout=int (timeout)
        logger.debug ('Connecting using : host=%s, port=%d, username=%s, password=%s, timeout=%d, alias=%s '%(host, port, username, password, timeout, alias))
        self.headers={"Authorization":"Basic "+base64.b64encode(username+":"+password)}
        try:
            self._connection=httplib.HTTPConnection (host, port, timeout)
            self._connection.connect()
            return self._cache.register(self._connection, alias)
        except socket.error, e:
            raise Exception ("Could not connect to RabbitMq", str(e))
 def __init__(self):
     self._connection=None
     self.headers=None
     self._cache=ConnectionCache()
Esempio n. 33
0
class Process(object):
    """Robot Framework test library for running processes.

    This library utilizes Python's
    [http://docs.python.org/2/library/subprocess.html|subprocess]
    module and its
    [http://docs.python.org/2/library/subprocess.html#subprocess.Popen|Popen]
    class.

    The library has following main usages:

    - Running processes in system and waiting for their completion using
      `Run Process` keyword.
    - Starting processes on background using `Start Process`.
    - Waiting started process to complete using `Wait For Process` or
      stopping them with `Terminate Process` or `Terminate All Processes`.

    This library is new in Robot Framework 2.8.

    == Table of contents ==

    - `Specifying command and arguments`
    - `Process configuration`
    - `Active process`
    - `Result object`
    - `Boolean arguments`
    - `Example`
    - `Shortcuts`
    - `Keywords`

    = Specifying command and arguments =

    Both `Run Process` and `Start Process` accept the command to execute and
    all arguments passed to the command as separate arguments. This makes usage
    convenient and also allows these keywords to automatically escape possible
    spaces and other special characters in commands and arguments. Notice that
    if a command accepts options that themselves accept values, these options
    and their values must be given as separate arguments.

    When `running processes in shell`, it is also possible to give the whole
    command to execute as a single string. The command can then contain
    multiple commands to be run together. When using this approach, the caller
    is responsible on escaping.

    Examples:
    | `Run Process` | ${tools}${/}prog.py | argument | second arg with spaces |
    | `Run Process` | java | -jar | ${jars}${/}example.jar | --option | value |
    | `Run Process` | prog.py "one arg" && tool.sh | shell=yes | cwd=${tools} |

    Starting from Robot Framework 2.8.6, possible non-string arguments are
    converted to strings automatically.

    = Process configuration =

    `Run Process` and `Start Process` keywords can be configured using
    optional ``**configuration`` keyword arguments. Configuration arguments
    must be given after other arguments passed to these keywords and must
    use syntax like ``name=value``. Available configuration arguments are
    listed below and discussed further in sections afterwards.

    |  = Name =  |                  = Explanation =                      |
    | shell      | Specifies whether to run the command in shell or not. |
    | cwd        | Specifies the working directory.                      |
    | env        | Specifies environment variables given to the process. |
    | env:<name> | Overrides the named environment variable(s) only.     |
    | stdout     | Path of a file where to write standard output.        |
    | stderr     | Path of a file where to write standard error.         |
    | alias      | Alias given to the process.                           |

    Note that because ``**configuration`` is passed using ``name=value`` syntax,
    possible equal signs in other arguments passed to `Run Process` and
    `Start Process` must be escaped with a backslash like ``name\\=value``.
    See `Run Process` for an example.

    == Running processes in shell ==

    The ``shell`` argument specifies whether to run the process in a shell or
    not. By default shell is not used, which means that shell specific commands,
    like ``copy`` and ``dir`` on Windows, are not available. You can, however,
    run shell scripts and batch files without using a shell.

    Giving the ``shell`` argument any non-false value, such as ``shell=True``,
    changes the program to be executed in a shell. It allows using the shell
    capabilities, but can also make the process invocation operating system
    dependent. Having a shell between the actually started process and this
    library can also interfere communication with the process such as stopping
    it and reading its outputs. Because of these problems, it is recommended
    to use the shell only when absolutely necessary.

    When using a shell it is possible to give the whole command to execute
    as a single string. See `Specifying command and arguments` section for
    examples and more details in general.

    == Current working directory ==

    By default the child process will be executed in the same directory
    as the parent process, the process running tests, is executed. This
    can be changed by giving an alternative location using the ``cwd`` argument.
    Forward slashes in the given path are automatically converted to
    backslashes on Windows.

    `Standard output and error streams`, when redirected to files,
    are also relative to the current working directory possibly set using
    the ``cwd`` argument.

    Example:
    | `Run Process` | prog.exe | cwd=${ROOT}/directory | stdout=stdout.txt |

    == Environment variables ==

    By default the child process will get a copy of the parent process's
    environment variables. The ``env`` argument can be used to give the
    child a custom environment as a Python dictionary. If there is a need
    to specify only certain environment variable, it is possible to use the
    ``env:<name>=<value>`` format to set or override only that named variables.
    It is also possible to use these two approaches together.

    Examples:
    | `Run Process` | program | env=${environ} |
    | `Run Process` | program | env:http_proxy=10.144.1.10:8080 | env:PATH=%{PATH}${:}${PROGDIR} |
    | `Run Process` | program | env=${environ} | env:EXTRA=value |

    == Standard output and error streams ==

    By default processes are run so that their standard output and standard
    error streams are kept in the memory. This works fine normally,
    but if there is a lot of output, the output buffers may get full and
    the program can hang. Additionally on Jython, everything written to
    these in-memory buffers can be lost if the process is terminated.

    To avoid the above mentioned problems, it is possible to use ``stdout``
    and ``stderr`` arguments to specify files on the file system where to
    redirect the outputs. This can also be useful if other processes or
    other keywords need to read or manipulate the outputs somehow.

    Given ``stdout`` and ``stderr`` paths are relative to the `current working
    directory`. Forward slashes in the given paths are automatically converted
    to backslashes on Windows.

    As a special feature, it is possible to redirect the standard error to
    the standard output by using ``stderr=STDOUT``.

    Regardless are outputs redirected to files or not, they are accessible
    through the `result object` returned when the process ends.

    Examples:
    | ${result} = | `Run Process` | program | stdout=${TEMPDIR}/stdout.txt | stderr=${TEMPDIR}/stderr.txt |
    | `Log Many`  | stdout: ${result.stdout} | stderr: ${result.stderr} |
    | ${result} = | `Run Process` | program | stderr=STDOUT |
    | `Log`       | all output: ${result.stdout} |

    Note that the created output files are not automatically removed after
    the test run. The user is responsible to remove them if needed.

    == Alias ==

    A custom name given to the process that can be used when selecting the
    `active process`.

    Examples:
    | `Start Process` | program | alias=example |
    | `Run Process`   | python  | -c | print 'hello' | alias=hello |

    = Active process =

    The test library keeps record which of the started processes is currently
    active. By default it is latest process started with `Start Process`,
    but `Switch Process` can be used to select a different one. Using
    `Run Process` does not affect the active process.

    The keywords that operate on started processes will use the active process
    by default, but it is possible to explicitly select a different process
    using the ``handle`` argument. The handle can be the identifier returned by
    `Start Process` or an ``alias`` explicitly given to `Start Process` or
    `Run Process`.

    = Result object =

    `Run Process`, `Wait For Process` and `Terminate Process` keywords return a
    result object that contains information about the process execution as its
    attributes. The same result object, or some of its attributes, can also
    be get using `Get Process Result` keyword. Attributes available in the
    object are documented in the table below.

    | = Attribute = |             = Explanation =               |
    | rc            | Return code of the process as an integer. |
    | stdout        | Contents of the standard output stream.   |
    | stderr        | Contents of the standard error stream.    |
    | stdout_path   | Path where stdout was redirected or ``None`` if not redirected. |
    | stderr_path   | Path where stderr was redirected or ``None`` if not redirected. |

    Example:
    | ${result} =            | `Run Process`         | program               |
    | `Should Be Equal As Integers` | ${result.rc}   | 0                     |
    | `Should Match`         | ${result.stdout}      | Some t?xt*            |
    | `Should Be Empty`      | ${result.stderr}      |                       |
    | ${stdout} =            | `Get File`            | ${result.stdout_path} |
    | `Should Be Equal`      | ${stdout}             | ${result.stdout}      |
    | `File Should Be Empty` | ${result.stderr_path} |                       |

    = Boolean arguments =

    Some keywords accept arguments that are handled as Boolean values true or
    false. If such an argument is given as a string, it is considered false if
    it is either empty or case-insensitively equal to ``false`` or ``no``.
    Other strings are considered true regardless their value, and other
    argument types are tested using same
    [http://docs.python.org/2/library/stdtypes.html#truth-value-testing|rules
    as in Python].

    True examples:
    | `Terminate Process` | kill=True     | # Strings are generally true.    |
    | `Terminate Process` | kill=yes      | # Same as the above.             |
    | `Terminate Process` | kill=${TRUE}  | # Python ``True`` is true.       |
    | `Terminate Process` | kill=${42}    | # Numbers other than 0 are true. |

    False examples:
    | `Terminate Process` | kill=False    | # String ``false`` is false.   |
    | `Terminate Process` | kill=no       | # Also string ``no`` is false. |
    | `Terminate Process` | kill=${EMPTY} | # Empty string is false.       |
    | `Terminate Process` | kill=${FALSE} | # Python ``False`` is false.   |

    Note that prior to Robot Framework 2.8 all non-empty strings, including
    ``false``, were considered true. Additionally, ``no`` is considered false
    only in Robot Framework 2.9 and newer.

    = Example =

    | ***** Settings *****
    | Library           Process
    | Suite Teardown    `Terminate All Processes`    kill=True
    |
    | ***** Test Cases *****
    | Example
    |     `Start Process`    program    arg1    arg2    alias=First
    |     ${handle} =    `Start Process`    command.sh arg | command2.sh    shell=True    cwd=/path
    |     ${result} =    `Run Process`    ${CURDIR}/script.py
    |     `Should Not Contain`    ${result.stdout}    FAIL
    |     `Terminate Process`    ${handle}
    |     ${result} =    `Wait For Process`    First
    |     `Should Be Equal As Integers`    ${result.rc}    0
    """

    ROBOT_LIBRARY_SCOPE = "GLOBAL"
    ROBOT_LIBRARY_VERSION = get_version()
    TERMINATE_TIMEOUT = 30
    KILL_TIMEOUT = 10

    def __init__(self):
        self._processes = ConnectionCache("No active process.")
        self._results = {}

    def run_process(self, command, *arguments, **configuration):
        """Runs a process and waits for it to complete.

        ``command`` and ``*arguments`` specify the command to execute and
        arguments passed to it. See `Specifying command and arguments` for
        more details.

        ``**configuration`` contains additional configuration related to
        starting processes and waiting for them to finish. See `Process
        configuration` for more details about configuration related to starting
        processes. Configuration related to waiting for processes consists of
        ``timeout`` and ``on_timeout`` arguments that have same semantics as
        with `Wait For Process` keyword. By default there is no timeout, and
        if timeout is defined the default action on timeout is ``terminate``.

        Returns a `result object` containing information about the execution.

        Note that possible equal signs in ``*arguments`` must be escaped
        with a backslash (e.g. ``name\\=value``) to avoid them to be passed in
        as ``**configuration``.

        Examples:
        | ${result} = | Run Process | python | -c | print 'Hello, world!' |
        | Should Be Equal | ${result.stdout} | Hello, world! |
        | ${result} = | Run Process | ${command} | stderr=STDOUT | timeout=10s |
        | ${result} = | Run Process | ${command} | timeout=1min | on_timeout=continue |
        | ${result} = | Run Process | java -Dname\\=value Example | shell=True | cwd=${EXAMPLE} |

        This keyword does not change the `active process`.

        ``timeout`` and ``on_timeout`` arguments are new in Robot Framework
        2.8.4.
        """
        current = self._processes.current
        timeout = configuration.pop("timeout", None)
        on_timeout = configuration.pop("on_timeout", "terminate")
        try:
            handle = self.start_process(command, *arguments, **configuration)
            return self.wait_for_process(handle, timeout, on_timeout)
        finally:
            self._processes.current = current

    def start_process(self, command, *arguments, **configuration):
        """Starts a new process on background.

        See `Specifying command and arguments` and `Process configuration`
        for more information about the arguments, and `Run Process` keyword
        for related examples.

        Makes the started process new `active process`. Returns an identifier
        that can be used as a handle to active the started process if needed.

        Starting from Robot Framework 2.8.5, processes are started so that
        they create a new process group. This allows sending signals to and
        terminating also possible child processes. This is not supported by
        Jython in general nor by Python versions prior to 2.7 on Windows.
        """
        config = ProcessConfig(**configuration)
        command = self._get_command(command, arguments, config.shell)
        self._log_start(command, config)
        process = subprocess.Popen(command, **config.full_config)
        self._results[process] = ExecutionResult(process, config.stdout_stream, config.stderr_stream)
        return self._processes.register(process, alias=config.alias)

    def _get_command(self, command, args, use_shell):
        command = [encode_to_system(item) for item in [command] + list(args)]
        if not use_shell:
            return command
        if args:
            return subprocess.list2cmdline(command)
        return command[0]

    def _log_start(self, command, config):
        if is_list_like(command):
            command = self.join_command_line(command)
        logger.info("Starting process:\n%s" % command)
        logger.debug("Process configuration:\n%s" % config)

    def is_process_running(self, handle=None):
        """Checks is the process running or not.

        If ``handle`` is not given, uses the current `active process`.

        Returns ``True`` if the process is still running and ``False`` otherwise.
        """
        return self._processes[handle].poll() is None

    def process_should_be_running(self, handle=None, error_message="Process is not running."):
        """Verifies that the process is running.

        If ``handle`` is not given, uses the current `active process`.

        Fails if the process has stopped.
        """
        if not self.is_process_running(handle):
            raise AssertionError(error_message)

    def process_should_be_stopped(self, handle=None, error_message="Process is running."):
        """Verifies that the process is not running.

        If ``handle`` is not given, uses the current `active process`.

        Fails if the process is still running.
        """
        if self.is_process_running(handle):
            raise AssertionError(error_message)

    def wait_for_process(self, handle=None, timeout=None, on_timeout="continue"):
        """Waits for the process to complete or to reach the given timeout.

        The process to wait for must have been started earlier with
        `Start Process`. If ``handle`` is not given, uses the current
        `active process`.

        ``timeout`` defines the maximum time to wait for the process. It can be
        given in
        [http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#time-format|
        various time formats] supported by Robot Framework, for example, ``42``,
        ``42 s``, or ``1 minute 30 seconds``.

        ``on_timeout`` defines what to do if the timeout occurs. Possible values
        and corresponding actions are explained in the table below. Notice
        that reaching the timeout never fails the test.

        | = Value = |               = Action =               |
        | continue  | The process is left running (default). |
        | terminate | The process is gracefully terminated.  |
        | kill      | The process is forcefully stopped.     |

        See `Terminate Process` keyword for more details how processes are
        terminated and killed.

        If the process ends before the timeout or it is terminated or killed,
        this keyword returns a `result object` containing information about
        the execution. If the process is left running, Python ``None`` is
        returned instead.

        Examples:
        | # Process ends cleanly      |                  |                  |
        | ${result} =                 | Wait For Process | example          |
        | Process Should Be Stopped   | example          |                  |
        | Should Be Equal As Integers | ${result.rc}     | 0                |
        | # Process does not end      |                  |                  |
        | ${result} =                 | Wait For Process | timeout=42 secs  |
        | Process Should Be Running   |                  |                  |
        | Should Be Equal             | ${result}        | ${NONE}          |
        | # Kill non-ending process   |                  |                  |
        | ${result} =                 | Wait For Process | timeout=1min 30s | on_timeout=kill |
        | Process Should Be Stopped   |                  |                  |
        | Should Be Equal As Integers | ${result.rc}     | -9               |

        ``timeout`` and ``on_timeout`` are new in Robot Framework 2.8.2.
        """
        process = self._processes[handle]
        logger.info("Waiting for process to complete.")
        if timeout:
            timeout = timestr_to_secs(timeout)
            if not self._process_is_stopped(process, timeout):
                logger.info("Process did not complete in %s." % secs_to_timestr(timeout))
                return self._manage_process_timeout(handle, on_timeout.lower())
        return self._wait(process)

    def _manage_process_timeout(self, handle, on_timeout):
        if on_timeout == "terminate":
            return self.terminate_process(handle)
        elif on_timeout == "kill":
            return self.terminate_process(handle, kill=True)
        else:
            logger.info("Leaving process intact.")
            return None

    def _wait(self, process):
        result = self._results[process]
        result.rc = process.wait() or 0
        result.close_streams()
        logger.info("Process completed.")
        return result

    def terminate_process(self, handle=None, kill=False):
        """Stops the process gracefully or forcefully.

        If ``handle`` is not given, uses the current `active process`.

        By default first tries to stop the process gracefully. If the process
        does not stop in 30 seconds, or ``kill`` argument is given a true value,
        (see `Boolean arguments`) kills the process forcefully. Stops also all
        the child processes of the originally started process.

        Waits for the process to stop after terminating it. Returns a `result
        object` containing information about the execution similarly as `Wait
        For Process`.

        On Unix-like machines graceful termination is done using ``TERM (15)``
        signal and killing using ``KILL (9)``. Use `Send Signal To Process`
        instead if you just want to send either of these signals without
        waiting for the process to stop.

        On Windows graceful termination is done using ``CTRL_BREAK_EVENT``
        event and killing using Win32 API function ``TerminateProcess()``.

        Examples:
        | ${result} =                 | Terminate Process |     |
        | Should Be Equal As Integers | ${result.rc}      | -15 | # On Unixes |
        | Terminate Process           | myproc            | kill=true |

        Limitations:
        - Graceful termination is not supported on Windows by Jython nor by
          Python versions prior to 2.7. Process is killed instead.
        - Stopping the whole process group is not supported by Jython at all
          nor by Python versions prior to 2.7 on Windows.
        - On Windows forceful kill only stops the main process, not possible
          child processes.

        Automatically killing the process if termination fails as well as
        returning a result object are new features in Robot Framework 2.8.2.
        Terminating also possible child processes, including using
        ``CTRL_BREAK_EVENT`` on Windows, is new in Robot Framework 2.8.5.
        """
        process = self._processes[handle]
        if not hasattr(process, "terminate"):
            raise RuntimeError("Terminating processes is not supported " "by this Python version.")
        terminator = self._kill if is_truthy(kill) else self._terminate
        try:
            terminator(process)
        except OSError:
            if not self._process_is_stopped(process, self.KILL_TIMEOUT):
                raise
            logger.debug("Ignored OSError because process was stopped.")
        return self._wait(process)

    def _kill(self, process):
        logger.info("Forcefully killing process.")
        if hasattr(os, "killpg"):
            os.killpg(process.pid, signal_module.SIGKILL)
        else:
            process.kill()
        if not self._process_is_stopped(process, self.KILL_TIMEOUT):
            raise RuntimeError("Failed to kill process.")

    def _terminate(self, process):
        logger.info("Gracefully terminating process.")
        # Sends signal to the whole process group both on POSIX and on Windows
        # if supported by the interpreter.
        if hasattr(os, "killpg"):
            os.killpg(process.pid, signal_module.SIGTERM)
        elif hasattr(signal_module, "CTRL_BREAK_EVENT"):
            if IRONPYTHON:
                # https://ironpython.codeplex.com/workitem/35020
                ctypes.windll.kernel32.GenerateConsoleCtrlEvent(signal_module.CTRL_BREAK_EVENT, process.pid)
            else:
                process.send_signal(signal_module.CTRL_BREAK_EVENT)
        else:
            process.terminate()
        if not self._process_is_stopped(process, self.TERMINATE_TIMEOUT):
            logger.info("Graceful termination failed.")
            self._kill(process)

    def terminate_all_processes(self, kill=False):
        """Terminates all still running processes started by this library.

        This keyword can be used in suite teardown or elsewhere to make
        sure that all processes are stopped,

        By default tries to terminate processes gracefully, but can be
        configured to forcefully kill them immediately. See `Terminate Process`
        that this keyword uses internally for more details.
        """
        for handle in range(1, len(self._processes) + 1):
            if self.is_process_running(handle):
                self.terminate_process(handle, kill=kill)
        self.__init__()

    def send_signal_to_process(self, signal, handle=None, group=False):
        """Sends the given ``signal`` to the specified process.

        If ``handle`` is not given, uses the current `active process`.

        Signal can be specified either as an integer as a signal name. In the
        latter case it is possible to give the name both with or without ``SIG``
        prefix, but names are case-sensitive. For example, all the examples
        below send signal ``INT (2)``:

        | Send Signal To Process | 2      |        | # Send to active process |
        | Send Signal To Process | INT    |        |                          |
        | Send Signal To Process | SIGINT | myproc | # Send to named process  |

        This keyword is only supported on Unix-like machines, not on Windows.
        What signals are supported depends on the system. For a list of
        existing signals on your system, see the Unix man pages related to
        signal handling (typically ``man signal`` or ``man 7 signal``).

        By default sends the signal only to the parent process, not to possible
        child processes started by it. Notice that when `running processes in
        shell`, the shell is the parent process and it depends on the system
        does the shell propagate the signal to the actual started process.

        To send the signal to the whole process group, ``group`` argument can
        be set to any true value (see `Boolean arguments`). This is not
        supported by Jython, however.

        New in Robot Framework 2.8.2. Support for ``group`` argument is new
        in Robot Framework 2.8.5.
        """
        if os.sep == "\\":
            raise RuntimeError("This keyword does not work on Windows.")
        process = self._processes[handle]
        signum = self._get_signal_number(signal)
        logger.info("Sending signal %s (%d)." % (signal, signum))
        if is_truthy(group) and hasattr(os, "killpg"):
            os.killpg(process.pid, signum)
        elif hasattr(process, "send_signal"):
            process.send_signal(signum)
        else:
            raise RuntimeError("Sending signals is not supported " "by this Python version.")

    def _get_signal_number(self, int_or_name):
        try:
            return int(int_or_name)
        except ValueError:
            return self._convert_signal_name_to_number(int_or_name)

    def _convert_signal_name_to_number(self, name):
        try:
            return getattr(signal_module, name if name.startswith("SIG") else "SIG" + name)
        except AttributeError:
            raise RuntimeError("Unsupported signal '%s'." % name)

    def get_process_id(self, handle=None):
        """Returns the process ID (pid) of the process as an integer.

        If ``handle`` is not given, uses the current `active process`.

        Notice that the pid is not the same as the handle returned by
        `Start Process` that is used internally by this library.
        """
        return self._processes[handle].pid

    def get_process_object(self, handle=None):
        """Return the underlying ``subprocess.Popen`` object.

        If ``handle`` is not given, uses the current `active process`.
        """
        return self._processes[handle]

    def get_process_result(
        self, handle=None, rc=False, stdout=False, stderr=False, stdout_path=False, stderr_path=False
    ):
        """Returns the specified `result object` or some of its attributes.

        The given ``handle`` specifies the process whose results should be
        returned. If no ``handle`` is given, results of the current `active
        process` are returned. In either case, the process must have been
        finishes before this keyword can be used. In practice this means
        that processes started with `Start Process` must be finished either
        with `Wait For Process` or `Terminate Process` before using this
        keyword.

        If no other arguments than the optional ``handle`` are given, a whole
        `result object` is returned. If one or more of the other arguments
        are given any true value, only the specified attributes of the
        `result object` are returned. These attributes are always returned
        in the same order as arguments are specified in the keyword signature.
        See `Boolean arguments` section for more details about true and false
        values.

        Examples:
        | Run Process           | python             | -c            | print 'Hello, world!' | alias=myproc |
        | # Get result object   |                    |               |
        | ${result} =           | Get Process Result | myproc        |
        | Should Be Equal       | ${result.rc}       | ${0}          |
        | Should Be Equal       | ${result.stdout}   | Hello, world! |
        | Should Be Empty       | ${result.stderr}   |               |
        | # Get one attribute   |                    |               |
        | ${stdout} =           | Get Process Result | myproc        | stdout=true |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | # Multiple attributes |                    |               |
        | ${stdout}             | ${stderr} =        | Get Process Result |  myproc | stdout=yes | stderr=yes |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | Should Be Empty       | ${stderr}          |               |

        Although getting results of a previously executed process can be handy
        in general, the main use case for this keyword is returning results
        over the remote library interface. The remote interface does not
        support returning the whole result object, but individual attributes
        can be returned without problems.

        New in Robot Framework 2.8.2.
        """
        result = self._results[self._processes[handle]]
        if result.rc is None:
            raise RuntimeError("Getting results of unfinished processes " "is not supported.")
        attributes = self._get_result_attributes(result, rc, stdout, stderr, stdout_path, stderr_path)
        if not attributes:
            return result
        elif len(attributes) == 1:
            return attributes[0]
        return attributes

    def _get_result_attributes(self, result, *includes):
        attributes = (result.rc, result.stdout, result.stderr, result.stdout_path, result.stderr_path)
        includes = (is_truthy(incl) for incl in includes)
        return tuple(attr for attr, incl in zip(attributes, includes) if incl)

    def switch_process(self, handle):
        """Makes the specified process the current `active process`.

        The handle can be an identifier returned by `Start Process` or
        the ``alias`` given to it explicitly.

        Example:
        | Start Process  | prog1    | alias=process1 |
        | Start Process  | prog2    | alias=process2 |
        | # currently active process is process2 |
        | Switch Process | process1 |
        | # now active process is process1 |
        """
        self._processes.switch(handle)

    def _process_is_stopped(self, process, timeout):
        stopped = lambda: process.poll() is not None
        max_time = time.time() + timeout
        while time.time() <= max_time and not stopped():
            time.sleep(min(0.1, timeout))
        return stopped()

    def split_command_line(self, args, escaping=False):
        """Splits command line string into a list of arguments.

        String is split from spaces, but argument surrounded in quotes may
        contain spaces in them. If ``escaping`` is given a true value, then
        backslash is treated as an escape character. It can escape unquoted
        spaces, quotes inside quotes, and so on, but it also requires using
        double backslashes when using Windows paths.

        Examples:
        | @{cmd} = | Split Command Line | --option "value with spaces" |
        | Should Be True | $cmd == ['--option', 'value with spaces'] |

        New in Robot Framework 2.9.2.
        """
        return cmdline2list(args, escaping=escaping)

    def join_command_line(self, *args):
        """Joins arguments into one command line string.

        In resulting command line string arguments are delimited with a space,
        arguments containing spaces are surrounded with quotes, and possible
        quotes are escaped with a backslash.

        If this keyword is given only one argument and that is a list like
        object, then the values of that list are joined instead.

        Example:
        | ${cmd} = | Join Command Line | --option | value with spaces |
        | Should Be Equal | ${cmd} | --option "value with spaces" |

        New in Robot Framework 2.9.2.
        """
        if len(args) == 1 and is_list_like(args[0]):
            args = args[0]
        return subprocess.list2cmdline(args)
class TestConnnectionCache(unittest.TestCase):
    
    def setUp(self):
        self.cache = ConnectionCache()
        
    def test_initial(self):
        self._verify_initial_state()

    def test_no_connection(self):
        assert_raises_with_msg(RuntimeError, 'No open connection', getattr,
                               ConnectionCache().current, 'whatever')
        assert_raises_with_msg(RuntimeError, 'Custom msg', getattr,
                               ConnectionCache('Custom msg').current, 'xxx')
        
    def test_register_one(self):
        conn = ConnectionMock()
        index = self.cache.register(conn)
        assert_equals(index, 1)
        assert_equals(self.cache.current, conn)
        assert_equals(self.cache._connections, [conn])
        assert_equals(self.cache._aliases, {})

    def test_register_multiple(self):
        conns = [ConnectionMock(), ConnectionMock(), ConnectionMock()]
        for i, conn in enumerate(conns):
            index = self.cache.register(conn)
            assert_equals(index, i+1)
            assert_equals(self.cache.current, conn)
        assert_equals(self.cache._connections, conns)
        
    def test_switch_with_index(self):
        self._register('a', 'b', 'c')
        self._assert_current('c', 3)
        self.cache.switch(1)
        self._assert_current('a', 1)
        self.cache.switch('2')
        self._assert_current('b', 2)

    def _assert_current(self, id, index):
        assert_equals(self.cache.current.id, id)
        assert_equals(self.cache.current_index, index)
        
    def test_switch_with_non_existing_index(self):
        self._register('a', 'b')
        assert_raises_with_msg(RuntimeError, "Non-existing index or alias '3'",
                               self.cache.switch, 3)
        assert_raises_with_msg(RuntimeError, "Non-existing index or alias '42'",
                               self.cache.switch, 42)

    def test_register_with_alias(self):
        conn = ConnectionMock()
        index = self.cache.register(conn, 'My Connection')
        assert_equals(index, 1)
        assert_equals(self.cache.current, conn)
        assert_equals(self.cache._connections, [conn])
        assert_equals(self.cache._aliases, {'myconnection': 1})

    def test_register_multiple_with_alis(self):
        c1 = ConnectionMock(); c2 = ConnectionMock(); c3 = ConnectionMock()
        for i, conn in enumerate([c1,c2,c3]):
            index = self.cache.register(conn, 'c%d' % (i+1))
            assert_equals(index, i+1)
            assert_equals(self.cache.current, conn)
        assert_equals(self.cache._connections, [c1, c2, c3])
        assert_equals(self.cache._aliases, {'c1': 1, 'c2': 2, 'c3': 3})
        
    def test_switch_with_alias(self):
        self._register('a', 'b', 'c', 'd', 'e')
        assert_equals(self.cache.current.id, 'e')
        self.cache.switch('a')
        assert_equals(self.cache.current.id, 'a')
        self.cache.switch('C')
        assert_equals(self.cache.current.id, 'c')
        self.cache.switch('  B   ')
        assert_equals(self.cache.current.id, 'b')
        
    def test_switch_with_non_existing_alias(self):
        self._register('a', 'b')
        assert_raises_with_msg(RuntimeError, "Non-existing index or alias 'whatever'",
                               self.cache.switch, 'whatever')
                
    def test_switch_with_alias_overriding_index(self):
        self._register('2', '1')
        self.cache.switch(1)
        assert_equals(self.cache.current.id, '2')
        self.cache.switch('1')
        assert_equals(self.cache.current.id, '1')
        
    def test_close_all(self):
        connections = self._register('a', 'b', 'c', 'd')
        self.cache.close_all()
        self._verify_initial_state()
        for conn in connections:
            assert_true(conn.closed_by_close)
            
    def test_close_all_with_given_method(self):
        connections = self._register('a', 'b', 'c', 'd')
        self.cache.close_all('exit')
        self._verify_initial_state()
        for conn in connections:
            assert_true(conn.closed_by_exit)
            
    def test_empty_cache(self):
        connections = self._register('a', 'b', 'c', 'd')
        self.cache.empty_cache()
        self._verify_initial_state()
        for conn in connections:
            assert_false(conn.closed_by_close)
            assert_false(conn.closed_by_exit)
        
    def _verify_initial_state(self):
        assert_equals(self.cache.current, self.cache._no_current)
        assert_none(self.cache.current_index)
        assert_equals(self.cache._connections, [])
        assert_equals(self.cache._aliases, {})

    def _register(self, *ids):
        connections = []
        for id in ids:
            conn = ConnectionMock(id)
            self.cache.register(conn, id)
            connections.append(conn)
        return connections
Esempio n. 35
0
 def __init__(self):
     self._processes = ConnectionCache('No active process.')
     self._results = {}
Esempio n. 36
0
class SSHTunnelKeywords(object):

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self):
        self._connections = ConnectionCache()

    @property
    def current(self):
        return self._connections.current

    def start_ssh_tunnel(self,
                         alias,
                         remote_host_ip,
                         remote_host_port,
                         ssh_server_ip,
                         ssh_server_port,
                         ssh_server_username,
                         ssh_server_password,
                         local_host_ip='127.0.0.1',
                         local_host_port=0):
        """ Starts SSH Tunnel to give remote host via SSH Server
        ``alias`` Robot framework alias to find the session
        ``remote_host_ip`` IP of remote host
        ``remote_host_port`` Port number of remote host to connect
        ``ssh_server_ip`` SSH Server which has connection to remote host.
        ``ssh_server_port`` SSH Port of SSH Server. Usually it is 22
        ``ssh_server_username`` User name of SSH Server
        ``ssh_server_password`` Password of SSH Server
        ``local_host_ip`` Local host IP.  It can be IP or '127.0.0.1'. It is optional variable, default value is  "127.0.0.1"
        ``local_host_port`` Local port to map, default is 0 and 0 means any available port

        returns the connection index
        """

        sshtunnel.DAEMON = True
        if str(local_host_port).isdigit():
            local_host_port = int(local_host_port)
        else:
            raise Exception("Given local host port is not number.")

        if str(ssh_server_port).isdigit():
            ssh_server_port = int(ssh_server_port)
        else:
            raise Exception("Given SSH Server port is not number.")

        if str(remote_host_port).isdigit():
            remote_host_port = int(remote_host_port)
        else:
            raise Exception("Given Remote host port is not number.")

        server = sshtunnel.SSHTunnelForwarder(
            (ssh_server_ip, ssh_server_port),
            ssh_username=ssh_server_username,
            ssh_password=ssh_server_password,
            remote_bind_address=(remote_host_ip, remote_host_port),
            local_bind_address=(local_host_ip, local_host_port))
        server.start()
        connection_index = self._connections.register(server, alias)
        return connection_index

    def stop_ssh_tunnel(self, index_or_alias=''):
        """ Stops the specifc SSH Tunnel using its index or alias
        ``index_or_alias`` Index or Alias of the session to be closed
        
        returns none
        """

        if index_or_alias != '':
            try:
                if self.switch_connection(index_or_alias):
                    self.current.stop()
                    self._connections.current = self._connections._no_current
            except:
                pass

    def switch_connection(self, index_or_alias):
        """ Switch to the specific session using its index or alias.
        ``index_or_alias`` Index or Alias of the session to switch

        returns previous sesssion index
        """

        old_index = self._connections.current_index
        if index_or_alias is not None:
            self._connections.switch(index_or_alias)
        else:
            return False
        return old_index

    def get_local_port(self):
        """ Gets the current local port whicch is bind to remote port

        returns the local port where it binds
        """

        return self.current.local_bind_port

    def connection_exists(self, index_or_alias):
        """ Validates whether connection or session exists or not
        ``index_or_alias Index or Alias of the session to be validate of its existance

        returns True if connection exists, False otherwise
        """

        try:
            self._connections._resolve_alias_or_index(index_or_alias)
            return True
        except ValueError:
            return False

    def stop_all_ssh_tunnel(self):
        """  Stops all the SSH Tunnel sessions
        """

        try:
            self._connections.close_all(closer_method='stop')
        except:
            pass
 def __init__(self) -> None:
     """Library initialization.
     Robot Framework ConnectionCache() class is prepared for working with concurrent connections."""
     self._connection: Optional[cx_Oracle.Connection] = None
     self._cache = ConnectionCache()
Esempio n. 38
0
class ProcessLibrary(object):

    def __init__(self):
        self._started_processes = ConnectionCache()
        self._logs = dict()
        self._tempdir = tempfile.mkdtemp(suffix="processlib")

    def run_process(self, command, *args, **conf):
        p = self.start_new_process(command, *args, **conf)
        return self.wait_for_process(p)

    def start_new_process(self, command, *args, **conf):
        cmd = [command] + [str(i) for i in args]
        stdout_stream = open(conf['stdout'], 'w') if 'stdout' in conf else self._get_temp_file()
        stderr_stream = open(conf['stderr'], 'w') if 'stderr' in conf else self._get_temp_file("stderr")
        print "stdout tempfile is", stdout_stream.name
        print "stderr tempfile is", stderr_stream.name
        print "args", args
        pd = ProcessData(stdout_stream.name, stderr_stream.name)
        if 'shell' in conf:
            use_shell = (conf['shell'] != 'False')
        else:
            use_shell = False
        if 'cwd' in conf:
            cwd = conf['cwd']
        else:
            cwd = '.'
        if use_shell and args:
            cmd = "exec " + subprocess.list2cmdline(cmd)
            print cmd
        p = subprocess.Popen(cmd, stdout=stdout_stream,
                             stderr=stderr_stream,
                             shell=use_shell, cwd=cwd)
        print p.pid
        if 'alias' in conf:
            alias = conf['alias']
        else:
            alias = None
        index = self._started_processes.register(p, alias=alias)
        self._logs[index] = pd
        return index

    def process_is_alive(self, handle=None):
        if handle:
            self._started_processes.switch(handle)
        return self._started_processes.current.poll() is None

    def process_should_be_alive(self, handle):
        if not self.process_is_alive(handle):
            raise AssertionError('Process is not alive')

    def process_should_be_dead(self, handle):
        if self.process_is_alive(handle):
            raise AssertionError('Process is alive')

    def wait_for_process(self, handle=None):
        if handle:
            self._started_processes.switch(handle)
        exit_code = self._started_processes.current.wait()
        logs = self._logs[handle]
        with open(logs.stdout, 'r') as f:
            stdout = f.read()
        with open(logs.stderr, 'r') as f:
            stderr = f.read()
        return ExecutionResult(stdout, stderr, exit_code)

    def kill_process(self, handle=None):
        if handle:
            self._started_processes.switch(handle)
        self._started_processes.current.kill()

    def terminate_process(self, handle=None):
        if handle:
            self._started_processes.switch(handle)
        self._started_processes.current.terminate()

    def kill_all_processes(self):
        for handle in range(len(self._started_processes._connections)):
            self.kill_process(handle)

    def get_pid(self, handle):
        self._started_processes.switch(handle)
        return self._started_processes.current.pid

    def _get_temp_file(self, suffix="stdout"):
        return tempfile.NamedTemporaryFile(delete=False,
                                           prefix='tmp_logfile_',
                                           suffix="_%s" % suffix,
                                           dir=self._tempdir)

    def input_to_process(self, handle, msg):
        if not msg:
            return
        alog = self._logs[handle]
        self._started_processes.switch(handle)
        self._started_processes.current.wait()
        with open(alog.stdout, 'a') as f:
            f.write(msg.encode('UTF-8'))
 def setUp(self):
     self.cache = ConnectionCache()
Esempio n. 40
0
class BaseAppLibrary(object):

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    if in_robot_context:
        __metaclass__ = _StateCapturing

    def __init__(self):
        self._cache = ConnectionCache()

    def open_session(self, device_id, alias=None):
        """Open a session.

        ``device_id`` is an identifier for looking up configurations of a specific device.

        The optional ``alias`` provided here can be used to switch between sessions/devices later.
        See `Switch Device` for more details.

        """
        _logger.info('Open session; device ID = [%s], alias = [%s])', device_id, alias)

        # init context and install delegates
        context = self._init_context(device_id)
        context._log_screenshot_delegate = self._log_screenshot_delegate
        context._log_page_source_delegate = self._log_page_source_delegate

        self._cache.register(RFConnectionCache(context), alias)

    def open_app(self, reset=None):
        """Open the app.

        To reset app state prior to opening the app, pass a non-empty string to ``reset`` argument.

        Examples:

        | Open App | reset | # reset app state        |
        | Open App |       | # do not reset app state |

        """
        context = self._current_context
        context.open_app(bool(reset))

        context.logs_all = [] # accumulate logs of each step
        log_text('\n'.join(context.get_initial_logs()), 'APP LOGS (Initial)', 'app_logs_initial_', '.log')

    def _init_context(self):
        raise NotImplementedError()

    def _log_screenshot_delegate(self, msg, *args, **kwargs):
        msg = msg % args
        level = kwargs['level'] if 'level' in kwargs else logging.INFO
        page = kwargs['page'] if 'page' in kwargs else None

        if page: msg += ' (%s)' % page.__class__.__name__
        log_screenshot(self._current_context.take_screenshot_as_png(), msg, level=level)

    def _log_page_source_delegate(self, msg, *args, **kwargs):
        msg = msg % args
        level = kwargs['level'] if 'level' in kwargs else logging.INFO
        page = kwargs['page'] if 'page' in kwargs else None

        if page: msg += ' (%s)' % page.__class__.__name__
        source, ext = self._current_context.dump_page_source()
        log_text(source, msg, prefix='page_source_', suffix='.%s' % ext, level=level)

    def close_session(self):
        """Terminate current session."""
        self._cache.current.close()

    def close_all_sessions(self):
        """Terminate all open sessions."""
        self._cache.close_all()

    def close_app(self):
        """Close the app."""
        self._cache.current.close_app()

    def switch_device(self, alias):
        """Switch between sessions/devices using alias.

        Examples:

        | Open App      | A | # current session/device is A      |
        | Open App      | B | # current session/device becomes B |
        | Switch Device | A | # switch back to A                 |

        """
        self._cache.switch(alias)

    def _capture_state(self, after=False, err=None):
        # To increase efficiency, screenshots are no longer taken automatically.
        # Developers should explicitly do that AFTER the UI has been changed.
        if not after: return

        context = self._current_context
        try:
            if after:
                logs_step = context.get_new_logs()
                context.logs_all.extend(logs_step)
                log_text('\n'.join(logs_step), 'APP LOGS (Step)', 'app_logs_step_', '.log')
        except:
            _logger.warning('Fail to capture state.', exc_info=True)

    @property
    def _current_context(self):
        return self._cache.current._context

    @property
    def _current_page(self):
        return self._cache.current._context.current_page

    @_current_page.setter
    def _current_page(self, page):
        self._cache.current._context.current_page = page
Esempio n. 41
0
 def __init__(self):
     self._connections = ConnectionCache()
class SessionManager(object):
    """Session manager keywords for DynamoDB operations."""

    def __init__(self):
        self._builtin = BuiltIn()
        self._cache = ConnectionCache('No sessions.')

    def create_dynamodb_session(self, *args, **kwargs):
        # pylint: disable=line-too-long
        """Create DynamoDB session object.

        Arguments:
        - ``region``: The name of AWS region.
        - ``session``: The session object to AWS connection. (Optional)
        - ``profile``: The profile name to be use to create the session. (Optional)
        - ``access_key``: If ``session`` is None,
                          use this access key to create the session. (Optional)
        - ``secret_key``: If ``session`` is None,
                          use this secret key to create the session. (Optional)
        - ``session_token``: If ``session`` is None,
                             use this session token to create the session. (Optional)
        - ``host``: The address of the host. Use this to connect to a local instance. (Optional)
        - ``port``: Connect to the host on this port. (Default 80)
        - ``is_secure``: Enforce https connection. (Default True)
        - ``label``: A case and space insensitive string to identify the DynamoDB session.
                     (Default ``region``)

        Examples:
        | Create DynamoDB Session |           |                  |                   |             | # Use default config  |
        | Create DynamoDB Session | us-west-1 |                  |                   |             | # Use default profile |
        | Create DynamoDB Session | us-west-1 | profile=profile1 |                   |             | # Use profile1        |
        | Create DynamoDB Session | us-west-1 | access_key=KEY   | secret_key=SECRET |             | # Label is us-west-1  |
        | Create DynamoDB Session | us-west-1 | access_key=KEY   | secret_key=SECRET | label=LABEL | # Label is LABEL      |
        """
        # pylint: disable=line-too-long
        kargs = dict(enumerate(args))
        region = kargs.get(0, kwargs.pop('region', None))
        label = kwargs.pop('label', region)
        session = Engine()
        # pylint: disable=protected-access
        session._session = kwargs.pop('session', None)
        # pylint: disable=protected-access
        if session._session is None:
            # pylint: disable=protected-access
            session._session = self._get_session(region=region, **kwargs)
        # pylint: disable=protected-access
        client = self._get_client(session._session, region=region, **kwargs)
        kwargs.pop('access_key', None)
        kwargs.pop('host', None)
        kwargs.pop('is_secure', None)
        kwargs.pop('port', None)
        kwargs.pop('profile', None)
        kwargs.pop('secret_key', None)
        kwargs.pop('session_token', None)
        session.connection = DynamoDBConnection(client, **kwargs)
        if label is None:
            label = session.connection.region
        # pylint: disable=protected-access
        self._builtin.log('Creating DynamoDB session: %s' % label, 'DEBUG')
        self._cache.register(session, alias=label)
        return label

    def delete_all_dynamodb_sessions(self):
        """Removes all DynamoDB sessions."""
        self._cache.empty_cache()

    def delete_dynamodb_session(self, label):
        """Removes DynamoDB session.

        Arguments:
        - ``label``: A case and space insensitive string to identify the DynamoDB session.
                     (Default ``region``)

        Examples:
        | Delete DynamoDB Session | LABEL |
        """
        self._cache.switch(label)
        index = self._cache.current_index
        # pylint: disable=protected-access
        self._cache.current = self._cache._no_current
        # pylint: disable=protected-access
        self._cache._connections[index - 1] = None
        # pylint: disable=protected-access
        self._cache._aliases['x-%s-x' % label] = self._cache._aliases.pop(label)

    def _get_client(self, session, **kwargs):
        """Returns boto3 client session object."""
        client_kwargs = {}
        host = kwargs.pop('host', None)
        is_secure = kwargs.pop('is_secure', True)
        port = kwargs.pop('port', None)
        region = kwargs.pop('region', None)
        url = self._get_url(host, port, is_secure)
        if region is not None:
            client_kwargs['region_name'] = region
        if url is not None:
            client_kwargs['endpoint_url'] = url
        if not is_secure:
            client_kwargs['use_ssl'] = is_secure
        return session.client('dynamodb', **client_kwargs)

    @staticmethod
    def _get_session(**kwargs):
        """Returns boto3 session object."""
        access_key = kwargs.pop('access_key', None)
        profile = kwargs.pop('profile', None)
        region = kwargs.pop('region', None)
        session_kwargs = {}
        token = kwargs.pop('session_token', None)
        if access_key is not None:
            session_kwargs['aws_access_key_id'] = access_key
            session_kwargs['aws_secret_access_key'] = kwargs.pop('secret_key', None)
        if profile is not None:
            session_kwargs['profile_name'] = profile
        if region is not None:
            session_kwargs['region_name'] = region
        if token is not None:
            session_kwargs['aws_session_token'] = token
        return Session(**session_kwargs)

    @staticmethod
    def _get_url(host, port, is_secure=True):
        """Returns pre-format host endpoint URL."""
        url = None
        if host is not None:
            protocol = 'https' if is_secure else 'http'
            url = '%s://%s' % (protocol, host)
            if port is not None:
                url += ':%d' % int(port)
        return url
 def __init__(self):
     """
     """
     self._connections = ConnectionCache()
 def __init__(self):
     ConnectionCache.__init__(self, no_current_msg='No current browser')
     self._closed = set()
class CassandraCQLLibrary(object):
    """
    Library for executing CQL statements in database [ http://cassandra.apache.org/ | Apache Cassandra ].

    == Dependencies ==
    | datastax python-driver | https://github.com/datastax/python-driver |
    | robot framework | http://robotframework.org |

    == Additional Information ==
    - [ http://www.datastax.com/documentation/cql/3.1/cql/cql_using/about_cql_c.html | CQL query language]

    == Example ==
    | *Settings* | *Value* | *Value* | *Value* |
    | Library    | CassandraCQLLibrary |
    | Library    | Collections |
    | Suite Setup     |  Connect To Cassandra  |  192.168.33.10  |  9042 |
    | Suite Teardown  |  Disconnect From Cassandra |

    | *Test Cases*  | *Action* | *Argument* | *Argument* |
    | Get Keyspaces |
    |               | Execute CQL  |  USE system |
    |               | ${result}=   |  Execute CQL  |  SELECT * FROM schema_keyspaces; |
    |               | Log List  |  ${result} |
    |               | Log  |  ${result[1].keyspace_name} |
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self) -> None:
        """ Initialization. """
        self._connection: Optional[Session] = None
        self._cache = ConnectionCache()

    @property
    def keyspace(self) -> str:
        """Get keyspace Cassandra.

        Returns:
            keyspace: the name of the keyspace in Cassandra.
        """
        if self._connection is None:
            raise Exception('There is no connection to Cassandra cluster.')
        return self._connection.keyspace

    def connect_to_cassandra(self,
                             host: str,
                             port: Union[int, str] = 9042,
                             alias: str = None,
                             keyspace: str = None,
                             username: str = None,
                             password: str = '') -> Session:
        """
        Connect to Apache Cassandra cluster.

        AllowAllAuthenticator and PasswordAuthenticator are supported as authentication backend.
        This setting should be in configuration file cassandra.yaml:
        by default:
        | authenticator: AllowAllAuthenticator
        or for password authentification:
        | authenticator: PasswordAuthenticator

        *Args:*\n
            _host_ - IP address or host name of a cluster node;\n
            _port_ - connection port;\n
            _alias_ - connection alias;\n
            _keyspace_ - the name of the keyspace that the UDT is defined in;\n
            _username_ - username to connect to cassandra
            _password_ - password for username

        *Returns:*\n
            Index of current connection.

        *Example:*\n
        | Connect To Cassandra  |  192.168.1.108  |  9042  |  alias=cluster1 |
        """

        logger.info('Connecting using : host={0}, port={1}, alias={2}'.format(
            host, port, alias))
        try:
            auth_provider = PlainTextAuthProvider(
                username=username, password=password) if username else None
            cluster = Cluster([host],
                              port=int(port),
                              auth_provider=auth_provider,
                              load_balancing_policy=TokenAwarePolicy(
                                  DCAwareRoundRobinPolicy()))

            session = cluster.connect()
            if keyspace is not None:
                session.set_keyspace(keyspace)
            self._connection = session
            return self._cache.register(self._connection, alias)
        except Exception as e:
            raise Exception('Connect to Cassandra error: {0}'.format(e))

    def disconnect_from_cassandra(self) -> None:
        """
        Close current connection with cluster.

        *Example:*\n
        | Connect To Cassandra  |  server-host.local |
        | Disconnect From Cassandra |
        """
        if self._connection is None:
            raise Exception('There is no connection to Cassandra cluster.')
        self._connection.shutdown()

    def close_all_cassandra_connections(self) -> None:
        """
        Close all connections with cluster.

        This keyword is used to close all connections only in case if there are several open connections.
        Do not use keywords [#Disconnect From Cassandra|Disconnect From Cassandra] and
        [#Close All Cassandra Connections|Close All Cassandra Connections] together.

        After this keyword is executed, the index returned by [#Connect To Cassandra | Connect To Cassandra]
        starts at 1.

        *Example:*\n
        | Connect To Cassandra  |  192.168.1.108  | alias=cluster1 |
        | Connect To Cassandra  |  192.168.1.208  | alias=cluster2 |
        | Close All Cassandra Connections |
        """
        self._connection = self._cache.close_all(closer_method='shutdown')

    def switch_cassandra_connection(self, index_or_alias: Union[int,
                                                                str]) -> int:
        """
        Switch between active connections with several clusters using their index or alias.

        Connection alias is set in keyword [#Connect To Cassandra|Connect To Cassandra],
        which also returns the index of connection.

        *Args:*\n
            _index_or_alias_ - connection index or alias;

        *Returns:*\n
            Index of the previous connection.

        *Example:* (switch by alias)\n
        | Connect To Cassandra  |  192.168.1.108  | alias=cluster1 |
        | Connect To Cassandra  |  192.168.1.208  | alias=cluster2 |
        | Switch Cassandra Connection  |  cluster1 |

        *Example:* (switch by index)\n
        | ${cluster1}=  |  Connect To Cassandra  |  192.168.1.108  |
        | ${cluster2}= | Connect To Cassandra  |  192.168.1.208  |
        | ${previous_index}=  |  Switch Cassandra Connection  |  ${cluster1} |
        | Switch Cassandra Connection  |  ${previous_index} |
        =>\n
        ${cluster1}= 1\n
        ${cluster2}= 2\n
        ${previous_index}= 2\n
        """
        old_index = self._cache.current_index
        self._connection = self._cache.switch(index_or_alias)
        return old_index

    def execute_cql(self, statement: str) -> ResultSet:
        """
        Execute CQL statement.

        *Args:*\n
            _statement_ - CQL statement;

        *Returns:*\n
            Execution result.

        *Example:*\n
        | ${result}=  |  Execute CQL  |  SELECT * FROM system.schema_keyspaces; |
        | Log  |  ${result[1].keyspace_name} |
        =>\n
        system
        """
        if not self._connection:
            raise Exception('There is no connection to Cassandra cluster.')

        logger.debug("Executing :\n %s" % statement)
        result = self._connection.execute(statement, timeout=60)
        return result

    def execute_async_cql(self, statement: str) -> ResponseFuture:
        """
        Execute asynchronous CQL statement.

        *Args:*\n
            _statement_ - CQL statement;

        *Returns:*\n
            Object that can be used with keyword [#Get Async Result | Get Async Result] to get the result of CQL statement.
        """
        if not self._connection:
            raise Exception('There is no connection to Cassandra cluster.')

        logger.debug("Executing :\n %s" % statement)
        future = self._connection.execute_async(statement)
        return future

    def get_async_result(self, future: ResponseFuture) -> ResultSet:
        """
        Get the result of asynchronous CQL statement.

        *Args:*\n
            _future_ - object, returned as a result of keyword [#Execute Async Cql | Execute Async Cql]

        *Returns:*\n
            Result of asynchronous CQL statement.

        *Example:*\n
        | ${obj}=  |  Execute Async Cql  |  SELECT * FROM system.schema_keyspaces; |
        | Sleep | 5 |
        | ${result}=  |  Get Async Result  |  ${obj} |
        | Log  |  ${result[1].keyspace_name} |
        =>\n
        system
        """
        try:
            result = future.result()
        except Exception as e:
            raise Exception('Operation failed: {0}'.format(e))
        return result

    def get_column(self, column: str, statement: str) -> List[str]:
        """
        Get column values from the data sampling.

        *Args:*\n
            _column_ - name of the column which value you want to get;\n
            _statement_ - CQL select statement.

        *Returns:*\n
            List of column values.

        *Example:*\n
        | ${result}=  |  Get Column  |  keyspace_name  |  SELECT * FROM system.schema_keyspaces LIMIT 2; |
        | Log List  |  ${result} |
        =>\n
        | List length is 2 and it contains following items:
        | 0: test
        | 1: OpsCenter
        """
        result = self.execute_cql(statement)
        result_values = []
        for item in result:
            column_attr = str(getattr(item, column))
            result_values.append(column_attr)
        return result_values

    def get_column_from_schema_keyspaces(self, column: str) -> List[str]:
        """Get column values from the table schema_keyspaces.

        *Args:*\n
            _column_ - the name of the column which values you want to get;\n

        *Returns:*\n
            List of column values from the table schema_keyspaces.
        """
        statement = """SELECT *
                       FROM system.schema_keyspaces"""

        return self.get_column(column, statement)
class TestConnectionCache(unittest.TestCase):

    def setUp(self):
        self.cache = ConnectionCache()

    def test_initial(self):
        self._verify_initial_state()

    def test_no_connection(self):
        assert_raises_with_msg(RuntimeError, 'No open connection.', getattr,
                               ConnectionCache().current, 'whatever')
        assert_raises_with_msg(RuntimeError, 'Custom msg', getattr,
                               ConnectionCache('Custom msg').current, 'xxx')

    def test_register_one(self):
        conn = ConnectionMock()
        index = self.cache.register(conn)
        assert_equal(index, 1)
        assert_equal(self.cache.current, conn)
        assert_equal(self.cache.current_index, 1)
        assert_equal(self.cache._connections, [conn])
        assert_equal(self.cache._aliases, {})

    def test_register_multiple(self):
        conns = [ConnectionMock(1), ConnectionMock(2), ConnectionMock(3)]
        for i, conn in enumerate(conns):
            index = self.cache.register(conn)
            assert_equal(index, i+1)
            assert_equal(self.cache.current, conn)
            assert_equal(self.cache.current_index, i+1)
        assert_equal(self.cache._connections, conns)

    def test_register_multiple_equal_objects(self):
        conns = [ConnectionMock(1), ConnectionMock(1), ConnectionMock(1)]
        for i, conn in enumerate(conns):
            index = self.cache.register(conn)
            assert_equal(index, i+1)
            assert_equal(self.cache.current, conn)
            assert_equal(self.cache.current_index, i+1)
        assert_equal(self.cache._connections, conns)

    def test_register_multiple_same_object(self):
        conns = [ConnectionMock()] * 3
        for i, conn in enumerate(conns):
            index = self.cache.register(conn)
            assert_equal(index, i+1)
            assert_equal(self.cache.current, conn)
            assert_equal(self.cache.current_index, 1)
        assert_equal(self.cache._connections, conns)

    def test_set_current_index(self):
        self.cache.current_index = None
        assert_equal(self.cache.current_index, None)
        self._register('a', 'b')
        self.cache.current_index = 1
        assert_equal(self.cache.current_index, 1)
        assert_equal(self.cache.current.id, 'a')
        self.cache.current_index = None
        assert_equal(self.cache.current_index, None)
        assert_equal(self.cache.current, self.cache._no_current)
        self.cache.current_index = 2
        assert_equal(self.cache.current_index, 2)
        assert_equal(self.cache.current.id, 'b')

    def test_set_invalid_index(self):
        assert_raises(IndexError, setattr, self.cache, 'current_index', 1)

    def test_switch_with_index(self):
        self._register('a', 'b', 'c')
        self._assert_current('c', 3)
        self.cache.switch(1)
        self._assert_current('a', 1)
        self.cache.switch('2')
        self._assert_current('b', 2)

    def _assert_current(self, id, index):
        assert_equal(self.cache.current.id, id)
        assert_equal(self.cache.current_index, index)

    def test_switch_with_non_existing_index(self):
        self._register('a', 'b')
        assert_raises_with_msg(RuntimeError, "Non-existing index or alias '3'.",
                               self.cache.switch, 3)
        assert_raises_with_msg(RuntimeError, "Non-existing index or alias '42'.",
                               self.cache.switch, 42)

    def test_register_with_alias(self):
        conn = ConnectionMock()
        index = self.cache.register(conn, 'My Connection')
        assert_equal(index, 1)
        assert_equal(self.cache.current, conn)
        assert_equal(self.cache._connections, [conn])
        assert_equal(self.cache._aliases, {'myconnection': 1})

    def test_register_multiple_with_alias(self):
        c1 = ConnectionMock(); c2 = ConnectionMock(); c3 = ConnectionMock()
        for i, conn in enumerate([c1,c2,c3]):
            index = self.cache.register(conn, 'c%d' % (i+1))
            assert_equal(index, i+1)
            assert_equal(self.cache.current, conn)
        assert_equal(self.cache._connections, [c1, c2, c3])
        assert_equal(self.cache._aliases, {'c1': 1, 'c2': 2, 'c3': 3})

    def test_switch_with_alias(self):
        self._register('a', 'b', 'c', 'd', 'e')
        assert_equal(self.cache.current.id, 'e')
        self.cache.switch('a')
        assert_equal(self.cache.current.id, 'a')
        self.cache.switch('C')
        assert_equal(self.cache.current.id, 'c')
        self.cache.switch('  B   ')
        assert_equal(self.cache.current.id, 'b')

    def test_switch_with_non_existing_alias(self):
        self._register('a', 'b')
        assert_raises_with_msg(RuntimeError,
                               "Non-existing index or alias 'whatever'.",
                               self.cache.switch, 'whatever')

    def test_switch_with_alias_overriding_index(self):
        self._register('2', '1')
        self.cache.switch(1)
        assert_equal(self.cache.current.id, '2')
        self.cache.switch('1')
        assert_equal(self.cache.current.id, '1')

    def test_get_connection_with_index(self):
        self._register('a', 'b')
        assert_equal(self.cache.get_connection(1).id, 'a')
        assert_equal(self.cache.current.id, 'b')
        assert_equal(self.cache[2].id, 'b')

    def test_get_connection_with_alias(self):
        self._register('a', 'b')
        assert_equal(self.cache.get_connection('a').id, 'a')
        assert_equal(self.cache.current.id, 'b')
        assert_equal(self.cache['b'].id, 'b')

    def test_get_connection_with_none_returns_current(self):
        self._register('a', 'b')
        assert_equal(self.cache.get_connection().id, 'b')
        assert_equal(self.cache[None].id, 'b')

    def test_get_connection_with_none_fails_if_no_current(self):
        assert_raises_with_msg(RuntimeError,
                               'No open connection.',
                               self.cache.get_connection)

    def test_close_all(self):
        connections = self._register('a', 'b', 'c', 'd')
        self.cache.close_all()
        self._verify_initial_state()
        for conn in connections:
            assert_true(conn.closed_by_close)

    def test_close_all_with_given_method(self):
        connections = self._register('a', 'b', 'c', 'd')
        self.cache.close_all('exit')
        self._verify_initial_state()
        for conn in connections:
            assert_true(conn.closed_by_exit)

    def test_empty_cache(self):
        connections = self._register('a', 'b', 'c', 'd')
        self.cache.empty_cache()
        self._verify_initial_state()
        for conn in connections:
            assert_false(conn.closed_by_close)
            assert_false(conn.closed_by_exit)

    def test_iter(self):
        conns = ['a', object(), 1, None]
        for c in conns:
            self.cache.register(c)
        assert_equal(list(self.cache), conns)

    def test_len(self):
        assert_equal(len(self.cache), 0)
        self.cache.register(None)
        assert_equal(len(self.cache), 1)
        self.cache.register(None)
        assert_equal(len(self.cache), 2)
        self.cache.empty_cache()
        assert_equal(len(self.cache), 0)

    def test_truthy(self):
        assert_false(self.cache)
        self.cache.register(None)
        assert_true(self.cache)
        self.cache.current_index = None
        assert_false(self.cache)
        self.cache.current_index = 1
        assert_true(self.cache)
        self.cache.empty_cache()
        assert_false(self.cache)

    def test_resolve_alias_or_index(self):
        self.cache.register(ConnectionMock(), 'alias')
        assert_equal(self.cache._resolve_alias_or_index('alias'), 1)
        assert_equal(self.cache.resolve_alias_or_index('1'), 1)
        assert_equal(self.cache.resolve_alias_or_index(1), 1)

    def test_resolve_invalid_alias_or_index(self):
        assert_raises_with_msg(ValueError,
                               "Non-existing index or alias 'nonex'.",
                               self.cache._resolve_alias_or_index, 'nonex')
        assert_raises_with_msg(ValueError,
                               "Non-existing index or alias '1'.",
                               self.cache.resolve_alias_or_index, '1')
        assert_raises_with_msg(ValueError,
                               "Non-existing index or alias '42'.",
                               self.cache.resolve_alias_or_index, 42)

    def _verify_initial_state(self):
        assert_equal(self.cache.current, self.cache._no_current)
        assert_equal(self.cache.current_index, None)
        assert_equal(self.cache._connections, [])
        assert_equal(self.cache._aliases, {})

    def _register(self, *ids):
        connections = []
        for id in ids:
            conn = ConnectionMock(id)
            self.cache.register(conn, id)
            connections.append(conn)
        return connections
Esempio n. 47
0
 def __init__(self, access_key, secret_key):
     self._builtin = BuiltIn()
     self._cache = ConnectionCache('No sessions.')
     self.session = None
     self.r_session = None
     self.client = None
Esempio n. 48
0
class RestKeywords(object):

    def __init__(self):
        self._cache = ConnectionCache()
        self._builtin = BuiltIn()

    @staticmethod
    def _normalize(d):
        if type(d) is dict:
            return dict((k, RestKeywords._normalize(v)) for k, v in d.iteritems() if v and RestKeywords._normalize(v))
        elif type(d) is list:
            return [RestKeywords._normalize(v) for v in d if v and RestKeywords._normalize(v)]
        else:
            return d

    @staticmethod
    def convert_to_json(strings, normalize="False"):
        if type(strings) is list:
            json_ = map(lambda s: json.loads(s), strings)
        else:
            json_ = json.loads(strings)
        if normalize.upper() == "TRUE":
            if type(json_) is list:
                json_ = map(lambda x: RestKeywords._normalize(x), json_)
            else:
                json_ = RestKeywords._normalize(json_)
        return json_

    @staticmethod
    def convert_to_multipart_encoded_files(files):
        mpe_files = []
        for f in files:
            form_field_name = f[0]
            file_name = path.basename(f[1])
            file_path = f[1]
            mime_type = f[2]
            mpe_files.append((form_field_name, (file_name, open(file_path, "rb"), mime_type)))
        return mpe_files

    def create_session(self, alias, headers=None, auth=None, verify="False", cert=None):
        session = Session()
        if headers:
            session.headers.update(headers)
        if auth:
            session.auth = tuple(auth)
        session.verify = self._builtin.convert_to_boolean(verify)
        session.cert = cert
        self._cache.register(session, alias)

    def head(self, alias, url, params=None, headers=None, cookies=None, timeout=10):
        logger.info("Sending HEAD request to: '" + url + "', session: '" + alias + "'")
        session = self._cache.switch(alias)
        resp = session.head(url, params=params, headers=headers, cookies=cookies, timeout=timeout)
        return {"status": resp.status_code, "headers": resp.headers}

    def get(self, alias, url, params=None, headers=None, cookies=None, timeout=10):
        logger.info("Sending GET request to: '" + url + "', session: '" + alias + "'")
        session = self._cache.switch(alias)
        resp = session.get(url, params=params, headers=headers, cookies=cookies, timeout=timeout)
        try:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.json()}
        except ValueError:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.content}

    def post(self, alias, url, headers=None, data=None, files=None, cookies=None, timeout=10):
        logger.info("Sending POST request to: '" + url + "', session: '" + alias + "'")
        session = self._cache.switch(alias)
        resp = session.post(url, headers=headers, cookies=cookies, data=data.encode("utf-8"), files=files, timeout=timeout)
        try:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.json()}
        except ValueError:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.content}

    def put(self, alias, url, headers=None, data=None, cookies=None, timeout=10):
        logger.info("Sending PUT request to: '" + url + "', session: '" + alias + "'")
        session = self._cache.switch(alias)
        resp = session.put(url, headers=headers, cookies=cookies, data=data.encode("utf-8"), timeout=timeout)
        try:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.json()}
        except ValueError:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.content}

    def delete(self, alias, url, headers=None, data=None, cookies=None, timeout=10):
        logger.info("Sending DELETE request to: '" + url + "', session: '" + alias + "'")
        session = self._cache.switch(alias)
        resp = session.delete(url, headers=headers, cookies=cookies, data=data.encode("utf-8"), timeout=timeout)
        try:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.json()}
        except ValueError:
            return {"status": resp.status_code, "headers": resp.headers, "body": resp.content}

    def close_all_sessions(self):
        self._cache.empty_cache()
Esempio n. 49
0
 def __init__(self):
     ConnectionCache.__init__(self, no_current_msg='No current browser')
     self._closed = set()
Esempio n. 50
0
class NetConfig():
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = VERSION

    def __init__(self):
        self._cache = ConnectionCache()
        self.dict_alias = {}

    def _register_alias(self, alias, name, mac_address, remote_url):

        # 对别名判断做了修改 zsj 2013-3-28
        # 改成以别名为健(当前要求别名唯一) change by yzm @ 20130328
        # 因前面已经保证了alias唯一,则直接对alias进行赋值(赋新值可保证网卡信息为最新的信息)
        self.dict_alias[alias] = ((name, mac_address), remote_url)

    def _is_init(self, name_or_mac, remote_url, alias):
        """
        return alias
        """
        # 先判断别名是否被使用过
        _value = self.dict_alias.get(alias)
        if _value:
            # 如果被使用过,需要判断是否被当前对象使用(相同的remote_url以及name或者mac)
            if remote_url in _value and name_or_mac in _value[0]:
                # 如果相符,则可以直接返回alias
                return alias
            else:
                raise RuntimeError(u"别名 %s 正在被另外的对象使用,请选择另外的别名!" % alias)
        else:
            # 如果没被使用过,需判断当前的对象是否曾经被初始化过
            for key, tuple_value in self.dict_alias.items():
                if remote_url in tuple_value and name_or_mac in tuple_value[0]:
                    # 如果相符,则可以直接返回_key(只要找到即可返回)
                    return key

        # 两种情况都不包含,则返回None
        return None

    def init_nic_card(self, alias, name_or_mac, remote_url=False):
        """
        功能描述:初始化网卡,为网卡配置别名;
        
        注意:用MAC地址来初始化,禁用网卡之后,启用网卡会有问题。
        需要在网卡禁用之后,再启用网卡的,请不要用MAC进行初始化,可以用网卡名称实现初始化。
        
        参数:
        alias:别名; 
        name_or_mac:网卡名称或者是MAC地址; 
        remote_url:是否要进行远程控制(默认不进行远程),remote_url格式为:http://remote_IP; 
        可以用以下的几种方式进行初始化。请设置不同的别名,切换的时候用别名进行切换。 
        
        返回值:无
        
        Example:
        | Init Nic Card  | One | 本地连接1         |                          
        | Init Nic Card  | two | 本地连接1         | http://10.10.10.84 |
        | Init Nic Card  |  3  | 44-37-E6-99-7C-B9 |                          
        | Init Nic Card  |  4  | 44:37:E6:99:7C:B9 |                          
        """
        # 输入的name_or_mac做转换,除去格式的差异
        name_or_mac = modified_name_or_mac(name_or_mac)
        # 对用户输入的remote_url做处理转换,添加http://头等
        remote_url = modified_remote_url(remote_url)

        if (is_remote(remote_url)):
            # already init?
            ret_alias = self._is_init(name_or_mac, remote_url, alias)
            if (ret_alias):
                reallib = self._cache.switch(ret_alias)
            else:
                reallib = Remote(remote_url)

            reallib._client.set_timeout(
                REMOTE_TIMEOUT)  # add connection remote timeout zsj 2013-3-28
            name, mac_address = auto_do_remote(reallib)

        else:
            # already init?
            ret_alias = self._is_init(name_or_mac, remote_url, alias)
            if (ret_alias):
                reallib = self._cache.switch(ret_alias)
            else:
                reallib = ATTNetConfig(name_or_mac)

            name = reallib.name
            mac_address = reallib.mac_address

        tag = self._cache.register(reallib, alias)
        self._register_alias(alias, name, mac_address, remote_url)

        return name, mac_address

    def _current_remotelocal(self):
        if not self._cache.current:
            raise RuntimeError('No remotelocal is open')
        return self._cache.current

    def switch_nic_card(self, alias):
        """
        功能描述:切换当前所使用的网卡。
        
        参数:
        alias:别名。
        
        返回值:无
        
        Example:
        | Init Nic Card                     | 1           | 本地连接1 |
        | Config Nic To Ipv4 Static Address | 99.99.9.9   |
        | Init Nic Card                     | 2           | 本地连接1 | http://10.10.10.84 |
        | Config Nic To Ipv4 Static Address | 88.88.188.8 |
        | Switch Nic Card                   | 1           |
        | Config Nic To Ipv4 Dhcp           |
        | Switch Nic Card                   | 2           |
        | Config Nic To Ipv4 Dhcp           |
        """
        try:
            cls = self._cache.switch(alias)
            if (isinstance(cls, Remote)):
                # remote class do switch
                auto_do_remote(cls)
            else:
                log_data = u'成功切换到别名为:%s 的网卡下,后续Netconfig操作都是针对该网卡,直到下一个初始化或切换动作' % alias
                log.user_info(log_data)
        except (RuntimeError, DataError):  # RF 2.6 uses RE, earlier DE
            raise RuntimeError("No remotelocal with alias '%s' found." % alias)

    def get_nic_ipv4_address(self):
        '''
        功能描述:获取当前网卡的IPv4地址及子网掩码。
        
        参数:无。
        
        返回值:当前网卡的IPv4地址和子网掩码,如果当前网卡未启用或连接断开,返回None。\n
                返回值为list形式,由ip/subnet构成,例如[u'172.16.28.21/255.255.254.0']
                
        
        Example:
        | Init Nic Card                     | 1                    | 本地连接1 |
        | ${ret}                            | Get Nic Ipv4 Address |
        | Config Nic To Ipv4 Static Address | 99.99.9.9            |
        | ${ret2}                           | Get Nic Ipv4 Address |
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv4_address()
        return ret

    def config_nic_to_ipv4_static_address(self,
                                          ip_address,
                                          ip_subnet='255.255.255.0',
                                          gateway=''):
        '''
        功能描述:配置当前网卡IP为IPV4的静态地址。
        
        参数:
        ip_address:当前网卡IP地址;
        ip_subnet:子网掩码(默认为255.255.255.0);
        gateway:网关,默认为空,即不配置网关;
        
        返回值:无
        
        Example:
        | Init Nic Card                     | 1            | 本地连接1   |
        | Config Nic To Ipv4 Static Address | 99.99.9.9    | 255.255.0.0 | 99.99.9.1 |
        | Config Nic To Ipv4 Static Address | 192.168.1.50 |
        | Config Nic To Ipv4 Static Address | 99.99.9.9    | 255.255.0.0 |  #不配置网关   |
        '''
        # 检查IP地址合法性
        if not check_ipaddr_validity(ip_address):
            raise RuntimeError(u"关键字执行失败,IP地址为非法地址!")

        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.config_nic_to_ipv4_static_address(ip_address, ip_subnet,
                                                  gateway)

    def config_nic_ipv4_dns_server(self, dns=''):
        '''
        功能描述:配置当前网卡的dns服务器地址。
        
        注意:若当前网卡为DHCP方式,参数为默认值或字符串"None"时,DNS配置为自动获取,否则配置为手动方式的DNS服务器;
              若当前网卡为静态IP方式,参数为默认值时,不修改当前DNS配置,参数为字符串"None"时,清除当前DNS列表,否则配置手动方式的DNS服务器;
        
        参数:
        dns: 由DNS服务器ipv4地址组成的列表,即使是只配置一个地址,也需要采用列表形式。可以配置为字符串"None",默认为空;
            
        返回值:无
        
        Example:
        | Init Nic Card              | 1            | 本地连接1      |
        | ${dns_list}                | Create List  | 10.10.28.100   |
        | Config Nic Ipv4 Dns Server |              | 
        | Config Nic Ipv4 Dns Server |  None        |
        | Config Nic Ipv4 Dns Server |  ${dns_list} | 
        '''

        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.config_nic_ipv4_dns_server(dns)

    def config_nic_to_ipv4_dhcp(self):
        '''
        功能描述:配置当前网卡为DHCP模式
        
        参数:无
        
        返回值:无
        
        Example:
        | Init Nic Card           | 1 | 本地连接1 |
        | Config Nic To Ipv4 Dhcp |
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.config_nic_to_ipv4_dhcp()

    #添加获取MAC地址的关键字 add by shenlige 2013-3-12
    def get_nic_mac_address(self):
        '''
        功能描述:获取当前网卡的MAC地址,网卡未启用时,返回None
        
        参数:无
        
        返回值:mac地址
        
        Example:
        | Init Nic Card  | 1                     | 本地连接1 |
        | ${macaddr}     | Get Nic Mac Address   |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_mac_address()
        return ret

    #获取网关IP地址
    def get_nic_ipv4_gateway(self):
        '''
        功能描述:获取当前网卡的网关地址,网卡未启用、连接断开或连接受限时返回为None
        
        参数:无
        
        返回值:网关IP地址。
        
        Example:
        | Init Nic Card  | 1                     | 本地连接1 |
        | ${gateway}     | Get Nic Ipv4 Gateway  |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv4_gateway()
        return ret

    #获取DHCP服务器地址
    def get_nic_ipv4_dhcp_server(self):
        '''
        功能描述:获取当前网卡DHCP服务器地址,网卡未启用、连接断开或静态IP时返回为None
        
        参数:无
        
        返回值:DHCP服务器IP地址。
        
        Example:
        | Init Nic Card  | 1                         | 本地连接1 |
        | ${dhcp_server} | Get Nic Ipv4 Dhcp Server  |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv4_dhcp_server()
        return ret

    #获取当前DNS服务器IP地址
    def get_nic_ipv4_dns_server(self):
        '''
        功能描述:获取当前网卡DNS服务器地址,网卡未启用、连接断开或连接受限时返回为None
        
        参数:无
        
        返回值:DNS服务器IP地址,返回值为list类型
        
        Example:
        | Init Nic Card  | 1                        | 本地连接1 |
        | ${dns_server}  | Get Nic Ipv4 Dns Server  |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv4_dns_server()
        return ret

    #获取dhcp租约时间
    def get_nic_ipv4_dhcp_lease_time(self):
        '''
        功能描述:获取当前网卡DHCP租约时间,该时间为租约到期时间和租约获得时间的差,单位为秒;\n
                  当前网卡未启用或连接断开或非DHCP方式时返回None.
        
        参数:无
        
        返回值:租约时间
        
        Example:
        | Init Nic Card      | 1                             | 本地连接1 |
        | ${dhcp_lease_time} | Get Nic Ipv4 Dhcp Lease Time  |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv4_dhcp_lease_time()
        return ret

    #执行release
    def nic_dhcpv4_release(self):
        '''
        功能描述:执行release操作,静态IP、网卡未启用、连接断开时不报错,提示相应的WMI返回码
        
        参数:无
        
        返回值:无
        
        Example:
        | Init Nic Card         | 1   | 本地连接1 |
        | Nic Dhcpv4 Release    |     |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.nic_dhcpv4_release()

    #执行renew
    def nic_dhcpv4_renew(self):
        '''
        功能描述:执行renew操作,静态IP、网卡未启用、连接断开时不报错,提示相应的WMI返回码
        
        参数:无
        
        返回值:无
        
        Example:
        | Init Nic Card       | 1   | 本地连接1 |
        | Nic Dhcpv4 Renew    |     |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.nic_dhcpv4_renew()

    #启用网卡 add by shenlige 2013-3-13
    def enable_nic_card(self):
        '''
        功能描述:启用网卡。
        
        注意:用MAC地址来初始化,禁用网卡之后,启用网卡会有问题。
              需要在禁用网卡之后,再启用网卡的。请不要用MAC来进行初始化。
        
        参数:无
        
        返回值:无
        
        Example:
        | Init Nic Card       | 1   | 本地连接1 |
        | Enable Nic Card     |     |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.enable_nic_card()

    #禁用网卡
    def disable_nic_card(self):
        '''
        功能描述:禁用网卡。
        
        注意:用MAC地址来初始化,禁用网卡之后,启用网卡会有问题。
              需要在禁用网卡之后,再启用网卡的。请不要用MAC来进行初始化。
        
        参数:无
        
        返回值:无
        
        Example:
        | Init Nic Card       |  1   | 本地连接1 |
        | Disable Nic Card    |      |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.disable_nic_card()

    #网卡重启 add by shenlige 2013-3-14
    def restart_nic_card(self):
        '''
        功能描述:重启网卡
        
        参数:无
        
        返回值:无
        
        Example:
        | Init Nic Card        |  1   | 本地连接1 |
        | Restart Nic Card     |      |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.restart_nic_card()

    #获取WINS服务器地址
    def get_nic_ipv4_wins(self):
        '''
        功能描述:获取WINS服务器地址,若为空则返回None,网卡禁用时返回为None;
        
        参数:无
        
        返回值:由WINS服务器ip地址组成的列表。
        
        Example:
        | Init Nic Card  | 1                  | 本地连接1 |
        | ${ip_wins}     | Get Nic Ipv4 Wins  |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv4_wins()
        return ret

    #获取主机名
    def get_host_name(self):
        '''
        功能描述:获取主机名
        
        参数:无
        
        返回值:主机名。
        
        Example:
        | Init Nic Card  | 1              | 本地连接1 |
        | ${hostname}    | Get Host Name  |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
        else:
            ret = cls.get_host_name()
        return ret

    def config_nic_to_ipv6_dhcp(self):
        '''
        功能描述:配置当前网卡为IPV6 DHCP模式;
        
        注意:ipv6设置目前仅支持win7系统;
        
        参数:无
        
        返回值:无
        
        Example:
        | Init Nic Card           | 1 | 本地连接1 |
        | Config Nic To Ipv6 Dhcp |
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.config_nic_to_ipv6_dhcp()

    def config_nic_to_ipv6_static_address(self,
                                          ipv6_address,
                                          prefix_length=64,
                                          default_gateway=''):
        '''
        功能描述:配置网卡为静态方式ipv6地址,支持默认网关配置,默认不配置,\n
                  使用该接口配置ipv6地址后,除fe80:开始的地址外,仅有本次设置的ipv6地址。\n
                  配置仅本次生效,网卡重启后该配置将不存在
        
        注意:ipv6设置目前仅支持win7系统
        
        参数:
        ipv6_address: 有效的ipv6地址; 
        prefix_length:前缀长度,有效长度为0——128,默认为64; 
        default_gateway:默认网关,默认不配置,即自动获取; 
        
        返回值:无
        
        Example:
        | Init Nic Card                     |     1                              | 本地连接1 |
        | Config Nic To Ipv6 Static Address | 2111:3c:123:0:cc38:7f4e:2810:9903  |
        | Config Nic To Ipv6 Static Address | 2111:3c:123:0:cc38:7f4e:2810:9903  |   96   |
        | Config Nic To Ipv6 Static Address | 2111:3c:123:0:cc38:7f4e:2810:9903  |   64   |  2111:3c:123::   |
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.config_nic_to_ipv6_static_address(ipv6_address, prefix_length,
                                                  default_gateway)

    def config_nic_ipv6_dns_server(self, dns=''):
        '''
        功能描述:配置当前网卡的dns服务器地址。
        
        注意:ipv6设置目前仅支持win7系统;\n
              若当前网卡为DHCP方式,参数为默认值或字符串"None"时,DNS配置为自动获取,否则依据参数配置手动方式的DNS服务器;
              若当前网卡为静态IP方式,参数为默认值时,不修改当前DNS配置,参数为字符串"None"时,清除当前DNS列表,否则依据参数配置手动方式DNS服务器;
              
        
        参数:
        dns:由DNS服务器ipv6地址组成的列表,即使是只配置一个地址,也需要采用列表形式。
             可以配置为字符串"None",默认为空;
              
        
        返回值:无
        
        Example:
        | Init Nic Card              | 1              | 本地连接1   |
        | ${dns_list}                | Create List    | 2111:123::2013   |
        | Config Nic Ipv6 Dns Server |                | 
        | Config Nic Ipv6 Dns Server |  None          |
        | Config Nic Ipv6 Dns Server | ${dns_list}    | 
        '''

        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.config_nic_ipv6_dns_server(dns)

    def nic_add_an_ipv6_address(self, ipv6_address):
        '''
        功能描述:增加一个IPV6地址,该设置本次有效,网卡重启后,设置将不存在;
        
        注意:  ipv6设置目前仅支持win7系统;\n
                若当前网卡为DHCP方式,则关键字执行失败
    
        参数:
        ipv6_address:有效的ipv6地址
        
        返回值:无
        
        Example:
        | Init Nic Card            |  1                                 | 本地连接1 |
        | Nic Add An Ipv6 Address  | 2111:3c:123:0:cc38:7f4e:2810:9903  |
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.nic_add_an_ipv6_address(ipv6_address)

    def nic_delete_an_ipv6_address(self, ipv6_address):
        '''
        功能描述:删除一个IPV6地址,该设置本次有效,网卡重启后,设置将不存在;
        
        注意:ipv6设置目前仅支持win7系统;\n
              若当前网卡为DHCP方式,则关键字执行失败;
              若预删除的ipv6地址不存在,关键字执行不报错,只在LOG中给出提示。
            
        参数:
        ipv6_address:有效的ipv6地址
        
        返回值:无
        
        Example:
        | Init Nic Card              |  1                                 | 本地连接1 |
        | Nic Delete An Ipv6 Address | 2111:3c:123:0:cc38:7f4e:2810:9903  |
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.nic_delete_an_ipv6_address(ipv6_address)

    def get_nic_ipv6_default_gateway(self):
        '''
        功能描述:获取当前网卡的默认网关地址,网卡未启用、连接断开或连接受限时返回为None
        
        注意:ipv6设置目前仅支持win7系统
        
        参数:无
        
        返回值:由当前网卡的网关IP地址所组成的列表
        
        Example:
        | Init Nic Card  | 1                             | 本地连接1 |
        | ${gateway}     | Get Nic Ipv6 Default Gateway  |           |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv6_default_gateway()
        return ret

    def get_nic_ipv6_address(self):
        '''
        功能描述:返回当前网卡的IPv6地址。
        
        注意:ipv6设置目前仅支持win7系统
        
        参数:无。
        
        返回值:由当前网卡的IPv6地址所组成的列表,一般情况下,其中的ipv6地址包含了前缀长度,如果没有查找到或者当前网卡没有IPv6地址,返回None。
        
        Example:
        | Init Nic Card                     | 1                    | 本地连接1 |
        | ${ret}                            | Get Nic Ipv6 Address |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv6_address()
        return ret

    def get_nic_ipv6_dns_server(self):
        '''
        功能描述:返回当前网卡的IPv6 DNS 服务器地址。
        
        注意:ipv6设置目前仅支持win7系统
        
        参数:无。
        
        返回值:由当前网卡的IPv6 DNS服务器地址组成的列表。
        
        Example:
        | Init Nic Card        | 1                       | 本地连接1 |
        | ${ret}               | Get Nic Ipv6 Dns Server |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_ipv6_dns_server()
        return ret

    # 增加解析ip地址返回值的接口  add by shenlige 2013-3-26
    def analyze_ip_and_subnet(self, ip_subnet):
        '''
        功能描述:主要是对关键字Get Nic Ipv4 Address返回值做解析。
        
        
        注意:只解析一个'ip地址-子网掩码'值对,不解析list形式的参数。
        
        参数:
        ip_subnet:ip地址和子网掩码所组成的字符串,格式为:ipv4_addr/subnet,例如ipv4的地址:'172.16.28.113/255.255.254.0'               
        
        返回值:由ip地址和子网掩码构成的list,即[ip_addr,subnet],
                例如['172.16.28.113','255.255.254.0'],当参数为None时,返回为[None,None]
        
        Example:
        |  Init Nic Card       |  1                     | 本地连接1                |
        |  ${addr_subnet}      |  Get Nic Ipv4 Address  |
        |  ${addr_subnet}      |  Run Keyword If        |  ${addr_subnet} != None  |   Get From List   | ${addr_subnet} |   0   |
        |  ${ret}              |  Analyze Ip And Subnet |  ${addr_subnet}          |    
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
        else:
            ret = cls.analyze_ip_and_subnet(ip_subnet)
        return ret

    # 增加添加ipv4地址的接口 add by shenlige 2013-3-26
    def nic_add_an_ipv4_address(self, ipv4_addr, mask):
        '''
        功能描述:增加一个ipv4地址,若当前网卡为DHCP方式,则关键字执行失败;
        
        参数:
        ipv4_addr:ipv4地址;
        mask:子网掩码;
        
        返回值:无
        
        Example:
        | Init Nic Card            |  1            |  本地连接1      |
        | Nic Add An Ipv4 Address  | 192.168.1.11  |  255.255.255.0  |
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.nic_add_an_ipv4_address(ipv4_addr, mask)

    def nic_delete_an_ipv4_address(self, ipv4_address):
        '''
        功能描述:删除一个IPV4地址;
        
        注意:若当前网卡为DHCP方式,则关键字执行失败;
              多个静态IP地址时该此关键字可正常执行,单个静态IP时关键字执行失败;
              若预删除的ipv4地址不存在,关键字执行不报错,只在LOG中给出提示。
        
        参数:
        ipv4_address:有效的ipv4地址
        
        返回值:无
        
        Example:
        | Init Nic Card              |  1            | 本地连接1 |
        | Nic Delete An Ipv4 Address | 192.168.1.10  |
        '''
        # 检查IP地址合法性
        if not check_ipaddr_validity(ipv4_address):
            raise RuntimeError(u"关键字执行失败,IP地址为非法地址!")

        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            auto_do_remote(cls)
        else:
            cls.nic_delete_an_ipv4_address(ipv4_address)

    #添加获取通过名称或MAC获取MAC或名称的关键字 add by shenlige 2014-3-18
    def get_nic_name_or_mac(self, name_or_mac):
        '''
        功能描述:根据用户输入的名称或MAC获取相应的MAC或名称,若用户输入的参数无法找到相匹配的信息,则返回None。
        
        参数:
        name_or_mac:网卡的名称或MAC值;
        
        返回值:
        相应网卡的MAC或名称;
        
        Example:
        | Init Nic Card      | 1                     | 本地连接1 |
        | ${mac_or_name}     | Get Nic Name Or Mac   |    test   |
        
        '''
        cls = self._current_remotelocal()
        if (isinstance(cls, Remote)):
            ret = auto_do_remote(cls)
            if not ret:
                ret = None
        else:
            ret = cls.get_nic_name_or_mac(name_or_mac)
        return ret
Esempio n. 51
0
class Process(object):
    """Robot Framework test library for running processes.

    This library utilizes Python's
    [http://docs.python.org/2/library/subprocess.html|subprocess]
    module and its
    [http://docs.python.org/2/library/subprocess.html#subprocess.Popen|Popen]
    class.

    The library has following main usages:

    - Running processes in system and waiting for their completion using
      `Run Process` keyword.
    - Starting processes on background using `Start Process`.
    - Waiting started process to complete using `Wait For Process` or
      stopping them with `Terminate Process` or `Terminate All Processes`.

    This library is new in Robot Framework 2.8.

    == Table of contents ==

    - `Specifying command and arguments`
    - `Process configuration`
    - `Active process`
    - `Result object`
    - `Boolean arguments`
    - `Using with OperatingSystem library`
    - `Example`
    - `Shortcuts`
    - `Keywords`

    = Specifying command and arguments =

    Both `Run Process` and `Start Process` accept the command to execute
    and all arguments passed to it as separate arguments. This is convenient
    to use and also allows these keywords to automatically escape possible
    spaces and other special characters in the command or arguments.

    When `running processes in shell`, it is also possible to give the
    whole command to execute as a single string. The command can then
    contain multiple commands, for example, connected with pipes. When
    using this approach the caller is responsible on escaping.

    Examples:
    | `Run Process` | ${progdir}${/}prog.py        | first arg | second         |
    | `Run Process` | script1.sh arg && script2.sh | shell=yes | cwd=${progdir} |

    Starting from Robot Framework 2.8.6, possible non-string arguments are
    converted to strings automatically.

    = Process configuration =

    `Run Process` and `Start Process` keywords can be configured using
    optional `**configuration` keyword arguments. Configuration arguments
    must be given after other arguments passed to these keywords and must
    use syntax like `name=value`. Available configuration arguments are
    listed below and discussed further in sections afterwards.

    |  = Name =  |                  = Explanation =                      |
    | shell      | Specifies whether to run the command in shell or not  |
    | cwd        | Specifies the working directory.                      |
    | env        | Specifies environment variables given to the process. |
    | env:<name> | Overrides the named environment variable(s) only.     |
    | stdout     | Path of a file where to write standard output.        |
    | stderr     | Path of a file where to write standard error.         |
    | alias      | Alias given to the process.                           |

    Note that because `**configuration` is passed using `name=value` syntax,
    possible equal signs in other arguments passed to `Run Process` and
    `Start Process` must be escaped with a backslash like `name\\=value`.
    See `Run Process` for an example.

    == Running processes in shell ==

    The `shell` argument specifies whether to run the process in a shell or
    not. By default shell is not used, which means that shell specific
    commands, like `copy` and `dir` on Windows, are not available.

    Giving the `shell` argument any non-false value, such as `shell=True`,
    changes the program to be executed in a shell. It allows using the shell
    capabilities, but can also make the process invocation operating system
    dependent.

    When using a shell it is possible to give the whole command to execute
    as a single string. See `Specifying command and arguments` section for
    examples and more details in general.

    == Current working directory ==

    By default the child process will be executed in the same directory
    as the parent process, the process running tests, is executed. This
    can be changed by giving an alternative location using the `cwd` argument.
    Forward slashes in the given path are automatically converted to
    backslashes on Windows.

    `Standard output and error streams`, when redirected to files,
    are also relative to the current working directory possibly set using
    the `cwd` argument.

    Example:
    | `Run Process` | prog.exe | cwd=${ROOT}/directory | stdout=stdout.txt |

    == Environment variables ==

    By default the child process will get a copy of the parent process's
    environment variables. The `env` argument can be used to give the
    child a custom environment as a Python dictionary. If there is a need
    to specify only certain environment variable, it is possible to use the
    `env:<name>=<value>` format to set or override only that named variables.
    It is also possible to use these two approaches together.

    Examples:
    | `Run Process` | program | env=${environ} |
    | `Run Process` | program | env:http_proxy=10.144.1.10:8080 | env:PATH=%{PATH}${:}${PROGDIR} |
    | `Run Process` | program | env=${environ} | env:EXTRA=value |

    == Standard output and error streams ==

    By default processes are run so that their standard output and standard
    error streams are kept in the memory. This works fine normally,
    but if there is a lot of output, the output buffers may get full and
    the program can hang. Additionally on Jython, everything written to
    these in-memory buffers can be lost if the process is terminated.

    To avoid the above mentioned problems, it is possible to use `stdout`
    and `stderr` arguments to specify files on the file system where to
    redirect the outputs. This can also be useful if other processes or
    other keywords need to read or manipulate the outputs somehow.

    Given `stdout` and `stderr` paths are relative to the `current working
    directory`. Forward slashes in the given paths are automatically converted
    to backslashes on Windows.

    As a special feature, it is possible to redirect the standard error to
    the standard output by using `stderr=STDOUT`.

    Regardless are outputs redirected to files or not, they are accessible
    through the `result object` returned when the process ends.

    Examples:
    | ${result} = | `Run Process` | program | stdout=${TEMPDIR}/stdout.txt | stderr=${TEMPDIR}/stderr.txt |
    | `Log Many`  | stdout: ${result.stdout} | stderr: ${result.stderr} |
    | ${result} = | `Run Process` | program | stderr=STDOUT |
    | `Log`       | all output: ${result.stdout} |

    Note that the created output files are not automatically removed after
    the test run. The user is responsible to remove them if needed.

    == Alias ==

    A custom name given to the process that can be used when selecting the
    `active process`.

    Examples:
    | `Start Process` | program | alias=example |
    | `Run Process`   | python  | -c | print 'hello' | alias=hello |

    = Active process =

    The test library keeps record which of the started processes is currently
    active. By default it is latest process started with `Start Process`,
    but `Switch Process` can be used to select a different one. Using
    `Run Process` does not affect the active process.

    The keywords that operate on started processes will use the active process
    by default, but it is possible to explicitly select a different process
    using the `handle` argument. The handle can be the identifier returned by
    `Start Process` or an `alias` explicitly given to `Start Process` or
    `Run Process`.

    = Result object =

    `Run Process`, `Wait For Process` and `Terminate Process` keywords return a
    result object that contains information about the process execution as its
    attributes. The same result object, or some of its attributes, can also
    be get using `Get Process Result` keyword. Attributes available in the
    object are documented in the table below.

    | = Attribute = |             = Explanation =               |
    | rc            | Return code of the process as an integer. |
    | stdout        | Contents of the standard output stream.   |
    | stderr        | Contents of the standard error stream.    |
    | stdout_path   | Path where stdout was redirected or `None` if not redirected. |
    | stderr_path   | Path where stderr was redirected or `None` if not redirected. |

    Example:
    | ${result} =            | `Run Process`         | program               |
    | `Should Be Equal As Integers` | ${result.rc}   | 0                     |
    | `Should Match`         | ${result.stdout}      | Some t?xt*            |
    | `Should Be Empty`      | ${result.stderr}      |                       |
    | ${stdout} =            | `Get File`            | ${result.stdout_path} |
    | `Should Be Equal`      | ${stdout}             | ${result.stdout}      |
    | `File Should Be Empty` | ${result.stderr_path} |                       |

    = Boolean arguments =

    Some keywords accept arguments that are handled as Boolean values.
    If such an argument is given as a string, it is considered false if it
    is either empty or case-insensitively equal to `false`. Other strings
    are considered true regardless what they contain, and other argument
    types are tested using same
    [http://docs.python.org/2/library/stdtypes.html#truth-value-testing|rules
    as in Python].

    True examples:
    | `Terminate Process` | kill=True     | # Strings are generally true.    |
    | `Terminate Process` | kill=yes      | # Same as above.                 |
    | `Terminate Process` | kill=${TRUE}  | # Python `True` is true.         |
    | `Terminate Process` | kill=${42}    | # Numbers other than 0 are true. |

    False examples:
    | `Terminate Process` | kill=False    | # String `False` is false.       |
    | `Terminate Process` | kill=${EMPTY} | # Empty string is false.         |
    | `Terminate Process` | kill=${FALSE} | # Python `False` is false.       |
    | `Terminate Process` | kill=${0}     | # Number 0 is false.             |

    Note that prior to Robot Framework 2.8 all non-empty strings, including
    `false`, were considered true.

    = Using with OperatingSystem library =

    The OperatingSystem library also contains keywords for running processes.
    They are not as flexible as the keywords provided by this library, and
    thus not recommended to be used anymore. They may eventually even be
    deprecated.

    There is a name collision because both of these libraries have
    `Start Process` and `Switch Process` keywords. This is handled so that
    if both libraries are imported, the keywords in the Process library are
    used by default. If there is a need to use the OperatingSystem variants,
    it is possible to use `OperatingSystem.Start Process` syntax or use
    the `BuiltIn` keyword `Set Library Search Order` to change the priority.

    Other keywords in the OperatingSystem library can be used freely with
    keywords in the Process library.

    = Example =

    | ***** Settings *****
    | Library    Process
    | Suite Teardown    `Terminate All Processes`    kill=True
    |
    | ***** Test Cases *****
    | Example
    |     `Start Process`    program    arg1   arg2    alias=First
    |     ${handle} =    `Start Process`    command.sh arg | command2.sh   shell=True    cwd=/path
    |     ${result} =    `Run Process`    ${CURDIR}/script.py
    |     `Should Not Contain`    ${result.stdout}    FAIL
    |     `Terminate Process`    ${handle}
    |     ${result} =    `Wait For Process`    First
    |     `Should Be Equal As Integers`   ${result.rc}    0
    """
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = get_version()
    TERMINATE_TIMEOUT = 30
    KILL_TIMEOUT = 10

    def __init__(self):
        self._processes = ConnectionCache('No active process.')
        self._results = {}

    def run_process(self, command, *arguments, **configuration):
        """Runs a process and waits for it to complete.

        `command` and `*arguments` specify the command to execute and arguments
        passed to it. See `Specifying command and arguments` for more details.

        `**configuration` contains additional configuration related to starting
        processes and waiting for them to finish. See `Process configuration`
        for more details about configuration related to starting processes.
        Configuration related to waiting for processes consists of `timeout`
        and `on_timeout` arguments that have same semantics as with `Wait
        For Process` keyword. By default there is no timeout, and if timeout
        is defined the default action on timeout is `terminate`.

        Returns a `result object` containing information about the execution.

        Note that possible equal signs in `*arguments` must be escaped
        with a backslash (e.g. `name\\=value`) to avoid them to be passed in
        as `**configuration`.

        Examples:
        | ${result} = | Run Process | python | -c | print 'Hello, world!' |
        | Should Be Equal | ${result.stdout} | Hello, world! |
        | ${result} = | Run Process | ${command} | stderr=STDOUT | timeout=10s |
        | ${result} = | Run Process | ${command} | timeout=1min | on_timeout=continue |
        | ${result} = | Run Process | java -Dname\\=value Example | shell=True | cwd=${EXAMPLE} |

        This command does not change the `active process`.
        `timeout` and `on_timeout` arguments are new in Robot Framework 2.8.4.
        """
        current = self._processes.current
        timeout = configuration.pop('timeout', None)
        on_timeout = configuration.pop('on_timeout', 'terminate')
        try:
            handle = self.start_process(command, *arguments, **configuration)
            return self.wait_for_process(handle, timeout, on_timeout)
        finally:
            self._processes.current = current

    def start_process(self, command, *arguments, **configuration):
        """Starts a new process on background.

        See `Specifying command and arguments` and `Process configuration`
        for more information about the arguments, and `Run Process` keyword
        for related examples.

        Makes the started process new `active process`. Returns an identifier
        that can be used as a handle to active the started process if needed.

        Starting from Robot Framework 2.8.5, processes are started so that
        they create a new process group. This allows sending signals to and
        terminating also possible child processes.
        """
        config = ProcessConfig(**configuration)
        executable_command = self._cmd(command, arguments, config.shell)
        logger.info('Starting process:\n%s' % executable_command)
        logger.debug('Process configuration:\n%s' % config)
        process = subprocess.Popen(executable_command, **config.full_config)
        self._results[process] = ExecutionResult(process, config.stdout_stream,
                                                 config.stderr_stream)
        return self._processes.register(process, alias=config.alias)

    def _cmd(self, command, args, use_shell):
        command = [encode_to_system(item) for item in [command] + list(args)]
        if not use_shell:
            return command
        if args:
            return subprocess.list2cmdline(command)
        return command[0]

    def is_process_running(self, handle=None):
        """Checks is the process running or not.

        If `handle` is not given, uses the current `active process`.

        Returns `True` if the process is still running and `False` otherwise.
        """
        return self._processes[handle].poll() is None

    def process_should_be_running(self,
                                  handle=None,
                                  error_message='Process is not running.'):
        """Verifies that the process is running.

        If `handle` is not given, uses the current `active process`.

        Fails if the process has stopped.
        """
        if not self.is_process_running(handle):
            raise AssertionError(error_message)

    def process_should_be_stopped(self,
                                  handle=None,
                                  error_message='Process is running.'):
        """Verifies that the process is not running.

        If `handle` is not given, uses the current `active process`.

        Fails if the process is still running.
        """
        if self.is_process_running(handle):
            raise AssertionError(error_message)

    def wait_for_process(self,
                         handle=None,
                         timeout=None,
                         on_timeout='continue'):
        """Waits for the process to complete or to reach the given timeout.

        The process to wait for must have been started earlier with
        `Start Process`. If `handle` is not given, uses the current
        `active process`.

        `timeout` defines the maximum time to wait for the process. It is
        interpreted according to Robot Framework User Guide Appendix
        `Time Format`, for example, '42', '42 s', or '1 minute 30 seconds'.

        `on_timeout` defines what to do if the timeout occurs. Possible values
        and corresponding actions are explained in the table below. Notice
        that reaching the timeout never fails the test.

        |  = Value =  |               = Action =               |
        | `continue`  | The process is left running (default). |
        | `terminate` | The process is gracefully terminated.  |
        | `kill`      | The process is forcefully stopped.     |

        See `Terminate Process` keyword for more details how processes are
        terminated and killed.

        If the process ends before the timeout or it is terminated or killed,
        this keyword returns a `result object` containing information about
        the execution. If the process is left running, Python `None` is
        returned instead.

        Examples:
        | # Process ends cleanly      |                  |                  |
        | ${result} =                 | Wait For Process | example          |
        | Process Should Be Stopped   | example          |                  |
        | Should Be Equal As Integers | ${result.rc}     | 0                |
        | # Process does not end      |                  |                  |
        | ${result} =                 | Wait For Process | timeout=42 secs  |
        | Process Should Be Running   |                  |                  |
        | Should Be Equal             | ${result}        | ${NONE}          |
        | # Kill non-ending process   |                  |                  |
        | ${result} =                 | Wait For Process | timeout=1min 30s | on_timeout=kill |
        | Process Should Be Stopped   |                  |                  |
        | Should Be Equal As Integers | ${result.rc}     | -9               |

        `timeout` and `on_timeout` are new in Robot Framework 2.8.2.
        """
        process = self._processes[handle]
        logger.info('Waiting for process to complete.')
        if timeout:
            timeout = timestr_to_secs(timeout)
            if not self._process_is_stopped(process, timeout):
                logger.info('Process did not complete in %s.' %
                            secs_to_timestr(timeout))
                return self._manage_process_timeout(handle, on_timeout.lower())
        return self._wait(process)

    def _manage_process_timeout(self, handle, on_timeout):
        if on_timeout == 'terminate':
            return self.terminate_process(handle)
        elif on_timeout == 'kill':
            return self.terminate_process(handle, kill=True)
        else:
            logger.info('Leaving process intact.')
            return None

    def _wait(self, process):
        result = self._results[process]
        result.rc = process.wait() or 0
        result.close_streams()
        logger.info('Process completed.')
        return result

    def terminate_process(self, handle=None, kill=False):
        """Stops the process gracefully or forcefully.

        If `handle` is not given, uses the current `active process`.

        Waits for the process to stop after terminating it. Returns
        a `result object` containing information about the execution
        similarly as `Wait For Process`.

        On Unix-like machines, by default, first tries to terminate the process
        group gracefully, but forcefully kills it if it does not stop in 30
        seconds. Kills the process group immediately if the `kill` argument is
        given any value considered true. See `Boolean arguments` section for
        more details about true and false values.

        Termination is done using `TERM (15)` signal and killing using
        `KILL (9)`. Use `Send Signal To Process` instead if you just want to
        send either of these signals without waiting for the process to stop.

        On Windows, by default, sends `CTRL_BREAK_EVENT` signal to the process
        group. If that does not stop the process in 30 seconds, or `kill`
        argument is given a true value, uses Win32 API function
        `TerminateProcess()` to kill the process forcefully. Note that
        `TerminateProcess()` does not kill possible child processes.

        | ${result} =                 | Terminate Process |     |
        | Should Be Equal As Integers | ${result.rc}      | -15 | # On Unixes |
        | Terminate Process           | myproc            | kill=true |

        *NOTE:* Stopping processes requires the
        [http://docs.python.org/2/library/subprocess.html|subprocess]
        module to have working `terminate` and `kill` functions. They were
        added in Python 2.6 and are thus missing from earlier versions.

        With Jython termination is supported starting from Jython 2.7 beta 3
        with some limitations. One problem is that everything written to the
        `standard output and error streams` before termination can be lost
        unless custom streams are used. A bigger problem is that at least
        beta 3 does not support killing the process

        Automatically killing the process if termination fails as well as
        returning a result object are new features in Robot Framework 2.8.2.
        Terminating also possible child processes, including using
        `CTRL_BREAK_EVENT` on Windows, is new in Robot Framework 2.8.5.
        """
        process = self._processes[handle]
        if not hasattr(process, 'terminate'):
            raise RuntimeError('Terminating processes is not supported '
                               'by this Python version.')
        terminator = self._kill if is_true(kill) else self._terminate
        try:
            terminator(process)
        except OSError:
            if not self._process_is_stopped(process, self.KILL_TIMEOUT):
                raise
            logger.debug('Ignored OSError because process was stopped.')
        return self._wait(process)

    def _kill(self, process):
        logger.info('Forcefully killing process.')
        if hasattr(os, 'killpg'):
            os.killpg(process.pid, signal_module.SIGKILL)
        else:
            process.kill()
        if not self._process_is_stopped(process, self.KILL_TIMEOUT):
            raise RuntimeError('Failed to kill process.')

    def _terminate(self, process):
        logger.info('Gracefully terminating process.')
        # Sends signal to the whole process group both on POSIX and on Windows
        # if supported by the interpreter.
        if hasattr(os, 'killpg'):
            os.killpg(process.pid, signal_module.SIGTERM)
        elif hasattr(signal_module, 'CTRL_BREAK_EVENT'):
            if sys.platform == 'cli':
                # https://ironpython.codeplex.com/workitem/35020
                ctypes.windll.kernel32.GenerateConsoleCtrlEvent(
                    signal_module.CTRL_BREAK_EVENT, process.pid)
            else:
                process.send_signal(signal_module.CTRL_BREAK_EVENT)
        else:
            process.terminate()
        if not self._process_is_stopped(process, self.TERMINATE_TIMEOUT):
            logger.info('Graceful termination failed.')
            self._kill(process)

    def terminate_all_processes(self, kill=False):
        """Terminates all still running processes started by this library.

        This keyword can be used in suite teardown or elsewhere to make
        sure that all processes are stopped,

        By default tries to terminate processes gracefully, but can be
        configured to forcefully kill them immediately. See `Terminate Process`
        that this keyword uses internally for more details.
        """
        for handle in range(1, len(self._processes) + 1):
            if self.is_process_running(handle):
                self.terminate_process(handle, kill=kill)
        self.__init__()

    def send_signal_to_process(self, signal, handle=None, group=False):
        """Sends the given `signal` to the specified process.

        If `handle` is not given, uses the current `active process`.

        Signal can be specified either as an integer, or anything that can
        be converted to an integer, or as a signal name. In the latter case it
        is possible to give the name both with or without a `SIG` prefix,
        but names are case-sensitive. For example, all the examples below
        send signal `INT (2)`:

        | Send Signal To Process | 2      |        | # Send to active process |
        | Send Signal To Process | INT    |        |                          |
        | Send Signal To Process | SIGINT | myproc | # Send to named process  |

        What signals are supported depends on the system. For a list of
        existing signals on your system, see the Unix man pages related to
        signal handling (typically `man signal` or `man 7 signal`).

        By default sends the signal only to the parent process, not to possible
        child processes started by it. Notice that when `running processes in
        shell`, the shell is the parent process and it depends on the system
        does the shell propagate the signal to the actual started process.
        To send the signal to the whole process group, `group` argument can
        be set to any true value:

        | Send Signal To Process | TERM  | group=yes |

        If you are stopping a process, it is often easier and safer to use
        `Terminate Process` keyword instead.

        *NOTE:* Sending signals requires the
        [http://docs.python.org/2/library/subprocess.html|subprocess]
        module to have working `send_signal` function. It was added
        in Python 2.6 and are thus missing from earlier versions.
        How well it will work with forthcoming Jython 2.7 is unknown.

        New in Robot Framework 2.8.2. Support for `group` argument is new
        in Robot Framework 2.8.5.
        """
        if os.sep == '\\':
            raise RuntimeError('This keyword does not work on Windows.')
        process = self._processes[handle]
        signum = self._get_signal_number(signal)
        logger.info('Sending signal %s (%d).' % (signal, signum))
        if is_true(group) and hasattr(os, 'killpg'):
            os.killpg(process.pid, signum)
        elif hasattr(process, 'send_signal'):
            process.send_signal(signum)
        else:
            raise RuntimeError('Sending signals is not supported '
                               'by this Python version.')

    def _get_signal_number(self, int_or_name):
        try:
            return int(int_or_name)
        except ValueError:
            return self._convert_signal_name_to_number(int_or_name)

    def _convert_signal_name_to_number(self, name):
        try:
            return getattr(signal_module,
                           name if name.startswith('SIG') else 'SIG' + name)
        except AttributeError:
            raise RuntimeError("Unsupported signal '%s'." % name)

    def get_process_id(self, handle=None):
        """Returns the process ID (pid) of the process.

        If `handle` is not given, uses the current `active process`.

        Returns the pid assigned by the operating system as an integer.
        Note that with Jython, at least with the 2.5 version, the returned
        pid seems to always be `None`.

        The pid is not the same as the identifier returned by
        `Start Process` that is used internally by this library.
        """
        return self._processes[handle].pid

    def get_process_object(self, handle=None):
        """Return the underlying `subprocess.Popen`  object.

        If `handle` is not given, uses the current `active process`.
        """
        return self._processes[handle]

    def get_process_result(self,
                           handle=None,
                           rc=False,
                           stdout=False,
                           stderr=False,
                           stdout_path=False,
                           stderr_path=False):
        """Returns the specified `result object` or some of its attributes.

        The given `handle` specifies the process whose results should be
        returned. If no `handle` is given, results of the current `active
        process` are returned. In either case, the process must have been
        finishes before this keyword can be used. In practice this means
        that processes started with `Start Process` must be finished either
        with `Wait For Process` or `Terminate Process` before using this
        keyword.

        If no other arguments than the optional `handle` are given, a whole
        `result object` is returned. If one or more of the other arguments
        are given any true value, only the specified attributes of the
        `result object` are returned. These attributes are always returned
        in the same order as arguments are specified in the keyword signature.
        See `Boolean arguments` section for more details about true and false
        values.

        Examples:
        | Run Process           | python             | -c            | print 'Hello, world!' | alias=myproc |
        | # Get result object   |                    |               |
        | ${result} =           | Get Process Result | myproc        |
        | Should Be Equal       | ${result.rc}       | ${0}          |
        | Should Be Equal       | ${result.stdout}   | Hello, world! |
        | Should Be Empty       | ${result.stderr}   |               |
        | # Get one attribute   |                    |               |
        | ${stdout} =           | Get Process Result | myproc        | stdout=true |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | # Multiple attributes |                    |               |
        | ${stdout}             | ${stderr} =        | Get Process Result |  myproc | stdout=yes | stderr=yes |
        | Should Be Equal       | ${stdout}          | Hello, world! |
        | Should Be Empty       | ${stderr}          |               |

        Although getting results of a previously executed process can be handy
        in general, the main use case for this keyword is returning results
        over the remote library interface. The remote interface does not
        support returning the whole result object, but individual attributes
        can be returned without problems.

        New in Robot Framework 2.8.2.
        """
        result = self._results[self._processes[handle]]
        if result.rc is None:
            raise RuntimeError('Getting results of unfinished processes '
                               'is not supported.')
        attributes = self._get_result_attributes(result, rc, stdout, stderr,
                                                 stdout_path, stderr_path)
        if not attributes:
            return result
        elif len(attributes) == 1:
            return attributes[0]
        return attributes

    def _get_result_attributes(self, result, *includes):
        attributes = (result.rc, result.stdout, result.stderr,
                      result.stdout_path, result.stderr_path)
        includes = (is_true(incl) for incl in includes)
        return tuple(attr for attr, incl in zip(attributes, includes) if incl)

    def switch_process(self, handle):
        """Makes the specified process the current `active process`.

        The handle can be an identifier returned by `Start Process` or
        the `alias` given to it explicitly.

        Example:
        | Start Process  | prog1    | alias=process1 |
        | Start Process  | prog2    | alias=process2 |
        | # currently active process is process2 |
        | Switch Process | process1 |
        | # now active process is process1 |
        """
        self._processes.switch(handle)

    def _process_is_stopped(self, process, timeout):
        max_time = time.time() + timeout
        while time.time() <= max_time:
            if process.poll() is not None:
                return True
            time.sleep(0.1)
        return False
Esempio n. 52
0
class REST(object):

    def __init__(self):
        self._builtin = BuiltIn()
        self._cache = ConnectionCache()

    def create_rest_session(self, alias, headers=None, auth=None, verify=False, cert=None):
        """
        Creates REST session with specified alias.

        Arguments:
        | alias | session alias |
        | headers | custom headers for all requests |
        | auth | basic auth |
        | verify | SSL verification |
        | cert | path to SSL certificate file |

        Example usage:
        | ${headers} | Create Dictionary | Content-Type | application/json |
        | @{service_basic_auth} | Set Variable | username | password |
        | Create Rest Session | session_alias | headers=${headers} | auth=${service_basic_auth} | verify=False |
        """
        session = Session()
        if headers:
            session.headers.update(headers)
        if auth:
            session.auth = tuple(auth)
        session.verify = self._builtin.convert_to_boolean(verify)
        session.cert = cert
        self._cache.register(session, alias)

    def head(self, alias, url, params=None, headers=None, cookies=None, timeout=10):
        """
        Sends HEAD request.

        Arguments:
        | alias | session alias |
        | url | service url |
        | params | request parameters |
        | headers | custom headers for request, rewrites session headers |
        | cookies | custom request cookies |
        | timeout | response timeout in seconds, raise exception on request timeout |

        Example usage:
        | ${payload} | Create Dictionary | param1 | value1 | param2 | value2 |
        | ${cookies} | Create Dictionary | sessionid | session12345 |
        | ${response} | Head | session_alias | http://localhost/service | params=${payload} | cookies=${cookies} | timeout=5 |
        """
        logger.info("Sending HEAD request to: '%s', session: '%s'" % (url, alias))
        session = self._cache.switch(alias)
        response = session.head(url, params=params, headers=headers, cookies=cookies, timeout=int(timeout))
        return {"status": response.status_code, "headers": response.headers}

    def get(self, alias, url, params=None, headers=None, cookies=None, timeout=10):
        """
        Sends GET request. See arguments description in `Head` keyword.
        """
        logger.info("Sending GET request to: '%s', session: '%s'" % (url, alias))
        session = self._cache.switch(alias)
        response = session.get(url, params=params, headers=headers, cookies=cookies, timeout=int(timeout))
        try:
            return {"status": response.status_code, "headers": response.headers, "body": response.json()}
        except ValueError:
            return {"status": response.status_code, "headers": response.headers, "body": response.content}

    def post(self, alias, url, headers=None, cookies=None, data=None, files=None, timeout=10):
        """
        Sends POST request.

        Arguments:
        | alias | session alias |
        | url | service url |
        | headers | custom headers for request, rewrites session headers |
        | cookies | custom request cookies |
        | data | dictionary, bytes, or file-like object to send in the body of the request |
        | files | dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload |
        | timeout | response timeout in seconds, raise exception on request timeout |

        Example usage:
        | @{files} | Set Variable | path_to_file_1 | path_to_file_2 |
        | ${mpe_files} | Convert To Multipart Encoded Files | ${files} |
        | ${payload} | Set Variable | {"id": "34","doc_type": "history"} |
        | ${response} | Post | service_alias | http://localhost/service | data=${payload} |
        | ${response} | Post | service_alias | http://localhost/service | files=${mpe_files} |
        """
        logger.info("Sending POST request to: '%s', session: '%s'" % (url, alias))
        session = self._cache.switch(alias)
        response = session.post(url, headers=headers, cookies=cookies, data=data.encode("utf-8"), files=files,
                                timeout=int(timeout))
        try:
            return {"status": response.status_code, "headers": response.headers, "body": response.json()}
        except ValueError:
            return {"status": response.status_code, "headers": response.headers, "body": response.content}

    def put(self, alias, url, headers=None, data=None, cookies=None, timeout=10):
        """
        Sends PUT request. See arguments description in `Post` keyword.
        """
        logger.info("Sending PUT request to: '%s', session: '%s'" % (url, alias))
        session = self._cache.switch(alias)
        response = session.put(url, headers=headers, cookies=cookies, data=data.encode("utf-8"), timeout=int(timeout))
        try:
            return {"status": response.status_code, "headers": response.headers, "body": response.json()}
        except ValueError:
            return {"status": response.status_code, "headers": response.headers, "body": response.content}

    def delete(self, alias, url, headers=None, data=None, cookies=None, timeout=10):
        """
        Sends DELETE request. See arguments description in `Post` keyword.
        """
        logger.info("Sending DELETE request to: '%s', session: '%s'" % (url, alias))
        session = self._cache.switch(alias)
        response = session.delete(url, headers=headers, cookies=cookies, data=data.encode("utf-8"),
                                  timeout=int(timeout))
        try:
            return {"status": response.status_code, "headers": response.headers, "body": response.json()}
        except ValueError:
            return {"status": response.status_code, "headers": response.headers, "body": response.content}

    def close_all_sessions(self):
        """
        Closes all created sessions.
        """
        self._cache.empty_cache()

    @staticmethod
    def convert_to_multipart_encoded_files(files):
        """
        Converts list of files to multipart encoded files.

        Example usage:
        | @{files} | Set Variable | path_to_file_1 | path_to_file_2 |
        | ${mpe_files} | Convert To Multipart Encoded Files | ${files} |
        """
        mpe_files = []
        for f in files:
            form_field_name = f[0]
            file_name = path.basename(f[1])
            file_path = f[1]
            mime_type = f[2]
            mpe_files.append((form_field_name, (file_name, open(file_path, "rb"), mime_type)))
        return mpe_files
Esempio n. 53
0
 def __init__(self, state):
     LibraryComponent.__init__(self, state)
     self._cache = ConnectionCache('No sessions.')
 def __init__(self):
     self._connectionCache = ConnectionCache()
Esempio n. 55
0
 def __init__(self):
     self._cache = ConnectionCache()
     self._builtin = BuiltIn()
class PysphereLibrary(object):
    """Robot Framework test library for VMWare interaction

    The library has the following main usages:
    - Identifying available virtual machines on a vCenter or
      ESXi host
    - Starting and stopping VMs
    - Shutting down, rebooting VM guest OS
    - Checking VM status
    - Waiting for VM tools to start running
    - Reverting VMs to a snapshot
    - Retrieving basic VM properties
    - File upload, deletion and relocation
    - Directory creation, deletion and relocation
    - Process execution and termination

    This library is essentially a wrapper around Pysphere
    http://code.google.com/p/pysphere/ adding connection
    caching consistent with other Robot Framework libraries.
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = VERSION

    def __init__(self):
        """
        """
        self._connections = ConnectionCache()
        self._vm_cache = {}


    def open_pysphere_connection(self, host, user, password, alias=None):
        """Opens a pysphere connection to the given `host`
        using the supplied `user` and `password`.

        The new connection is made active and any existing connections
        are left open in the background.

        This keyword returns the index of the new connection which
        can be used later to switch back to it. Indices start from `1`
        and are reset when `Close All Pysphere Connections` is called.

        An optional `alias` can be supplied for the connection and used
        for switching between connections. See `Switch Pysphere
        Connection` for details.

        Example:
        | ${index}= | Open Pysphere Connection | my.vcenter.server.com | username | password | alias=myserver |
        """
        server = VIServer()
        server.connect(host, user, password)
        connection_index = self._connections.register(server, alias)
        logger.info("Pysphere connection opened to host {}".format(host))
        return connection_index


    def is_connected_to_pysphere(self):
        return self._connections.current.is_connected()


    def switch_pysphere_connection(self, index_or_alias):
        """Switches the active connection by index of alias.

        `index_or_alias` is either a connection index (an integer)
        or alias (a string). Index can be obtained by capturing
        the return value from `Open Pysphere Connection` and
        alias can be set as a named variable with the same method.

        Example:
        | ${my_connection}= | Open Pysphere Connection | myhost | myuser | mypassword |
        | Open Pysphere Connection | myotherhost | myuser | mypassword | alias=otherhost |
        | Switch Pysphere Connection | ${my_connection} |
        | Power On Vm | myvm |
        | Switch Pysphere Connection | otherhost |
        | Power On Vm | myothervm |
        """
        old_index = self._connections.current_index
        if index_or_alias is not None:
            self._connections.switch(index_or_alias)
            logger.info(u"Pysphere connection switched to {}".format(index_or_alias))
        else:
            logger.info("No index or alias given, pysphere connection has not been switched.")


    def close_pysphere_connection(self):
        """Closes the current pysphere connection.

        No other connection is made active by this keyword. use
        `Switch Pysphere Connection` to switch to another connection.

        Example:
        | ${my_connection}= | Open Pysphere Connection | myhost | myuser | mypassword |
        | Power On Vm | myvm |
        | Close Pysphere Connection |

        """
        self._connections.current.disconnect()
        logger.info("Connection closed, there will no longer be a current pysphere connection.")
        self._connections.current = self._connections._no_current


    def close_all_pysphere_connections(self):
        """Closes all active pysphere connections.

        This keyword is appropriate for use in test or suite
        teardown. The assignment of connection indices resets
        after calling this keyword, and the next connection
        opened will be allocated index `1`.

        Example:
        | ${my_connection}= | Open Pysphere Connection | myhost | myuser | mypassword |
        | Open Pysphere Connection | myotherhost | myuser | mypassword | alias=otherhost |
        | Switch Pysphere Connection | ${myserver} |
        | Power On Vm | myvm |
        | Switch Pysphere Connection | otherhost |
        | Power On Vm | myothervm |
        | [Teardown] | Close All Pysphere Connections |
        """
        self._connections.close_all(closer_method='disconnect')
        logger.info("All pysphere connections closed.")


    def get_vm_names(self):
        """Returns a list of all registered VMs for the
        currently active connection.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | @{vm_names}= | Get Vm Names |
        """
        return self._connections.current.get_registered_vms()


    def get_vm_properties(self, name):
        """Returns a dictionary of the properties
        associated with the named VM.
        """
        vm = self._get_vm(name)
        return vm.get_properties(from_cache=False)


    def power_on_vm(self, name):
        """Power on the vm if it is not
        already running. This method blocks
        until the operation is completed.
        """
        if not self.vm_is_powered_on(name):
            vm = self._get_vm(name)
            vm.power_on()
            logger.info(u"VM {} powered on.".format(name))
        else:
            logger.info(u"VM {} was already powered on.".format(name))


    def power_off_vm(self, name):
        """Power off the vm if it is not
        already powered off. This method blocks
        until the operation is completed.
        """
        if not self.vm_is_powered_off(name):
            vm = self._get_vm(name)
            vm.power_off()
            logger.info(u"VM {} powered off.".format(name))
        else:
            logger.info(u"VM {} was already powered off.".format(name))


    def reset_vm(self, name):
        """Perform a reset on the VM. This
        method blocks until the operation is
        completed.
        """
        vm = self._get_vm(name)
        vm.reset()
        logger.info(u"VM {} reset.".format(name))


    def shutdown_vm_os(self, name):
        """Initiate a shutdown in the guest OS
        in the VM, returning immediately.
        """
        vm = self._get_vm(name)
        vm.shutdown_guest()
        logger.info(u"VM {} shutdown initiated.".format(name))


    def reboot_vm_os(self, name):
        """Initiate a reboot in the guest OS
        in the VM, returning immediately.
        """
        vm = self._get_vm(name)
        vm.reboot_guest()
        logger.info(u"VM {} reboot initiated.".format(name))


    def vm_is_powered_on(self, name):
        """Returns true if the VM is in the
        powered on state.
        """
        vm = self._get_vm(name)
        return vm.is_powered_on()


    def vm_is_powered_off(self, name):
        """Returns true if the VM is in the
        powered off state.
        """
        vm = self._get_vm(name)
        return vm.is_powered_off()


    def vm_wait_for_tools(self, name, timeout=120):
        """Waits for up to the `timeout` interval for the VM tools to start
        running on the named VM. VMware tools must be running on the VM for the
        `Vm Login In Guest` keyword to succeed.
        """
        vm = self._get_vm(name)
        vm.wait_for_tools(timeout)
        logger.info(u"VM tools are running on {}.".format(name))


    def vm_login_in_guest(self, name, username, password):
        """Logs into the named VM with the specified `username` and `password`.
        The VM must be powered on and the VM tools must be running on the VM,
        which can be verified using the `Vm Wait For Tools` keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Power On Vm | myvm |
        | Vm Wait For Tools | myvm |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        """
        vm = self._get_vm(name)
        vm.login_in_guest(username, password)
        logger.info(u"Logged into VM {}.".format(name))


    def vm_make_directory(self, name, path):
        """Creates a directory with the specified `path` on the named VM. The
        `Vm Login In Guest` keyword must precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | Vm Make Directory | myvm | C:\\some\\directory\\path |
        """
        vm = self._get_vm(name)
        vm.make_directory(path, True)
        logger.info(u"Created directory {} on VM {}.".format(path, name))


    def vm_move_directory(self, name, src_path, dst_path):
        """Moves or renames a directory from `src_path` to `dst_path` on the
        named VM. The `Vm Login In Guest` keyword must precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | Vm Move Directory | myvm | C:\\directory1 | C:\\directory2 |
        """
        vm = self._get_vm(name)
        vm.move_directory(src_path, dst_path)
        logger.info(u"Moved directory {} to {} on VM {}.".format(
            src_path, dst_path, name))


    def vm_delete_directory(self, name, path):
        """Deletes the directory with the given `path` on the named VM,
        including its contents. The `Vm Login In Guest` keyword must precede
        this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | Vm Delete Directory | myvm | C:\\directory |
        """
        vm = self._get_vm(name)
        vm.delete_directory(path, True)
        logger.info(u"Deleted directory {} on VM {}.".format(path, name))


    def vm_get_file(self, name, remote_path, local_path):
        """Downloads a file from the `remote_path` on the named VM to the
        specified `local_path`, overwriting any existing local file. The
        `Vm Login In Guest` keyword must precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | Vm Get File | myvm | C:\\remote\\location.txt | C:\\local\\location.txt |
        """
        vm = self._get_vm(name)
        vm.get_file(remote_path, local_path, True)
        logger.info(u"Downloaded file {} on VM {} to {}.".format(
            remote_path, name, local_path))


    def vm_send_file(self, name, local_path, remote_path):
        """Uploads a file from `local_path` to the specified `remote_path` on
        the named VM, overwriting any existing remote file. The
        `Vm Login In Guest` keyword must precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | Vm Send File | myvm | C:\\local\\location.txt | C:\\remote\\location.txt |
        """
        local_path = os.path.abspath(local_path)
        logger.info(u"Uploading file {} to {} on VM {}.".format(
            local_path, remote_path, name))
        vm = self._get_vm(name)
        vm.send_file(local_path, remote_path, True)
        logger.info(u"Uploaded file {} to {} on VM {}.".format(
            local_path, remote_path, name))


    def vm_move_file(self, name, src_path, dst_path):
        """Moves a remote file on the named VM from `src_path` to `dst_path`,
        overwriting any existing file at the target location. The
        `Vm Login In Guest` keyword must precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | Vm Move File | myvm | C:\\original_location.txt | C:\\new_location.txt |
        """
        vm = self._get_vm(name)
        vm.move_file(src_path, dst_path, True)
        logger.info(u"Moved file from {} to {} on VM {}.".format(
            src_path, dst_path, name))


    def vm_delete_file(self, name, remote_path):
        """Deletes the file with the given `remote_path` on the named VM. The
        `Vm Login In Guest` keyword must precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | Vm Delete File | myvm | C:\\remote_file.txt |
        """
        vm = self._get_vm(name)
        vm.delete_file(remote_path)
        logger.info(u"Deleted file {} from VM {}.".format(remote_path, name))


    def vm_start_process(self, name, cwd, program_path, *args, **kwargs):
        """Starts a program in the named VM with the working directory specified
        by `cwd`. Returns the process PID. The `Vm Login In Guest` keyword must
        precede this keyword.

        The optional `env` argument can be used to provide a dictionary
        containing environment variables to be set for the program
        being run.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | ${pid}= | Vm Start Process | myvm | C:\\ | C:\\windows\\system32\\cmd.exe | /c | echo | hello world |
        """
        env = kwargs.get('env', None)
        logger.info(u"Starting process '{} {}' on VM {} cwd={} env={}".format(
            program_path, " ".join(args), name, cwd, env))
        vm = self._get_vm(name)
        pid = vm.start_process(program_path, args, env, cwd)
        logger.info(u"Process '{} {}' running on VM {} with pid={} cwd={} env={}".format(
            program_path, " ".join(args), name, pid, cwd, env))
        return pid


    def vm_run_synchronous_process(self, name, cwd, program_path, *args, **kwargs):
        """Executes a process on the named VM and blocks until the process has
        completed. Parameters are the same as for `vm_start_process`. Returns
        the exit code of the process. The `Vm Login In Guest` keyword must
        precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | ${rc}= | Vm Run Synchronous Process | myvm | C:\\ | C:\\windows\\system32\\cmd.exe | /c | echo | hello world |
        | Should Be Equal As Integers | ${rc} | 0 |
        """
        pid = self.vm_start_process(name, cwd, program_path, *args, **kwargs)

        vm = self._get_vm(name)
        while True:
            processes = [x for x in vm.list_processes() if x["pid"] == pid]

            if len(processes) != 1:
                raise Exception("Process terminated and could not retrieve exit code")

            process = processes[0]

            if process['end_time'] != None:
                logger.info(u"Process completed on {}: {}".format(name, repr(process)))
                return process['exit_code']

            time.sleep(2)


    def vm_terminate_process(self, name, pid):
        """Terminates the process with the given `pid` on the named VM. The
        `Vm Login In Guest` keyword must precede this keyword.

        Example:
        | Open Pysphere Connection | myhost | myuser | mypassword |
        | Vm Login In Guest | myvm | vm_username | vm_password |
        | ${pid}= | Vm Start Process | myvm | C:\\ | C:\\windows\\system32\\cmd.exe | /c | pause |
        | Vm Terminate Process | myvm | ${pid} |
        """
        pid = int(pid)
        vm = self._get_vm(name)
        vm.terminate_process(pid)
        logger.info(u"Process with pid {} terminated on VM {}".format(pid, name))


    def revert_vm_to_snapshot(self, name, snapshot_name=None):
        """Revert the named VM to a snapshot. If `snapshot_name`
        is supplied it is reverted to that snapshot, otherwise
        it is reverted to the current snapshot. This method
        blocks until the operation is completed.
        """
        vm = self._get_vm(name)
        if snapshot_name is None:
            vm.revert_to_snapshot()
            logger.info(u"VM {} reverted to current snapshot.".format(name))
        else:
            vm.revert_to_named_snapshot(snapshot_name)
            logger.info(u"VM {} reverted to snapshot {}.".format(
                name, snapshot_name))


    def _get_vm(self, name):
        if name not in self._vm_cache or not self._vm_cache[name]._server.keep_session_alive():
            logger.debug(u"VM {} not in cache or vcenter connection expired.".format(name))
        connection = self._connections.current
        if isinstance(name, unicode):
            name = name.encode("utf8")
            self._vm_cache[name] = connection.get_vm_by_name(name)
        else:
            logger.debug(u"VM {} already in cache.".format(name))

        return self._vm_cache[name]
Esempio n. 57
0
 def __init__(self):
     self._cache = ConnectionCache()
     self.dict_alias = {}
class ConnectionManager(object):
    """
    Class that handles connection/disconnection to databases.
    """

    def __init__(self):
        self._connectionCache = ConnectionCache()

    def connect_to_database(self, driverName=None, dbName=None, username=None,
                            password=None, host='localhost',
                            port="5432", alias=None):
        """
        Connects to database.

        *Arguments:*
            - driverName: string, name of python database driver.
            - dbName: string, name of database.
            - username: string, name of user.
            - password: string, user password.
            - host: string, database host.
            - port: int, database port.
            - alias: string, database alias for future use.

        *Return:*
            - None

        *Examples:*
        | Connect To Database | psycopg2 | PyDB | username | password \
        | localhost | 5432 | SomeCompanyDB |
        """

        if isinstance(driverName, str):
            dbModule = __import__(driverName)
        else:
            dbModule = driverName
            driverName = 'Mock DB Driver'

        connParams = {'database': dbName, 'user': username,
                      'password': password, 'host': host, 'port': port}
        if driverName in ("MySQLdb", "pymysql"):
            connParams = {'db': dbName, 'user': username, 'passwd': password,
                          'host': host, 'port': port}
        elif driverName in ("psycopg2"):
            connParams = {'database': dbName, 'user': username,
                          'password': password, 'host': host, 'port': port}

        connStr = ['%s: %s' % (k, str(connParams[k])) for k in connParams]
        logger.debug('Connect using: %s' % ', '.join(connStr))

        dbConnection = _Connection(driverName, dbModule.connect(**connParams))

        self._connectionCache.register(dbConnection, alias)
        logger.info("Established connection to the %s database. "
                    "Alias %s. Driver name: %s." % (dbName, alias, driverName))

    def disconnect_from_database(self):
        """
        Disconnects from database.

        *Arguments:*
            - None

        *Return:*
            - None

        *Examples:*
        | Disconnect From Database |
        """

        if self._connectionCache.current:
            self._connectionCache.current.close()

            curIndex = self._connectionCache.current_index
            aliasesCache = self._connectionCache._aliases
            if curIndex in aliasesCache.values():
                keyForDeletion = \
                    aliasesCache.keys()[aliasesCache.values().index(curIndex)]
                del self._connectionCache._aliases[keyForDeletion]

            self._connectionCache.current = self._connectionCache._no_current
            logger.info("Current database was disconnected.")
            cls_attr = getattr(type(self._connectionCache),
                               'current_index', None)
            if isinstance(cls_attr, property) and cls_attr.fset is not None:
                self._connectionCache.current_index = None

    def disconnect_from_all_databases(self):
        """
        Disconnects from all previously opened databases.

        *Arguments:*
            - None

        *Return:*
            - None

        *Examples:*
        | Disconnect From All Databases |
        """

        self._connectionCache.close_all('close')
        logger.info("All databases were disconnected.")

    def set_current_database(self, aliasOrIndex):
        """
        Sets current database by alias or index.

        *Arguments:*
            - aliasOrIndex: int or string, alias or index of opened database.

        *Return:*
            - None

        *Examples:*
        | # Using index |
        | Set Current Database | 1 |
        | # Using alias |
        | Set Current Database | SomeCompanyDB |
        """

        self._connectionCache.switch(aliasOrIndex)
        logger.info("Connection switched to the %s database." % aliasOrIndex)