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:
parent
e562918d4f
commit
99abbe590a
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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."))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue