diff --git a/api-ref/source/v1/floatingips.inc b/api-ref/source/v1/floatingips.inc new file mode 100644 index 00000000..6b4ba04d --- /dev/null +++ b/api-ref/source/v1/floatingips.inc @@ -0,0 +1,162 @@ +.. -*- rst -*- + +============ +Floating IPs +============ + +List FloatingIPs +================ + +.. rest_method:: GET v1/floatingips + +List floating IPs. + +**Response codes** + +Normal response code: 200 + +Error response codes: Bad Request(400), Unauthorized(401), Forbidden(403), +Internal Server Error(500) + +Request +------- + +No body content, path, nor query option. + +Response +-------- + +.. rest_parameters:: parameters.yaml + + - floatingips: floatingips + - id: floatingip_id + - floating_network_id: floating_network_id + - subnet_id: floating_subnet_id + - floating_ip_address: floating_ip_address + - reservable: floatingip_reservable + - created_at: created_at + - updated_at: updated_at + +**Example of List Hosts Response** + +.. literalinclude:: ../../../doc/api_samples/floatingips/floatingip-list-resp.json + :language: javascript + +Create Floating IP +================== + +.. rest_method:: POST v1/floatingips + +Create a floating IP. + +**Response codes** + +Normal response code: 201 + +Error response codes: Bad Request(400), Unauthorized(401), Forbidden(403), +Conflict(409), Internal Server Error(500) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - floating_network_id: floating_network_id + - floating_ip_address: floating_ip_address_create + +**Example of Create Host Request** + +.. literalinclude:: ../../../doc/api_samples/floatingips/floatingip-create-req.json + :language: javascript + +Response +-------- + +.. rest_parameters:: parameters.yaml + + - floatingip: floatingip + - id: floatingip_id + - floating_ip_address: floating_ip_address + - network_id: floating_network_id + - subnet_id: floating_subnet_id + - reservable: floatingip_reservable + - created_at: created_at + - updated_at: updated_at + +**Example of Create Host Response** + +.. literalinclude:: ../../../doc/api_samples/floatingips/floatingip-create-resp.json + :language: javascript + +Show Floating IP Details +======================== + +.. rest_method:: GET v1/floatingips/{floatingip_id} + +Show details of a floating IP. + +**Preconditions** + +The floating IP must exist. + +**Response codes** + +Normal response code: 200 + +Error response codes: Bad Request(400), Unauthorized(401), Forbidden(403), +Not Found(404), Internal Server Error(500) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - floatingip_id: floatingip_id_path + +Response +-------- + +.. rest_parameters:: parameters.yaml + + - floatingip: floatingip + - id: floatingip_id + - floating_network_id: floating_network_id + - floating_ip_address: floating_ip_address + - reservable: floatingip_reservable + - created_at: created_at + - updated_at: updated_at + +**Example of Show Floating IP Details Response** + +.. literalinclude:: ../../../doc/api_samples/floatingips/floatingip-details-resp.json + :language: javascript + +Delete Floating IP +================== + +.. rest_method:: DELETE v1/floatingips/{floatingip_id} + +Delete a floating IP. + +**Preconditions** + +The floating IP must exist. + +**Response codes** + +Normal response code: 204 + +Error response codes: Bad Request(400), Unauthorized(401), Forbidden(403), +Not Found(404), Conflict(409), Internal Server Error(500) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - floatingip_id: floatingip_id_path + +Repsponse +--------- + +No body content is returned on a successful DELETE. diff --git a/api-ref/source/v1/index.rst b/api-ref/source/v1/index.rst index 8d46362e..283ee6a4 100644 --- a/api-ref/source/v1/index.rst +++ b/api-ref/source/v1/index.rst @@ -9,3 +9,4 @@ Blazar project. .. include:: leases.inc .. include:: hosts.inc +.. include:: floatingips.inc diff --git a/api-ref/source/v1/parameters.yaml b/api-ref/source/v1/parameters.yaml index a335c222..22304c83 100644 --- a/api-ref/source/v1/parameters.yaml +++ b/api-ref/source/v1/parameters.yaml @@ -1,4 +1,10 @@ # variables in path +floatingip_id_path: + description: | + The ID of the floating IP. + in: path + required: true + type: string host_id_path: description: | The ID of the host. @@ -96,6 +102,61 @@ events_optional: in: body required: false type: array + +## parameters for floating IP + +floating_ip_address: + description: + The floating IP address. + in: body + required: true + type: string +floating_ip_address_create: + description: + The floating IP address. The IP must be the out side of allocation_pools + and within its subnet's CIDR network. + in: body + required: true + type: string +floating_network_id: + description: + An external network ID the floating IP belongs to. + in: body + required: true + type: string +floating_subnet_id: + description: | + An external subnet ID the floating IP belongs to. + in: body + required: true + type: boolean +floatingip: + description: | + A ``floatingip`` object. + in: body + required: true + type: object +floatingip_id: + description: + The ID of the floating ip resources. + in: body + required: true + type: string +floatingip_reservable: + description: | + The flag which represents whether the floating IP is reservable or not. + in: body + required: true + type: boolean +floatingips: + description: | + A list of ``floatingip`` objects. + in: body + required: true + type: array + +## parameters for host + host: description: | A ``host`` object. @@ -197,6 +258,9 @@ hosts: in: body required: true type: array + +## parameters for leases + lease: description: | A ``lease`` object. diff --git a/blazar/api/v1/app.py b/blazar/api/v1/app.py index 492a5da0..a2ab347b 100644 --- a/blazar/api/v1/app.py +++ b/blazar/api/v1/app.py @@ -22,10 +22,9 @@ from keystonemiddleware import auth_token from oslo_config import cfg from oslo_log import log as logging from oslo_middleware import debug +from stevedore import enabled from werkzeug import exceptions as werkzeug_exceptions -from blazar.api.v1.leases import v1_0 as leases_api_v1_0 -from blazar.api.v1.oshosts import v1_0 as host_api_v1_0 from blazar.api.v1 import utils as api_utils @@ -69,14 +68,19 @@ def make_app(): app.route('/', methods=['GET'])(version_list) app.route('/versions', methods=['GET'])(version_list) - app.register_blueprint(leases_api_v1_0.rest, url_prefix='/v1') LOG.debug("List of plugins: %s", cfg.CONF.manager.plugins) - # TODO(sbauza) : Change this whole crap by removing hardcoded values and - # maybe using stevedore for achieving this - if (cfg.CONF.manager.plugins - and 'physical.host.plugin' in cfg.CONF.manager.plugins): - app.register_blueprint(host_api_v1_0.rest, url_prefix='/v1/os-hosts') + + plugins = cfg.CONF.manager.plugins + ['leases'] + extension_manager = enabled.EnabledExtensionManager( + check_func=lambda ext: ext.name in plugins, + namespace='blazar.api.v1.extensions', + invoke_on_load=False + ) + + for ext in extension_manager.extensions: + bp = ext.plugin() + app.register_blueprint(bp, url_prefix=bp.url_prefix) for code in werkzeug_exceptions.default_exceptions: app.register_error_handler(code, make_json_error) diff --git a/blazar/api/v1/floatingips/__init__.py b/blazar/api/v1/floatingips/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/blazar/api/v1/floatingips/service.py b/blazar/api/v1/floatingips/service.py new file mode 100644 index 00000000..8181061b --- /dev/null +++ b/blazar/api/v1/floatingips/service.py @@ -0,0 +1,57 @@ +# Copyright (c) 2019 NTT +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from blazar.manager.floatingips import rpcapi as manager_rpcapi +from blazar import policy +from blazar.utils import trusts + + +class API(object): + def __init__(self): + self.manager_rpcapi = manager_rpcapi.ManagerRPCAPI() + + @policy.authorize('floatingips', 'get') + def get_floatingips(self): + """List all existing floatingip.""" + return self.manager_rpcapi.list_floatingips() + + @policy.authorize('floatingips', 'post') + @trusts.use_trust_auth() + def create_floatingip(self, data): + """Create new floatingip. + + :param data: New floatingip characteristics. + :type data: dict + """ + + return self.manager_rpcapi.create_floatingip(data) + + @policy.authorize('floatingips', 'get') + def get_floatingip(self, floatingip_id): + """Get floatingip by its ID. + + :param floatingip_id: ID of the floatingip in Blazar DB. + :type floatingip_id: str + """ + return self.manager_rpcapi.get_floatingip(floatingip_id) + + @policy.authorize('floatingips', 'delete') + def delete_floatingip(self, floatingip_id): + """Delete specified floatingip. + + :param floatingip_id: ID of the floatingip in Blazar DB. + :type floatingip_id: str + """ + self.manager_rpcapi.delete_floatingip(floatingip_id) diff --git a/blazar/api/v1/floatingips/v1_0.py b/blazar/api/v1/floatingips/v1_0.py new file mode 100644 index 00000000..4a2815e5 --- /dev/null +++ b/blazar/api/v1/floatingips/v1_0.py @@ -0,0 +1,58 @@ +# Copyright (c) 2019 NTT. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from blazar.api.v1.floatingips import service +from blazar.api.v1 import utils as api_utils +from blazar.api.v1 import validation +from blazar import utils + + +def get_rest(): + """Return Rest app""" + return rest + + +rest = api_utils.Rest('floatingip_v1_0', __name__, + url_prefix='/v1/floatingips') +_api = utils.LazyProxy(service.API) + + +# Floatingips operations + +@rest.get('') +def floatingips_list(): + """List all existing floatingips.""" + return api_utils.render(floatingips=_api.get_floatingips()) + + +@rest.post('') +def floatingips_create(data): + """Create new floatingip.""" + return api_utils.render(floatingip=_api.create_floatingip(data)) + + +@rest.get('/') +@validation.check_exists(_api.get_floatingip, floatingip_id='floatingip_id') +def floatingips_get(floatingip_id): + """Get floatingip by its ID.""" + return api_utils.render(floatingip=_api.get_floatingip(floatingip_id)) + + +@rest.delete('/') +@validation.check_exists(_api.get_floatingip, floatingip_id='floatingip_id') +def floatingips_delete(floatingip_id): + """Delete specified floatingip.""" + _api.delete_floatingip(floatingip_id) + return api_utils.render() diff --git a/blazar/api/v1/leases/v1_0.py b/blazar/api/v1/leases/v1_0.py index 78049451..56f3190f 100644 --- a/blazar/api/v1/leases/v1_0.py +++ b/blazar/api/v1/leases/v1_0.py @@ -22,7 +22,13 @@ from blazar import utils LOG = logging.getLogger(__name__) -rest = api_utils.Rest('v1_0', __name__) + +def get_rest(): + """Return Rest app""" + return rest + + +rest = api_utils.Rest('v1_0', __name__, url_prefix='/v1') _api = utils.LazyProxy(service.API) diff --git a/blazar/api/v1/oshosts/v1_0.py b/blazar/api/v1/oshosts/v1_0.py index 07e6d0e4..755f0b6a 100644 --- a/blazar/api/v1/oshosts/v1_0.py +++ b/blazar/api/v1/oshosts/v1_0.py @@ -19,7 +19,12 @@ from blazar.api.v1 import validation from blazar import utils -rest = api_utils.Rest('host_v1_0', __name__) +def get_rest(): + """Return Rest app""" + return rest + + +rest = api_utils.Rest('host_v1_0', __name__, url_prefix='/v1/os-hosts') _api = utils.LazyProxy(service.API) diff --git a/blazar/api/v1/utils.py b/blazar/api/v1/utils.py index 57d3ac65..c6c35994 100644 --- a/blazar/api/v1/utils.py +++ b/blazar/api/v1/utils.py @@ -36,6 +36,7 @@ class Rest(flask.Blueprint): def __init__(self, *args, **kwargs): super(Rest, self).__init__(*args, **kwargs) + self.url_prefix = kwargs.get('url_prefix', None) self.routes_with_query_support = [] def get(self, rule, status_code=200, query=False): diff --git a/blazar/manager/floatingips/__init__.py b/blazar/manager/floatingips/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/blazar/manager/floatingips/rpcapi.py b/blazar/manager/floatingips/rpcapi.py new file mode 100644 index 00000000..34322c69 --- /dev/null +++ b/blazar/manager/floatingips/rpcapi.py @@ -0,0 +1,53 @@ +# Copyright (c) 2019 NTT. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from oslo_config import cfg + +from blazar import manager +from blazar.utils import service + +CONF = cfg.CONF +CONF.import_opt('rpc_topic', 'blazar.manager.service', 'manager') + + +class ManagerRPCAPI(service.RPCClient): + """Client side for the Manager RPC API. + + Used from other services to communicate with blazar-manager service. + """ + BASE_RPC_API_VERSION = '1.0' + + def __init__(self): + """Initiate RPC API client with needed topic and RPC version.""" + super(ManagerRPCAPI, self).__init__(manager.get_target()) + + def get_floatingip(self, floatingip_id): + """Get detailed info about a floatingip.""" + return self.call('virtual:floatingip:get_floatingip', + fip_id=floatingip_id) + + def list_floatingips(self): + """List all floatingips.""" + return self.call('virtual:floatingip:list_floatingip') + + def create_floatingip(self, floatingip_values): + """Create floatingip with specified parameters.""" + return self.call('virtual:floatingip:create_floatingip', + values=floatingip_values) + + def delete_floatingip(self, floatingip_id): + """Delete specified floatingip.""" + return self.call('virtual:floatingip:delete_floatingip', + fip_id=floatingip_id) diff --git a/blazar/policies/__init__.py b/blazar/policies/__init__.py index 2fde2952..a8916c0b 100644 --- a/blazar/policies/__init__.py +++ b/blazar/policies/__init__.py @@ -13,6 +13,7 @@ import itertools from blazar.policies import base +from blazar.policies import floatingips from blazar.policies import leases from blazar.policies import oshosts @@ -22,4 +23,5 @@ def list_rules(): base.list_rules(), leases.list_rules(), oshosts.list_rules(), + floatingips.list_rules() ) diff --git a/blazar/policies/floatingips.py b/blazar/policies/floatingips.py new file mode 100644 index 00000000..c946b662 --- /dev/null +++ b/blazar/policies/floatingips.py @@ -0,0 +1,61 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_policy import policy + +from blazar.policies import base + +POLICY_ROOT = 'blazar:floatingips:%s' + +floatingips_policies = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'get', + check_str=base.RULE_ADMIN, + description='Policy rule for List/Show FloatingIP(s) API.', + operations=[ + { + 'path': '/{api_version}/floatingips', + 'method': 'GET' + }, + { + 'path': '/{api_version}/floatingips/{floatingip_id}', + 'method': 'GET' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'post', + check_str=base.RULE_ADMIN, + description='Policy rule for Create Floating IP API.', + operations=[ + { + 'path': '/{api_version}/floatingips', + 'method': 'POST' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'delete', + check_str=base.RULE_ADMIN, + description='Policy rule for Delete Floating IP API.', + operations=[ + { + 'path': '/{api_version}/floatingips/{floatingip_id}', + 'method': 'DELETE' + } + ] + ) +] + + +def list_rules(): + return floatingips_policies diff --git a/blazar/tests/api/v1/test_app.py b/blazar/tests/api/v1/test_app.py index 45a65e1b..9d549a71 100644 --- a/blazar/tests/api/v1/test_app.py +++ b/blazar/tests/api/v1/test_app.py @@ -19,6 +19,7 @@ from oslo_config import cfg from werkzeug import exceptions as werkzeug_exceptions from blazar.api.v1 import app +from blazar.api.v1.leases import v1_0 as lease_api_v1_0 from blazar.api.v1.oshosts import v1_0 as host_api_v1_0 from blazar.api.v1 import utils as api_utils from blazar import tests @@ -81,11 +82,14 @@ class AppTestCaseForHostsPlugin(tests.TestCase): cfg.CONF.set_override('plugins', ['physical.host.plugin'], 'manager') self.app = app self.host_api_v1_0 = host_api_v1_0 + self.lease_api_v1_0 = lease_api_v1_0 self.flask = flask self.fake_blueprint = self.patch(self.flask.Flask, 'register_blueprint') def test_make_app_with_host_plugin(self): self.app.make_app() - self.fake_blueprint.assert_called_with(self.host_api_v1_0.rest, - url_prefix='/v1/os-hosts') + self.fake_blueprint.assert_any_call(self.lease_api_v1_0.rest, + url_prefix='/v1') + self.fake_blueprint.assert_any_call(self.host_api_v1_0.rest, + url_prefix='/v1/os-hosts') diff --git a/doc/api_samples/floatingips/floatingip-create-req.json b/doc/api_samples/floatingips/floatingip-create-req.json new file mode 100644 index 00000000..854acce7 --- /dev/null +++ b/doc/api_samples/floatingips/floatingip-create-req.json @@ -0,0 +1,4 @@ +{ + "floating_network_id": "1e17587e-a7ed-4b82-a17b-4beb32523e28", + "floating_ip_address": "172.24.4.101" +} diff --git a/doc/api_samples/floatingips/floatingip-create-resp.json b/doc/api_samples/floatingips/floatingip-create-resp.json new file mode 100644 index 00000000..6670a489 --- /dev/null +++ b/doc/api_samples/floatingips/floatingip-create-resp.json @@ -0,0 +1,11 @@ +{ + "floatingip": { + "id": "84c4d37e-1f8b-45ce-897b-16ad7f49b0e9", + "floating_network_id": "1e17587e-a7ed-4b82-a17b-4beb32523e28", + "floating_ip_address": "172.24.4.101", + "subnet_id": "a2fa9b7e-8451-4868-85f4-92ee7c77eed6", + "reservable": true, + "created_at": "2019-01-28T08:01:46.000000", + "updated_at": null + } +} diff --git a/doc/api_samples/floatingips/floatingip-details-resp.json b/doc/api_samples/floatingips/floatingip-details-resp.json new file mode 100644 index 00000000..6670a489 --- /dev/null +++ b/doc/api_samples/floatingips/floatingip-details-resp.json @@ -0,0 +1,11 @@ +{ + "floatingip": { + "id": "84c4d37e-1f8b-45ce-897b-16ad7f49b0e9", + "floating_network_id": "1e17587e-a7ed-4b82-a17b-4beb32523e28", + "floating_ip_address": "172.24.4.101", + "subnet_id": "a2fa9b7e-8451-4868-85f4-92ee7c77eed6", + "reservable": true, + "created_at": "2019-01-28T08:01:46.000000", + "updated_at": null + } +} diff --git a/doc/api_samples/floatingips/floatingip-list-resp.json b/doc/api_samples/floatingips/floatingip-list-resp.json new file mode 100644 index 00000000..da30d2a9 --- /dev/null +++ b/doc/api_samples/floatingips/floatingip-list-resp.json @@ -0,0 +1,23 @@ +{ + "floatingips": [ + { + "id": "84c4d37e-1f8b-45ce-897b-16ad7f49b0e9", + "floating_network_id": "1e17587e-a7ed-4b82-a17b-4beb32523e28", + "subnet_id": "a2fa9b7e-8451-4868-85f4-92ee7c77eed6", + "floating_ip_address": "172.24.4.101", + "reservable": true, + "created_at": "2019-01-28 08:01:46", + "updated_at": null + + }, + { + "id": "f180cf4c-f886-4dd1-8c36-854d17fbefb5", + "floating_network_id": "1e17587e-a7ed-4b82-a17b-4beb32523e28", + "subnet_id": "a2fa9b7e-8451-4868-85f4-92ee7c77eed6", + "floating_ip_address": "172.24.4.102", + "reservable": true, + "created_at": "2019-01-28 08:08:22", + "updated_at": null, + } + ] +} diff --git a/setup.cfg b/setup.cfg index 09cfd088..41e0eb82 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,6 +39,12 @@ blazar.resource.plugins = dummy.vm.plugin=blazar.plugins.dummy_vm_plugin:DummyVMPlugin physical.host.plugin=blazar.plugins.oshosts.host_plugin:PhysicalHostPlugin virtual.instance.plugin=blazar.plugins.instances.instance_plugin:VirtualInstancePlugin + virtual.floatingip.plugin=blazar.plugins.floatingips.floatingip_plugin:FloatingIpPlugin + +blazar.api.v1.extensions = + leases=blazar.api.v1.leases.v1_0:get_rest + physical.host.plugin=blazar.api.v1.oshosts.v1_0:get_rest + virtual.floatingip.plugin=blazar.api.v1.floatingips.v1_0:get_rest blazar.api.v2.controllers.extensions = oshosts=blazar.api.v2.controllers.extensions.host:HostsController