Merge "Supports string values 'none' and 'auto' for networks"
This commit is contained in:
commit
a8484dd44d
@ -530,3 +530,7 @@ class InvalidServiceVersion(HeatException):
|
|||||||
class InvalidTemplateVersions(HeatException):
|
class InvalidTemplateVersions(HeatException):
|
||||||
msg_fmt = _('A template version alias %(version)s was added for a '
|
msg_fmt = _('A template version alias %(version)s was added for a '
|
||||||
'template class that has no official YYYY-MM-DD version.')
|
'template class that has no official YYYY-MM-DD version.')
|
||||||
|
|
||||||
|
|
||||||
|
class UnableToAutoAllocateNetwork(HeatException):
|
||||||
|
msg_fmt = _('Unable to automatically allocate a network: %(message)s')
|
||||||
|
@ -59,9 +59,9 @@ class NovaClientPlugin(client_plugin.ClientPlugin):
|
|||||||
NOVA_API_VERSION = '2.1'
|
NOVA_API_VERSION = '2.1'
|
||||||
|
|
||||||
validate_versions = [
|
validate_versions = [
|
||||||
V2_2, V2_8, V2_10, V2_15, V2_26
|
V2_2, V2_8, V2_10, V2_15, V2_26, V2_37
|
||||||
] = [
|
] = [
|
||||||
'2.2', '2.8', '2.10', '2.15', '2.26'
|
'2.2', '2.8', '2.10', '2.15', '2.26', '2.37'
|
||||||
]
|
]
|
||||||
|
|
||||||
supported_versions = [NOVA_API_VERSION] + validate_versions
|
supported_versions = [NOVA_API_VERSION] + validate_versions
|
||||||
|
@ -109,10 +109,12 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
|||||||
|
|
||||||
_NETWORK_KEYS = (
|
_NETWORK_KEYS = (
|
||||||
NETWORK_UUID, NETWORK_ID, NETWORK_FIXED_IP, NETWORK_PORT,
|
NETWORK_UUID, NETWORK_ID, NETWORK_FIXED_IP, NETWORK_PORT,
|
||||||
NETWORK_SUBNET, NETWORK_PORT_EXTRA, NETWORK_FLOATING_IP
|
NETWORK_SUBNET, NETWORK_PORT_EXTRA, NETWORK_FLOATING_IP,
|
||||||
|
ALLOCATE_NETWORK,
|
||||||
) = (
|
) = (
|
||||||
'uuid', 'network', 'fixed_ip', 'port',
|
'uuid', 'network', 'fixed_ip', 'port',
|
||||||
'subnet', 'port_extra_properties', 'floating_ip'
|
'subnet', 'port_extra_properties', 'floating_ip',
|
||||||
|
'allocate_network',
|
||||||
)
|
)
|
||||||
|
|
||||||
_SOFTWARE_CONFIG_FORMATS = (
|
_SOFTWARE_CONFIG_FORMATS = (
|
||||||
@ -127,6 +129,12 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
|||||||
'POLL_SERVER_CFN', 'POLL_SERVER_HEAT', 'POLL_TEMP_URL', 'ZAQAR_MESSAGE'
|
'POLL_SERVER_CFN', 'POLL_SERVER_HEAT', 'POLL_TEMP_URL', 'ZAQAR_MESSAGE'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_ALLOCATE_TYPES = (
|
||||||
|
NETWORK_NONE, NETWORK_AUTO,
|
||||||
|
) = (
|
||||||
|
'none', 'auto',
|
||||||
|
)
|
||||||
|
|
||||||
ATTRIBUTES = (
|
ATTRIBUTES = (
|
||||||
NAME_ATTR, ADDRESSES, NETWORKS_ATTR, FIRST_ADDRESS,
|
NAME_ATTR, ADDRESSES, NETWORKS_ATTR, FIRST_ADDRESS,
|
||||||
INSTANCE_NAME, ACCESSIPV4, ACCESSIPV6, CONSOLE_URLS, TAGS_ATTR
|
INSTANCE_NAME, ACCESSIPV4, ACCESSIPV6, CONSOLE_URLS, TAGS_ATTR
|
||||||
@ -405,6 +413,23 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
|||||||
constraints.CustomConstraint('neutron.network')
|
constraints.CustomConstraint('neutron.network')
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
ALLOCATE_NETWORK: properties.Schema(
|
||||||
|
properties.Schema.STRING,
|
||||||
|
_('The special string values of network, '
|
||||||
|
'auto: means either a network that is already '
|
||||||
|
'available to the project will be used, or if one '
|
||||||
|
'does not exist, will be automatically created for '
|
||||||
|
'the project; none: means no networking will be '
|
||||||
|
'allocated for the created server. Supported by '
|
||||||
|
'Nova API since version "2.37". This property can '
|
||||||
|
'not be used with other network keys.'),
|
||||||
|
support_status=support.SupportStatus(version='9.0.0'),
|
||||||
|
constraints=[
|
||||||
|
constraints.AllowedValues(
|
||||||
|
[NETWORK_NONE, NETWORK_AUTO])
|
||||||
|
],
|
||||||
|
update_allowed=True,
|
||||||
|
),
|
||||||
NETWORK_FIXED_IP: properties.Schema(
|
NETWORK_FIXED_IP: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Fixed IP address to specify for the port '
|
_('Fixed IP address to specify for the port '
|
||||||
@ -754,7 +779,15 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
|||||||
|
|
||||||
server = None
|
server = None
|
||||||
try:
|
try:
|
||||||
server = self.client().servers.create(
|
# if 'auto' or 'none' is specified, we get the string type
|
||||||
|
# nics after self._build_nics(), and the string network
|
||||||
|
# is supported since nova microversion 2.37
|
||||||
|
if isinstance(nics, six.string_types):
|
||||||
|
nc = self.client(version=self.client_plugin().V2_37)
|
||||||
|
else:
|
||||||
|
nc = self.client()
|
||||||
|
|
||||||
|
server = nc.servers.create(
|
||||||
name=self._server_name(),
|
name=self._server_name(),
|
||||||
image=image,
|
image=image,
|
||||||
flavor=flavor,
|
flavor=flavor,
|
||||||
@ -1370,16 +1403,38 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
|||||||
if image:
|
if image:
|
||||||
self._validate_image_flavor(image, flavor)
|
self._validate_image_flavor(image, flavor)
|
||||||
|
|
||||||
# network properties 'uuid' and 'network' shouldn't be used
|
|
||||||
# both at once for all networks
|
|
||||||
networks = self.properties[self.NETWORKS] or []
|
networks = self.properties[self.NETWORKS] or []
|
||||||
# record if any networks include explicit ports
|
|
||||||
networks_with_port = False
|
|
||||||
for network in networks:
|
for network in networks:
|
||||||
networks_with_port = (networks_with_port or
|
|
||||||
network.get(self.NETWORK_PORT) is not None)
|
|
||||||
self._validate_network(network)
|
self._validate_network(network)
|
||||||
|
|
||||||
|
has_str_net = self._str_network(networks) is not None
|
||||||
|
if has_str_net:
|
||||||
|
if len(networks) != 1:
|
||||||
|
msg = _('Property "%s" can not be specified if '
|
||||||
|
'multiple network interfaces set for '
|
||||||
|
'server.') % self.ALLOCATE_NETWORK
|
||||||
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
# Check if str_network is allowed to use
|
||||||
|
try:
|
||||||
|
self.client(
|
||||||
|
version=self.client_plugin().V2_37)
|
||||||
|
except exception.InvalidServiceVersion as ex:
|
||||||
|
msg = (_('Cannot use "%(prop)s" property - compute service '
|
||||||
|
'does not support the required api '
|
||||||
|
'microversion: %(ex)s')
|
||||||
|
% {'prop': self.ALLOCATE_NETWORK,
|
||||||
|
'ex': six.text_type(ex)})
|
||||||
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
|
||||||
|
# record if any networks include explicit ports
|
||||||
|
has_port = any(n[self.NETWORK_PORT] is not None for n in networks)
|
||||||
|
# if 'security_groups' present for the server and explicit 'port'
|
||||||
|
# in one or more entries in 'networks', raise validation error
|
||||||
|
if has_port and self.properties[self.SECURITY_GROUPS]:
|
||||||
|
raise exception.ResourcePropertyConflict(
|
||||||
|
self.SECURITY_GROUPS,
|
||||||
|
"/".join([self.NETWORKS, self.NETWORK_PORT]))
|
||||||
|
|
||||||
# Check if tags is allowed to use
|
# Check if tags is allowed to use
|
||||||
if self.properties[self.TAGS]:
|
if self.properties[self.TAGS]:
|
||||||
try:
|
try:
|
||||||
@ -1396,13 +1451,6 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
|||||||
if metadata or personality:
|
if metadata or personality:
|
||||||
limits = self.client_plugin().absolute_limits()
|
limits = self.client_plugin().absolute_limits()
|
||||||
|
|
||||||
# if 'security_groups' present for the server and explicit 'port'
|
|
||||||
# in one or more entries in 'networks', raise validation error
|
|
||||||
if networks_with_port and self.properties[self.SECURITY_GROUPS]:
|
|
||||||
raise exception.ResourcePropertyConflict(
|
|
||||||
self.SECURITY_GROUPS,
|
|
||||||
"/".join([self.NETWORKS, self.NETWORK_PORT]))
|
|
||||||
|
|
||||||
# verify that the number of metadata entries is not greater
|
# verify that the number of metadata entries is not greater
|
||||||
# than the maximum number allowed in the provider's absolute
|
# than the maximum number allowed in the provider's absolute
|
||||||
# limits
|
# limits
|
||||||
|
@ -35,16 +35,28 @@ class ServerNetworkMixin(object):
|
|||||||
subnet = network.get(self.NETWORK_SUBNET)
|
subnet = network.get(self.NETWORK_SUBNET)
|
||||||
fixed_ip = network.get(self.NETWORK_FIXED_IP)
|
fixed_ip = network.get(self.NETWORK_FIXED_IP)
|
||||||
floating_ip = network.get(self.NETWORK_FLOATING_IP)
|
floating_ip = network.get(self.NETWORK_FLOATING_IP)
|
||||||
|
str_network = network.get(self.ALLOCATE_NETWORK)
|
||||||
|
|
||||||
if net_id is None and port is None and subnet is None:
|
if (net_id is None and
|
||||||
msg = _('One of the properties "%(id)s", "%(port_id)s" '
|
port is None and
|
||||||
'or "%(subnet)s" should be set for the '
|
subnet is None and
|
||||||
|
not str_network):
|
||||||
|
msg = _('One of the properties "%(id)s", "%(port_id)s", '
|
||||||
|
'"%(str_network)s" or "%(subnet)s" should be set for the '
|
||||||
'specified network of server "%(server)s".'
|
'specified network of server "%(server)s".'
|
||||||
'') % dict(id=self.NETWORK_ID,
|
'') % dict(id=self.NETWORK_ID,
|
||||||
port_id=self.NETWORK_PORT,
|
port_id=self.NETWORK_PORT,
|
||||||
subnet=self.NETWORK_SUBNET,
|
subnet=self.NETWORK_SUBNET,
|
||||||
|
str_network=self.ALLOCATE_NETWORK,
|
||||||
server=self.name)
|
server=self.name)
|
||||||
raise exception.StackValidationFailed(message=msg)
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
# can not specify str_network with other keys of networks
|
||||||
|
# at the same time
|
||||||
|
has_value_keys = [k for k, v in network.items() if v is not None]
|
||||||
|
if str_network and len(has_value_keys) != 1:
|
||||||
|
msg = _('Can not specify "%s" with other keys of networks '
|
||||||
|
'at the same time.') % self.ALLOCATE_NETWORK
|
||||||
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
|
||||||
if port is not None and not self.is_using_neutron():
|
if port is not None and not self.is_using_neutron():
|
||||||
msg = _('Property "%s" is supported only for '
|
msg = _('Property "%s" is supported only for '
|
||||||
@ -214,6 +226,11 @@ class ServerNetworkMixin(object):
|
|||||||
def _build_nics(self, networks, security_groups=None):
|
def _build_nics(self, networks, security_groups=None):
|
||||||
if not networks:
|
if not networks:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
str_network = self._str_network(networks)
|
||||||
|
if str_network:
|
||||||
|
return str_network
|
||||||
|
|
||||||
nics = []
|
nics = []
|
||||||
|
|
||||||
for idx, net in enumerate(networks):
|
for idx, net in enumerate(networks):
|
||||||
@ -329,8 +346,42 @@ class ServerNetworkMixin(object):
|
|||||||
if net is not None:
|
if net is not None:
|
||||||
net['port'] = props['port']
|
net['port'] = props['port']
|
||||||
|
|
||||||
def calculate_networks(self, old_nets, new_nets, ifaces,
|
def _get_available_networks(self):
|
||||||
security_groups=None):
|
# first we get the private networks owned by the tenant
|
||||||
|
search_opts = {'tenant_id': self.context.tenant_id, 'shared': False,
|
||||||
|
'admin_state_up': True, }
|
||||||
|
nc = self.client('neutron')
|
||||||
|
nets = nc.list_networks(**search_opts).get('networks', [])
|
||||||
|
# second we get the public shared networks
|
||||||
|
search_opts = {'shared': True}
|
||||||
|
nets += nc.list_networks(**search_opts).get('networks', [])
|
||||||
|
|
||||||
|
ids = [net['id'] for net in nets]
|
||||||
|
|
||||||
|
return ids
|
||||||
|
|
||||||
|
def _auto_allocate_network(self):
|
||||||
|
topology = self.client('neutron').get_auto_allocated_topology(
|
||||||
|
self.context.tenant_id)['auto_allocated_topology']
|
||||||
|
|
||||||
|
return topology['id']
|
||||||
|
|
||||||
|
def _calculate_using_str_network(self, ifaces, str_net):
|
||||||
|
add_nets = []
|
||||||
|
remove_ports = [iface.port_id for iface in ifaces or []]
|
||||||
|
if str_net == self.NETWORK_AUTO:
|
||||||
|
nets = self._get_available_networks()
|
||||||
|
if not nets:
|
||||||
|
nets = [self._auto_allocate_network()]
|
||||||
|
if len(nets) > 1:
|
||||||
|
msg = 'Multiple possible networks found.'
|
||||||
|
raise exception.UnableToAutoAllocateNetwork(message=msg)
|
||||||
|
|
||||||
|
add_nets.append({'port_id': None, 'net_id': nets[0], 'fip': None})
|
||||||
|
return remove_ports, add_nets
|
||||||
|
|
||||||
|
def _calculate_using_list_networks(self, old_nets, new_nets, ifaces,
|
||||||
|
security_groups):
|
||||||
remove_ports = []
|
remove_ports = []
|
||||||
add_nets = []
|
add_nets = []
|
||||||
attach_first_free_port = False
|
attach_first_free_port = False
|
||||||
@ -351,33 +402,38 @@ class ServerNetworkMixin(object):
|
|||||||
# 3. detach unmatched networks, which were present in old_nets
|
# 3. detach unmatched networks, which were present in old_nets
|
||||||
# 4. attach unmatched networks, which were present in new_nets
|
# 4. attach unmatched networks, which were present in new_nets
|
||||||
else:
|
else:
|
||||||
# remove not updated networks from old and new networks lists,
|
# if old net is string net, remove the interfaces
|
||||||
# also get list these networks
|
if self._str_network(old_nets):
|
||||||
not_updated_nets = self._exclude_not_updated_networks(old_nets,
|
remove_ports = [iface.port_id for iface in ifaces or []]
|
||||||
new_nets)
|
else:
|
||||||
|
# remove not updated networks from old and new networks lists,
|
||||||
|
# also get list these networks
|
||||||
|
not_updated_nets = self._exclude_not_updated_networks(
|
||||||
|
old_nets,
|
||||||
|
new_nets)
|
||||||
|
|
||||||
self.update_networks_matching_iface_port(
|
self.update_networks_matching_iface_port(
|
||||||
old_nets + not_updated_nets, ifaces)
|
old_nets + not_updated_nets, ifaces)
|
||||||
|
|
||||||
# according to nova interface-detach command detached port
|
# according to nova interface-detach command detached port
|
||||||
# will be deleted
|
# will be deleted
|
||||||
inter_port_data = self._data_get_ports()
|
inter_port_data = self._data_get_ports()
|
||||||
inter_port_ids = [p['id'] for p in inter_port_data]
|
inter_port_ids = [p['id'] for p in inter_port_data]
|
||||||
for net in old_nets:
|
for net in old_nets:
|
||||||
port_id = net.get(self.NETWORK_PORT)
|
port_id = net.get(self.NETWORK_PORT)
|
||||||
# we can't match the port for some user case, like:
|
# we can't match the port for some user case, like:
|
||||||
# the internal port was detached in nova first, then
|
# the internal port was detached in nova first, then
|
||||||
# user update template to detach this nic. The internal
|
# user update template to detach this nic. The internal
|
||||||
# port will remains till we delete the server resource.
|
# port will remains till we delete the server resource.
|
||||||
if port_id:
|
if port_id:
|
||||||
remove_ports.append(port_id)
|
remove_ports.append(port_id)
|
||||||
if port_id in inter_port_ids:
|
if port_id in inter_port_ids:
|
||||||
# if we have internal port with such id, remove it
|
# if we have internal port with such id, remove it
|
||||||
# instantly.
|
# instantly.
|
||||||
self._delete_internal_port(port_id)
|
self._delete_internal_port(port_id)
|
||||||
if net.get(self.NETWORK_FLOATING_IP):
|
if net.get(self.NETWORK_FLOATING_IP):
|
||||||
self._floating_ip_disassociate(
|
self._floating_ip_disassociate(
|
||||||
net.get(self.NETWORK_FLOATING_IP))
|
net.get(self.NETWORK_FLOATING_IP))
|
||||||
|
|
||||||
handler_kwargs = {'port_id': None, 'net_id': None, 'fip': None}
|
handler_kwargs = {'port_id': None, 'net_id': None, 'fip': None}
|
||||||
# if new_nets is None, we should attach first free port,
|
# if new_nets is None, we should attach first free port,
|
||||||
@ -415,6 +471,23 @@ class ServerNetworkMixin(object):
|
|||||||
|
|
||||||
return remove_ports, add_nets
|
return remove_ports, add_nets
|
||||||
|
|
||||||
|
def _str_network(self, networks):
|
||||||
|
# if user specify 'allocate_network', return it
|
||||||
|
# otherwise we return None
|
||||||
|
for net in networks or []:
|
||||||
|
str_net = net.get(self.ALLOCATE_NETWORK)
|
||||||
|
if str_net:
|
||||||
|
return str_net
|
||||||
|
|
||||||
|
def calculate_networks(self, old_nets, new_nets, ifaces,
|
||||||
|
security_groups=None):
|
||||||
|
new_str_net = self._str_network(new_nets)
|
||||||
|
if new_str_net:
|
||||||
|
return self._calculate_using_str_network(ifaces, new_str_net)
|
||||||
|
else:
|
||||||
|
return self._calculate_using_list_networks(
|
||||||
|
old_nets, new_nets, ifaces, security_groups)
|
||||||
|
|
||||||
def update_floating_ip_association(self, floating_ip, flip_associate):
|
def update_floating_ip_association(self, floating_ip, flip_associate):
|
||||||
if self.is_using_neutron() and flip_associate.get('port_id'):
|
if self.is_using_neutron() and flip_associate.get('port_id'):
|
||||||
self._floating_ip_neutron_associate(floating_ip, flip_associate)
|
self._floating_ip_neutron_associate(floating_ip, flip_associate)
|
||||||
|
@ -507,6 +507,31 @@ class ServersTest(common.HeatTestCase):
|
|||||||
args, kwargs = mock_create.call_args
|
args, kwargs = mock_create.call_args
|
||||||
self.assertEqual({}, kwargs['meta'])
|
self.assertEqual({}, kwargs['meta'])
|
||||||
|
|
||||||
|
def test_server_create_with_str_network(self):
|
||||||
|
stack_name = 'server_with_str_network'
|
||||||
|
return_server = self.fc.servers.list()[1]
|
||||||
|
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||||
|
mock_nc = self.patchobject(nova.NovaClientPlugin, '_create',
|
||||||
|
return_value=self.fc)
|
||||||
|
self.patchobject(glance.GlanceClientPlugin, 'get_image',
|
||||||
|
return_value=self.mock_image)
|
||||||
|
self.patchobject(nova.NovaClientPlugin, 'get_flavor',
|
||||||
|
return_value=self.mock_flavor)
|
||||||
|
self.patchobject(neutron.NeutronClientPlugin,
|
||||||
|
'find_resourceid_by_name_or_id')
|
||||||
|
|
||||||
|
props = tmpl['Resources']['WebServer']['Properties']
|
||||||
|
props['networks'] = [{'allocate_network': 'none'}]
|
||||||
|
resource_defns = tmpl.resource_definitions(stack)
|
||||||
|
server = servers.Server('WebServer',
|
||||||
|
resource_defns['WebServer'], stack)
|
||||||
|
self.patchobject(server, 'store_external_ports')
|
||||||
|
create_mock = self.patchobject(self.fc.servers, 'create',
|
||||||
|
return_value=return_server)
|
||||||
|
scheduler.TaskRunner(server.create)()
|
||||||
|
mock_nc.assert_called_with(version='2.37')
|
||||||
|
self.assertEqual('none', create_mock.call_args[1]['nics'])
|
||||||
|
|
||||||
def test_server_create_with_image_id(self):
|
def test_server_create_with_image_id(self):
|
||||||
return_server = self.fc.servers.list()[1]
|
return_server = self.fc.servers.list()[1]
|
||||||
return_server.id = '5678'
|
return_server.id = '5678'
|
||||||
@ -1298,8 +1323,9 @@ class ServersTest(common.HeatTestCase):
|
|||||||
'find_resourceid_by_name_or_id')
|
'find_resourceid_by_name_or_id')
|
||||||
ex = self.assertRaises(exception.StackValidationFailed,
|
ex = self.assertRaises(exception.StackValidationFailed,
|
||||||
server.validate)
|
server.validate)
|
||||||
self.assertIn(_('One of the properties "network", "port" or "subnet" '
|
self.assertIn(_('One of the properties "network", "port", '
|
||||||
'should be set for the specified network of '
|
'"allocate_network" or "subnet" should be set '
|
||||||
|
'for the specified network of '
|
||||||
'server "%s".') % server.name,
|
'server "%s".') % server.name,
|
||||||
six.text_type(ex))
|
six.text_type(ex))
|
||||||
|
|
||||||
@ -1328,6 +1354,30 @@ class ServersTest(common.HeatTestCase):
|
|||||||
'corresponding port can not be retrieved.'),
|
'corresponding port can not be retrieved.'),
|
||||||
six.text_type(ex))
|
six.text_type(ex))
|
||||||
|
|
||||||
|
def test_server_validate_with_networks_str_net(self):
|
||||||
|
stack_name = 'srv_networks_str_nets'
|
||||||
|
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||||
|
# create a server with 'uuid' and 'network' properties
|
||||||
|
tmpl['Resources']['WebServer']['Properties']['networks'] = (
|
||||||
|
[{'network': '6b1688bb-18a0-4754-ab05-19daaedc5871',
|
||||||
|
'allocate_network': 'auto'}])
|
||||||
|
self.patchobject(nova.NovaClientPlugin, '_create',
|
||||||
|
return_value=self.fc)
|
||||||
|
resource_defns = tmpl.resource_definitions(stack)
|
||||||
|
server = servers.Server('server_validate_net_list_str',
|
||||||
|
resource_defns['WebServer'], stack)
|
||||||
|
self.patchobject(glance.GlanceClientPlugin, 'get_image',
|
||||||
|
return_value=self.mock_image)
|
||||||
|
self.patchobject(nova.NovaClientPlugin, 'get_flavor',
|
||||||
|
return_value=self.mock_flavor)
|
||||||
|
self.patchobject(neutron.NeutronClientPlugin,
|
||||||
|
'find_resourceid_by_name_or_id')
|
||||||
|
ex = self.assertRaises(exception.StackValidationFailed,
|
||||||
|
server.validate)
|
||||||
|
self.assertIn(_('Can not specify "allocate_network" with '
|
||||||
|
'other keys of networks at the same time.'),
|
||||||
|
six.text_type(ex))
|
||||||
|
|
||||||
def test_server_validate_port_fixed_ip(self):
|
def test_server_validate_port_fixed_ip(self):
|
||||||
stack_name = 'port_with_fixed_ip'
|
stack_name = 'port_with_fixed_ip'
|
||||||
(tmpl, stack) = self._setup_test_stack(stack_name,
|
(tmpl, stack) = self._setup_test_stack(stack_name,
|
||||||
@ -3139,10 +3189,12 @@ class ServersTest(common.HeatTestCase):
|
|||||||
|
|
||||||
def create_old_net(self, port=None, net=None,
|
def create_old_net(self, port=None, net=None,
|
||||||
ip=None, uuid=None, subnet=None,
|
ip=None, uuid=None, subnet=None,
|
||||||
port_extra_properties=None, floating_ip=None):
|
port_extra_properties=None, floating_ip=None,
|
||||||
|
str_network=None):
|
||||||
return {'port': port, 'network': net, 'fixed_ip': ip, 'uuid': uuid,
|
return {'port': port, 'network': net, 'fixed_ip': ip, 'uuid': uuid,
|
||||||
'subnet': subnet, 'floating_ip': floating_ip,
|
'subnet': subnet, 'floating_ip': floating_ip,
|
||||||
'port_extra_properties': port_extra_properties}
|
'port_extra_properties': port_extra_properties,
|
||||||
|
'allocate_network': str_network}
|
||||||
|
|
||||||
def create_fake_iface(self, port, net, ip):
|
def create_fake_iface(self, port, net, ip):
|
||||||
class fake_interface(object):
|
class fake_interface(object):
|
||||||
@ -3212,7 +3264,8 @@ class ServersTest(common.HeatTestCase):
|
|||||||
old_nets_copy = copy.deepcopy(old_nets)
|
old_nets_copy = copy.deepcopy(old_nets)
|
||||||
for net in new_nets_copy:
|
for net in new_nets_copy:
|
||||||
for key in ('port', 'network', 'fixed_ip', 'uuid', 'subnet',
|
for key in ('port', 'network', 'fixed_ip', 'uuid', 'subnet',
|
||||||
'port_extra_properties', 'floating_ip'):
|
'port_extra_properties', 'floating_ip',
|
||||||
|
'allocate_network'):
|
||||||
net.setdefault(key)
|
net.setdefault(key)
|
||||||
|
|
||||||
matched_nets = server._exclude_not_updated_networks(old_nets,
|
matched_nets = server._exclude_not_updated_networks(old_nets,
|
||||||
@ -3243,7 +3296,8 @@ class ServersTest(common.HeatTestCase):
|
|||||||
old_nets_copy = copy.deepcopy(old_nets)
|
old_nets_copy = copy.deepcopy(old_nets)
|
||||||
for net in new_nets_copy:
|
for net in new_nets_copy:
|
||||||
for key in ('port', 'network', 'fixed_ip', 'uuid', 'subnet',
|
for key in ('port', 'network', 'fixed_ip', 'uuid', 'subnet',
|
||||||
'port_extra_properties', 'floating_ip'):
|
'port_extra_properties', 'floating_ip',
|
||||||
|
'allocate_network'):
|
||||||
net.setdefault(key)
|
net.setdefault(key)
|
||||||
|
|
||||||
matched_nets = server._exclude_not_updated_networks(old_nets, new_nets)
|
matched_nets = server._exclude_not_updated_networks(old_nets, new_nets)
|
||||||
@ -3267,7 +3321,8 @@ class ServersTest(common.HeatTestCase):
|
|||||||
'subnet': None,
|
'subnet': None,
|
||||||
'uuid': None,
|
'uuid': None,
|
||||||
'port_extra_properties': None,
|
'port_extra_properties': None,
|
||||||
'floating_ip': None}]
|
'floating_ip': None,
|
||||||
|
'allocate_network': None}]
|
||||||
new_nets_copy = copy.deepcopy(new_nets)
|
new_nets_copy = copy.deepcopy(new_nets)
|
||||||
|
|
||||||
matched_nets = server._exclude_not_updated_networks(old_nets, new_nets)
|
matched_nets = server._exclude_not_updated_networks(old_nets, new_nets)
|
||||||
@ -3310,35 +3365,40 @@ class ServersTest(common.HeatTestCase):
|
|||||||
'subnet': None,
|
'subnet': None,
|
||||||
'floating_ip': None,
|
'floating_ip': None,
|
||||||
'port_extra_properties': None,
|
'port_extra_properties': None,
|
||||||
'uuid': None},
|
'uuid': None,
|
||||||
|
'allocate_network': None},
|
||||||
{'port': 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
{'port': 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
||||||
'network': 'gggggggg-1111-1111-1111-gggggggggggg',
|
'network': 'gggggggg-1111-1111-1111-gggggggggggg',
|
||||||
'fixed_ip': '1.2.3.4',
|
'fixed_ip': '1.2.3.4',
|
||||||
'subnet': None,
|
'subnet': None,
|
||||||
'port_extra_properties': None,
|
'port_extra_properties': None,
|
||||||
'floating_ip': None,
|
'floating_ip': None,
|
||||||
'uuid': None},
|
'uuid': None,
|
||||||
|
'allocate_network': None},
|
||||||
{'port': 'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
{'port': 'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
||||||
'network': 'gggggggg-1111-1111-1111-gggggggggggg',
|
'network': 'gggggggg-1111-1111-1111-gggggggggggg',
|
||||||
'fixed_ip': None,
|
'fixed_ip': None,
|
||||||
'subnet': None,
|
'subnet': None,
|
||||||
'port_extra_properties': None,
|
'port_extra_properties': None,
|
||||||
'floating_ip': None,
|
'floating_ip': None,
|
||||||
'uuid': None},
|
'uuid': None,
|
||||||
|
'allocate_network': None},
|
||||||
{'port': 'dddddddd-dddd-dddd-dddd-dddddddddddd',
|
{'port': 'dddddddd-dddd-dddd-dddd-dddddddddddd',
|
||||||
'network': None,
|
'network': None,
|
||||||
'fixed_ip': None,
|
'fixed_ip': None,
|
||||||
'subnet': None,
|
'subnet': None,
|
||||||
'port_extra_properties': None,
|
'port_extra_properties': None,
|
||||||
'floating_ip': None,
|
'floating_ip': None,
|
||||||
'uuid': None},
|
'uuid': None,
|
||||||
|
'allocate_network': None},
|
||||||
{'port': 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
|
{'port': 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
|
||||||
'uuid': 'gggggggg-1111-1111-1111-gggggggggggg',
|
'uuid': 'gggggggg-1111-1111-1111-gggggggggggg',
|
||||||
'fixed_ip': '5.6.7.8',
|
'fixed_ip': '5.6.7.8',
|
||||||
'subnet': None,
|
'subnet': None,
|
||||||
'port_extra_properties': None,
|
'port_extra_properties': None,
|
||||||
'floating_ip': None,
|
'floating_ip': None,
|
||||||
'network': None}]
|
'network': None,
|
||||||
|
'allocate_network': None}]
|
||||||
|
|
||||||
self.patchobject(neutron.NeutronClientPlugin,
|
self.patchobject(neutron.NeutronClientPlugin,
|
||||||
'find_resourceid_by_name_or_id',
|
'find_resourceid_by_name_or_id',
|
||||||
@ -3513,6 +3573,110 @@ class ServersTest(common.HeatTestCase):
|
|||||||
self.assertEqual(1, mock_detach_check.call_count)
|
self.assertEqual(1, mock_detach_check.call_count)
|
||||||
self.assertEqual(1, mock_attach_check.call_count)
|
self.assertEqual(1, mock_attach_check.call_count)
|
||||||
|
|
||||||
|
def _test_server_update_to_auto(self, available_multi_nets=None):
|
||||||
|
multi_nets = available_multi_nets or []
|
||||||
|
return_server = self.fc.servers.list()[1]
|
||||||
|
return_server.id = '5678'
|
||||||
|
server = self._create_test_server(return_server, 'networks_update')
|
||||||
|
|
||||||
|
old_networks = [
|
||||||
|
{'port': '95e25541-d26a-478d-8f36-ae1c8f6b74dc'}]
|
||||||
|
|
||||||
|
before_props = self.server_props.copy()
|
||||||
|
before_props['networks'] = old_networks
|
||||||
|
update_props = self.server_props.copy()
|
||||||
|
update_props['networks'] = [{'allocate_network': 'auto'}]
|
||||||
|
update_template = server.t.freeze(properties=update_props)
|
||||||
|
server.t = server.t.freeze(properties=before_props)
|
||||||
|
|
||||||
|
self.patchobject(self.fc.servers, 'get', return_value=return_server)
|
||||||
|
poor_interfaces = [
|
||||||
|
self.create_fake_iface('95e25541-d26a-478d-8f36-ae1c8f6b74dc',
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||||
|
'11.12.13.14')
|
||||||
|
]
|
||||||
|
|
||||||
|
self.patchobject(return_server, 'interface_list',
|
||||||
|
return_value=poor_interfaces)
|
||||||
|
self.patchobject(server, '_get_available_networks',
|
||||||
|
return_value=multi_nets)
|
||||||
|
mock_detach = self.patchobject(return_server, 'interface_detach')
|
||||||
|
mock_attach = self.patchobject(return_server, 'interface_attach')
|
||||||
|
updater = scheduler.TaskRunner(server.update, update_template)
|
||||||
|
if not multi_nets:
|
||||||
|
self.patchobject(nova.NovaClientPlugin, 'check_interface_detach',
|
||||||
|
return_value=True)
|
||||||
|
self.patchobject(nova.NovaClientPlugin,
|
||||||
|
'check_interface_attach',
|
||||||
|
return_value=True)
|
||||||
|
|
||||||
|
auto_allocate_net = '9cfe6c74-c105-4906-9a1f-81d9064e9bca'
|
||||||
|
self.patchobject(server, '_auto_allocate_network',
|
||||||
|
return_value=[auto_allocate_net])
|
||||||
|
updater()
|
||||||
|
self.assertEqual((server.UPDATE, server.COMPLETE), server.state)
|
||||||
|
self.assertEqual(1, mock_detach.call_count)
|
||||||
|
self.assertEqual(1, mock_attach.call_count)
|
||||||
|
mock_attach.called_once_with(
|
||||||
|
{'port_id': None,
|
||||||
|
'net_id': auto_allocate_net,
|
||||||
|
'fip': None})
|
||||||
|
else:
|
||||||
|
self.assertRaises(exception.ResourceFailure, updater)
|
||||||
|
self.assertEqual(0, mock_detach.call_count)
|
||||||
|
self.assertEqual(0, mock_attach.call_count)
|
||||||
|
|
||||||
|
def test_server_update_str_networks_auto(self):
|
||||||
|
self._test_server_update_to_auto()
|
||||||
|
|
||||||
|
def test_server_update_str_networks_auto_multi_nets(self):
|
||||||
|
available_nets = ['net_1', 'net_2']
|
||||||
|
self._test_server_update_to_auto(available_nets)
|
||||||
|
|
||||||
|
def test_server_update_str_networks_none(self):
|
||||||
|
return_server = self.fc.servers.list()[1]
|
||||||
|
return_server.id = '5678'
|
||||||
|
server = self._create_test_server(return_server, 'networks_update')
|
||||||
|
|
||||||
|
old_networks = [
|
||||||
|
{'port': '95e25541-d26a-478d-8f36-ae1c8f6b74dc'},
|
||||||
|
{'port': '4121f61a-1b2e-4ab0-901e-eade9b1cb09d'},
|
||||||
|
{'network': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||||
|
'fixed_ip': '31.32.33.34'}]
|
||||||
|
|
||||||
|
before_props = self.server_props.copy()
|
||||||
|
before_props['networks'] = old_networks
|
||||||
|
update_props = self.server_props.copy()
|
||||||
|
update_props['networks'] = [{'allocate_network': 'none'}]
|
||||||
|
update_template = server.t.freeze(properties=update_props)
|
||||||
|
server.t = server.t.freeze(properties=before_props)
|
||||||
|
|
||||||
|
self.patchobject(self.fc.servers, 'get', return_value=return_server)
|
||||||
|
port_interfaces = [
|
||||||
|
self.create_fake_iface('95e25541-d26a-478d-8f36-ae1c8f6b74dc',
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||||
|
'11.12.13.14'),
|
||||||
|
self.create_fake_iface('4121f61a-1b2e-4ab0-901e-eade9b1cb09d',
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||||
|
'21.22.23.24'),
|
||||||
|
self.create_fake_iface('0907fa82-a024-43c2-9fc5-efa1bccaa74a',
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||||
|
'31.32.33.34')
|
||||||
|
]
|
||||||
|
|
||||||
|
self.patchobject(return_server, 'interface_list',
|
||||||
|
return_value=port_interfaces)
|
||||||
|
mock_detach = self.patchobject(return_server, 'interface_detach')
|
||||||
|
self.patchobject(nova.NovaClientPlugin,
|
||||||
|
'check_interface_detach',
|
||||||
|
return_value=True)
|
||||||
|
mock_attach = self.patchobject(return_server, 'interface_attach')
|
||||||
|
|
||||||
|
scheduler.TaskRunner(server.update, update_template)()
|
||||||
|
self.assertEqual((server.UPDATE, server.COMPLETE), server.state)
|
||||||
|
self.assertEqual(3, mock_detach.call_count)
|
||||||
|
self.assertEqual(0, mock_attach.call_count)
|
||||||
|
|
||||||
def test_server_update_networks_with_complex_parameters(self):
|
def test_server_update_networks_with_complex_parameters(self):
|
||||||
return_server = self.fc.servers.list()[1]
|
return_server = self.fc.servers.list()[1]
|
||||||
return_server.id = '5678'
|
return_server.id = '5678'
|
||||||
|
Loading…
Reference in New Issue
Block a user