Merge "Add port_extra_properties to Nova::Server nics"
This commit is contained in:
commit
cead4b8cdf
@ -32,16 +32,19 @@ LOG = logging.getLogger(__name__)
|
||||
class Port(neutron.NeutronResource):
|
||||
|
||||
PROPERTIES = (
|
||||
NETWORK_ID, NETWORK, NAME, VALUE_SPECS,
|
||||
ADMIN_STATE_UP, FIXED_IPS, MAC_ADDRESS,
|
||||
DEVICE_ID, SECURITY_GROUPS, ALLOWED_ADDRESS_PAIRS,
|
||||
DEVICE_OWNER, REPLACEMENT_POLICY, VNIC_TYPE,
|
||||
PORT_SECURITY_ENABLED,
|
||||
NAME, NETWORK_ID, NETWORK, FIXED_IPS, SECURITY_GROUPS,
|
||||
REPLACEMENT_POLICY, DEVICE_ID, DEVICE_OWNER
|
||||
) = (
|
||||
'network_id', 'network', 'name', 'value_specs',
|
||||
'admin_state_up', 'fixed_ips', 'mac_address',
|
||||
'device_id', 'security_groups', 'allowed_address_pairs',
|
||||
'device_owner', 'replacement_policy', 'binding:vnic_type',
|
||||
'name', 'network_id', 'network', 'fixed_ips', 'security_groups',
|
||||
'replacement_policy', 'device_id', 'device_owner'
|
||||
)
|
||||
|
||||
EXTRA_PROPERTIES = (
|
||||
VALUE_SPECS, ADMIN_STATE_UP, MAC_ADDRESS,
|
||||
ALLOWED_ADDRESS_PAIRS, VNIC_TYPE, PORT_SECURITY_ENABLED,
|
||||
) = (
|
||||
'value_specs', 'admin_state_up', 'mac_address',
|
||||
'allowed_address_pairs', 'binding:vnic_type',
|
||||
'port_security_enabled',
|
||||
)
|
||||
|
||||
@ -70,6 +73,11 @@ class Port(neutron.NeutronResource):
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('A symbolic name for this port.'),
|
||||
update_allowed=True
|
||||
),
|
||||
NETWORK_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
support_status=support.SupportStatus(
|
||||
@ -97,22 +105,16 @@ class Port(neutron.NeutronResource):
|
||||
constraints.CustomConstraint('neutron.network')
|
||||
],
|
||||
),
|
||||
|
||||
NAME: properties.Schema(
|
||||
DEVICE_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('A symbolic name for this port.'),
|
||||
_('Device ID of this port.'),
|
||||
update_allowed=True
|
||||
),
|
||||
VALUE_SPECS: properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
_('Extra parameters to include in the "port" object in the '
|
||||
'creation request.'),
|
||||
default={}
|
||||
),
|
||||
ADMIN_STATE_UP: properties.Schema(
|
||||
properties.Schema.BOOLEAN,
|
||||
_('The administrative state of this port.'),
|
||||
default=True,
|
||||
DEVICE_OWNER: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name of the network owning the port. '
|
||||
'The value is typically network:floatingip '
|
||||
'or network:router_interface or network:dhcp'),
|
||||
update_allowed=True
|
||||
),
|
||||
FIXED_IPS: properties.Schema(
|
||||
@ -157,6 +159,40 @@ class Port(neutron.NeutronResource):
|
||||
),
|
||||
update_allowed=True
|
||||
),
|
||||
SECURITY_GROUPS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('Security group IDs to associate with this port.'),
|
||||
update_allowed=True
|
||||
),
|
||||
REPLACEMENT_POLICY: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Policy on how to respond to a stack-update for this resource. '
|
||||
'REPLACE_ALWAYS will replace the port regardless of any '
|
||||
'property changes. AUTO will update the existing port for any '
|
||||
'changed update-allowed property.'),
|
||||
default='AUTO',
|
||||
constraints=[
|
||||
constraints.AllowedValues(['REPLACE_ALWAYS', 'AUTO']),
|
||||
],
|
||||
update_allowed=True
|
||||
),
|
||||
}
|
||||
|
||||
# NOTE(prazumovsky): properties_schema has been separated because some
|
||||
# properties used in server for creating internal port.
|
||||
extra_properties_schema = {
|
||||
VALUE_SPECS: properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
_('Extra parameters to include in the "port" object in the '
|
||||
'creation request.'),
|
||||
default={}
|
||||
),
|
||||
ADMIN_STATE_UP: properties.Schema(
|
||||
properties.Schema.BOOLEAN,
|
||||
_('The administrative state of this port.'),
|
||||
default=True,
|
||||
update_allowed=True
|
||||
),
|
||||
MAC_ADDRESS: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('MAC address to give to this port.'),
|
||||
@ -164,16 +200,6 @@ class Port(neutron.NeutronResource):
|
||||
constraints.CustomConstraint('mac_addr')
|
||||
]
|
||||
),
|
||||
DEVICE_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Device ID of this port.'),
|
||||
update_allowed=True
|
||||
),
|
||||
SECURITY_GROUPS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('Security group IDs to associate with this port.'),
|
||||
update_allowed=True
|
||||
),
|
||||
ALLOWED_ADDRESS_PAIRS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('Additional MAC/IP address pairs allowed to pass through the '
|
||||
@ -199,25 +225,6 @@ class Port(neutron.NeutronResource):
|
||||
},
|
||||
)
|
||||
),
|
||||
DEVICE_OWNER: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name of the network owning the port. '
|
||||
'The value is typically network:floatingip '
|
||||
'or network:router_interface or network:dhcp'),
|
||||
update_allowed=True
|
||||
),
|
||||
REPLACEMENT_POLICY: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Policy on how to respond to a stack-update for this resource. '
|
||||
'REPLACE_ALWAYS will replace the port regardless of any '
|
||||
'property changes. AUTO will update the existing port for any '
|
||||
'changed update-allowed property.'),
|
||||
default='AUTO',
|
||||
constraints=[
|
||||
constraints.AllowedValues(['REPLACE_ALWAYS', 'AUTO']),
|
||||
],
|
||||
update_allowed=True
|
||||
),
|
||||
VNIC_TYPE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('The vnic type to be bound on the neutron port. '
|
||||
@ -300,6 +307,11 @@ class Port(neutron.NeutronResource):
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, name, definition, stack):
|
||||
"""Overloaded init in case of merging two schemas to one."""
|
||||
self.properties_schema.update(self.extra_properties_schema)
|
||||
super(Port, self).__init__(name, definition, stack)
|
||||
|
||||
def translation_rules(self):
|
||||
return [
|
||||
properties.TranslationRule(
|
||||
|
@ -27,6 +27,7 @@ from heat.engine.clients import progress
|
||||
from heat.engine import constraints
|
||||
from heat.engine import function
|
||||
from heat.engine import properties
|
||||
from heat.engine.resources.openstack.neutron import port as neutron_port
|
||||
from heat.engine.resources.openstack.neutron import subnet
|
||||
from heat.engine.resources.openstack.nova import server_network_mixin
|
||||
from heat.engine.resources import scheduler_hints as sh
|
||||
@ -96,10 +97,10 @@ class Server(stack_user.StackUser, sh.SchedulerHintsMixin,
|
||||
|
||||
_NETWORK_KEYS = (
|
||||
NETWORK_UUID, NETWORK_ID, NETWORK_FIXED_IP, NETWORK_PORT,
|
||||
NETWORK_SUBNET
|
||||
NETWORK_SUBNET, NETWORK_PORT_EXTRA
|
||||
) = (
|
||||
'uuid', 'network', 'fixed_ip', 'port',
|
||||
'subnet'
|
||||
'subnet', 'port_extra_properties'
|
||||
)
|
||||
|
||||
_SOFTWARE_CONFIG_FORMATS = (
|
||||
@ -121,6 +122,7 @@ class Server(stack_user.StackUser, sh.SchedulerHintsMixin,
|
||||
'name', 'addresses', 'networks', 'first_address',
|
||||
'instance_name', 'accessIPv4', 'accessIPv6', 'console_urls',
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
@ -364,6 +366,14 @@ class Server(stack_user.StackUser, sh.SchedulerHintsMixin,
|
||||
constraints.CustomConstraint('neutron.port')
|
||||
]
|
||||
),
|
||||
NETWORK_PORT_EXTRA: properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
_('Dict, which has expand properties for port. '
|
||||
'Used only if port property is not specified '
|
||||
'for creating port.'),
|
||||
schema=neutron_port.Port.extra_properties_schema,
|
||||
support_status=support.SupportStatus(version='6.0.0')
|
||||
),
|
||||
NETWORK_SUBNET: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Subnet in which to allocate the IP address for '
|
||||
|
@ -22,6 +22,8 @@ from heat.common.i18n import _
|
||||
from heat.common.i18n import _LI
|
||||
from heat.engine import resource
|
||||
|
||||
from heat.engine.resources.openstack.neutron import port as neutron_port
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -86,8 +88,19 @@ class ServerNetworkMixin(object):
|
||||
name = _('%(server)s-port-%(number)s') % {'server': self.name,
|
||||
'number': net_number}
|
||||
|
||||
kwargs = {'network_id': self._get_network_id(net_data),
|
||||
'name': name}
|
||||
kwargs = self._prepare_internal_port_kwargs(net_data)
|
||||
kwargs['name'] = name
|
||||
|
||||
port = self.client('neutron').create_port({'port': kwargs})['port']
|
||||
|
||||
# Store ids (used for floating_ip association, updating, etc.)
|
||||
# in resource's data.
|
||||
self._data_update_ports(port['id'], 'add')
|
||||
|
||||
return port['id']
|
||||
|
||||
def _prepare_internal_port_kwargs(self, net_data):
|
||||
kwargs = {'network_id': self._get_network_id(net_data)}
|
||||
fixed_ip = net_data.get(self.NETWORK_FIXED_IP)
|
||||
subnet = net_data.get(self.NETWORK_SUBNET)
|
||||
body = {}
|
||||
@ -99,13 +112,34 @@ class ServerNetworkMixin(object):
|
||||
if body:
|
||||
kwargs.update({'fixed_ips': [body]})
|
||||
|
||||
port = self.client('neutron').create_port({'port': kwargs})['port']
|
||||
if net_data.get(self.SECURITY_GROUPS):
|
||||
sec_uuids = self.client_plugin(
|
||||
'neutron').get_secgroup_uuids(net_data.get(
|
||||
self.SECURITY_GROUPS))
|
||||
kwargs['security_groups'] = sec_uuids
|
||||
|
||||
# Store ids (used for floating_ip association, updating, etc.)
|
||||
# in resource's data.
|
||||
self._data_update_ports(port['id'], 'add')
|
||||
extra_props = net_data.get(self.NETWORK_PORT_EXTRA)
|
||||
if extra_props is not None:
|
||||
port_extra_keys = list(neutron_port.Port.EXTRA_PROPERTIES)
|
||||
port_extra_keys.remove(neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
|
||||
for key in port_extra_keys:
|
||||
if extra_props.get(key) is not None:
|
||||
kwargs[key] = extra_props.get(key)
|
||||
|
||||
return port['id']
|
||||
allowed_address_pairs = extra_props.get(
|
||||
neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
|
||||
if allowed_address_pairs is not None:
|
||||
for pair in allowed_address_pairs:
|
||||
if (neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS
|
||||
in pair and pair.get(
|
||||
neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS)
|
||||
is None):
|
||||
del pair[
|
||||
neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS]
|
||||
kwargs[neutron_port.Port.ALLOWED_ADDRESS_PAIRS] = \
|
||||
allowed_address_pairs
|
||||
|
||||
return kwargs
|
||||
|
||||
def _delete_internal_port(self, port_id):
|
||||
"""Delete physical port by id."""
|
||||
|
@ -2996,10 +2996,12 @@ class ServersTest(common.HeatTestCase):
|
||||
scheduler.TaskRunner(server.create)()
|
||||
self.m.VerifyAll()
|
||||
|
||||
def create_old_net(self, port=None, net=None, ip=None, uuid=None,
|
||||
subnet=None):
|
||||
def create_old_net(self, port=None, net=None,
|
||||
ip=None, uuid=None, subnet=None,
|
||||
port_extra_properties=None):
|
||||
return {'port': port, 'network': net, 'fixed_ip': ip, 'uuid': uuid,
|
||||
'subnet': subnet}
|
||||
'subnet': subnet,
|
||||
'port_extra_properties': port_extra_properties}
|
||||
|
||||
def create_fake_iface(self, port, net, ip):
|
||||
class fake_interface(object):
|
||||
@ -3082,7 +3084,8 @@ class ServersTest(common.HeatTestCase):
|
||||
new_nets_copy = copy.deepcopy(new_nets)
|
||||
old_nets_copy = copy.deepcopy(old_nets)
|
||||
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'):
|
||||
net.setdefault(key)
|
||||
|
||||
matched_nets = server._exclude_not_updated_networks(old_nets,
|
||||
@ -3112,7 +3115,8 @@ class ServersTest(common.HeatTestCase):
|
||||
new_nets_copy = copy.deepcopy(new_nets)
|
||||
old_nets_copy = copy.deepcopy(old_nets)
|
||||
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'):
|
||||
net.setdefault(key)
|
||||
|
||||
matched_nets = server._exclude_not_updated_networks(old_nets, new_nets)
|
||||
@ -3135,7 +3139,8 @@ class ServersTest(common.HeatTestCase):
|
||||
'fixed_ip': None,
|
||||
'port': None,
|
||||
'uuid': None,
|
||||
'subnet': None}]
|
||||
'subnet': None,
|
||||
'port_extra_properties': None}]
|
||||
new_nets_copy = copy.deepcopy(new_nets)
|
||||
|
||||
matched_nets = server._exclude_not_updated_networks(old_nets, new_nets)
|
||||
@ -3176,27 +3181,33 @@ class ServersTest(common.HeatTestCase):
|
||||
'network': None,
|
||||
'fixed_ip': None,
|
||||
'uuid': None,
|
||||
'subnet': None},
|
||||
'subnet': None,
|
||||
'port_extra_properties': None,
|
||||
'uuid': None},
|
||||
{'port': 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
||||
'network': 'gggggggg-1111-1111-1111-gggggggggggg',
|
||||
'fixed_ip': '1.2.3.4',
|
||||
'uuid': None,
|
||||
'subnet': None},
|
||||
'subnet': None,
|
||||
'port_extra_properties': None,
|
||||
'uuid': None},
|
||||
{'port': 'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
||||
'network': 'gggggggg-1111-1111-1111-gggggggggggg',
|
||||
'fixed_ip': None,
|
||||
'uuid': None,
|
||||
'subnet': None},
|
||||
'subnet': None,
|
||||
'port_extra_properties': None,
|
||||
'uuid': None},
|
||||
{'port': 'dddddddd-dddd-dddd-dddd-dddddddddddd',
|
||||
'network': None,
|
||||
'fixed_ip': None,
|
||||
'uuid': None,
|
||||
'subnet': None},
|
||||
'subnet': None,
|
||||
'port_extra_properties': None,
|
||||
'uuid': None},
|
||||
{'port': 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
|
||||
'uuid': 'gggggggg-1111-1111-1111-gggggggggggg',
|
||||
'fixed_ip': '5.6.7.8',
|
||||
'network': None,
|
||||
'subnet': None}]
|
||||
'subnet': None,
|
||||
'port_extra_properties': None,
|
||||
'network': None}]
|
||||
|
||||
self.patchobject(neutron.NeutronClientPlugin, 'resolve_network',
|
||||
return_value='gggggggg-1111-1111-1111-gggggggggggg')
|
||||
@ -3901,7 +3912,7 @@ class ServerInternalPortTest(common.HeatTestCase):
|
||||
self.assertEqual('Specified subnet 1234 does not belongs to '
|
||||
'network 4321.', six.text_type(ex))
|
||||
|
||||
def test_build_nics_create_internal_port_all_props(self):
|
||||
def test_build_nics_create_internal_port_all_props_without_extras(self):
|
||||
tmpl = """
|
||||
heat_template_version: 2015-10-15
|
||||
resources:
|
||||
@ -3952,6 +3963,55 @@ class ServerInternalPortTest(common.HeatTestCase):
|
||||
self.assertFalse(self.port_create.called)
|
||||
self.assertFalse(data_set.called)
|
||||
|
||||
def test_prepare_port_kwargs_with_extras(self):
|
||||
tmpl = """
|
||||
heat_template_version: 2015-10-15
|
||||
resources:
|
||||
server:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
flavor: m1.small
|
||||
image: F17-x86_64-gold
|
||||
networks:
|
||||
- network: 4321
|
||||
subnet: 1234
|
||||
fixed_ip: 127.0.0.1
|
||||
port_extra_properties:
|
||||
mac_address: 00:00:00:00:00:00
|
||||
allowed_address_pairs:
|
||||
- ip_address: 127.0.0.1
|
||||
mac_address: None
|
||||
- mac_address: 00:00:00:00:00:00
|
||||
|
||||
"""
|
||||
|
||||
t, stack, server = self._return_template_stack_and_rsrc_defn('test',
|
||||
tmpl)
|
||||
|
||||
self.resolve.side_effect = ['4321', '1234']
|
||||
|
||||
network = {'network': '4321', 'subnet': '1234',
|
||||
'fixed_ip': '127.0.0.1',
|
||||
'port_extra_properties': {
|
||||
'mac_address': '00:00:00:00:00:00',
|
||||
'allowed_address_pairs': [
|
||||
{'ip_address': '127.0.0.1',
|
||||
'mac_address': None},
|
||||
{'mac_address': '00:00:00:00:00:00'}
|
||||
]
|
||||
}}
|
||||
kwargs = server._prepare_internal_port_kwargs(network)
|
||||
|
||||
self.assertEqual({'network_id': '4321',
|
||||
'fixed_ips': [
|
||||
{'ip_address': '127.0.0.1', 'subnet_id': '1234'}
|
||||
],
|
||||
'mac_address': '00:00:00:00:00:00',
|
||||
'allowed_address_pairs': [
|
||||
{'ip_address': '127.0.0.1'},
|
||||
{'mac_address': '00:00:00:00:00:00'}]},
|
||||
kwargs)
|
||||
|
||||
def test_build_nics_create_internal_port_without_net(self):
|
||||
tmpl = """
|
||||
heat_template_version: 2015-10-15
|
||||
|
Loading…
Reference in New Issue
Block a user