blob: 47196ec317958d15686a2835395068244e60b3cb [file] [log] [blame]
Joseph Lanouxb3e1f872015-01-30 11:13:07 +00001# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
Clark Boylan844180e2017-03-15 15:24:58 -070016import base64
Markus Zoellerae36ce82017-03-20 16:27:26 +010017import socket
xxj8eb90982017-04-10 21:18:39 +080018import ssl
Markus Zoellerae36ce82017-03-20 16:27:26 +010019import struct
Clark Boylan844180e2017-03-15 15:24:58 -070020import textwrap
21
Markus Zoellerae36ce82017-03-20 16:27:26 +010022import six
23from six.moves.urllib import parse as urlparse
24
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000025from oslo_log import log as logging
26from oslo_utils import excutils
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000027
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +000028from tempest.common import waiters
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000029from tempest import config
Matthew Treinishb19c55d2017-07-17 12:38:35 -040030from tempest.lib.common import fixed_network
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -080031from tempest.lib.common import rest_client
Ken'ichi Ohmichi757833a2017-03-10 10:30:30 -080032from tempest.lib.common.utils import data_utils
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000033
Markus Zoellerae36ce82017-03-20 16:27:26 +010034if six.PY2:
35 ord_func = ord
36else:
37 ord_func = int
38
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000039CONF = config.CONF
40
41LOG = logging.getLogger(__name__)
42
43
Andrea Frittoli88eb6772017-08-07 21:06:27 +010044def is_scheduler_filter_enabled(filter_name):
45 """Check the list of enabled compute scheduler filters from config.
46
47 This function checks whether the given compute scheduler filter is
48 available and configured in the config file. If the
49 scheduler_available_filters option is set to 'all' (Default value. which
50 means default filters are configured in nova) in tempest.conf then, this
51 function returns True with assumption that requested filter 'filter_name'
52 is one of available filter in nova ("nova.scheduler.filters.all_filters").
53 """
54
55 filters = CONF.compute_feature_enabled.scheduler_available_filters
56 if not filters:
57 return False
58 if 'all' in filters:
59 return True
60 if filter_name in filters:
61 return True
62 return False
63
64
Andrea Frittoli (andreaf)476e9192015-08-14 23:59:58 +010065def create_test_server(clients, validatable=False, validation_resources=None,
Joe Gordon8843f0f2015-03-17 15:07:34 -070066 tenant_network=None, wait_until=None,
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +053067 volume_backed=False, name=None, flavor=None,
ghanshyam61db96e2016-12-16 12:49:25 +090068 image_id=None, **kwargs):
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000069 """Common wrapper utility returning a test server.
70
71 This method is a common wrapper returning a test server that can be
72 pingable or sshable.
73
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +090074 :param clients: Client manager which provides OpenStack Tempest clients.
Joseph Lanouxb3e1f872015-01-30 11:13:07 +000075 :param validatable: Whether the server will be pingable or sshable.
76 :param validation_resources: Resources created for the connection to the
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010077 server. Include a keypair, a security group and an IP.
Ken'ichi Ohmichid5bc31a2015-09-02 01:45:28 +000078 :param tenant_network: Tenant network to be used for creating a server.
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +000079 :param wait_until: Server status to wait for the server to reach after
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010080 its creation.
ghanshyam61db96e2016-12-16 12:49:25 +090081 :param volume_backed: Whether the server is volume backed or not.
82 If this is true, a volume will be created and
83 create server will be requested with
84 'block_device_mapping_v2' populated with below
85 values:
86 --------------------------------------------
87 bd_map_v2 = [{
88 'uuid': volume['volume']['id'],
89 'source_type': 'volume',
90 'destination_type': 'volume',
91 'boot_index': 0,
92 'delete_on_termination': True}]
93 kwargs['block_device_mapping_v2'] = bd_map_v2
94 ---------------------------------------------
95 If server needs to be booted from volume with other
96 combination of bdm inputs than mentioned above, then
97 pass the bdm inputs explicitly as kwargs and image_id
98 as empty string ('').
Andrea Frittoli (andreaf)9df3a522016-07-06 14:09:48 +010099 :param name: Name of the server to be provisioned. If not defined a random
100 string ending with '-instance' will be generated.
101 :param flavor: Flavor of the server to be provisioned. If not defined,
102 CONF.compute.flavor_ref will be used instead.
103 :param image_id: ID of the image to be used to provision the server. If not
104 defined, CONF.compute.image_ref will be used instead.
lei zhangdd552b22015-11-25 20:41:48 +0800105 :returns: a tuple
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000106 """
107
108 # TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE
109
Anusha Ramineni9aaef8b2016-01-19 10:56:40 +0530110 if name is None:
111 name = data_utils.rand_name(__name__ + "-instance")
112 if flavor is None:
113 flavor = CONF.compute.flavor_ref
114 if image_id is None:
115 image_id = CONF.compute.image_ref
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000116
117 kwargs = fixed_network.set_networks_kwarg(
118 tenant_network, kwargs) or {}
119
Ghanshyam4de44ae2015-12-25 10:34:00 +0900120 multiple_create_request = (max(kwargs.get('min_count', 0),
121 kwargs.get('max_count', 0)) > 1)
122
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000123 if CONF.validation.run_validation and validatable:
124 # As a first implementation, multiple pingable or sshable servers will
125 # not be supported
Ghanshyam4de44ae2015-12-25 10:34:00 +0900126 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000127 msg = ("Multiple pingable or sshable servers not supported at "
128 "this stage.")
129 raise ValueError(msg)
130
131 if 'security_groups' in kwargs:
132 kwargs['security_groups'].append(
133 {'name': validation_resources['security_group']['name']})
134 else:
135 try:
136 kwargs['security_groups'] = [
137 {'name': validation_resources['security_group']['name']}]
138 except KeyError:
139 LOG.debug("No security group provided.")
140
141 if 'key_name' not in kwargs:
142 try:
143 kwargs['key_name'] = validation_resources['keypair']['name']
144 except KeyError:
145 LOG.debug("No key provided.")
146
147 if CONF.validation.connect_method == 'floating':
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000148 if wait_until is None:
149 wait_until = 'ACTIVE'
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000150
Clark Boylan844180e2017-03-15 15:24:58 -0700151 if 'user_data' not in kwargs:
152 # If nothing overrides the default user data script then run
153 # a simple script on the host to print networking info. This is
154 # to aid in debugging ssh failures.
155 script = '''
156 #!/bin/sh
157 echo "Printing {user} user authorized keys"
158 cat ~{user}/.ssh/authorized_keys || true
159 '''.format(user=CONF.validation.image_ssh_user)
160 script_clean = textwrap.dedent(script).lstrip().encode('utf8')
161 script_b64 = base64.b64encode(script_clean)
162 kwargs['user_data'] = script_b64
163
Joe Gordon8843f0f2015-03-17 15:07:34 -0700164 if volume_backed:
zhuflc6ce5392016-08-17 14:34:37 +0800165 volume_name = data_utils.rand_name(__name__ + '-volume')
John Griffithbc678ad2015-09-29 09:38:39 -0600166 volumes_client = clients.volumes_v2_client
ghanshyam3bd0d2b2017-03-23 01:57:28 +0000167 params = {'name': volume_name,
ghanshyam61db96e2016-12-16 12:49:25 +0900168 'imageRef': image_id,
169 'size': CONF.volume.volume_size}
170 volume = volumes_client.create_volume(**params)
lkuchlan52d7b0d2016-11-07 20:53:19 +0200171 waiters.wait_for_volume_resource_status(volumes_client,
172 volume['volume']['id'],
173 'available')
Joe Gordon8843f0f2015-03-17 15:07:34 -0700174
175 bd_map_v2 = [{
176 'uuid': volume['volume']['id'],
177 'source_type': 'volume',
178 'destination_type': 'volume',
179 'boot_index': 0,
ghanshyam61db96e2016-12-16 12:49:25 +0900180 'delete_on_termination': True}]
Joe Gordon8843f0f2015-03-17 15:07:34 -0700181 kwargs['block_device_mapping_v2'] = bd_map_v2
182
183 # Since this is boot from volume an image does not need
184 # to be specified.
185 image_id = ''
186
Ken'ichi Ohmichif2d436e2015-09-03 01:13:16 +0000187 body = clients.servers_client.create_server(name=name, imageRef=image_id,
188 flavorRef=flavor,
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000189 **kwargs)
190
191 # handle the case of multiple servers
Ghanshyam4de44ae2015-12-25 10:34:00 +0900192 if multiple_create_request:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000193 # Get servers created which name match with name param.
194 body_servers = clients.servers_client.list_servers()
195 servers = \
196 [s for s in body_servers['servers'] if s['name'].startswith(name)]
ghanshyam0f825252015-08-25 16:02:50 +0900197 else:
Ken'ichi Ohmichi54030522016-03-02 11:01:34 -0800198 body = rest_client.ResponseBody(body.response, body['server'])
ghanshyam0f825252015-08-25 16:02:50 +0900199 servers = [body]
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000200
201 # The name of the method to associate a floating IP to as server is too
202 # long for PEP8 compliance so:
John Warrene74890a2015-11-11 15:18:01 -0500203 assoc = clients.compute_floating_ips_client.associate_floating_ip_to_server
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000204
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000205 if wait_until:
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000206 for server in servers:
207 try:
Ken'ichi Ohmichi0eb153c2015-07-13 02:18:25 +0000208 waiters.wait_for_server_status(
Ken'ichi Ohmichifc25e692015-09-02 01:48:06 +0000209 clients.servers_client, server['id'], wait_until)
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000210
211 # Multiple validatable servers are not supported for now. Their
212 # creation will fail with the condition above (l.58).
213 if CONF.validation.run_validation and validatable:
214 if CONF.validation.connect_method == 'floating':
215 assoc(floating_ip=validation_resources[
216 'floating_ip']['ip'],
217 server_id=servers[0]['id'])
218
219 except Exception:
220 with excutils.save_and_reraise_exception():
Jordan Pittier87ba2872016-03-08 11:43:11 +0100221 for server in servers:
222 try:
223 clients.servers_client.delete_server(
224 server['id'])
225 except Exception:
Jordan Pittier525ec712016-12-07 17:51:26 +0100226 LOG.exception('Deleting server %s failed',
227 server['id'])
Artom Lifshitz9b3f42b2017-06-19 05:46:32 +0000228 for server in servers:
229 # NOTE(artom) If the servers were booted with volumes
230 # and with delete_on_termination=False we need to wait
231 # for the servers to go away before proceeding with
232 # cleanup, otherwise we'll attempt to delete the
233 # volumes while they're still attached to servers that
234 # are in the process of being deleted.
235 try:
236 waiters.wait_for_server_termination(
237 clients.servers_client, server['id'])
238 except Exception:
239 LOG.exception('Server %s failed to delete in time',
240 server['id'])
Joseph Lanouxb3e1f872015-01-30 11:13:07 +0000241
242 return body, servers
ghanshyam017b5fe2016-04-15 18:49:26 +0900243
244
ghanshyam4c1391c2016-12-01 13:13:06 +0900245def shelve_server(servers_client, server_id, force_shelve_offload=False):
ghanshyam017b5fe2016-04-15 18:49:26 +0900246 """Common wrapper utility to shelve server.
247
248 This method is a common wrapper to make server in 'SHELVED'
249 or 'SHELVED_OFFLOADED' state.
250
ghanshyam4c1391c2016-12-01 13:13:06 +0900251 :param servers_clients: Compute servers client instance.
ghanshyam017b5fe2016-04-15 18:49:26 +0900252 :param server_id: Server to make in shelve state
253 :param force_shelve_offload: Forcefully offload shelve server if it
254 is configured not to offload server
255 automatically after offload time.
256 """
ghanshyam4c1391c2016-12-01 13:13:06 +0900257 servers_client.shelve_server(server_id)
ghanshyam017b5fe2016-04-15 18:49:26 +0900258
259 offload_time = CONF.compute.shelved_offload_time
260 if offload_time >= 0:
ghanshyam4c1391c2016-12-01 13:13:06 +0900261 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900262 'SHELVED_OFFLOADED',
263 extra_timeout=offload_time)
264 else:
ghanshyam4c1391c2016-12-01 13:13:06 +0900265 waiters.wait_for_server_status(servers_client, server_id, 'SHELVED')
ghanshyam017b5fe2016-04-15 18:49:26 +0900266 if force_shelve_offload:
ghanshyam4c1391c2016-12-01 13:13:06 +0900267 servers_client.shelve_offload_server(server_id)
268 waiters.wait_for_server_status(servers_client, server_id,
ghanshyam017b5fe2016-04-15 18:49:26 +0900269 'SHELVED_OFFLOADED')
Markus Zoellerae36ce82017-03-20 16:27:26 +0100270
271
272def create_websocket(url):
273 url = urlparse.urlparse(url)
xxj8eb90982017-04-10 21:18:39 +0800274 if url.scheme == 'https':
275 client_socket = ssl.wrap_socket(socket.socket(socket.AF_INET,
276 socket.SOCK_STREAM))
277 else:
278 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100279 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
280 client_socket.connect((url.hostname, url.port))
281 # Turn the Socket into a WebSocket to do the communication
282 return _WebSocket(client_socket, url)
283
284
285class _WebSocket(object):
286 def __init__(self, client_socket, url):
287 """Contructor for the WebSocket wrapper to the socket."""
288 self._socket = client_socket
jianghua wangd22514a2017-05-08 08:05:04 +0100289 # cached stream for early frames.
290 self.cached_stream = b''
Markus Zoellerae36ce82017-03-20 16:27:26 +0100291 # Upgrade the HTTP connection to a WebSocket
292 self._upgrade(url)
293
jianghua wangd22514a2017-05-08 08:05:04 +0100294 def _recv(self, recv_size):
295 """Wrapper to receive data from the cached stream or socket."""
296 if recv_size <= 0:
297 return None
298
299 data_from_cached = b''
300 data_from_socket = b''
301 if len(self.cached_stream) > 0:
302 read_from_cached = min(len(self.cached_stream), recv_size)
303 data_from_cached += self.cached_stream[:read_from_cached]
304 self.cached_stream = self.cached_stream[read_from_cached:]
305 recv_size -= read_from_cached
306 if recv_size > 0:
307 data_from_socket = self._socket.recv(recv_size)
308 return data_from_cached + data_from_socket
309
Markus Zoellerae36ce82017-03-20 16:27:26 +0100310 def receive_frame(self):
311 """Wrapper for receiving data to parse the WebSocket frame format"""
312 # We need to loop until we either get some bytes back in the frame
313 # or no data was received (meaning the socket was closed). This is
314 # done to handle the case where we get back some empty frames
315 while True:
jianghua wangd22514a2017-05-08 08:05:04 +0100316 header = self._recv(2)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100317 # If we didn't receive any data, just return None
Masayuki Igawa0c0f0142017-04-10 17:22:02 +0900318 if not header:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100319 return None
320 # We will make the assumption that we are only dealing with
321 # frames less than 125 bytes here (for the negotiation) and
322 # that only the 2nd byte contains the length, and since the
323 # server doesn't do masking, we can just read the data length
324 if ord_func(header[1]) & 127 > 0:
jianghua wangd22514a2017-05-08 08:05:04 +0100325 return self._recv(ord_func(header[1]) & 127)
Markus Zoellerae36ce82017-03-20 16:27:26 +0100326
327 def send_frame(self, data):
328 """Wrapper for sending data to add in the WebSocket frame format."""
329 frame_bytes = list()
330 # For the first byte, want to say we are sending binary data (130)
331 frame_bytes.append(130)
332 # Only sending negotiation data so don't need to worry about > 125
333 # We do need to add the bit that says we are masking the data
334 frame_bytes.append(len(data) | 128)
335 # We don't really care about providing a random mask for security
336 # So we will just hard-code a value since a test program
337 mask = [7, 2, 1, 9]
338 for i in range(len(mask)):
339 frame_bytes.append(mask[i])
340 # Mask each of the actual data bytes that we are going to send
341 for i in range(len(data)):
342 frame_bytes.append(ord_func(data[i]) ^ mask[i % 4])
343 # Convert our integer list to a binary array of bytes
344 frame_bytes = struct.pack('!%iB' % len(frame_bytes), * frame_bytes)
345 self._socket.sendall(frame_bytes)
346
347 def close(self):
348 """Helper method to close the connection."""
349 # Close down the real socket connection and exit the test program
350 if self._socket is not None:
351 self._socket.shutdown(1)
352 self._socket.close()
353 self._socket = None
354
355 def _upgrade(self, url):
356 """Upgrade the HTTP connection to a WebSocket and verify."""
357 # The real request goes to the /websockify URI always
358 reqdata = 'GET /websockify HTTP/1.1\r\n'
359 reqdata += 'Host: %s:%s\r\n' % (url.hostname, url.port)
360 # Tell the HTTP Server to Upgrade the connection to a WebSocket
361 reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
362 # The token=xxx is sent as a Cookie not in the URI
363 reqdata += 'Cookie: %s\r\n' % url.query
364 # Use a hard-coded WebSocket key since a test program
365 reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
366 reqdata += 'Sec-WebSocket-Version: 13\r\n'
367 # We are choosing to use binary even though browser may do Base64
368 reqdata += 'Sec-WebSocket-Protocol: binary\r\n\r\n'
369 # Send the HTTP GET request and get the response back
370 self._socket.sendall(reqdata.encode('utf8'))
371 self.response = data = self._socket.recv(4096)
372 # Loop through & concatenate all of the data in the response body
jianghua wangd22514a2017-05-08 08:05:04 +0100373 end_loc = self.response.find(b'\r\n\r\n')
374 while data and end_loc < 0:
Markus Zoellerae36ce82017-03-20 16:27:26 +0100375 data = self._socket.recv(4096)
376 self.response += data
jianghua wangd22514a2017-05-08 08:05:04 +0100377 end_loc = self.response.find(b'\r\n\r\n')
378
379 if len(self.response) > end_loc + 4:
380 # In case some frames (e.g. the first RFP negotiation) have
381 # arrived, cache it for next reading.
382 self.cached_stream = self.response[end_loc + 4:]
383 # ensure response ends with '\r\n\r\n'.
384 self.response = self.response[:end_loc + 4]