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