Specify neutron port when creating servers

Now Mogan can support network_ids in request of creation servers.

This feature introduces the support for specifying the port_ids
in request body too.

Change-Id: I7f25a97da6e45f06f9ef2def68eeaf294659a932
Implements: bp specify-neutron-port-when-creating-instances
This commit is contained in:
wanghao 2017-06-05 11:09:12 +08:00
parent e562918d4f
commit 99abbe590a
9 changed files with 164 additions and 11 deletions

View File

@ -333,16 +333,19 @@ network_port_type:
network_uuid:
description: |
To provision the server with a NIC for a network, specify the UUID of
the network in the ``net_id`` key in a dict in ``networks`` list.
the network with the ``net_id`` key in a dict in ``networks`` list.
in: body
required: true
required: false
type: string
networks:
description: |
A list of networks of the tenant. Optionally, you can create one or more NICs on the server.
To provision the server with a NIC for a network, specify the UUID of the network
in the ``net_id`` key in a dict in ``networks`` list. To provision the server with a
with the ``net_id`` key in a dict in ``networks`` list. To provision the server with a
specified type of NIC, specify the port-type key in a dict in a ``networks`` list.
To provision the server with a NIC for an already existing port, specify the port_id in
a ``networks`` list. Now net_id and port_id are exclusive, so you should use only one of
them at one time.
in: body
required: true
type: array
@ -367,6 +370,13 @@ personality:
in: body
required: false
type: string
port_uuid:
description: |
To provision the server with a NIC for an already existing port,
specify the port_id in a ``networks`` list.
in: body
required: false
type: string
power_state:
description: |
The current power state of this Server. Usually, "power on" or

View File

@ -38,6 +38,7 @@ Request
- networks: networks
- networks.net_id: network_uuid
- networks.port_type: network_port_type
- networks.port_id: port_uuid
- user_data: user_data
- personality: personality
- key_name: key_name

View File

@ -32,8 +32,12 @@ create_server = {
'properties': {
'net_id': parameter_types.network_id,
'port_type': parameter_types.port_type,
'port_id': parameter_types.network_port_id,
},
'required': ['net_id'],
'oneOf': [
{'required': ['net_id']},
{'required': ['port_id']}
],
'additionalProperties': False,
},
},

View File

@ -681,9 +681,13 @@ class ServerController(ServerControllerBase):
exception.ServerUserDataTooLarge,
exception.Base64Exception,
exception.NetworkRequiresSubnet,
exception.NetworkNotFound) as e:
exception.NetworkNotFound,
exception.PortRequiresFixedIP) as e:
raise wsme.exc.ClientSideError(
e.message, status_code=http_client.BAD_REQUEST)
except exception.PortInUse as e:
raise wsme.exc.ClientSideError(
e.message, status_code=http_client.CONFLICT)
# Set the HTTP Location Header for the first server.
pecan.response.location = link.build_url('server', servers[0].uuid)

View File

@ -297,6 +297,14 @@ class PortNotFound(NotFound):
_msg_fmt = _("Port id %(port_id)s could not be found.")
class PortRequiresFixedIP(Invalid):
msg_fmt = _("Port %(port_id)s requires a FixedIP in order to be used.")
class PortInUse(Conflict):
msg_fmt = _("Port %(port_id)s is still in use.")
class InterfaceAttachFailed(Invalid):
msg_fmt = _("Failed to attach network adapter device to "
"%(server_uuid)s")

View File

