class CouchbaseManager(object):
    """
    Library for managing Couchbase server.

    Based on:
    [ http://docs.couchbase.com/couchbase-manual-2.5/cb-rest-api/ | Using the REST API ]

    == Dependencies ==
        | robot framework | http://robotframework.org |

    == Example ==
        | *Settings* | *Value* |
        | Library    | CouchbaseManager |
        | Library    | Collections |

        | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* | *Argument* | *Argument* |
        | Simple |
        |    | Connect To Couchbase | my_host_name | 8091 | administrator | administrator | alias=couchbase |
        |    | ${overview}= | Overview |
        |    | Log Dictionary | ${overview} |
        |    | Close All Couchbase Connections |
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    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

    @property
    def connection(self) -> RequestConnection:
        """Check and return connection to Couchbase server.

        *Raises:*\n
            RuntimeError: if connection to Couchbase server hasn't been created yet.

        *Returns:*\n
            Current connection to Couchbase server.
        """
        if self._connection is None:
            raise RuntimeError(
                'There is no open connection to Couchbase server.')
        return self._connection

    def connect_to_couchbase(self,
                             host: str,
                             port: Union[int, str],
                             username: str = 'administrator',
                             password: str = 'administrator',
                             timeout: Union[int, str] = 15,
                             alias: str = None) -> int:
        """
        Create connection to Couchbase server and set it as active connection.

        *Args:*\n
            _host_ - hostname;\n
            _port_ - port address;\n
            _username_ - username;\n
            _password_ - user password;\n
            _timeout_ - connection attempt timeout;\n
            _alias_ - connection alias;\n

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

        *Example:*\n
            | Connect To Couchbase | my_host_name | 8091 | administrator | administrator | alias=rmq |
        """

        port = int(port)
        timeout = int(timeout)
        logger.debug(
            f'Connecting using : host={host}, port={port}, username={username},'
            f'password={password}, timeout={timeout}, alias={alias}')

        self._connection = RequestConnection(host, port, username, password,
                                             timeout)
        return self._cache.register(self.connection, alias)

    def switch_couchbase_connection(self, index_or_alias: Union[int,
                                                                str]) -> int:
        """
        Switch to another existing Couchbase connection using its index or alias.\n
        Connection alias is set in keyword [#Connect To Couchbase|Connect To Couchbase], which also returns connection index.

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

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

        *Example:*\n
            | Connect To Couchbase | my_host_name_1 | 8091 | administrator | administrator | alias=couchbase1 |
            | Connect To Couchbase | my_host_name_2 | 8091 | administrator | administrator | alias=couchbase2 |
            | Switch Couchbase Connection | couchbase1 |
            | ${overview}= | Overview |
            | Switch Couchbase Connection | couchbase2 |
            | ${overview}= | Overview |
            | Close All Couchbase Connections |
        """

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

    def disconnect_from_couchbase(self) -> None:
        """
        Close active Couchbase connection.

        *Example:*\n
            | Connect To Couchbase | my_host_name | 8091 | administrator | administrator | alias=couchbase |
            | Disconnect From Couchbase |
        """
        logger.debug(
            f'Close connection with : host={self.connection.host}, port={self.connection.port}'
        )
        self.connection.close()

    def close_all_couchbase_connections(self) -> None:
        """
        Close all open Couchbase connections.\n
        You should not use [#Disconnect From Couchbase|Disconnect From Couchbase] and
        [#Close All Couchbase Connections|Close All Couchbase Connections] together.\n
        After executing this keyword connection indexes returned by opening new connections
        [#Connect To Couchbase |Connect To Couchbase] starts from 1.\n

        *Example:*\n
            | Connect To Couchbase | my_host_name | 8091 | administrator | administrator | alias=couchbase |
            | Close All Couchbase Connections |
        """

        self._connection = self._cache.close_all()

    def _prepare_request_headers(self, body: Any = None) -> Dict[str, str]:
        """
        Prepare headers for HTTP request.

        Args:*\n
            _body_: HTTP request body.\n

        *Returns:*\n
            Dictionary with HTTP request headers\n
        """
        headers = self.headers.copy()
        headers["Accept"] = "application/json"
        if body:
            headers["Content-Type"] = "application/x-www-form-urlencoded"
        return headers

    def overview(self) -> Dict[str, Any]:
        """
        Get overview info on Couchbase server.

        *Returns:*\n
            Dictionary with overview info.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
            | ${overview}=  |  Overview |
            | Log Dictionary  |  ${overview} |
            | ${version}=  |  Get From Dictionary | ${overview}  |  implementationVersion |
            =>\n
            | ${version} = 2.2.0-821-rel-enterprise
        """
        url = f'{self.connection.url}/pools/'
        response = requests.get(url,
                                auth=self.connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self.connection.timeout)
        response.raise_for_status()
        return response.json()

    def view_all_buckets(self) -> List[Dict[str, Any]]:
        """
        Retrieve information on all buckets and their operations.

        *Returns:*\n
            List with buckets information

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
            | ${buckets}=  |  View all buckets  |  default |
            | Log list  |  ${buckets} |
            =>\n
            | List length is 3 and it contains following items:
            | 0: {u'bucketType': u'membase', u'localRandomKeyUri'
            | ...
        """
        url = f'{self.connection.url}/pools/{self.pool}/buckets'
        response = requests.get(url,
                                auth=self.connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self.connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_names_of_all_buckets(self) -> List[str]:
        """
        Retrieve all bucket names for active pool.

        *Returns:*\n
            List with bucket names.

        *Example:*\n
            | ${names}=  |   Get names of all buckets  |  default |
            | Log list  |  ${names} |
            =>\n
            | 0: default
            | 1: ufm
            | 2: ufm_support
        """

        names = []
        data = self.view_all_buckets()
        for item in data:
            names.append(item['name'])
        return names

    def flush_bucket(self, bucket: str) -> None:
        """
        Flush specified bucket.

        *Args:*\n
            _bucket_ - bucket name;\n

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
            | Flush bucket  |  default |

        """
        url = f'{self.connection.url}/pools/{self.pool}/buckets/{bucket}/controller/doFlush'
        response = requests.post(url,
                                 auth=self.connection.auth,
                                 headers=self._prepare_request_headers(),
                                 timeout=self.connection.timeout)
        response.raise_for_status()

    def modify_bucket_parameters(self, bucket: str, **kwargs: Any) -> None:
        """
        Modify bucket parameters.

        *Args:*\n
            _bucket_ - bucket name;\n
            _**kwargs_ - bucket parameters, parameter_name=value; parameter list can be found in
                         [http://docs.couchbase.com/couchbase-manual-2.5/cb-rest-api/#modifying-bucket-parameters| Couchbase doc]

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
            | Modify bucket parameters  |  default  |  flushEnabled=1  |  ramQuotaMB=297 |
        """
        url = f'{self.connection.url}/pools/{self.pool}/buckets/{bucket}'
        response = requests.post(
            url,
            auth=self.connection.auth,
            data=kwargs,
            headers=self._prepare_request_headers(body=kwargs),
            timeout=self.connection.timeout)
        response.raise_for_status()
Exemple #2
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
Exemple #3
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
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 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)
class OracleDB(object):
    """
    Robot Framework library for working with Oracle DB.

    == Dependencies ==
    | cx_Oracle | http://cx-oracle.sourceforge.net | version >= 5.3 |
    | robot framework | http://robotframework.org |
    """

    DEFAULT_TIMEOUT = 900.0  # The default timeout for executing an SQL query is 15 minutes
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    last_executed_statement: Optional[str] = None
    last_executed_statement_params: Optional[Dict[str, Any]] = None
    last_used_connection_index: Optional[int] = None

    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()

    @property
    def connection(self) -> cx_Oracle.Connection:
        """Get current connection to Oracle database.

        *Raises:*\n
            RuntimeError: if there isn't any open connection.

        *Returns:*\n
            Current connection to the database.
        """
        if self._connection is None:
            raise RuntimeError(
                'There is no open connection to Oracle database.')
        return self._connection

    def make_dsn(self,
                 host: str,
                 port: str,
                 sid: str,
                 service_name: str = '') -> str:
        """
        Build dsn string for use in connection.

        *Args:*\n
            host - database host;\n
            port - database port;\n
            sid - database sid;\n
            service_name - database service name;\n

        *Returns:*\n
            Returns dsn string.
        """
        return cx_Oracle.makedsn(host=host,
                                 port=port,
                                 sid=sid,
                                 service_name=service_name)

    def connect_to_oracle(self,
                          dbname: str,
                          dbusername: str,
                          dbpassword: str = None,
                          alias: str = None) -> int:
        """
        Connection to Oracle DB.

        *Args:*\n
            _dbname_ - database name;\n
            _dbusername_ - username for db connection;\n
            _dbpassword_ - password for db connection;\n
            _alias_ - connection alias, used for switching between open connections;\n

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

        *Example:*\n
            | Connect To Oracle  |  rb60db  |  bis  |  password |
        """

        try:
            logger.debug(
                f'Connecting using : dbname={dbname}, dbusername={dbusername}, dbpassword={dbpassword}'
            )
            connection_string = f'{dbusername}/{dbpassword}@{dbname}'
            self._connection = cx_Oracle.connect(connection_string)
            return self._cache.register(self.connection, alias)
        except cx_Oracle.DatabaseError as err:
            raise Exception("Logon to oracle  Error:", str(err))

    def disconnect_from_oracle(self) -> None:
        """
        Close active Oracle connection.

        *Example:*\n
            | Connect To Oracle  |  rb60db  |  bis  |  password |
            | Disconnect From Oracle |
        """

        self.connection.close()
        self._cache.empty_cache()

    def close_all_oracle_connections(self) -> None:
        """
        Close all Oracle connections that were opened.
        You should not use [#Disconnect From Oracle|Disconnect From Oracle] and [#Close All Oracle Connections|Close All Oracle Connections]
        together.
        After calling this keyword connection IDs returned by opening new connections [#Connect To Oracle|Connect To Oracle],
        will start from 1.

        *Example:*\n
            | Connect To Oracle  |  rb60db  |  bis |   password  |  alias=bis |
            | Connect To Oracle  |  rb60db  |  bis_dcs  |  password  |  alias=bis_dsc |
            | Switch Oracle Connection  |  bis |
            | @{sql_out_bis}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Switch Oracle Connection  |  bis_dsc |
            | @{sql_out_bis_dsc}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Close All Oracle Connections |
        """

        self._connection = self._cache.close_all()

    def switch_oracle_connection(self, index_or_alias: Union[int, str]) -> int:
        """
        Switch between existing Oracle connections using their connection IDs or aliases.
        The connection ID is obtained on creating connection.
        Connection alias is optional and can be set at connecting to DB [#Connect To Oracle|Connect To Oracle].

        *Args:*\n
            _index_or_alias_ - connection ID or alias assigned to connection;

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

        *Example:* (switch by alias)\n
            | Connect To Oracle  |  rb60db  |  bis |   password  |  alias=bis |
            | Connect To Oracle  |  rb60db  |  bis_dcs  |  password  |  alias=bis_dsc |
            | Switch Oracle Connection  |  bis |
            | @{sql_out_bis}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Switch Oracle Connection  |  bis_dsc |
            | @{sql_out_bis_dsc}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Close All Oracle Connections |
            =>\n
            @{sql_out_bis} = BIS\n
            @{sql_out_bis_dcs}= BIS_DCS

        *Example:* (switch by index)\n
            | ${bis_index}=  |  Connect To Oracle  |  rb60db  |  bis  |  password  |
            | ${bis_dcs_index}=  |  Connect To Oracle  |  rb60db  |  bis_dcs  |  password |
            | @{sql_out_bis_dcs_1}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | ${previous_index}=  |  Switch Oracle Connection  |  ${bis_index} |
            | @{sql_out_bis}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Switch Oracle Connection  |  ${previous_index} |
            | @{sql_out_bis_dcs_2}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Close All Oracle Connections |
            =>\n
            ${bis_index}= 1\n
            ${bis_dcs_index}= 2\n
            @{sql_out_bis_dcs_1} = BIS_DCS\n
            ${previous_index}= 2\n
            @{sql_out_bis} = BIS\n
            @{sql_out_bis_dcs_2}= BIS_DCS
        """

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

    @staticmethod
    def wrap_into_html_details(statement: str, summary: str) -> str:
        """Format statement for html logging.

        *Args:*\n
            _statement_: statement to log.
            _summary_: summary for details tag.

        *Returns:*\n
            Formatted statement.
        """
        statement = sqlparse.format(statement,
                                    reindent=True,
                                    indent_width=4,
                                    keyword_case='upper')
        statement_html = escape(statement)
        data = f'<details><summary>{summary}</summary><p>{statement_html}</p></details>'
        return data

    def _execute_sql(self, cursor: cx_Oracle.Cursor, statement: str,
                     params: Dict[str, Any]) -> cx_Oracle.Cursor:
        """ Execute SQL query on Oracle DB using active connection.

        *Args*:\n
            _cursor_: cursor object.\n
            _statement_: SQL query to be executed.\n
            _params_: SQL query parameters.\n

        *Returns:*\n
            Query results.
        """
        statement_with_params = self._replace_parameters_in_statement(
            statement, params)
        _connection_info = '@'.join(
            (cursor.connection.username, cursor.connection.dsn))
        data = self.wrap_into_html_details(
            statement=statement_with_params,
            summary=f'Executed PL/SQL statement on {_connection_info}')
        logger.info(data, html=True)
        cursor.prepare(statement)
        self.last_executed_statement = self._replace_parameters_in_statement(
            statement, params)
        self.last_used_connection_index = self._cache.current_index
        cursor.execute(None, params)

    @staticmethod
    def _get_timeout_from_execution_context() -> float:
        """Get timeout from Robot Framework execution context.

        Returns:
            Current timeout value in seconds or None if timeout is not set.
        """
        timeouts = {}
        default_timeout = OracleDB.DEFAULT_TIMEOUT
        for timeout in EXECUTION_CONTEXTS.current.timeouts:
            if timeout.active:
                timeouts[timeout.type] = timeout.time_left()

        if timeouts.get(KeywordTimeout.type, None):
            return timeouts[KeywordTimeout.type]
        test_timeout = timeouts.get(TestTimeout.type, None)
        return test_timeout if test_timeout and test_timeout < default_timeout else default_timeout

    def _replace_parameters_in_statement(self, statement: str,
                                         params: Dict[str, Any]) -> str:
        """Update SQL query parameters, if any exist, with their values for logging purposes.

        *Args*:\n
            _statement_: SQL query to be updated.\n
            _params_: SQL query parameters.\n

        *Returns:*\n
            SQL query with parameter names replaced with their values.
        """
        params_keys = sorted(params.keys(), reverse=True)
        for key in params_keys:
            if isinstance(params[key], (int, float)):
                statement = statement.replace(f':{key}', str(params[key]))
            elif params[key] is None:
                statement = statement.replace(f':{key}', 'NULL')
            else:
                statement = statement.replace(f':{key}', f"'{params[key]}'")
        return statement

    def execute_plsql_block(self, plsqlstatement: str, **params: Any) -> None:
        """
        PL/SQL block execution.

        *Args:*\n
            _plsqlstatement_ - PL/SQL block;\n
            _params_ - PL/SQL block parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run

        *Returns:*\n
            PL/SQL block execution result.

        *Example:*\n
            | *Settings* | *Value* |
            | Library    |       OracleDB |

            | *Variables* | *Value* |
            | ${var_failed}    |       3 |

            | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* |
            | Simple |
            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := ${var_failed}; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |    END; |
            |    | Execute Plsql Block   |  plsqlstatement=${statement} |
            =>\n
            DatabaseError: ORA-20001: This is a custom error

            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := :var; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |    END; |
            |    | Execute Plsql Block   |  plsqlstatement=${statement} | var=${var_failed} |
            =>\n
            DatabaseError: ORA-20001: This is a custom error
        """
        cursor = self.connection.cursor()
        with sql_timeout(timeout=self._get_timeout_from_execution_context(),
                         connection=cursor.connection):
            try:
                self._execute_sql(cursor, plsqlstatement, params)
                self.connection.commit()
            finally:
                self.connection.rollback()

    def execute_plsql_block_with_dbms_output(self, plsqlstatement: str,
                                             **params: Any) -> List[str]:
        """
        Execute PL/SQL block with dbms_output().

        *Args:*\n
            _plsqlstatement_ - PL/SQL block;\n
            _params_ - PL/SQL block parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run.

        *Returns:*\n
            List of values returned by Oracle dbms_output.put_line().

        *Example:*\n
            | *Settings* | *Value* |
            | Library    |       OracleDB |

            | *Variables* | *Value* |
            | ${var}    |       4 |

            | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* |
            | Simple |
            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := ${var}; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |       dbms_output.put_line ('text '||a||', e-mail text'); |
            |    | ...            |             |                     |       dbms_output.put_line ('string 2 '); |
            |    | ...            |             |                     |    END; |
            |    | @{dbms}=       | Execute Plsql Block With Dbms Output   |  plsqlstatement=${statement} |
            =>\n
            | @{dbms} | text 5, e-mail text |
            | | string 2 |

            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := :var; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |       dbms_output.put_line ('text '||a||', e-mail text'); |
            |    | ...            |             |                     |       dbms_output.put_line ('string 2 '); |
            |    | ...            |             |                     |    END; |
            |    | @{dbms}=       | Execute Plsql Block With Dbms Output   |  plsqlstatement=${statement} |  var=${var} |
            =>\n
            | @{dbms} | text 5, e-mail text |
            | | string 2 |
        """
        dbms_output = []
        cursor = self.connection.cursor()
        with sql_timeout(timeout=self._get_timeout_from_execution_context(),
                         connection=cursor.connection):
            try:
                cursor.callproc("dbms_output.enable")
                self._execute_sql(cursor, plsqlstatement, params)
                self.connection.commit()
                statusvar = cursor.var(cx_Oracle.NUMBER)
                linevar = cursor.var(cx_Oracle.STRING)
                while True:
                    cursor.callproc("dbms_output.get_line",
                                    (linevar, statusvar))
                    if statusvar.getvalue() != 0:
                        break
                    dbms_output.append(linevar.getvalue())
                return dbms_output
            finally:
                self.connection.rollback()

    def execute_plsql_script(self, file_path: str, **params: Any) -> None:
        """
         Execution of PL/SQL code from file.

        *Args:*\n
            _file_path_ - path to PL/SQL script file;\n
            _params_ - PL/SQL code parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run.

        *Example:*\n
            |  Execute Plsql Script  |  ${CURDIR}${/}plsql_script.sql |
            |  Execute Plsql Script  |  ${CURDIR}${/}plsql_script.sql | first_param=1 | second_param=2 |
        """

        with open(file_path, "r") as script:
            data = script.read()
            self.execute_plsql_block(data, **params)

    def execute_sql_string(self, plsqlstatement: str,
                           **params: Any) -> List[Tuple[Any, ...]]:
        """
        Execute PL/SQL string.

        *Args:*\n
            _plsqlstatement_ - PL/SQL string;\n
            _params_ - PL/SQL string parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run.

        *Returns:*\n
            PL/SQL string execution result.

        *Example:*\n
            | @{query}= | Execute Sql String | select sysdate, sysdate+1 from dual |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][0]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][1]} |

            | @{query}= | Execute Sql String | select sysdate, sysdate+:d from dual | d=1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][0]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][1]} |
        """
        cursor = self.connection.cursor()
        with sql_timeout(timeout=self._get_timeout_from_execution_context(),
                         connection=cursor.connection):
            try:
                self._execute_sql(cursor, plsqlstatement, params)
                query_result = cursor.fetchall()
                self.result_logger(query_result)
                return query_result
            finally:
                self.connection.rollback()

    def execute_sql_string_mapped(self, sql_statement: str,
                                  **params: Any) -> List[Dict[str, Any]]:
        """SQL query execution where each result row is mapped as a dict with column names as keys.

        *Args:*\n
            _sql_statement_ - PL/SQL string;\n
            _params_ - PL/SQL string parameters;\n

        *Returns:*\n
            A list of dictionaries where column names are mapped as keys.

        *Example:*\n
            | @{query}= | Execute Sql String Mapped| select sysdate, sysdate+1 from dual |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][sysdate]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][sysdate1]} |

            | @{query}= | Execute Sql String Mapped| select sysdate, sysdate+:d from dual | d=1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][sysdate]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][sysdate1]} |
        """
        cursor = self.connection.cursor()
        with sql_timeout(timeout=self._get_timeout_from_execution_context(),
                         connection=cursor.connection):
            try:
                self._execute_sql(cursor, sql_statement, params)
                col_name = tuple(i[0] for i in cursor.description)
                query_result = [dict(zip(col_name, row)) for row in cursor]
                self.result_logger(query_result)
                return query_result
            finally:
                self.connection.rollback()

    def execute_sql_string_generator(
            self, sql_statement: str,
            **params: Any) -> Iterable[Dict[str, Any]]:
        """Generator that yields each result row mapped as a dict with column names as keys.\n
        Intended for use mainly in code for other keywords.
        *If used, the generator must be explicitly closed before closing DB connection*

        *Args:*\n
            _sql_statement_ - PL/SQL string;\n
            _params_ - PL/SQL string parameters;\n

        Yields:*\n
            results dict.
        """
        self.last_executed_statement = sql_statement
        self.last_executed_statement_params = params
        cursor = self.connection.cursor()
        with sql_timeout(timeout=self._get_timeout_from_execution_context(),
                         connection=cursor.connection):
            try:
                self._execute_sql(cursor, sql_statement, params)
                col_name = tuple(i[0] for i in cursor.description)
                for row in cursor:
                    yield dict(zip(col_name, row))
            finally:
                self.connection.rollback()

    def result_logger(self,
                      query_result: List[Any],
                      result_amount: int = 10) -> None:
        """Log first n rows from the query results

        *Args:*\n
            _query_result_ - query result to log, must be greater than 0
            _result_amount_ - amount of entries to display from result
        """
        if len(query_result) > result_amount > 0:
            query_result = query_result[:result_amount]
        logged_result = self.wrap_into_html_details(str(query_result),
                                                    "SQL Query Result")
        logger.info(logged_result, html=True)

    @contextmanager
    def use_connection(self, conn_index: Union[int, str]) -> Iterator[None]:
        """Context manager for switching connection.

        Args:
            conn_index: Connection index or alias to switch.

        Yields: generator.
        """
        _old_con_index = self.switch_oracle_connection(conn_index)
        yield
        self.switch_oracle_connection(_old_con_index)
Exemple #7
0
class SerialOperate(object):
    
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = __version__

    DEFAULT_TIMEOUT = 0.1
    DEFAULT_NEWLINE = 'LF'
    DEFAULT_PROMPT = '>'
    DEFAULT_LOGLEVEL = 'INFO'
    DEFAULT_TERM_TYPE = 'vt100'
    DEFAULT_TERM_WIDTH = 80
    DEFAULT_TERM_HEIGHT = 24
    DEFAULT_PATH_SEPARATOR = '/'
    DEFAULT_ENCODING = 'UTF-8'
    
    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"
        
    @property
    def current(self):
        return self._connections.current
    
    
    def open_serial_connection(self, port='com1',baudrate = 115200, alias=None, timeout=None,
                        newline=None, prompt=None, term_type=None, width=None,
                        height=None, path_separator=None, encoding=None):
        """Opens a new serial connection to the given `host` and `port`.

        The new connection is made active. Possible 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 Connections` is used.

        Optional `alias` can be given for the connection and can be used for
        switching between connections, similarly as the index.
        See `Switch Connection` for more details.

        Connection parameters, like [#Default timeout|`timeout`] and
        [#Default newline|`newline`] are documented in `configuration`.
        If they are not defined as arguments, [#Configuration|the library
        defaults] are used for the connection.

        All the arguments, except `host`, `alias` and `port`
        can be later updated with `Set Client Configuration`.

        Starting from SerialLibrary 1.1, a shell is automatically opened
        by this keyword.

        Port `22` is assumed by default:
        | ${index}= | Open Connection | my.server.com |

        Non-standard port may be given as an argument:
        | ${index}= | Open Connection | 192.168.1.1 | port=23 |

        Aliases are handy, if you need to switch back to the connection later:
        | Open Connection   | my.server.com | alias=myserver |
        | # Do something with my.server.com |
        | Open Connection   | 192.168.1.1   |
        | Switch Connection | myserver      |                | # Back to my.server.com |

        Settings can be overridden per connection, otherwise the ones set on
        `library importing` or with `Set Default Configuration` are used:
        | Open Connection | 192.168.1.1   | timeout=1 hour    | newline=CRLF          |
        | # Do something with the connection                  |
        | Open Connection | my.server.com | # Default timeout | # Default line breaks |

        [#Default terminal settings|The terminal settings] are also configurable
        per connection:
        | Open Connection | 192.168.1.1  | term_type=ansi | width=40 |

        Arguments [#Default path separator|`path_separator`] and
        [#Default encoding|`encoding`]
        were added in SSHLibrary 2.0.
        """
        timeout = timeout or self._config.timeout
        newline = newline or self._config.newline
        prompt = prompt or self._config.prompt
        term_type = term_type or self._config.term_type
        width = width or self._config.width
        height = height or self._config.height
        path_separator = path_separator or self._config.path_separator
        encoding = encoding or self._config.encoding
 #       client = SSHClient(host, alias, port, timeout, newline, prompt,
 #                          term_type, width, height, path_separator, encoding)
        client = Serial(port,baudrate, timeout = 0.1)
        connection_index = self._connections.register(client, alias)
        client.config.update(index=connection_index)
        return connection_index
    def switch_serial_connection(self, index_or_alias):
        """Switches the active connection by index or alias.

        `index_or_alias` is either connection index (an integer) or alias
        (a string). Index is got as the return value of `Open Connection`.
        Alternatively, both index and alias can queried as attributes
        of the object returned by `Get Connection`.

        This keyword returns the index of the previous active connection,
        which can be used to switch back to that connection later.

        Example:
        | ${myserver}=      | Open Connection | my.server.com |
        | Login             | johndoe         | secretpasswd  |
        | Open Connection   | build.local.net | alias=Build   |
        | Login             | jenkins         | jenkins       |
        | Switch Connection | ${myserver}     |               | # Switch using index          |
        | ${username}=      | Execute Command | whoami        | # Executed on my.server.com   |
        | Should Be Equal   | ${username}     | johndoe       |
        | Switch Connection | Build           |               | # Switch using alias          |
        | ${username}=      | Execute Command | whoami        | # Executed on build.local.net |
        | Should Be Equal   | ${username}     | jenkins       |
        """
        old_index = self._connections.current_index
        if index_or_alias is None:
            self.close_connection()
        else:
            self._connections.switch(index_or_alias)
        return old_index
#     def open_serial(self,*args, **kwargs):
#         try:
#             self.console = Serial(*args, **kwargs)
#         except Exception as e: 
#             raise IndoorException("open serial failed")
#         if self.console.isOpen():
#             logging.info(u'[ open serial successfully\n')
#         else:
#             logging.info(u'[open serial failed\n')
#         return self.console
    def close_serial_connection(self):
        """Closes the current connection.

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

        Example:
        | Open Connection  | my.server.com  |
        | Login            | johndoe        | secretpasswd |
        | Get File         | results.txt    | /tmp         |
        | Close Connection |
        | # Do something with /tmp/results.txt             |
        """
        self.current.close()
        self._connections.current = self._connections._no_current
    def close_all_serial_connections(self):
        """Closes all open connections.

        This keyword is ought to be used either in test or suite teardown to
        make sure all the connections are closed before the test execution
        finishes.

        After this keyword, the connection indices returned by `Open Connection`
        are reset and start from `1`.

        Example:
        | Open Connection | my.server.com         |
        | Open Connection | build.local.net       |
        | # Do something with the connections     |
        | [Teardown]      | Close all connections |
        """
        self._connections.close_all()    
    
    def _info(self, msg):
        self._log(msg, 'INFO')

    def _log(self, msg, level=None):
        level = self._active_loglevel(level)
        msg = msg.strip()
        if not msg:
            return
        if logger:
            logger.write(msg, level)
        else:
            print '*%s* %s' % (level, msg)
    def _active_loglevel(self, level):
        if level is None:
            return self._config.loglevel
        if isinstance(level, basestring) and \
                level.upper() in ['TRACE', 'DEBUG', 'INFO', 'WARN', 'HTML']:
            return level.upper()
        raise AssertionError("Invalid log level '%s'." % level)
    
    def _legacy_output_options(self, stdout, stderr, rc):
        if not isinstance(stdout, basestring):
            return stdout, stderr, rc
        stdout = stdout.lower()
        if stdout == 'stderr':
            return False, True, rc
        if stdout == 'both':
            return True, True, rc
        return stdout, stderr, rc
    def execute_serial_command(self, command, return_stdout=True, return_stderr=False,
                        return_rc=False):
        """Executes `command` on the remote machine and returns its outputs.

        This keyword executes the `command` and returns after the execution
        has been finished. Use `Start Command` if the command should be
        started on the background.

        By default, only the standard output is returned:
        | ${stdout}=     | Execute Command | echo 'Hello John!' |
        | Should Contain | ${stdout}       | Hello John!        |

        Arguments `return_stdout`, `return_stderr` and `return_rc` are used
        to specify, what is returned by this keyword.
        If several arguments evaluate to true, multiple values are returned.
        Non-empty strings, except `false` and `False`, evaluate to true.

        If errors are needed as well, set the respective argument value to true:
        | ${stdout}       | ${stderr}= | Execute Command | echo 'Hello John!' | return_stderr=True |
        | Should Be Empty | ${stderr}  |

        Often checking the return code is enough:
        | ${rc}=                      | Execute Command | echo 'Hello John!' | return_stdout=False | return_rc=True |
        | Should Be Equal As Integers | ${rc}           | 0                  | # succeeded         |

        The `command` is always executed in a new shell. Thus possible changes
        to the environment (e.g. changing working directory) are not visible
        to the later keywords:
        | ${pwd}=         | Execute Command | pwd           |
        | Should Be Equal | ${pwd}          | /home/johndoe |
        | Execute Command | cd /tmp         |
        | ${pwd}=         | Execute Command | pwd           |
        | Should Be Equal | ${pwd}          | /home/johndoe |

        `Write` and `Read` can be used for
        [#Interactive shells|running multiple commands in the same shell].

        This keyword logs the executed command and its exit status with
        log level `INFO`.
        """
        self._info("Executing command '%s'." % command)
        opts = self._legacy_output_options(return_stdout, return_stderr,
                                           return_rc)
        self.current.write_cmd(command)
        return self.current.read(1024)
    
    def read_command_output(self, return_stdout=True, return_stderr=False,
                            return_rc=False):
        """Returns outputs of the most recent started command.

        At least one command must have been started using `Start Command`
        before this keyword can be used.

        By default, only the standard output of the started command is returned:
        | Start Command  | echo 'Hello John!'  |
        | ${stdout}=     | Read Command Output |
        | Should Contain | ${stdout}           | Hello John! |

        Arguments `return_stdout`, `return_stderr` and `return_rc` are used
        to specify, what is returned by this keyword.
        If several arguments evaluate to true, multiple values are returned.
        Non-empty strings, except `false` and `False`, evaluate to true.

        If errors are needed as well, set the argument value to true:
        | Start Command   | echo 'Hello John!' |
        | ${stdout}       | ${stderr}=         | Read Command Output | return_stderr=True |
        | Should Be Empty | ${stderr}          |

        Often checking the return code is enough:
        | Start Command               | echo 'Hello John!'  |
        | ${rc}=                      | Read Command Output | return_stdout=False | return_rc=True |
        | Should Be Equal As Integers | ${rc}               | 0                   | # succeeded    |

        Using `Start Command` and `Read Command Output` follows
        'last in, first out' (LIFO) policy, meaning that `Read Command Output`
        operates on the most recent started command, after which that command
        is discarded and its output cannot be read again.

        If several commands have been started, the output of the last started
        command is returned. After that, a subsequent call will return the
        output of the new last (originally the second last) command:
        | Start Command  | echo 'HELLO'        |
        | Start Command  | echo 'SECOND'       |
        | ${stdout}=     | Read Command Output |
        | Should Contain | ${stdout}           | 'SECOND' |
        | ${stdout}=     | Read Command Output |
        | Should Contain | ${stdout}           | 'HELLO'  |

        This keyword logs the read command with log level `INFO`.
        """
        self._info("Reading output of command '%s'." % self._last_command)
        opts = self._legacy_output_options(return_stdout, return_stderr,
                                           return_rc)
        stdout, stderr, rc = self.current.read(1024)
  
        return self._return_command_output(stdout, stderr, rc, *opts)

    
    def _return_command_output(self, stdout, stderr, rc, return_stdout,
                               return_stderr, return_rc):
        self._info("Command exited with return code %d." % rc)
        ret = []
        if self._output_wanted(return_stdout):
            ret.append(stdout.rstrip('\n'))
        if self._output_wanted(return_stderr):
            ret.append(stderr.rstrip('\n'))
        if self._output_wanted(return_rc):
            ret.append(rc)
        if len(ret) == 1:
            return ret[0]
        return ret
    
    def _output_wanted(self, value):
        return value and str(value).lower() != 'false'    

    def read(self, loglevel=None, delay=None):
        """Consumes and returns everything available on the server output.

        If `delay` is given, this keyword waits that amount of time and reads
        output again. This wait-read cycle is repeated as long as further reads
        return more output or the [#Default timeout|timeout] expires.
        `delay` must be given in Robot Framework's time format (e.g. `5`,
        `4.5s`, `3 minutes`, `2 min 3 sec`) that is explained in detail in
        the User Guide.

        This keyword is most useful for reading everything from
        the server output, thus clearing it.

        The read output is logged. `loglevel` can be used to override
        the [#Default loglevel|default log level].

        Example:
        | Open Serial Connection | com1 |
        | Login           | johndoe       | secretpasswd                 |
        | Write           | sudo su -     |                              |
        | ${output}=      | Read          | delay=0.5s                   |
        | Should Contain  | ${output}     | [sudo] password for johndoe: |
        | Write           | secretpasswd  |                              |
        | ${output}=      | Read          | loglevel=WARN | # Shown in the console due to loglevel |
        | Should Contain  | ${output}     | root@                        |

        See `interactive shells` for more information about writing and reading
        in general.

        Argument `delay` was added in SSHLibrary 2.0.
        """
        return self._read_and_log(loglevel, self.current.read, timeout = delay)    
    def read_until_prompt(self, loglevel=None):
        """Consumes and returns the server output until the prompt is found.

        Text up and until prompt is returned. [#Default prompt|The prompt must
        be set] before this keyword is used.

        If [#Default timeout|the timeout] expires before the match is found,
        this keyword fails.

        This keyword is useful for reading output of a single command when
        output of previous command has been read and that command does not
        produce prompt characters in its output.

        The read output is logged. `loglevel` can be used to override
        the [#Default loglevel|default log level].

        Example:
        | Open Connection          | my.server.com     | prompt=$         |
        | Login                    | johndoe           | ${PASSWORD}      |
        | Write                    | sudo su -         |                  |
        | Write                    | ${PASSWORD}       |                  |
        | Set Client Configuration | prompt=#          | # For root, the prompt is # |
        | ${output}=               | Read Until Prompt |                  |
        | Should End With          | ${output}         | root@myserver:~# |

        See also `Read Until` and `Read Until Regexp` keywords. For more
        details about reading and writing in general, see `interactive shells`
        section.
        """
        return self._read_and_log(loglevel, self.current.read_until_prompt)   
    
    def _read_and_log(self, loglevel, reader, *args):
        try:
            output = reader(*args)
        except Exception, e:
            raise SerialException(e)
        self._log(output, loglevel)
        return output         
class TarantoolLibrary(object):
    """
    Robot Framework library for working with Tarantool DB.

    == Dependencies ==
    | tarantool | https://pypi.org/project/tarantool/ | version > 0.5 |
    | robot framework | http://robotframework.org |
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

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

    def _modify_key_type(self, key, key_type):
        """
        Convert key to the required tarantool data type.

        Tarantool data types corresponds to the following Python types:
        STR - unicode (str for Python 3.x)
        NUM - int
        NUM64 - int or long (int for Python 3.x)

        *Args:*\n
            _key_: key to modify;\n
            _key_type_: key type: STR, NUM, NUM64;\n

        *Returns:*\n
            modified key.
        """
        key_type = key_type.upper()
        if key_type == "STR":
            if isinstance(key, bytes):
                return codecs.decode(key)
            return str(key)

        if key_type == "NUM":
            return int(key)

        if key_type == "NUM64":
            if int(sys.version[0]) == 2:
                return long(key)  # noqa: F821
            else:
                return int(key)
        raise Exception("Wrong key type for conversation: {}. Allowed ones are STR, NUM and NUM64".format(key_type))

    def connect_to_tarantool(self, host, port, user=None, password=None, alias=None):
        """
        Connection to Tarantool DB.

        *Args:*\n
            _host_ - host for db connection;\n
            _port_ - port for db connection;\n
            _user_ - username for db connection;\n
            _password_ - password for db connection;\n
            _alias_ - connection alias, used for switching between open connections;\n

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

        *Example:*\n
            | Connect To Tarantool  |  127.0.0.1  |  3301  |
        """
        logger.debug('Connecting  to the Tarantool DB using \
        host={host}, port={port}, user={user}'.format(host=host,
                                                      port=port,
                                                      user=user))
        try:
            self._connection = Connection(host=host, port=int(port), user=user, password=password)
            return self._cache.register(self._connection, alias)
        except Exception as exc:
            raise Exception("Logon to Tarantool error:", str(exc))

    def close_all_tarantool_connections(self):
        """
        Close all Tarantool connections that were opened.
        After calling this keyword connection index returned by opening new connections [#Connect To Tarantool |Connect To Tarantool],
        starts from 1.

        *Example:*\n
            | Connect To Tarantool  |  192.168.0.1  |  3031  |  user |   password  |  alias=trnt_1  |
            | Connect To Tarantool  |  192.168.0.2  |  3031  |  user  |  password  |  alias=trnt_2  |
            | Switch Tarantool Connection |  trnt_1 |
            | @{data1}=  |  Select  |  space1  |  key1  |
            | Switch Tarantool Connection  |  trnt_2 |
            | @{data2}=  |  Select  |  space2  |  key2  |
            | Close All Tarantool Connections |
        """
        self._cache.close_all()
        self._connection = None

    def switch_tarantool_connection(self, index_or_alias):
        """
        Switch to another existing Tarantool 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 DB [#Connect To Tarantool|Connect To Tarantool].


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

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

        *Example:* (switch by alias)\n
            | Connect To Tarantool  |  192.168.0.1  |  3031  |  user |   password  |  alias=trnt_1  |
            | Connect To Tarantool  |  192.168.0.2  |  3031  |  user  |  password  |  alias=trnt_2  |
            | Switch Tarantool Connection  |  trnt_1 |
            | @{data1}=  |  Select  |  space1  |  key1  |
            | Switch Tarantool Connection  |  trnt_2 |
            | @{data2}=  |  Select  |  space2  |  key2  |
            | Close All Tarantool Connections |

        *Example:* (switch by connection index)\n
            | ${trnt_index1}=  |  Connect To Tarantool  |  192.168.0.1  |  3031  |  user |   password  |
            | ${trnt_index2}=  |  Connect To Tarantool  |  192.168.0.2  |  3031  |  user  |  password  |
            | @{data1}=  |  Select  |  space1  |  key1  |
            | ${previous_index}=  |  Switch Tarantool Connection  |  ${trnt_index1} |
            | @{data2}=  |  Select  |  space2  |  key2  |
            | Switch Tarantool Connection  |  ${previous_index} |
            | @{data3}=  |  Select  |  space1  |  key1  |
            | Close All Tarantool Connections |
        """
        logger.debug('Switching to tarantool connection with alias/index {}'.format(index_or_alias))
        old_index = self._cache.current_index
        self._connection = self._cache.switch(index_or_alias)
        return old_index

    def select(self, space_name, key, offset=0, limit=0xffffffff, index=0, key_type=None, **kwargs):
        """
        Select and retrieve data from the database.

        *Args:*\n
            _space_name_: space id to insert a record;\n
            _key_: values to search over the index;\n
            _offset_: offset in the resulting tuple set;\n
            _limit_: limits the total number of returned tuples. Deafult is max of unsigned int32;\n
            _index_: specifies which index to use. Default is 0 which means that the primary index will be used;\n
            _key_type_: type of the key;\n
            _kwargs_: additional params;\n

        *Returns:*\n
            Tarantool server response.

        *Example:*\n
            | ${data_from_trnt}= | Select | space_name=some_space_name | key=0 | key_type=NUM |
            | Set Test Variable | ${key} | ${data_from_trnt[0][0]} |
            | Set Test Variable | ${data_from_field} | ${data_from_trnt[0][1]} |
        """
        logger.debug('Select data from space {space} by key {key}'.format(
            space=space_name,
            key=key)
        )
        if key_type:
            key = self._modify_key_type(key=key, key_type=key_type)
        return self._connection.select(
            space_name=space_name,
            key=key,
            offset=offset,
            limit=limit,
            index=index,
            **kwargs
        )

    def insert(self, space_name, values):
        """
        Execute insert request.

        *Args:*\n
            _space_name_: space id to insert a record;\n
            _values_: record to be inserted. The tuple must contain only scalar (integer or strings) values;\n

        *Returns:*\n
            Tarantool server response

        *Example:*\n
            | ${data_to_insert}= | Create List | 1 | ${data} |
            | ${response}= | Insert | space_name=${SPACE_NAME} | values=${data_to_insert} |
            | Set Test Variable | ${key} | ${response[0][0]} |

        """
        logger.debug('Insert values {values} in space {space}'.format(space=space_name, values=values))
        return self._connection.insert(space_name=space_name, values=values)

    def create_operation(self, operation, field, arg):
        """
        Check and prepare operation tuple.

        *Allowed operations:*;\n
          '+' for addition (values must be numeric);\n
          '-' for subtraction (values must be numeric);\n
          '&' for bitwise AND (values must be unsigned numeric);\n
          '|' for bitwise OR (values must be unsigned numeric);\n
          '^' for bitwise XOR (values must be unsigned numeric);\n
          ':' for string splice (you must provide 'offset', 'count' and 'value'
         for this operation);\n
          '!' for insertion (provide any element to insert);\n
          '=' for assignment (provide any element to assign);\n
          '#' for deletion (provide count of fields to delete);\n

        *Args:*\n
            _operation_: operation sign;\n
            _field_:  field number, to apply operation to;\n
            _arg_: depending on operation argument or list of arguments;\n

        *Returns:*\n
            Tarantool server response.

        *Example:*\n
            | ${list_to_append}= | Create List | ${offset} | ${count} | ${value} |
            | ${operation}= | Create Operation | operation=: | field=${1} | arg=${list_to_append} |
        """
        if operation not in ('+', '-', '&', '|', '^', ':', '!', '=', '#'):
            raise Exception('Unsupported operation: {}'.format(operation))
        if isinstance(arg, (list, tuple)):
            op_field_list = [operation, field]
            op_field_list.extend(arg)
            return tuple(op_field_list)
        else:
            return operation, field, arg

    def update(self, space_name, key, op_list, key_type=None, **kwargs):
        """
        Execute update request.

        Update accepts both operation and list of operations for the argument op_list.

        *Args:*\n
            _space_name_: space number or name to update a record;\n
            _key_: key that identifies a record;\n
            _op_list_: operation or list of operations. Each operation is tuple of three (or more) values;\n
            _key_type_: type of the key;\n
            _kwargs_: additional params;\n

        *Returns:*\n
            Tarantool server response.

        *Example:* (list of operations)\n
            | ${operation1}= | Create Operation | operation== | field=${1} | arg=NEW DATA |
            | ${operation2}= | Create Operation | operation== | field=${2} | arg=ANOTHER NEW DATA |
            | ${op_list}= | Create List | ${operation1} | ${operation2} |
            | Update | space_name=${SPACE_NAME} | key=${key} | op_list=${op_list} |

        *Example:* (one operation)\n
            | ${list_to_append}= | Create List | ${offset} | ${count} | ${value} |
            | ${operation}= | Create Operation | operation== | field=${1} | arg=NEW DATA |
            | Update | space_name=${SPACE_NAME} | key=${key} | op_list=${operation} |
        """
        logger.debug('Update data in space {space} with key {key} with operations {op_list}'.format(
            space=space_name,
            key=key,
            op_list=op_list
        ))
        if key_type:
            key = self._modify_key_type(key=key, key_type=key_type)
        if isinstance(op_list[0], (list, tuple)):
            return self._connection.update(space_name=space_name, key=key, op_list=op_list, **kwargs)
        else:
            return self._connection.update(space_name=space_name, key=key, op_list=[op_list], **kwargs)

    def delete(self, space_name, key, key_type=None, **kwargs):
        """
        Execute delete request.

        *Args:*\n
            _space_name_: space number or name to delete a record;\n
            _key_: key that identifies a record;\n
            _key_type_: type of the key;\n
            _kwargs_: additional params;\n

        *Returns:*\n
            Tarantool server response.

        *Example:*\n
            | Delete | space_name=${SPACE_NAME}| key=${key} |
        """
        logger.debug('Delete data in space {space} by key {key}'.format(space=space_name, key=key))
        if key_type:
            key = self._modify_key_type(key=key, key_type=key_type)
        return self._connection.delete(space_name=space_name, key=key, **kwargs)
Exemple #9
0
class OracleDB(object):
    """
    Robot Framework library for working with Oracle DB.

    == Dependencies ==
    | cx_Oracle | http://cx-oracle.sourceforge.net | version > 3.0 |
    | robot framework | http://robotframework.org |
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    last_executed_statement = None
    last_executed_statement_params = None

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

    def connect_to_oracle(self, dbname, dbusername, dbpassword, alias=None):
        """
        Connection to Oracle DB.

        *Args:*\n
            _dbname_ - database name;\n
            _dbusername_ - username for db connection;\n
            _dbpassword_ - password for db connection;\n
            _alias_ - connection alias, used for switching between open connections;\n

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

        *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 as err:
            raise Exception("Logon to oracle  Error:", str(err))

    def disconnect_from_oracle(self):
        """
        Close active Oracle connection.

        *Example:*\n
            | Connect To Oracle  |  rb60db  |  bis  |  password |
            | Disconnect From Oracle |
        """

        self._connection.close()
        self._cache.empty_cache()

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

        *Example:*\n
            | Connect To Oracle  |  rb60db  |  bis |   password  |  alias=bis |
            | Connect To Oracle  |  rb60db  |  bis_dcs  |  password  |  alias=bis_dsc |
            | Switch Oracle Connection  |  bis |
            | @{sql_out_bis}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Switch Oracle Connection  |  bis_dsc |
            | @{sql_out_bis_dsc}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Close All Oracle Connections |
        """

        self._connection = self._cache.close_all()

    def switch_oracle_connection(self, index_or_alias):
        """
        Switch between existing Oracle connections using their connection IDs or aliases.
        The connection ID is obtained on creating connection.
        Connection alias is optional and can be set at connecting to DB [#Connect To Oracle|Connect To Oracle].

        *Args:*\n
            _index_or_alias_ - connection ID or alias assigned to connection;

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

        *Example:* (switch by alias)\n
            | Connect To Oracle  |  rb60db  |  bis |   password  |  alias=bis |
            | Connect To Oracle  |  rb60db  |  bis_dcs  |  password  |  alias=bis_dsc |
            | Switch Oracle Connection  |  bis |
            | @{sql_out_bis}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Switch Oracle Connection  |  bis_dsc |
            | @{sql_out_bis_dsc}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Close All Oracle Connections |
            =>\n
            @{sql_out_bis} = BIS\n
            @{sql_out_bis_dcs}= BIS_DCS

        *Example:* (switch by index)\n
            | ${bis_index}=  |  Connect To Oracle  |  rb60db  |  bis  |  password  |
            | ${bis_dcs_index}=  |  Connect To Oracle  |  rb60db  |  bis_dcs  |  password |
            | @{sql_out_bis_dcs_1}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | ${previous_index}=  |  Switch Oracle Connection  |  ${bis_index} |
            | @{sql_out_bis}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Switch Oracle Connection  |  ${previous_index} |
            | @{sql_out_bis_dcs_2}=  |  Execute Sql String  |  select SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') from dual |
            | Close All Oracle Connections |
            =>\n
            ${bis_index}= 1\n
            ${bis_dcs_index}= 2\n
            @{sql_out_bis_dcs_1} = BIS_DCS\n
            ${previous_index}= 2\n
            @{sql_out_bis} = BIS\n
            @{sql_out_bis_dcs_2}= BIS_DCS
        """

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

    def _execute_sql(self, cursor, statement, params):
        """ Execute SQL query on Oracle DB using active connection.

        *Args*:\n
            _cursor_: cursor object.\n
            _statement_: SQL query to be executed.\n
            _params_: SQL query parameters.\n

        *Returns:*\n
            Query results.
        """
        statement_with_params = self._replace_parameters_in_statement(
            statement, params)
        logger.info(statement_with_params, html=True)
        cursor.prepare(statement)
        self.last_executed_statement = self._replace_parameters_in_statement(
            statement, params)
        return cursor.execute(None, params)

    def _replace_parameters_in_statement(self, statement, params):
        """Update SQL query parameters, if any exist, with their values for logging purposes.

        *Args*:\n
            _statement_: SQL query to be updated.\n
            _params_: SQL query parameters.\n

        *Returns:*\n
            SQL query with parameter names replaced with their values.
        """
        params_keys = sorted(params.keys(), reverse=True)
        for key in params_keys:
            if isinstance(params[key], (int, float)):
                statement = statement.replace(':{}'.format(key),
                                              str(params[key]))
            else:
                statement = statement.replace(':{}'.format(key),
                                              "'{}'".format(params[key]))
        return statement

    def execute_plsql_block(self, plsqlstatement, **params):
        """
        PL\SQL block execution.

        *Args:*\n
            _plsqlstatement_ - PL\SQL block;\n
            _params_ - PL\SQL block parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run

        *Returns:*\n
            PL\SQL block execution result.

        *Example:*\n
            | *Settings* | *Value* |
            | Library    |       OracleDB |

            | *Variables* | *Value* |
            | ${var_failed}    |       3 |

            | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* |
            | Simple |
            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := ${var_failed}; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |    END; |
            |    | Execute Plsql Block   |  plsqlstatement=${statement} |
            =>\n
            DatabaseError: ORA-20001: This is a custom error

            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := :var; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |    END; |
            |    | Execute Plsql Block   |  plsqlstatement=${statement} | var=${var_failed} |
            =>\n
            DatabaseError: ORA-20001: This is a custom error
        """

        cursor = None
        try:
            cursor = self._connection.cursor()
            self._execute_sql(cursor, plsqlstatement, params)
            self._connection.commit()
        finally:
            if cursor:
                self._connection.rollback()

    def execute_plsql_block_with_dbms_output(self, plsqlstatement, **params):
        """
        Execute PL\SQL block with dbms_output().

        *Args:*\n
            _plsqlstatement_ - PL\SQL block;\n
            _params_ - PL\SQL block parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run.

        *Returns:*\n
            List of values returned by Oracle dbms_output.put_line().

        *Example:*\n
            | *Settings* | *Value* |
            | Library    |       OracleDB |

            | *Variables* | *Value* |
            | ${var}    |       4 |

            | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* |
            | Simple |
            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := ${var}; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |       dbms_output.put_line ('text '||a||', e-mail text'); |
            |    | ...            |             |                     |       dbms_output.put_line ('string 2 '); |
            |    | ...            |             |                     |    END; |
            |    | @{dbms}=       | Execute Plsql Block With Dbms Output   |  plsqlstatement=${statement} |
            =>\n
            | @{dbms} | text 5, e-mail text |
            | | string 2 |

            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DECLARE  |
            |    | ...            |             |                     |       a NUMBER := :var; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       a := a + 1; |
            |    | ...            |             |                     |       if a = 4 then |
            |    | ...            |             |                     |         raise_application_error ( -20001, 'This is a custom error' ); |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |       dbms_output.put_line ('text '||a||', e-mail text'); |
            |    | ...            |             |                     |       dbms_output.put_line ('string 2 '); |
            |    | ...            |             |                     |    END; |
            |    | @{dbms}=       | Execute Plsql Block With Dbms Output   |  plsqlstatement=${statement} |  var=${var} |
            =>\n
            | @{dbms} | text 5, e-mail text |
            | | string 2 |
        """

        cursor = None
        dbms_output = []
        try:
            cursor = self._connection.cursor()
            cursor.callproc("dbms_output.enable")
            self._execute_sql(cursor, plsqlstatement, params)
            self._connection.commit()
            statusvar = cursor.var(cx_Oracle.NUMBER)
            linevar = cursor.var(cx_Oracle.STRING)
            while True:
                cursor.callproc("dbms_output.get_line", (linevar, statusvar))
                if statusvar.getvalue() != 0:
                    break
                dbms_output.append(linevar.getvalue())
            return dbms_output
        finally:
            if cursor:
                self._connection.rollback()

    def execute_plsql_script(self, file_path, **params):
        """
         Execution of PL\SQL code from file.

        *Args:*\n
            _file_path_ - path to PL\SQL script file;\n
            _params_ - PL\SQL code parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run.

        *Example:*\n
            |  Execute Plsql Script  |  ${CURDIR}${/}plsql_script.sql |
            |  Execute Plsql Script  |  ${CURDIR}${/}plsql_script.sql | first_param=1 | second_param=2 |
        """

        with open(file_path, "r") as script:
            data = script.read()
            self.execute_plsql_block(data, **params)

    def execute_sql_string(self, plsqlstatement, **params):
        """
        Execute PL\SQL string.

        *Args:*\n
            _plsqlstatement_ - PL\SQL string;\n
            _params_ - PL\SQL string parameters;\n

        *Raises:*\n
            PLSQL Error: Error message encoded according to DB where the code was run.

        *Returns:*\n
            PL\SQL string execution result.

        *Example:*\n
            | @{query}= | Execute Sql String | select sysdate, sysdate+1 from dual |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][0]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][1]} |

            | @{query}= | Execute Sql String | select sysdate, sysdate+:d from dual | d=1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][0]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][1]} |
        """

        cursor = None
        try:
            cursor = self._connection.cursor()
            self._execute_sql(cursor, plsqlstatement, params)
            query_result = cursor.fetchall()
            self.result_logger(query_result)
            return query_result
        finally:
            if cursor:
                self._connection.rollback()

    def execute_sql_string_mapped(self, sql_statement, **params):
        """SQL query execution where each result row is mapped as a dict with column names as keys.

        *Args:*\n
            _sql_statement_ - PL\SQL string;\n
            _params_ - PL\SQL string parameters;\n

        *Returns:*\n
            A list of dictionaries where column names are mapped as keys.

        *Example:*\n
            | @{query}= | Execute Sql String Mapped| select sysdate, sysdate+1 from dual |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][sysdate]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][sysdate1]} |

            | @{query}= | Execute Sql String Mapped| select sysdate, sysdate+:d from dual | d=1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][sysdate]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][sysdate1]} |
        """
        cursor = None
        try:
            cursor = self._connection.cursor()
            self._execute_sql(cursor, sql_statement, params)
            col_name = tuple(i[0] for i in cursor.description)
            query_result = [dict(zip(col_name, row)) for row in cursor]
            self.result_logger(query_result)
            return query_result
        finally:
            if cursor:
                self._connection.rollback()

    def execute_sql_string_generator(self, sql_statement, **params):
        """Generator that yields each result row mapped as a dict with column names as keys.\n
        Intended for use mainly in code for other keywords.
        *If used, the generator must be explicitly closed before closing DB connection*

        *Args:*\n
            _sql_statement_ - PL\SQL string;\n
            _params_ - PL\SQL string parameters;\n

        Yields:*\n
            results dict.
        """
        cursor = None
        self.last_executed_statement = sql_statement
        self.last_executed_statement_params = params
        try:
            cursor = self._connection.cursor()
            self._execute_sql(cursor, sql_statement, params)
            col_name = tuple(i[0] for i in cursor.description)
            for row in cursor:
                yield dict(zip(col_name, row))
        finally:
            if cursor:
                self._connection.rollback()

    def result_logger(self, query_result, result_amount=10):
        """Log first n rows from the query results

        *Args:*\n
            _query_result_ - query result to log, must be greater than 0
            _result_amount_ - amount of entries to display from result
        """
        if len(query_result) > result_amount > 0:
            query_result = query_result[:result_amount]
        logger.info(query_result, html=True)
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 |

        """
        cache_copy = self._vm_cache.copy()
        for name, vm in self._vm_cache.iteritems():
            if id(vm._server) == id(self._connections.current):
                del cache_copy[name]
                logger.debug("Removed VM '{}' from cache".format(name))

        self._vm_cache = cache_copy

        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')
        self._vm_cache = {}
        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]
Exemple #11
0
class PostgreSQLDB(object):
    """
    Robot Framework library for working with PostgreSQL.

    == Dependencies ==
    | psycopg2 | http://initd.org/psycopg/ | version > 2.7.3 |
    | robot framework | http://robotframework.org |
    """
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self) -> None:
        """Library initialization.
        Robot Framework ConnectionCache() class is prepared for working with concurrent connections."""
        self._connection: Optional[db_connection] = None
        self._cache = ConnectionCache()

    @property
    def connection(self) -> db_connection:
        """Get current connection to Postgres database.

        *Raises:*\n
            RuntimeError: if there isn't any open connection.

        *Returns:*\n
            Current connection to the database.
        """
        if self._connection is None:
            raise RuntimeError(
                'There is no open connection to PostgreSQL database.')
        return self._connection

    def connect_to_postgresql(self,
                              dbname: str,
                              dbusername: str,
                              dbpassword: str,
                              dbhost: str = None,
                              dbport: str = None,
                              alias: str = None) -> db_connection:
        """
        Connection to Postgres DB.

        *Args:*\n
            _dbname_ - database name;\n
            _dbusername_ - username for db connection;\n
            _dbpassword_ - password for db connection;\n
            _dbhost_ - host for db connection, default is localhost;\n
            _dbport_ - port for db connection, default is 5432;\n
            _alias_ - connection alias, used for switching between open connections.

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

        *Example:*\n
            | Connect To Postgresql  |  postgres  |  postgres  |  password | localhost | 5332  | None |
        """
        try:
            logger.debug(
                f'Connecting using : dbhost={dbhost or "localhost"}, dbport={dbport or "5432"}, '
                f'dbname={dbname}, dbusername={dbusername}, dbpassword={dbpassword}, alias={alias}'
            )

            self._connection = psycopg2.connect(host=dbhost,
                                                port=dbport,
                                                dbname=dbname,
                                                user=dbusername,
                                                password=dbpassword)
            return self._cache.register(self._connection, alias)
        except psycopg2.Error as err:
            raise Exception("Logon to PostgreSQL  Error:", str(err))

    def disconnect_from_postgresql(self) -> None:
        """
        Close active PostgreSQL connection.
        You will have to manually open or switch to a new connection.
        Due to how ConnectionCache works, connection's index and alias are not removed from cache, so you are able to switch to
        closed connection, if it was closed with Disconnect from Postgresql. It will still remain closed though.

        *Example:*\n
            | Connect To Postgresql  |  postgres  |  postgres  |  password |
            | Disconnect From Postgresql |
        """
        self.connection.close()
        self._cache._current = self._cache._no_current

    def close_all_postgresql_connections(self) -> None:
        """
        Close all PostgreSQL connections that were opened.
        After calling this keyword connection index returned by opening new connections [#Connect To Postgresql |Connect To Postgresql],
        starts from 1.

        *Example:*\n
            | Connect To Postgresql  |  postgres  |  postgres |   password  |  alias=plain_pg |
            | Connect To Postgresql  |  postgres  |  login  |  password  |  alias=psc2 |
            | Switch Postgresql Connection  |  plain_pg |
            | @{sql_out_plain_pg}=  |  Execute Sql String  |  select * from postgres |
            | Switch Postgresql Connection  |  psc2 |
            | @{sql_out_psc2}=  |  Execute Sql String  |  select 1 |
            | Close All Postgresql Connections |
        """
        self._connection = self._cache.close_all()

    def switch_postgresql_connection(self, index_or_alias: Union[int,
                                                                 str]) -> int:
        """
        Switch to another existing PostgreSQL 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 DB [#Connect To Postgresql|Connect To Postgresql].
        Due to how ConnectionCache works, you are able to switch to closed connection,
        if it was closed with Disconnect from Postgresql. It will still remain closed though.


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

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

        *Example:* (switch by alias)\n
            | Connect To Postgresql  |  postgres  |  postgres |   password  |  alias=bis |
            | Connect To Postgresql  |  postgres  |  postgres  |  password  |  alias=bis_dsc |
            | Switch Postgresql Connection  |  bis |
            | @{sql_out_bis}=  |  Execute Sql String  |  select 1 |
            | Switch Postgresql Connection  |  bis_dsc |
            | @{sql_out_bis_dsc}=  |  Execute Sql String  |  select 2 |
            | Close All Postgresql Connections |
            =>\n
            @{sql_out_bis} = BIS\n
            @{sql_out_bis_dcs}= BIS_DCS
            \n
        *Example:* (switch by connection index)\n
            | ${bis_index}=  |  Connect To Postgresql  |  postgres  |  postgres  |  password  |
            | ${bis_dcs_index}=  |  Connect To Postgresql  |  postgres  |  postgres  |  password |
            | @{sql_out_bis_dcs_1}=  |  Execute Sql String  |  select 1 |
            | ${previous_index}=  |  Switch Postgresql Connection  |  ${bis_index} |
            | @{sql_out_bis}=  |  Execute Sql String  |  select 2 |
            | Switch Postgresql Connection  |  ${previous_index} |
            | @{sql_out_bis_dcs_2}=  |  Execute Sql String  |  select 3 |
            | Close All Postgresql Connections |
            =>\n
            ${bis_index}= 1\n
            ${bis_dcs_index}= 2\n
            @{sql_out_bis_dcs_1} = BIS_DCS\n
            ${previous_index}= 2\n
            @{sql_out_bis} = BIS\n
            @{sql_out_bis_dcs_2}= BIS_DCS
        """
        old_index = self._cache.current_index
        self._connection = self._cache.switch(index_or_alias)
        return old_index

    @staticmethod
    def wrap_into_html_details(statement: str, summary: str) -> str:
        """Format statement for html logging.

        *Args:*\n
            _statement_: statement to log.
            _summary_: summary for details tag.

        *Returns:*\n
            Formatted statement.
        """
        statement_html = escape(statement)
        data = f'<details><summary>{summary}</summary><p>{statement_html}</p></details>'
        return data

    def _execute_sql(self, cursor: cursor, statement: str,
                     params: Dict[str, Any]) -> cursor:
        """ Execute SQL query on Postgres DB using active connection.

        *Args*:\n
            _cursor_: cursor object.\n
            _statement_: SQL query to be executed.\n
            _params_: SQL query parameters.\n

        *Returns:*\n
            Query results.
        """
        statement_with_params = self._replace_parameters_in_statement(
            statement, params)
        data = self.wrap_into_html_details(
            statement=statement_with_params,
            summary='Running PL/PGSQL statement')
        logger.info(data, html=True)
        return cursor.execute(statement, params)

    def _replace_parameters_in_statement(self, statement: str,
                                         params: Dict[str, Any]) -> str:
        """Update SQL query parameters, if any exist, with their values for logging purposes.

        *Args*:\n
            _statement_: SQL query to be updated.\n
            _params_: SQL query parameters.\n

        *Returns:*\n
            SQL query with parameter names replaced with their values.
        """
        for key, value in params.items():
            if isinstance(value, (int, float)):
                statement = statement.replace(f':{key}', str(value))
            else:
                statement = statement.replace(f':{key}', f"'{value}'")
        return statement

    def execute_plpgsql_block(self, plpgsqlstatement: str,
                              **params: Any) -> None:
        """
        PL/PGSQL block execution.
        For parametrized SQL queries please consider psycopg2 guide on the subject:
        http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries

        *Args:*\n
            _plpgsqlstatement_ - PL/PGSQL block;\n
            _params_ - PL/PGSQL block parameters;\n

        *Raises:*\n
            PostgreSQL error in form of psycopg2 exception.

        *Returns:*\n
            PL/PGSQL block execution result.

        *Example:*\n
            | *Settings* | *Value* |
            | Library    |       PostgreSQLDB |

            | *Variables* | *Value* |
            | ${var_failed}    |       TRUE |

            | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* |
            | Simple |
            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DO $$  |
            |    | ...            |             |                     |    DECLARE |
            |    | ...            |             |                     |       a boolean := ${var_failed}; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       if a = TRUE then |
            |    | ...            |             |                     |         RAISE EXCEPTION USING ERRCODE = 'P0001', MESSAGE = 'This is a custom error'; |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |    END$$; |
            |    | Execute Plpgsql Block   |  plpgsqlstatement=${statement} |
            =>\n
            InternalError: This is a custom error\n

            | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* |
            | Simple |
            |    | ${statement}=  |  catenate   |   SEPARATOR=\\r\\n  |    DO $$  |
            |    | ...            |             |                     |    DECLARE |
            |    | ...            |             |                     |       a boolean := ${var_failed}; |
            |    | ...            |             |                     |    BEGIN |
            |    | ...            |             |                     |       if a = TRUE then |
            |    | ...            |             |                     |         RAISE EXCEPTION USING ERRCODE = 'P0001', MESSAGE = 'This is a custom error'; |
            |    | ...            |             |                     |       end if; |
            |    | ...            |             |                     |    END$$; |
            |    | Execute Plpgsql Block   |  plpgsqlstatement=${statement} | var=${var_failed} |
            =>\n
            InternalError: This is a custom error
        """
        cursor = None
        try:
            cursor = self.connection.cursor()
            self._execute_sql(cursor, plpgsqlstatement, params)
            self.connection.commit()
        finally:
            if cursor:
                self.connection.rollback()

    def execute_plpgsql_script(self, file_path: str, **params: Any) -> None:
        """
        Execution of PL/PGSQL from file.
        For parametrized SQL queries please consider psycopg2 guide on the subject:
        http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries

        *Args:*\n
            _file_path_ - path to PL/PGSQL script file;\n
            _params_ - PL/PGSQL code parameters;\n

        *Raises:*\n
            PostgreSQL error in form of psycopg2 exception.

        *Returns:*\n
            PL/PGSQL script execution result.

        *Example:*\n
            |  Execute Plpgsql Script  |  ${CURDIR}${/}plpgsql_script.sql |
            |  Execute Plpgsql Script  |  ${CURDIR}${/}plpgsql_script.sql | first_param=1 | second_param=2 |
        """
        with open(file_path, "r") as script:
            data = script.read()
            self.execute_plpgsql_block(data, **params)

    def execute_sql_string(self, plpgsqlstatement: str,
                           **params: Any) -> List[Tuple[Any, ...]]:
        """
        Execute PL/PGSQL string.
        For parametrized SQL queries please consider psycopg2 guide on the subject:
        http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries

        *Args:*\n
            _plpgsqlstatement_ - PL/PGSQL string;\n
            _params_ - PL/PGSQL string parameters;\n

        *Raises:*\n
            PostgreSQL error in form of psycopg2 exception.

        *Returns:*\n
            PL/PGSQL string execution result.

        *Example:*\n
            | @{query}= | Execute Sql String | SELECT CURRENT_DATE, CURRENT_DATE+1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][0]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][1]} |

            | @{query}= | Execute Sql String | SELECT CURRENT_DATE, CURRENT_DATE+%(d)s | d=1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][0]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][1]} |
        """
        cursor = None
        try:
            cursor = self.connection.cursor()
            self._execute_sql(cursor, plpgsqlstatement, params)
            query_result = cursor.fetchall()
            self.result_logger(query_result)
            return query_result
        finally:
            if cursor:
                self.connection.rollback()

    def execute_sql_string_mapped(self, plpgsqlstatement: str,
                                  **params: Any) -> List[Dict[str, Any]]:
        """SQL query execution where each result row is mapped as a dict with column names as keys.

        For parametrized SQL queries please consider psycopg2 guide on the subject:
        http://initd.org/psycopg/docs/usage.html#passing-parameters-to-sql-queries

        *Args:*\n
            _plpgsqlstatement_ - PL/PGSQL string;\n
            _params_ - PL/PGSQL string parameters;\n

        *Returns:*\n
            A list of dictionaries where column names are mapped as keys

        *Example:*\n
            | @{query}= | Execute Sql String Mapped| SELECT CURRENT_DATE, CURRENT_DATE+1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][sysdate]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][sysdate1]} |

            | @{query}= | Execute Sql String Mapped| SELECT CURRENT_DATE, CURRENT_DATE+%(d)s | d=1 |
            | Set Test Variable  |  ${sys_date}  |  ${query[0][sysdate]} |
            | Set Test Variable  |  ${next_date}  |  ${query[0][sysdate1]} |
        """
        cursor = None
        try:
            cursor = self.connection.cursor()
            self._execute_sql(cursor, plpgsqlstatement, params)
            col_name = tuple(i[0] for i in cursor.description)
            query_result = [dict(zip(col_name, row)) for row in cursor]
            self.result_logger(query_result)
            return query_result
        finally:
            if cursor:
                self.connection.rollback()

    def result_logger(self,
                      query_result: List[Any],
                      result_amount: int = 10) -> None:
        """Log first n results of the query results

        *Args:*\n
            _query_result_ - query result to log, must be greater than 0
            _result_amount_ - amount of entries to display from result
        """
        if len(query_result) > result_amount > 0:
            query_result = query_result[:result_amount]
        logged_result = self.wrap_into_html_details(str(query_result),
                                                    "SQL Query Result")
        logger.info(logged_result, html=True)
Exemple #12
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 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]
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)
Exemple #15
0
class RabbitMqCustom(object):
    """
    Library for managing the server RabbitMq.

    == 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):
        """
        Connecting to the server RabbitMq.

        *Args:*\n
        _host_ - server name;\n
        _port_ - port number;\n
        _username_ - is the name of the user;\n
        _password_ - is the user password;\n
        _timeout_ - connection timeout;\n
        _alias_ - connection alias;\n

        *Returns:*\n
        The index of the current connection.

        *Raises:*\n
        socket.error if you can not create a connection.

        *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":
            b"Basic " +
            base64.b64encode(username.encode() + b":" + password.encode())
        }
        logger.debug(self.headers)
        try:
            self._connection = http.client.HTTPConnection(host, port, timeout)
            self._connection.connect()
            return self._cache.register(self._connection, alias)
        except socket.error as e:
            raise Exception("Could not connect to RabbitMq", str(e))

    def switch_rabbitmq_connection(self, index_or_alias):
        """
        Switch between active connections with RabbitMq, using their index or alias..

        The alias is specified in the keyword [#Connect To Rabbitmq|Connect To Rabbitmq], which also returns the connection index.

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

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

        *Example:*\n
        | Connect To Rabbitmq | my_host_name_1 | 15672 | guest | guest | alias=rmq1 |
        | Connect To Rabbitmq | my_host_name_2 | 15672 | guest | guest | alias=rmq2 |
        | Switch Rabbitmq Connection | rmq1 |
        | ${live}= | Is alive |
        | Switch Rabbitmq Connection | rmq2 |
        | ${live}= | Is alive |
        | Close All Rabbitmq Connections |
        """

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

    def disconnect_from_rabbitmq(self):
        """
        Closing the current connection to RabbitMq.

        *Example:*\n
        | Connect To Rabbitmq | my_host_name | 15672 | guest | guest | alias=rmq |
        | Disconnect From Rabbitmq |
        """

        logger.debug('Close connection with : host=%s, port=%d  ' %
                     (self._connection.host, self._connection.port))
        self._connection.close()

    def close_all_rabbitmq_connections(self):
        """
        Closing all connections from RabbitMq..

        This keyword is used to close all connections in the event that several of them were opened.
        Use [#Disconnect From Rabbitmq|Disconnect From Rabbitmq] и [#Close All Rabbitmq Connections|Close All Rabbitmq Connections]
        together it is impossible..

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

        *Example:*\n
        | Connect To Rabbitmq | my_host_name | 15672 | guest | guest | alias=rmq |
        | Close All Rabbitmq Connections |
        """

        self._connection = self._cache.close_all()

    def _http_request(self, method, path, body):
        """
        Querying for RabbitMq

        *Args:*\n
        _method_ - query method;\n
        _path_ - uri request;\n
        _body_ - body POST request;\n
        """

        if body != "":
            self.headers["Content-Type"] = "application/json"

        logger.debug('Prepared request with method ' + method + ' to ' +
                     'http://' + self._connection.host + ':' +
                     str(self._connection.port) + path + ' and body\n' + body)

        try:
            # TODO: analiser o funcionamento do cache de conexão
            # precisa recriar a toda hora?
            self._connection = http.client.HTTPConnection(
                self._connection.host, self._connection.port,
                self._connection.timeout)
            self._connection.connect()

            self._connection.request(method, path, body, self.headers)
        except socket.error as e:
            raise Exception("Could not send request: {0}".format(e))

        resp = self._connection.getresponse()
        s = resp.read()

        if resp.status == 400:
            raise Exception(json.loads(s))
        if resp.status == 401:
            raise Exception(
                "Access refused: {0}".format('http://' +
                                             self._connection.host + ':' +
                                             str(self._connection.port) +
                                             path))
        if resp.status == 404:
            raise Exception(
                "Not found: {0}".format('http://' + self._connection.host +
                                        ':' + str(self._connection.port) +
                                        path))
        if resp.status == 301:
            url = urllib.parse.urlparse(resp.getheader('location'))
            raise Exception(url)
            [host, port] = url.netloc.split(':')
            self.options.hostname = host
            self.options.port = int(port)
            return self.http(method, url.path + '?' + url.query, body)
        if resp.status < 200 or resp.status > 400:
            raise Exception(
                "Received %d %s for request %s\n%s" %
                (resp.status, resp.reason, 'http://' + self._connection.host +
                 ':' + str(self._connection.port) + path, resp.read()))
        return s

    def _get(self, path):
        return self._http_request('GET', '/api%s' % path, '')

    def _put(self, path, body):
        print("/api%s" % path)
        return self._http_request("PUT", "/api%s" % path, body)

    def _post(self, path, body):
        return self._http_request("POST", "/api%s" % path, body)

    def _delete(self, path):
        return self._http_request("DELETE", "/api%s" % path, "")

    def _quote_vhost(self, vhost):
        """
        Decodificação vhost.
        """

        if vhost == '/':
            vhost = '%2F'
        if vhost != '%2F':
            vhost = urllib.parse.quote(vhost)
        return vhost

    def is_alive(self):
        """
        RabbitMq health check..

        The GET request is sent as follows: 'http://<host>:<port>/api/' and the return code is checked.

        *Returns:*\n
        bool True, if the return code is 200.\n
        bool False in all other cases.

        *Raises:*\n
        socket.error in case it is unreasonable to send a GET request.

        *Example:*\n
        | ${live}=  |  Is Alive |
        =>\n
        True
        """

        try:
            self._get('/cluster-name')
        except Exception:
            return False

        return True

    def overview(self):
        """
        Information about the server RabbitMq.

        *Returns:*\n
        Dictionary with information about the server.

        *Example:*\n
        | ${overview}=  |  Overview |
        | Log Dictionary  |  ${overview} |
        | ${version}=  |  Get From Dictionary | ${overview}  |  rabbitmq_version |
        =>\n
        Dictionary size is 13 and it contains following items:
        | erlang_full_version | Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:2:2] [async-threads:30] [hipe] [kernel-poll:true] |
        | erlang_version | R16B02 |
        | listeners | [{u'node': u'rabbit@srv2-rs582b-m', u'ip_address': u'0.0.0.0', u'protocol': u'amqp', u'port': 5672}] |
        | management_version | 3.6.6 |
        | message_stats | [] |

        ${version} = 3.6.6
        """

        return json.loads(self._get('/overview'))

    def connections(self):
        """
        A list of open connections..
        """

        return json.loads(self._get('/connections'))

    def get_name_of_all_connections(self):
        """
        A list of the names of all open connections.
        """

        names = []
        data = self.connections()
        for item in data:
            names.append(item['name'])
        return names

    def channels(self):
        """
        List of open channels.
        """

        return json.loads(self._get('/channels'))

    def exchanges(self):
        """
        Exchange list.

        *Example:*\n
        | ${exchanges}=  |  Exchanges |
        | Log List  |  ${exchanges} |
        | ${item}=  |  Get From list  |  ${exchanges}  |  1 |
        | ${name}=  |  Get From Dictionary  |  ${q}  |  name  |
        =>\n
        List length is 8 and it contains following items:
        | 0 | {u'name': u'', u'durable': True, u'vhost': u'/', u'internal': False, u'message_stats': [], u'arguments': {}, u'type': u'direct', u'auto_delete': False} |
        | 1 | {u'name': u'amq.direct', u'durable': True, u'vhost': u'/', u'internal': False, u'message_stats': [], u'arguments': {}, u'type': u'direct', u'auto_delete': False} |
        ...\n
        ${name} = amq.direct
        """

        return json.loads(self._get('/exchanges'))

    def get_names_of_all_exchanges(self):
        """
        List of names of all exchanges.

        *Example:*\n
        | ${names}=  |  Get Names Of All Exchanges |
        | Log List  |  ${names} |
        =>\n
        | List has one item:
        | amq.direct
        """

        names = []
        data = self.exchanges()
        for item in data:
            names.append(item['name'])
        return names

    def queues(self):
        """
        List of queues.
        """

        return json.loads(self._get('/queues'))

    def get_queues_on_vhost(self, vhost='%2F'):
        """
        List of queues for the virtual host.

        *Args:*\n
        _vhost_ -the name of the virtual host (recoded using urllib.parse.quote)
        """

        return json.loads(self._get('/queues/' + self._quote_vhost(vhost)))

    def get_queue(self, name, vhost='%2F'):
        """
        List of queues for the virtual host.

        *Args:*\n
        _queue_name_ - queue name;\n
        _vhost_ - the name of the virtual host (recoded using urllib.parse.quote)
        """

        return json.loads(
            self._get('/queues/' + self._quote_vhost(vhost) + '/' +
                      urllib.parse.quote(name)))

    def get_names_of_queues_on_vhost(self, vhost='%2F'):
        """
        List of virtual host queue names.

        *Args:*\n
        - vhost: the name of the virtual host (recoded using urllib.parse.quote)

        *Example:*\n
        | ${names}=  |  Get Names Of Queues On Vhost |
        | Log List  |  ${names} |
        =>\n
        | List has one item:
        | federation: ex2 -> [email protected]
        """
        names = []
        data = self.get_queues_on_vhost(vhost)
        for item in data:
            names.append(item['name'])
        return names

    def queue_exists(self, queue, vhost='%2F'):
        """
        Verifies that the one or more queues exists
        """
        names = self.get_names_of_queues_on_vhost()
        if queue in names:
            return True
        else:
            return False

    def delete_queues_by_name(self, name, vhost='%2F'):
        """
        Remove the queue from the virtual host.

        *Args:*\n
        _name_ -  is the name of the queue;\n
        _vhost_ - is the name of the virtual host;\n
        """
        print('Deletando a fila: %s' % (name))
        return self._delete('/queues/' + self._quote_vhost(vhost) + '/' +
                            urllib.parse.quote(name))

    def vhosts(self):
        """
        List of virtual hosts.
        """
        return json.loads(self._get('/vhosts'))

    def nodes(self):
        """
        List of nodes in the RabbitMQ cluster
        """
        return json.loads(self._get('/nodes'))

    @property
    def _cluster_name(self):
        """
        Name identifying this RabbitMQ cluster.
        """
        return json.loads(self._get('/cluster-name'))

    def create_queues_by_name(self,
                              name,
                              auto_delete=False,
                              durable=True,
                              arguments={},
                              vhost='%2F'):
        """
        Create an individual queue.
        """
        print('Criando a fila: %s' % (name))
        node = self._cluster_name['name']
        body = json.dumps({
            "auto_delete": auto_delete,
            "durable": "true",
            "arguments": arguments,
            "node": node
        })
        print("Body: %s" % body)
        print('/queues/' + self._quote_vhost(vhost) + '/' +
              urllib.parse.quote(name))
        return self._put('/queues/' + self._quote_vhost(vhost) + '/' +
                         urllib.parse.quote(name),
                         body=body)

    def publish_message_by_name(self, queue, msg, properties, vhost='%2F'):
        """
        Publish a message to a given exchange
        """

        name = "amq.default"
        body = json.dumps({
            "properties": properties,
            "routing_key": queue,
            "payload": msg,
            "payload_encoding": "string"
        })
        routed = self._post('/exchanges/' + self._quote_vhost(vhost) + '/' +
                            urllib.parse.quote(name) + '/publish',
                            body=body)
        print("Body Enviado: %s" % body)
        print("Fila Publicada: %s" % queue)
        return json.loads(routed)

    def get_messages_by_queue(self,
                              queue,
                              count=5,
                              requeue=False,
                              encoding="auto",
                              truncate=2000000,
                              vhost='%2F'):
        """
        Get messages from a queue.
        """

        print('Pegando %s mensagens da fila: %s' % (count, queue))
        body = json.dumps({
            "count":
            count,
            "requeue":
            requeue,
            "encoding":
            encoding,
            "truncate":
            truncate,
            "ackmode":
            "ack_requeue_true" if requeue else "ack_requeue_false"
        })
        messages = self._post('/queues/' + self._quote_vhost(vhost) + '/' +
                              urllib.parse.quote(queue) + '/get',
                              body=body)
        return json.loads(messages)

    def purge_messages_by_queue(self, name, vhost='%2F'):
        """
        Purge contents of a queue.
        """
        print('Apagando as mensagens da fila: %s' % (name))
        return self._delete('/queues/' + self._quote_vhost(vhost) + '/' +
                            urllib.parse.quote(name) + '/contents')
class CouchbaseLibrary(object):
    """
    Robot Framework library to work with Couchbase.

    Based on:
    [ http://pythonhosted.org/couchbase | Couchbase Python Client Library]

    == Dependencies ==
    | robot framework | http://robotframework.org |
    | robotframework-jsonvalidator | https://pypi.python.org/pypi/robotframework-jsonvalidator |
    | Couchbase Python Client Library | http://pythonhosted.org/couchbase/ |
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self):
        """ Initialization. """
        self._bucket = None
        self._cache = ConnectionCache()

    def connect_to_couchbase_bucket(self,
                                    host,
                                    port,
                                    bucket_name,
                                    password=None,
                                    alias=None):
        """
        Connect to a Couchbase bucket.

        *Args:*\n
        _host_ - couchbase server host name;\n
        _port_ - couchbase server port number;\n
        _bucket_name_ - couchbase bucket name;\n
        _password_ - password;\n
        _alias_ - connection alias;\n

        *Example:*\n
        | Connect To Couchbase Bucket | my_host_name | 8091 | bucket_name | password | alias=bucket |
        """
        logger.debug(
            'Connecting using : host=%s, port=%s, bucketName=%s, password=%s '
            % (host, port, bucket_name, password))
        connection_string = '{host}:{port}/{bucket}'.format(host=host,
                                                            port=port,
                                                            bucket=bucket_name)
        try:
            bucket = Bucket(connection_string, password=password)
            self._bucket = bucket
            return self._cache.register(self._bucket, alias)
        except CouchbaseError as info:
            raise Exception("Could not connect to Couchbase bucket. Error:",
                            str(info))

    def disconnect_from_couchbase_bucket(self):
        """
        Close the current connection with a Couchbase bucket.

        *Example:*\n
        | Connect To Couchbase Bucket | my_host_name | 8091 | bucket_name | password | alias=bucket |
        | Disconnect From Couchbase Bucket |
        """
        if self._bucket:
            self._bucket._close()
        self._cache.empty_cache()

    def close_all_couchbase_bucket_connections(self):
        """
        Close all connections with Couchbase buckets.

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

        After execution of this keyword, index returned by [#Connect To Couchbase Bucket | Connect To Couchbase Bucket]
        starts at 1.

        *Example:*\n
        | Connect To Couchbase Bucket | my_host_name | 8091 | bucket_name | password | alias=bucket |
        | Close All Couchbase Bucket Connections |
        """
        self._bucket = self._cache.close_all()

    def switch_couchbase_bucket_connections(self, index_or_alias):
        """
        Switch between active connections with Couchbase buckets using their index or alias.

        Connection alias is set in [#Connect To Couchbase Bucket | Connect To Couchbase Bucket],
        which also returns the connection index.

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

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

        *Example:*\n
        | Connect To Couchbase Bucket | my_host_name | 8091 | bucket_name | password | alias=bucket1 |
        | Connect To Couchbase Bucket | my_host_name | 8091 | bucket_name | password | alias=bucket2 |
        | Switch Couchbase Bucket Connection | bucket1 |
        | View Document By Key | key=1C1#000 |
        | Switch Couchbase Connection | bucket2 |
        | View Document By Key | key=1C1#000 |
        | Close All Couchbase Bucket Connections |
        """
        old_index = self._cache.current_index
        self._bucket = self._cache.switch(index_or_alias)
        return old_index

    def view_document_by_key(self, key):
        """
        Get information about the presence of a document in the Couchbase bucket by the given key.
        Depending on the value of the return code, the presence or absence of the document in the bucket is determined.

        *Args:*\n
        _key_ - document key;\n

        *Returns:*\n
        Value of return code.\n
        If rc=0, the document is available in the bucket.

        *Example:*\n
        | ${rc}= | View Document By Key | key=1C1#000 |
        """
        result = self._bucket.get(key, quiet=True).rc
        return result

    def bucket_contains_document_by_key(self, key):
        """
        Check if the Couchbase bucket contains the document by the given key.
        Also it's possible to specify a reference document for comparison.
        The option `quite` is used to check the presence of the document in the bucket.

        *Args:*\n
        _key_ - document key;\n

        *Returns:*\n
        True, if there is a document with this key.

        *Example:*\n
        | ${contain}=   |   Bucket Contains Document By Key |   key=1C1#000 |
        | Should Be True    |   ${contain}  |
        """
        result = self._bucket.get(key, quiet=True)
        logger.debug("{key} contains is {success} with code={code}".format(
            key=key, success=result.success, code=result.rc))
        return result.success is True

    def get_document_cas_by_key(self, key):
        """
        Get CAS of the document in the Couchbase bucket by the given key.

        *Args:*\n
        _key_ - document key;\n

        *Returns:*\n
        CAS value of the document.\n

        *Example:*\n
        | ${cas}= | Get Document Cas By Key | key=1C1#000 |
        """
        result = self._bucket.get(key).cas
        return result

    def get_document_value_by_key(self, key):
        """
        Get a document value in the Couchbase bucket by the given key.

        *Args:*\n
        _key_ - document key;\n

        *Returns:*\n
        Dictionary with the value of the document.\n

        *Example:*\n
        | ${value}= | Get Document Value By Key | key=1C1#000 |
        """
        result = self._bucket.get(key).value
        return result

    def validate_document_by_json(self, key, json_expr):
        """
        Checking a document to match the json expression.
        The document is specified by the key in the bucket.

        *Args:*\n
        _key_ - document key in the bucket;\n
        _json_expr_ - JSONSelect expression.\n

        *Returns:*\n
        True if the document exists in the bucket and matches the json expression.\n

        *Example:*\n
        | ${valid}= |   Validate Document By Json   |   key=dockey  |   json_expr=.somekey:val("value") |
        | Should Be True    |   ${valid}  |
        """
        result = self._bucket.get(key, quiet=True)
        if result.success is not True:
            return False
        try:
            validator = JsonValidator()
            json_txt = validator.json_to_string(result.value)
            validator.element_should_exist(json_txt, json_expr)
        except JsonValidatorError as error:
            logger.debug(
                "on json validation got exception {ex}".format(ex=error))
            return False
        return True

    def certainly_delete_document_by_key(self, key):
        """
        Remove a document for a given key from Couchbase bucket.
        Doesn't raise NotFoundError if the key doesn't exist.

        *Args:*\n
        _key_ - document key;\n

        *Example:*\n
        | Certainly Delete Document By Key | key=1C1#000 |
        """
        self._bucket.remove(key, quiet=True)

    def upsert_document(self, key, value):
        """
        Insert or update a document in the current Couchbase bucket.

        *Args:*\n
        _key_ - document key;\n
        _value_ - document body;\n

        *Example:*\n
        |   Upsert Document |   somekey |   {'key': 'value'}    |
        """
        self._bucket.upsert(key, value)
class ApacheTomcatManager(object):
    """
    Library to manage Apache Tomcat server.


    Implemented on the basis of:
    - [ http://tomcat.apache.org/tomcat-7.0-doc/manager-howto.html | Manager App HOW-TO ]
    - [ https://github.com/kotfu/tomcat-manager | tomcat-manager ]

    == Dependencies ==
    | robot framework | http://robotframework.org |
    | requests | https://pypi.python.org/pypi/requests |

    == Example ==
    | *Settings* | *Value* |
    | Library    |       ApacheTomcatManager |
    | Library     |      Collections |

    | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* | *Argument* | *Argument* |
    | Simple |
    |    | Connect To Tomcat | my_host_name | 8080 | tomcat | tomcat | alias=tmc |
    |    | ${info}= | Serverinfo |
    |    | Log Dictionary | ${info} |
    |    | Close All Tomcat Connections |

    == Additional Information ==
    To use this library you need to set up setting for Apache Tomcat user. Add a user with roles:
    - manager-gui,
    - manager-script,
    - manager-jmx,
    - manager-status,
    to file tomcat-users.xml on Apache Tomcat server.

    Example:
    | <tomcat-users>
    | <user username="******" password="******" roles="manager-jmx,manager-status,manager-script,admin,manager-gui,admin-gui,manager-script,admin"/>
    | </tomcat-users>
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self) -> None:
        """ Initialization. """
        self._connection: Optional[RequestConnection] = None
        self.headers: Dict[str, str] = {}
        self._cache = ConnectionCache()

    def connect_to_tomcat(self,
                          host: str,
                          port: Union[int, str],
                          username: str = 'tomcat',
                          password: str = 'tomcat',
                          timeout: Union[int, str] = 15,
                          alias: str = None) -> int:
        """Connect to Apache Tomcat server.

        *Args:*\n
            _host_ - server host name;\n
            _port_ - port name;\n
            _username_ - user name;\n
            _password_ - user password;\n
            _timeout_ - connection timeout;\n
            _alias_ - connection alias;\n

        *Returns:*\n
            The current connection index.

        *Example:*\n
        | Connect To Tomcat | my_host_name | 8080 | tomcat | tomcat | alias=tmc1 |
        """
        port = int(port)
        timeout = int(timeout)
        self.headers["Content-Type"] = "text/plain"

        logger.debug(
            f'Connecting using : host={host}, port={port}, username={username}, password={password}, '
            f'timeout={timeout}, alias={alias}')

        self._connection = RequestConnection(host, port, username, password,
                                             timeout)
        return self._cache.register(self._connection, alias)

    def switch_tomcat_connection(self, index_or_alias: Union[int, str]) -> int:
        """Switch between active Apache Tomcat connections, using their index or alias.

        Alias is set in keyword [#Connect To Tomcat|Connect To Tomcat], which also returns the index of connection.

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

        *Returns:*\n
            The index of previous connection.

        *Example:*\n
        | Connect To Tomcat | my_host_name_1 | 8080 | tomcat | tomcat | alias=rmq1 |
        | Connect To Tomcat | my_host_name_2 | 8080 | tomcat | tomcat | alias=rmq2 |
        | Switch Tomcat Connection | rmq1 |
        | ${info1}= | Serverinfo |
        | Switch Tomcat Connection | rmq2 |
        | ${info2}= | Serverinfo |
        | Close All Tomcat Connections |
        """
        old_index = self._cache.current_index
        self._connection = self._cache.switch(index_or_alias)
        return old_index

    def disconnect_from_tomcat(self) -> None:
        """Close the current connection with Apache Tomcat.

        *Example:*\n
        | Connect To Tomcat | my_host_name | 8080 | tomcat | tomcat | alias=rmq1 |
        | Disconnect From Tomcat |
        """
        if self._connection:
            logger.debug(
                f'Close connection with : host={self._connection.host}, port={self._connection.port}'
            )
            self._connection.close()

    def close_all_tomcat_connections(self) -> None:
        """Close all connections with Apache Tomcat.

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

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

        *Example:*\n
        | Connect To Tomcat | my_host_name_1 | 8080 | tomcat | tomcat | alias=rmq1 |
        | Close All Tomcat Connections |
        """
        self._connection = self._cache.close_all()

    def list(self) -> List[List[str]]:
        """Get list of installed applications.

        *Returns:*\n
            List of installed web applications of following format:
            | application path | running status | number of sessions | application name |

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | ${list}=  |  List |
        | Log List  |  ${list} |
        =>
        | /dcs-workbench1 | running | 0 | dcs-workbench1 |
        | /manager | running | 1 | manager |
        | / | running | 0 | ROOT |
        | /docs | running | 0 | docs |
        | /dcs-workbench | running | 0 | dcs-workbench |
        | /dcs | running | 0 | dcs |
        | /host-manager | running | 0 | host-manager |
        """
        if self._connection is None:
            raise Exception('No open connection to Apache Tomcat server.')

        apps = []
        url = f'{self._connection.url}/list'
        logger.debug(f'Prepared request with method GET to {url}')
        response = requests.get(url,
                                auth=self._connection.auth,
                                headers=self.headers,
                                timeout=self._connection.timeout)
        response.raise_for_status()
        resp_list = response.text.split('\n')
        for lines in resp_list[1:-1]:
            apps.append(lines.split(':'))
        return apps

    def application_status(self, path: str) -> str:
        """Get the web application running status.

        *Args:*\n
            _path_ - path to the web application;

        *Returns:*\n
            The web application running status: running, stopping.

        *Raises:*\n
            raise Exception в том случае, если web-приложение не загружено на Apache Tomcat.

        *Example:*\n
        | ${status}=  |  Application Status  |  /dcs-workbench |
        =>
        running
        """
        app_list = self.list()
        for app_name, status, _, _ in app_list:
            if app_name == path:
                return status
        raise Exception(
            f'Application with path "{path}" not found on Apache Tomcat')

    def serverinfo(self) -> Dict[str, str]:
        """Get information about server.

        *Returns:*\n
            Information of Apache Tomcat server in dictionary format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | ${info}=  |  Serverinfo  |
        | Log Dictionary  |  ${info} |
        =>
        | JVM Vendor: Oracle Corporation
        | JVM Version: 1.7.0_40-b43
        | OS Architecture: amd64
        | OS Name: Linux
        | OS Version: 2.6.32-279.el6.x86_64
        | Tomcat Version: Apache Tomcat/7.0.22
        """
        if self._connection is None:
            raise Exception('No open connection to Apache Tomcat server.')

        serverinfo = {}
        url = f'{self._connection.url}/serverinfo'
        logger.debug(f'Prepared request with method GET to {url}')
        response = requests.get(url,
                                auth=self._connection.auth,
                                headers=self.headers,
                                timeout=self._connection.timeout)
        response.raise_for_status()
        resp_list = response.text.split('\n')

        for lines in resp_list[1:-1]:
            key, value = lines.rstrip().split(":", 1)
            serverinfo[key] = value.lstrip()
        return serverinfo

    def application_stop(self, path: str) -> None:
        """Stop the running web application.

        *Args:*\n
            _path_ - path to web application;

        *Raises:*\n
            raise Exception in case the web application could not be stopped.

        *Example:*\n
        |  Application Stop  |  /dcs-workbench |
        """
        if self._connection is None:
            raise Exception('No open connection to Apache Tomcat server.')

        url = f'{self._connection.url}/stop?path={quote(path)}'
        logger.debug(f'Prepared request with method GET to {url}')
        response = requests.get(url,
                                auth=self._connection.auth,
                                headers=self.headers,
                                timeout=self._connection.timeout)
        logger.debug(f'Response: {response.text}')
        if response.text != f'OK - Stopped application at context path {path}\n':
            raise Exception('Application  is not stopped:\n' + response.text)

    def application_start(self, path: str) -> None:
        """Start web application.

        *Args:*\n
            _path_ - path to web application;

        *Raises:*\n
            raise Exception in case the web application could not be stopped.

        *Example:*\n
        |  Application Start  |  /dcs-workbench |
        """
        if self._connection is None:
            raise Exception('No open connection to Apache Tomcat server.')

        url = f'{self._connection.url}/start?path={quote(path)}'
        logger.debug(f'Prepared request with method GET to {url}')
        response = requests.get(url,
                                auth=self._connection.auth,
                                headers=self.headers,
                                timeout=self._connection.timeout)
        logger.debug(f'Response: {response.text}')
        if response.text != f'OK - Started application at context path {path}\n':
            raise Exception('Application  is not started:\n' + response.text)

    def application_reload(self, path: str) -> None:
        """Reload web application.

        *Args:*\n
            _path_ - path to web application;

        *Raises:*\n
            raise Exception in case the web application could not be stopped.

        *Example:*\n
        |  Application Reload  |  /dcs-workbench |
        """
        if self._connection is None:
            raise Exception('No open connection to Apache Tomcat server.')

        url = f'{self._connection.url}/reload?path={quote(path)}'
        logger.debug(f'Prepared request with method GET to {url}')
        response = requests.get(url,
                                auth=self._connection.auth,
                                headers=self.headers,
                                timeout=self._connection.timeout)
        logger.debug(f'Response: {response.text}')
        if response.text != f'OK - Reloaded application at context path {path}\n':
            raise Exception('Application  is not started:\n' + response.text)
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
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_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 _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
Exemple #20
0
class amqp_library(object):
    """
    Connect to an AMQP server and receive messages or send messages in Robotframework
    """
    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_amqp_connection(self, amqp_host, amqp_port, amqp_user, amqp_pass, amqp_vhost, alias):
        """
        Init the connection to the amqp server
        Example:
        *** Keywords ***
        Before tests
            Init AMQP connection    ${amqp_host}  ${amqp_port}   ${amqp_user}  ${amqp_pass}   ${amqp_vhost}
        """
        self.amqp_addr = "amqp://{user}:{passwd}@{host}:{port}/{vhost}".format(user=amqp_user,
                                                                               passwd=amqp_pass,
                                                                               host=amqp_host,
                                                                               port=amqp_port,
                                                                               vhost=amqp_vhost)

        logger.debug("AMQP connect to: {}".format(self.amqp_addr))
        res = False
        cnt = 0
        while res is False and cnt < 3:
            try:
                params = pika.URLParameters(self.amqp_addr)
                self.amqp_connection = pika.BlockingConnection(parameters=params)
                if self.amqp_connection is not None:
                    self.amqp_channel = self.amqp_connection.channel()
                    self._cache.register(self.amqp_connection, alias)
                res = True
            except Exception as e:
                logger.debug("exception {}".format(e))
            time.sleep(3)
            cnt = cnt + 1
        return res

    def switch_rabbitmq_connection(self, index_or_alias):
        """
        *Example:*\n
        | Connect To Rabbitmq | my_host_name_1 | 15672 | guest | guest | alias=rmq1 |
        | Connect To Rabbitmq | my_host_name_2 | 15672 | guest | guest | alias=rmq2 |
        | Switch Rabbitmq Connection | rmq1 |
        | ${live}= | Is alive |
        | Switch Rabbitmq Connection | rmq2 |
        | ${live}= | Is alive |
        | Close All Rabbitmq Connections |
        """

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

    def close_amqp_connection(self):
        """
        Close the amqp connection
        Usage:
        *** Keywords ***
        After tests
            close amqp connection
        """
        self.amqp_connection.close()

    def close_all_rabbitmq_connections(self):
        """
        Close all the amqp connections
        Usage:
        ** Keywords ***
        | Connect To Rabbitmq | my_host_name | 15672 | guest | guest | alias=rmq |
        | Close All Rabbitmq Connections |
        """
        self.amqp_connection = self._cache.close_all()

    def is_alive(self):
        """
        Return if this connection is open
        *Returns:*\n
        bool True, connection is alive.\n
        bool False, connection closed

        *Example:*\n
        | ${live}=  |  Is Alive |
        =>\n
        True
        """

        return self.amqp_connection.is_open

    def set_amqp_destination(self, exchange, routing_key):
        """
        Set destination for subsequent send_amqp_msg calls
        :param exchange:    amqp exchange name
        :param routing_key: amqp routing_key
        """
        self.exchange = exchange
        self.routing_key = routing_key

    def set_amqp_queue(self, amqp_queue):
        """
        Set queue to listen to and declare it on AMQP server for the subsequent get_amqp_msg calls
        :param amqp_queue string:
        """
        self.queue = amqp_queue
        self.amqp_channel.queue_declare(queue=self.queue)

    def send_amqp_msg(self, msg, exchange=None, routing_key=None):
        """
        Send one message via AMQP
        :param msg:
        :param exchange: name of the exchange to send the message to; default: self.exchange
        :param routing_key: the routing key to use; default is self.routing_key
        """
        amqp_exchange = exchange if exchange is not None else self.exchange
        amqp_routing_key = routing_key if routing_key is not None else self.routing_key

        logger.debug("AMQP send ---> ({} / {})".format(amqp_exchange, amqp_routing_key))
        logger.debug("AMQP msg to send: {}".format(msg))

        self.amqp_channel.basic_publish(exchange=amqp_exchange,
                                        routing_key=amqp_routing_key,
                                        body=msg)

    def get_amqp_msg(self, msg_number=1, queue=None):
        """
        Get at least 1 message from the configured queue
        :param msg_number:  number of messages to consume form the queue
        :param queue:   queue_name to listen to; if missing listen to the queue configured via set_amqp_queue
        :return:
        """

        queue_name = queue if queue is not None else self.queue
        received_messages = []

        # variant with basic_get
        try:
            for ev_method, ev_prop, ev_body in self.amqp_channel.consume(queue_name,
                                                                         inactivity_timeout=self.amqp_timeout):
                if ev_method:
                    logger.debug("AMQP received <-- {}".format(ev_body))
                    self.amqp_channel.basic_ack(ev_method.delivery_tag)
                    received_messages.append(ev_body)
                if ev_method.delivery_tag == int(msg_number):
                    break
        except Exception as e:
            logger.debug("exception {}".format(e))
        requeued_messages = self.amqp_channel.cancel()
        logger.debug("AMQP requeued after {} received: {}".format(msg_number, requeued_messages))
        return received_messages
Exemple #21
0
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)
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
Exemple #23
0
class RabbitMq(object):
    """
    Library for working with RabbitMQ.

    == Dependencies ==
    | amqp | https://pypi.python.org/pypi/amqp |
    | requests | https://pypi.python.org/pypi/requests |
    | robot framework | http://robotframework.org |

    == Example ==
    | *Settings* | *Value* |
    | Library    | RabbitMq |
    | Library    | Collections |

    | *Test Cases* | *Action* | *Argument* | *Argument* | *Argument* | *Argument* | *Argument* |
    | Simple |
    |    | Create Rabbitmq Connection | my_host_name | 15672 | 5672 | guest | guest | alias=rmq |
    |    | ${overview}= | Overview |
    |    | Log Dictionary | ${overview} |
    |    | Close All Rabbitmq Connections |
    """

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self):
        """ Initialization. """
        self._http_connection = None
        self._http_cache = ConnectionCache()
        self._amqp_connection = None
        self._amqp_cache = ConnectionCache()
        self._channel = None

    def _connect_to_amqp(self,
                         host,
                         port,
                         username='******',
                         password='******',
                         alias=None,
                         virtual_host='/'):
        """ Connect to server via AMQP.

        *Args*:\n
            _host_: server host name.\n
            _port_: port number.\n
            _username_: user name.\n
            _password_: user password.\n
            _alias_: connection alias.\n
            _virtual_host_: virtual host name.\n

        *Returns:*\n
            Server connection object.
        """

        if port is None:
            BuiltIn().fail(msg="RabbitMq: port for connect is None")
        port = int(port)
        timeout = 15
        if virtual_host is None:
            BuiltIn().fail(msg="RabbitMq: virtual host for connect is None")
        virtual_host = str(virtual_host)

        parameters_for_connect = "host={host}, port={port}, username={username}, timeout={timeout}, alias={alias}".format(
            host=host,
            port=port,
            username=username,
            timeout=timeout,
            alias=alias)

        logger.debug('Connecting using : {params}'.format(
            params=parameters_for_connect))
        hostname = "{host}:{port}".format(host=host, port=port)
        try:
            self._amqp_connection = BlockedConnection(
                host=hostname,
                userid=username,
                password=password,
                connect_timeout=timeout,
                virtual_host=virtual_host,
                confirm_publish=True)
        except (gaierror, error, IOError):
            BuiltIn().fail(
                msg=
                "RabbitMq: Could not connect with following parameters: {params}"
                .format(params=parameters_for_connect))
        self._channel = None
        return self._amqp_cache.register(self._amqp_connection, alias)

    def _connect_to_http(self, host, port, username, password, alias):
        """ Connect to server via HTTP.

        *Args*:\n
            _host_: server host name.\n
            _port_: port number.\n
            _username_: user name.\n
            _password_: user password.\n
            _alias_: connection alias.\n

        *Returns:*\n
            Server connection object.
        """
        if port is None:
            BuiltIn().fail(msg="RabbitMq: port for connect is None")
        port = int(port)
        timeout = 15
        parameters_for_connect = "host={host}, port={port}, username={username}, timeout={timeout}, alias={alias}".format(
            host=host,
            port=port,
            username=username,
            timeout=timeout,
            alias=alias)

        logger.debug('Connecting using : {params}'.format(
            params=parameters_for_connect))
        try:
            self._http_connection = RequestConnection(host, port, username,
                                                      password, timeout)
        except (gaierror, error, IOError):
            BuiltIn().fail(
                msg=
                "RabbitMq: Could not connect with following parameters: {params}"
                .format(params=parameters_for_connect))
        return self._http_cache.register(self._http_connection, alias)

    def create_rabbitmq_connection(self, host, http_port, amqp_port, username,
                                   password, alias, vhost):
        """
        Connect to RabbitMq server.

        *Args:*\n
        _host_ - server host name;\n
        _http_port_ - port number of http-connection \n
        _amqp_port_ - port number of amqp-connection \n
        _username_ - user name;\n
        _password_ - user password;\n
        _alias_ - connection alias;\n
        _virtual_host_ - virtual host name;\n

        *Returns:*\n
        Current connection index.

        *Raises:*\n
        socket.error if connection cannot be created.

        *Example:*\n
        | Create Rabbitmq Connection | my_host_name | 15672 | 5672 | guest | guest | alias=rmq | vhost=/ |
        """

        self._connect_to_http(host=host,
                              port=http_port,
                              username=username,
                              password=password,
                              alias=alias + "_http")
        self._connect_to_amqp(host=host,
                              port=amqp_port,
                              username=username,
                              password=password,
                              alias=alias + "_amqp",
                              virtual_host=vhost)

    def switch_rabbitmq_connection(self, alias):
        """Switch between active RabbitMq connections using their index or alias.\n

        Alias is set in keyword [#Create Rabbitmq Connection|Create Rabbitmq Connection]
        which also returns the index of connection.\n

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

        *Returns:*\n
        Index of previous connection.

        *Example:*\n
        | Create Rabbitmq Connection | my_host_name_1 | 15672 | 5672 | guest | guest | alias=rmq1 |
        | Create Rabbitmq Connection | my_host_name_2 | 15672 | 5672 | guest | guest | alias=rmq2 |
        | Switch Rabbitmq Connection | rmq1 |
        | ${live}= | Is alive |
        | Switch Rabbitmq Connection | rmq2 |
        | ${live}= | Is alive |
        | Close All Rabbitmq Connections |
        """

        old_index = self._http_cache.current_index
        logger.debug('Switch active connection from {old} to {new}'.format(
            old=old_index, new=alias))
        self._http_connection = self._http_cache.switch(alias + '_http')
        self._amqp_connection = self._amqp_cache.switch(alias + '_amqp')
        self._channel = None
        return old_index

    def disconnect_from_rabbitmq(self):
        """
        Close current RabbitMq connection.

        *Example:*\n
        | Create Rabbitmq Connection | my_host_name | 15672 | 5672 | guest | guest | alias=rmq |
        | Disconnect From Rabbitmq |
        """

        logger.debug('Close connection with : host={host}, port={port}'.format(
            host=self._http_connection.host, port=self._http_connection.port))
        self._http_connection.close()
        self._http_connection = None
        self._channel = None
        if self._amqp_connection is not None:
            if self._amqp_connection.connected:
                self._amqp_connection.close()
            self._amqp_connection = None

    def close_all_rabbitmq_connections(self):
        """
        Close all RabbitMq connections.

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

        After this keyword is executed the index returned by [#Create Rabbitmq Connection|Create Rabbitmq Connection]
        starts at 1.\n

        *Example:*\n
        | Create Rabbitmq Connection | my_host_name | 15672 | 5672 | guest | guest | alias=rmq |
        | Close All Rabbitmq Connections |
        """

        self._http_cache.close_all()
        self._http_connection = None
        self._amqp_cache.close_all()
        self._amqp_connection = None
        self._channel = None

    # AMQP API

    def _get_channel(self):
        """ Get channel from current connection.

        *Returns:*\n
            Channel.
        """
        if self._channel is None:
            self._channel = self._amqp_connection.channel()
        if self._amqp_connection.blocked:
            raise Exception('Connection is blocked')
        return self._channel

    def create_exchange(self,
                        exchange_name,
                        exchange_type,
                        auto_delete=None,
                        durable=None,
                        arguments=None):
        """
        Create exchange.

        The parameter _arguments_ is passed as dictionary.\n
        When defining "alternate-exchange" argument in the dictionary
        it is necessary to pass exchange's alternative name
        (if message cannot be routed it will be sent to alternative exchange).\n

        *Args:*\n
        _exchange_name_ - exchange name;\n
        _exchange_type_ - exchange type (direct, topic, headers, fanout);\n
        _auto_delete_ - delete exchange when all queues finish working with it (true, false);\n
        _durable_ - exchange survives when broker restarts (true, false);\n
        _arguments_ - additional arguments in dictionary format;\n

        *Example:*\n
        | ${list}= | Create List | list_value | ${TRUE} | 18080 |
        | ${args}= | Create Dictionary | arg1=value1 | arg2=${list} | alternate-exchange=amq.fanout |
        | Create Exchange | exchange_name=testExchange | exchange_type=fanout | auto_delete=false | durable=true | arguments=${args} |
        """

        exchange_name = str(exchange_name)
        exchange_type = str(exchange_type)
        logger.debug("Creating new exchange {ex} with type {t}".format(
            ex=exchange_name, t=exchange_type))
        self._get_channel().exchange_declare(exchange=exchange_name,
                                             type=exchange_type,
                                             durable=durable,
                                             auto_delete=auto_delete,
                                             arguments=arguments)

    def is_exchange_exist(self, name, exchange_type):
        """
        Check if exchange exists

        *Args:*\n
        _name_ - exchange name;\n
        _exchange_type_ - exchange type;\n

        *Example:*\n
        | ${is_exist}= | Is Exchange Exist | name='name' | exchange_type='direct' |
        | Should Be True | ${is_exist} |

        *Returns:*\n
        True if exchange exists otherwise False
        """

        name = str(name)
        exchange_type = str(exchange_type)
        try:
            self._get_channel().exchange_declare(exchange=name,
                                                 type=exchange_type,
                                                 passive=True)
            return True
        except NotFound:
            return False

    def delete_exchange(self, exchange_name):
        """
        Delete exchange.

        *Args:*\n
        _exchange_name_ - exchange name;\n

        *Example:*\n
        | Delete Exchange | exchange_name=testExchange |
        """

        exchange_name = str(exchange_name)
        self._get_channel().exchange_delete(exchange=exchange_name)

    def create_queue(self,
                     queue_name,
                     auto_delete=None,
                     durable=None,
                     node=None,
                     arguments=None):
        """
        Create queue.

        *Args:*\n
        _queue_name_ - queue name (quoted with requests.utils.quote);\n
        _auto_delete_ - delete queue when last subscriber unsubscribes from queue (true, false);\n
        _durable_ - queue survives when broker restarts (true, false);\n
        _node_ - RabbitMq node name;\n
        _arguments_ - additional arguments in dictionary format;\n

        *Example:*\n
        | ${list}= | Create List | list_value | ${FALSE} | 15240 |
        | ${args}= | Create Dictionary | arg1=value1 | arg2=${list} |
        | Create Queue | queue_name=testQueue | auto_delete=false | durable=true | node=rabbit@primary | arguments=${args} |
        """

        queue_name = str(queue_name)
        logger.debug('Create queue {n}'.format(n=queue_name))
        self._get_channel().queue_declare(queue=queue_name,
                                          durable=durable,
                                          auto_delete=auto_delete,
                                          arguments=arguments)

    def is_queue_exist(self, name):
        """
        Check if queue exists

        *Args:*\n
        _name_ - queue name

        *Example:*\n
        | ${exist}= | Is Queue Exist | name='queue' |
        | Should Be True | ${exist} |

        *Returns:*\n
        True if queue exists otherwise False
        """

        try:
            self._get_channel().queue_declare(queue=name, passive=True)
            return True
        except NotFound:
            return False

    def binding_exchange_with_queue(self,
                                    exchange_name,
                                    queue_name,
                                    routing_key='',
                                    arguments=None):
        """
        Create binding of exchange with queue.

        *Args:*\n
        _exchange_name_ - exchange name;\n
        _queue_name_ - queue name;\n
        _routing_key_ - routing key;\n
        _arguments_ - additional arguments in dictionary format;\n

        *Example:*\n
        | ${list}= | Create List | str1 | ${FALSE} |
        | ${args}= | Create Dictionary | arg1=value1 | arg2=${list} |
        | Binding Exchange With Queue | exchange_name=testExchange | queue_name=testQueue | routing_key=key | arguments=${args} |
        """

        queue_name = str(queue_name)
        exchange_name = str(exchange_name)
        logger.debug(
            'Binding queue {q} to exchange {e}, with routing key {r}'.format(
                q=queue_name, e=exchange_name, r=routing_key))
        self._get_channel().queue_bind(queue=queue_name,
                                       exchange=exchange_name,
                                       routing_key=routing_key,
                                       arguments=arguments)

    def unbind_queue(self,
                     queue_name,
                     exchange_name,
                     routing_key='',
                     arguments=None):
        """
        Unbind queue from exchange.

        *Args:*\n
        _queue_name_ - queue name;\n
        _exchange_name_ - exchange name;\n
        _routing_key_ - routing key;\n
        _arguments_ - additional arguments in dictionary format;\n
        """

        queue_name = str(queue_name)
        exchange_name = str(exchange_name)
        logger.debug(
            'Unbind queue {q} from exchange {e} with routing key {r}'.format(
                q=queue_name, r=routing_key, e=exchange_name))
        self._get_channel().queue_unbind(queue=queue_name,
                                         exchange=exchange_name,
                                         routing_key=routing_key,
                                         arguments=arguments)

    def purge_queue(self, queue_name):
        """
        Purge queue.

        *Args:*\n
        _queue_name_ - queue name;\n
        """

        queue_name = str(queue_name)
        logger.debug('Purge queue {q}'.format(q=queue_name))
        self._get_channel().queue_purge(queue=queue_name)

    def delete_queue(self, queue_name):
        """
        Delete queue.

        *Args:*\n
        _queue_name_ - queue name;\n

        *Example:*\n
        | Delete Queue | queue_name=testQueue |
        """

        queue_name = str(queue_name)
        self._get_channel().queue_delete(queue=queue_name)

    def enable_consuming_messages_in_queue(self, queue_name, count, requeue,
                                           consumed_list):
        """
        Enable consuming messages in queue.

        *Args:*\n
        _queue_name_ - queue name;\n
        _count_ - number of messages to consume;\n
        _requeue_ - re-placing consumed message in the queue with setting of redelivered attribute (true, false);\n
        _consumed_list_ - list of delivery_tag of all consumed messages;\n

        *Returns:*\n
        Identifier of message handler in the queue.

        *Example:*\n
        | ${list}= | Create List |
        | Enable Consuming Messages In Queue | queue_name=${QUEUE_NAME} | count=1 | requeue=${FALSE} | consumed_list=${list} |
        | Log List | ${list} |
        """

        count = int(count)
        queue_name = str(queue_name)
        consumer_name = "consumer{name}".format(name=queue_name)

        def consumer_callback(message):
            """
            Callback for consuming messages from the queue.

            Processes specified number of messages and closes.

            *Args:*\n
            _message_ - message from the queue.
            """
            channel = message.channel
            tag = message.delivery_info['delivery_tag']
            logger.debug("Consume message {} - {}".format(tag, message.body))
            channel.basic_reject(tag, requeue)
            consumed_list.append(tag)
            if len(consumed_list) >= count:
                channel.basic_cancel(consumer_name)

        logger.debug('Begin consuming messages. Queue={q}, count={c}'.format(
            q=queue_name, c=count))
        self._get_channel().basic_consume(queue=queue_name,
                                          consumer_tag=consumer_name,
                                          callback=consumer_callback)
        return consumer_name

    def publish_message(self, exchange_name, routing_key, payload, props=None):
        """
        Publish message to the queue.

        *Args:*\n
        _exchange_name_ - exchange name;\n
        _routing_key_ - routing key (quoted with requests.utils.quote);\n
        _payload_ - payload message;\n
        _props_ - additional arguments in dictionary format;\n
         Includes such keys as:\n
        - _content-type_ - message content type (shortstr);
        - _content_encoding_ - message encoding type (shortstr);
        - _application_headers_ - message headers table, a dictionary with keys of type string and values of types
         string | int | Decimal | datetime | dict values (table);
        - _delivery_mode_ - Non-persistent (1) or persistent (2) (octet);
        - _priority_ - message priority from 0 to 9 (octet);
        - _correlation_id_ - message identifier to which current message responds (shortstr);
        - _reply_to_ - commonly used to name a reply queue (shortstr);
        - _expiration_ - expiration date of message (shortstr);
        - _message_id_ - message identifier (shortstr);
        - _timestamp_ - timestamp of sending message (shortstr);
        - _type_ - message type (shortstr);
        - _user_id_ - user-sender identifier (shortstr);
        - _app_id_ - application identifier (shortstr);
        - _cluster_id_ - cluster identifier (shortstr);\n

        *Attention:*\n

        When using library in robot-files parameters (props)
         must be cast to the correct type.\n
        Example:\n
        | ${delivery_mode}= | Convert To Integer | 2 |
        This is due to the feature of RabbitMq library.\n

        *Example:*\n
        | ${list_headers}= | Create List | head_value | 2 | ${TRUE} |
        | ${headers_dict}= | Create Dictionary | head1=val1 | head2=${list_headers} |
        | ${prop_dict}= | Create Dictionary | application_headers=${headers_dict} | content-type=text/plain | priority=1 | expiration=1410966000 | message_id=101 | user_id=guest |
        | Publish Message | exchange_name=testExchange | routing_key=testQueue | payload=message body | props=${prop_dict} |
        """

        if props is None:
            props = {}
        exchange_name = str(exchange_name)
        routing_key = str(routing_key)
        logger.debug('Publish message to {exc} with routing {r}'.format(
            exc=exchange_name, r=routing_key))
        msg = Message(payload, **props)
        self._get_channel().basic_publish(msg, exchange_name, routing_key)

    def process_published_message_in_queries(self, waiting=1):
        """
        Send processing of published message in queues to handler.\n
        May end with exception if handler is not installed or there are no messages in queue.\n

        *Args:*\n
        _waiting_ - server response timeout.
        """

        waiting = int(waiting)
        try:
            self._amqp_connection.drain_events(waiting)
        except timeout:
            pass

    def enable_message_sending_confirmation(self,
                                            confirmed_list,
                                            activate=True):
        """
        Enable processing of successful message sending confirmation in the exchange servers.\n
        If message is successfully sent to confirmed_list, delivery_tag of the message is added.\n

        *Args:*\n
        _confirmed_list_ - list in which all the delivery tag of sent messages are saved;\n
        _activate_ - indicates that message sending listener should start;\n

        *Example:*\n
        | ${list}= | Create List |
        | Enable Message Sending Confirmation | confirmed_list=${list} |
        | Publish Message | exchange_name=${EXCHANGE_NAME} | routing_key=${ROUTING_KEY} | payload=message body |
        | Process Published Message In Queries |
        | Length Should Be | ${list} | 1 |
        """
        def confirm_callback(delivery_tag, multiple):
            """
            Called when sending message notification is received.
            """
            logger.debug('Capture confirm message with tag={tag}'.format(
                tag=delivery_tag))
            confirmed_list.append(delivery_tag)

        self._get_channel().confirm_select()
        logger.debug('Begin checking confirm publish')
        if activate is True:
            self._get_channel().events['basic_ack'].add(confirm_callback)
        else:
            self._get_channel().events['basic_ack'].remove(confirm_callback)

    # Manager API

    @staticmethod
    def _prepare_request_headers(body=None):
        """
        Headers definition for HTTP-request.
        Args:*\n
            _body_: HTTP-request body.

        *Returns:*\n
            Dictionary with headers for HTTP-request.
        """
        headers = {}
        if body:
            headers["Content-Type"] = "application/json"
        return headers

    @staticmethod
    def _quote_vhost(vhost):
        """ Vhost quote.

        *Args:*\n
            _vhost_: vhost name for quoting.

        *Returns:*\n
            Quoted name of vhost.
        """
        if vhost == '/':
            vhost = '%2F'
        if vhost != '%2F':
            vhost = quote(vhost)
        return vhost

    def is_alive(self):
        """
        Rabbitmq health check.

        Sends GET-request : 'http://<host>:<port>/api/' and checks response status code.\n

        *Returns:*\n
        bool True if return code is 200.
        bool False in all other cases.

        *Raises:*\n
        RequestException if it is not possible to send GET-request.

        *Example:*\n
        | ${live}= | Is Alive |
        =>\n
        True
        """
        try:
            response = requests.get(self._http_connection.url,
                                    auth=self._http_connection.auth,
                                    headers=self._prepare_request_headers(),
                                    timeout=self._http_connection.timeout)
        except requests.exceptions.RequestException as e:
            raise Exception('Could not send request: {0}'.format(e))
        logger.debug('Response status={0}'.format(response.status_code))
        return response.status_code == 200

    def overview(self):
        """ Information about RabbitMq server.

        *Returns:*\n
        Dictionary with information about the server.

        *Raises:*\n
        raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | ${overview}=  |  Overview |
        | Log Dictionary | ${overview} |
        | ${version}= | Get From Dictionary | ${overview}  |  rabbitmq_version |
        =>\n
        Dictionary size is 14 and it contains following items:
        | cluster_name | rabbit@primary |
        | contexts | [{u'node': u'rabbit@primary', u'path': u'/', u'description': u'RabbitMQ Management', u'port': 15672}, {u'node': u'rabbit@primary', u'path': u'/web-stomp-examples', u'description': u'WEB-STOMP: examples', u'port': 15670}] |
        | erlang_full_version | Erlang R16B03 (erts-5.10.4) [source] [64-bit] [async-threads:30] [kernel-poll:true] |
        | erlang_version | R16B03 |
        | exchange_types | [{u'enabled': True, u'name': u'fanout', u'description': u'AMQP fanout exchange, as per the AMQP specification'}, {u'internal_purpose': u'federation', u'enabled': True, u'name': u'x-federation-upstream', u'description': u'Federation upstream helper exchange'}, {u'enabled': True, u'name': u'direct', u'description': u'AMQP direct exchange, as per the AMQP specification'}, {u'enabled': True, u'name': u'headers', u'description': u'AMQP headers exchange, as per the AMQP specification'}, {u'enabled': True, u'name': u'topic', u'description': u'AMQP topic exchange, as per the AMQP specification'}, {u'enabled': True, u'name': u'x-consistent-hash', u'description': u'Consistent Hashing Exchange'}] |
        | listeners | [{u'node': u'rabbit@primary', u'ip_address': u'::', u'protocol': u'amqp', u'port': 5672}, {u'node': u'rabbit@primary', u'ip_address': u'::', u'protocol': u'clustering', u'port': 25672}, {u'node': u'rabbit@primary', u'ip_address': u'::', u'protocol': u'mqtt', u'port': 1883}, {u'node': u'rabbit@primary', u'ip_address': u'::', u'protocol': u'stomp', u'port': 61613}] |
        | management_version | 3.3.0 |
        | message_stats | {u'publish_details': {u'rate': 0.0}, u'confirm': 85, u'deliver_get': 85, u'publish': 85, u'confirm_details': {u'rate': 0.0}, u'get_no_ack': 85, u'get_no_ack_details': {u'rate': 0.0}, u'deliver_get_details': {u'rate': 0.0}} |
        | node | rabbit@primary |
        | object_totals | {u'connections': 0, u'channels': 0, u'queues': 2, u'consumers': 0, u'exchanges': 10} |
        | queue_totals | {u'messages_details': {u'rate': 0.0}, u'messages': 0, u'messages_ready': 0, u'messages_ready_details': {u'rate': 0.0}, u'messages_unacknowledged': 0, u'messages_unacknowledged_details': {u'rate': 0.0}} |
        | rabbitmq_version | 3.3.0 |
        | statistics_db_node | rabbit@primary |
        | statistics_level | fine |

        ${version} = 3.3.0
        """
        url = self._http_connection.url + '/overview'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def connections(self):
        """ List of open connections.

        *Returns:*\n
            List of open connections in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.
        """
        url = self._http_connection.url + '/connections'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_name_of_all_connections(self):
        """ List with names of all open connections.

        *Returns:*\n
            List with names of all open connections.
        """
        return [item['name'] for item in self.connections()]

    def channels(self):
        """ List of open channels.

        *Returns:*\n
             List of open channels in JSON format.
        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.
        """
        url = self._http_connection.url + '/channels'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_exchange(self, exchange_name, vhost='%2F'):
        """ Get information about exchange.
        Parameters are quoted with requests.utils.quote.

        *Args:*\n
        _exchange_name_ - exchange name;\n
        _vhost_ - virtual host name;\n

        *Returns:*\n
            Dictionary with information about exchange.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | ${exchange}= | Get Exchange | exchange_name=testExchange | vhost=/ |
        | Log Dictionary | ${exchange}    |
        | ${value}= | Get From Dictionary | ${exchange} | name |
        | Log | ${value} |
        =>\n
        Dictionary size is 9 and it contains following items:
        | arguments | {u'arg1': u'value1', u'arg2': [u'list_value', True, u'18080'], u'alternate-exchange': u'amq.topic'} |
        | auto_delete | False |
        | durable | True |
        | incoming | [] |
        | internal | False |
        | name | testExchange |
        | outgoing | [] |
        | type | fanout |
        | vhost | / |

        ${value} = testExchange
        """
        path = '/exchanges/{vhost}/{name}'.format(
            vhost=self._quote_vhost(vhost), name=quote(exchange_name))
        response = requests.get(self._http_connection.url + path,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def exchanges(self):
        """ List of exchanges.

        *Returns:*\n
            List of exchanges in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | ${exchanges}=  |  Exchanges |
        | Log List  |  ${exchanges} |
        | ${item}=  |  Get From list  |  ${exchanges}  |  1 |
        | ${name}=  |  Get From Dictionary  |  ${q}  |  name  |
        =>\n
        List length is 8 and it contains following items:
        | 0 | {u'name': u'', u'durable': True, u'vhost': u'/', u'internal': False, u'message_stats': [], u'arguments': {}, u'type': u'direct', u'auto_delete': False} |
        | 1 | {u'name': u'amq.direct', u'durable': True, u'vhost': u'/', u'internal': False, u'message_stats': [], u'arguments': {}, u'type': u'direct', u'auto_delete': False} |
        ...\n
        ${name} = amq.direct
        """

        url = self._http_connection.url + '/exchanges'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_names_of_all_exchanges(self):
        """ List of names of all exchanges.

        *Returns:*\n
            List of names of all exchanges.

        *Example:*\n
        | ${names}=  |  Get Names Of All Exchanges |
        | Log List  |  ${names} |
        =>\n
        | List has one item:
        | amq.direct
        """
        return [item['name'] for item in self.exchanges()]

    def get_exchanges_on_vhost(self, vhost='%2F'):
        """ List of exchanges on virtual host.

        *Returns:*\n
            List of exchanges in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Args:*\n
        _vhost_ - virtual host name (quoted with requests.utils.quote);
        """

        url = self._http_connection.url + '/exchanges/' + self._quote_vhost(
            vhost)
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_names_of_exchanges_on_vhost(self, vhost='%2F'):
        """List of exchanges names on virtual host.

        *Args:*\n
        _vhost_: virtual host name (quoted with requests.utils.quote);

        *Returns:*\n
            List of exchanges names.

        *Example:*\n
        | ${names}=  |  Get Names Of Exchanges On Vhost |
        | Log List  |  ${names} |
        =>\n
        | List has one item:
        | federation: ex2 -> [email protected]
        """
        return [item['name'] for item in self.get_exchanges_on_vhost(vhost)]

    def get_queue(self, queue_name, vhost='%2F'):
        """
        Get information about queue.

        Parameters are quoted with requests.utils.quote.

        *Args:*\n
        _queue_name_ - queue name;\n
        _vhost_ - virtual host name (quoted with requests.utils.quote);\n

        *Returns:*\n
        Dictionary with information about queue.

        *Raises:*\n
        raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | ${queue}= | Get Queue | queue_name=testQueue | vhost=/ |
        | Log Dictionary | ${queue} |
        | ${value}= | Get From Dictionary | ${queue} | name |
        | Log | ${value} |
        =>\n
        Dictionary size is 23 and it contains following items:
        | arguments | {u'arg1': u'value1', u'arg2': [u'list_value', False, u'15240']} |
        | auto_delete | False |
        | backing_queue_status | {u'q1': 0, u'q3': 0, u'q2': 0, u'q4': 0, u'avg_ack_egress_rate': 0.0, u'ram_msg_count': 0, u'ram_ack_count': 0, u'len': 0, u'persistent_count': 0, u'target_ram_count': u'infinity', u'next_seq_id': 0, u'delta': [u'delta', u'undefined', 0, u'undefined'], u'pending_acks': 0, u'avg_ack_ingress_rate': 0.0, u'avg_egress_rate': 0.0, u'avg_ingress_rate': 0.0} |
        | consumer_details | [] |
        | consumer_utilisation | |
        | consumers | 0 |
        | deliveries | [] |
        | durable | True |
        | exclusive_consumer_tag | |
        | idle_since | 2014-09-16 7:37:35 |
        | incoming | [{u'stats': {u'publish_details': {u'rate': 0.0}, u'publish': 5}, u'exchange': {u'vhost': u'/', u'name': u'testExchange'}}] |
        | memory | 34528 |
        | messages | 0 |
        | messages_details | {u'rate': 0.0} |
        | messages_ready | 0 |
        | messages_ready_details | {u'rate': 0.0} |
        | messages_unacknowledged | 0 |
        | messages_unacknowledged_details | {u'rate': 0.0} |
        | name | testQueue |
        | node | rabbit@primary |
        | policy | |
        | state | running |
        | vhost | / |

        ${value} = testQueue
        """
        path = '/queues/{vhost}/{name}'.format(vhost=self._quote_vhost(vhost),
                                               name=quote(queue_name))
        response = requests.get(self._http_connection.url + path,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def queues(self):
        """ List of queues.

        *Returns:*\n
            List of queues in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.
        """
        url = self._http_connection.url + '/queues'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_queues_on_vhost(self, vhost='%2F'):
        """ List of queues on virtual host.

        *Args:*\n
        _vhost_ - virtual host name (quoted with requests.utils.quote);\n

        *Returns:*\n
            List of queues in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.
        """
        url = self._http_connection.url + '/queues/' + self._quote_vhost(vhost)
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_names_of_queues_on_vhost(self, vhost='%2F'):
        """
        List of queues names on virtual host.

        *Args:*\n
        _vhost_: virtual host name (quoted with requests.utils.quote);

        *Returns:*\n
            List of queues names.

        *Example:*\n
        | ${names}=  |  Get Names Of Queues On Vhost |
        | Log List  |  ${names} |
        =>\n
        | List has one item:
        | federation: ex2 -> [email protected]
        """
        return [item['name'] for item in self.get_queues_on_vhost(vhost)]

    def get_binding_exchange_with_queue_list(self,
                                             exchange_name,
                                             queue_name,
                                             vhost='%2F'):
        """
        Get information about bindings of exchange with queue.

        Parameters are quoted with requests.utils.quote.

        *Args:*\n
        _exchange_name_ - exchange name;\n
        _queue_name_ - queue name;\n
        _vhost_ - virtual host name (quoted with requests.utils.quote);\n

        *Returns:*\n
        List of bindings of exchange with queue in JSON format.

        *Raises:*\n
        raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | @{bind}= | Get Binding Exchange With Queue List | exchange_name=testExchange | queue_name=testQueue | vhost=/ |
        | Log Dictionary | ${bind[0]} |
        | Log | ${bind[0]["vhost"]} |
        =>\n
        Dictionary size is 7 and it contains following items:
        | arguments | {u'arg1': u'value1', u'arg2': [u'str1', False]} |
        | destination | testQueue |
        | destination_type | queue |
        | properties_key | ~2_oPmnDANCoVhkSJTkivZw |
        | routing_key: | |
        | source | testExchange |
        | vhost: | / |
        """
        path = '/bindings/{vhost}/e/{exchange}/q/{queue}'.format(
            vhost=self._quote_vhost(vhost),
            exchange=quote(exchange_name),
            queue=quote(queue_name))

        response = requests.get(self._http_connection.url + path,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def get_message(self,
                    queue_name,
                    count,
                    requeue,
                    encoding,
                    truncate=None,
                    vhost='%2F'):
        """
        Get message from the queue.

        *Args:*\n
        _queue_name_ - queue name;\n
        _count_ - number of messages to get;\n
        _requeue_ - re-placing received message in the queue with setting of redelivered attribute (true, false);\n
        _encoding_ - message encoding (auto, base64);\n
        _truncate_ - size of the message split (in bytes) in case it is greater than specified parameter (optional);\n
        _vhost_ - virtual host name (quoted with requests.utils.quote);\n

        *Returns:*\n
        List with information about returned messages in dictionary format.
        Body of the message in the dictionary is "payload" key.

        *Raises:*\n
        raise HTTPError if the HTTP request returned an unsuccessful status code.

        *Example:*\n
        | ${msg}= | Get Message | queue_name=testQueue | count=2 | requeue=false | encoding=auto | truncate=50000 | vhost=/ |
        | Log List | ${msg} |
        =>\n
        List length is 5 and it contains following items:
        | 0 | {u'payload': u'message body 0', u'exchange': u'testExchange', u'routing_key': u'testQueue', u'payload_bytes': 14, u'message_count': 4, u'payload_encoding': u'string', u'redelivered': False, u'properties': []} |
        | 1 | {u'payload': u'message body 1', u'exchange': u'testExchange', u'routing_key': u'testQueue', u'payload_bytes': 14, u'message_count': 3, u'payload_encoding': u'string', u'redelivered': False, u'properties': []} |
        | ... |
        """
        path = '/queues/{vhost}/{name}/get'.format(
            vhost=self._quote_vhost(vhost), name=quote(queue_name))
        body = {"count": count, "requeue": requeue, "encoding": encoding}
        if truncate is not None:
            body["truncate"] = truncate
        response = requests.post(
            self._http_connection.url + path,
            auth=self._http_connection.auth,
            headers=self._prepare_request_headers(body=body),
            data=json.dumps(body),
            timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def vhosts(self):
        """ List of virtual hosts.
        *Returns:*\n
            List of virtual hosts in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.
        """
        url = self._http_connection.url + '/vhosts'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def nodes(self):
        """ List of nodes.

        *Returns:*\n
            List of nodes in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.
        """
        url = self._http_connection.url + '/nodes'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()

    def _cluster_name(self):
        """ List of clusters.

        *Returns:*\n
            List of clusters in JSON format.

        *Raises:*\n
            raise HTTPError if the HTTP request returned an unsuccessful status code.
        """
        url = self._http_connection.url + '/cluster-name'
        response = requests.get(url,
                                auth=self._http_connection.auth,
                                headers=self._prepare_request_headers(),
                                timeout=self._http_connection.timeout)
        response.raise_for_status()
        return response.json()
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)
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(DataError, 'No open connection', getattr,
                               ConnectionCache().current, 'whatever')
        assert_raises_with_msg(DataError, '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(DataError, "Non-existing index or alias '3'",
                               self.cache.switch, 3)
        assert_raises_with_msg(DataError, "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(DataError,
                               "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
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)
class DatabaseLib(HybridCore):
    """
    DatabaseLib is created based on [https://www.sqlalchemy.org/|sqlalchemy].

    It support below features:
    - Database operations(select/insert/update/delete...)
    - Multi database connections, user could use "Switch Connection" to change current connection
    - ORM extension support
    - Extension this libraries easily
    """
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    ROBOT_LIBRARY_VERSION = VERSION

    def __init__(self, libraryComponents=[]):
        """
        DatabaseLib could be extened through parameter libraryComponents
        """
        self._connections = ConnectionCache()
        self._sessions = {}
        super(DatabaseLib, self).__init__(libraryComponents)

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

    @property
    def session(self):
        if self.current not in self._sessions:
            raise RuntimeError('Session is not created!')
        return self._sessions[self.current]

    @keyword
    def connect_to_db(self,
                      hostOrUrl,
                      port=None,
                      database=None,
                      user=None,
                      password=None,
                      dbPrefix=None,
                      alias=None,
                      **kwargs):
        """
        Connect to database  [http://docs.sqlalchemy.org/en/latest/core/engines.html|sqlalchemy]

        :param hostOrUrl: database hostname or database connection string
        :param port: database port
        :param database: database name
        :param user: database user name
        :param password: database password
        :param dbPrefix: format is dialect+driver, dialect is optional
        :param alias: connection alias, could be used to switch connection
        :param kwargs: please check [http://docs.sqlalchemy.org/en/latest/core/engines.html|create_engine] to get more details
        :return:  connection index

        Example:
        | Connect To Db | 127.0.0.1 | 3306 | test | user | password | mysql |
        | Connect To Db | mysql://user:[email protected]:3306/test?charset=utf8 |

        """
        if '://' in hostOrUrl:
            connectStr = hostOrUrl
        elif 'mysql' in dbPrefix.lower():
            connectStr = '%s://%s:%s@%s:%s/%s?charset=utf8' % (
                dbPrefix, user, password, hostOrUrl, port, database)
        else:
            connectStr = '%s://%s:%s@%s:%s/%s' % (dbPrefix, user, password,
                                                  hostOrUrl, port, database)
        logger.debug('Connection String: {0}'.format(connectStr))
        engine = create_engine(connectStr, **kwargs)
        connectionIndex = self._connections.register(engine, alias)
        return connectionIndex

    @keyword
    def switch_connection(self, indexOrAlias):
        """
        Switch database connection

        :param indexOrAlias: connection alias or index
        :return:  previous index

        Example:
        | Connect To Db | 127.0.0.1 | 3306 | test1 | user | password | mysql | connection_1 |
        | Connect To Db | 127.0.0.1 | 3306 | test2 | user | password | oracle | connection_2 |
        | Switch Connection | connection_1 |
        """
        oldIndex = self._connections.current_index
        self._connections.switch(indexOrAlias)
        return oldIndex

    @keyword
    def create_session(self,
                       autoflush=True,
                       autocommit=False,
                       expireOnCommit=True,
                       info=None,
                       **kwargs):
        """
        Create session based on current connection(engine)

        if session is already for current connection, keyword will return created session directly.
        This keyword could be used to extend library with ORM

        :param autoflush: default value is True
        :param autocommit: default value is False
        :param expireOnCommit: default value is True
        :param info: default value is None
        :param kwargs: Please check Session in sqlalchemy
        :return: session
        """
        if self.current in self._sessions:
            return self._sessions[self.current]
        elif self.current is not None:
            self.current.echo = 'debug'
            session = scoped_session(
                sessionmaker(bind=self.current,
                             autoflush=autoflush,
                             autocommit=autocommit,
                             expire_on_commit=expireOnCommit,
                             info=info,
                             **kwargs))
            self._sessions[self.current] = session
            return session
        raise RuntimeError(
            'Current connection may closed, or not create connection yet!')

    @keyword
    def close_connection(self):
        """
        Close current database connection

        :return: None
        """
        if self.current in self._sessions:
            self._sessions.pop(self.current)
        self.current.dispose()
        self._connections.current = self._connections._no_current

    @keyword
    def close_all_connections(self):
        """
        Close all database connections

        :return: None
        """
        self._sessions.clear()
        self._connections.close_all('dispose')

    @keyword
    def execute(self, sql):
        """
        Execute sql

        :param sql:  sql
        :return: sqlalchemy ResultProxy
        """
        return self.current.execute(sql)

    @keyword
    def query(self, sql, *args, **kwargs):
        """
        Execute query

        :param sql: sql string
        :param args: if params in sql want to be replaced by index, use args
        :param kwargs: if params in sql want to be replaced by key, use kwargs
        :return: List of ResultProxy

        Examples:

        | ${results}= | Query |   SELECT {0}, {1} FROM MY_TABLE         |   c1   |  c2    |
        | ${results}= | Query |   SELECT {col1}, {col2} FROM MY_TABLE   | col1=c1 | col2=c2 |
        | ${results}= | Query |   SELECT c1, c2 FROM MY_TABLE           |        |        |
        """
        if not args:
            args = []
        if not kwargs:
            kwargs = {}
        sql = sql.format(*args, **kwargs)
        logger.debug('Execute: %s' % sql)
        resultProxy = self.execute(sql)
        results = [result for result in resultProxy]
        logger.debug('Results: %s' % results)
        return results

    @keyword
    def execute_sql_script(self, sqlFile):
        """
        Execute sql script file

        :param sqlFile: Path to sql script file, not format should be utf-8
        :return: None
        """
        with open(sqlFile, 'r', encoding='utf-8') as f:
            content = f.read()
        content = content.replace('\ufeff', '')
        sqlStrList = sqlparse.split(
            sqlparse.format(content, strip_comments=True))
        sqlStrList = [
            sqlparse.format(sqlStr.strip(';'),
                            keyword_case='upper',
                            reindent=True) for sqlStr in sqlStrList
            if sqlStr.strip()
        ]
        for sqlStr in sqlStrList:
            self.execute(sqlStr)

    @keyword
    def call_stored_procedure(self, name, *params):
        """
        Call stored procedure

        :param name: name of stored procedure
        :param params: parameters of stored procedure
        :return: results
        """
        connection = self.current.raw_connection()
        try:
            cursor = connection.cursor()
            results = cursor.callproc(name, params)
            cursor.close()
            connection.commit()
        finally:
            connection.close()
        return results