blob: a8c1ae5a84db6e77e005911cc25c091a81eefc94 [file] [log] [blame]
Kurt Taylor6a6f5be2013-04-02 18:53:47 -04001# Copyright 2012 IBM Corp.
Matthew Treinish9854d5b2012-09-20 10:22:13 -04002# 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
Matthew Treinish26dd0fa2012-12-04 17:14:37 -050017import urllib
Matthew Treinish96e9e882014-06-09 18:37:19 -040018from xml.sax import saxutils
Matthew Treinish9854d5b2012-09-20 10:22:13 -040019
20from lxml import etree
21
vponomaryov960eeb42014-02-22 18:25:25 +020022from tempest.common import rest_client
Matthew Treinish28f164c2014-03-04 18:55:06 +000023from tempest.common import xml_utils as common
Matthew Treinish684d8992014-01-30 16:27:40 +000024from tempest import config
Matthew Treinish9854d5b2012-09-20 10:22:13 -040025from tempest import exceptions
Matthew Treinish9854d5b2012-09-20 10:22:13 -040026
Matthew Treinish684d8992014-01-30 16:27:40 +000027CONF = config.CONF
28
Attila Fazekasd38f7162014-03-05 19:28:40 +010029VOLUME_NS_BASE = 'http://docs.openstack.org/volume/ext/'
30VOLUME_HOST_NS = VOLUME_NS_BASE + 'volume_host_attribute/api/v1'
31VOLUME_MIG_STATUS_NS = VOLUME_NS_BASE + 'volume_mig_status_attribute/api/v1'
32VOLUMES_TENANT_NS = VOLUME_NS_BASE + 'volume_tenant_attribute/api/v1'
33
Matthew Treinish9854d5b2012-09-20 10:22:13 -040034
Zhi Kun Liu6e6cf832014-05-08 17:25:22 +080035class BaseVolumesClientXML(rest_client.RestClient):
Matthew Treinish9854d5b2012-09-20 10:22:13 -040036 """
Zhi Kun Liu6e6cf832014-05-08 17:25:22 +080037 Base client class to send CRUD Volume API requests to a Cinder endpoint
Matthew Treinish9854d5b2012-09-20 10:22:13 -040038 """
vponomaryov960eeb42014-02-22 18:25:25 +020039 TYPE = "xml"
Matthew Treinish9854d5b2012-09-20 10:22:13 -040040
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000041 def __init__(self, auth_provider):
Zhi Kun Liu6e6cf832014-05-08 17:25:22 +080042 super(BaseVolumesClientXML, self).__init__(auth_provider)
Matthew Treinish684d8992014-01-30 16:27:40 +000043 self.service = CONF.volume.catalog_type
44 self.build_interval = CONF.compute.build_interval
45 self.build_timeout = CONF.compute.build_timeout
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +000046 self.create_resp = 200
Matthew Treinish9854d5b2012-09-20 10:22:13 -040047
Attila Fazekasd38f7162014-03-05 19:28:40 +010048 def _translate_attributes_to_json(self, volume):
49 volume_host_attr = '{' + VOLUME_HOST_NS + '}host'
50 volume_mig_stat_attr = '{' + VOLUME_MIG_STATUS_NS + '}migstat'
51 volume_mig_name_attr = '{' + VOLUME_MIG_STATUS_NS + '}name_id'
52 volume_tenant_id_attr = '{' + VOLUMES_TENANT_NS + '}tenant_id'
53 if volume_host_attr in volume:
54 volume['os-vol-host-attr:host'] = volume.pop(volume_host_attr)
55 if volume_mig_stat_attr in volume:
56 volume['os-vol-mig-status-attr:migstat'] = volume.pop(
57 volume_mig_stat_attr)
58 if volume_mig_name_attr in volume:
59 volume['os-vol-mig-status-attr:name_id'] = volume.pop(
60 volume_mig_name_attr)
61 if volume_tenant_id_attr in volume:
62 volume['os-vol-tenant-attr:tenant_id'] = volume.pop(
63 volume_tenant_id_attr)
64
Matthew Treinish9854d5b2012-09-20 10:22:13 -040065 def _parse_volume(self, body):
66 vol = dict((attr, body.get(attr)) for attr in body.keys())
67
68 for child in body.getchildren():
69 tag = child.tag
70 if tag.startswith("{"):
71 ns, tag = tag.split("}", 1)
72 if tag == 'metadata':
73 vol['metadata'] = dict((meta.get('key'),
Attila Fazekas786236c2013-01-31 16:06:51 +010074 meta.text) for meta in
75 child.getchildren())
Matthew Treinish9854d5b2012-09-20 10:22:13 -040076 else:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +000077 vol[tag] = common.xml_to_json(child)
Attila Fazekasd38f7162014-03-05 19:28:40 +010078 self._translate_attributes_to_json(vol)
79 self._check_if_bootable(vol)
Attila Fazekas786236c2013-01-31 16:06:51 +010080 return vol
Matthew Treinish9854d5b2012-09-20 10:22:13 -040081
anju tiwari789449a2013-08-29 16:56:17 +053082 def get_attachment_from_volume(self, volume):
83 """Return the element 'attachment' from input volumes."""
84 return volume['attachments']['attachment']
85
Nayna Patel5e76be12013-08-19 12:10:16 +000086 def _check_if_bootable(self, volume):
87 """
88 Check if the volume is bootable, also change the value
89 of 'bootable' from string to boolean.
90 """
John Griffithf55f69e2013-09-19 14:10:57 -060091
92 # NOTE(jdg): Version 1 of Cinder API uses lc strings
93 # We should consider being explicit in this check to
94 # avoid introducing bugs like: LP #1227837
95
96 if volume['bootable'].lower() == 'true':
Nayna Patel5e76be12013-08-19 12:10:16 +000097 volume['bootable'] = True
John Griffithf55f69e2013-09-19 14:10:57 -060098 elif volume['bootable'].lower() == 'false':
Nayna Patel5e76be12013-08-19 12:10:16 +000099 volume['bootable'] = False
100 else:
101 raise ValueError(
102 'bootable flag is supposed to be either True or False,'
103 'it is %s' % volume['bootable'])
104 return volume
105
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400106 def list_volumes(self, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500107 """List all the volumes created."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400108 url = 'volumes'
109
110 if params:
111 url += '?%s' % urllib.urlencode(params)
112
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200113 resp, body = self.get(url)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400114 body = etree.fromstring(body)
115 volumes = []
116 if body is not None:
117 volumes += [self._parse_volume(vol) for vol in list(body)]
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000118 self.expected_success(200, resp.status)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400119 return resp, volumes
120
121 def list_volumes_with_detail(self, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500122 """List all the details of volumes."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400123 url = 'volumes/detail'
124
125 if params:
126 url += '?%s' % urllib.urlencode(params)
127
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200128 resp, body = self.get(url)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400129 body = etree.fromstring(body)
130 volumes = []
131 if body is not None:
132 volumes += [self._parse_volume(vol) for vol in list(body)]
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000133 self.expected_success(200, resp.status)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400134 return resp, volumes
135
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100136 def get_volume(self, volume_id):
Sean Daguef237ccb2013-01-04 15:19:14 -0500137 """Returns the details of a single volume."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400138 url = "volumes/%s" % str(volume_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200139 resp, body = self.get(url)
Nayna Patel5e76be12013-08-19 12:10:16 +0000140 body = self._parse_volume(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000141 self.expected_success(200, resp.status)
Nayna Patel5e76be12013-08-19 12:10:16 +0000142 return resp, body
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400143
Jerry Cai9733d0e2014-03-19 15:50:49 +0800144 def create_volume(self, size=None, **kwargs):
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400145 """Creates a new Volume.
146
Jerry Cai9733d0e2014-03-19 15:50:49 +0800147 :param size: Size of volume in GB.
Zhi Kun Liu6e6cf832014-05-08 17:25:22 +0800148 :param display_name: Optional Volume Name(only for V1).
149 :param name: Optional Volume Name(only for V2).
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400150 :param display_name: Optional Volume Name.
151 :param metadata: An optional dictionary of values for metadata.
Attila Fazekas786236c2013-01-31 16:06:51 +0100152 :param volume_type: Optional Name of volume_type for the volume
153 :param snapshot_id: When specified the volume is created from
154 this snapshot
Giulio Fidente36836c42013-04-05 15:43:51 +0200155 :param imageRef: When specified the volume is created from this
156 image
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400157 """
Jerry Cai9733d0e2014-03-19 15:50:49 +0800158 # for bug #1293885:
159 # If no size specified, read volume size from CONF
160 if size is None:
161 size = CONF.volume.volume_size
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200162 # NOTE(afazekas): it should use a volume namespace
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000163 volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400164
Attila Fazekas786236c2013-01-31 16:06:51 +0100165 if 'metadata' in kwargs:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000166 _metadata = common.Element('metadata')
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400167 volume.append(_metadata)
Attila Fazekas786236c2013-01-31 16:06:51 +0100168 for key, value in kwargs['metadata'].items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000169 meta = common.Element('meta')
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400170 meta.add_attr('key', key)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000171 meta.append(common.Text(value))
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400172 _metadata.append(meta)
Attila Fazekas786236c2013-01-31 16:06:51 +0100173 attr_to_add = kwargs.copy()
174 del attr_to_add['metadata']
175 else:
176 attr_to_add = kwargs
177
178 for key, value in attr_to_add.items():
179 volume.add_attr(key, value)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400180
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000181 resp, body = self.post('volumes', str(common.Document(volume)))
182 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000183 self.expected_success(self.create_resp, resp.status)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400184 return resp, body
185
QingXin Meng611768a2013-09-18 00:51:33 -0700186 def update_volume(self, volume_id, **kwargs):
187 """Updates the Specified Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000188 put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
QingXin Meng611768a2013-09-18 00:51:33 -0700189
190 resp, body = self.put('volumes/%s' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000191 str(common.Document(put_body)))
192 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000193 self.expected_success(200, resp.status)
QingXin Meng611768a2013-09-18 00:51:33 -0700194 return resp, body
195
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400196 def delete_volume(self, volume_id):
Sean Daguef237ccb2013-01-04 15:19:14 -0500197 """Deletes the Specified Volume."""
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000198 resp, body = self.delete("volumes/%s" % str(volume_id))
199 self.expected_success(202, resp.status)
200 return resp, body
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400201
202 def wait_for_volume_status(self, volume_id, status):
Sean Daguef237ccb2013-01-04 15:19:14 -0500203 """Waits for a Volume to reach a given status."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400204 resp, body = self.get_volume(volume_id)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400205 volume_status = body['status']
206 start = int(time.time())
207
208 while volume_status != status:
209 time.sleep(self.build_interval)
210 resp, body = self.get_volume(volume_id)
211 volume_status = body['status']
212 if volume_status == 'error':
213 raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
214
215 if int(time.time()) - start >= self.build_timeout:
216 message = 'Volume %s failed to reach %s status within '\
Attila Fazekas786236c2013-01-31 16:06:51 +0100217 'the required time (%s s).' % (volume_id,
218 status,
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400219 self.build_timeout)
220 raise exceptions.TimeoutException(message)
221
222 def is_resource_deleted(self, id):
223 try:
Attila Fazekasf53172c2013-01-26 01:04:42 +0100224 self.get_volume(id)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400225 except exceptions.NotFound:
226 return True
227 return False
anju tiwari789449a2013-08-29 16:56:17 +0530228
229 def attach_volume(self, volume_id, instance_uuid, mountpoint):
230 """Attaches a volume to a given instance on a given mountpoint."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000231 post_body = common.Element("os-attach",
232 instance_uuid=instance_uuid,
233 mountpoint=mountpoint
234 )
anju tiwari789449a2013-08-29 16:56:17 +0530235 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000236 resp, body = self.post(url, str(common.Document(post_body)))
anju tiwari789449a2013-08-29 16:56:17 +0530237 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000238 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000239 self.expected_success(202, resp.status)
anju tiwari789449a2013-08-29 16:56:17 +0530240 return resp, body
241
242 def detach_volume(self, volume_id):
243 """Detaches a volume from an instance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000244 post_body = common.Element("os-detach")
anju tiwari789449a2013-08-29 16:56:17 +0530245 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)))
anju tiwari789449a2013-08-29 16:56:17 +0530247 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000248 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000249 self.expected_success(202, resp.status)
anju tiwari789449a2013-08-29 16:56:17 +0530250 return resp, body
251
Ryan Hsua67f4632013-08-29 16:03:06 -0700252 def upload_volume(self, volume_id, image_name, disk_format):
anju tiwari789449a2013-08-29 16:56:17 +0530253 """Uploads a volume in Glance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000254 post_body = common.Element("os-volume_upload_image",
255 image_name=image_name,
256 disk_format=disk_format)
anju tiwari789449a2013-08-29 16:56:17 +0530257 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000258 resp, body = self.post(url, str(common.Document(post_body)))
259 volume = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000260 self.expected_success(202, resp.status)
anju tiwari789449a2013-08-29 16:56:17 +0530261 return resp, volume
wanghao5b981752013-10-22 11:41:41 +0800262
263 def extend_volume(self, volume_id, extend_size):
264 """Extend a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000265 post_body = common.Element("os-extend",
266 new_size=extend_size)
wanghao5b981752013-10-22 11:41:41 +0800267 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000268 resp, body = self.post(url, str(common.Document(post_body)))
wanghao5b981752013-10-22 11:41:41 +0800269 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000270 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000271 self.expected_success(202, resp.status)
wanghao5b981752013-10-22 11:41:41 +0800272 return resp, body
wanghaoaa1f2f92013-10-10 11:30:37 +0800273
274 def reset_volume_status(self, volume_id, status):
275 """Reset the Specified Volume's Status."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000276 post_body = common.Element("os-reset_status",
277 status=status
278 )
wanghaoaa1f2f92013-10-10 11:30:37 +0800279 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000280 resp, body = self.post(url, str(common.Document(post_body)))
wanghaoaa1f2f92013-10-10 11:30:37 +0800281 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000282 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000283 self.expected_success(202, resp.status)
wanghaoaa1f2f92013-10-10 11:30:37 +0800284 return resp, body
285
286 def volume_begin_detaching(self, volume_id):
287 """Volume Begin Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000288 post_body = common.Element("os-begin_detaching")
wanghaoaa1f2f92013-10-10 11:30:37 +0800289 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000290 resp, body = self.post(url, str(common.Document(post_body)))
wanghaoaa1f2f92013-10-10 11:30:37 +0800291 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000292 body = common.xml_to_json(etree.fromstring(body))
wanghaoaa1f2f92013-10-10 11:30:37 +0800293 return resp, body
294
295 def volume_roll_detaching(self, volume_id):
296 """Volume Roll Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000297 post_body = common.Element("os-roll_detaching")
wanghaoaa1f2f92013-10-10 11:30:37 +0800298 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000299 resp, body = self.post(url, str(common.Document(post_body)))
wanghaoaa1f2f92013-10-10 11:30:37 +0800300 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000301 body = common.xml_to_json(etree.fromstring(body))
wanghaoaa1f2f92013-10-10 11:30:37 +0800302 return resp, body
zhangyanzi6b632432013-10-24 19:08:50 +0800303
304 def reserve_volume(self, volume_id):
305 """Reserves a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000306 post_body = common.Element("os-reserve")
zhangyanzi6b632432013-10-24 19:08:50 +0800307 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000308 resp, body = self.post(url, str(common.Document(post_body)))
zhangyanzi6b632432013-10-24 19:08:50 +0800309 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000310 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000311 self.expected_success(202, resp.status)
zhangyanzi6b632432013-10-24 19:08:50 +0800312 return resp, body
313
314 def unreserve_volume(self, volume_id):
315 """Restore a reserved volume ."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000316 post_body = common.Element("os-unreserve")
zhangyanzi6b632432013-10-24 19:08:50 +0800317 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000318 resp, body = self.post(url, str(common.Document(post_body)))
zhangyanzi6b632432013-10-24 19:08:50 +0800319 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000320 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000321 self.expected_success(202, resp.status)
zhangyanzi6b632432013-10-24 19:08:50 +0800322 return resp, body
wingwjcbd82dc2013-10-22 16:38:39 +0800323
324 def create_volume_transfer(self, vol_id, display_name=None):
325 """Create a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000326 post_body = common.Element("transfer",
327 volume_id=vol_id)
wingwjcbd82dc2013-10-22 16:38:39 +0800328 if display_name:
329 post_body.add_attr('name', display_name)
330 resp, body = self.post('os-volume-transfer',
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000331 str(common.Document(post_body)))
332 volume = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000333 self.expected_success(202, resp.status)
wingwjcbd82dc2013-10-22 16:38:39 +0800334 return resp, volume
335
336 def get_volume_transfer(self, transfer_id):
337 """Returns the details of a volume transfer."""
338 url = "os-volume-transfer/%s" % str(transfer_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200339 resp, body = self.get(url)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000340 volume = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000341 self.expected_success(200, resp.status)
wingwjcbd82dc2013-10-22 16:38:39 +0800342 return resp, volume
343
344 def list_volume_transfers(self, params=None):
345 """List all the volume transfers created."""
346 url = 'os-volume-transfer'
347 if params:
348 url += '?%s' % urllib.urlencode(params)
349
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200350 resp, body = self.get(url)
wingwjcbd82dc2013-10-22 16:38:39 +0800351 body = etree.fromstring(body)
352 volumes = []
353 if body is not None:
354 volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000355 self.expected_success(200, resp.status)
wingwjcbd82dc2013-10-22 16:38:39 +0800356 return resp, volumes
357
358 def _parse_volume_transfer(self, body):
359 vol = dict((attr, body.get(attr)) for attr in body.keys())
360 for child in body.getchildren():
361 tag = child.tag
362 if tag.startswith("{"):
363 tag = tag.split("}", 1)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000364 vol[tag] = common.xml_to_json(child)
wingwjcbd82dc2013-10-22 16:38:39 +0800365 return vol
366
367 def delete_volume_transfer(self, transfer_id):
368 """Delete a volume transfer."""
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000369 resp, body = self.delete("os-volume-transfer/%s" % str(transfer_id))
370 self.expected_success(202, resp.status)
371 return resp, body
wingwjcbd82dc2013-10-22 16:38:39 +0800372
373 def accept_volume_transfer(self, transfer_id, transfer_auth_key):
374 """Accept a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000375 post_body = common.Element("accept", auth_key=transfer_auth_key)
wingwjcbd82dc2013-10-22 16:38:39 +0800376 url = 'os-volume-transfer/%s/accept' % transfer_id
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000377 resp, body = self.post(url, str(common.Document(post_body)))
378 volume = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000379 self.expected_success(202, resp.status)
wingwjcbd82dc2013-10-22 16:38:39 +0800380 return resp, volume
zhangyanziaa180072013-11-21 12:31:26 +0800381
382 def update_volume_readonly(self, volume_id, readonly):
383 """Update the Specified Volume readonly."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000384 post_body = common.Element("os-update_readonly_flag",
385 readonly=readonly)
zhangyanziaa180072013-11-21 12:31:26 +0800386 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000387 resp, body = self.post(url, str(common.Document(post_body)))
zhangyanziaa180072013-11-21 12:31:26 +0800388 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000389 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000390 self.expected_success(202, resp.status)
zhangyanziaa180072013-11-21 12:31:26 +0800391 return resp, body
wanghao9d3d6cb2013-11-12 15:10:10 +0800392
393 def force_delete_volume(self, volume_id):
394 """Force Delete Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000395 post_body = common.Element("os-force_delete")
wanghao9d3d6cb2013-11-12 15:10:10 +0800396 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000397 resp, body = self.post(url, str(common.Document(post_body)))
wanghao9d3d6cb2013-11-12 15:10:10 +0800398 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000399 body = common.xml_to_json(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000400 self.expected_success(202, resp.status)
wanghao9d3d6cb2013-11-12 15:10:10 +0800401 return resp, body
huangtianhua0ff41682013-12-16 14:49:31 +0800402
403 def _metadata_body(self, meta):
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000404 post_body = common.Element('metadata')
huangtianhua0ff41682013-12-16 14:49:31 +0800405 for k, v in meta.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000406 data = common.Element('meta', key=k)
Ryan McNair9acfcf72014-01-27 21:06:48 +0000407 # Escape value to allow for special XML chars
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000408 data.append(common.Text(saxutils.escape(v)))
huangtianhua0ff41682013-12-16 14:49:31 +0800409 post_body.append(data)
410 return post_body
411
412 def _parse_key_value(self, node):
413 """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
414 data = {}
415 for node in node.getchildren():
416 data[node.get('key')] = node.text
417 return data
418
419 def create_volume_metadata(self, volume_id, metadata):
420 """Create metadata for the volume."""
421 post_body = self._metadata_body(metadata)
422 resp, body = self.post('volumes/%s/metadata' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000423 str(common.Document(post_body)))
huangtianhua0ff41682013-12-16 14:49:31 +0800424 body = self._parse_key_value(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000425 self.expected_success(200, resp.status)
huangtianhua0ff41682013-12-16 14:49:31 +0800426 return resp, body
427
428 def get_volume_metadata(self, volume_id):
429 """Get metadata of the volume."""
430 url = "volumes/%s/metadata" % str(volume_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200431 resp, body = self.get(url)
huangtianhua0ff41682013-12-16 14:49:31 +0800432 body = self._parse_key_value(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000433 self.expected_success(200, resp.status)
huangtianhua0ff41682013-12-16 14:49:31 +0800434 return resp, body
435
436 def update_volume_metadata(self, volume_id, metadata):
437 """Update metadata for the volume."""
438 put_body = self._metadata_body(metadata)
439 url = "volumes/%s/metadata" % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000440 resp, body = self.put(url, str(common.Document(put_body)))
huangtianhua0ff41682013-12-16 14:49:31 +0800441 body = self._parse_key_value(etree.fromstring(body))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000442 self.expected_success(200, resp.status)
huangtianhua0ff41682013-12-16 14:49:31 +0800443 return resp, body
444
445 def update_volume_metadata_item(self, volume_id, id, meta_item):
446 """Update metadata item for the volume."""
447 for k, v in meta_item.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000448 put_body = common.Element('meta', key=k)
449 put_body.append(common.Text(v))
huangtianhua0ff41682013-12-16 14:49:31 +0800450 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000451 resp, body = self.put(url, str(common.Document(put_body)))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000452 self.expected_success(200, resp.status)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000453 body = common.xml_to_json(etree.fromstring(body))
huangtianhua0ff41682013-12-16 14:49:31 +0800454 return resp, body
455
456 def delete_volume_metadata_item(self, volume_id, id):
457 """Delete metadata item for the volume."""
458 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
Swapnil Kulkarnid9df38c2014-08-16 18:06:52 +0000459 resp, body = self.delete(url)
460 self.expected_success(200, resp.status)
461 return resp, body
Zhi Kun Liu6e6cf832014-05-08 17:25:22 +0800462
463
464class VolumesClientXML(BaseVolumesClientXML):
465 """
466 Client class to send CRUD Volume API V1 requests to a Cinder endpoint
467 """