Merge "Attach interface for server"
This commit is contained in:
commit
896ef30bf0
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"net_id": "a19ffb45-8207-4771-b0ef-f63c91fb46d6"
|
||||
}
|
|
@ -97,3 +97,32 @@ Response
|
|||
--------
|
||||
|
||||
If successful, this method does not return content in the response body.
|
||||
|
||||
|
||||
Add (Associate) Interface
|
||||
=========================
|
||||
|
||||
.. rest_method:: POST /v1/servers/{server_uuid}/networks/interfaces
|
||||
|
||||
Adds an interface to a server.
|
||||
|
||||
Normal response code: 204
|
||||
|
||||
Error codes: 400,401,403,404,409
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- server_uuid: server_ident
|
||||
- net_id: network_uuid
|
||||
|
||||
**Example request to Add (Associate) Interface to a server:**
|
||||
|
||||
.. literalinclude:: samples/server_networks/server-attach-interface-req.json
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
If successful, this method does not return content in the response body.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2017 Intel
|
||||
# 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.
|
||||
|
||||
|
||||
from mogan.api.validation import parameter_types
|
||||
|
||||
|
||||
attach_interface = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'net_id': parameter_types.network_id,
|
||||
},
|
||||
'required': ['net_id'],
|
||||
'additionalProperties': False
|
||||
}
|
|
@ -20,12 +20,14 @@ from oslo_utils import netutils
|
|||
import pecan
|
||||
from pecan import rest
|
||||
from six.moves import http_client
|
||||
from webob import exc
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from mogan.api.controllers import base
|
||||
from mogan.api.controllers import link
|
||||
from mogan.api.controllers.v1.schemas import floating_ips as fip_schemas
|
||||
from mogan.api.controllers.v1.schemas import interfaces as interface_schemas
|
||||
from mogan.api.controllers.v1.schemas import servers as server_schemas
|
||||
from mogan.api.controllers.v1 import types
|
||||
from mogan.api.controllers.v1 import utils as api_utils
|
||||
|
@ -35,6 +37,7 @@ from mogan.common import exception
|
|||
from mogan.common.i18n import _
|
||||
from mogan.common import policy
|
||||
from mogan.common import states
|
||||
from mogan.common import utils
|
||||
from mogan import network
|
||||
from mogan import objects
|
||||
|
||||
|
@ -331,6 +334,42 @@ class FloatingIPController(ServerControllerBase):
|
|||
msg, status_code=http_client.BAD_REQUEST)
|
||||
|
||||
|
||||
class InterfaceController(ServerControllerBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InterfaceController, self).__init__(*args, **kwargs)
|
||||
|
||||
@policy.authorize_wsgi("mogan:server", "attach_interface", False)
|
||||
@expose.expose(None, types.uuid, body=types.jsontype,
|
||||
status_code=http_client.NO_CONTENT)
|
||||
def post(self, server_uuid, interface):
|
||||
"""Attach Interface.
|
||||
|
||||
:param server_uuid: UUID of a server.
|
||||
:param interface: The Baremetal Network ID within the request body.
|
||||
"""
|
||||
validation.check_schema(interface, interface_schemas.attach_interface)
|
||||
|
||||
net_id = interface.get('net_id', None)
|
||||
|
||||
if not net_id:
|
||||
msg = _("Must input network_id")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
server = self._resource or self._get_resource(server_uuid)
|
||||
try:
|
||||
pecan.request.engine_api.attach_interface(
|
||||
pecan.request.context,
|
||||
server, net_id)
|
||||
except (exception.ServerIsLocked,
|
||||
exception.ComputePortInUse,
|
||||
exception.NetworkNotFound) as e:
|
||||
raise wsme.exc.ClientSideError(
|
||||
e.message, status_code=http_client.BAD_REQUEST)
|
||||
except exception.InterfaceAttachFailed as state_error:
|
||||
utils.raise_http_conflict_for_server_invalid_state(
|
||||
state_error, 'attach_interface', server_uuid)
|
||||
|
||||
|
||||
class ServerNetworks(base.APIBase):
|
||||
"""API representation of the networks of a server."""
|
||||
|
||||
|
@ -348,6 +387,8 @@ class ServerNetworksController(ServerControllerBase):
|
|||
|
||||
floatingips = FloatingIPController()
|
||||
"""Expose floatingip as a sub-element of networks"""
|
||||
interfaces = InterfaceController()
|
||||
"""Expose interface as a sub-element of networks"""
|
||||
|
||||
@policy.authorize_wsgi("mogan:server", "get_networks")
|
||||
@expose.expose(ServerNetworks, types.uuid)
|
||||
|
|
|
@ -190,6 +190,10 @@ class ComputePortNotFound(NotFound):
|
|||
_msg_fmt = _("ComputePort %(port)s could not be found.")
|
||||
|
||||
|
||||
class ComputePortInUse(Invalid):
|
||||
_msg_fmt = _("ComputePort id %(port)s is in use.")
|
||||
|
||||
|
||||
class ComputeDiskAlreadyExists(MoganException):
|
||||
_msg_fmt = _("ComputeDisk with disk_uuid %(disk)s already exists.")
|
||||
|
||||
|
@ -311,6 +315,11 @@ class PortNotFound(NotFound):
|
|||
_msg_fmt = _("Port id %(port_id)s could not be found.")
|
||||
|
||||
|
||||
class InterfaceAttachFailed(Invalid):
|
||||
msg_fmt = _("Failed to attach network adapter device to "
|
||||
"%(server_uuid)s")
|
||||
|
||||
|
||||
class FloatingIpNotFoundForAddress(NotFound):
|
||||
_msg_fmt = _("Floating IP not found for address %(address)s.")
|
||||
|
||||
|
|
|
@ -105,6 +105,9 @@ server_policies = [
|
|||
policy.RuleDefault('mogan:server:set_lock_state',
|
||||
'rule:default',
|
||||
description='Lock/UnLock a server'),
|
||||
policy.RuleDefault('mogan:server:attach_interface',
|
||||
'rule:default',
|
||||
description='Attach an interface for an server'),
|
||||
policy.RuleDefault('mogan:server:set_provision_state',
|
||||
'rule:default',
|
||||
description='Set the provision state of a server'),
|
||||
|
|
|
@ -25,6 +25,7 @@ import re
|
|||
import shutil
|
||||
import tempfile
|
||||
import traceback
|
||||
import webob
|
||||
|
||||
from cryptography.hazmat import backends
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
@ -409,3 +410,22 @@ def generate_key_pair(bits=2048):
|
|||
key.get_name(), key.get_base64())
|
||||
fingerprint = generate_fingerprint(public_key)
|
||||
return (private_key, public_key, fingerprint)
|
||||
|
||||
|
||||
def raise_http_conflict_for_server_invalid_state(exc, action, server_id):
|
||||
"""Raises a webob.exc.HTTPConflict instance containing a message
|
||||
appropriate to return via the API based on the original
|
||||
ServerInvalidState exception.
|
||||
"""
|
||||
attr = exc.kwargs.get('attr')
|
||||
state = exc.kwargs.get('state')
|
||||
if attr is not None and state is not None:
|
||||
msg = _("Cannot '%(action)s' server %(server_id)s while it is in "
|
||||
"%(attr)s %(state)s") % {'action': action, 'attr': attr,
|
||||
'state': state,
|
||||
'server_id': server_id}
|
||||
else:
|
||||
# At least give some meaningful message
|
||||
msg = _("Server %(server_id)s is in an invalid state for "
|
||||
"'%(action)s'") % {'action': action, 'server_id': server_id}
|
||||
raise webob.exc.HTTPConflict(explanation=msg)
|
||||
|
|
|
@ -512,3 +512,6 @@ class API(object):
|
|||
def get_key_pair(self, context, user_id, key_name):
|
||||
"""Get a keypair by name."""
|
||||
return objects.KeyPair.get_by_name(context, user_id, key_name)
|
||||
|
||||
def attach_interface(self, context, server, net_id):
|
||||
self.engine_rpcapi.attach_interface(context, server, net_id)
|
||||
|
|
|
@ -87,6 +87,14 @@ class IronicDriver(base_driver.BaseEngineDriver):
|
|||
return self.ironicclient.call('node.get', node_uuid,
|
||||
fields=_NODE_FIELDS)
|
||||
|
||||
def get_node_by_server_uuid(self, server_uuid):
|
||||
"""Get the node by server uuid"""
|
||||
try:
|
||||
return self.ironicclient.call('node.get_by_instance_uuid',
|
||||
server_uuid, fields=_NODE_FIELDS)
|
||||
except ironic_exc.NotFound:
|
||||
raise exception.ServerNotFound(server=server_uuid)
|
||||
|
||||
def _validate_server_and_node(self, server):
|
||||
"""Get the node associated with the server.
|
||||
|
||||
|
|
|
@ -552,3 +552,44 @@ class EngineManager(base_manager.BaseEngineManager):
|
|||
'host': parsed_url.hostname,
|
||||
'port': parsed_url.port,
|
||||
'internal_access_path': None}
|
||||
|
||||
def _choose_pif_from_node(self, context, node):
|
||||
pifs = self.driver.get_ports_from_node(node.uuid, detail=True)
|
||||
pif_ids = []
|
||||
for pif in pifs:
|
||||
pif_ids.append(pif.uuid)
|
||||
vif = pif.extra.get('vif_port_id', None)
|
||||
if not vif:
|
||||
return pif
|
||||
if not pif_ids:
|
||||
LOG.debug("Node %(node.uuid)s has no pysical ports. ")
|
||||
else:
|
||||
raise exception.ComputePortInUse(port=pif_ids)
|
||||
|
||||
def _check_server_state(self, context, server):
|
||||
if server.locked and not context.is_admin:
|
||||
raise exception.ServerIsLocked(server_uuid=server.uuid)
|
||||
|
||||
def attach_interface(self, context, server, net_id=None):
|
||||
self._check_server_state(context, server)
|
||||
node = self.driver.get_node_by_server_uuid(server.uuid)
|
||||
pif = self._choose_pif_from_node(context, node)
|
||||
mac = pif.address
|
||||
vif = self.network_api.create_port(context, net_id, mac, server.uuid)
|
||||
vif_dict = vif['port']
|
||||
try:
|
||||
self.driver.plug_vif(pif.uuid, vif_dict['id'])
|
||||
|
||||
nics_obj = objects.ServerNics(context)
|
||||
nic_dict = {'port_id': vif_dict['id'],
|
||||
'network_id': vif_dict['network_id'],
|
||||
'mac_address': vif_dict['mac_address'],
|
||||
'fixed_ips': vif_dict['fixed_ips'],
|
||||
'port_type': vif_dict.get('port_type'),
|
||||
'server_uuid': server.uuid}
|
||||
nics_obj.objects.append(objects.ServerNic(
|
||||
context, **nic_dict))
|
||||
server.nics = nics_obj
|
||||
server.save()
|
||||
except Exception as e:
|
||||
raise exception.InterfaceAttachFailed(message=e.message)
|
||||
|
|
|
@ -82,3 +82,8 @@ class EngineAPI(object):
|
|||
cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
|
||||
return cctxt.call(context, 'get_serial_console',
|
||||
server=server)
|
||||
|
||||
def attach_interface(self, context, server, net_id):
|
||||
cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
|
||||
cctxt.call(context, 'attach_interface',
|
||||
server=server, net_id=net_id)
|
||||
|
|
Loading…
Reference in New Issue