def _ssh_cluster_nodes(self, cluster_id, wait=False):
        """
        Return `(cluster, nodes)`, where `nodes` is a list of all non-Ambari
        nodes in the cluster. If the cluster is not ACTIVE/ERROR/IMPAIRED and
        wait is `True`, the function will block until it becomes active;
        otherwise, an exception is thrown.
        """
        cluster = self.get(cluster_id)
        status = cluster.status.upper()
        if status not in FINAL_STATES:
            LOG.debug('Cluster status: %s', status)

            if status not in IN_PROGRESS_STATES:
                raise error.InvalidError(
                    'Cluster is in state {0}'.format(status))
            elif not wait:
                raise error.InvalidError('Cluster is not yet active')

            self.wait(cluster_id)

        if cluster.status.upper() == 'ERROR':
            raise error.InvalidError('Cluster is in state ERROR')

        nodes = [
            node for node in self.nodes(cluster_id)
            if node.name.lower() != 'ambari'
        ]
        return cluster, nodes
    def _validate_endpoint(self, endpoint, tenant_id):
        """Validate that the endpoint ends with v2/<tenant_id>"""

        endpoint = endpoint.rstrip('/')

        if tenant_id is None:
            if re.search(r'v2/[^/]+$', endpoint):
                return endpoint

            raise error.InvalidError('Endpoint must end with v2/<tenant_id>')

        if endpoint.endswith('v2/{0}'.format(tenant_id)):
            return endpoint
        elif endpoint.endswith('v2'):
            return '{0}/{1}'.format(endpoint, tenant_id)

        raise error.InvalidError('Endpoint must end with v2 or v2/<tenant_id>')
    def _get_component_node(self, nodes, component, wait=False):
        components_by_node = ((node, (comp['name'].lower()
                                      for comp in node.components))
                              for node in nodes)

        try:
            return six.next(node for node, components in components_by_node
                            if component.lower() in components)
        except StopIteration:
            raise error.InvalidError(
                'Component {0} not found in cluster'.format(component))
    def _get_named_node(self, nodes, node_name=None, wait=False):
        if node_name is None:
            return nodes[0]

        try:
            return six.next(node for node in nodes
                            if node.name.lower() == node_name.lower())
        except StopIteration:
            raise error.InvalidError(
                'Invalid node: {0}; available nodes are {1}'.format(
                    node_name, ', '.join(node.name for node in nodes)))
