1 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
17 from glanceclient import exc as glance_exception
19 from glanceclient.openstack.common.apiclient.exceptions import NotFound as GlanceImageNotFound
21 from glanceclient.v1.apiclient.exceptions import NotFound as GlanceImageNotFound
28 class Compute(object):
29 def __init__(self, nova_client, glance_client, config):
30 self.novaclient = nova_client
31 self.glance_client = glance_client
34 def find_image(self, image_name):
36 return next(self.glance_client.images.list(filters={'name': image_name}), None)
37 except (novaclient.exceptions.NotFound, keystoneauth1.exceptions.http.NotFound,
42 def upload_image_via_url(self, final_image_name, image_file, retry_count=60):
43 """Directly upload image to Nova via URL if image is not present."""
46 # check image is file/url based.
47 with open(image_file) as f_image:
48 img = self.glance_client.images.create(name=str(final_image_name),
50 container_format="bare",
52 self.glance_client.images.upload(img.id, image_data=f_image)
53 # Check for the image in glance
54 while img.status in ['queued', 'saving'] and retry < retry_count:
55 img = self.glance_client.images.get(img.id)
57 LOG.debug("Image not yet active, retrying %s of %s...", retry, retry_count)
58 time.sleep(self.config.generic_poll_sec)
59 if img.status != 'active':
60 LOG.error("Image uploaded but too long to get to active state")
61 raise Exception("Image update active state timeout")
62 except glance_exception.HTTPForbidden:
63 LOG.error("Cannot upload image without admin access. Please make "
64 "sure the image is uploaded and is either public or owned by you.")
67 # catch the exception for file based errors.
68 LOG.error("Failed while uploading the image. Please make sure the "
69 "image at the specified location %s is correct.", image_file)
71 except keystoneauth1.exceptions.http.NotFound as exc:
72 LOG.error("Authentication error while uploading the image: %s", str(exc))
75 LOG.error(traceback.format_exc())
76 LOG.error("Failed to upload image %s.", image_file)
80 def delete_image(self, img_name):
82 LOG.log("Deleting image %s...", img_name)
83 img = self.find_image(image_name=img_name)
84 self.glance_client.images.delete(img.id)
86 LOG.error("Failed to delete the image %s.", img_name)
91 # Create a server instance with name vmname
92 # and check that it gets into the ACTIVE state
93 def create_server(self, vmname, image, flavor, key_name,
94 nic, sec_group, avail_zone=None, user_data=None,
95 config_drive=None, files=None):
98 security_groups = [sec_group['id']]
100 security_groups = None
102 # Also attach the created security group for the test
103 instance = self.novaclient.servers.create(name=vmname,
108 availability_zone=avail_zone,
110 config_drive=config_drive,
112 security_groups=security_groups)
115 def poll_server(self, instance):
116 return self.novaclient.servers.get(instance.id)
118 def get_server_list(self):
119 servers_list = self.novaclient.servers.list()
122 def delete_server(self, server):
123 self.novaclient.servers.delete(server)
125 def find_flavor(self, flavor_type):
127 flavor = self.novaclient.flavors.find(name=flavor_type)
132 def create_flavor(self, name, ram, vcpus, disk, ephemeral=0):
133 return self.novaclient.flavors.create(name=name, ram=ram, vcpus=vcpus, disk=disk,
136 def normalize_az_host(self, az, host):
138 az = self.config.availability_zone
139 return az + ':' + host
141 def auto_fill_az(self, host_list, host):
142 """Auto fill az:host.
144 no az provided, if there is a host list we can auto-fill the az
145 else we use the configured az if available
146 else we return an error
149 for hyp in host_list:
151 return self.normalize_az_host(hyp.zone, host)
153 LOG.error('Passed host name does not exist: %s', host)
155 if self.config.availability_zone:
156 return self.normalize_az_host(None, host)
157 LOG.error('--hypervisor passed without an az and no az configured')
160 def sanitize_az_host(self, host_list, az_host):
161 """Sanitize the az:host string.
163 host_list: list of hosts as retrieved from openstack (can be empty)
164 az_host: either a host or a az:host string
165 if a host, will check host is in the list, find the corresponding az and
167 if az:host is passed will check the host is in the list and az matches
168 if host_list is empty, will return the configured az if there is no
172 # no host_list, return as is (no check)
175 # if there is a host_list, extract and verify the az and host
176 az_host_list = az_host.split(':')
177 zone = az_host_list[0]
178 host = az_host_list[1]
179 for hyp in host_list:
184 # else continue - another zone with same host name?
186 LOG.error('No match for availability zone and host %s', az_host)
189 return self.auto_fill_az(host_list, az_host)
192 # Return a list of 0, 1 or 2 az:host
194 # The list is computed as follows:
195 # The list of all hosts is retrieved first from openstack
196 # if this fails, checks and az auto-fill are disabled
198 # If the user provides a configured az name (config.availability_zone)
199 # up to the first 2 hosts from the list that match the az are returned
201 # If the user did not configure an az name
202 # up to the first 2 hosts from the list are returned
203 # Possible return values:
206 # [ az1:hyp1, az2:hyp2 ]
207 # [] if an error occurred (error message printed to console)
209 def get_enabled_az_host_list(self, required_count=1):
210 """Check which hypervisors are enabled and on which compute nodes they are running.
212 Pick up to the required count of hosts (can be less or zero)
214 :param required_count: count of compute-nodes to return
215 :return: list of enabled available compute nodes
221 hypervisor_list = self.novaclient.hypervisors.list()
222 host_list = self.novaclient.services.list()
223 except novaclient.exceptions.Forbidden:
224 LOG.warning('Operation Forbidden: could not retrieve list of hypervisors'
225 ' (likely no permission)')
227 hypervisor_list = [h for h in hypervisor_list if h.status == 'enabled' and h.state == 'up']
228 if self.config.availability_zone:
229 host_list = [h for h in host_list if h.zone == self.config.availability_zone]
231 if self.config.compute_nodes:
232 host_list = [h for h in host_list if h.host in self.config.compute_nodes]
234 hosts = [h.hypervisor_hostname for h in hypervisor_list]
235 host_list = [h for h in host_list if h.host in hosts]
238 for host in host_list:
239 candidate = self.normalize_az_host(host.zone, host.host)
241 avail_list.append(candidate)
242 if len(avail_list) == required_count:
247 def get_hypervisor(self, hyper_name):
248 # can raise novaclient.exceptions.NotFound
249 # first get the id from name
250 hyper = self.novaclient.hypervisors.search(hyper_name)[0]
251 # get full hypervisor object
252 return self.novaclient.hypervisors.get(hyper.id)