Merge "Enable to specify network for Trove Cluster"

This commit is contained in:
Zuul 2017-12-18 05:41:41 +00:00 committed by Gerrit Code Review
commit a96b876764
4 changed files with 192 additions and 12 deletions

View File

@ -20,6 +20,7 @@ from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine import resource from heat.engine import resource
from heat.engine import support from heat.engine import support
from heat.engine import translation
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -62,9 +63,15 @@ class TroveCluster(resource.Resource):
) )
_INSTANCE_KEYS = ( _INSTANCE_KEYS = (
FLAVOR, VOLUME_SIZE, FLAVOR, VOLUME_SIZE, NETWORKS,
) = ( ) = (
'flavor', 'volume_size', 'flavor', 'volume_size', 'networks',
)
_NICS_KEYS = (
NET, PORT, V4_FIXED_IP
) = (
'network', 'port', 'fixed_ip'
) )
ATTRIBUTES = ( ATTRIBUTES = (
@ -121,10 +128,50 @@ class TroveCluster(resource.Resource):
constraints=[ constraints=[
constraints.Range(1, 150), constraints.Range(1, 150),
] ]
) ),
NETWORKS: properties.Schema(
properties.Schema.LIST,
_("List of network interfaces to create on instance."),
support_status=support.SupportStatus(version='10.0.0'),
default=[],
schema=properties.Schema(
properties.Schema.MAP,
schema={
NET: properties.Schema(
properties.Schema.STRING,
_('Name or UUID of the network to attach '
'this NIC to. Either %(port)s or '
'%(net)s must be specified.') % {
'port': PORT, 'net': NET},
constraints=[
constraints.CustomConstraint(
'neutron.network')
]
),
PORT: properties.Schema(
properties.Schema.STRING,
_('Name or UUID of Neutron port to '
'attach this NIC to. Either %(port)s '
'or %(net)s must be specified.')
% {'port': PORT, 'net': NET},
constraints=[
constraints.CustomConstraint(
'neutron.port')
],
),
V4_FIXED_IP: properties.Schema(
properties.Schema.STRING,
_('Fixed IPv4 address for this NIC.'),
constraints=[
constraints.CustomConstraint('ip_addr')
]
),
},
),
),
} }
) )
) ),
} }
attributes_schema = { attributes_schema = {
@ -142,6 +189,30 @@ class TroveCluster(resource.Resource):
entity = 'clusters' entity = 'clusters'
def translation_rules(self, properties):
return [
translation.TranslationRule(
properties,
translation.TranslationRule.RESOLVE,
translation_path=[self.INSTANCES, self.NETWORKS, self.NET],
client_plugin=self.client_plugin('neutron'),
finder='find_resourceid_by_name_or_id',
entity='network'),
translation.TranslationRule(
properties,
translation.TranslationRule.RESOLVE,
translation_path=[self.INSTANCES, self.NETWORKS, self.PORT],
client_plugin=self.client_plugin('neutron'),
finder='find_resourceid_by_name_or_id',
entity='port'),
translation.TranslationRule(
properties,
translation.TranslationRule.RESOLVE,
translation_path=[self.INSTANCES, self.FLAVOR],
client_plugin=self.client_plugin(),
finder='find_flavor_by_name_or_id'),
]
def _cluster_name(self): def _cluster_name(self):
return self.properties[self.NAME] or self.physical_resource_name() return self.properties[self.NAME] or self.physical_resource_name()
@ -152,11 +223,14 @@ class TroveCluster(resource.Resource):
# convert instances to format required by troveclient # convert instances to format required by troveclient
instances = [] instances = []
for instance in self.properties[self.INSTANCES]: for instance in self.properties[self.INSTANCES]:
instances.append({ instance_dict = {
'flavorRef': self.client_plugin().find_flavor_by_name_or_id( 'flavorRef': instance[self.FLAVOR],
instance[self.FLAVOR]), 'volume': {'size': instance[self.VOLUME_SIZE]},
'volume': {'size': instance[self.VOLUME_SIZE]} }
}) instance_nics = self.get_instance_nics(instance)
if instance_nics:
instance_dict["nics"] = instance_nics
instances.append(instance_dict)
args = { args = {
'name': self._cluster_name(), 'name': self._cluster_name(),
@ -168,6 +242,21 @@ class TroveCluster(resource.Resource):
self.resource_id_set(cluster.id) self.resource_id_set(cluster.id)
return cluster.id return cluster.id
def get_instance_nics(self, instance):
nics = []
for nic in instance[self.NETWORKS]:
nic_dict = {}
if nic.get(self.NET):
nic_dict['net-id'] = nic.get(self.NET)
if nic.get(self.PORT):
nic_dict['port-id'] = nic.get(self.PORT)
ip = nic.get(self.V4_FIXED_IP)
if ip:
nic_dict['v4-fixed-ip'] = ip
nics.append(nic_dict)
return nics
def _refresh_cluster(self, cluster_id): def _refresh_cluster(self, cluster_id):
try: try:
cluster = self.client().clusters.get(cluster_id) cluster = self.client().clusters.get(cluster_id)
@ -256,6 +345,24 @@ class TroveCluster(resource.Resource):
datastore_type, datastore_version, datastore_type, datastore_version,
self.DATASTORE_TYPE, self.DATASTORE_VERSION) self.DATASTORE_TYPE, self.DATASTORE_VERSION)
# check validity of instances' NETWORKS
is_neutron = self.is_using_neutron()
for instance in self.properties[self.INSTANCES]:
for nic in instance[self.NETWORKS]:
# 'nic.get(self.PORT) is not None' including two cases:
# 1. has set port value in template
# 2. using 'get_resource' to reference a new resource
if not is_neutron and nic.get(self.PORT) is not None:
msg = (_("Can not use %s property on Nova-network.")
% self.PORT)
raise exception.StackValidationFailed(message=msg)
if (bool(nic.get(self.NET) is not None) ==
bool(nic.get(self.PORT) is not None)):
msg = (_("Either %(net)s or %(port)s must be provided.")
% {'net': self.NET, 'port': self.PORT})
raise exception.StackValidationFailed(message=msg)
def _resolve_attribute(self, name): def _resolve_attribute(self, name):
if self.resource_id is None: if self.resource_id is None:
return return

View File

@ -18,6 +18,7 @@ from troveclient import exceptions as troveexc
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.clients.os import neutron
from heat.engine.clients.os import trove from heat.engine.clients.os import trove
from heat.engine.resources.openstack.trove import cluster from heat.engine.resources.openstack.trove import cluster
from heat.engine import scheduler from heat.engine import scheduler
@ -38,10 +39,16 @@ resources:
instances: instances:
- flavor: m1.heat - flavor: m1.heat
volume_size: 1 volume_size: 1
networks:
- port: port1
- flavor: m1.heat - flavor: m1.heat
volume_size: 1 volume_size: 1
networks:
- port: port2
- flavor: m1.heat - flavor: m1.heat
volume_size: 1 volume_size: 1
networks:
- port: port3
''' '''
@ -86,6 +93,9 @@ class TroveClusterTest(common.HeatTestCase):
self.client = mock_client.return_value self.client = mock_client.return_value
self.troveclient = mock.Mock() self.troveclient = mock.Mock()
self.troveclient.flavors.get.return_value = FakeFlavor(1, 'm1.heat') self.troveclient.flavors.get.return_value = FakeFlavor(1, 'm1.heat')
self.patchobject(neutron.NeutronClientPlugin,
'find_resourceid_by_name_or_id',
return_value='someportid')
self.troveclient.datastore_versions.list.return_value = [FakeVersion()] self.troveclient.datastore_versions.list.return_value = [FakeVersion()]
self.patchobject(trove.TroveClientPlugin, 'client', self.patchobject(trove.TroveClientPlugin, 'client',
return_value=self.troveclient) return_value=self.troveclient)
@ -106,9 +116,12 @@ class TroveClusterTest(common.HeatTestCase):
expected_state = (tc.CREATE, tc.COMPLETE) expected_state = (tc.CREATE, tc.COMPLETE)
self.assertEqual(expected_state, tc.state) self.assertEqual(expected_state, tc.state)
args = self.client.clusters.create.call_args[1] args = self.client.clusters.create.call_args[1]
self.assertEqual([{'flavorRef': 1, 'volume': {'size': 1}}, self.assertEqual([{'flavorRef': '1', 'volume': {'size': 1},
{'flavorRef': 1, 'volume': {'size': 1}}, 'nics': [{'port-id': 'someportid'}]},
{'flavorRef': 1, 'volume': {'size': 1}}], {'flavorRef': '1', 'volume': {'size': 1},
'nics': [{'port-id': 'someportid'}]},
{'flavorRef': '1', 'volume': {'size': 1},
'nics': [{'port-id': 'someportid'}]}],
args['instances']) args['instances'])
self.assertEqual('mongodb', args['datastore']) self.assertEqual('mongodb', args['datastore'])
self.assertEqual('2.6.1', args['datastore_version']) self.assertEqual('2.6.1', args['datastore_version'])

View File

@ -597,6 +597,63 @@ class TestTranslationRule(common.HeatTestCase):
self.assertEqual('yellow', result) self.assertEqual('yellow', result)
self.assertEqual('yellow', tran.resolved_translations['far.0.red']) self.assertEqual('yellow', tran.resolved_translations['far.0.red'])
def test_resolve_rule_nested_list_populated(self):
client_plugin, schema = self._test_resolve_rule_nested_list()
data = {
'instances': [{'networks': [{'port': 'port1', 'net': 'net1'}]}]
}
props = properties.Properties(schema, data)
rule = translation.TranslationRule(
props,
translation.TranslationRule.RESOLVE,
['instances', 'networks', 'port'],
client_plugin=client_plugin,
finder='find_name_id',
entity='port'
)
tran = translation.Translation(props)
tran.set_rules([rule])
self.assertTrue(tran.has_translation('instances.networks.port'))
result = tran.translate('instances.0.networks.0.port',
data['instances'][0]['networks'][0]['port'])
self.assertEqual('port1_id', result)
self.assertEqual('port1_id', tran.resolved_translations[
'instances.0.networks.0.port'])
def _test_resolve_rule_nested_list(self):
class FakeClientPlugin(object):
def find_name_id(self, entity=None, value=None):
if entity == 'net':
return 'net1_id'
if entity == 'port':
return 'port1_id'
schema = {
'instances': properties.Schema(
properties.Schema.LIST,
schema=properties.Schema(
properties.Schema.MAP,
schema={
'networks': properties.Schema(
properties.Schema.LIST,
schema=properties.Schema(
properties.Schema.MAP,
schema={
'port': properties.Schema(
properties.Schema.STRING,
),
'net': properties.Schema(
properties.Schema.STRING,
),
}
)
)
}
)
)}
return FakeClientPlugin(), schema
def test_resolve_rule_list_with_function(self): def test_resolve_rule_list_with_function(self):
client_plugin, schema = self._test_resolve_rule(is_list=True) client_plugin, schema = self._test_resolve_rule(is_list=True)
join_func = cfn_funcs.Join(None, join_func = cfn_funcs.Join(None,

View File

@ -0,0 +1,3 @@
---
features:
- Allow to set networks of instances for OS::Trove::Cluster resource.