def test_make_request_with_macaroons(self): def handler(url, request): self.assertEqual(request.headers['Macaroons'], 'my-macaroons') return {'status_code': 200} # Test with a JSON decoded object. with HTTMock(handler): make_request(URL, macaroons='my-macaroons')
def test_make_request_server_error(self): with HTTMock(self.failing_response_server): with patch_log_error() as mock_log_error: with self.assertRaises(ServerError) as ctx: make_request(URL, query={'uuid': 'foo'}) expected_error = ('Error during request: http://example.com/?uuid=foo ' 'message: server-failed') self.assertEqual((500, expected_error), ctx.exception.args) mock_log_error.assert_called_once_with(expected_error)
def test_make_request_unexpected_error(self): with mock.patch('theblues.utils.requests.get') as mock_get: mock_get.side_effect = ValueError('bad wolf') with patch_log_error() as mock_log_error: with self.assertRaises(ServerError) as ctx: make_request(URL, query={'uuid': 'foo'}) expected_error = ('Error during request: http://example.com/?uuid=foo ' 'message: bad wolf') self.assertEqual(expected_error, ctx.exception.args[0]) mock_log_error.assert_called_once_with(expected_error)
def test_make_request_server_error(self): with HTTMock(self.failing_response_server): with patch_log_error() as mock_log_error: with self.assertRaises(ServerError) as ctx: make_request(URL, query={'uuid': 'foo'}) expected_error = ( 'Error during request: http://example.com/?uuid=foo ' 'message: server-failed') self.assertEqual((500, expected_error), ctx.exception.args) mock_log_error.assert_called_once_with(expected_error)
def set_extra_info(self, username, extra_info): """Set extra info for the given user. Raise a ServerError if an error occurs in the request process. @param username The username for the user to update. @param info The extra info as a JSON encoded string, or as a Python dictionary like object. """ url = self._get_extra_info_url(username) make_request(url, method='PUT', body=extra_info, timeout=self.timeout)
def test_make_request_unexpected_error(self): with mock.patch('theblues.utils.requests.get') as mock_get: mock_get.side_effect = ValueError('bad wolf') with patch_log_error() as mock_log_error: with self.assertRaises(ServerError) as ctx: make_request(URL, query={'uuid': 'foo'}) expected_error = ( 'Error during request: http://example.com/?uuid=foo ' 'message: bad wolf') self.assertEqual(expected_error, ctx.exception.args[0]) mock_log_error.assert_called_once_with(expected_error)
def set_extra_info(self, username, extra_info): """Set extra info for the given user. Raise a ServerError if an error occurs in the request process. @param username The username for the user to update. @param info The extra info as a JSON encoded string, or as a Python dictionary like object. """ url = self._get_extra_info_url(username) make_request( url, method='PUT', body=extra_info, auth=self.auth, timeout=self.timeout)
def login(self, username, json_document): """Send user identity information to the identity manager. Raise a ServerError if an error occurs in the request process. @param username The logged in user. @param json_document The JSON payload for login. """ url = '{}u/{}'.format(self.url, username) make_request(url, method='PUT', body=json_document, timeout=self.timeout)
def login(self, username, json_document): """Send user identity information to the identity manager. Raise a ServerError if an error occurs in the request process. @param username The logged in user. @param json_document The JSON payload for login. """ url = '{}u/{}'.format(self.url, username) make_request( url, method='PUT', body=json_document, auth=self.auth, timeout=self.timeout)
def get_users_models(self, macaroons): """ Get the logged in user's models from the JEM service. @param macaroons The discharged JEM macaroons. @return The json decoded list of environments. """ return make_request("{}model".format(self.url), macaroons=macaroons)
def discharge(self, username, macaroon): """Discharge the macarooon for the identity. Raise a ServerError if an error occurs in the request process. @param username The logged in user. @param macaroon The macaroon returned from the charm store. @return The resulting base64 encoded macaroon. @raises ServerError when making request to the discharge endpoint InvalidMacaroon when the macaroon passedin or discharged is invalid """ caveats = macaroon.third_party_caveats() if len(caveats) != 1: raise InvalidMacaroon( 'Invalid number of third party caveats (1 != {})' ''.format(len(caveats))) url = '{}discharger/discharge?discharge-for-user={}&id={}'.format( self.url, quote(username), caveats[0][1]) logging.debug('Sending identity info to {}'.format(url)) logging.debug('data is {}'.format(caveats[0][1])) response = make_request( url, method='POST', auth=self.auth, timeout=self.timeout) try: macaroon = response['Macaroon'] json_macaroon = json.dumps(macaroon) except (KeyError, UnicodeDecodeError) as err: raise InvalidMacaroon( 'Invalid macaroon from discharger: {}'.format(err.message)) return base64.urlsafe_b64encode(json_macaroon.encode('utf-8'))
def debug(self): """Retrieve the debug information from the identity manager.""" url = '{}debug/status'.format(self.url) try: return make_request(url, timeout=self.timeout) except ServerError as err: return {"error": str(err)}
def get_plans(self, reference): """Get the plans for a given charm. @param the Reference to a charm. @return a tuple of plans or an empty tuple if no plans. @raise ServerError """ json = make_request( '{}charm?charm-url={}'.format(self.url, 'cs:' + reference.path())) try: return tuple(map(lambda plan: Plan( url=plan['url'], plan=plan['plan'], created_on=datetime.datetime.strptime( plan['created-on'], "%Y-%m-%dT%H:%M:%SZ" ), description=plan.get('description'), price=plan.get('price')), json)) except (KeyError, TypeError, ValueError) as err: log.info( 'cannot process terms: invalid JSON response: {!r}'.format( json)) raise ServerError( 'unable to get list of plans for {}: {}'.format( reference.path(), err))
def get_terms(self, name, revision=None): """ Retrieve a specific term and condition. @param name of the terms. @param revision of the terms, if none provided it will return the latest. @return The list of terms. @raise ServerError """ url = '{}terms/{}'.format(self.url, name) if revision: url = '{}?revision={}'.format(url, revision) json = make_request(url) try: # This is always a list of one element. data = json[0] return Term(name=data['name'], title=data.get('title'), revision=data['revision'], created_on=datetime.datetime.strptime( data['created-on'], "%Y-%m-%dT%H:%M:%SZ" ), content=data['content']) except (KeyError, TypeError, ValueError) as err: log.info( 'cannot process terms: invalid JSON response: {!r}'.format( json)) raise ServerError( 'unable to get terms for {}: {}'.format(name, err))
def discharge(self, username, macaroon): """Discharge the macarooon for the identity. Raise a ServerError if an error occurs in the request process. @param username The logged in user. @param macaroon The macaroon returned from the charm store. @return The resulting base64 encoded macaroon. @raises ServerError when making request to the discharge endpoint InvalidMacaroon when the macaroon passedin or discharged is invalid """ caveats = macaroon.third_party_caveats() if len(caveats) != 1: raise InvalidMacaroon( 'Invalid number of third party caveats (1 != {})' ''.format(len(caveats))) url = '{}discharger/discharge?discharge-for-user={}&id={}'.format( self.url, quote(username), caveats[0][1]) logging.debug('Sending identity info to {}'.format(url)) logging.debug('data is {}'.format(caveats[0][1])) response = make_request(url, method='POST', auth=self.auth, timeout=self.timeout) try: macaroon = response['Macaroon'] json_macaroon = json.dumps(macaroon) except (KeyError, UnicodeDecodeError) as err: raise InvalidMacaroon( 'Invalid macaroon from discharger: {}'.format(err.message)) return base64.urlsafe_b64encode(json_macaroon.encode('utf-8'))
def debug(self): """Retrieve the debug information from the identity manager.""" url = '{}debug/status'.format(self.url) try: return make_request(url, timeout=self.timeout) except ServerError as err: return {"error": str(err)}
def get_terms(self, name, revision=None): """ Retrieve a specific term and condition. @param name of the terms. @param revision of the terms, if none provided it will return the latest. @return The list of terms. @raise ServerError """ url = '{}terms/{}'.format(self.url, name) if revision: url = '{}?revision={}'.format(url, revision) json = make_request(url, timeout=self.timeout, client=self._client) try: # This is always a list of one element. data = json[0] return Term(name=data['name'], title=data.get('title'), revision=data['revision'], created_on=datetime.datetime.strptime( data['created-on'], "%Y-%m-%dT%H:%M:%SZ"), content=data['content']) except (KeyError, TypeError, ValueError, IndexError) as err: log.info( 'cannot process terms: invalid JSON response: {!r}'.format( json)) raise ServerError('unable to get terms for {}: {}'.format( name, err))
def get_wallet(self, wallet_name): """Get a single wallet. @param the name of the wallet. @return the wallet's total. @raise ServerError """ response = make_request( '{}wallet/{}'.format(self.url, wallet_name), timeout=self.timeout, client=self._client) try: total = response['total'] return { 'credit': response['credit'], 'limit': response['limit'], 'total': WalletTotal( limit=total['limit'], budgeted=total['budgeted'], available=total['available'], unallocated=total['unallocated'], usage=total['usage'], consumed=total['consumed']) } except Exception as exc: log.error( 'cannot get wallet from server: {!r}'.format(exc)) raise ServerError( 'unable to get list of wallets: {!r}'.format(exc))
def get_plans(self, reference): """Get the plans for a given charm. @param the Reference to a charm. @return a tuple of plans or an empty tuple if no plans. @raise ServerError """ response = make_request( '{}charm?charm-url={}'.format(self.url, 'cs:' + reference.path()), timeout=self.timeout, client=self._client) try: return tuple(map(lambda plan: Plan( url=plan['url'], plan=plan['plan'], created_on=datetime.datetime.strptime( plan['created-on'], "%Y-%m-%dT%H:%M:%SZ" ), description=plan.get('description'), price=plan.get('price')), response)) except Exception as err: log.error( 'cannot process plans: invalid JSON response: {!r}'.format( response)) raise ServerError( 'unable to get list of plans for {}: {}'.format( reference.path(), err))
def list_models(self, macaroons): """ Get the logged in user's models from the JIMM controller. @param macaroons The discharged JIMM macaroons. @return The json decoded list of environments. """ return make_request("{}model".format(self.url), macaroons=macaroons, timeout=self.timeout)
def get_extra_info(self, username): """Get extra info for the given user. Raise a ServerError if an error occurs in the request process. @param username The username for the user who's info is being accessed. """ url = self._get_extra_info_url(username) return make_request(url, auth=self.auth, timeout=self.timeout)
def get_extra_info(self, username): """Get extra info for the given user. Raise a ServerError if an error occurs in the request process. @param username The username for the user who's info is being accessed. """ url = self._get_extra_info_url(username) return make_request(url, auth=self.auth, timeout=self.timeout)
def get_user(self, username): """Fetch user data. Raise a ServerError if an error occurs in the request process. @param username the user's name. """ url = '{}u/{}'.format(self.url, username) return make_request(url, auth=self.auth, timeout=self.timeout)
def get_user(self, username): """Fetch user data. Raise a ServerError if an error occurs in the request process. @param username the user's name. """ url = '{}u/{}'.format(self.url, username) return make_request(url, auth=self.auth, timeout=self.timeout)
def get_model(self, macaroons, user, name): """ Get a specified model. @param macaroons The discharged JEM macaroons. @param user The username of the model's owner. @param name The name of the model. @return The json decoded model. """ return make_request('{}model/{}/{}'.format(self.url, user, name), macaroons=macaroons)
def check_write_request(self, method): def handler(url, request): self.assertEqual('http://example.com/', url.geturl()) self.assertEqual(method, request.method) self.assertEqual('{"uuid": "foo"}', request.body) self.assertEqual( 'application/json', request.headers['Content-Type']) return { 'status_code': 200, 'content': b'{"foo":"bar","baz":"bax"}', } # Test with a JSON decoded object. with HTTMock(handler): response = make_request(URL, method=method, body={'uuid': 'foo'}) self.assertEqual({u'foo': u'bar', u'baz': u'bax'}, response) # Test with a JSON encoded string. with HTTMock(handler): response = make_request(URL, method=method, body='{"uuid": "foo"}') self.assertEqual({u'foo': u'bar', u'baz': u'bax'}, response)
def check_write_request(self, method): def handler(url, request): self.assertEqual('http://example.com/', url.geturl()) self.assertEqual(method, request.method) self.assertEqual('{"uuid": "foo"}', request.body) self.assertEqual( 'application/json', request.headers['Content-Type']) return { 'status_code': 200, 'content': b'{"foo":"bar","baz":"bax"}', } # Test with a JSON decoded object. with HTTMock(handler): response = make_request(URL, method=method, body={'uuid': 'foo'}) self.assertEqual({u'foo': u'bar', u'baz': u'bax'}, response) # Test with a JSON encoded string. with HTTMock(handler): response = make_request(URL, method=method, body='{"uuid": "foo"}') self.assertEqual({u'foo': u'bar', u'baz': u'bax'}, response)
def get_user(self, username, macaroons): """Fetch user data. Raise a ServerError if an error occurs in the request process. @param username the user's name. @param macaroons the encoded macaroons string. """ url = '{}u/{}'.format(self.url, username) return make_request(url, timeout=self.timeout, macaroons=macaroons)
def delete_wallet(self, wallet_name): """Delete a wallet. @param the name of the wallet. @return a success string from the plans server. @raise ServerError via make_request. """ return make_request( '{}wallet/{}'.format(self.url, wallet_name), method='DELETE', timeout=self.timeout, client=self._client)
def delete_budget(self, model_uuid): """Delete a budget. @param the name of the wallet. @param the model UUID. @return a success string from the plans server. @raise ServerError via make_request. """ return make_request( '{}model/{}/budget'.format(self.url, model_uuid), method='DELETE', timeout=self.timeout, client=self._client)
def create_wallet(self, wallet_name, limit): """Create a new wallet. @param the name of the wallet. @param the value of the limit. @return a success string from the plans server. @raise ServerError via make_request. """ request = { 'wallet': wallet_name, 'limit': str(limit), } return make_request( '{}wallet'.format(self.url), method='POST', body=request, timeout=self.timeout, client=self._client)
def create_budget(self, wallet_name, model_uuid, limit): """Create a new budget for a model and wallet. @param the name of the wallet. @param the model UUID. @param the new value of the limit. @return a success string from the plans server. @raise ServerError via make_request. """ request = { 'model': model_uuid, 'limit': limit, } return make_request( '{}wallet/{}/budget'.format(self.url, wallet_name), method='POST', body=request, timeout=self.timeout, client=self._client)
def update_wallet(self, wallet_name, limit): """Update a wallet with a new limit. @param the name of the wallet. @param the new value of the limit. @return a success string from the plans server. @raise ServerError via make_request. """ request = { 'update': { 'limit': str(limit), } } return make_request( '{}wallet/{}'.format(self.url, wallet_name), method='PATCH', body=request, timeout=self.timeout, client=self._client)
def discharge_token(self, username): """Discharge token for a user. Raise a ServerError if an error occurs in the request process. @param username The logged in user. @return The resulting base64 encoded discharged token. """ url = '{}discharge-token-for-user?username={}'.format( self.url, quote(username)) logging.debug('Sending identity info to {}'.format(url)) response = make_request(url, method='GET', timeout=self.timeout) try: macaroon = response['DischargeToken'] json_macaroon = json.dumps(macaroon) except (KeyError, UnicodeDecodeError) as err: raise InvalidMacaroon( 'Invalid macaroon from discharger: {}'.format(err.message)) return base64.urlsafe_b64encode( "[{}]".format(json_macaroon).encode('utf-8'))
def update_budget(self, wallet_name, model_uuid, limit): """Update a budget limit. @param the name of the wallet. @param the model UUID. @param the new value of the limit. @return a success string from the plans server. @raise ServerError via make_request. """ request = { 'update': { 'wallet': wallet_name, 'limit': limit, } } return make_request( '{}model/{}/budget'.format(self.url, model_uuid), method='PATCH', body=request, timeout=self.timeout, client=self._client)
def discharge_token(self, username): """Discharge token for a user. Raise a ServerError if an error occurs in the request process. @param username The logged in user. @return The resulting base64 encoded discharged token. """ url = '{}discharge-token-for-user?username={}'.format( self.url, quote(username)) logging.debug('Sending identity info to {}'.format(url)) response = make_request( url, method='GET', auth=self.auth, timeout=self.timeout) try: macaroon = response['DischargeToken'] json_macaroon = json.dumps(macaroon) except (KeyError, UnicodeDecodeError) as err: raise InvalidMacaroon( 'Invalid macaroon from discharger: {}'.format(err.message)) return base64.urlsafe_b64encode("[{}]".format( json_macaroon).encode('utf-8'))
def list_wallets(self): """Get the list of wallets. @return an dict containing a list of wallets, a total, and available credit. @raise ServerError """ response = make_request( '{}wallet'.format(self.url), timeout=self.timeout, client=self._client) try: total = response['total'] return { 'credit': response['credit'], 'total': WalletTotal( limit=total['limit'], budgeted=total['budgeted'], available=total['available'], unallocated=total['unallocated'], usage=total['usage'], consumed=total['consumed']), 'wallets': tuple(Wallet( owner=wallet['owner'], wallet=wallet['wallet'], limit=wallet['limit'], budgeted=wallet['budgeted'], unallocated=wallet['unallocated'], available=wallet['available'], consumed=wallet['consumed'], default='default' in wallet) for wallet in response['wallets']), } except Exception as err: log.error( 'cannot process wallets: invalid JSON response: {!r}'.format( response)) raise ServerError( 'unable to get list of wallets: {!r}'.format(err))
def test_make_request_invalid_method(self): with self.assertRaises(ValueError) as ctx: make_request('http://1.2.3.4', method='bad') self.assertEqual('invalid method bad', ctx.exception.args[0])
def test_make_request_invalid_method(self): with self.assertRaises(ValueError) as ctx: make_request('http://1.2.3.4', method='bad') self.assertEqual('invalid method bad', ctx.exception.args[0])
def test_make_request(self): with HTTMock(self.subscription_response): response = make_request(URL, query={'uuid': 'foo'}) self.assertEqual({u'foo': u'bar', u'baz': u'bax'}, response)
def test_make_request_empty_response(self): with HTTMock(self.empty_response): response = make_request(URL, method='POST', body={'uuid': 'foo'}) self.assertEqual({}, response)
def test_make_request_timeout(self): with self.assert_timeout('http://example.com/?uuid=foo', 42): make_request(URL, query={'uuid': 'foo'}, timeout=42)
def test_make_request_timeout(self): with self.assert_timeout('http://example.com/?uuid=foo', 42): make_request(URL, query={'uuid': 'foo'}, timeout=42)
def test_make_request_invalid_json(self): with HTTMock(self.invalid_response): with self.assertRaises(ServerError) as ctx: make_request(URL, query={'uuid': 'foo'}) self.assertIn('Error decoding JSON response', ctx.exception.args[0])
def test_make_request_empty_response(self): with HTTMock(self.empty_response): response = make_request(URL, method='POST', body={'uuid': 'foo'}) self.assertEqual({}, response)
def test_make_request(self): with HTTMock(self.subscription_response): response = make_request(URL, query={'uuid': 'foo'}) self.assertEqual({u'foo': u'bar', u'baz': u'bax'}, response)
def test_make_request_invalid_json(self): with HTTMock(self.invalid_response): with self.assertRaises(ServerError) as ctx: make_request(URL, query={'uuid': 'foo'}) self.assertIn('Error decoding JSON response', ctx.exception.args[0])