@ -160,9 +160,13 @@ class BuildNetworkTask(flow_utils.MoganTask):
# Match the specified port type with physical interface type
if vif.get('port_type', 'None') == pif.port_type:
try:
port = self.manager.network_api.create_port(
context, vif['net_id'], pif.address, server.uuid)
port_dict = port['port']
if vif.get('net_id'):
port = self.manager.network_api.create_port(
context, vif['net_id'], pif.address, server.uuid)
port_dict = port['port']
elif vif.get('port_id'):
port_dict = self.manager.network_api.show_port(
context, vif.get('port_id'))
self.manager.driver.plug_vif(pif.port_uuid,
port_dict['id'])
@ -179,8 +183,8 @@ class BuildNetworkTask(flow_utils.MoganTask):
# Set nics here, so we can clean up the
# created networks during reverting.
server.nics = nics_obj
LOG.error("Server %(server)s: create network failed. "
"The reason is %(reason)s",
LOG.error("Server %(server)s: create or get network "
"failed. The reason is %(reason)s",
{"server": server.uuid, "reason": e})
raise exception.NetworkError(_(
"Build network for server failed."))

View File

@ -84,6 +84,10 @@ class API(object):
raise exception.NetworkError(msg)
return port
def show_port(self, context, port_uuid):
client = get_client(context.auth_token)
return self._show_port(client, port_uuid)
def _show_port(self, client, port_id):
"""Return the port for the client given the port id."""
@ -201,13 +205,30 @@ class API(object):
return nets
def _get_available_ports(self, port_ids, client):
"""Return a port list available for the tenant."""
search_opts = {'id': port_ids}
ports = client.list_ports(**search_opts).get('ports', [])
_ensure_requested_network_ordering(
lambda x: x['id'],
ports,
port_ids)
return ports
def _ports_needed_per_server(self, client, requested_networks):
ports_needed_per_server = 0
net_ids_requested = []
port_ids_requested = []
for request in requested_networks:
ports_needed_per_server += 1
net_ids_requested.append(request['net_id'])
if request.get('net_id'):
net_ids_requested.append(request.get('net_id'))
if request.get('port_id'):
port_ids_requested.append(request.get('port_id'))
# Now check to see if all requested networks exist
if net_ids_requested:
@ -228,6 +249,27 @@ class API(object):
id_str = id_str and id_str + ', ' + _id or _id
raise exception.NetworkNotFound(network_id=id_str)
# Now check to see if all requested ports exist
if port_ids_requested:
ports = self._get_available_ports(port_ids_requested, client)
if len(ports) != len(port_ids_requested):
requested_portid_set = set(port_ids_requested)
returned_portid_set = set([port['id'] for port in ports])
lostid_set = requested_portid_set - returned_portid_set
if lostid_set:
id_str = ''
for _id in lostid_set:
id_str = id_str and id_str + ', ' + _id or _id
raise exception.PortNotFound(port_id=id_str)
# Check if those ports are used and have FixedIP now.
for port in ports:
if port.get('device_id', None):
raise exception.PortInUse(port_id=request.port_id)
deferred_ip = port.get('ip_allocation') == 'deferred'
if not deferred_ip and not port.get('fixed_ips'):
raise exception.PortRequiresFixedIP(port_id=port['id'])
return ports_needed_per_server
def validate_networks(self, context, requested_networks, num_servers):

View File

@ -70,6 +70,65 @@ class TestServerAuthorization(v1_test.APITestV1):
headers = self.gen_headers(self.context)
self.post_json('/servers', body, headers=headers, status=201)
@mock.patch('mogan.engine.api.API.create')
@mock.patch('mogan.objects.Flavor.get')
def test_server_post_with_port_ids(self, mock_get, mock_engine_create):
flavor = mock.MagicMock()
flavor.nics = [{"type": "Ethernet", "speed": "10GE"},
{"type": "Ethernet", "speed": "10GE"}]
mock_get.return_value = flavor
mock_engine_create.side_effect = None
mock_engine_create.return_value = [self.server1]
fake_networks = [
{
"port_id": "c1940655-8b8e-4370-b8f9-03ba1daeca31",
"port_type": "Ethernet"
},
{
"port_id": "8e8ceb07-4641-4188-9b22-840755e92ee2",
"port_type": "Ethernet"
}
]
body = gen_post_body(**{'networks': fake_networks})
self.context.roles = "no-admin"
# we can not prevent the evil tenant, quota will limite him.
# Note(Shaohe): quota is in plan
self.context.tenant = self.evil_project
headers = self.gen_headers(self.context)
self.post_json('/servers', body, headers=headers, status=201)
@mock.patch('mogan.engine.api.API.create')
@mock.patch('mogan.objects.Flavor.get')
def test_server_post_with_port_ids_and_networks(self, mock_get,
mock_engine_create):
flavor = mock.MagicMock()
flavor.nics = [{"type": "Ethernet", "speed": "10GE"},
{"type": "Ethernet", "speed": "10GE"}]
mock_get.return_value = flavor
mock_engine_create.side_effect = None
mock_engine_create.return_value = [self.server1]
fake_networks = [
{
"port_id": "c1940655-8b8e-4370-b8f9-03ba1daeca31",
"net_id": "c1940655-8b8e-4370-b8f9-03ba1daeca32",
"port_type": "Ethernet"
},
{
"port_id": "8e8ceb07-4641-4188-9b22-840755e92ee2",
"net_id": "8e8ceb07-4641-4188-9b22-840755e92ee3",
"port_type": "Ethernet"
}
]
body = gen_post_body(**{'networks': fake_networks})
self.context.roles = "no-admin"
# we can not prevent the evil tenant, quota will limite him.
# Note(Shaohe): quota is in plan
self.context.tenant = self.evil_project
headers = self.gen_headers(self.context)
ret = self.post_json('/servers', body, headers=headers,
expect_errors=True)
self.assertTrue(ret.json['error_message'])
def test_server_get_one_by_owner(self):
# not admin but the owner
self.context.tenant = self.server1.project_id

View File

@ -92,6 +92,27 @@ class ComputeAPIUnitTest(base.DbTestCase):
None,
1)
@mock.patch('mogan.network.api.get_client')
def test__check_requested_networks(self, mock_get_client):
mock_get_client.return_value = mock.MagicMock()
mock_get_client.return_value.list_networks.return_value = \
{'networks': [{'id': '1', 'subnets': {'id': '2'}},
{'id': '3', 'subnets': {'id': '4'}}]}
mock_get_client.return_value.list_ports.return_value = \
{'ports': [{'id': '5',
'fixed_ips': [{'ip_address': '192.168.1.1'}]},
{'id': '6',
'fixed_ips': [{'ip_address': '192.168.1.2'}]}]}
mock_get_client.return_value.show_quota.return_value = \
{'quota': {'port': 10}}
requested_networks = [{'net_id': '1'}, {'net_id': '3'},
{'port_id': '5'}, {'port_id': '6'}]
max_network_count = self.engine_api._check_requested_networks(
self.context, requested_networks=requested_networks, max_count=2)
self.assertEqual(2, max_network_count)
@mock.patch.object(objects.Server, 'create')
def test__provision_servers(self, mock_server_create):
mock_server_create.return_value = mock.MagicMock()