blob: 6866dad9e8695cd1db6ab8d5288c20be7c82f52d [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 Treinish9854d5b2012-09-20 10:22:13 -040018
19from lxml import etree
Yuiko Takada4d41c2f2014-03-07 11:58:31 +000020from xml.sax import saxutils
Matthew Treinish9854d5b2012-09-20 10:22:13 -040021
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
Matthew Treinish9854d5b2012-09-20 10:22:13 -040029
vponomaryov960eeb42014-02-22 18:25:25 +020030class VolumesClientXML(rest_client.RestClient):
Matthew Treinish9854d5b2012-09-20 10:22:13 -040031 """
32 Client class to send CRUD Volume API requests to a Cinder endpoint
33 """
vponomaryov960eeb42014-02-22 18:25:25 +020034 TYPE = "xml"
Matthew Treinish9854d5b2012-09-20 10:22:13 -040035
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000036 def __init__(self, auth_provider):
37 super(VolumesClientXML, self).__init__(auth_provider)
Matthew Treinish684d8992014-01-30 16:27:40 +000038 self.service = CONF.volume.catalog_type
39 self.build_interval = CONF.compute.build_interval
40 self.build_timeout = CONF.compute.build_timeout
Matthew Treinish9854d5b2012-09-20 10:22:13 -040041
42 def _parse_volume(self, body):
43 vol = dict((attr, body.get(attr)) for attr in body.keys())
44
45 for child in body.getchildren():
46 tag = child.tag
47 if tag.startswith("{"):
48 ns, tag = tag.split("}", 1)
49 if tag == 'metadata':
50 vol['metadata'] = dict((meta.get('key'),
Attila Fazekas786236c2013-01-31 16:06:51 +010051 meta.text) for meta in
52 child.getchildren())
Matthew Treinish9854d5b2012-09-20 10:22:13 -040053 else:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +000054 vol[tag] = common.xml_to_json(child)
Attila Fazekas786236c2013-01-31 16:06:51 +010055 return vol
Matthew Treinish9854d5b2012-09-20 10:22:13 -040056
anju tiwari789449a2013-08-29 16:56:17 +053057 def get_attachment_from_volume(self, volume):
58 """Return the element 'attachment' from input volumes."""
59 return volume['attachments']['attachment']
60
Nayna Patel5e76be12013-08-19 12:10:16 +000061 def _check_if_bootable(self, volume):
62 """
63 Check if the volume is bootable, also change the value
64 of 'bootable' from string to boolean.
65 """
John Griffithf55f69e2013-09-19 14:10:57 -060066
67 # NOTE(jdg): Version 1 of Cinder API uses lc strings
68 # We should consider being explicit in this check to
69 # avoid introducing bugs like: LP #1227837
70
71 if volume['bootable'].lower() == 'true':
Nayna Patel5e76be12013-08-19 12:10:16 +000072 volume['bootable'] = True
John Griffithf55f69e2013-09-19 14:10:57 -060073 elif volume['bootable'].lower() == 'false':
Nayna Patel5e76be12013-08-19 12:10:16 +000074 volume['bootable'] = False
75 else:
76 raise ValueError(
77 'bootable flag is supposed to be either True or False,'
78 'it is %s' % volume['bootable'])
79 return volume
80
Matthew Treinish9854d5b2012-09-20 10:22:13 -040081 def list_volumes(self, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -050082 """List all the volumes created."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -040083 url = 'volumes'
84
85 if params:
86 url += '?%s' % urllib.urlencode(params)
87
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +020088 resp, body = self.get(url)
Matthew Treinish9854d5b2012-09-20 10:22:13 -040089 body = etree.fromstring(body)
90 volumes = []
91 if body is not None:
92 volumes += [self._parse_volume(vol) for vol in list(body)]
Nayna Patel5e76be12013-08-19 12:10:16 +000093 for v in volumes:
94 v = self._check_if_bootable(v)
Matthew Treinish9854d5b2012-09-20 10:22:13 -040095 return resp, volumes
96
97 def list_volumes_with_detail(self, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -050098 """List all the details of volumes."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -040099 url = 'volumes/detail'
100
101 if params:
102 url += '?%s' % urllib.urlencode(params)
103
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200104 resp, body = self.get(url)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400105 body = etree.fromstring(body)
106 volumes = []
107 if body is not None:
108 volumes += [self._parse_volume(vol) for vol in list(body)]
Nayna Patel5e76be12013-08-19 12:10:16 +0000109 for v in volumes:
110 v = self._check_if_bootable(v)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400111 return resp, volumes
112
Attila Fazekasb8aa7592013-01-26 01:25:45 +0100113 def get_volume(self, volume_id):
Sean Daguef237ccb2013-01-04 15:19:14 -0500114 """Returns the details of a single volume."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400115 url = "volumes/%s" % str(volume_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200116 resp, body = self.get(url)
Nayna Patel5e76be12013-08-19 12:10:16 +0000117 body = self._parse_volume(etree.fromstring(body))
118 body = self._check_if_bootable(body)
119 return resp, body
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400120
Attila Fazekas786236c2013-01-31 16:06:51 +0100121 def create_volume(self, size, **kwargs):
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400122 """Creates a new Volume.
123
124 :param size: Size of volume in GB. (Required)
125 :param display_name: Optional Volume Name.
126 :param metadata: An optional dictionary of values for metadata.
Attila Fazekas786236c2013-01-31 16:06:51 +0100127 :param volume_type: Optional Name of volume_type for the volume
128 :param snapshot_id: When specified the volume is created from
129 this snapshot
Giulio Fidente36836c42013-04-05 15:43:51 +0200130 :param imageRef: When specified the volume is created from this
131 image
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400132 """
Attila Fazekasa8b5fe72013-08-01 16:59:06 +0200133 # NOTE(afazekas): it should use a volume namespace
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000134 volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400135
Attila Fazekas786236c2013-01-31 16:06:51 +0100136 if 'metadata' in kwargs:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000137 _metadata = common.Element('metadata')
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400138 volume.append(_metadata)
Attila Fazekas786236c2013-01-31 16:06:51 +0100139 for key, value in kwargs['metadata'].items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000140 meta = common.Element('meta')
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400141 meta.add_attr('key', key)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000142 meta.append(common.Text(value))
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400143 _metadata.append(meta)
Attila Fazekas786236c2013-01-31 16:06:51 +0100144 attr_to_add = kwargs.copy()
145 del attr_to_add['metadata']
146 else:
147 attr_to_add = kwargs
148
149 for key, value in attr_to_add.items():
150 volume.add_attr(key, value)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400151
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000152 resp, body = self.post('volumes', str(common.Document(volume)))
153 body = common.xml_to_json(etree.fromstring(body))
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400154 return resp, body
155
QingXin Meng611768a2013-09-18 00:51:33 -0700156 def update_volume(self, volume_id, **kwargs):
157 """Updates the Specified Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000158 put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
QingXin Meng611768a2013-09-18 00:51:33 -0700159
160 resp, body = self.put('volumes/%s' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000161 str(common.Document(put_body)))
162 body = common.xml_to_json(etree.fromstring(body))
QingXin Meng611768a2013-09-18 00:51:33 -0700163 return resp, body
164
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400165 def delete_volume(self, volume_id):
Sean Daguef237ccb2013-01-04 15:19:14 -0500166 """Deletes the Specified Volume."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400167 return self.delete("volumes/%s" % str(volume_id))
168
169 def wait_for_volume_status(self, volume_id, status):
Sean Daguef237ccb2013-01-04 15:19:14 -0500170 """Waits for a Volume to reach a given status."""
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400171 resp, body = self.get_volume(volume_id)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400172 volume_status = body['status']
173 start = int(time.time())
174
175 while volume_status != status:
176 time.sleep(self.build_interval)
177 resp, body = self.get_volume(volume_id)
178 volume_status = body['status']
179 if volume_status == 'error':
180 raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
181
182 if int(time.time()) - start >= self.build_timeout:
183 message = 'Volume %s failed to reach %s status within '\
Attila Fazekas786236c2013-01-31 16:06:51 +0100184 'the required time (%s s).' % (volume_id,
185 status,
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400186 self.build_timeout)
187 raise exceptions.TimeoutException(message)
188
189 def is_resource_deleted(self, id):
190 try:
Attila Fazekasf53172c2013-01-26 01:04:42 +0100191 self.get_volume(id)
Matthew Treinish9854d5b2012-09-20 10:22:13 -0400192 except exceptions.NotFound:
193 return True
194 return False
anju tiwari789449a2013-08-29 16:56:17 +0530195
196 def attach_volume(self, volume_id, instance_uuid, mountpoint):
197 """Attaches a volume to a given instance on a given mountpoint."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000198 post_body = common.Element("os-attach",
199 instance_uuid=instance_uuid,
200 mountpoint=mountpoint
201 )
anju tiwari789449a2013-08-29 16:56:17 +0530202 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000203 resp, body = self.post(url, str(common.Document(post_body)))
anju tiwari789449a2013-08-29 16:56:17 +0530204 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000205 body = common.xml_to_json(etree.fromstring(body))
anju tiwari789449a2013-08-29 16:56:17 +0530206 return resp, body
207
208 def detach_volume(self, volume_id):
209 """Detaches a volume from an instance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000210 post_body = common.Element("os-detach")
anju tiwari789449a2013-08-29 16:56:17 +0530211 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000212 resp, body = self.post(url, str(common.Document(post_body)))
anju tiwari789449a2013-08-29 16:56:17 +0530213 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000214 body = common.xml_to_json(etree.fromstring(body))
anju tiwari789449a2013-08-29 16:56:17 +0530215 return resp, body
216
Ryan Hsua67f4632013-08-29 16:03:06 -0700217 def upload_volume(self, volume_id, image_name, disk_format):
anju tiwari789449a2013-08-29 16:56:17 +0530218 """Uploads a volume in Glance."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000219 post_body = common.Element("os-volume_upload_image",
220 image_name=image_name,
221 disk_format=disk_format)
anju tiwari789449a2013-08-29 16:56:17 +0530222 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000223 resp, body = self.post(url, str(common.Document(post_body)))
224 volume = common.xml_to_json(etree.fromstring(body))
anju tiwari789449a2013-08-29 16:56:17 +0530225 return resp, volume
wanghao5b981752013-10-22 11:41:41 +0800226
227 def extend_volume(self, volume_id, extend_size):
228 """Extend a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000229 post_body = common.Element("os-extend",
230 new_size=extend_size)
wanghao5b981752013-10-22 11:41:41 +0800231 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000232 resp, body = self.post(url, str(common.Document(post_body)))
wanghao5b981752013-10-22 11:41:41 +0800233 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000234 body = common.xml_to_json(etree.fromstring(body))
wanghao5b981752013-10-22 11:41:41 +0800235 return resp, body
wanghaoaa1f2f92013-10-10 11:30:37 +0800236
237 def reset_volume_status(self, volume_id, status):
238 """Reset the Specified Volume's Status."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000239 post_body = common.Element("os-reset_status",
240 status=status
241 )
wanghaoaa1f2f92013-10-10 11:30:37 +0800242 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000243 resp, body = self.post(url, str(common.Document(post_body)))
wanghaoaa1f2f92013-10-10 11:30:37 +0800244 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000245 body = common.xml_to_json(etree.fromstring(body))
wanghaoaa1f2f92013-10-10 11:30:37 +0800246 return resp, body
247
248 def volume_begin_detaching(self, volume_id):
249 """Volume Begin Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000250 post_body = common.Element("os-begin_detaching")
wanghaoaa1f2f92013-10-10 11:30:37 +0800251 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000252 resp, body = self.post(url, str(common.Document(post_body)))
wanghaoaa1f2f92013-10-10 11:30:37 +0800253 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000254 body = common.xml_to_json(etree.fromstring(body))
wanghaoaa1f2f92013-10-10 11:30:37 +0800255 return resp, body
256
257 def volume_roll_detaching(self, volume_id):
258 """Volume Roll Detaching."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000259 post_body = common.Element("os-roll_detaching")
wanghaoaa1f2f92013-10-10 11:30:37 +0800260 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000261 resp, body = self.post(url, str(common.Document(post_body)))
wanghaoaa1f2f92013-10-10 11:30:37 +0800262 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000263 body = common.xml_to_json(etree.fromstring(body))
wanghaoaa1f2f92013-10-10 11:30:37 +0800264 return resp, body
zhangyanzi6b632432013-10-24 19:08:50 +0800265
266 def reserve_volume(self, volume_id):
267 """Reserves a volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000268 post_body = common.Element("os-reserve")
zhangyanzi6b632432013-10-24 19:08:50 +0800269 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000270 resp, body = self.post(url, str(common.Document(post_body)))
zhangyanzi6b632432013-10-24 19:08:50 +0800271 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000272 body = common.xml_to_json(etree.fromstring(body))
zhangyanzi6b632432013-10-24 19:08:50 +0800273 return resp, body
274
275 def unreserve_volume(self, volume_id):
276 """Restore a reserved volume ."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000277 post_body = common.Element("os-unreserve")
zhangyanzi6b632432013-10-24 19:08:50 +0800278 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000279 resp, body = self.post(url, str(common.Document(post_body)))
zhangyanzi6b632432013-10-24 19:08:50 +0800280 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000281 body = common.xml_to_json(etree.fromstring(body))
zhangyanzi6b632432013-10-24 19:08:50 +0800282 return resp, body
wingwjcbd82dc2013-10-22 16:38:39 +0800283
284 def create_volume_transfer(self, vol_id, display_name=None):
285 """Create a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000286 post_body = common.Element("transfer",
287 volume_id=vol_id)
wingwjcbd82dc2013-10-22 16:38:39 +0800288 if display_name:
289 post_body.add_attr('name', display_name)
290 resp, body = self.post('os-volume-transfer',
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000291 str(common.Document(post_body)))
292 volume = common.xml_to_json(etree.fromstring(body))
wingwjcbd82dc2013-10-22 16:38:39 +0800293 return resp, volume
294
295 def get_volume_transfer(self, transfer_id):
296 """Returns the details of a volume transfer."""
297 url = "os-volume-transfer/%s" % str(transfer_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200298 resp, body = self.get(url)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000299 volume = common.xml_to_json(etree.fromstring(body))
wingwjcbd82dc2013-10-22 16:38:39 +0800300 return resp, volume
301
302 def list_volume_transfers(self, params=None):
303 """List all the volume transfers created."""
304 url = 'os-volume-transfer'
305 if params:
306 url += '?%s' % urllib.urlencode(params)
307
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200308 resp, body = self.get(url)
wingwjcbd82dc2013-10-22 16:38:39 +0800309 body = etree.fromstring(body)
310 volumes = []
311 if body is not None:
312 volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
313 return resp, volumes
314
315 def _parse_volume_transfer(self, body):
316 vol = dict((attr, body.get(attr)) for attr in body.keys())
317 for child in body.getchildren():
318 tag = child.tag
319 if tag.startswith("{"):
320 tag = tag.split("}", 1)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000321 vol[tag] = common.xml_to_json(child)
wingwjcbd82dc2013-10-22 16:38:39 +0800322 return vol
323
324 def delete_volume_transfer(self, transfer_id):
325 """Delete a volume transfer."""
326 return self.delete("os-volume-transfer/%s" % str(transfer_id))
327
328 def accept_volume_transfer(self, transfer_id, transfer_auth_key):
329 """Accept a volume transfer."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000330 post_body = common.Element("accept", auth_key=transfer_auth_key)
wingwjcbd82dc2013-10-22 16:38:39 +0800331 url = 'os-volume-transfer/%s/accept' % transfer_id
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000332 resp, body = self.post(url, str(common.Document(post_body)))
333 volume = common.xml_to_json(etree.fromstring(body))
wingwjcbd82dc2013-10-22 16:38:39 +0800334 return resp, volume
zhangyanziaa180072013-11-21 12:31:26 +0800335
336 def update_volume_readonly(self, volume_id, readonly):
337 """Update the Specified Volume readonly."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000338 post_body = common.Element("os-update_readonly_flag",
339 readonly=readonly)
zhangyanziaa180072013-11-21 12:31:26 +0800340 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000341 resp, body = self.post(url, str(common.Document(post_body)))
zhangyanziaa180072013-11-21 12:31:26 +0800342 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000343 body = common.xml_to_json(etree.fromstring(body))
zhangyanziaa180072013-11-21 12:31:26 +0800344 return resp, body
wanghao9d3d6cb2013-11-12 15:10:10 +0800345
346 def force_delete_volume(self, volume_id):
347 """Force Delete Volume."""
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000348 post_body = common.Element("os-force_delete")
wanghao9d3d6cb2013-11-12 15:10:10 +0800349 url = 'volumes/%s/action' % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000350 resp, body = self.post(url, str(common.Document(post_body)))
wanghao9d3d6cb2013-11-12 15:10:10 +0800351 if body:
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000352 body = common.xml_to_json(etree.fromstring(body))
wanghao9d3d6cb2013-11-12 15:10:10 +0800353 return resp, body
huangtianhua0ff41682013-12-16 14:49:31 +0800354
355 def _metadata_body(self, meta):
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000356 post_body = common.Element('metadata')
huangtianhua0ff41682013-12-16 14:49:31 +0800357 for k, v in meta.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000358 data = common.Element('meta', key=k)
Ryan McNair9acfcf72014-01-27 21:06:48 +0000359 # Escape value to allow for special XML chars
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000360 data.append(common.Text(saxutils.escape(v)))
huangtianhua0ff41682013-12-16 14:49:31 +0800361 post_body.append(data)
362 return post_body
363
364 def _parse_key_value(self, node):
365 """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
366 data = {}
367 for node in node.getchildren():
368 data[node.get('key')] = node.text
369 return data
370
371 def create_volume_metadata(self, volume_id, metadata):
372 """Create metadata for the volume."""
373 post_body = self._metadata_body(metadata)
374 resp, body = self.post('volumes/%s/metadata' % volume_id,
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000375 str(common.Document(post_body)))
huangtianhua0ff41682013-12-16 14:49:31 +0800376 body = self._parse_key_value(etree.fromstring(body))
377 return resp, body
378
379 def get_volume_metadata(self, volume_id):
380 """Get metadata of the volume."""
381 url = "volumes/%s/metadata" % str(volume_id)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200382 resp, body = self.get(url)
huangtianhua0ff41682013-12-16 14:49:31 +0800383 body = self._parse_key_value(etree.fromstring(body))
384 return resp, body
385
386 def update_volume_metadata(self, volume_id, metadata):
387 """Update metadata for the volume."""
388 put_body = self._metadata_body(metadata)
389 url = "volumes/%s/metadata" % str(volume_id)
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000390 resp, body = self.put(url, str(common.Document(put_body)))
huangtianhua0ff41682013-12-16 14:49:31 +0800391 body = self._parse_key_value(etree.fromstring(body))
392 return resp, body
393
394 def update_volume_metadata_item(self, volume_id, id, meta_item):
395 """Update metadata item for the volume."""
396 for k, v in meta_item.items():
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000397 put_body = common.Element('meta', key=k)
398 put_body.append(common.Text(v))
huangtianhua0ff41682013-12-16 14:49:31 +0800399 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
Yuiko Takada4d41c2f2014-03-07 11:58:31 +0000400 resp, body = self.put(url, str(common.Document(put_body)))
401 body = common.xml_to_json(etree.fromstring(body))
huangtianhua0ff41682013-12-16 14:49:31 +0800402 return resp, body
403
404 def delete_volume_metadata_item(self, volume_id, id):
405 """Delete metadata item for the volume."""
406 url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
407 return self.delete(url)