Attach interface for server

Partially Implements: bp attach-detach-interface

Change-Id: I3c852428676c19ebaeee1954c1a78e77a61eb014
This commit is contained in:
Zhong Luyao 2017-04-25 19:03:22 +08:00
parent 6e2783700e
commit fea6689e74
11 changed files with 189 additions and 0 deletions

View File

@ -0,0 +1,3 @@
{
"net_id": "a19ffb45-8207-4771-b0ef-f63c91fb46d6"
}

View File

@ -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.

View File

@ -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
}

View File

@ -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."""
@ -343,6 +382,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)

View File

@ -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.")

View File

@ -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'),

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -551,3 +551,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)

View File

@ -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)