Support os_version, os_type, and os_distro image properties

Modified default images to use os_type, os_version and os_distro
properties.  Added logic to look for distribution/os_distro,
type/os_type and version/os_version image properties when
matching image properties.

Cleaned up minor python warnings in tosca_compute.py

Removed DOS-style newline characters from
tosca_cluster_policies_scaling.py

Change-Id: I72043cf4a4358cfdbc8f98238276d85dc2f5bcc0
Closes-Bug: 1689673
This commit is contained in:
Bob.Haddleton 2017-05-17 21:53:36 -05:00 committed by Bob Haddleton
parent db5a60659b
commit 6904aab001
3 changed files with 266 additions and 239 deletions

61
translator/common/images.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
@ -24,37 +25,46 @@ log = logging.getLogger('heat-translator')
PREDEF_IMAGES = { PREDEF_IMAGES = {
'ubuntu-software-config-os-init': {'architecture': 'x86_64', 'ubuntu-software-config-os-init': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'Ubuntu', 'os_distro': 'ubuntu',
'version': '14.04'}, 'os_version': '14.04'
},
'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64', 'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'Ubuntu', 'os_distro': 'ubuntu',
'version': '12.04'}, 'os_version': '12.04'
},
'fedora-amd64-heat-config': {'architecture': 'x86_64', 'fedora-amd64-heat-config': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'Fedora', 'os_distro': 'fedora',
'version': '18.0'}, 'os_version': '18.0'
},
'F18-x86_64-cfntools': {'architecture': 'x86_64', 'F18-x86_64-cfntools': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'Fedora', 'os_distro': 'fedora',
'version': '19'}, 'os_version': '19'
},
'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64', 'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'Fedora', 'os_distro': 'fedora',
'version': '20'}, 'os_version': '20'
},
'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64', 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'CirrOS', 'os_distro': 'cirros',
'version': '0.3.1'}, 'os_version': '0.3.1'
},
'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64', 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'CirrOS', 'os_distro': 'cirros',
'version': '0.3.2'}, 'os_version': '0.3.2'
},
'rhel-6.5-test-image': {'architecture': 'x86_64', 'rhel-6.5-test-image': {'architecture': 'x86_64',
'type': 'Linux', 'os_type': 'linux',
'distribution': 'RHEL', 'os_distro': 'rhel',
'version': '6.5'} 'os_version': '6.5'
}
} }
SESSION = None SESSION = None
@ -78,7 +88,8 @@ def get_images():
else: else:
for image in client.images.list(): for image in client.images.list():
image_name = image.name.encode('ascii', 'ignore') image_name = image.name.encode('ascii', 'ignore')
metadata = ["architecture", "type", "distribution", "version"] metadata = ["architecture", "type", "distribution", "version",
"os_distro", "os_type", "os_version"]
if any(key in image.keys() for key in metadata): if any(key in image.keys() for key in metadata):
IMAGES[image_name] = {} IMAGES[image_name] = {}
for key in metadata: for key in metadata:

View File