Beispiel #5
0
 def _marshal_request(self, data, request_class, wrapper=None):
     """
     Check that the json request body conforms to the request class, then
     return the unmodified data.  If wrapper is not None, wrap data in a
     dictionary with the wrapper as the key.
     """
     try:
         marshaled = _prune_marshaled_data(
             request_class(data).to_dict(), data)
         return marshaled if wrapper is None else {wrapper: marshaled}
     except (figgis.PropertyError, figgis.ValidationError) as exc:
         msg = 'Invalid request data: {0}'.format(exc)
         LOG.critical(msg, exc_info=exc)
         raise error.InvalidError(msg)
    def _get_endpoint(self, region, tenant_id):
        filters = dict(
            service_type=constants.CBD_SERVICE_TYPE,
            region_name=region.upper(),
            service_name=constants.CBD_SERVICE_NAME)

        if tenant_id:
            filters.update(filter_attr='tenantId', filter_value=tenant_id)

        try:
            return self._filter_current_endpoint(self._auth.service_catalog,
                                                 **filters)
        except ks_error.EndpointNotFound as exc:
            LOG.critical('Error getting endpoint: {0}'.format(exc),
                         exc_info=exc)
            raise error.InvalidError(str(exc))
    def ssh_tunnel(self,
                   cluster_id,
                   local_port,
                   remote_port,
                   node_name=None,
                   component=None,
                   ssh_command=None,
                   wait=False):
        """
        Create a SSH tunnel from the local host to a particular port on a
        cluster node.  Returns the SSH process (via
        :py:class:`~subprocess.Popen`), which can be stopped via the
        :func:`kill` method.

        :param cluster_id: Cluster ID
        :param local_port: Port on which to bind on localhost
        :param remote_port: Port on which to bind on the cluster node
        :param node_name: Name of node on which to make the SSH connection.
                          Mutually exclusive with `component`
        :param component: Name of a component installed on a node in the
                          cluster, e.g. `HiveServer2`. SSH tunnel will be set
                          up on the first node containing the component.
                          Mutually exclusive with `node_name`
        :param wait: If `True`, wait for the cluster to become active before
                     creating the proxy
        :returns: :py:class:`~subprocess.Popen` object representing the SSH
                  connection.
        """
        if node_name and component:
            raise error.InvalidError(
                'node_name and component are mutually exclusive')
        elif not (node_name or component):
            raise error.InvalidError(
                'One of node_name or component is required')

        cluster, nodes = self._ssh_cluster_nodes(cluster_id, wait=wait)

        if component:
            ssh_node = self._get_component_node(nodes, component)
        else:
            ssh_node = self._get_named_node(nodes, node_name=node_name)

        printer = self._cli_printer(LOG)
        printer('Starting SSH tunnel from localhost:{0} to {1}:{2} '
                '({3})'.format(local_port, ssh_node.name, remote_port,
                               ssh_node.public_ip))

        process = create_ssh_tunnel(cluster.username,
                                    ssh_node,
                                    local_port,
                                    remote_port,
                                    ssh_command=ssh_command)

        printer(
            'Successfully created SSH tunnel to localhost:{0}'.format(
                local_port), logging.INFO)

        if not self._command_line:
            return process

        try:
            printer('Use Ctrl-C to stop tunnel', logging.NOTSET)
            process.communicate()
        except KeyboardInterrupt:
            printer('SSH tunnel closed')
    def __init__(self,
                 username,
                 region=None,
                 password=None,
                 token=None,
                 api_key=None,
                 auth_url=None,
                 tenant_id=None,
                 endpoint=None,
                 verify_ssl=None,
                 timeout=None,
                 max_retries=None,
                 retry_backoff=None,
                 _cli_args=None):
        if not any((api_key, password, token)):
            raise error.InvalidError("One of api_key, token, or password is "
                                     "required")

        if not endpoint and not region:
            raise error.InvalidError('One of endpoint or region is required')

        # Ensure tenant_id is unicode
        if tenant_id is not None:
            tenant_id = six.text_type(tenant_id)

        if auth_url is None:
            auth_url = constants.DEFAULT_AUTH_URL

        self._request_timeout = timeout
        self._auth_url = auth_url
        self._region = region
        self._api_key = api_key
        self._password = password
        self._username = username
        self._tenant_id = tenant_id
        self._verify_ssl = verify_ssl
        self._token = token

        if token and not endpoint:
            raise error.InvalidError(
                'Token must be accompanied by a hard-coded endpoint')
        elif token:
            self._auth = None
        else:
            if username is None:
                raise error.InvalidError("Missing username")

            self._auth = self._authenticate(auth_url,
                                            api_key,
                                            region,
                                            username,
                                            password,
                                            tenant_id)
        if endpoint is None:
            endpoint = self._get_endpoint(region, tenant_id)

        self._endpoint = self._validate_endpoint(endpoint, tenant_id)
        self._session = self._make_session(max_retries, retry_backoff,
                                           auth_url, self._endpoint)

        # Initialize API resources
        self.clusters = clusters.Resource(self, cli_args=_cli_args)
        self.limits = limits.Resource(self, cli_args=_cli_args)
        self.flavors = flavors.Resource(self, cli_args=_cli_args)
        self.stacks = stacks.Resource(self, cli_args=_cli_args)
        self.distros = distros.Resource(self, cli_args=_cli_args)
        self.scripts = scripts.Resource(self, cli_args=_cli_args)
        self.nodes = nodes.Resource(self, cli_args=_cli_args)
        self.credentials = credentials.Resource(self, cli_args=_cli_args)

        # Workloads isn't terrible useful right now, but I don't want to delete
        # it entirely. Therefore, I'll just make it private for now.
        self._workloads = workloads.Resource(self, cli_args=_cli_args)

        self._auth_lock = Lock()