2 # Licensed under the Apache License, Version 2.0 (the "License"); you may
3 # not use this file except in compliance with the License. You may obtain
4 # a copy of the License at
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 # License for the specific language governing permissions and limitations
18 from toscaparser.utils.gettextutils import _
19 import translator.common.utils
20 from translator.hot.syntax.hot_resource import HotResource
22 log = logging.getLogger('heat-translator')
25 # Name used to dynamically load appropriate map class.
26 TARGET_CLASS_NAME = 'ToscaCompute'
28 # A design issue to be resolved is how to translate the generic TOSCA server
29 # properties to OpenStack flavors and images. At the Atlanta design summit,
30 # there was discussion on using Glance to store metadata and Graffiti to
31 # describe artifacts. We will follow these projects to see if they can be
32 # leveraged for this TOSCA translation.
33 # For development purpose at this time, we temporarily hardcode a list of
34 # flavors and images here
35 FLAVORS = {'m1.xlarge': {'mem_size': 16384, 'disk_size': 160, 'num_cpus': 8},
36 'm1.large': {'mem_size': 8192, 'disk_size': 80, 'num_cpus': 4},
37 'm1.medium': {'mem_size': 4096, 'disk_size': 40, 'num_cpus': 2},
38 'm1.small': {'mem_size': 2048, 'disk_size': 20, 'num_cpus': 1},
39 'm1.tiny': {'mem_size': 512, 'disk_size': 1, 'num_cpus': 1},
40 'm1.micro': {'mem_size': 128, 'disk_size': 0, 'num_cpus': 1},
41 'm1.nano': {'mem_size': 64, 'disk_size': 0, 'num_cpus': 1}}
43 IMAGES = {'ubuntu-software-config-os-init': {'architecture': 'x86_64',
45 'distribution': 'Ubuntu',
47 'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64',
49 'distribution': 'Ubuntu',
51 'fedora-amd64-heat-config': {'architecture': 'x86_64',
53 'distribution': 'Fedora',
55 'F18-x86_64-cfntools': {'architecture': 'x86_64',
57 'distribution': 'Fedora',
59 'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64',
61 'distribution': 'Fedora',
63 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64',
65 'distribution': 'CirrOS',
67 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64',
69 'distribution': 'CirrOS',
71 'rhel-6.5-test-image': {'architecture': 'x86_64',
73 'distribution': 'RHEL',
77 class ToscaCompute(HotResource):
78 '''Translate TOSCA node type tosca.nodes.Compute.'''
80 COMPUTE_HOST_PROP = (DISK_SIZE, MEM_SIZE, NUM_CPUS) = \
81 ('disk_size', 'mem_size', 'num_cpus')
83 COMPUTE_OS_PROP = (ARCHITECTURE, DISTRIBUTION, TYPE, VERSION) = \
84 ('architecture', 'distribution', 'type', 'version')
85 toscatype = 'tosca.nodes.Compute'
87 ALLOWED_NOVA_SERVER_PROPS = \
88 ('admin_pass', 'availability_zone', 'block_device_mapping',
89 'block_device_mapping_v2', 'config_drive', 'diskConfig', 'flavor',
90 'flavor_update_policy', 'image', 'image_update_policy', 'key_name',
91 'metadata', 'name', 'networks', 'personality', 'reservation_id',
92 'scheduler_hints', 'security_groups', 'software_config_transport',
93 'user_data', 'user_data_format', 'user_data_update_policy')
95 def __init__(self, nodetemplate):
96 super(ToscaCompute, self).__init__(nodetemplate,
97 type='OS::Nova::Server')
98 # List with associated hot port resources with this server
99 self.assoc_port_resources = []
102 def handle_properties(self):
103 self.properties = self.translate_compute_flavor_and_image(
104 self.nodetemplate.get_capability('host'),
105 self.nodetemplate.get_capability('os'))
106 self.properties['user_data_format'] = 'SOFTWARE_CONFIG'
107 tosca_props = self.get_tosca_props()
108 for key, value in tosca_props.items():
109 if key in self.ALLOWED_NOVA_SERVER_PROPS:
110 self.properties[key] = value
112 # To be reorganized later based on new development in Glance and Graffiti
113 def translate_compute_flavor_and_image(self,
122 for prop in host_capability.get_properties_objects():
123 host_cap_props[prop.name] = prop.value
124 # if HOST properties are not specified, we should not attempt to
125 # find best match of flavor
127 flavor = self._best_flavor(host_cap_props)
129 for prop in os_capability.get_properties_objects():
130 os_cap_props[prop.name] = prop.value
131 # if OS properties are not specified, we should not attempt to
132 # find best match of image
134 image = self._best_image(os_cap_props)
135 hot_properties['flavor'] = flavor
136 hot_properties['image'] = image
137 return hot_properties
139 def _create_nova_flavor_dict(self):
140 '''Populates and returns the flavors dict using Nova ReST API'''
142 access_dict = translator.common.utils.get_ks_access_dict()
143 access_token = translator.common.utils.get_token_id(access_dict)
144 if access_token is None:
146 nova_url = translator.common.utils.get_url_for(access_dict,
150 nova_response = requests.get(nova_url + '/flavors/detail',
151 headers={'X-Auth-Token':
153 if nova_response.status_code != 200:
155 flavors = json.loads(nova_response.content)['flavors']
157 for flavor in flavors:
158 flavor_name = str(flavor['name'])
159 flavor_dict[flavor_name] = {
160 'mem_size': flavor['ram'],
161 'disk_size': flavor['disk'],
162 'num_cpus': flavor['vcpus'],
164 except Exception as e:
165 # Handles any exception coming from openstack
166 log.warn(_('Choosing predefined flavors since received '
167 'Openstack Exception: %s') % str(e))
171 def _populate_image_dict(self):
172 '''Populates and returns the images dict using Glance ReST API'''
175 access_dict = translator.common.utils.get_ks_access_dict()
176 access_token = translator.common.utils.get_token_id(access_dict)
177 if access_token is None:
179 glance_url = translator.common.utils.get_url_for(access_dict,
183 glance_response = requests.get(glance_url + '/v2/images',
184 headers={'X-Auth-Token':
186 if glance_response.status_code != 200:
188 images = json.loads(glance_response.content)["images"]
190 image_resp = requests.get(glance_url + '/v2/images/' +
192 headers={'X-Auth-Token':
194 if image_resp.status_code != 200:
196 metadata = ["architecture", "type", "distribution", "version"]
197 image_data = json.loads(image_resp.content)
198 if any(key in image_data.keys() for key in metadata):
199 images_dict[image_data["name"]] = dict()
201 if key in image_data.keys():
202 images_dict[image_data["name"]][key] = \
207 except Exception as e:
208 # Handles any exception coming from openstack
209 log.warn(_('Choosing predefined flavors since received '
210 'Openstack Exception: %s') % str(e))
213 def _best_flavor(self, properties):
214 log.info(_('Choosing the best flavor for given attributes.'))
215 # Check whether user exported all required environment variables.
217 if translator.common.utils.check_for_env_variables():
218 resp = self._create_nova_flavor_dict()
222 # start with all flavors
223 match_all = flavors.keys()
225 # TODO(anyone): Handle the case where the value contains something like
226 # get_input instead of a value.
227 # flavors that fit the CPU count
228 cpu = properties.get(self.NUM_CPUS)
230 self._log_compute_msg(self.NUM_CPUS, 'flavor')
231 match_cpu = self._match_flavors(match_all, flavors, self.NUM_CPUS, cpu)
233 # flavors that fit the mem size
234 mem = properties.get(self.MEM_SIZE)
236 mem = translator.common.utils.MemoryUnit.convert_unit_size_to_num(
239 self._log_compute_msg(self.MEM_SIZE, 'flavor')
240 match_cpu_mem = self._match_flavors(match_cpu, flavors,
242 # flavors that fit the disk size
243 disk = properties.get(self.DISK_SIZE)
245 disk = translator.common.utils.MemoryUnit.\
246 convert_unit_size_to_num(disk, 'GB')
248 self._log_compute_msg(self.DISK_SIZE, 'flavor')
249 match_cpu_mem_disk = self._match_flavors(match_cpu_mem, flavors,
250 self.DISK_SIZE, disk)
251 # if multiple match, pick the flavor with the least memory
252 # the selection can be based on other heuristic, e.g. pick one with the
253 # least total resource
254 if len(match_cpu_mem_disk) > 1:
255 return self._least_flavor(match_cpu_mem_disk, flavors, 'mem_size')
256 elif len(match_cpu_mem_disk) == 1:
257 return match_cpu_mem_disk[0]
261 def _best_image(self, properties):
262 # Check whether user exported all required environment variables.
264 if translator.common.utils.check_for_env_variables():
265 resp = self._populate_image_dict()
266 if resp and len(resp.keys()) > 0:
268 match_all = images.keys()
269 architecture = properties.get(self.ARCHITECTURE)
270 if architecture is None:
271 self._log_compute_msg(self.ARCHITECTURE, 'image')
272 match_arch = self._match_images(match_all, images,
273 self.ARCHITECTURE, architecture)
274 type = properties.get(self.TYPE)
276 self._log_compute_msg(self.TYPE, 'image')
277 match_type = self._match_images(match_arch, images, self.TYPE, type)
278 distribution = properties.get(self.DISTRIBUTION)
279 if distribution is None:
280 self._log_compute_msg(self.DISTRIBUTION, 'image')
281 match_distribution = self._match_images(match_type, images,
284 version = properties.get(self.VERSION)
286 self._log_compute_msg(self.VERSION, 'image')
287 match_version = self._match_images(match_distribution, images,
288 self.VERSION, version)
290 if len(match_version):
291 return list(match_version)[0]
293 def _match_flavors(self, this_list, this_dict, attr, size):
294 '''Return from this list all flavors matching the attribute size.'''
296 return list(this_list)
297 matching_flavors = []
298 for flavor in this_list:
299 if isinstance(size, int):
300 if this_dict[flavor][attr] >= size:
301 matching_flavors.append(flavor)
302 log.debug(_('Returning list of flavors matching the attribute size.'))
303 return matching_flavors
305 def _least_flavor(self, this_list, this_dict, attr):
306 '''Return from this list the flavor with the smallest attr.'''
307 least_flavor = this_list[0]
308 for flavor in this_list:
309 if this_dict[flavor][attr] < this_dict[least_flavor][attr]:
310 least_flavor = flavor
313 def _match_images(self, this_list, this_dict, attr, prop):
317 for image in this_list:
318 if this_dict[image][attr].lower() == str(prop).lower():
319 matching_images.append(image)
320 return matching_images
322 def get_hot_attribute(self, attribute, args):
324 # Convert from a TOSCA attribute for a nodetemplate to a HOT
325 # attribute for the matching resource. Unless there is additional
326 # runtime support, this should be a one to one mapping.
328 # Note: We treat private and public IP addresses equally, but
329 # this will change in the future when TOSCA starts to support
330 # multiple private/public IP addresses.
331 log.debug(_('Converting TOSCA attribute for a nodetemplate to a HOT \
333 if attribute == 'private_address' or \
334 attribute == 'public_address':
335 attr['get_attr'] = [self.name, 'networks']
339 def _log_compute_msg(self, prop, what):
340 msg = _('No value is provided for Compute capability '
341 'property "%(prop)s". This may set an undesired "%(what)s" '
342 'in the template.') % {'prop': prop, 'what': what}