Add support for floating IPs association

We should add floating ips realted methods, so users can associate
or dissociate fips to their instances.

Change-Id: I284caf6ab0b8297024983db79f5b6ecaea8f4f40
Implements: bp associate-dissociate-fip
This commit is contained in:
Zhenguo Niu 2017-01-23 00:20:18 +08:00
parent aa57478af4
commit 6440dcc4d0
5 changed files with 190 additions and 5 deletions

View File

@ -30,3 +30,5 @@
"mogan:instance:set_power_state": "rule:default"
# Get Instance network information
"mogan:instance:get_networks": "rule:default"
# Associate floating IP to instance
"mogan:instance:associate_floatingip": "rule:default"

View File

@ -17,6 +17,7 @@ import datetime
import jsonschema
from oslo_log import log
from oslo_utils import netutils
import pecan
from pecan import rest
from six.moves import http_client
@ -30,9 +31,11 @@ from mogan.api.controllers.v1 import utils as api_utils
from mogan.api import expose
from mogan.common import exception
from mogan.common.i18n import _
from mogan.common.i18n import _LW
from mogan.common import policy
from mogan.common import states
from mogan.engine.baremetal import ironic_states as ir_states
from mogan import network
from mogan import objects
_DEFAULT_INSTANCE_RETURN_FIELDS = ('uuid', 'name', 'description',
@ -78,6 +81,18 @@ _CREATE_INSTANCE_SCHEMA = {
'additionalProperties': False,
}
_ASSOCIATE_FIP_SCHEMA = {
"$schema": "http://json-schema.org/schema#",
"title": "Associate floating ip schema",
'type': 'object',
'properties': {
'address': {'type': 'string', 'format': 'ip-address'},
'fixed_address': {'type': 'string', 'format': 'ip-address'}
},
'required': ['address'],
'additionalProperties': False
}
class InstanceStates(base.APIBase):
"""API representation of the states of a instance."""
@ -156,6 +171,114 @@ class InstanceStatesController(rest.RestController):
pecan.response.location = link.build_url('instances', url_args)
class FloatingIP(base.APIBase):
"""API representation of the floatingip information for an instance."""
id = types.uuid
"""The ID of the floating IP"""
port_id = types.uuid
"""The ID of the port that associated to"""
class FloatingIPController(rest.RestController):
"""REST controller for Instance floatingips."""
def __init__(self, *args, **kwargs):
super(FloatingIPController, self).__init__(*args, **kwargs)
self.network_api = network.API()
_resource = None
# This _resource is used for authorization.
def _get_resource(self, uuid, *args, **kwargs):
self._resource = objects.Instance.get(pecan.request.context, uuid)
return self._resource
@policy.authorize_wsgi("mogan:instance", "associate_floatingip", False)
@expose.expose(FloatingIP, types.uuid, types.jsontype,
status_code=http_client.CREATED)
def post(self, instance_uuid, floatingip):
"""Add(Associate) Floating Ip.
:param floatingip: The floating IP within the request body.
"""
# Add jsonschema validate
_check_create_body(floatingip, _ASSOCIATE_FIP_SCHEMA)
instance = self._resource or self._get_resource(instance_uuid)
address = floatingip['address']
ports = instance.network_info
if not ports:
msg = _('No ports associated to instance')
raise wsme.exc.ClientSideError(
msg, status_code=http_client.BAD_REQUEST)
fixed_address = None
if 'fixed_address' in floatingip:
fixed_address = floatingip['fixed_address']
for port_id, port in ports.items():
for port_address in port['fixed_ips']:
if port_address['ip_address'] == fixed_address:
break
else:
continue
break
else:
msg = _('Specified fixed address not assigned to instance')
raise wsme.exc.ClientSideError(
msg, status_code=http_client.BAD_REQUEST)
if not fixed_address:
for port_id, port in ports.items():
for port_address in port['fixed_ips']:
if netutils.is_valid_ipv4(port_address['ip_address']):
fixed_address = port_address['ip_address']
break
else:
continue
break
else:
msg = _('Unable to associate floating IP %(address)s '
'to any fixed IPs for instance %(id)s. '
'Instance has no fixed IPv4 addresses to '
'associate.') % ({'address': address,
'id': instance.uuid})
raise wsme.exc.ClientSideError(
msg, status_code=http_client.BAD_REQUEST)
if len(ports) > 1:
LOG.warning(_LW('multiple ports exist, using the first '
'IPv4 fixed_ip: %s'), fixed_address)
try:
fip = self.network_api.associate_floating_ip(
pecan.request.context, floating_address=address,
port_id=port_id, fixed_address=fixed_address)
except exception.FloatingIpNotFoundForAddress:
msg = _('floating IP not found')
raise wsme.exc.ClientSideError(
msg, status_code=http_client.NOT_FOUND)
except exception.Forbidden as e:
raise wsme.exc.ClientSideError(
msg, status_code=http_client.FORBIDDEN)
except Exception as e:
msg = _('Unable to associate floating IP %(address)s to '
'fixed IP %(fixed_address)s for instance %(id)s. '
'Error: %(error)s') % ({'address': address,
'fixed_address': fixed_address,
'id': instance.uuid, 'error': e})
LOG.exception(msg)
raise wsme.exc.ClientSideError(
msg, status_code=http_client.BAD_REQUEST)
# Set the HTTP Location Header, user can get the floating ips
# by locaton.
url_args = '/'.join([instance_uuid, 'networks'])
pecan.response.location = link.build_url('instances', url_args)
return FloatingIP(id=fip['id'], port_id=fip['port_id'])
class InstanceNetworks(base.APIBase):
"""API representation of the networks of an instance."""
@ -166,6 +289,9 @@ class InstanceNetworks(base.APIBase):
class InstanceNetworksController(rest.RestController):
"""REST controller for Instance networks."""
floatingip = FloatingIPController()
"""Expose floatingip as a sub-element of networks"""
_resource = None
# This _resource is used for authorization.
@ -370,7 +496,7 @@ class InstanceController(rest.RestController):
:param instance: a instance within the request body.
"""
# Add jsonschema validate
_check_create_body(instance)
_check_create_body(instance, _CREATE_INSTANCE_SCHEMA)
requested_networks = instance.pop('networks', None)
instance_type_uuid = instance.get('instance_type_uuid')
@ -455,17 +581,17 @@ class InstanceController(rest.RestController):
pecan.request.engine_api.delete(pecan.request.context, rpc_instance)
def _check_create_body(body):
def _check_create_body(body, schema):
"""Ensure all necessary keys are present and correct in create body.
Check that the user-specified create body is in the expected format and
include the required information.
:param body: create instance body
:param body: create body
:raises: InvalidParameterValue if validation of create body fails.
"""
try:
jsonschema.validate(body, _CREATE_INSTANCE_SCHEMA)
jsonschema.validate(body, schema)
except jsonschema.ValidationError as exc:
raise exception.InvalidParameterValue(_('Invalid create body: %s') %
exc)

View File

@ -97,6 +97,11 @@ class OperationNotPermitted(NotAuthorized):
_msg_fmt = _("Operation not permitted.")
class Forbidden(MoganException):
msg_fmt = _("Forbidden")
code = 403
class HTTPForbidden(NotAuthorized):
_msg_fmt = _("Access was denied to the following resource: %(resource)s")
@ -265,4 +270,12 @@ class DuplicateState(Conflict):
_msg_fmt = _("Resource already exists.")
class FloatingIpNotFoundForAddress(NotFound):
msg_fmt = _("Floating IP not found for address %(address)s.")
class FloatingIpMultipleFoundForAddress(MoganException):
msg_fmt = _("Multiple floating IPs are found for address %(address)s.")
ObjectActionError = obj_exc.ObjectActionError

View File

@ -97,6 +97,9 @@ instance_policies = [
policy.RuleDefault('mogan:instance:get_networks',
'rule:default',
description='Get Instance network information'),
policy.RuleDefault('mogan:instance:associate_floatingip',
'rule:default',
description='Associate a floating ip with an instance'),
]

View File

@ -13,9 +13,10 @@
from neutronclient.common import exceptions as neutron_exceptions
from neutronclient.v2_0 import client as clientv20
from oslo_log import log as logging
from oslo_utils import excutils
from mogan.common import exception
from mogan.common.i18n import _
from mogan.common.i18n import _, _LE
from mogan.common import keystone
from mogan.conf import CONF
@ -94,3 +95,43 @@ class API(object):
{'vif': port_id, 'instance': instance_uuid, 'exc': e})
LOG.exception(msg)
raise exception.NetworkError(msg)
def _safe_get_floating_ips(self, client, **kwargs):
"""Get floating IP gracefully handling 404 from Neutron."""
try:
return client.list_floatingips(**kwargs)['floatingips']
# If a neutron plugin does not implement the L3 API a 404 from
# list_floatingips will be raised.
except neutron_exceptions.NotFound:
return []
except neutron_exceptions.NeutronClientException as e:
# bug/1513879 neutron client is currently using
# NeutronClientException when there is no L3 API
if e.status_code == 404:
return []
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Unable to access floating IP for %s'),
', '.join(['%s %s' % (k, v)
for k, v in kwargs.items()]))
def _get_floating_ip_by_address(self, client, address):
"""Get floating IP from floating IP address."""
if not address:
raise exception.FloatingIpNotFoundForAddress(address=address)
fips = self._safe_get_floating_ips(client, floating_ip_address=address)
if len(fips) == 0:
raise exception.FloatingIpNotFoundForAddress(address=address)
elif len(fips) > 1:
raise exception.FloatingIpMultipleFoundForAddress(address=address)
return fips[0]
def associate_floating_ip(self, context, floating_address,
port_id, fixed_address):
"""Associate a floating IP with a fixed IP."""
client = get_client(context.auth_token)
fip = self._get_floating_ip_by_address(client, floating_address)
param = {'port_id': port_id,
'fixed_ip_address': fixed_address}
client.update_floatingip(fip['id'], {'floatingip': param})
return fip