blob: 1fdaf19b0f694907a236d405be17dc41182df905 [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
Jerry Cai9733d0e2014-03-19 15:50:49 +0800120 def create_volume(self, size=None, **kwargs):
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800121 """Creates a new Volume.
122
Jerry Cai9733d0e2014-03-19 15:50:49 +0800123 :param size: Size of volume in GB.
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 """
Jerry Cai9733d0e2014-03-19 15:50:49 +0800132 # for bug #1293885:
133 # If no size specified, read volume size from CONF
134 if size is None:
135 size = CONF.volume.volume_size
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800136 # NOTE(afazekas): it should use a volume namespace
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000137 volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800138
139 if 'metadata' in kwargs:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000140 _metadata = common.Element('metadata')
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800141 volume.append(_metadata)
142 for key, value in kwargs['metadata'].items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000143 meta = common.Element('meta')
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800144 meta.add_attr('key', key)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000145 meta.append(common.Text(value))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800146 _metadata.append(meta)
147 attr_to_add = kwargs.copy()
148 del attr_to_add['metadata']
149 else:
150 attr_to_add = kwargs
151
152 for key, value in attr_to_add.items():
153 volume.add_attr(key, value)
154
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000155 resp, body = self.post('volumes', str(common.Document(volume)))
156 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800157 return resp, body
158
159 def update_volume(self, volume_id, **kwargs):
160 """Updates the Specified Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000161 put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800162
163 resp, body = self.put('volumes/%s' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000164 str(common.Document(put_body)))
165 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800166 return resp, body
167
168 def delete_volume(self, volume_id):
169 """Deletes the Specified Volume."""
170 return self.delete("volumes/%s" % str(volume_id))
171
172 def wait_for_volume_status(self, volume_id, status):
173 """Waits for a Volume to reach a given status."""
174 resp, body = self.get_volume(volume_id)
175 volume_status = body['status']
176 start = int(time.time())
177
178 while volume_status != status:
179 time.sleep(self.build_interval)
180 resp, body = self.get_volume(volume_id)
181 volume_status = body['status']
182 if volume_status == 'error':
183 raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
184
185 if int(time.time()) - start >= self.build_timeout:
186 message = 'Volume %s failed to reach %s status within '\
187 'the required time (%s s).' % (volume_id,
188 status,
189 self.build_timeout)
190 raise exceptions.TimeoutException(message)
191
192 def is_resource_deleted(self, id):
193 try:
194 self.get_volume(id)
195 except exceptions.NotFound:
196 return True
197 return False
198
199 def attach_volume(self, volume_id, instance_uuid, mountpoint):
200 """Attaches a volume to a given instance on a given mountpoint."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000201 post_body = common.Element("os-attach",
202 instance_uuid=instance_uuid,
203 mountpoint=mountpoint
204 )
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800205 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000206 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800207 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000208 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800209 return resp, body
210
211 def detach_volume(self, volume_id):
212 """Detaches a volume from an instance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000213 post_body = common.Element("os-detach")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800214 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000215 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800216 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000217 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800218 return resp, body
219
220 def upload_volume(self, volume_id, image_name, disk_format):
221 """Uploads a volume in Glance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000222 post_body = common.Element("os-volume_upload_image",
223 image_name=image_name,
224 disk_format=disk_format)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800225 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000226 resp, body = self.post(url, str(common.Document(post_body)))
227 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800228 return resp, volume
229
230 def extend_volume(self, volume_id, extend_size):
231 """Extend a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000232 post_body = common.Element("os-extend",
233 new_size=extend_size)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800234 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000235 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800236 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000237 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800238 return resp, body
239
240 def reset_volume_status(self, volume_id, status):
241 """Reset the Specified Volume's Status."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000242 post_body = common.Element("os-reset_status",
243 status=status
244 )
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800245 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000246 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800247 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000248 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800249 return resp, body
250
251 def volume_begin_detaching(self, volume_id):
252 """Volume Begin Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000253 post_body = common.Element("os-begin_detaching")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800254 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000255 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800256 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000257 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800258 return resp, body
259
260 def volume_roll_detaching(self, volume_id):
261 """Volume Roll Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000262 post_body = common.Element("os-roll_detaching")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800263 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000264 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800265 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000266 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800267 return resp, body
268
269 def reserve_volume(self, volume_id):
270 """Reserves a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000271 post_body = common.Element("os-reserve")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800272 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000273 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800274 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000275 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800276 return resp, body
277
278 def unreserve_volume(self, volume_id):
279 """Restore a reserved volume ."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000280 post_body = common.Element("os-unreserve")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800281 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000282 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800283 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000284 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800285 return resp, body
286
Zhi Kun Liu8cc3c842014-01-07 10:44:34 +0800287 def create_volume_transfer(self, vol_id, name=None):
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800288 """Create a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000289 post_body = common.Element("transfer", volume_id=vol_id)
Zhi Kun Liu8cc3c842014-01-07 10:44:34 +0800290 if name:
291 post_body.add_attr('name', name)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800292 resp, body = self.post('os-volume-transfer',
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000293 str(common.Document(post_body)))
294 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800295 return resp, volume
296
297 def get_volume_transfer(self, transfer_id):
298 """Returns the details of a volume transfer."""
299 url = "os-volume-transfer/%s" % str(transfer_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200300 resp, body = self.get(url)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000301 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800302 return resp, volume
303
304 def list_volume_transfers(self, params=None):
305 """List all the volume transfers created."""
306 url = 'os-volume-transfer'
307 if params:
308 url += '?%s' % urllib.urlencode(params)
309
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200310 resp, body = self.get(url)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800311 body = etree.fromstring(body)
312 volumes = []
313 if body is not None:
314 volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
315 return resp, volumes
316
317 def _parse_volume_transfer(self, body):
318 vol = dict((attr, body.get(attr)) for attr in body.keys())
319 for child in body.getchildren():
320 tag = child.tag
321 if tag.startswith("{"):
322 tag = tag.split("}", 1)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000323 vol[tag] = common.xml_to_json(child)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800324 return vol
325
326 def delete_volume_transfer(self, transfer_id):
327 """Delete a volume transfer."""
328 return self.delete("os-volume-transfer/%s" % str(transfer_id))
329
330 def accept_volume_transfer(self, transfer_id, transfer_auth_key):
331 """Accept a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000332 post_body = common.Element("accept", auth_key=transfer_auth_key)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800333 url = 'os-volume-transfer/%s/accept' % transfer_id
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000334 resp, body = self.post(url, str(common.Document(post_body)))
335 volume = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800336 return resp, volume
337
338 def update_volume_readonly(self, volume_id, readonly):
339 """Update the Specified Volume readonly."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000340 post_body = common.Element("os-update_readonly_flag",
341 readonly=readonly)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800342 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000343 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800344 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000345 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800346 return resp, body
347
348 def force_delete_volume(self, volume_id):
349 """Force Delete Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000350 post_body = common.Element("os-force_delete")
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800351 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000352 resp, body = self.post(url, str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800353 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000354 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800355 return resp, body
356
357 def _metadata_body(self, meta):
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000358 post_body = common.Element('metadata')
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800359 for k, v in meta.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000360 data = common.Element('meta', key=k)
361 data.append(common.Text(v))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800362 post_body.append(data)
363 return post_body
364
365 def _parse_key_value(self, node):
366 """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
367 data = {}
368 for node in node.getchildren():
369 data[node.get('key')] = node.text
370 return data
371
372 def create_volume_metadata(self, volume_id, metadata):
373 """Create metadata for the volume."""
374 post_body = self._metadata_body(metadata)
375 resp, body = self.post('volumes/%s/metadata' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000376 str(common.Document(post_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800377 body = self._parse_key_value(etree.fromstring(body))
378 return resp, body
379
380 def get_volume_metadata(self, volume_id):
381 """Get metadata of the volume."""
382 url = "volumes/%s/metadata" % str(volume_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200383 resp, body = self.get(url)
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800384 body = self._parse_key_value(etree.fromstring(body))
385 return resp, body
386
387 def update_volume_metadata(self, volume_id, metadata):
388 """Update metadata for the volume."""
389 put_body = self._metadata_body(metadata)
390 url = "volumes/%s/metadata" % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000391 resp, body = self.put(url, str(common.Document(put_body)))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800392 body = self._parse_key_value(etree.fromstring(body))
393 return resp, body
394
395 def update_volume_metadata_item(self, volume_id, id, meta_item):
396 """Update metadata item for the volume."""
397 for k, v in meta_item.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000398 put_body = common.Element('meta', key=k)
399 put_body.append(common.Text(v))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800400 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000401 resp, body = self.put(url, str(common.Document(put_body)))
402 body = common.xml_to_json(etree.fromstring(body))
Zhi Kun Liu4be2f602014-02-08 11:52:05 +0800403 return resp, body
404
405 def delete_volume_metadata_item(self, volume_id, id):
406 """Delete metadata item for the volume."""
407 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
408 return self.delete(url)