blob: e735a657e44521ed5bba7cc23002a29ab3cda9f2 [file] [log] [blame]
Zhi Kun Liu4be2f602014-02-08 11:52:05 +08001# Copyright 2012 IBM Corp.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# 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, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import time
17import urllib
18
19from lxml import etree
20
vponomaryov960eeb42014-02-22 18:25:25 +020021from tempest.common import rest_client
Matthew Treinish28f164c2014-03-04 18:55:06 +000022from tempest.common import xml_utils as common
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080023from tempest import config
24from tempest import exceptions
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080025
26CONF = config.CONF
27
28
vponomaryov960eeb42014-02-22 18:25:25 +020029class VolumesV2ClientXML(rest_client.RestClient):
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080030 """
31 Client class to send CRUD Volume API requests to a Cinder endpoint
32 """
vponomaryov960eeb42014-02-22 18:25:25 +020033 TYPE = "xml"
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080034
35 def __init__(self, auth_provider):
Zhi Kun Liu8cc3c842014-01-07 10:44:34 +080036 super(VolumesV2ClientXML, self).__init__(auth_provider)
37
38 self.api_version = "v2"
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080039 self.service = CONF.volume.catalog_type
40 self.build_interval = CONF.compute.build_interval
41 self.build_timeout = CONF.compute.build_timeout
42
43 def _parse_volume(self, body):
44 vol = dict((attr, body.get(attr)) for attr in body.keys())
45
46 for child in body.getchildren():
47 tag = child.tag
48 if tag.startswith("{"):
49 ns, tag = tag.split("}", 1)
50 if tag == 'metadata':
51 vol['metadata'] = dict((meta.get('key'),
52 meta.text) for meta in
53 child.getchildren())
54 else:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +000055 vol[tag] = common.xml_to_json(child)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080056 return vol
57
58 def get_attachment_from_volume(self, volume):
59 """Return the element 'attachment' from input volumes."""
60 return volume['attachments']['attachment']
61
62 def _check_if_bootable(self, volume):
63 """
64 Check if the volume is bootable, also change the value
65 of 'bootable' from string to boolean.
66 """
67
68 # NOTE(jdg): Version 1 of Cinder API uses lc strings
69 # We should consider being explicit in this check to
70 # avoid introducing bugs like: LP #1227837
71
72 if volume['bootable'].lower() == 'true':
73 volume['bootable'] = True
74 elif volume['bootable'].lower() == 'false':
75 volume['bootable'] = False
76 else:
77 raise ValueError(
78 'bootable flag is supposed to be either True or False,'
79 'it is %s' % volume['bootable'])
80 return volume
81
82 def list_volumes(self, params=None):
83 """List all the volumes created."""
84 url = 'volumes'
85
86 if params:
87 url += '?%s' % urllib.urlencode(params)
88
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +020089 resp, body = self.get(url)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080090 body = etree.fromstring(body)
91 volumes = []
92 if body is not None:
93 volumes += [self._parse_volume(vol) for vol in list(body)]
Zhi Kun Liu4be2f602014-02-08 11:52:05 +080094 return resp, volumes
95
96 def list_volumes_with_detail(self, params=None):
97 """List all the details of volumes."""
98 url = 'volumes/detail'
99
100 if params:
101 url += '?%s' % urllib.urlencode(params)
102
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200103 resp, body = self.get(url)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800104 body = etree.fromstring(body)
105 volumes = []
106 if body is not None:
107 volumes += [self._parse_volume(vol) for vol in list(body)]
108 for v in volumes:
109 v = self._check_if_bootable(v)
110 return resp, volumes
111
112 def get_volume(self, volume_id):
113 """Returns the details of a single volume."""
114 url = "volumes/%s" % str(volume_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200115 resp, body = self.get(url)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800116 body = self._parse_volume(etree.fromstring(body))
117 body = self._check_if_bootable(body)
118 return resp, body
119
120 def create_volume(self, size, **kwargs):
121 """Creates a new Volume.
122
123 :param size: Size of volume in GB. (Required)
Zhi Kun Liu8cc3c842014-01-07 10:44:34 +0800124 :param name: Optional Volume Name.
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800125 :param metadata: An optional dictionary of values for metadata.
126 :param volume_type: Optional Name of volume_type for the volume
127 :param snapshot_id: When specified the volume is created from
128 this snapshot
129 :param imageRef: When specified the volume is created from this
130 image
131 """
132 # NOTE(afazekas): it should use a volume namespace
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000133 volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800134
135 if 'metadata' in kwargs:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000136 _metadata = common.Element('metadata')
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800137 volume.append(_metadata)
138 for key, value in kwargs['metadata'].items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000139 meta = common.Element('meta')
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800140 meta.add_attr('key', key)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000141 meta.append(common.Text(value))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800142 _metadata.append(meta)
143 attr_to_add = kwargs.copy()
144 del attr_to_add['metadata']
145 else:
146 attr_to_add = kwargs
147
148 for key, value in attr_to_add.items():
149 volume.add_attr(key, value)
150
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000151 resp, body = self.post('volumes', str(common.Document(volume)))
152 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800153 return resp, body
154
155 def update_volume(self, volume_id, **kwargs):
156 """Updates the Specified Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000157 put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800158
159 resp, body = self.put('volumes/%s' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000160 str(common.Document(put_body)))
161 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800162 return resp, body
163
164 def delete_volume(self, volume_id):
165 """Deletes the Specified Volume."""
166 return self.delete("volumes/%s" % str(volume_id))
167
168 def wait_for_volume_status(self, volume_id, status):
169 """Waits for a Volume to reach a given status."""
170 resp, body = self.get_volume(volume_id)
171 volume_status = body['status']
172 start = int(time.time())
173
174 while volume_status != status:
175 time.sleep(self.build_interval)
176 resp, body = self.get_volume(volume_id)
177 volume_status = body['status']
178 if volume_status == 'error':
179 raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
180
181 if int(time.time()) - start >= self.build_timeout:
182 message = 'Volume %s failed to reach %s status within '\
183 'the required time (%s s).' % (volume_id,
184 status,
185 self.build_timeout)
186 raise exceptions.TimeoutException(message)
187
188 def is_resource_deleted(self, id):
189 try:
190 self.get_volume(id)
191 except exceptions.NotFound:
192 return True
193 return False
194
195 def attach_volume(self, volume_id, instance_uuid, mountpoint):
196 """Attaches a volume to a given instance on a given mountpoint."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000197 post_body = common.Element("os-attach",
198 instance_uuid=instance_uuid,
199 mountpoint=mountpoint
200 )
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800201 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000202 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800203 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000204 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800205 return resp, body
206
207 def detach_volume(self, volume_id):
208 """Detaches a volume from an instance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000209 post_body = common.Element("os-detach")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800210 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000211 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800212 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000213 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800214 return resp, body
215
216 def upload_volume(self, volume_id, image_name, disk_format):
217 """Uploads a volume in Glance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000218 post_body = common.Element("os-volume_upload_image",
219 image_name=image_name,
220 disk_format=disk_format)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800221 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000222 resp, body = self.post(url, str(common.Document(post_body)))
223 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800224 return resp, volume
225
226 def extend_volume(self, volume_id, extend_size):
227 """Extend a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000228 post_body = common.Element("os-extend",
229 new_size=extend_size)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800230 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000231 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800232 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000233 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800234 return resp, body
235
236 def reset_volume_status(self, volume_id, status):
237 """Reset the Specified Volume's Status."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000238 post_body = common.Element("os-reset_status",
239 status=status
240 )
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800241 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000242 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800243 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000244 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800245 return resp, body
246
247 def volume_begin_detaching(self, volume_id):
248 """Volume Begin Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000249 post_body = common.Element("os-begin_detaching")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800250 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000251 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800252 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000253 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800254 return resp, body
255
256 def volume_roll_detaching(self, volume_id):
257 """Volume Roll Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000258 post_body = common.Element("os-roll_detaching")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800259 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000260 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800261 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000262 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800263 return resp, body
264
265 def reserve_volume(self, volume_id):
266 """Reserves a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000267 post_body = common.Element("os-reserve")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800268 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000269 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800270 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000271 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800272 return resp, body
273
274 def unreserve_volume(self, volume_id):
275 """Restore a reserved volume ."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000276 post_body = common.Element("os-unreserve")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800277 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000278 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800279 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000280 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800281 return resp, body
282
Zhi Kun Liu8cc3c842014-01-07 10:44:34 +0800283 def create_volume_transfer(self, vol_id, name=None):
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800284 """Create a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000285 post_body = common.Element("transfer", volume_id=vol_id)
Zhi Kun Liu8cc3c842014-01-07 10:44:34 +0800286 if name:
287 post_body.add_attr('name', name)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800288 resp, body = self.post('os-volume-transfer',
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000289 str(common.Document(post_body)))
290 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800291 return resp, volume
292
293 def get_volume_transfer(self, transfer_id):
294 """Returns the details of a volume transfer."""
295 url = "os-volume-transfer/%s" % str(transfer_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200296 resp, body = self.get(url)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000297 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800298 return resp, volume
299
300 def list_volume_transfers(self, params=None):
301 """List all the volume transfers created."""
302 url = 'os-volume-transfer'
303 if params:
304 url += '?%s' % urllib.urlencode(params)
305
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200306 resp, body = self.get(url)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800307 body = etree.fromstring(body)
308 volumes = []
309 if body is not None:
310 volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
311 return resp, volumes
312
313 def _parse_volume_transfer(self, body):
314 vol = dict((attr, body.get(attr)) for attr in body.keys())
315 for child in body.getchildren():
316 tag = child.tag
317 if tag.startswith("{"):
318 tag = tag.split("}", 1)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000319 vol[tag] = common.xml_to_json(child)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800320 return vol
321
322 def delete_volume_transfer(self, transfer_id):
323 """Delete a volume transfer."""
324 return self.delete("os-volume-transfer/%s" % str(transfer_id))
325
326 def accept_volume_transfer(self, transfer_id, transfer_auth_key):
327 """Accept a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000328 post_body = common.Element("accept", auth_key=transfer_auth_key)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800329 url = 'os-volume-transfer/%s/accept' % transfer_id
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000330 resp, body = self.post(url, str(common.Document(post_body)))
331 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800332 return resp, volume
333
334 def update_volume_readonly(self, volume_id, readonly):
335 """Update the Specified Volume readonly."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000336 post_body = common.Element("os-update_readonly_flag",
337 readonly=readonly)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800338 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000339 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800340 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000341 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800342 return resp, body
343
344 def force_delete_volume(self, volume_id):
345 """Force Delete Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000346 post_body = common.Element("os-force_delete")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800347 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000348 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800349 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000350 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800351 return resp, body
352
353 def _metadata_body(self, meta):
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000354 post_body = common.Element('metadata')
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800355 for k, v in meta.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000356 data = common.Element('meta', key=k)
357 data.append(common.Text(v))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800358 post_body.append(data)
359 return post_body
360
361 def _parse_key_value(self, node):
362 """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
363 data = {}
364 for node in node.getchildren():
365 data[node.get('key')] = node.text
366 return data
367
368 def create_volume_metadata(self, volume_id, metadata):
369 """Create metadata for the volume."""
370 post_body = self._metadata_body(metadata)
371 resp, body = self.post('volumes/%s/metadata' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000372 str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800373 body = self._parse_key_value(etree.fromstring(body))
374 return resp, body
375
376 def get_volume_metadata(self, volume_id):
377 """Get metadata of the volume."""
378 url = "volumes/%s/metadata" % str(volume_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200379 resp, body = self.get(url)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800380 body = self._parse_key_value(etree.fromstring(body))
381 return resp, body
382
383 def update_volume_metadata(self, volume_id, metadata):
384 """Update metadata for the volume."""
385 put_body = self._metadata_body(metadata)
386 url = "volumes/%s/metadata" % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000387 resp, body = self.put(url, str(common.Document(put_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800388 body = self._parse_key_value(etree.fromstring(body))
389 return resp, body
390
391 def update_volume_metadata_item(self, volume_id, id, meta_item):
392 """Update metadata item for the volume."""
393 for k, v in meta_item.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000394 put_body = common.Element('meta', key=k)
395 put_body.append(common.Text(v))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800396 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000397 resp, body = self.put(url, str(common.Document(put_body)))
398 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800399 return resp, body
400
401 def delete_volume_metadata_item(self, volume_id, id):
402 """Delete metadata item for the volume."""
403 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
404 return self.delete(url)