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 def __init__(self, nodetemplate):
88 super(ToscaCompute, self).__init__(nodetemplate,
89 type='OS::Nova::Server')
90 # List with associated hot port resources with this server
91 self.assoc_port_resources = []
94 def handle_properties(self):
95 self.properties = self.translate_compute_flavor_and_image(
96 self.nodetemplate.get_capability('host'),
97 self.nodetemplate.get_capability('os'))
98 self.properties['user_data_format'] = 'SOFTWARE_CONFIG'
99 tosca_props = self.get_tosca_props()
100 for key, value in tosca_props.items():
101 self.properties[key] = value
103 # To be reorganized later based on new development in Glance and Graffiti
104 def translate_compute_flavor_and_image(self,
113 for prop in host_capability.get_properties_objects():
114 host_cap_props[prop.name] = prop.value
115 flavor = self._best_flavor(host_cap_props)
117 for prop in os_capability.get_properties_objects():
118 os_cap_props[prop.name] = prop.value
119 image = self._best_image(os_cap_props)
120 hot_properties['flavor'] = flavor
121 hot_properties['image'] = image
122 return hot_properties
124 def _create_nova_flavor_dict(self):
125 '''Populates and returns the flavors dict using Nova ReST API'''
127 access_dict = translator.common.utils.get_ks_access_dict()
128 access_token = translator.common.utils.get_token_id(access_dict)
129 if access_token is None:
131 nova_url = translator.common.utils.get_url_for(access_dict,
135 nova_response = requests.get(nova_url + '/flavors/detail',
136 headers={'X-Auth-Token':
138 if nova_response.status_code != 200:
140 flavors = json.loads(nova_response.content)['flavors']
142 for flavor in flavors:
143 flavor_name = str(flavor['name'])
144 flavor_dict[flavor_name] = {
145 'mem_size': flavor['ram'],
146 'disk_size': flavor['disk'],
147 'num_cpus': flavor['vcpus'],
149 except Exception as e:
150 # Handles any exception coming from openstack
151 log.warn(_('Choosing predefined flavors since received '
152 'Openstack Exception: %s') % str(e))
156 def _best_flavor(self, properties):
157 log.info(_('Choosing the best flavor for given attributes.'))
158 # Check whether user exported all required environment variables.
160 if translator.common.utils.check_for_env_variables():
161 resp = self._create_nova_flavor_dict()
165 # start with all flavors
166 match_all = flavors.keys()
168 # TODO(anyone): Handle the case where the value contains something like
169 # get_input instead of a value.
170 # flavors that fit the CPU count
171 cpu = properties.get(self.NUM_CPUS)
173 self._log_compute_msg(self.NUM_CPUS, 'flavor')
174 match_cpu = self._match_flavors(match_all, flavors, self.NUM_CPUS, cpu)
176 # flavors that fit the mem size
177 mem = properties.get(self.MEM_SIZE)
179 mem = translator.common.utils.MemoryUnit.convert_unit_size_to_num(
182 self._log_compute_msg(self.MEM_SIZE, 'flavor')
183 match_cpu_mem = self._match_flavors(match_cpu, flavors,
185 # flavors that fit the disk size
186 disk = properties.get(self.DISK_SIZE)
188 disk = translator.common.utils.MemoryUnit.\
189 convert_unit_size_to_num(disk, 'GB')
191 self._log_compute_msg(self.DISK_SIZE, 'flavor')
192 match_cpu_mem_disk = self._match_flavors(match_cpu_mem, flavors,
193 self.DISK_SIZE, disk)
194 # if multiple match, pick the flavor with the least memory
195 # the selection can be based on other heuristic, e.g. pick one with the
196 # least total resource
197 if len(match_cpu_mem_disk) > 1:
198 return self._least_flavor(match_cpu_mem_disk, flavors, 'mem_size')
199 elif len(match_cpu_mem_disk) == 1:
200 return match_cpu_mem_disk[0]
204 def _best_image(self, properties):
205 match_all = IMAGES.keys()
206 architecture = properties.get(self.ARCHITECTURE)
207 if architecture is None:
208 self._log_compute_msg(self.ARCHITECTURE, 'image')
209 match_arch = self._match_images(match_all, IMAGES,
210 self.ARCHITECTURE, architecture)
211 type = properties.get(self.TYPE)
213 self._log_compute_msg(self.TYPE, 'image')
214 match_type = self._match_images(match_arch, IMAGES, self.TYPE, type)
215 distribution = properties.get(self.DISTRIBUTION)
216 if distribution is None:
217 self._log_compute_msg(self.DISTRIBUTION, 'image')
218 match_distribution = self._match_images(match_type, IMAGES,
221 version = properties.get(self.VERSION)
223 self._log_compute_msg(self.VERSION, 'image')
224 match_version = self._match_images(match_distribution, IMAGES,
225 self.VERSION, version)
227 if len(match_version):
228 return list(match_version)[0]
230 def _match_flavors(self, this_list, this_dict, attr, size):
231 '''Return from this list all flavors matching the attribute size.'''
233 return list(this_list)
234 matching_flavors = []
235 for flavor in this_list:
236 if isinstance(size, int):
237 if this_dict[flavor][attr] >= size:
238 matching_flavors.append(flavor)
239 log.debug(_('Returning list of flavors matching the attribute size.'))
240 return matching_flavors
242 def _least_flavor(self, this_list, this_dict, attr):
243 '''Return from this list the flavor with the smallest attr.'''
244 least_flavor = this_list[0]
245 for flavor in this_list:
246 if this_dict[flavor][attr] < this_dict[least_flavor][attr]:
247 least_flavor = flavor
250 def _match_images(self, this_list, this_dict, attr, prop):
254 for image in this_list:
255 if this_dict[image][attr].lower() == str(prop).lower():
256 matching_images.append(image)
257 return matching_images
259 def get_hot_attribute(self, attribute, args):
261 # Convert from a TOSCA attribute for a nodetemplate to a HOT
262 # attribute for the matching resource. Unless there is additional
263 # runtime support, this should be a one to one mapping.
265 # Note: We treat private and public IP addresses equally, but
266 # this will change in the future when TOSCA starts to support
267 # multiple private/public IP addresses.
268 log.debug(_('Converting TOSCA attribute for a nodetemplate to a HOT \
270 if attribute == 'private_address' or \
271 attribute == 'public_address':
272 attr['get_attr'] = [self.name, 'networks', 'private', 0]
276 def _log_compute_msg(self, prop, what):
277 msg = _('No value is provided for Compute capability '
278 'property "%(prop)s". This may set an undesired "%(what)s" '
279 'in the template.') % {'prop': prop, 'what': what}