# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # 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. import netaddr import netaddr.core as netexc from oslo_log import log as logging import six from webob import exc from nova.api.openstack.api_version_request \ import MAX_PROXY_API_SUPPORT_VERSION from nova.api.openstack.compute.schemas import tenant_networks as schema from nova.api.openstack import wsgi from nova.api import validation import nova.conf from nova import context as nova_context from nova import exception from nova.i18n import _ import nova.network from nova import objects from nova.policies import tenant_networks as tn_policies from nova import quota CONF = nova.conf.CONF QUOTAS = quota.QUOTAS LOG = logging.getLogger(__name__) def network_dict(network): # NOTE(danms): Here, network should be an object, which could have come # from neutron and thus be missing most of the attributes. Providing a # default to get() avoids trying to lazy-load missing attributes. return {"id": network.get("uuid", None) or network.get("id", None), "cidr": str(network.get("cidr", None)), "label": network.get("label", None)} class TenantNetworkController(wsgi.Controller): def __init__(self, network_api=None): self.network_api = nova.network.API() self._default_networks = [] def _refresh_default_networks(self): self._default_networks = [] if CONF.api.use_neutron_default_nets: try: self._default_networks = self._get_default_networks() except Exception: LOG.exception("Failed to get default networks") def _get_default_networks(self): project_id = CONF.api.neutron_default_tenant_id ctx = nova_context.RequestContext(user_id=None, project_id=project_id) networks = {} for n in self.network_api.get_all(ctx): networks[n['id']] = n['label'] return [{'id': k, 'label': v} for k, v in networks.items()] @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors(()) def index(self, req): context = req.environ['nova.context'] context.can(tn_policies.BASE_POLICY_NAME) networks = list(self.network_api.get_all(context)) if not self._default_networks: self._refresh_default_networks() networks.extend(self._default_networks) return {'networks': [network_dict(n) for n in networks]} @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors(404) def show(self, req, id): context = req.environ['nova.context'] context.can(tn_policies.BASE_POLICY_NAME) try: network = self.network_api.get(context, id) except exception.NetworkNotFound: msg = _("Network not found") raise exc.HTTPNotFound(explanation=msg) return {'network': network_dict(network)} @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors((403, 404, 409)) @wsgi.response(202) def delete(self, req, id): context = req.environ['nova.context'] context.can(tn_policies.BASE_POLICY_NAME) try: self.network_api.disassociate(context, id) self.network_api.delete(context, id) except exception.PolicyNotAuthorized as e: raise exc.HTTPForbidden(explanation=six.text_type(e)) except exception.NetworkInUse as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.NetworkNotFound: msg = _("Network not found") raise exc.HTTPNotFound(explanation=msg) @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors((400, 403, 409, 503)) @validation.schema(schema.create) def create(self, req, body): context = req.environ["nova.context"] context.can(tn_policies.BASE_POLICY_NAME) network = body["network"] keys = ["cidr", "cidr_v6", "ipam", "vlan_start", "network_size", "num_networks"] kwargs = {k: network.get(k) for k in keys} label = network["label"] if kwargs["cidr"]: try: net = netaddr.IPNetwork(kwargs["cidr"]) if net.size < 4: msg = _("Requested network does not contain " "enough (2+) usable hosts") raise exc.HTTPBadRequest(explanation=msg) except netexc.AddrConversionError: msg = _("Address could not be converted.") raise exc.HTTPBadRequest(explanation=msg) try: if CONF.enable_network_quota: objects.Quotas.check_deltas(context, {'networks': 1}, context.project_id) except exception.OverQuota: msg = _("Quota exceeded, too many networks.") raise exc.HTTPForbidden(explanation=msg) kwargs['project_id'] = context.project_id try: networks = self.network_api.create(context, label=label, **kwargs) except exception.PolicyNotAuthorized as e: raise exc.HTTPForbidden(explanation=six.text_type(e)) except exception.CidrConflict as e: raise exc.HTTPConflict(explanation=e.format_message()) except Exception: msg = _("Create networks failed") LOG.exception(msg, extra=network) raise exc.HTTPServiceUnavailable(explanation=msg) # NOTE(melwitt): We recheck the quota after creating the object to # prevent users from allocating more resources than their allowed quota # in the event of a race. This is configurable because it can be # expensive if strict quota limits are not required in a deployment. if CONF.quota.recheck_quota and CONF.enable_network_quota: try: objects.Quotas.check_deltas(context, {'networks': 0}, context.project_id) except exception.OverQuota: self.network_api.delete(context, network_dict(networks[0])['id']) msg = _("Quota exceeded, too many networks.") raise exc.HTTPForbidden(explanation=msg) return {"network": network_dict(networks[0])} def _network_count(context, project_id): # NOTE(melwitt): This assumes a single cell. ctx = nova_context.RequestContext(user_id=None, project_id=project_id) ctx = ctx.elevated() networks = nova.network.api.API().get_all(ctx) return {'project': {'networks': len(networks)}} def _register_network_quota(): if CONF.enable_network_quota: QUOTAS.register_resource(quota.CountableResource('networks', _network_count, 'quota_networks')) _register_network_quota()