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:
|
network_uuid:
|
||||||
description: |
|
description: |
|
||||||
To provision the server with a NIC for a network, specify the UUID of
|
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
|
in: body
|
||||||
required: true
|
required: false
|
||||||
type: string
|
type: string
|
||||||
networks:
|
networks:
|
||||||
description: |
|
description: |
|
||||||
A list of networks of the tenant. Optionally, you can create one or more NICs on the server.
|
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
|
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.
|
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
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: array
|
type: array
|
||||||
|
@ -367,6 +370,13 @@ personality:
|
||||||
in: body
|
in: body
|
||||||
required: false
|
required: false
|
||||||
type: string
|
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:
|
power_state:
|
||||||
description: |
|
description: |
|
||||||
The current power state of this Server. Usually, "power on" or
|
The current power state of this Server. Usually, "power on" or
|
||||||
|
|
|
@ -38,6 +38,7 @@ Request
|
||||||
- networks: networks
|
- networks: networks
|
||||||
- networks.net_id: network_uuid
|
- networks.net_id: network_uuid
|
||||||
- networks.port_type: network_port_type
|
- networks.port_type: network_port_type
|
||||||
|
- networks.port_id: port_uuid
|
||||||
- user_data: user_data
|
- user_data: user_data
|
||||||
- personality: personality
|
- personality: personality
|
||||||
- key_name: key_name
|
- key_name: key_name
|
||||||
|
|
|
@ -32,8 +32,12 @@ create_server = {
|
||||||
'properties': {
|
'properties': {
|
||||||
'net_id': parameter_types.network_id,
|
'net_id': parameter_types.network_id,
|
||||||
'port_type': parameter_types.port_type,
|
'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,
|
'additionalProperties': False,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -681,9 +681,13 @@ class ServerController(ServerControllerBase):
|
||||||
exception.ServerUserDataTooLarge,
|
exception.ServerUserDataTooLarge,
|
||||||
exception.Base64Exception,
|
exception.Base64Exception,
|
||||||
exception.NetworkRequiresSubnet,
|
exception.NetworkRequiresSubnet,
|
||||||
exception.NetworkNotFound) as e:
|
exception.NetworkNotFound,
|
||||||
|
exception.PortRequiresFixedIP) as e:
|
||||||
raise wsme.exc.ClientSideError(
|
raise wsme.exc.ClientSideError(
|
||||||
e.message, status_code=http_client.BAD_REQUEST)
|
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.
|
# Set the HTTP Location Header for the first server.
|
||||||
pecan.response.location = link.build_url('server', servers[0].uuid)
|
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.")
|
_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):
|
class InterfaceAttachFailed(Invalid):
|
||||||
msg_fmt = _("Failed to attach network adapter device to "
|
msg_fmt = _("Failed to attach network adapter device to "
|
||||||
"%(server_uuid)s")
|
"%(server_uuid)s")
|
||||||
|
|
|
@ -160,9 +160,13 @@ class BuildNetworkTask(flow_utils.MoganTask):
|
||||||
# Match the specified port type with physical interface type
|
# Match the specified port type with physical interface type
|
||||||
if vif.get('port_type', 'None') == pif.port_type:
|
if vif.get('port_type', 'None') == pif.port_type:
|
||||||
try:
|
try:
|
||||||
port = self.manager.network_api.create_port(
|
if vif.get('net_id'):
|
||||||
context, vif['net_id'], pif.address, server.uuid)
|
port = self.manager.network_api.create_port(
|
||||||
port_dict = port['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,
|
self.manager.driver.plug_vif(pif.port_uuid,
|
||||||
port_dict['id'])
|
port_dict['id'])
|
||||||
|
@ -179,8 +183,8 @@ class BuildNetworkTask(flow_utils.MoganTask):
|
||||||
# Set nics here, so we can clean up the
|
# Set nics here, so we can clean up the
|
||||||
# created networks during reverting.
|
# created networks during reverting.
|
||||||
server.nics = nics_obj
|
server.nics = nics_obj
|
||||||
LOG.error("Server %(server)s: create network failed. "
|
LOG.error("Server %(server)s: create or get network "
|
||||||
"The reason is %(reason)s",
|
"failed. The reason is %(reason)s",
|
||||||
{"server": server.uuid, "reason": e})
|
{"server": server.uuid, "reason": e})
|
||||||
raise exception.NetworkError(_(
|
raise exception.NetworkError(_(
|
||||||
"Build network for server failed."))
|
"Build network for server failed."))
|
||||||
|
|
|
@ -84,6 +84,10 @@ class API(object):
|
||||||
raise exception.NetworkError(msg)
|
raise exception.NetworkError(msg)
|
||||||
return port
|
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):
|
def _show_port(self, client, port_id):
|
||||||
"""Return the port for the client given the port id."""
|
"""Return the port for the client given the port id."""
|
||||||
|
|
||||||
|
@ -201,13 +205,30 @@ class API(object):
|
||||||
|
|
||||||
return nets
|
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):
|
def _ports_needed_per_server(self, client, requested_networks):
|
||||||
|
|
||||||
ports_needed_per_server = 0
|
ports_needed_per_server = 0
|
||||||
net_ids_requested = []
|
net_ids_requested = []
|
||||||
|
port_ids_requested = []
|
||||||
for request in requested_networks:
|
for request in requested_networks:
|
||||||
ports_needed_per_server += 1
|
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
|
# Now check to see if all requested networks exist
|
||||||
if net_ids_requested:
|
if net_ids_requested:
|
||||||
|
@ -228,6 +249,27 @@ class API(object):
|
||||||
id_str = id_str and id_str + ', ' + _id or _id
|
id_str = id_str and id_str + ', ' + _id or _id
|
||||||
raise exception.NetworkNotFound(network_id=id_str)
|
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
|
return ports_needed_per_server
|
||||||
|
|
||||||
def validate_networks(self, context, requested_networks, num_servers):
|
def validate_networks(self, context, requested_networks, num_servers):
|
||||||
|
|
|
@ -70,6 +70,65 @@ class TestServerAuthorization(v1_test.APITestV1):
|
||||||
headers = self.gen_headers(self.context)
|
headers = self.gen_headers(self.context)
|
||||||
self.post_json('/servers', body, headers=headers, status=201)
|
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):
|
def test_server_get_one_by_owner(self):
|
||||||
# not admin but the owner
|
# not admin but the owner
|
||||||
self.context.tenant = self.server1.project_id
|
self.context.tenant = self.server1.project_id
|
||||||
|
|
|
@ -92,6 +92,27 @@ class ComputeAPIUnitTest(base.DbTestCase):
|
||||||
None,
|
None,
|
||||||
1)
|
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')
|
@mock.patch.object(objects.Server, 'create')
|
||||||
def test__provision_servers(self, mock_server_create):
|
def test__provision_servers(self, mock_server_create):
|
||||||
mock_server_create.return_value = mock.MagicMock()
|
mock_server_create.return_value = mock.MagicMock()
|
||||||
|
|
Loading…
Reference in New Issue