Add API versioning

- All OpenStack projects have API versioning
- Existing endpoints are now prefixed with /v1
- Still fully backward compatible with old endpoints
- No HTTP redirects is used to avoid unexpected behaviors with
existing clients

Change-Id: If51f3291c44615991b3378b711dffacc1bd2591f
This commit is contained in:
Frédéric Guillot 2017-05-12 13:28:10 -04:00
parent 3392b59188
commit ef8897f58b
9 changed files with 149 additions and 109 deletions

View File

@ -24,7 +24,7 @@ from werkzeug import wrappers
from almanach.core import exception
LOG = log.getLogger(__name__)
api = flask.Blueprint("api", __name__)
api = flask.Blueprint('v1', __name__)
instance_ctl = None
volume_ctl = None
volume_type_ctl = None
@ -85,12 +85,14 @@ def authenticated(api_call):
return decorator
@api.route("/v1/info", methods=["GET"])
@api.route("/info", methods=["GET"])
@to_json
def get_info():
return app_ctl.get_application_info()
@api.route("/v1/project/<project_id>/instance", methods=["POST"])
@api.route("/project/<project_id>/instance", methods=["POST"])
@authenticated
@to_json
@ -112,6 +114,7 @@ def create_instance(project_id):
return flask.Response(status=201)
@api.route("/v1/instance/<instance_id>", methods=["DELETE"])
@api.route("/instance/<instance_id>", methods=["DELETE"])
@authenticated
@to_json
@ -126,6 +129,7 @@ def delete_instance(instance_id):
return flask.Response(status=202)
@api.route("/v1/instance/<instance_id>/resize", methods=["PUT"])
@api.route("/instance/<instance_id>/resize", methods=["PUT"])
@authenticated
@to_json
@ -141,6 +145,7 @@ def resize_instance(instance_id):
return flask.Response(status=200)
@api.route("/v1/instance/<instance_id>/rebuild", methods=["PUT"])
@api.route("/instance/<instance_id>/rebuild", methods=["PUT"])
@authenticated
@to_json
@ -158,6 +163,7 @@ def rebuild_instance(instance_id):
return flask.Response(status=200)
@api.route("/v1/project/<project_id>/instances", methods=["GET"])
@api.route("/project/<project_id>/instances", methods=["GET"])
@authenticated
@to_json
@ -167,6 +173,7 @@ def list_instances(project_id):
return instance_ctl.list_instances(project_id, start, end)
@api.route("/v1/project/<project_id>/volume", methods=["POST"])
@api.route("/project/<project_id>/volume", methods=["POST"])
@authenticated
@to_json
@ -186,6 +193,7 @@ def create_volume(project_id):
return flask.Response(status=201)
@api.route("/v1/volume/<volume_id>", methods=["DELETE"])
@api.route("/volume/<volume_id>", methods=["DELETE"])
@authenticated
@to_json
@ -200,6 +208,7 @@ def delete_volume(volume_id):
return flask.Response(status=202)
@api.route("/v1/volume/<volume_id>/resize", methods=["PUT"])
@api.route("/volume/<volume_id>/resize", methods=["PUT"])
@authenticated
@to_json
@ -215,6 +224,7 @@ def resize_volume(volume_id):
return flask.Response(status=200)
@api.route("/v1/volume/<volume_id>/attach", methods=["PUT"])
@api.route("/volume/<volume_id>/attach", methods=["PUT"])
@authenticated
@to_json
@ -230,6 +240,7 @@ def attach_volume(volume_id):
return flask.Response(status=200)
@api.route("/v1/volume/<volume_id>/detach", methods=["PUT"])
@api.route("/volume/<volume_id>/detach", methods=["PUT"])
@authenticated
@to_json
@ -245,6 +256,7 @@ def detach_volume(volume_id):
return flask.Response(status=200)
@api.route("/v1/project/<project_id>/volumes", methods=["GET"])
@api.route("/project/<project_id>/volumes", methods=["GET"])
@authenticated
@to_json
@ -254,6 +266,7 @@ def list_volumes(project_id):
return volume_ctl.list_volumes(project_id, start, end)
@api.route("/v1/project/<project_id>/entities", methods=["GET"])
@api.route("/project/<project_id>/entities", methods=["GET"])
@authenticated
@to_json
@ -263,6 +276,7 @@ def list_entity(project_id):
return entity_ctl.list_entities(project_id, start, end)
@api.route("/v1/entity/instance/<instance_id>", methods=["PUT"])
@api.route("/entity/instance/<instance_id>", methods=["PUT"])
@authenticated
@to_json
@ -277,6 +291,7 @@ def update_instance_entity(instance_id):
return result
@api.route("/v1/entity/<entity_id>", methods=["HEAD"])
@api.route("/entity/<entity_id>", methods=["HEAD"])
@authenticated
def entity_exists(entity_id):
@ -287,6 +302,7 @@ def entity_exists(entity_id):
return response
@api.route("/v1/entity/<entity_id>", methods=["GET"])
@api.route("/entity/<entity_id>", methods=["GET"])
@authenticated
@to_json
@ -294,6 +310,7 @@ def get_entity(entity_id):
return entity_ctl.get_all_entities_by_id(entity_id)
@api.route("/v1/volume_types", methods=["GET"])
@api.route("/volume_types", methods=["GET"])
@authenticated
@to_json
@ -302,6 +319,7 @@ def list_volume_types():
return volume_type_ctl.list_volume_types()
@api.route("/v1/volume_type/<volume_type_id>", methods=["GET"])
@api.route("/volume_type/<volume_type_id>", methods=["GET"])
@authenticated
@to_json
@ -310,6 +328,7 @@ def get_volume_type(volume_type_id):
return volume_type_ctl.get_volume_type(volume_type_id)
@api.route("/v1/volume_type", methods=["POST"])
@api.route("/volume_type", methods=["POST"])
@authenticated
@to_json
@ -323,6 +342,7 @@ def create_volume_type():
return flask.Response(status=201)
@api.route("/v1/volume_type/<volume_type_id>", methods=["DELETE"])
@api.route("/volume_type/<volume_type_id>", methods=["DELETE"])
@authenticated
@to_json

