Provides ability to reference existing neutron resources

Additional properties 'network' and 'subnet' are added to
the resources (existing properties 'network_id' and
'subnet_id' are deprecated). 'network'  and 'subnet' can
now carry name or id. Pre-existing neutron resources
can now be referenced in heat templates by their name
and not necessarily by their UUID. This would help porting
templates across deployments.

Change-Id: I644f5d6ee2e5ccabde190bd73eacc71daaf41f4d
Closes-Bug: #1286128
This commit is contained in:
Rabi Mishra 2014-02-28 12:11:00 +05:30
parent 11c99d7b06
commit 4bf2a1fad2
13 changed files with 895 additions and 190 deletions

View File

@ -16,7 +16,7 @@ from testtools import skipIf
from heat.common import template_format from heat.common import template_format
from heat.engine import clients from heat.engine import clients
from heat.engine import resource from heat.engine import resource
from heat.engine.resources.neutron import router from heat.engine.resources.neutron import neutron
from heat.engine import scheduler from heat.engine import scheduler
from heat.openstack.common.importutils import try_import from heat.openstack.common.importutils import try_import
from heat.tests.common import HeatTestCase from heat.tests.common import HeatTestCase
@ -60,7 +60,7 @@ neutron_template = '''
@skipIf(neutronclient is None, 'neutronclient unavailable') @skipIf(neutronclient is None, 'neutronclient unavailable')
class NeutronExtraRouteTest(HeatTestCase): class NeutronExtraRouteTest(HeatTestCase):
@skipIf(router.neutronV20 is None, "Missing Neutron v2_0") @skipIf(neutron.neutronV20 is None, "Missing Neutron v2_0")
def setUp(self): def setUp(self):
super(NeutronExtraRouteTest, self).setUp() super(NeutronExtraRouteTest, self).setUp()
self.m.StubOutWithMock(neutronclient.Client, 'show_router') self.m.StubOutWithMock(neutronclient.Client, 'show_router')

View File

@ -15,6 +15,7 @@ from heat.engine import clients
from heat.engine import properties from heat.engine import properties
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
from heat.engine.resources.neutron import router from heat.engine.resources.neutron import router
from heat.engine import support
if clients.neutronclient is not None: if clients.neutronclient is not None:
from neutronclient.common.exceptions import NeutronClientException from neutronclient.common.exceptions import NeutronClientException
@ -22,16 +23,25 @@ if clients.neutronclient is not None:
class FloatingIP(neutron.NeutronResource): class FloatingIP(neutron.NeutronResource):
PROPERTIES = ( PROPERTIES = (
FLOATING_NETWORK_ID, VALUE_SPECS, PORT_ID, FIXED_IP_ADDRESS, FLOATING_NETWORK_ID, FLOATING_NETWORK,
VALUE_SPECS, PORT_ID, FIXED_IP_ADDRESS,
) = ( ) = (
'floating_network_id', 'value_specs', 'port_id', 'fixed_ip_address', 'floating_network_id', 'floating_network',
'value_specs', 'port_id', 'fixed_ip_address',
) )
properties_schema = { properties_schema = {
FLOATING_NETWORK_ID: properties.Schema( FLOATING_NETWORK_ID: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('ID of network to allocate floating IP from.'), support_status=support.SupportStatus(
required=True support.DEPRECATED,
_('Use property %s.') % FLOATING_NETWORK),
required=False
),
FLOATING_NETWORK: properties.Schema(
properties.Schema.STRING,
_('Network to allocate floating IP from.'),
required=False
), ),
VALUE_SPECS: properties.Schema( VALUE_SPECS: properties.Schema(
properties.Schema.MAP, properties.Schema.MAP,
@ -69,15 +79,28 @@ class FloatingIP(neutron.NeutronResource):
# depend on any RouterGateway in this template with the same # depend on any RouterGateway in this template with the same
# network_id as this floating_network_id # network_id as this floating_network_id
for resource in self.stack.itervalues(): for resource in self.stack.itervalues():
if (resource.has_interface('OS::Neutron::RouterGateway') and if resource.has_interface('OS::Neutron::RouterGateway'):
resource.properties.get(router.RouterGateway.NETWORK_ID) == gateway_network = resource.properties.get(
self.properties.get(self.FLOATING_NETWORK_ID)): router.RouterGateway.NETWORK) or resource.properties.get(
deps += (self, resource) router.RouterGateway.NETWORK_ID)
floating_network = self.properties.get(
self.FLOATING_NETWORK) or self.properties.get(
self.FLOATING_NETWORK_ID)
if gateway_network == floating_network:
deps += (self, resource)
def validate(self):
super(FloatingIP, self).validate()
self._validate_depr_property_required(
self.properties, self.FLOATING_NETWORK, self.FLOATING_NETWORK_ID)
def handle_create(self): def handle_create(self):
props = self.prepare_properties( props = self.prepare_properties(
self.properties, self.properties,
self.physical_resource_name()) self.physical_resource_name())
self._resolve_network(
self.neutron(), props, self.FLOATING_NETWORK,
'floating_network_id')
fip = self.neutron().create_floatingip({ fip = self.neutron().create_floatingip({
'floatingip': props})['floatingip'] 'floatingip': props})['floatingip']
self.resource_id_set(fip['id']) self.resource_id_set(fip['id'])

View File

@ -19,10 +19,10 @@ from heat.engine import resource
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
from heat.engine.resources import nova_utils from heat.engine.resources import nova_utils
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine import support
if clients.neutronclient is not None: if clients.neutronclient is not None:
from neutronclient.common.exceptions import NeutronClientException from neutronclient.common.exceptions import NeutronClientException
from neutronclient.neutron import v2_0 as neutronV20
class HealthMonitor(neutron.NeutronResource): class HealthMonitor(neutron.NeutronResource):
@ -148,10 +148,10 @@ class Pool(neutron.NeutronResource):
""" """
PROPERTIES = ( PROPERTIES = (
PROTOCOL, SUBNET_ID, LB_METHOD, NAME, DESCRIPTION, PROTOCOL, SUBNET_ID, SUBNET, LB_METHOD, NAME, DESCRIPTION,
ADMIN_STATE_UP, VIP, MONITORS, ADMIN_STATE_UP, VIP, MONITORS,
) = ( ) = (
'protocol', 'subnet_id', 'lb_method', 'name', 'description', 'protocol', 'subnet_id', 'subnet', 'lb_method', 'name', 'description',
'admin_state_up', 'vip', 'monitors', 'admin_state_up', 'vip', 'monitors',
) )
@ -181,10 +181,17 @@ class Pool(neutron.NeutronResource):
] ]
), ),
SUBNET_ID: properties.Schema( SUBNET_ID: properties.Schema(
properties.Schema.STRING,
support_status=support.SupportStatus(
support.DEPRECATED,
_('Use property %s.') % SUBNET),
required=False
),
SUBNET: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('The subnet for the port on which the members ' _('The subnet for the port on which the members '
'of the pool will be connected.'), 'of the pool will be connected.'),
required=True required=False
), ),
LB_METHOD: properties.Schema( LB_METHOD: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
@ -298,7 +305,8 @@ class Pool(neutron.NeutronResource):
res = super(Pool, self).validate() res = super(Pool, self).validate()
if res: if res:
return res return res
self._validate_depr_property_required(
self.properties, self.SUBNET, self.SUBNET_ID)
session_p = self.properties[self.VIP].get(self.VIP_SESSION_PERSISTENCE) session_p = self.properties[self.VIP].get(self.VIP_SESSION_PERSISTENCE)
if session_p is None: if session_p is None:
# session persistence is not configured, skip validation # session persistence is not configured, skip validation
@ -317,6 +325,8 @@ class Pool(neutron.NeutronResource):
properties = self.prepare_properties( properties = self.prepare_properties(
self.properties, self.properties,
self.physical_resource_name()) self.physical_resource_name())
self._resolve_subnet(
self.neutron(), properties, self.SUBNET, 'subnet_id')
vip_properties = properties.pop(self.VIP) vip_properties = properties.pop(self.VIP)
monitors = properties.pop(self.MONITORS) monitors = properties.pop(self.MONITORS)
client = self.neutron() client = self.neutron()
@ -339,13 +349,11 @@ class Pool(neutron.NeutronResource):
vip_arguments['protocol'] = self.properties[self.PROTOCOL] vip_arguments['protocol'] = self.properties[self.PROTOCOL]
if vip_arguments.get(self.VIP_SUBNET) is None: if vip_arguments.get(self.VIP_SUBNET) is None:
vip_arguments['subnet_id'] = self.properties[self.SUBNET_ID] vip_arguments['subnet_id'] = properties[self.SUBNET_ID]
else: else:
vip_arguments[ vip_arguments['subnet_id'] = self._resolve_subnet(
'subnet_id'] = neutronV20.find_resourceid_by_name_or_id( self.neutron(),
self.neutron(), vip_arguments, self.VIP_SUBNET, 'subnet_id')
'subnet',
vip_arguments.pop(self.VIP_SUBNET))
vip_arguments['pool_id'] = pool['id'] vip_arguments['pool_id'] = pool['id']
vip = client.create_vip({'vip': vip_arguments})['vip'] vip = client.create_vip({'vip': vip_arguments})['vip']

View File

@ -19,6 +19,7 @@ from heat.engine import clients
from heat.engine import constraints from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
from heat.engine import support
if clients.neutronclient is not None: if clients.neutronclient is not None:
from neutronclient.common.exceptions import NeutronClientException from neutronclient.common.exceptions import NeutronClientException
@ -42,9 +43,9 @@ class NetworkGateway(neutron.NeutronResource):
) )
_CONNECTIONS_KEYS = ( _CONNECTIONS_KEYS = (
NETWORK_ID, SEGMENTATION_TYPE, SEGMENTATION_ID, NETWORK_ID, NETWORK, SEGMENTATION_TYPE, SEGMENTATION_ID,
) = ( ) = (
'network_id', 'segmentation_type', 'segmentation_id', 'network_id', 'network', 'segmentation_type', 'segmentation_id',
) )
properties_schema = { properties_schema = {
@ -86,11 +87,18 @@ class NetworkGateway(neutron.NeutronResource):
properties.Schema.MAP, properties.Schema.MAP,
schema={ schema={
NETWORK_ID: properties.Schema( NETWORK_ID: properties.Schema(
properties.Schema.STRING,
support_status=support.SupportStatus(
support.DEPRECATED,
_('Use property %s.') % NETWORK),
required=False
),
NETWORK: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
description=_( description=_(
'The id of internal network to connect on ' 'The internal network to connect on '
'the network gateway.'), 'the network gateway.'),
required=True required=False
), ),
SEGMENTATION_TYPE: properties.Schema( SEGMENTATION_TYPE: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
@ -133,6 +141,8 @@ class NetworkGateway(neutron.NeutronResource):
connections = self.properties[self.CONNECTIONS] connections = self.properties[self.CONNECTIONS]
for connection in connections: for connection in connections:
self._validate_depr_property_required(
connection, self.NETWORK, self.NETWORK_ID)
segmentation_type = connection[self.SEGMENTATION_TYPE] segmentation_type = connection[self.SEGMENTATION_TYPE]
segmentation_id = connection.get(self.SEGMENTATION_ID) segmentation_id = connection.get(self.SEGMENTATION_ID)
@ -156,6 +166,10 @@ class NetworkGateway(neutron.NeutronResource):
{'network_gateway': props})['network_gateway'] {'network_gateway': props})['network_gateway']
for connection in connections: for connection in connections:
self._resolve_network(
self.neutron(), connection, self.NETWORK, 'network_id')
if self.NETWORK in connection.keys():
connection.pop(self.NETWORK)
self.neutron().connect_network_gateway( self.neutron().connect_network_gateway(
ret['id'], connection ret['id'], connection
) )
@ -170,6 +184,10 @@ class NetworkGateway(neutron.NeutronResource):
connections = self.properties[self.CONNECTIONS] connections = self.properties[self.CONNECTIONS]
for connection in connections: for connection in connections:
try: try:
self._resolve_network(
self.neutron(), connection, self.NETWORK, 'network_id')
if self.NETWORK in connection.keys():
connection.pop(self.NETWORK)
client.disconnect_network_gateway( client.disconnect_network_gateway(
self.resource_id, connection self.resource_id, connection
) )
@ -202,12 +220,20 @@ class NetworkGateway(neutron.NeutronResource):
if self.CONNECTIONS in prop_diff: if self.CONNECTIONS in prop_diff:
for connection in self.properties[self.CONNECTIONS]: for connection in self.properties[self.CONNECTIONS]:
try: try:
self._resolve_network(
self.neutron(), connection, self.NETWORK, 'network_id')
if self.NETWORK in connection.keys():
connection.pop(self.NETWORK)
self.neutron().disconnect_network_gateway( self.neutron().disconnect_network_gateway(
self.resource_id, connection self.resource_id, connection
) )
except NeutronClientException as ex: except NeutronClientException as ex:
self._handle_not_found_exception(ex) self._handle_not_found_exception(ex)
for connection in connections: for connection in connections:
self._resolve_network(
self.neutron(), connection, self.NETWORK, 'network_id')
if self.NETWORK in connection.keys():
connection.pop(self.NETWORK)
self.neutron().connect_network_gateway( self.neutron().connect_network_gateway(
self.resource_id, connection self.resource_id, connection
) )

View File

@ -12,6 +12,7 @@
# under the License. # under the License.
from neutronclient.common.exceptions import NeutronClientException from neutronclient.common.exceptions import NeutronClientException
from neutronclient.neutron import v2_0 as neutronV20
from heat.common import exception from heat.common import exception
from heat.engine import function from heat.engine import function
@ -51,6 +52,42 @@ class NeutronResource(resource.Resource):
for k in banned_keys.intersection(vs.keys()): for k in banned_keys.intersection(vs.keys()):
return '%s not allowed in value_specs' % k return '%s not allowed in value_specs' % k
@staticmethod
def _validate_depr_property_required(properties, prop_key, depr_prop_key):
prop_value = properties.get(prop_key)
depr_prop_value = properties.get(depr_prop_key)
if prop_value and depr_prop_value:
raise exception.ResourcePropertyConflict(prop_key,
depr_prop_key)
if not prop_value and not depr_prop_value:
msg = _('Either %(prop_key)s or %(depr_prop_key)s'
' should be specified.'
) % {'prop_key': prop_key,
'depr_prop_key': depr_prop_key}
raise exception.StackValidationFailed(message=msg)
@staticmethod
def _find_neutron_resource(neutron_client, props, key, key_type):
return neutronV20.find_resourceid_by_name_or_id(
neutron_client, key_type, props.get(key))
@staticmethod
def _resolve_network(neutron_client, props, net_key, net_id_key):
if props.get(net_key):
props[net_id_key] = NeutronResource._find_neutron_resource(
neutron_client, props, net_key, 'network')
props.pop(net_key)
return props[net_id_key]
@staticmethod
def _resolve_subnet(neutron_client, props, subnet_key, subnet_id_key):
if props.get(subnet_key):
props[subnet_id_key] = NeutronResource._find_neutron_resource(
neutron_client, props, subnet_key, 'subnet')
props.pop(subnet_key)
return props[subnet_id_key]
@staticmethod @staticmethod
def prepare_properties(properties, name): def prepare_properties(properties, name):
''' '''

View File

@ -15,6 +15,7 @@ from heat.engine import clients
from heat.engine import properties from heat.engine import properties
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
from heat.engine.resources.neutron import subnet from heat.engine.resources.neutron import subnet
from heat.engine import support
from heat.openstack.common import log as logging from heat.openstack.common import log as logging
if clients.neutronclient is not None: if clients.neutronclient is not None:
@ -26,19 +27,21 @@ logger = logging.getLogger(__name__)
class Port(neutron.NeutronResource): class Port(neutron.NeutronResource):
PROPERTIES = ( PROPERTIES = (
NETWORK_ID, NAME, VALUE_SPECS, ADMIN_STATE_UP, FIXED_IPS, NETWORK_ID, NETWORK, NAME, VALUE_SPECS,
MAC_ADDRESS, DEVICE_ID, SECURITY_GROUPS, ALLOWED_ADDRESS_PAIRS, ADMIN_STATE_UP, FIXED_IPS, MAC_ADDRESS,
DEVICE_ID, SECURITY_GROUPS, ALLOWED_ADDRESS_PAIRS,
DEVICE_OWNER, DEVICE_OWNER,
) = ( ) = (
'network_id', 'name', 'value_specs', 'admin_state_up', 'fixed_ips', 'network_id', 'network', 'name', 'value_specs',
'mac_address', 'device_id', 'security_groups', 'allowed_address_pairs', 'admin_state_up', 'fixed_ips', 'mac_address',
'device_id', 'security_groups', 'allowed_address_pairs',
'device_owner', 'device_owner',
) )
_FIXED_IP_KEYS = ( _FIXED_IP_KEYS = (
FIXED_IP_SUBNET_ID, FIXED_IP_IP_ADDRESS, FIXED_IP_SUBNET_ID, FIXED_IP_SUBNET, FIXED_IP_IP_ADDRESS,
) = ( ) = (
'subnet_id', 'ip_address', 'subnet_id', 'subnet', 'ip_address',
) )
_ALLOWED_ADDRESS_PAIR_KEYS = ( _ALLOWED_ADDRESS_PAIR_KEYS = (
@ -50,9 +53,16 @@ class Port(neutron.NeutronResource):
properties_schema = { properties_schema = {
NETWORK_ID: properties.Schema( NETWORK_ID: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('Network ID this port belongs to.'), support_status=support.SupportStatus(
required=True support.DEPRECATED,
_('Use property %s.') % NETWORK)
), ),
NETWORK: properties.Schema(
properties.Schema.STRING,
_('Network this port belongs to.')
),
NAME: properties.Schema( NAME: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('A symbolic name for this port.'), _('A symbolic name for this port.'),
@ -78,6 +88,12 @@ class Port(neutron.NeutronResource):
properties.Schema.MAP, properties.Schema.MAP,
schema={ schema={
FIXED_IP_SUBNET_ID: properties.Schema( FIXED_IP_SUBNET_ID: properties.Schema(
properties.Schema.STRING,
support_status=support.SupportStatus(
support.DEPRECATED,
_('Use property %s.') % FIXED_IP_SUBNET)
),
FIXED_IP_SUBNET: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('Subnet in which to allocate the IP address for ' _('Subnet in which to allocate the IP address for '
'this port.') 'this port.')
@ -151,6 +167,11 @@ class Port(neutron.NeutronResource):
update_allowed_keys = ('Properties',) update_allowed_keys = ('Properties',)
def validate(self):
super(Port, self).validate()
self._validate_depr_property_required(self.properties,
self.NETWORK, self.NETWORK_ID)
def add_dependencies(self, deps): def add_dependencies(self, deps):
super(Port, self).add_dependencies(deps) super(Port, self).add_dependencies(deps)
# Depend on any Subnet in this template with the same # Depend on any Subnet in this template with the same
@ -159,16 +180,21 @@ class Port(neutron.NeutronResource):
# to so all subnets in a network should be created before # to so all subnets in a network should be created before
# the ports in that network. # the ports in that network.
for resource in self.stack.itervalues(): for resource in self.stack.itervalues():
if (resource.has_interface('OS::Neutron::Subnet') and if resource.has_interface('OS::Neutron::Subnet'):
resource.properties.get(subnet.Subnet.NETWORK_ID) == dep_network = resource.properties.get(
self.properties.get(self.NETWORK_ID)): subnet.Subnet.NETWORK) or resource.properties.get(
deps += (self, resource) subnet.Subnet.NETWORK_ID)
network = self.properties.get(
self.NETWORK) or self.properties.get(self.NETWORK_ID)
if dep_network == network:
deps += (self, resource)
def handle_create(self): def handle_create(self):
props = self.prepare_properties( props = self.prepare_properties(
self.properties, self.properties,
self.physical_resource_name()) self.physical_resource_name())
self._resolve_network(self.neutron(),
props, self.NETWORK, 'network_id')
self._prepare_list_properties(props) self._prepare_list_properties(props)
if not props['fixed_ips']: if not props['fixed_ips']:
@ -182,7 +208,10 @@ class Port(neutron.NeutronResource):
for key, value in fixed_ip.items(): for key, value in fixed_ip.items():
if value is None: if value is None:
fixed_ip.pop(key) fixed_ip.pop(key)
if fixed_ip.get(self.FIXED_IP_SUBNET):
self._resolve_subnet(
self.neutron(), fixed_ip,
self.FIXED_IP_SUBNET, 'subnet_id')
# delete empty MAC addresses so that Neutron validation code # delete empty MAC addresses so that Neutron validation code
# wouldn't fail as it not accepts Nones # wouldn't fail as it not accepts Nones
for pair in props.get(self.ALLOWED_ADDRESS_PAIRS, []): for pair in props.get(self.ALLOWED_ADDRESS_PAIRS, []):

View File

@ -20,7 +20,6 @@ from heat.engine import support
if clients.neutronclient is not None: if clients.neutronclient is not None:
from neutronclient.common.exceptions import NeutronClientException from neutronclient.common.exceptions import NeutronClientException
from neutronclient.neutron import v2_0 as neutronV20
class Router(neutron.NeutronResource): class Router(neutron.NeutronResource):
@ -104,7 +103,9 @@ class Router(neutron.NeutronResource):
external_gw_net = external_gw.get(self.EXTERNAL_GATEWAY_NETWORK) external_gw_net = external_gw.get(self.EXTERNAL_GATEWAY_NETWORK)
for res in self.stack.itervalues(): for res in self.stack.itervalues():
if res.has_interface('OS::Neutron::Subnet'): if res.has_interface('OS::Neutron::Subnet'):
subnet_net = res.properties.get(subnet.Subnet.NETWORK_ID) subnet_net = res.properties.get(
subnet.Subnet.NETWORK) or res.properties.get(
subnet.Subnet.NETWORK_ID)
if subnet_net == external_gw_net: if subnet_net == external_gw_net:
deps += (self, res) deps += (self, res)
@ -112,10 +113,9 @@ class Router(neutron.NeutronResource):
props = super(Router, self).prepare_properties(properties, name) props = super(Router, self).prepare_properties(properties, name)
gateway = props.get(self.EXTERNAL_GATEWAY) gateway = props.get(self.EXTERNAL_GATEWAY)
if gateway: if gateway:
gateway['network_id'] = neutronV20.find_resourceid_by_name_or_id( self._resolve_network(
self.neutron(), self.neutron(), gateway,
'network', self.EXTERNAL_GATEWAY_NETWORK, 'network_id')
gateway.pop(self.EXTERNAL_GATEWAY_NETWORK))
if gateway[self.EXTERNAL_GATEWAY_ENABLE_SNAT] is None: if gateway[self.EXTERNAL_GATEWAY_ENABLE_SNAT] is None:
del gateway[self.EXTERNAL_GATEWAY_ENABLE_SNAT] del gateway[self.EXTERNAL_GATEWAY_ENABLE_SNAT]
return props return props
@ -176,9 +176,9 @@ class Router(neutron.NeutronResource):
class RouterInterface(neutron.NeutronResource): class RouterInterface(neutron.NeutronResource):
PROPERTIES = ( PROPERTIES = (
ROUTER_ID, SUBNET_ID, PORT_ID, ROUTER_ID, SUBNET_ID, SUBNET, PORT_ID,
) = ( ) = (
'router_id', 'subnet_id', 'port_id', 'router_id', 'subnet_id', 'subnet', 'port_id',
) )
properties_schema = { properties_schema = {
@ -189,33 +189,54 @@ class RouterInterface(neutron.NeutronResource):
), ),
SUBNET_ID: properties.Schema( SUBNET_ID: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('The subnet id, either subnet_id or port_id should be ' support_status=support.SupportStatus(
support.DEPRECATED,
_('Use property %s.') % SUBNET)
),
SUBNET: properties.Schema(
properties.Schema.STRING,
_('The subnet, either subnet or port_id should be '
'specified.') 'specified.')
), ),
PORT_ID: properties.Schema( PORT_ID: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('The port id, either subnet_id or port_id should be specified.') _('The port id, either subnet or port_id should be specified.')
), ),
} }
@staticmethod
def _validate_depr_subnet_keys(properties, subnet_key, depr_subnet_key):
subnet_value = properties.get(subnet_key)
subnet_id_value = properties.get(depr_subnet_key)
if subnet_value and subnet_id_value:
raise exception.ResourcePropertyConflict(subnet_key,
subnet_key)
if not subnet_value and not subnet_id_value:
return False
return True
def validate(self): def validate(self):
''' '''
Validate any of the provided params Validate any of the provided params
''' '''
super(RouterInterface, self).validate() super(RouterInterface, self).validate()
subnet_id = self.properties.get(self.SUBNET_ID)
prop_subnet_exists = self._validate_depr_subnet_keys(
self.properties, self.SUBNET, self.SUBNET_ID)
port_id = self.properties.get(self.PORT_ID) port_id = self.properties.get(self.PORT_ID)
if subnet_id and port_id: if prop_subnet_exists and port_id:
raise exception.ResourcePropertyConflict(self.SUBNET_ID, raise exception.ResourcePropertyConflict(self.SUBNET,
self.PORT_ID) self.PORT_ID)
if not subnet_id and not port_id: if not prop_subnet_exists and not port_id:
msg = 'Either subnet_id or port_id must be specified.' msg = 'Either subnet or port_id must be specified.'
raise exception.StackValidationFailed(message=msg) raise exception.StackValidationFailed(message=msg)
def handle_create(self): def handle_create(self):
router_id = self.properties.get(self.ROUTER_ID) router_id = self.properties.get(self.ROUTER_ID)
key = self.SUBNET_ID key = 'subnet_id'
value = self.properties.get(key) value = self._resolve_subnet(
self.neutron(), dict(self.properties), self.SUBNET, key)
if not value: if not value:
key = self.PORT_ID key = self.PORT_ID
value = self.properties.get(key) value = self.properties.get(key)
@ -250,9 +271,9 @@ class RouterGateway(neutron.NeutronResource):
) )
PROPERTIES = ( PROPERTIES = (
ROUTER_ID, NETWORK_ID, ROUTER_ID, NETWORK_ID, NETWORK,
) = ( ) = (
'router_id', 'network_id', 'router_id', 'network_id', 'network'
) )
properties_schema = { properties_schema = {
@ -263,34 +284,52 @@ class RouterGateway(neutron.NeutronResource):
), ),
NETWORK_ID: properties.Schema( NETWORK_ID: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('ID of the external network for the gateway.'), support_status=support.SupportStatus(
required=True support.DEPRECATED,
_('Use property %s.') % NETWORK),
required=False
), ),
NETWORK: properties.Schema(
properties.Schema.STRING,
_('external network for the gateway.'),
required=False
),
} }
def validate(self):
super(RouterGateway, self).validate()
self._validate_depr_property_required(
self.properties, self.NETWORK, self.NETWORK_ID)
def add_dependencies(self, deps): def add_dependencies(self, deps):
super(RouterGateway, self).add_dependencies(deps) super(RouterGateway, self).add_dependencies(deps)
for resource in self.stack.itervalues(): for resource in self.stack.itervalues():
# depend on any RouterInterface in this template with the same # depend on any RouterInterface in this template with the same
# router_id as this router_id # router_id as this router_id
if (resource.has_interface('OS::Neutron::RouterInterface') and if resource.has_interface('OS::Neutron::RouterInterface'):
resource.properties.get(RouterInterface.ROUTER_ID) == dep_router_id = resource.properties.get(
self.properties.get(self.ROUTER_ID)): RouterInterface.ROUTER_ID)
deps += (self, resource) router_id = self.properties.get(self.ROUTER_ID)
if dep_router_id == router_id:
deps += (self, resource)
# depend on any subnet in this template with the same network_id # depend on any subnet in this template with the same network_id
# as this network_id, as the gateway implicitly creates a port # as this network_id, as the gateway implicitly creates a port
# on that subnet # on that subnet
elif (resource.has_interface('OS::Neutron::Subnet') and if resource.has_interface('OS::Neutron::Subnet'):
resource.properties.get(subnet.Subnet.NETWORK_ID) == dep_network = resource.properties.get(
self.properties.get(self.NETWORK_ID)): subnet.Subnet.NETWORK) or resource.properties.get(
deps += (self, resource) subnet.Subnet.NETWORK_ID)
network = self.properties.get(
self.NETWORK) or self.properties.get(self.NETWORK_ID)
if dep_network == network:
deps += (self, resource)
def handle_create(self): def handle_create(self):
router_id = self.properties.get(self.ROUTER_ID) router_id = self.properties.get(self.ROUTER_ID)
network_id = neutronV20.find_resourceid_by_name_or_id( network_id = self._resolve_network(
self.neutron(), self.neutron(), dict(self.properties), self.NETWORK,
'network', 'network_id')
self.properties.get(self.NETWORK_ID))
self.neutron().add_gateway_router( self.neutron().add_gateway_router(
router_id, router_id,
{'network_id': network_id}) {'network_id': network_id})

View File

@ -15,6 +15,7 @@ from heat.engine import clients
from heat.engine import constraints from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
from heat.engine import support
if clients.neutronclient is not None: if clients.neutronclient is not None:
from neutronclient.common.exceptions import NeutronClientException from neutronclient.common.exceptions import NeutronClientException
@ -23,11 +24,11 @@ if clients.neutronclient is not None:
class Subnet(neutron.NeutronResource): class Subnet(neutron.NeutronResource):
PROPERTIES = ( PROPERTIES = (
NETWORK_ID, CIDR, VALUE_SPECS, NAME, IP_VERSION, NETWORK_ID, NETWORK, CIDR, VALUE_SPECS, NAME, IP_VERSION,
DNS_NAMESERVERS, GATEWAY_IP, ENABLE_DHCP, ALLOCATION_POOLS, DNS_NAMESERVERS, GATEWAY_IP, ENABLE_DHCP, ALLOCATION_POOLS,
TENANT_ID, HOST_ROUTES, TENANT_ID, HOST_ROUTES,
) = ( ) = (
'network_id', 'cidr', 'value_specs', 'name', 'ip_version', 'network_id', 'network', 'cidr', 'value_specs', 'name', 'ip_version',
'dns_nameservers', 'gateway_ip', 'enable_dhcp', 'allocation_pools', 'dns_nameservers', 'gateway_ip', 'enable_dhcp', 'allocation_pools',
'tenant_id', 'host_routes', 'tenant_id', 'host_routes',
) )
@ -46,9 +47,16 @@ class Subnet(neutron.NeutronResource):
properties_schema = { properties_schema = {
NETWORK_ID: properties.Schema( NETWORK_ID: properties.Schema(
properties.Schema.STRING,
support_status=support.SupportStatus(
support.DEPRECATED,
_('Use property %s.') % NETWORK),
required=False
),
NETWORK: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('The ID of the attached network.'), _('The ID of the attached network.'),
required=True required=False
), ),
CIDR: properties.Schema( CIDR: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
@ -160,11 +168,17 @@ class Subnet(neutron.NeutronResource):
if props.get(cls.GATEWAY_IP) == '': if props.get(cls.GATEWAY_IP) == '':
props[cls.GATEWAY_IP] = None props[cls.GATEWAY_IP] = None
def validate(self):
super(Subnet, self).validate()
self._validate_depr_property_required(self.properties,
self.NETWORK, self.NETWORK_ID)
def handle_create(self): def handle_create(self):
props = self.prepare_properties( props = self.prepare_properties(
self.properties, self.properties,
self.physical_resource_name()) self.physical_resource_name())
self._resolve_network(
self.neutron(), props, self.NETWORK, 'network_id')
self._null_gateway_ip(props) self._null_gateway_ip(props)
subnet = self.neutron().create_subnet({'subnet': props})['subnet'] subnet = self.neutron().create_subnet({'subnet': props})['subnet']

View File

@ -15,6 +15,7 @@ from heat.engine import clients
from heat.engine import constraints from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine.resources.neutron import neutron from heat.engine.resources.neutron import neutron
from heat.engine import support
if clients.neutronclient is not None: if clients.neutronclient is not None:
from neutronclient.common.exceptions import NeutronClientException from neutronclient.common.exceptions import NeutronClientException
@ -26,9 +27,11 @@ class VPNService(neutron.NeutronResource):
""" """
PROPERTIES = ( PROPERTIES = (
NAME, DESCRIPTION, ADMIN_STATE_UP, SUBNET_ID, ROUTER_ID, NAME, DESCRIPTION, ADMIN_STATE_UP,
SUBNET_ID, SUBNET, ROUTER_ID,
) = ( ) = (
'name', 'description', 'admin_state_up', 'subnet_id', 'router_id', 'name', 'description', 'admin_state_up',
'subnet_id', 'subnet', 'router_id',
) )
properties_schema = { properties_schema = {
@ -50,9 +53,15 @@ class VPNService(neutron.NeutronResource):
), ),
SUBNET_ID: properties.Schema( SUBNET_ID: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('Unique identifier for the subnet in which the vpn service ' support_status=support.SupportStatus(
'will be created.'), support.DEPRECATED,
required=True _('Use property %s.') % SUBNET),
required=False
),
SUBNET: properties.Schema(
properties.Schema.STRING,
_('Subnet in which the vpn service will be created.'),
required=False
), ),
ROUTER_ID: properties.Schema( ROUTER_ID: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
@ -81,10 +90,18 @@ class VPNService(neutron.NeutronResource):
def _show_resource(self): def _show_resource(self):
return self.neutron().show_vpnservice(self.resource_id)['vpnservice'] return self.neutron().show_vpnservice(self.resource_id)['vpnservice']
def validate(self):
super(VPNService, self).validate()
self._validate_depr_property_required(
self.properties, self.SUBNET, self.SUBNET_ID)
def handle_create(self): def handle_create(self):
props = self.prepare_properties( props = self.prepare_properties(
self.properties, self.properties,
self.physical_resource_name()) self.physical_resource_name())
self._resolve_subnet(
self.neutron(), props,
self.SUBNET, 'subnet_id')
vpnservice = self.neutron().create_vpnservice({'vpnservice': props})[ vpnservice = self.neutron().create_vpnservice({'vpnservice': props})[
'vpnservice'] 'vpnservice']
self.resource_id_set(vpnservice['id']) self.resource_id_set(vpnservice['id'])

View File

@ -22,6 +22,7 @@ from heat.engine import clients
from heat.engine import properties from heat.engine import properties
from heat.engine import resource from heat.engine import resource
from heat.engine.resources.neutron import net from heat.engine.resources.neutron import net
from heat.engine.resources.neutron import neutron
from heat.engine.resources.neutron.neutron import NeutronResource as qr from heat.engine.resources.neutron.neutron import NeutronResource as qr
from heat.engine.resources.neutron import provider_net from heat.engine.resources.neutron import provider_net
from heat.engine.resources.neutron import router from heat.engine.resources.neutron import router
@ -61,6 +62,85 @@ neutron_template = '''
"admin_state_up": false "admin_state_up": false
} }
}, },
"subnet": {
"Type": "OS::Neutron::Subnet",
"Properties": {
"network": { "Ref" : "network" },
"tenant_id": "c1210485b2424d48804aad5d39c61b8f",
"ip_version": 4,
"cidr": "10.0.3.0/24",
"allocation_pools": [{"start": "10.0.3.20", "end": "10.0.3.150"}],
"host_routes": [
{"destination": "10.0.4.0/24", "nexthop": "10.0.3.20"}],
"dns_nameservers": ["8.8.8.8"]
}
},
"port": {
"Type": "OS::Neutron::Port",
"Properties": {
"device_id": "d6b4d3a5-c700-476f-b609-1493dd9dadc0",
"name": "port1",
"network": { "Ref" : "network" },
"fixed_ips": [{
"subnet": { "Ref" : "subnet" },
"ip_address": "10.0.3.21"
}]
}
},
"port2": {
"Type": "OS::Neutron::Port",
"Properties": {
"name": "port2",
"network": { "Ref" : "network" }
}
},
"router": {
"Type": "OS::Neutron::Router",
"Properties": {
"l3_agent_id": "792ff887-6c85-4a56-b518-23f24fa65581"
}
},
"router_interface": {
"Type": "OS::Neutron::RouterInterface",
"Properties": {
"router_id": { "Ref" : "router" },
"subnet": { "Ref" : "subnet" }
}
},
"gateway": {
"Type": "OS::Neutron::RouterGateway",
"Properties": {
"router_id": { "Ref" : "router" },
"network": { "Ref" : "network" }
}
}
}
}
'''
neutron_template_deprecated = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test Neutron resources",
"Parameters" : {},
"Resources" : {
"network": {
"Type": "OS::Neutron::Net",
"Properties": {
"name": "the_network",
"tenant_id": "c1210485b2424d48804aad5d39c61b8f",
"shared": true
}
},
"unnamed_network": {
"Type": "OS::Neutron::Net"
},
"admin_down_network": {
"Type": "OS::Neutron::Net",
"Properties": {
"admin_state_up": false
}
},
"subnet": { "subnet": {
"Type": "OS::Neutron::Subnet", "Type": "OS::Neutron::Subnet",
"Properties": { "Properties": {
@ -156,7 +236,7 @@ neutron_external_gateway_template = '''
} }
''' '''
neutron_floating_template = ''' neutron_floating_template_deprecated = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test Neutron resources", "Description" : "Template to test Neutron resources",
@ -199,7 +279,50 @@ neutron_floating_template = '''
} }
''' '''
neutron_port_template = ''' neutron_floating_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test Neutron resources",
"Parameters" : {},
"Resources" : {
"port_floating": {
"Type": "OS::Neutron::Port",
"Properties": {
"network": "xyz1234",
"fixed_ips": [{
"subnet": "sub1234",
"ip_address": "10.0.0.10"
}]
}
},
"floating_ip": {
"Type": "OS::Neutron::FloatingIP",
"Properties": {
"floating_network": "abcd1234",
}
},
"floating_ip_assoc": {
"Type": "OS::Neutron::FloatingIPAssociation",
"Properties": {
"floatingip_id": { "Ref" : "floating_ip" },
"port_id": { "Ref" : "port_floating" }
}
},
"router": {
"Type": "OS::Neutron::Router"
},
"gateway": {
"Type": "OS::Neutron::RouterGateway",
"Properties": {
"router_id": { "Ref" : "router" },
"network": "abcd1234"
}
}
}
}
'''
neutron_port_template_deprecated = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test Neutron resources", "Description" : "Template to test Neutron resources",
@ -220,6 +343,28 @@ neutron_port_template = '''
} }
''' '''
neutron_port_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test Neutron resources",
"Parameters" : {},
"Resources" : {
"port": {
"Type": "OS::Neutron::Port",
"Properties": {
"network": "net1234",
"fixed_ips": [{
"subnet": "sub1234",
"ip_address": "10.0.3.21"
}],
"device_owner": "network:dhcp"
}
}
}
}
'''
neutron_port_with_address_pair_template = ''' neutron_port_with_address_pair_template = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
@ -229,7 +374,7 @@ neutron_port_with_address_pair_template = '''
"port": { "port": {
"Type": "OS::Neutron::Port", "Type": "OS::Neutron::Port",
"Properties": { "Properties": {
"network_id": "abcd1234", "network": "abcd1234",
"allowed_address_pairs": [{ "allowed_address_pairs": [{
"ip_address": "10.0.3.21", "ip_address": "10.0.3.21",
"mac_address": "00-B0-D0-86-BB-F7" "mac_address": "00-B0-D0-86-BB-F7"
@ -330,6 +475,19 @@ class NeutronTest(HeatTestCase):
vs['foo'] = '1234' vs['foo'] = '1234'
self.assertIsNone(qr.validate_properties(p)) self.assertIsNone(qr.validate_properties(p))
def test_validate_depr_properties_required(self):
data = {'network_id': '1234',
'network': 'abc'}
p = properties.Properties(subnet.Subnet.properties_schema, data)
self.assertRaises(exception.ResourcePropertyConflict,
qr._validate_depr_property_required,
p, 'network', 'network_id')
data = {}
p = properties.Properties(subnet.Subnet.properties_schema, data)
self.assertRaises(exception.StackValidationFailed,
qr._validate_depr_property_required,
p, 'network', 'network_id')
def test_prepare_properties(self): def test_prepare_properties(self):
data = {'admin_state_up': False, data = {'admin_state_up': False,
'value_specs': {'router:external': True}} 'value_specs': {'router:external': True}}
@ -570,6 +728,7 @@ class NeutronNetTest(HeatTestCase):
@skipIf(neutronclient is None, 'neutronclient unavailable') @skipIf(neutronclient is None, 'neutronclient unavailable')
class NeutronProviderNetTest(HeatTestCase): class NeutronProviderNetTest(HeatTestCase):
def setUp(self): def setUp(self):
super(NeutronProviderNetTest, self).setUp() super(NeutronProviderNetTest, self).setUp()
self.m.StubOutWithMock(neutronclient.Client, 'create_network') self.m.StubOutWithMock(neutronclient.Client, 'create_network')
@ -698,17 +857,86 @@ class NeutronSubnetTest(HeatTestCase):
self.m.StubOutWithMock(neutronclient.Client, 'delete_subnet') self.m.StubOutWithMock(neutronclient.Client, 'delete_subnet')
self.m.StubOutWithMock(neutronclient.Client, 'show_subnet') self.m.StubOutWithMock(neutronclient.Client, 'show_subnet')
self.m.StubOutWithMock(neutronclient.Client, 'update_subnet') self.m.StubOutWithMock(neutronclient.Client, 'update_subnet')
self.m.StubOutWithMock(neutron.neutronV20,
'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
def create_subnet(self, t, stack, resource_name): def create_subnet(self, t, stack, resource_name):
rsrc = subnet.Subnet('test_subnet', t['Resources'][resource_name], rsrc = subnet.Subnet('test_subnet', t['Resources'][resource_name],
stack) stack)
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
return rsrc return rsrc
def test_subnet(self): def test_subnet(self):
t = self._test_subnet()
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'None'
).AndReturn('None')
stack = utils.parse_stack(t)
rsrc = self.create_subnet(t, stack, 'subnet')
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
rsrc.validate()
ref_id = rsrc.FnGetRefId()
self.assertEqual('91e47a57-7508-46fe-afc9-fc454e8580e1', ref_id)
self.assertIsNone(rsrc.FnGetAtt('network_id'))
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766',
rsrc.FnGetAtt('network_id'))
self.assertEqual('8.8.8.8', rsrc.FnGetAtt('dns_nameservers')[0])
# assert the dependency (implicit or explicit) between the ports
# and the subnet
self.assertIn(stack['port'], stack.dependencies[stack['subnet']])
self.assertIn(stack['port2'], stack.dependencies[stack['subnet']])
update_snippet = {
"Type": "OS::Neutron::Subnet",
"Properties": {
"name": 'mysubnet',
"network": {"Ref": "network"},
"tenant_id": "c1210485b2424d48804aad5d39c61b8f",
"ip_version": 4,
"cidr": "10.0.3.0/24",
"allocation_pools": [
{"start": "10.0.3.20", "end": "10.0.3.150"}],
"dns_nameservers": ["8.8.8.8", "192.168.1.254"]
}
}
rsrc.handle_update(stack.resolve_static_data(update_snippet), {}, {})
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again')
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
self.m.VerifyAll()
def test_subnet_deprecated(self):
t = self._test_subnet(resolve_neutron=False)
stack = utils.parse_stack(t)
rsrc = self.create_subnet(t, stack, 'subnet')
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
rsrc.validate()
ref_id = rsrc.FnGetRefId()
self.assertEqual('91e47a57-7508-46fe-afc9-fc454e8580e1', ref_id)
self.assertIsNone(rsrc.FnGetAtt('network_id'))
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766',
rsrc.FnGetAtt('network_id'))
self.assertEqual('8.8.8.8', rsrc.FnGetAtt('dns_nameservers')[0])
# assert the dependency (implicit or explicit) between the ports
# and the subnet
self.assertIn(stack['port'], stack.dependencies[stack['subnet']])
self.assertIn(stack['port2'], stack.dependencies[stack['subnet']])
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again')
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
self.m.VerifyAll()
def _test_subnet(self, resolve_neutron=True):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutronclient.Client.create_subnet({ neutronclient.Client.create_subnet({
@ -769,16 +997,6 @@ class NeutronSubnetTest(HeatTestCase):
neutronclient.Client.show_subnet( neutronclient.Client.show_subnet(
'91e47a57-7508-46fe-afc9-fc454e8580e1').AndReturn(sn) '91e47a57-7508-46fe-afc9-fc454e8580e1').AndReturn(sn)
# Update script
neutronclient.Client.update_subnet(
'91e47a57-7508-46fe-afc9-fc454e8580e1',
{'subnet': {
'dns_nameservers': ['8.8.8.8', '192.168.1.254'],
'name': 'mysubnet',
'enable_dhcp': True
}}
)
# Delete script # Delete script
neutronclient.Client.delete_subnet( neutronclient.Client.delete_subnet(
'91e47a57-7508-46fe-afc9-fc454e8580e1' '91e47a57-7508-46fe-afc9-fc454e8580e1'
@ -792,51 +1010,32 @@ class NeutronSubnetTest(HeatTestCase):
'91e47a57-7508-46fe-afc9-fc454e8580e1' '91e47a57-7508-46fe-afc9-fc454e8580e1'
).AndRaise(qe.NeutronClientException(status_code=404)) ).AndRaise(qe.NeutronClientException(status_code=404))
self.m.ReplayAll() if resolve_neutron:
t = template_format.parse(neutron_template) t = template_format.parse(neutron_template)
stack = utils.parse_stack(t) # Update script
rsrc = self.create_subnet(t, stack, 'subnet') neutronclient.Client.update_subnet(
'91e47a57-7508-46fe-afc9-fc454e8580e1',
{'subnet': {
'dns_nameservers': ['8.8.8.8', '192.168.1.254'],
'name': 'mysubnet',
'enable_dhcp': True
}}
)
rsrc.validate() else:
t = template_format.parse(neutron_template_deprecated)
ref_id = rsrc.FnGetRefId() return t
self.assertEqual('91e47a57-7508-46fe-afc9-fc454e8580e1', ref_id)
self.assertIsNone(rsrc.FnGetAtt('network_id'))
self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766',
rsrc.FnGetAtt('network_id'))
self.assertEqual('8.8.8.8', rsrc.FnGetAtt('dns_nameservers')[0])
# assert the dependency (implicit or explicit) between the ports
# and the subnet
self.assertIn(stack['port'], stack.dependencies[stack['subnet']])
self.assertIn(stack['port2'], stack.dependencies[stack['subnet']])
update_snippet = {
"Type": "OS::Neutron::Subnet",
"Properties": {
"name": 'mysubnet',
"network_id": {"Ref": "network"},
"tenant_id": "c1210485b2424d48804aad5d39c61b8f",
"ip_version": 4,
"cidr": "10.0.3.0/24",
"allocation_pools": [
{"start": "10.0.3.20", "end": "10.0.3.150"}],
"dns_nameservers": ["8.8.8.8", "192.168.1.254"],
'host_routes': [
{'destination': u'10.0.4.0/24', 'nexthop': u'10.0.3.20'}]
}
}
rsrc.handle_update(stack.resolve_static_data(update_snippet), {}, {})
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again')
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
self.m.VerifyAll()
def test_subnet_disable_dhcp(self): def test_subnet_disable_dhcp(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'None'
).AndReturn('None')
neutronclient.Client.create_subnet({ neutronclient.Client.create_subnet({
'subnet': { 'subnet': {
'name': utils.PhysName('test_stack', 'test_subnet'), 'name': utils.PhysName('test_stack', 'test_subnet'),
@ -903,6 +1102,9 @@ class NeutronSubnetTest(HeatTestCase):
stack = utils.parse_stack(t) stack = utils.parse_stack(t)
rsrc = self.create_subnet(t, stack, 'subnet') rsrc = self.create_subnet(t, stack, 'subnet')
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
rsrc.validate() rsrc.validate()
ref_id = rsrc.FnGetRefId() ref_id = rsrc.FnGetRefId()
@ -956,7 +1158,7 @@ class NeutronSubnetTest(HeatTestCase):
@skipIf(neutronclient is None, 'neutronclient unavailable') @skipIf(neutronclient is None, 'neutronclient unavailable')
class NeutronRouterTest(HeatTestCase): class NeutronRouterTest(HeatTestCase):
@skipIf(router.neutronV20 is None, "Missing Neutron v2_0") @skipIf(neutron.neutronV20 is None, "Missing Neutron v2_0")
def setUp(self): def setUp(self):
super(NeutronRouterTest, self).setUp() super(NeutronRouterTest, self).setUp()
self.m.StubOutWithMock(neutronclient.Client, 'create_router') self.m.StubOutWithMock(neutronclient.Client, 'create_router')
@ -973,7 +1175,7 @@ class NeutronRouterTest(HeatTestCase):
'remove_router_from_l3_agent') 'remove_router_from_l3_agent')
self.m.StubOutWithMock(neutronclient.Client, self.m.StubOutWithMock(neutronclient.Client,
'list_l3_agent_hosting_routers') 'list_l3_agent_hosting_routers')
self.m.StubOutWithMock(router.neutronV20, self.m.StubOutWithMock(neutron.neutronV20,
'find_resourceid_by_name_or_id') 'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
@ -1180,6 +1382,12 @@ class NeutronRouterTest(HeatTestCase):
self.assertIn(stack['router'], deps) self.assertIn(stack['router'], deps)
def test_router_interface(self): def test_router_interface(self):
self._test_router_interface()
def test_router_interface_deprecated(self):
self._test_router_interface(resolve_neutron=False)
def _test_router_interface(self, resolve_neutron=True):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutronclient.Client.add_interface_router( neutronclient.Client.add_interface_router(
@ -1194,16 +1402,27 @@ class NeutronRouterTest(HeatTestCase):
'3e46229d-8fce-4733-819a-b5fe630550f8', '3e46229d-8fce-4733-819a-b5fe630550f8',
{'subnet_id': '91e47a57-7508-46fe-afc9-fc454e8580e1'} {'subnet_id': '91e47a57-7508-46fe-afc9-fc454e8580e1'}
).AndRaise(qe.NeutronClientException(status_code=404)) ).AndRaise(qe.NeutronClientException(status_code=404))
self.m.ReplayAll()
t = template_format.parse(neutron_template) t = template_format.parse(neutron_template)
stack = utils.parse_stack(t) stack = utils.parse_stack(t)
if resolve_neutron:
rsrc = self.create_router_interface( neutron.neutronV20.find_resourceid_by_name_or_id(
t, stack, 'router_interface', properties={ mox.IsA(neutronclient.Client),
'router_id': '3e46229d-8fce-4733-819a-b5fe630550f8', 'subnet',
'subnet_id': '91e47a57-7508-46fe-afc9-fc454e8580e1' '91e47a57-7508-46fe-afc9-fc454e8580e1'
}) ).AndReturn('91e47a57-7508-46fe-afc9-fc454e8580e1')
self.m.ReplayAll()
rsrc = self.create_router_interface(
t, stack, 'router_interface', properties={
'router_id': '3e46229d-8fce-4733-819a-b5fe630550f8',
'subnet': '91e47a57-7508-46fe-afc9-fc454e8580e1'
})
else:
self.m.ReplayAll()
rsrc = self.create_router_interface(
t, stack, 'router_interface', properties={
'router_id': '3e46229d-8fce-4733-819a-b5fe630550f8',
'subnet_id': '91e47a57-7508-46fe-afc9-fc454e8580e1'
})
scheduler.TaskRunner(rsrc.delete)() scheduler.TaskRunner(rsrc.delete)()
rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again') rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again')
scheduler.TaskRunner(rsrc.delete)() scheduler.TaskRunner(rsrc.delete)()
@ -1304,13 +1523,13 @@ class NeutronRouterTest(HeatTestCase):
stack = utils.parse_stack(t) stack = utils.parse_stack(t)
res = router.RouterInterface('router_interface', json, stack) res = router.RouterInterface('router_interface', json, stack)
ex = self.assertRaises(exception.StackValidationFailed, res.validate) ex = self.assertRaises(exception.StackValidationFailed, res.validate)
self.assertEqual("Either subnet_id or port_id must be specified.", self.assertEqual("Either subnet or port_id must be specified.",
str(ex)) str(ex))
def test_gateway_router(self): def test_gateway_router(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
router.neutronV20.find_resourceid_by_name_or_id( neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client), mox.IsA(neutronclient.Client),
'network', 'network',
'fc68ea2c-b60b-4b4f-bd82-94ec81110766' 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
@ -1332,7 +1551,7 @@ class NeutronRouterTest(HeatTestCase):
rsrc = self.create_gateway_router( rsrc = self.create_gateway_router(
t, stack, 'gateway', properties={ t, stack, 'gateway', properties={
'router_id': '3e46229d-8fce-4733-819a-b5fe630550f8', 'router_id': '3e46229d-8fce-4733-819a-b5fe630550f8',
'network_id': 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' 'network': 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
}) })
scheduler.TaskRunner(rsrc.delete)() scheduler.TaskRunner(rsrc.delete)()
@ -1344,7 +1563,7 @@ class NeutronRouterTest(HeatTestCase):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
router.neutronV20.find_resourceid_by_name_or_id( neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client), mox.IsA(neutronclient.Client),
'network', 'network',
'public' 'public'
@ -1426,7 +1645,7 @@ class NeutronRouterTest(HeatTestCase):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
router.neutronV20.find_resourceid_by_name_or_id( neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client), mox.IsA(neutronclient.Client),
'network', 'network',
'public' 'public'
@ -1487,7 +1706,7 @@ class NeutronRouterTest(HeatTestCase):
def test_update_router_gateway_as_property(self): def test_update_router_gateway_as_property(self):
self._create_router_with_gateway() self._create_router_with_gateway()
router.neutronV20.find_resourceid_by_name_or_id( neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client), mox.IsA(neutronclient.Client),
'network', 'network',
'other_public' 'other_public'
@ -1572,9 +1791,17 @@ class NeutronFloatingIPTest(HeatTestCase):
self.m.StubOutWithMock(neutronclient.Client, 'delete_port') self.m.StubOutWithMock(neutronclient.Client, 'delete_port')
self.m.StubOutWithMock(neutronclient.Client, 'update_port') self.m.StubOutWithMock(neutronclient.Client, 'update_port')
self.m.StubOutWithMock(neutronclient.Client, 'show_port') self.m.StubOutWithMock(neutronclient.Client, 'show_port')
self.m.StubOutWithMock(neutron.neutronV20,
'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
def test_floating_ip(self): def test_floating_ip(self):
self._test_floating_ip()
def test_floating_ip_deprecated(self):
self._test_floating_ip(resolve_neutron=False)
def _test_floating_ip(self, resolve_neutron=True):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
@ -1600,14 +1827,24 @@ class NeutronFloatingIPTest(HeatTestCase):
neutronclient.Client.delete_floatingip( neutronclient.Client.delete_floatingip(
'fc68ea2c-b60b-4b4f-bd82-94ec81110766').AndRaise( 'fc68ea2c-b60b-4b4f-bd82-94ec81110766').AndRaise(
qe.NeutronClientException(status_code=404)) qe.NeutronClientException(status_code=404))
self.m.ReplayAll() if resolve_neutron:
t = template_format.parse(neutron_floating_template)
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'abcd1234'
).AndReturn('abcd1234')
else:
t = template_format.parse(neutron_floating_template_deprecated)
t = template_format.parse(neutron_floating_template)
stack = utils.parse_stack(t) stack = utils.parse_stack(t)
# assert the implicit dependency between the floating_ip # assert the implicit dependency between the floating_ip
# and the gateway # and the gateway
self.m.ReplayAll()
deps = stack.dependencies[stack['gateway']] deps = stack.dependencies[stack['gateway']]
self.assertIn(stack['floating_ip'], deps) self.assertIn(stack['floating_ip'], deps)
fip = stack['floating_ip'] fip = stack['floating_ip']
@ -1637,6 +1874,16 @@ class NeutronFloatingIPTest(HeatTestCase):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'xyz1234'
).AndReturn('xyz1234')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub1234'
).AndReturn('sub1234')
neutronclient.Client.create_port({'port': { neutronclient.Client.create_port({'port': {
'network_id': u'xyz1234', 'network_id': u'xyz1234',
'fixed_ips': [ 'fixed_ips': [
@ -1708,7 +1955,7 @@ class NeutronFloatingIPTest(HeatTestCase):
update_snippet = { update_snippet = {
"Type": "OS::Neutron::Port", "Type": "OS::Neutron::Port",
"Properties": { "Properties": {
"network_id": "xyz1234", "network": "xyz1234",
"fixed_ips": [{ "fixed_ips": [{
"subnet_id": "sub1234", "subnet_id": "sub1234",
"ip_address": "10.0.0.11" "ip_address": "10.0.0.11"
@ -1727,6 +1974,21 @@ class NeutronFloatingIPTest(HeatTestCase):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'abcd1234'
).AndReturn('abcd1234')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'xyz1234'
).AndReturn('xyz1234')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub1234'
).AndReturn('sub1234')
neutronclient.Client.create_floatingip({ neutronclient.Client.create_floatingip({
'floatingip': {'floating_network_id': u'abcd1234'} 'floatingip': {'floating_network_id': u'abcd1234'}
}).AndReturn({'floatingip': { }).AndReturn({'floatingip': {
@ -1842,11 +2104,18 @@ class NeutronPortTest(HeatTestCase):
super(NeutronPortTest, self).setUp() super(NeutronPortTest, self).setUp()
self.m.StubOutWithMock(neutronclient.Client, 'create_port') self.m.StubOutWithMock(neutronclient.Client, 'create_port')
self.m.StubOutWithMock(neutronclient.Client, 'show_port') self.m.StubOutWithMock(neutronclient.Client, 'show_port')
self.m.StubOutWithMock(neutron.neutronV20,
'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
def test_missing_subnet_id(self): def test_missing_subnet_id(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'net1234'
).AndReturn('net1234')
neutronclient.Client.create_port({'port': { neutronclient.Client.create_port({'port': {
'network_id': u'net1234', 'network_id': u'net1234',
'fixed_ips': [ 'fixed_ips': [
@ -1869,7 +2138,7 @@ class NeutronPortTest(HeatTestCase):
self.m.ReplayAll() self.m.ReplayAll()
t = template_format.parse(neutron_port_template) t = template_format.parse(neutron_port_template)
t['Resources']['port']['Properties']['fixed_ips'][0].pop('subnet_id') t['Resources']['port']['Properties']['fixed_ips'][0].pop('subnet')
stack = utils.parse_stack(t) stack = utils.parse_stack(t)
port = stack['port'] port = stack['port']
@ -1880,6 +2149,17 @@ class NeutronPortTest(HeatTestCase):
def test_missing_ip_address(self): def test_missing_ip_address(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'net1234'
).AndReturn('net1234')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub1234'
).AndReturn('sub1234')
neutronclient.Client.create_port({'port': { neutronclient.Client.create_port({'port': {
'network_id': u'net1234', 'network_id': u'net1234',
'fixed_ips': [ 'fixed_ips': [
@ -1912,6 +2192,11 @@ class NeutronPortTest(HeatTestCase):
def test_missing_fixed_ips(self): def test_missing_fixed_ips(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'net1234'
).AndReturn('net1234')
neutronclient.Client.create_port({'port': { neutronclient.Client.create_port({'port': {
'network_id': u'net1234', 'network_id': u'net1234',
'name': utils.PhysName('test_stack', 'port'), 'name': utils.PhysName('test_stack', 'port'),
@ -1945,6 +2230,11 @@ class NeutronPortTest(HeatTestCase):
def test_allowed_address_pair(self): def test_allowed_address_pair(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'abcd1234'
).AndReturn('abcd1234')
neutronclient.Client.create_port({'port': { neutronclient.Client.create_port({'port': {
'network_id': u'abcd1234', 'network_id': u'abcd1234',
'allowed_address_pairs': [{ 'allowed_address_pairs': [{
@ -1976,6 +2266,11 @@ class NeutronPortTest(HeatTestCase):
def test_missing_mac_address(self): def test_missing_mac_address(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'abcd1234'
).AndReturn('abcd1234')
neutronclient.Client.create_port({'port': { neutronclient.Client.create_port({'port': {
'network_id': u'abcd1234', 'network_id': u'abcd1234',
'allowed_address_pairs': [{ 'allowed_address_pairs': [{
@ -2009,6 +2304,16 @@ class NeutronPortTest(HeatTestCase):
def test_security_groups(self): def test_security_groups(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'net1234'
).AndReturn('net1234')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub1234'
).AndReturn('sub1234')
neutronclient.Client.create_port({'port': { neutronclient.Client.create_port({'port': {
'network_id': u'net1234', 'network_id': u'net1234',
'security_groups': ['8a2f582a-e1cd-480f-b85d-b02631c10656', 'security_groups': ['8a2f582a-e1cd-480f-b85d-b02631c10656',

View File

@ -20,6 +20,7 @@ from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.engine import clients from heat.engine import clients
from heat.engine.resources.neutron import loadbalancer from heat.engine.resources.neutron import loadbalancer
from heat.engine.resources.neutron import neutron
from heat.engine import scheduler from heat.engine import scheduler
from heat.openstack.common.importutils import try_import from heat.openstack.common.importutils import try_import
from heat.tests.common import HeatTestCase from heat.tests.common import HeatTestCase
@ -58,7 +59,7 @@ pool_template_with_vip_subnet = '''
"Type": "OS::Neutron::Pool", "Type": "OS::Neutron::Pool",
"Properties": { "Properties": {
"protocol": "HTTP", "protocol": "HTTP",
"subnet_id": "sub123", "subnet": "sub123",
"lb_method": "ROUND_ROBIN", "lb_method": "ROUND_ROBIN",
"vip": { "vip": {
"protocol_port": 80, "protocol_port": 80,
@ -69,7 +70,8 @@ pool_template_with_vip_subnet = '''
} }
} }
''' '''
pool_template = '''
pool_template_deprecated = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test load balancer resources", "Description" : "Template to test load balancer resources",
@ -90,6 +92,28 @@ pool_template = '''
} }
''' '''
pool_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test load balancer resources",
"Parameters" : {},
"Resources" : {
"pool": {
"Type": "OS::Neutron::Pool",
"Properties": {
"protocol": "HTTP",
"subnet": "sub123",
"lb_method": "ROUND_ROBIN",
"vip": {
"protocol_port": 80
}
}
}
}
}
'''
member_template = ''' member_template = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
@ -136,7 +160,7 @@ pool_with_session_persistence_template = '''
"Type": "OS::Neutron::Pool", "Type": "OS::Neutron::Pool",
"Properties": { "Properties": {
"protocol": "HTTP", "protocol": "HTTP",
"subnet_id": "sub123", "subnet": "sub123",
"lb_method": "ROUND_ROBIN", "lb_method": "ROUND_ROBIN",
"vip": { "vip": {
"protocol_port": 80, "protocol_port": 80,
@ -338,13 +362,13 @@ class PoolTest(HeatTestCase):
self.m.StubOutWithMock(neutronclient.Client, self.m.StubOutWithMock(neutronclient.Client,
'disassociate_health_monitor') 'disassociate_health_monitor')
self.m.StubOutWithMock(neutronclient.Client, 'create_vip') self.m.StubOutWithMock(neutronclient.Client, 'create_vip')
self.m.StubOutWithMock(loadbalancer.neutronV20, self.m.StubOutWithMock(neutron.neutronV20,
'find_resourceid_by_name_or_id') 'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(neutronclient.Client, 'delete_vip') self.m.StubOutWithMock(neutronclient.Client, 'delete_vip')
self.m.StubOutWithMock(neutronclient.Client, 'show_vip') self.m.StubOutWithMock(neutronclient.Client, 'show_vip')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
def create_pool(self, with_vip_subnet=False): def create_pool(self, resolve_neutron=True, with_vip_subnet=False):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
@ -353,7 +377,10 @@ class PoolTest(HeatTestCase):
'name': utils.PhysName('test_stack', 'pool'), 'name': utils.PhysName('test_stack', 'pool'),
'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}} 'lb_method': 'ROUND_ROBIN', 'admin_state_up': True}}
).AndReturn({'pool': {'id': '5678'}}) ).AndReturn({'pool': {'id': '5678'}})
neutronclient.Client.show_pool('5678').AndReturn(
{'pool': {'status': 'ACTIVE'}})
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'status': 'ACTIVE'}})
stvipvsn = { stvipvsn = {
'vip': { 'vip': {
'protocol': u'HTTP', 'name': 'pool.vip', 'protocol': u'HTTP', 'name': 'pool.vip',
@ -364,38 +391,52 @@ class PoolTest(HeatTestCase):
stvippsn = copy.deepcopy(stvipvsn) stvippsn = copy.deepcopy(stvipvsn)
stvippsn['vip']['subnet_id'] = 'sub123' stvippsn['vip']['subnet_id'] = 'sub123'
if with_vip_subnet: if resolve_neutron and with_vip_subnet:
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub9999'
).AndReturn('sub9999')
snippet = template_format.parse(pool_template_with_vip_subnet)
neutronclient.Client.create_vip(stvipvsn neutronclient.Client.create_vip(stvipvsn
).AndReturn({'vip': {'id': 'xyz'}}) ).AndReturn({'vip': {'id': 'xyz'}})
snippet = template_format.parse(pool_template_with_vip_subnet)
else: elif resolve_neutron and not with_vip_subnet:
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
snippet = template_format.parse(pool_template)
neutronclient.Client.create_vip(stvippsn
).AndReturn({'vip': {'id': 'xyz'}})
else:
snippet = template_format.parse(pool_template_deprecated)
neutronclient.Client.create_vip(stvippsn neutronclient.Client.create_vip(stvippsn
).AndReturn({'vip': {'id': 'xyz'}}) ).AndReturn({'vip': {'id': 'xyz'}})
snippet = template_format.parse(pool_template)
neutronclient.Client.show_pool('5678').AndReturn(
{'pool': {'status': 'ACTIVE'}})
neutronclient.Client.show_vip('xyz').AndReturn(
{'vip': {'status': 'ACTIVE'}})
stack = utils.parse_stack(snippet) stack = utils.parse_stack(snippet)
return loadbalancer.Pool( return loadbalancer.Pool(
'pool', snippet['Resources']['pool'], stack) 'pool', snippet['Resources']['pool'], stack)
def test_create(self): def test_create(self):
rsrc = self.create_pool() self._test_create()
def test_create_deprecated(self):
self._test_create(resolve_neutron=False, with_vip_subnet=False)
def _test_create(self, resolve_neutron=True, with_vip_subnet=False):
rsrc = self.create_pool(resolve_neutron, with_vip_subnet)
self.m.ReplayAll() self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)() scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll() self.m.VerifyAll()
def test_create_with_vip_subnet(self): def test_create_with_vip_subnet(self):
loadbalancer.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub9999'
).AndReturn('sub9999')
rsrc = self.create_pool(with_vip_subnet=True) rsrc = self.create_pool(with_vip_subnet=True)
self.m.ReplayAll() self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)() scheduler.TaskRunner(rsrc.create)()
@ -405,6 +446,12 @@ class PoolTest(HeatTestCase):
def test_create_pending(self): def test_create_pending(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
'pool': { 'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP', 'subnet_id': 'sub123', 'protocol': u'HTTP',
@ -438,6 +485,12 @@ class PoolTest(HeatTestCase):
def test_create_failed_unexpected_status(self): def test_create_failed_unexpected_status(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
'pool': { 'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP', 'subnet_id': 'sub123', 'protocol': u'HTTP',
@ -470,6 +523,12 @@ class PoolTest(HeatTestCase):
def test_create_failed_unexpected_vip_status(self): def test_create_failed_unexpected_vip_status(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
'pool': { 'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP', 'subnet_id': 'sub123', 'protocol': u'HTTP',
@ -504,6 +563,12 @@ class PoolTest(HeatTestCase):
def test_create_failed(self): def test_create_failed(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
'pool': { 'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP', 'subnet_id': 'sub123', 'protocol': u'HTTP',
@ -527,6 +592,11 @@ class PoolTest(HeatTestCase):
def test_create_with_session_persistence(self): def test_create_with_session_persistence(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
'pool': { 'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP', 'subnet_id': 'sub123', 'protocol': u'HTTP',
@ -584,6 +654,12 @@ class PoolTest(HeatTestCase):
def test_properties_are_prepared_for_session_persistence(self): def test_properties_are_prepared_for_session_persistence(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
'pool': { 'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP', 'subnet_id': 'sub123', 'protocol': u'HTTP',
@ -728,6 +804,11 @@ class PoolTest(HeatTestCase):
def test_update_monitors(self): def test_update_monitors(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_pool({ neutronclient.Client.create_pool({
'pool': { 'pool': {
'subnet_id': 'sub123', 'protocol': u'HTTP', 'subnet_id': 'sub123', 'protocol': u'HTTP',

View File

@ -15,12 +15,14 @@
# limitations under the License. # limitations under the License.
from mox import IgnoreArg from mox import IgnoreArg
import mox
from testtools import skipIf from testtools import skipIf
from heat.common import exception from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.engine import clients from heat.engine import clients
from heat.engine.resources.neutron import network_gateway from heat.engine.resources.neutron import network_gateway
from heat.engine.resources.neutron import neutron
from heat.engine import scheduler from heat.engine import scheduler
from heat.openstack.common.importutils import try_import from heat.openstack.common.importutils import try_import
from heat.tests.common import HeatTestCase from heat.tests.common import HeatTestCase
@ -32,6 +34,29 @@ neutronV20 = try_import('neutronclient.neutron.v2_0')
qe = try_import('neutronclient.common.exceptions') qe = try_import('neutronclient.common.exceptions')
gw_template_deprecated = '''
{
'AWSTemplateFormatVersion': '2010-09-09',
'Description': 'Template to test Network Gateway resource',
'Parameters': {},
'Resources': {
'NetworkGateway': {
'Type': 'OS::Neutron::NetworkGateway',
'Properties': {
'name': 'NetworkGateway',
'devices': [{
'id': 'e52148ca-7db9-4ec3-abe6-2c7c0ff316eb',
'interface_name': 'breth1'}],
'connections': [{
'network_id': '6af055d3-26f6-48dd-a597-7611d7e58d35',
'segmentation_type': 'vlan',
'segmentation_id': 10}]
}
}
}
}
'''
gw_template = ''' gw_template = '''
{ {
'AWSTemplateFormatVersion': '2010-09-09', 'AWSTemplateFormatVersion': '2010-09-09',
@ -46,7 +71,7 @@ gw_template = '''
'id': 'e52148ca-7db9-4ec3-abe6-2c7c0ff316eb', 'id': 'e52148ca-7db9-4ec3-abe6-2c7c0ff316eb',
'interface_name': 'breth1'}], 'interface_name': 'breth1'}],
'connections': [{ 'connections': [{
'network_id': '6af055d3-26f6-48dd-a597-7611d7e58d35', 'network': '6af055d3-26f6-48dd-a597-7611d7e58d35',
'segmentation_type': 'vlan', 'segmentation_type': 'vlan',
'segmentation_id': 10}] 'segmentation_id': 10}]
} }
@ -85,9 +110,11 @@ class NeutronNetworkGatewayTest(HeatTestCase):
self.m.StubOutWithMock(neutronclient.Client, self.m.StubOutWithMock(neutronclient.Client,
'disconnect_network_gateway') 'disconnect_network_gateway')
self.m.StubOutWithMock(neutronclient.Client, 'list_networks') self.m.StubOutWithMock(neutronclient.Client, 'list_networks')
self.m.StubOutWithMock(neutron.neutronV20,
'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
def prepare_create_network_gateway(self): def prepare_create_network_gateway(self, resolve_neutron=True):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutronclient.Client.create_network_gateway({ neutronclient.Client.create_network_gateway({
@ -122,17 +149,35 @@ class NeutronNetworkGatewayTest(HeatTestCase):
'port_id': u'32acc49c-899e-44ea-8177-6f4157e12eb4' 'port_id': u'32acc49c-899e-44ea-8177-6f4157e12eb4'
} }
}) })
if resolve_neutron:
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
t = template_format.parse(gw_template)
else:
t = template_format.parse(gw_template_deprecated)
t = template_format.parse(gw_template)
stack = utils.parse_stack(t) stack = utils.parse_stack(t)
rsrc = network_gateway.NetworkGateway( rsrc = network_gateway.NetworkGateway(
'test_network_gateway', 'test_network_gateway',
t['Resources']['NetworkGateway'], stack) t['Resources']['NetworkGateway'], stack)
return rsrc return rsrc
def test_network_gateway_create(self): def _test_network_gateway_create(self, resolve_neutron=True):
rsrc = self.prepare_create_network_gateway() rsrc = self.prepare_create_network_gateway(resolve_neutron)
if resolve_neutron:
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutronclient.Client.disconnect_network_gateway( neutronclient.Client.disconnect_network_gateway(
'ed4c03b9-8251-4c09-acc4-e59ee9e6aa37', { 'ed4c03b9-8251-4c09-acc4-e59ee9e6aa37', {
'network_id': u'6af055d3-26f6-48dd-a597-7611d7e58d35', 'network_id': u'6af055d3-26f6-48dd-a597-7611d7e58d35',
@ -185,8 +230,44 @@ class NeutronNetworkGatewayTest(HeatTestCase):
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll() self.m.VerifyAll()
def test_network_gateway_create_deprecated(self):
self._test_network_gateway_create(resolve_neutron=False)
def test_network_gateway_create(self):
self._test_network_gateway_create()
def test_network_gateway_update(self): def test_network_gateway_update(self):
rsrc = self.prepare_create_network_gateway() rsrc = self.prepare_create_network_gateway()
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'network',
'6af055d3-26f6-48dd-a597-7611d7e58d35'
).AndReturn('6af055d3-26f6-48dd-a597-7611d7e58d35')
neutronclient.Client.update_network_gateway( neutronclient.Client.update_network_gateway(
u'ed4c03b9-8251-4c09-acc4-e59ee9e6aa37', { u'ed4c03b9-8251-4c09-acc4-e59ee9e6aa37', {
@ -301,7 +382,7 @@ class NeutronNetworkGatewayTest(HeatTestCase):
'id': u'e52148ca-7db9-4ec3-abe6-2c7c0ff316eb', 'id': u'e52148ca-7db9-4ec3-abe6-2c7c0ff316eb',
'interface_name': u'breth1'}], 'interface_name': u'breth1'}],
'connections': [{ 'connections': [{
'network_id': '6af055d3-26f6-48dd-a597-7611d7e58d35', 'network': '6af055d3-26f6-48dd-a597-7611d7e58d35',
'segmentation_type': 'vlan', 'segmentation_type': 'vlan',
'segmentation_id': 10}] 'segmentation_id': 10}]
} }
@ -319,14 +400,14 @@ class NeutronNetworkGatewayTest(HeatTestCase):
'id': u'e52148ca-7db9-4ec3-abe6-2c7c0ff316eb', 'id': u'e52148ca-7db9-4ec3-abe6-2c7c0ff316eb',
'interface_name': u'breth1'}], 'interface_name': u'breth1'}],
'connections': [{ 'connections': [{
'network_id': u'6af055d3-26f6-48dd-a597-7611d7e58d35', 'network': u'6af055d3-26f6-48dd-a597-7611d7e58d35',
'segmentation_type': u'flat', 'segmentation_type': u'flat',
'segmentation_id': 0}] 'segmentation_id': 0}]
} }
} }
prop_diff = { prop_diff = {
'connections': [{ 'connections': [{
'network_id': u'6af055d3-26f6-48dd-a597-7611d7e58d35', 'network': u'6af055d3-26f6-48dd-a597-7611d7e58d35',
'segmentation_type': u'flat', 'segmentation_type': u'flat',
'segmentation_id': 0}] 'segmentation_id': 0}]
} }

View File

@ -12,6 +12,7 @@
# under the License. # under the License.
import copy import copy
import mox
from testtools import skipIf from testtools import skipIf
@ -19,6 +20,7 @@ from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.engine import clients from heat.engine import clients
from heat.engine.resources.neutron import vpnservice from heat.engine.resources.neutron import vpnservice
from heat.engine.resources.neutron import neutron
from heat.engine import scheduler from heat.engine import scheduler
from heat.openstack.common.importutils import try_import from heat.openstack.common.importutils import try_import
from heat.tests.common import HeatTestCase from heat.tests.common import HeatTestCase
@ -28,6 +30,26 @@ from heat.tests import utils
neutronclient = try_import('neutronclient.v2_0.client') neutronclient = try_import('neutronclient.v2_0.client')
vpnservice_template_deprecated = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test VPN service resource",
"Parameters" : {},
"Resources" : {
"VPNService" : {
"Type" : "OS::Neutron::VPNService",
"Properties" : {
"name" : "VPNService",
"description" : "My new VPN service",
"admin_state_up" : true,
"router_id" : "rou123",
"subnet_id" : "sub123"
}
}
}
}
'''
vpnservice_template = ''' vpnservice_template = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
@ -41,7 +63,7 @@ vpnservice_template = '''
"description" : "My new VPN service", "description" : "My new VPN service",
"admin_state_up" : true, "admin_state_up" : true,
"router_id" : "rou123", "router_id" : "rou123",
"subnet_id" : "sub123" "subnet" : "sub123"
} }
} }
} }
@ -152,21 +174,38 @@ class VPNServiceTest(HeatTestCase):
self.m.StubOutWithMock(neutronclient.Client, 'delete_vpnservice') self.m.StubOutWithMock(neutronclient.Client, 'delete_vpnservice')
self.m.StubOutWithMock(neutronclient.Client, 'show_vpnservice') self.m.StubOutWithMock(neutronclient.Client, 'show_vpnservice')
self.m.StubOutWithMock(neutronclient.Client, 'update_vpnservice') self.m.StubOutWithMock(neutronclient.Client, 'update_vpnservice')
self.m.StubOutWithMock(neutron.neutronV20,
'find_resourceid_by_name_or_id')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone') self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
def create_vpnservice(self): def create_vpnservice(self, resolve_neutron=True):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
if resolve_neutron:
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
snippet = template_format.parse(vpnservice_template)
else:
snippet = template_format.parse(vpnservice_template_deprecated)
neutronclient.Client.create_vpnservice( neutronclient.Client.create_vpnservice(
self.VPN_SERVICE_CONF).AndReturn({'vpnservice': {'id': 'vpn123'}}) self.VPN_SERVICE_CONF).AndReturn({'vpnservice': {'id': 'vpn123'}})
snippet = template_format.parse(vpnservice_template)
self.stack = utils.parse_stack(snippet) self.stack = utils.parse_stack(snippet)
return vpnservice.VPNService('vpnservice', return vpnservice.VPNService('vpnservice',
snippet['Resources']['VPNService'], snippet['Resources']['VPNService'],
self.stack) self.stack)
def test_create_deprecated(self):
self._test_create(resolve_neutron=False)
def test_create(self): def test_create(self):
rsrc = self.create_vpnservice() self._test_create()
def _test_create(self, resolve_neutron=True):
rsrc = self.create_vpnservice(resolve_neutron)
self.m.ReplayAll() self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)() scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
@ -175,6 +214,12 @@ class VPNServiceTest(HeatTestCase):
def test_create_failed(self): def test_create_failed(self):
clients.OpenStackClients.keystone().AndReturn( clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient()) fakes.FakeKeystoneClient())
neutron.neutronV20.find_resourceid_by_name_or_id(
mox.IsA(neutronclient.Client),
'subnet',
'sub123'
).AndReturn('sub123')
neutronclient.Client.create_vpnservice(self.VPN_SERVICE_CONF).AndRaise( neutronclient.Client.create_vpnservice(self.VPN_SERVICE_CONF).AndRaise(
vpnservice.NeutronClientException()) vpnservice.NeutronClientException())
self.m.ReplayAll() self.m.ReplayAll()