@ -1,195 +1,195 @@
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from collections import defaultdict from collections import defaultdict
from translator.hot.syntax.hot_resource import HotResource from translator.hot.syntax.hot_resource import HotResource
# Name used to dynamically load appropriate map class. # Name used to dynamically load appropriate map class.
TARGET_CLASS_NAME = 'ToscaClusterAutoscaling' TARGET_CLASS_NAME = 'ToscaClusterAutoscaling'
SCALE_POLICY = 'senlin.policy.scaling-1.0' SCALE_POLICY = 'senlin.policy.scaling-1.0'
SERVER_TYPE = 'os.nova.server-1.0' SERVER_TYPE = 'os.nova.server-1.0'
SCALE_TYPE = {'SCALE_IN': 'CLUSTER_SCALE_IN', SCALE_TYPE = {'SCALE_IN': 'CLUSTER_SCALE_IN',
'SCALE_OUT': 'CLUSTER_SCALE_OUT'} 'SCALE_OUT': 'CLUSTER_SCALE_OUT'}
ALARM_METER_NAME = {'utilization': 'cpu_util'} ALARM_METER_NAME = {'utilization': 'cpu_util'}
ALARM_COMPARISON_OPERATOR = {'greater_than': 'gt', 'gerater_equal': 'ge', ALARM_COMPARISON_OPERATOR = {'greater_than': 'gt', 'gerater_equal': 'ge',
'less_than': 'lt', 'less_equal': 'le', 'less_than': 'lt', 'less_equal': 'le',
'equal': 'eq', 'not_equal': 'ne'} 'equal': 'eq', 'not_equal': 'ne'}
ALARM_STATISTIC = {'average': 'avg'} ALARM_STATISTIC = {'average': 'avg'}
class ToscaClusterAutoscaling(HotResource): class ToscaClusterAutoscaling(HotResource):
'''Translate TOSCA node type tosca.policies.Scaling.Cluster''' '''Translate TOSCA node type tosca.policies.Scaling.Cluster'''
toscatype = 'tosca.policies.Scaling.Cluster' toscatype = 'tosca.policies.Scaling.Cluster'
def __init__(self, policy, csar_dir=None): def __init__(self, policy, csar_dir=None):
hot_type = "OS::Senlin::Policy" hot_type = "OS::Senlin::Policy"
super(ToscaClusterAutoscaling, self).__init__(policy, super(ToscaClusterAutoscaling, self).__init__(policy,
type=hot_type, type=hot_type,
csar_dir=csar_dir) csar_dir=csar_dir)
self.policy = policy self.policy = policy
def _generate_scale_properties(self, def _generate_scale_properties(self,
target_cluster_nodes, target_cluster_nodes,
cluster_scale_type): cluster_scale_type):
properties = {} properties = {}
bindings = [] bindings = []
policy_res = {} policy_res = {}
adjustment = {} adjustment = {}
properties["type"] = SCALE_POLICY properties["type"] = SCALE_POLICY
for cluster_node in target_cluster_nodes: for cluster_node in target_cluster_nodes:
bindings.append({'cluster': cluster_node}) bindings.append({'cluster': cluster_node})
properties["bindings"] = bindings properties["bindings"] = bindings
policy_res["event"] = cluster_scale_type policy_res["event"] = cluster_scale_type
adjustment["type"] = "CHANGE_IN_CAPACITY" adjustment["type"] = "CHANGE_IN_CAPACITY"
adjustment["number"] = self.\ adjustment["number"] = self.\
policy.entity_tpl["properties"]["increment"] policy.entity_tpl["properties"]["increment"]
policy_res["adjustment"] = adjustment policy_res["adjustment"] = adjustment
properties["properties"] = policy_res properties["properties"] = policy_res
return properties return properties
def handle_expansion(self): def handle_expansion(self):
hot_resources = [] hot_resources = []
trigger_receivers = defaultdict(list) trigger_receivers = defaultdict(list)
for node in self.policy.targets: for node in self.policy.targets:
for trigger in self.policy.entity_tpl['triggers']: for trigger in self.policy.entity_tpl['triggers']:
for action in self.policy.\ for action in self.policy.\
entity_tpl['triggers'][trigger]['action']: entity_tpl['triggers'][trigger]['action']:
scale_name = action scale_name = action
action_sample = self.policy.\ action_sample = self.policy.\
entity_tpl['triggers'][trigger]['action'][action] entity_tpl['triggers'][trigger]['action'][action]
scale_type = action_sample['type'] scale_type = action_sample['type']
scale_implement = action_sample['implementation'] scale_implement = action_sample['implementation']
(entity, method) = scale_implement.split('.') (entity, method) = scale_implement.split('.')
receiver_prop = {} receiver_prop = {}
receiver_prop['cluster'] = { receiver_prop['cluster'] = {
"get_resource": "%s_cluster" % node "get_resource": "%s_cluster" % node
} }
receiver_prop['action'] = SCALE_TYPE[scale_type] receiver_prop['action'] = SCALE_TYPE[scale_type]
receiver_prop['type'] = method receiver_prop['type'] = method
receiver_name = node + '_' + scale_name + '_receiver' receiver_name = node + '_' + scale_name + '_receiver'
trigger_receivers[trigger].append(receiver_name) trigger_receivers[trigger].append(receiver_name)
receiver_resources = HotResource(self.nodetemplate, receiver_resources = HotResource(self.nodetemplate,
type='OS::Senlin::Receiver', type='OS::Senlin::Receiver',
name=receiver_name, name=receiver_name,
properties=receiver_prop) properties=receiver_prop)
hot_resources.append(receiver_resources) hot_resources.append(receiver_resources)
for trigger in self.policy.entity_tpl['triggers']: for trigger in self.policy.entity_tpl['triggers']:
sample = self.policy.\ sample = self.policy.\
entity_tpl['triggers'][trigger]['condition'] entity_tpl['triggers'][trigger]['condition']
(meter_name, comparison_operator, threshold) = \ (meter_name, comparison_operator, threshold) = \
sample["constraint"].split() sample["constraint"].split()
threshold = threshold.strip("%") threshold = threshold.strip("%")
alarm_prop = {} alarm_prop = {}
alarm_prop["description"] = self.policy.entity_tpl['description'] alarm_prop["description"] = self.policy.entity_tpl['description']
alarm_prop["meter_name"] = self.policy.\ alarm_prop["meter_name"] = self.policy.\
entity_tpl['triggers'][trigger]['event_type']['metrics'] entity_tpl['triggers'][trigger]['event_type']['metrics']
alarm_prop["statistic"] = ALARM_STATISTIC[sample['method']] alarm_prop["statistic"] = ALARM_STATISTIC[sample['method']]
alarm_prop["period"] = sample["period"] alarm_prop["period"] = sample["period"]
alarm_prop["evaluation_periods"] = sample["evaluations"] alarm_prop["evaluation_periods"] = sample["evaluations"]
alarm_prop["threshold"] = threshold alarm_prop["threshold"] = threshold
alarm_prop["comparison_operator"] = \ alarm_prop["comparison_operator"] = \
ALARM_COMPARISON_OPERATOR[comparison_operator] ALARM_COMPARISON_OPERATOR[comparison_operator]
alarm_prop["repeat_actions"] = "True" alarm_prop["repeat_actions"] = "True"
alarm_prop["alarm_actions"] = [] alarm_prop["alarm_actions"] = []
for index in range(len(trigger_receivers[trigger])): for index in range(len(trigger_receivers[trigger])):
alarm_prop["alarm_actions"].\ alarm_prop["alarm_actions"].\
append({'get_attr': [trigger_receivers[trigger][index], append({'get_attr': [trigger_receivers[trigger][index],
'channel', 'channel',
'alarm_url']}) 'alarm_url']})
ceilometer_resources = HotResource(self.nodetemplate, ceilometer_resources = HotResource(self.nodetemplate,
type='OS::Aodh::Alarm', type='OS::Aodh::Alarm',
name=trigger + '_alarm', name=trigger + '_alarm',
properties=alarm_prop) properties=alarm_prop)
hot_resources.append(ceilometer_resources) hot_resources.append(ceilometer_resources)
return hot_resources return hot_resources
def handle_properties(self, resources): def handle_properties(self, resources):
remove_resources = [] remove_resources = []
networks = defaultdict(list) networks = defaultdict(list)
for index, resource in enumerate(resources): for index, resource in enumerate(resources):
if resource.type == 'OS::Neutron::Port': if resource.type == 'OS::Neutron::Port':
for hot_resource in resource.depends_on_nodes: for hot_resource in resource.depends_on_nodes:
if hot_resource.type != 'OS::Neutron::Net': if hot_resource.type != 'OS::Neutron::Net':
networks[hot_resource.name].\ networks[hot_resource.name].\
append( append(
{'network': '%s' % resource.properties['network']} {'network': '%s' % resource.properties['network']}
) )
remove_resources.append(resource) remove_resources.append(resource)
elif resource.type == 'OS::Neutron::Net': elif resource.type == 'OS::Neutron::Net':
remove_resources.append(resource) remove_resources.append(resource)
elif resource.name in self.policy.targets and \ elif resource.name in self.policy.targets and \
resource.type != 'OS::Senlin::Policy': resource.type != 'OS::Senlin::Policy':
props = {} props = {}
del resource.properties['user_data_format'] del resource.properties['user_data_format']
del resource.properties['networks'] del resource.properties['networks']
props['type'] = SERVER_TYPE props['type'] = SERVER_TYPE
props['properties'] = resource.properties props['properties'] = resource.properties
profile_resources = \ profile_resources = \
HotResource(resource, HotResource(resource,
type='OS::Senlin::Profile', type='OS::Senlin::Profile',
name=resource.name, name=resource.name,
properties=props) properties=props)
resources.pop(index) resources.pop(index)
resources.insert(index, profile_resources) resources.insert(index, profile_resources)
for remove_resource in remove_resources: for remove_resource in remove_resources:
resources.remove(remove_resource) resources.remove(remove_resource)
for index, resource in enumerate(resources): for index, resource in enumerate(resources):
if resource.name in self.policy.targets: if resource.name in self.policy.targets:
resource.properties['properties']['networks'] = \ resource.properties['properties']['networks'] = \
networks[resource.name] networks[resource.name]
for node in self.policy.targets: for node in self.policy.targets:
props = {} props = {}
props["profile"] = {'get_resource': '%s' % node} props["profile"] = {'get_resource': '%s' % node}
temp = self.policy.entity_tpl["properties"] temp = self.policy.entity_tpl["properties"]
props["min_size"] = temp["min_instances"] props["min_size"] = temp["min_instances"]
props["max_size"] = temp["max_instances"] props["max_size"] = temp["max_instances"]
props["desired_capacity"] = temp["default_instances"] props["desired_capacity"] = temp["default_instances"]
self.cluster_name = '%s_cluster' % node self.cluster_name = '%s_cluster' % node
cluster_resources = \ cluster_resources = \
HotResource(self.nodetemplate, HotResource(self.nodetemplate,
type='OS::Senlin::Cluster', type='OS::Senlin::Cluster',
name=self.cluster_name, name=self.cluster_name,
properties=props) properties=props)
resources.append(cluster_resources) resources.append(cluster_resources)
trigger_num = len(self.policy.entity_tpl['triggers']) trigger_num = len(self.policy.entity_tpl['triggers'])
for num, trigger in enumerate(self.policy.entity_tpl['triggers']): for num, trigger in enumerate(self.policy.entity_tpl['triggers']):
target_cluster_nodes = [] target_cluster_nodes = []
for action in self.policy.\ for action in self.policy.\
entity_tpl['triggers'][trigger]['action']: entity_tpl['triggers'][trigger]['action']:
scale_type = self.policy.\ scale_type = self.policy.\
entity_tpl['triggers'][trigger]['action'][action]['type'] entity_tpl['triggers'][trigger]['action'][action]['type']
for node in self.policy.targets: for node in self.policy.targets:
target_cluster_nodes.\ target_cluster_nodes.\
append({"get_resource": "%s_cluster" % node}) append({"get_resource": "%s_cluster" % node})
cluster_scale_type = SCALE_TYPE[scale_type] cluster_scale_type = SCALE_TYPE[scale_type]
scale_in_props = \ scale_in_props = \
self._generate_scale_properties(target_cluster_nodes, self._generate_scale_properties(target_cluster_nodes,
cluster_scale_type) cluster_scale_type)
if num == trigger_num - 1: if num == trigger_num - 1:
self.name = self.name + '_' + trigger self.name = self.name + '_' + trigger
self.properties = scale_in_props self.properties = scale_in_props
break break
policy_resources = \ policy_resources = \
HotResource(self.nodetemplate, HotResource(self.nodetemplate,
type='OS::Senlin::Policy', type='OS::Senlin::Policy',
name=self.name + '_' + trigger, name=self.name + '_' + trigger,
properties=scale_in_props) properties=scale_in_props)
resources.append(policy_resources) resources.append(policy_resources)
return resources return resources

