Merge "Enable to specify network for Trove Cluster"
This commit is contained in:
commit
a96b876764
@ -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
|
||||||
|
@ -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'])
|
||||||
|
@ -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,
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Allow to set networks of instances for OS::Trove::Cluster resource.
|
Loading…
Reference in New Issue
Block a user