def _request(self, token, url):
        """
        Perform a request and place the token header inside the request header

        :param token: A valid token
        :param url:  The URL to perform a request
        :return: Request Object
        """
        headers = {'Accept': 'application/json', 'X-SDS-AUTH-TOKEN': token}
        try:
            return self.session.get(url,
                                    verify=self.verify_ssl,
                                    headers=headers,
                                    timeout=self.request_timeout)
        except requests.ConnectionError as conn_err:
            msg = 'Connection error: {0}'.format(conn_err.args)
            log.error(msg)
            raise ECSClientException(msg)
        except requests.HTTPError as http_err:
            msg = 'HTTP error: {0}'.format(http_err.args)
            log.error(msg)
            raise ECSClientException(msg)
        except requests.RequestException as req_err:
            msg = 'Request error: {0}'.format(req_err.args)
            log.error(msg)
            raise ECSClientException(msg)
Ejemplo n.º 2
0
    def _request(self, url, json_payload='{}', http_verb='GET', params=None):
        json_payload = json.dumps(json_payload)

        try:
            if http_verb == "PUT":
                req = self._session.put(
                    self._construct_url(url),
                    verify=self.verify_ssl,
                    headers=self._fetch_headers(),
                    timeout=self.request_timeout,
                    data=json_payload)
            elif http_verb == 'POST':
                req = self._session.post(
                    self._construct_url(url),
                    verify=self.verify_ssl,
                    headers=self._fetch_headers(),
                    timeout=self.request_timeout,
                    data=json_payload)
            elif http_verb == 'DELETE':
                # Need to follow up - if 'accept' is in the headers
                # delete calls are not working because ECS 2.0 is returning
                # XML even if JSON is specified
                headers = self._fetch_headers()
                del headers['Accept']

                req = self._session.delete(
                    self._construct_url(url),
                    verify=self.verify_ssl,
                    headers=headers,
                    timeout=self.request_timeout,
                    params=params)
            else:  # Default to GET
                req = self._session.get(
                    self._construct_url(url),
                    verify=self.verify_ssl,
                    headers=self._fetch_headers(),
                    timeout=self.request_timeout,
                    params=params)

            # Because some delete actions in the API return HTTP/1.1 204 No Content
            if not (200 <= req.status_code < 300):
                log.error("Status code NOT OK")
                raise ECSClientException.from_response(req)
            try:
                return req.json()
            except ValueError:
                return req.text

        except requests.ConnectionError as conn_err:
            msg = 'Connection error: {0}'.format(conn_err.args)
            log.error(msg)
            raise ECSClientException(message=msg)
        except requests.HTTPError as http_err:
            msg = 'HTTP error: {0}'.format(http_err.args)
            log.error(msg)
            raise ECSClientException(message=msg)
        except requests.RequestException as req_err:
            msg = 'Request error: {0}'.format(req_err.args)
            log.error(msg)
            raise ECSClientException(message=msg)
Ejemplo n.º 3
0
    def __init__(self, username=None, password=None, token=None,
                 ecs_endpoint=None, token_endpoint=None, verify_ssl=False,
                 token_path='/tmp/ecsclient.tkn',
                 request_timeout=15.0, cache_token=True, override_header=None):
        """
        Creates the ECSClient class that the client will directly work with

        :param username:
        The username to fetch a token
        :param password: The password to fetch a token
        :param token: Supply a valid token to use instead of username/password
        :param ecs_endpoint: The URL where ECS is located
        :param token_endpoint: The URL where the ECS login is located
        :param verify_ssl: Verify SSL certificates
        :param token_path: Path to the cached token file
        :param request_timeout: How long to wait for ECS to respond
        :param cache_token: Whether to cache the token, by default this is true
        you should only switch this to false when you want to directly fetch
        a token for a user
        :param override_header: X-EMC-Override header value into API calls
        """
        if not ecs_endpoint:
            raise ECSClientException("Missing 'ecs_endpoint'")

        self.token_endpoint = token_endpoint

        if token_endpoint:
            if not (username and password):
                raise ECSClientException("'token_endpoint' provided but missing ('username','password')")
            self.token_endpoint = self.token_endpoint.rstrip('/')
        else:
            if not (token or os.path.isfile(token_path)):
                raise ECSClientException("'token_endpoint' not provided and missing 'token'|'token_path'")

        self.override_header = override_header
        self.username = username
        self.password = password
        self.token = token
        self.ecs_endpoint = ecs_endpoint.rstrip('/')
        self.verify_ssl = verify_ssl
        self.token_path = token_path
        self.request_timeout = request_timeout
        self.cache_token = cache_token
        self._session = requests.Session()
        self._token_request = TokenRequest(
            username=self.username,
            password=self.password,
            ecs_endpoint=self.ecs_endpoint,
            token_endpoint=self.token_endpoint,
            verify_ssl=self.verify_ssl,
            token_path=self.token_path,
            request_timeout=self.request_timeout,
            cache_token=self.cache_token)

        # Authentication
        self.authentication = Authentication(self)
    def get_token(self):
        """
        Attempt to get an existing token, if successful then ensure it
        hasn't expired yet. If its expired, fetch a new token

        :return: A token
        """
        token = self._get_existing_token()

        if not token:
            log.debug("No Token found getting new one")
            return self.get_new_token()

        # FIXME: Avoid validation at every call
        log.debug("Validating token")
        req = self._request(token, self.token_verification_endpoint)

        if req.status_code == 200:
            log.debug("Token validated successfully")
            return token
        elif req.status_code in (401, 403, 415):
            msg = "Invalid token. Trying to get a new one (Code: {})".format(
                req.status_code)
            log.warning(msg)
            return self.get_new_token()
        else:  # i.e. 500 or unknown raise an exception
            msg = "Token validation error (Code: {})".format(req.status_code)
            log.error(msg)
            raise ECSClientException.from_response(req, message=msg)