View File

@ -28,13 +28,16 @@ TARGET_CLASS_NAME = 'ToscaCompute'
class ToscaCompute(HotResource): class ToscaCompute(HotResource):
'''Translate TOSCA node type tosca.nodes.Compute.''' """Translate TOSCA node type tosca.nodes.Compute."""
COMPUTE_HOST_PROP = (DISK_SIZE, MEM_SIZE, NUM_CPUS) = \ COMPUTE_HOST_PROP = (DISK_SIZE, MEM_SIZE, NUM_CPUS) = \
('disk_size', 'mem_size', 'num_cpus') ('disk_size', 'mem_size', 'num_cpus')
COMPUTE_OS_PROP = (ARCHITECTURE, DISTRIBUTION, TYPE, VERSION) = \ COMPUTE_OS_PROP = (ARCHITECTURE, DISTRIBUTION, TYPE, VERSION) = \
('architecture', 'distribution', 'type', 'version') ('architecture', 'distribution', 'type', 'version')
IMAGE_OS_PROP = (OS_DISTRO, OS_TYPE, OS_VERSION) = \
('os_distro', 'os_type', 'os_version')
toscatype = 'tosca.nodes.Compute' toscatype = 'tosca.nodes.Compute'
ALLOWED_NOVA_SERVER_PROPS = \ ALLOWED_NOVA_SERVER_PROPS = \
@ -141,32 +144,41 @@ class ToscaCompute(HotResource):
# Check whether user exported all required environment variables. # Check whether user exported all required environment variables.
images = glance_images.get_images() images = glance_images.get_images()
match_all = images.keys() match_all = images.keys()
architecture = properties.get(self.ARCHITECTURE) architecture = properties.get(self.ARCHITECTURE)
if architecture is None: if architecture is None:
self._log_compute_msg(self.ARCHITECTURE, 'image') self._log_compute_msg(self.ARCHITECTURE, 'image')
match_arch = self._match_images(match_all, images, match_arch = self._match_images(match_all, images,
self.ARCHITECTURE, architecture) [self.ARCHITECTURE], architecture)
type = properties.get(self.TYPE)
if type is None: image_type = properties.get(self.TYPE)
if image_type is None:
self._log_compute_msg(self.TYPE, 'image') self._log_compute_msg(self.TYPE, 'image')
match_type = self._match_images(match_arch, images, self.TYPE, type) match_type = self._match_images(match_arch, images, [self.TYPE,
self.OS_TYPE],
image_type)
distribution = properties.get(self.DISTRIBUTION) distribution = properties.get(self.DISTRIBUTION)
if distribution is None: if distribution is None:
self._log_compute_msg(self.DISTRIBUTION, 'image') self._log_compute_msg(self.DISTRIBUTION, 'image')
match_distribution = self._match_images(match_type, images, match_distribution = self._match_images(match_type, images,
self.DISTRIBUTION, [self.DISTRIBUTION,
self.OS_DISTRO],
distribution) distribution)
version = properties.get(self.VERSION) version = properties.get(self.VERSION)
if version is None: if version is None:
self._log_compute_msg(self.VERSION, 'image') self._log_compute_msg(self.VERSION, 'image')
match_version = self._match_images(match_distribution, images, match_version = self._match_images(match_distribution, images,
self.VERSION, version) [self.VERSION, self.OS_VERSION],
version)
if len(match_version): if len(match_version):
return list(match_version)[0] return list(match_version)[0]
def _match_flavors(self, this_list, this_dict, attr, size): @staticmethod
'''Return from this list all flavors matching the attribute size.''' def _match_flavors(this_list, this_dict, attr, size):
"""Return from this list all flavors matching the attribute size."""
if not size: if not size:
return list(this_list) return list(this_list)
matching_flavors = [] matching_flavors = []
@ -177,24 +189,27 @@ class ToscaCompute(HotResource):
log.debug(_('Returning list of flavors matching the attribute size.')) log.debug(_('Returning list of flavors matching the attribute size.'))
return matching_flavors return matching_flavors
def _least_flavor(self, this_list, this_dict, attr): @staticmethod
'''Return from this list the flavor with the smallest attr.''' def _least_flavor(this_list, this_dict, attr):
"""Return from this list the flavor with the smallest attr."""
least_flavor = this_list[0] least_flavor = this_list[0]
for flavor in this_list: for flavor in this_list:
if this_dict[flavor][attr] < this_dict[least_flavor][attr]: if this_dict[flavor][attr] < this_dict[least_flavor][attr]:
least_flavor = flavor least_flavor = flavor
return least_flavor return least_flavor
def _match_images(self, this_list, this_dict, attr, prop): @staticmethod
def _match_images(this_list, this_dict, attr_list, prop):
if not prop: if not prop:
return this_list return this_list
matching_images = [] matching_images = []
for image in this_list: for image in this_list:
if attr in this_dict[image]: for attr in attr_list:
if this_dict[image][attr].lower() == str(prop).lower(): if attr in this_dict[image]:
matching_images.insert(0, image) if this_dict[image][attr].lower() == str(prop).lower():
else: matching_images.insert(0, image)
matching_images.append(image) else:
matching_images.append(image)
return matching_images return matching_images
def get_hot_attribute(self, attribute, args): def get_hot_attribute(self, attribute, args):
@ -214,8 +229,9 @@ class ToscaCompute(HotResource):
return attr return attr
def _log_compute_msg(self, prop, what): @staticmethod
def _log_compute_msg(prop, what):
msg = _('No value is provided for Compute capability ' msg = _('No value is provided for Compute capability '
'property "%(prop)s". This may set an undesired "%(what)s" ' 'property "%(prop)s". This may set an undesired "%(what)s" '
'in the template.') % {'prop': prop, 'what': what} 'in the template.') % {'prop': prop, 'what': what}
log.warn(msg) log.warning(msg)