2.0 beta NFVBENCH-91 Allow multi-chaining with separate edge networks
[nfvbench.git] / nfvbench / compute.py
1 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
2 #
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
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
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
13 #    under the License.
14 import time
15 import traceback
16
17 from glanceclient import exc as glance_exception
18 try:
19     from glanceclient.openstack.common.apiclient.exceptions import NotFound as GlanceImageNotFound
20 except ImportError:
21     from glanceclient.v1.apiclient.exceptions import NotFound as GlanceImageNotFound
22 import keystoneauth1
23 import novaclient
24
25 from log import LOG
26
27
28 class Compute(object):
29     def __init__(self, nova_client, glance_client, config):
30         self.novaclient = nova_client
31         self.glance_client = glance_client
32         self.config = config
33
34     def find_image(self, image_name):
35         try:
36             return next(self.glance_client.images.list(filters={'name': image_name}), None)
37         except (novaclient.exceptions.NotFound, keystoneauth1.exceptions.http.NotFound,
38                 GlanceImageNotFound):
39             pass
40         return None
41
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."""
44         retry = 0
45         try:
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),
49                                                        disk_format="qcow2",
50                                                        container_format="bare",
51                                                        visibility="public")
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)
56                 retry += 1
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.")
65             return False
66         except IOError:
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)
70             return False
71         except keystoneauth1.exceptions.http.NotFound as exc:
72             LOG.error("Authentication error while uploading the image: %s", str(exc))
73             return False
74         except Exception:
75             LOG.error(traceback.format_exc())
76             LOG.error("Failed to upload image %s.", image_file)
77             return False
78         return True
79
80     def delete_image(self, img_name):
81         try:
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)
85         except Exception:
86             LOG.error("Failed to delete the image %s.", img_name)
87             return False
88
89         return True
90
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):
96
97         if sec_group:
98             security_groups = [sec_group['id']]
99         else:
100             security_groups = None
101
102         # Also attach the created security group for the test
103         instance = self.novaclient.servers.create(name=vmname,
104                                                   image=image,
105                                                   flavor=flavor,
106                                                   key_name=key_name,
107                                                   nics=nic,
108                                                   availability_zone=avail_zone,
109                                                   userdata=user_data,
110                                                   config_drive=config_drive,
111                                                   files=files,
112                                                   security_groups=security_groups)
113         return instance
114
115     def poll_server(self, instance):
116         return self.novaclient.servers.get(instance.id)
117
118     def get_server_list(self):
119         servers_list = self.novaclient.servers.list()
120         return servers_list
121
122     def delete_server(self, server):
123         self.novaclient.servers.delete(server)
124
125     def find_flavor(self, flavor_type):
126         try:
127             flavor = self.novaclient.flavors.find(name=flavor_type)
128             return flavor
129         except Exception:
130             return None
131
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,
134                                               ephemeral=ephemeral)
135
136     def normalize_az_host(self, az, host):
137         if not az:
138             az = self.config.availability_zone
139         return az + ':' + host
140
141     def auto_fill_az(self, host_list, host):
142         """Auto fill az:host.
143
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
147         """
148         if host_list:
149             for hyp in host_list:
150                 if hyp.host == host:
151                     return self.normalize_az_host(hyp.zone, host)
152             # no match on host
153             LOG.error('Passed host name does not exist: %s', host)
154             return None
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')
158         return None
159
160     def sanitize_az_host(self, host_list, az_host):
161         """Sanitize the az:host string.
162
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
166                     return az:host
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
169                     az passed
170         """
171         if ':' in az_host:
172             # no host_list, return as is (no check)
173             if not host_list:
174                 return az_host
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:
180                 if hyp.host == host:
181                     if hyp.zone == zone:
182                         # matches
183                         return az_host
184                         # else continue - another zone with same host name?
185             # no match
186             LOG.error('No match for availability zone and host %s', az_host)
187             return None
188         else:
189             return self.auto_fill_az(host_list, az_host)
190
191     #
192     #   Return a list of 0, 1 or 2 az:host
193     #
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
197     #
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
200     #
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:
204     #   [ az ]
205     #   [ az:hyp ]
206     #   [ az1:hyp1, az2:hyp2 ]
207     #   []  if an error occurred (error message printed to console)
208     #
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.
211
212         Pick up to the required count of hosts (can be less or zero)
213
214         :param required_count: count of compute-nodes to return
215         :return: list of enabled available compute nodes
216         """
217         host_list = []
218         hypervisor_list = []
219
220         try:
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)')
226
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]
230
231         if self.config.compute_nodes:
232             host_list = [h for h in host_list if h.host in self.config.compute_nodes]
233
234         hosts = [h.hypervisor_hostname for h in hypervisor_list]
235         host_list = [h for h in host_list if h.host in hosts]
236
237         avail_list = []
238         for host in host_list:
239             candidate = self.normalize_az_host(host.zone, host.host)
240             if candidate:
241                 avail_list.append(candidate)
242                 if len(avail_list) == required_count:
243                     return avail_list
244
245         return avail_list
246
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)