Ejemplo n.º 5
0
    def do_ping():
        if c:
            msg = " (CTRL-C to break)"
        else:
            msg = ""
        o("Pinging endpoint {}...{}".format(conf.api_endpoint, msg))

        pinging = True
        while pinging is True:

            try:
                resp_dict = conf.api_client.user_info.whoami()
                if resp_dict is not None:
                    if resp_dict['common_name'] is not None:
                        o('PONG: api_endpoint={} username={} {}'.format(
                            conf.api_endpoint, resp_dict['common_name'],
                            conf.diag_dt_status_text()))
                        if x:
                            pinging = False
                    else:
                        raise ECSClientException(
                            "Unexpected response from API")
            except requests.ConnectionError or httplib.HTTPException:
                o("FAIL: API service unavailable {}".format(
                    conf.diag_dt_status_text()))
                try:
                    del conf.api_client
                    if not c:
                        sys.exit(1)
                except AssertionError:
                    if not c:
                        sys.exit(1)
            except ECSClientException as e:
                if 'Connection refused' in e.message:
                    o('WAIT: API service is not alive. This is likely temporary.'
                      )
                elif 'connection failed' in e.message:
                    o('WAIT: API service is alive but ECS is not. This is likely temporary.'
                      )
                elif 'Invalid username or password' in e.message:
                    o('WAIT: Invalid username or password. If ECS authsvc is bootstrapping, this is likely temporary.'
                      )
                elif 'Non-200' in e.message:
                    o('WAIT: ECS API internal error. If ECS services are still bootstrapping, this is likely temporary.'
                      )
                elif 'Read timed out' in e.message:
                    o('WAIT: ECS API timed out.  If ECS services are still bootstrapping, this is likely temporary.'
                      )
                else:
                    o('FAIL: Unexpected response from API client: {0}'.format(
                        e))
                    if not c:
                        raise
            if not c:
                pinging = False
            if c and pinging is True:
                time.sleep(w)
    def get_new_token(self):
        """
        Request a new authentication token from ECS and persist it
        to a file for future usage if cache_token is true

        :return: Returns a valid token, or None if failed
        """
        log.info("Getting new token")
        self.session.auth = (self.username, self.password)

        req = self.session.get(self.token_endpoint,
                               verify=self.verify_ssl,
                               headers={'Accept': 'application/json'},
                               timeout=self.request_timeout)

        if req.status_code == 401:
            msg = 'Invalid username or password'
            log.fatal(msg)
            raise ECSClientException.from_response(req, message=msg)
        if req.status_code != 200:
            msg = 'Non-200 status returned ({0})'.format(req.status_code)
            log.fatal(msg)
            raise ECSClientException.from_response(req, message=msg)

        self.token = req.headers['x-sds-auth-token']

        if self.cache_token:
            log.debug("Caching token to '{0}'".format(self.token_path))

            token_dir = os.path.dirname(os.path.abspath(self.token_path))
            if not os.path.isdir(token_dir):
                raise ECSClientException('Token directory not found')

            with open(self.token_path, 'w') as token_file:
                token_file.write(self.token)

        return self.token
    def test_format(self):
        test_kwargs = {
            'ecs_description': 'Error description',
            'ecs_details': 'Error details',
            'http_scheme': 'https',
            'http_host': 'localhost',
            'http_port': 4443,
            'http_path': '/path',
            'http_query': 'param1=value1',
            'http_status': 500,
            'http_reason': 'Reason',
            'http_response_content': 'Response content'
        }

        for key, value in test_kwargs.items():
            kwargs = {key: value}
            exc = ECSClientException('test', **kwargs)
            self.assertIn(str(value), str(exc))
    def test_attrs(self):
        test_kwargs = {
            'ecs_code': '21000',
            'ecs_retryable': True,
            'ecs_description': 'Error description',
            'ecs_details': 'Error details',
            'http_scheme': 'https',
            'http_host': 'localhost',
            'http_port': 4443,
            'http_path': '/path',
            'http_query': 'param1=value1',
            'http_status': 500,
            'http_reason': 'Reason',
            'http_response_content': 'Response content',
            'http_response_headers': [{'x-header-1': 'value1'}]
        }
        exc = ECSClientException('test', **test_kwargs)

        for key, value in test_kwargs.items():
            self.assertIs(True, hasattr(exc, key))
            self.assertEqual(getattr(exc, key), value)
    def test_from_response(self):
        response_content = '{"code": "1000", "retryable": false, ' \
                           '"description": "Error description", ' \
                           '"details": "Error details"}'
        self.requests_mock.register_uri('POST', self.TEST_URL,
                                        status_code=500,
                                        text=response_content)
        resp = requests.post(self.TEST_URL, data='body')
        exc = ECSClientException.from_response(resp)

        self.assertEqual(exc.ecs_code, '1000')
        self.assertEqual(exc.ecs_retryable, False)
        self.assertEqual(exc.ecs_description, 'Error description')
        self.assertEqual(exc.ecs_details, 'Error details')
        self.assertEqual(exc.http_scheme, 'https')
        self.assertEqual(exc.http_host, '127.0.0.1')
        self.assertEqual(exc.http_port, 4443)
        self.assertEqual(exc.http_path, '/foor/bar')
        self.assertEqual(exc.http_query, 'param1=value1&param2=value2')
        self.assertEqual(exc.http_status, 500)
        self.assertEqual(exc.http_reason, None)
        self.assertEqual(exc.http_response_content, response_content)