View File

@ -19,6 +19,7 @@ CONF = config.CONF
class AlmanachClient(rest_client.RestClient):
api_version = 'v1'
def __init__(self, auth_provider):
super(AlmanachClient, self).__init__(
@ -32,11 +33,11 @@ class AlmanachClient(rest_client.RestClient):
return resp, response_body
def create_server(self, tenant_id, body):
resp, response_body = self.post('/project/{}/instance'.format(tenant_id), body=body)
resp, response_body = self.post('project/{}/instance'.format(tenant_id), body=body)
return resp, response_body
def delete_server(self, instance_id, body):
resp, response_body = self.delete('/instance/{}'.format(instance_id), body=body)
resp, response_body = self.delete('instance/{}'.format(instance_id), body=body)
return resp, response_body
def get_volume_type(self, volume_type_id):
@ -44,28 +45,28 @@ class AlmanachClient(rest_client.RestClient):
return resp, response_body
def create_volume_type(self, body):
resp, response_body = self.post('/volume_type', body)
resp, response_body = self.post('volume_type', body)
return resp, response_body
def create_volume(self, tenant_id, body):
url = '/project/{}/volume'.format(tenant_id)
url = 'project/{}/volume'.format(tenant_id)
resp, response_body = self.post(url, body)
return resp, response_body
def delete_volume(self, volume_id, body):
resp, response_body = self.delete('/volume/{}'.format(volume_id), body=body)
resp, response_body = self.delete('volume/{}'.format(volume_id), body=body)
return resp, response_body
def resize_volume(self, volume_id, body):
resp, response_body = self.put('/volume/{}/resize'.format(volume_id), body)
resp, response_body = self.put('volume/{}/resize'.format(volume_id), body)
return resp, response_body
def attach_volume(self, volume_id, body):
resp, response_body = self.put('/volume/{}/attach'.format(volume_id), body)
resp, response_body = self.put('volume/{}/attach'.format(volume_id), body)
return resp, response_body
def detach_volume(self, volume_id, body):
resp, response_body = self.put('/volume/{}/detach'.format(volume_id), body)
resp, response_body = self.put('volume/{}/detach'.format(volume_id), body)
return resp, response_body
def get_tenant_entities(self, tenant_id):
@ -74,16 +75,16 @@ class AlmanachClient(rest_client.RestClient):
return resp, response_body
def update_server(self, instance_id, body):
url = '/entity/instance/{}'.format(instance_id)
url = 'entity/instance/{}'.format(instance_id)
resp, response_body = self.put(url, body)
return resp, response_body
def rebuild(self, instance_id, body):
update_instance_rebuild_query = "/instance/{}/rebuild".format(instance_id)
update_instance_rebuild_query = "instance/{}/rebuild".format(instance_id)
resp, response_body = self.put(update_instance_rebuild_query, body)
return resp, response_body
def resize(self, instance_id, body):
url = "/instance/{}/resize".format(instance_id)
url = "instance/{}/resize".format(instance_id)
resp, response_body = self.put(url, body)
return resp, response_body

View File

@ -26,7 +26,7 @@ class TestApiAuthentication(base_api.BaseApi):
self.auth_adapter.validate.side_effect = exception.AuthenticationFailureException('Unauthorized')
query_string = {'start': '2014-01-01 00:00:00.0000', 'end': '2014-02-01 00:00:00.0000'}
code, result = self.api_get(url='/project/TENANT_ID/entities',
code, result = self.api_get(url='/v1/project/TENANT_ID/entities',
query_string=query_string,
headers={'X-Auth-Token': 'wrong token'})

View File

@ -35,7 +35,7 @@ class TestApiEntity(base_api.BaseApi):
end = '2016-03-03 00:00:00.000000'
code, result = self.api_put(
'/entity/instance/INSTANCE_ID',
'/v1/entity/instance/INSTANCE_ID',
headers={'X-Auth-Token': 'some token value'},
query_string={
'start': start,
@ -63,7 +63,7 @@ class TestApiEntity(base_api.BaseApi):
self.entity_ctl.update_active_instance_entity.return_value = an_instance
code, result = self.api_put(
'/entity/instance/INSTANCE_ID',
'/v1/entity/instance/INSTANCE_ID',
headers={'X-Auth-Token': 'some token value'},
data=data,
)
@ -87,11 +87,9 @@ class TestApiEntity(base_api.BaseApi):
'flavor': 'A_FLAVOR',
}
code, result = self.api_put(
'/entity/instance/INSTANCE_ID',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_put('/v1/entity/instance/INSTANCE_ID',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.entity_ctl.update_active_instance_entity.assert_called_once_with(instance_id=instance_id, **data)
self.assertIn("error", result)
@ -115,11 +113,9 @@ class TestApiEntity(base_api.BaseApi):
'flavor': 'A_FLAVOR',
}
code, result = self.api_put(
'/entity/instance/INSTANCE_ID',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_put('/v1/entity/instance/INSTANCE_ID',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.entity_ctl.update_active_instance_entity.assert_called_once_with(instance_id=instance_id, **data)
self.assertIn("error", result)
@ -130,7 +126,8 @@ class TestApiEntity(base_api.BaseApi):
self.entity_ctl.entity_exists.return_value = True
entity_id = "entity_id"
code, result = self.api_head('/entity/{id}'.format(id=entity_id), headers={'X-Auth-Token': 'some token value'})
code, result = self.api_head('/v1/entity/{id}'.format(id=entity_id),
headers={'X-Auth-Token': 'some token value'})
self.entity_ctl.entity_exists.assert_called_once_with(entity_id=entity_id)
self.assertEqual(code, 200)
@ -139,7 +136,8 @@ class TestApiEntity(base_api.BaseApi):
self.entity_ctl.entity_exists.return_value = False
entity_id = "entity_id"
code, result = self.api_head('/entity/{id}'.format(id=entity_id), headers={'X-Auth-Token': 'some token value'})
code, result = self.api_head('/v1/entity/{id}'.format(id=entity_id),
headers={'X-Auth-Token': 'some token value'})
self.entity_ctl.entity_exists.assert_called_once_with(entity_id=entity_id)
self.assertEqual(code, 404)

View File

@ -18,10 +18,16 @@ from almanach.tests.unit.api.v1 import base_api
class TestApiInfo(base_api.BaseApi):
def test_info(self):
self.assert_info_call('/v1/info')
def test_info_with_legacy_url(self):
self.assert_info_call('/info')
def assert_info_call(self, url):
info = {'info': {'version': '1.0'}, 'database': {'all_entities': 10, 'active_entities': 2}}
self.app_ctl.get_application_info.return_value = info
code, result = self.api_get('/info')
code, result = self.api_get(url)
self.app_ctl.get_application_info.assert_called_once()
self.assertEqual(code, 200)

View File

@ -22,7 +22,7 @@ class TestApiInstance(base_api.BaseApi):
def test_get_instances(self):
self.instance_ctl.list_instances.return_value = [a(instance().with_id('123'))]
code, result = self.api_get('/project/TENANT_ID/instances',
code, result = self.api_get('/v1/project/TENANT_ID/instances',
query_string={
'start': '2014-01-01 00:00:00.0000',
'end': '2014-02-01 00:00:00.0000'
@ -47,11 +47,9 @@ class TestApiInstance(base_api.BaseApi):
os_distro="A_DISTRIBUTION",
os_version="AN_OS_VERSION")
code, result = self.api_post(
'/project/PROJECT_ID/instance',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_post('/v1/project/PROJECT_ID/instance',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.create_instance.assert_called_once_with(
tenant_id="PROJECT_ID",
@ -73,11 +71,9 @@ class TestApiInstance(base_api.BaseApi):
os_type="AN_OS_TYPE",
os_version="AN_OS_VERSION")
code, result = self.api_post(
'/project/PROJECT_ID/instance',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_post('/v1/project/PROJECT_ID/instance',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.create_instance.assert_not_called()
self.assertEqual(result["error"], "The 'os_distro' param is mandatory for the request you have made.")
@ -93,11 +89,9 @@ class TestApiInstance(base_api.BaseApi):
os_distro="A_DISTRIBUTION",
os_version="AN_OS_VERSION")
code, result = self.api_post(
'/project/PROJECT_ID/instance',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_post('/v1/project/PROJECT_ID/instance',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.create_instance.assert_called_once_with(
tenant_id="PROJECT_ID",
@ -122,7 +116,7 @@ class TestApiInstance(base_api.BaseApi):
flavor="A_FLAVOR")
code, result = self.api_put(
'/instance/INSTANCE_ID/resize',
'/v1/instance/INSTANCE_ID/resize',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
@ -137,7 +131,9 @@ class TestApiInstance(base_api.BaseApi):
def test_successfull_instance_delete(self):
data = dict(date="DELETE_DATE")
code, result = self.api_delete('/instance/INSTANCE_ID', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/instance/INSTANCE_ID',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.delete_instance.assert_called_once_with(
instance_id="INSTANCE_ID",
@ -147,7 +143,7 @@ class TestApiInstance(base_api.BaseApi):
def test_instance_delete_missing_a_param_returns_bad_request_code(self):
code, result = self.api_delete(
'/instance/INSTANCE_ID',
'/v1/instance/INSTANCE_ID',
data=dict(),
headers={'X-Auth-Token': 'some token value'}
)
@ -160,7 +156,7 @@ class TestApiInstance(base_api.BaseApi):
self.assertEqual(code, 400)
def test_instance_delete_no_data_bad_request_code(self):
code, result = self.api_delete('/instance/INSTANCE_ID', headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/instance/INSTANCE_ID', headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.delete_instance.assert_not_called()
self.assertIn(
@ -173,7 +169,9 @@ class TestApiInstance(base_api.BaseApi):
data = dict(date="A_BAD_DATE")
self.instance_ctl.delete_instance.side_effect = exception.DateFormatException
code, result = self.api_delete('/instance/INSTANCE_ID', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/instance/INSTANCE_ID',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.delete_instance.assert_called_once_with(
instance_id="INSTANCE_ID",
@ -189,7 +187,7 @@ class TestApiInstance(base_api.BaseApi):
def test_instance_resize_missing_a_param_returns_bad_request_code(self):
data = dict(date="UPDATED_AT")
code, result = self.api_put(
'/instance/INSTANCE_ID/resize',
'/v1/instance/INSTANCE_ID/resize',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
@ -206,7 +204,7 @@ class TestApiInstance(base_api.BaseApi):
data = dict(date="A_BAD_DATE",
flavor="A_FLAVOR")
code, result = self.api_put(
'/instance/INSTANCE_ID/resize',
'/v1/instance/INSTANCE_ID/resize',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
@ -231,11 +229,9 @@ class TestApiInstance(base_api.BaseApi):
'os_type': 'AN_OS_TYPE',
'rebuild_date': 'UPDATE_DATE',
}
code, result = self.api_put(
'/instance/INSTANCE_ID/rebuild',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_put('/v1/instance/INSTANCE_ID/rebuild',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.rebuild_instance.assert_called_once_with(
instance_id=instance_id,
@ -251,11 +247,9 @@ class TestApiInstance(base_api.BaseApi):
'distro': 'A_DISTRIBUTION',
'rebuild_date': 'UPDATE_DATE',
}
code, result = self.api_put(
'/instance/INSTANCE_ID/rebuild',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_put('/v1/instance/INSTANCE_ID/rebuild',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.rebuild_instance.assert_not_called()
self.assertIn(
@ -273,11 +267,9 @@ class TestApiInstance(base_api.BaseApi):
'os_type': 'AN_OS_TYPE',
'rebuild_date': 'A_BAD_UPDATE_DATE',
}
code, result = self.api_put(
'/instance/INSTANCE_ID/rebuild',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
code, result = self.api_put('/v1/instance/INSTANCE_ID/rebuild',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.instance_ctl.rebuild_instance.assert_called_once_with(
instance_id=instance_id,

View File

@ -29,7 +29,7 @@ class TestApiVolume(base_api.BaseApi):
attached_to=["INSTANCE_ID"])
code, result = self.api_post(
'/project/PROJECT_ID/volume',
'/v1/project/PROJECT_ID/volume',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
@ -48,7 +48,7 @@ class TestApiVolume(base_api.BaseApi):
attached_to=[])
code, result = self.api_post(
'/project/PROJECT_ID/volume',
'/v1/project/PROJECT_ID/volume',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
@ -70,7 +70,7 @@ class TestApiVolume(base_api.BaseApi):
attached_to=["INSTANCE_ID"])
code, result = self.api_post(
'/project/PROJECT_ID/volume',
'/v1/project/PROJECT_ID/volume',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
@ -89,7 +89,9 @@ class TestApiVolume(base_api.BaseApi):
def test_successful_volume_delete(self):
data = dict(date="DELETE_DATE")
code, result = self.api_delete('/volume/VOLUME_ID', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/volume/VOLUME_ID',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.volume_ctl.delete_volume.assert_called_once_with(
volume_id="VOLUME_ID",
@ -98,7 +100,9 @@ class TestApiVolume(base_api.BaseApi):
self.assertEqual(code, 202)
def test_volume_delete_missing_a_param_returns_bad_request_code(self):
code, result = self.api_delete('/volume/VOLUME_ID', data=dict(), headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/volume/VOLUME_ID',
data=dict(),
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"The 'date' param is mandatory for the request you have made.",
@ -108,7 +112,8 @@ class TestApiVolume(base_api.BaseApi):
self.assertEqual(code, 400)
def test_volume_delete_no_data_bad_request_code(self):
code, result = self.api_delete('/volume/VOLUME_ID', headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/volume/VOLUME_ID',
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"Invalid parameter or payload",
@ -121,7 +126,9 @@ class TestApiVolume(base_api.BaseApi):
self.volume_ctl.delete_volume.side_effect = exception.DateFormatException
data = dict(date="A_BAD_DATE")
code, result = self.api_delete('/volume/VOLUME_ID', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/volume/VOLUME_ID',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"The provided date has an invalid format. "
@ -138,7 +145,9 @@ class TestApiVolume(base_api.BaseApi):
data = dict(date="UPDATED_AT",
size="NEW_SIZE")
code, result = self.api_put('/volume/VOLUME_ID/resize', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/resize',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.volume_ctl.resize_volume.assert_called_once_with(
volume_id="VOLUME_ID",
@ -150,7 +159,9 @@ class TestApiVolume(base_api.BaseApi):
def test_volume_resize_missing_a_param_returns_bad_request_code(self):
data = dict(date="A_DATE")
code, result = self.api_put('/volume/VOLUME_ID/resize', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/resize',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"The 'size' param is mandatory for the request you have made.",
@ -164,7 +175,9 @@ class TestApiVolume(base_api.BaseApi):
data = dict(date="BAD_DATE",
size="NEW_SIZE")
code, result = self.api_put('/volume/VOLUME_ID/resize', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/resize',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"The provided date has an invalid format. "
@ -182,7 +195,9 @@ class TestApiVolume(base_api.BaseApi):
data = dict(date="UPDATED_AT",
attachments=[uuidutils.generate_uuid()])
code, result = self.api_put('/volume/VOLUME_ID/attach', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/attach',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.volume_ctl.attach_volume.assert_called_once_with(
volume_id="VOLUME_ID",
@ -195,7 +210,7 @@ class TestApiVolume(base_api.BaseApi):
data = dict(date="A_DATE")
code, result = self.api_put(
'/volume/VOLUME_ID/attach',
'/v1/volume/VOLUME_ID/attach',
data=data,
headers={'X-Auth-Token': 'some token value'}
)
@ -212,7 +227,9 @@ class TestApiVolume(base_api.BaseApi):
data = dict(date="A_BAD_DATE",
attachments=[uuidutils.generate_uuid()])
code, result = self.api_put('/volume/VOLUME_ID/attach', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/attach',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"The provided date has an invalid format. "
@ -230,7 +247,9 @@ class TestApiVolume(base_api.BaseApi):
data = dict(date="UPDATED_AT",
attachments=[uuidutils.generate_uuid()])
code, result = self.api_put('/volume/VOLUME_ID/detach', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/detach',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.volume_ctl.detach_volume.assert_called_once_with(
volume_id="VOLUME_ID",
@ -242,7 +261,9 @@ class TestApiVolume(base_api.BaseApi):
def test_volume_detach_missing_a_param_returns_bad_request_code(self):
data = dict(date="A_DATE")
code, result = self.api_put('/volume/VOLUME_ID/detach', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/detach',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"The 'attachments' param is mandatory for the request you have made.",
@ -256,7 +277,9 @@ class TestApiVolume(base_api.BaseApi):
data = dict(date="A_BAD_DATE",
attachments=[uuidutils.generate_uuid()])
code, result = self.api_put('/volume/VOLUME_ID/detach', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_put('/v1/volume/VOLUME_ID/detach',
data=data,
headers={'X-Auth-Token': 'some token value'})
self.assertIn(
"The provided date has an invalid format. "

View File

@ -25,7 +25,7 @@ class TestApiVolumeType(base_api.BaseApi):
a(volume_type().with_volume_type_name('some_volume_type_name'))
]
code, result = self.api_get('/volume_types', headers={'X-Auth-Token': 'some token value'})
code, result = self.api_get('/v1/volume_types', headers={'X-Auth-Token': 'some token value'})
self.volume_type_ctl.list_volume_types.assert_called_once()
self.assertEqual(code, 200)
@ -39,7 +39,7 @@ class TestApiVolumeType(base_api.BaseApi):
type_name="A_VOLUME_TYPE_NAME"
)
code, result = self.api_post('/volume_type', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_post('/v1/volume_type', data=data, headers={'X-Auth-Token': 'some token value'})
self.volume_type_ctl.create_volume_type.assert_called_once_with(
volume_type_id=data['type_id'],
@ -50,14 +50,14 @@ class TestApiVolumeType(base_api.BaseApi):
def test_volume_type_create_missing_a_param_returns_bad_request_code(self):
data = dict(type_name="A_VOLUME_TYPE_NAME")
code, result = self.api_post('/volume_type', data=data, headers={'X-Auth-Token': 'some token value'})
code, result = self.api_post('/v1/volume_type', data=data, headers={'X-Auth-Token': 'some token value'})
self.volume_type_ctl.create_volume_type.assert_not_called()
self.assertEqual(result["error"], "The 'type_id' param is mandatory for the request you have made.")
self.assertEqual(code, 400)
def test_volume_type_delete_with_authentication(self):
code, result = self.api_delete('/volume_type/A_VOLUME_TYPE_ID', headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/volume_type/A_VOLUME_TYPE_ID', headers={'X-Auth-Token': 'some token value'})
self.volume_type_ctl.delete_volume_type.assert_called_once_with('A_VOLUME_TYPE_ID')
self.assertEqual(code, 202)
@ -65,7 +65,7 @@ class TestApiVolumeType(base_api.BaseApi):
def test_volume_type_delete_not_in_database(self):
self.volume_type_ctl.delete_volume_type.side_effect = exception.AlmanachException("An exception occurred")
code, result = self.api_delete('/volume_type/A_VOLUME_TYPE_ID', headers={'X-Auth-Token': 'some token value'})
code, result = self.api_delete('/v1/volume_type/A_VOLUME_TYPE_ID', headers={'X-Auth-Token': 'some token value'})
self.volume_type_ctl.delete_volume_type.assert_called_once_with('A_VOLUME_TYPE_ID')
self.assertIn("An exception occurred", result["error"])

View File

@ -244,10 +244,10 @@ Almanach will process those events:
- :code:`volume.exists`
- :code:`volume_type.create`
API documentation
-----------------
API v1 Documentation
--------------------
:code:`GET /volume_types`
:code:`GET /v1/volume_types`
List volume types.
@ -260,7 +260,7 @@ API documentation
.. literalinclude:: api_examples/output/volume_types.json
:language: json
:code:`GET /volume_type/<volume_type_id>`
:code:`GET /v1/volume_type/<volume_type_id>`
Get a volume type.
@ -291,7 +291,7 @@ API documentation
.. literalinclude:: api_examples/output/volume_type.json
:language: json
:code:`POST /volume_type`
:code:`POST /v1/volume_type`
Create a volume type.
@ -324,7 +324,7 @@ API documentation
.. literalinclude:: api_examples/input/create_volume_type-body.json
:language: json
:code:`DELETE /volume_type/<volume_type_id>`
:code:`DELETE /v1/volume_type/<volume_type_id>`
Delete a volume type.
@ -348,7 +348,7 @@ API documentation
- uuid
- The Volume Type Uuid
:code:`GET /info`
:code:`GET /v1/info`
Display information about the current version and entity counts.
@ -361,7 +361,7 @@ API documentation
.. literalinclude:: api_examples/output/info.json
:language: json
:code:`POST /project/<project_id>/instance`
:code:`POST /v1/project/<project_id>/instance`
Create an instance.
@ -419,7 +419,7 @@ API documentation
.. literalinclude:: api_examples/input/create_instance-body.json
:language: json
:code:`DELETE /instance/<instance_id>`
:code:`DELETE /v1/instance/<instance_id>`
Delete an instance.
@ -453,7 +453,7 @@ API documentation
.. literalinclude:: api_examples/input/delete_instance-body.json
:language: json
:code:`PUT /instance/<instance_id>/resize`
:code:`PUT /v1/instance/<instance_id>/resize`
Re-size an instance.
@ -491,7 +491,7 @@ API documentation
.. literalinclude:: api_examples/input/resize_instance-body.json
:language: json
:code:`PUT /instance/<instance_id>/rebuild`
:code:`PUT /v1/instance/<instance_id>/rebuild`
Rebuild an instance.
@ -537,7 +537,7 @@ API documentation
.. literalinclude:: api_examples/input/rebuild_instance-body.json
:language: json
:code:`GET /project/<project_id>/instances`
:code:`GET /v1/project/<project_id>/instances`
List instances for a tenant.
@ -575,7 +575,7 @@ API documentation
.. literalinclude:: api_examples/output/instances.json
:language: json
:code:`POST /project/<project_id>/volume`
:code:`POST /v1/project/<project_id>/volume`
Create a volume.
@ -629,7 +629,7 @@ API documentation
.. literalinclude:: api_examples/input/create_volume-body.json
:language: json
:code:`DELETE /volume/<volume_id>`
:code:`DELETE /v1/volume/<volume_id>`
Delete a volume.
@ -663,7 +663,7 @@ API documentation
.. literalinclude:: api_examples/input/delete_volume-body.json
:language: json
:code:`PUT /volume/<volume_id>/resize`
:code:`PUT /v1/volume/<volume_id>/resize`
Re-size a volume.
@ -701,7 +701,7 @@ API documentation
.. literalinclude:: api_examples/input/resize_volume-body.json
:language: json
:code:`PUT /volume/<volume_id>/attach`
:code:`PUT /v1/volume/<volume_id>/attach`
Update the attachments for a volume.
@ -739,7 +739,7 @@ API documentation
.. literalinclude:: api_examples/input/attach_volume-body.json
:language: json
:code:`PUT /volume/<volume_id>/detach`
:code:`PUT /v1/volume/<volume_id>/detach`
Detach a volume.
@ -777,7 +777,7 @@ API documentation
.. literalinclude:: api_examples/input/detach_volume-body.json
:language: json
:code:`GET /project/<project_id>/volumes`
:code:`GET /v1/project/<project_id>/volumes`
List volumes for a tenant.
@ -815,7 +815,7 @@ API documentation
.. literalinclude:: api_examples/output/volumes.json
:language: json
:code:`GET /project/<project_id>/entities`
:code:`GET /v1/project/<project_id>/entities`
List entities for a tenant.
@ -853,7 +853,7 @@ API documentation
.. literalinclude:: api_examples/output/entities.json
:language: json
:code:`PUT /entity/instance/<instance_id>`
:code:`PUT /v1/entity/instance/<instance_id>`
Update an instance.
@ -896,7 +896,7 @@ API documentation
.. literalinclude:: api_examples/output/update_instance_entity.json
:language: json
:code:`HEAD /entity/<entity_id>`
:code:`HEAD /v1/entity/<entity_id>`
Verify that an entity exists.
@ -925,7 +925,7 @@ API documentation
.. literalinclude:: api_examples/output/entity.json
:language: json
:code:`GET /entity/<entity_id>`
:code:`GET /v1/entity/<entity_id>`
Get an entity.