blob: b17ae782e5bdfe1d52987cef01bc844bb4a7f728 [file] [log] [blame]
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -04001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2#
Kurt Taylor6a6f5be2013-04-02 18:53:47 -04003# Copyright 2012 IBM Corp.
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -04004# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import time
19import urllib
20
21from lxml import etree
22
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040023from tempest.common.rest_client import RestClientXML
Matthew Treinisha83a16e2012-12-07 13:44:02 -050024from tempest import exceptions
dwallecke62b9f02012-10-10 23:34:42 -050025from tempest.services.compute.xml.common import Document
26from tempest.services.compute.xml.common import Element
27from tempest.services.compute.xml.common import Text
28from tempest.services.compute.xml.common import xml_to_json
29from tempest.services.compute.xml.common import XMLNS_11
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040030
31
32class ImagesClientXML(RestClientXML):
33
34 def __init__(self, config, username, password, auth_url, tenant_name=None):
35 super(ImagesClientXML, self).__init__(config, username, password,
36 auth_url, tenant_name)
37 self.service = self.config.compute.catalog_type
38 self.build_interval = self.config.compute.build_interval
39 self.build_timeout = self.config.compute.build_timeout
40
41 def _parse_server(self, node):
Attila Fazekas7b487be2013-02-12 11:14:41 +010042 data = xml_to_json(node)
43 return self._parse_links(node, data)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040044
45 def _parse_image(self, node):
Sean Daguef237ccb2013-01-04 15:19:14 -050046 """Parses detailed XML image information into dictionary."""
Attila Fazekas7b487be2013-02-12 11:14:41 +010047 data = xml_to_json(node)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040048
Attila Fazekas7b487be2013-02-12 11:14:41 +010049 self._parse_links(node, data)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040050
51 # parse all metadata
Attila Fazekas7b487be2013-02-12 11:14:41 +010052 if 'metadata' in data:
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040053 tag = node.find('{%s}metadata' % XMLNS_11)
Attila Fazekas7b487be2013-02-12 11:14:41 +010054 data['metadata'] = dict((x.get('key'), x.text)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040055 for x in tag.getchildren())
56
57 # parse server information
Attila Fazekas7b487be2013-02-12 11:14:41 +010058 if 'server' in data:
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040059 tag = node.find('{%s}server' % XMLNS_11)
Attila Fazekas7b487be2013-02-12 11:14:41 +010060 data['server'] = self._parse_server(tag)
61 return data
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040062
Attila Fazekas7b487be2013-02-12 11:14:41 +010063 def _parse_links(self, node, data):
Sean Daguef237ccb2013-01-04 15:19:14 -050064 """Append multiple links under a list."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040065 # look for links
Attila Fazekas7b487be2013-02-12 11:14:41 +010066 if 'link' in data:
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040067 # remove single link element
Attila Fazekas7b487be2013-02-12 11:14:41 +010068 del data['link']
69 data['links'] = [xml_to_json(x) for x in
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040070 node.findall('{http://www.w3.org/2005/Atom}link')]
Attila Fazekas7b487be2013-02-12 11:14:41 +010071 return data
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040072
Matthew Treinish4e1b3e62013-01-10 11:30:49 -050073 def _parse_images(self, xml):
Attila Fazekas7b487be2013-02-12 11:14:41 +010074 data = {'images': []}
Matthew Treinish4e1b3e62013-01-10 11:30:49 -050075 images = xml.getchildren()
76 for image in images:
Attila Fazekas7b487be2013-02-12 11:14:41 +010077 data['images'].append(self._parse_image(image))
78 return data
Matthew Treinish4e1b3e62013-01-10 11:30:49 -050079
nayna-pateleda1d122013-03-20 14:44:31 +000080 def _parse_key_value(self, node):
81 """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
82 data = {}
83 for node in node.getchildren():
84 data[node.get('key')] = node.text
85 return data
86
87 def _parse_metadata(self, node):
88 """Parse the response body without children."""
89 data = {}
90 data[node.get('key')] = node.text
91 return data
92
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040093 def create_image(self, server_id, name, meta=None):
Sean Daguef237ccb2013-01-04 15:19:14 -050094 """Creates an image of the original server."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -040095 post_body = Element('createImage', name=name)
96
97 if meta:
98 metadata = Element('metadata')
99 post_body.append(metadata)
100 for k, v in meta.items():
101 data = Element('meta', key=k)
102 data.append(Text(v))
103 metadata.append(data)
104 resp, body = self.post('servers/%s/action' % str(server_id),
105 str(Document(post_body)), self.headers)
106 return resp, body
107
108 def list_images(self, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500109 """Returns a list of all images filtered by any parameters."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400110 url = 'images'
111 if params:
Matthew Treinish26dd0fa2012-12-04 17:14:37 -0500112 url += '?%s' % urllib.urlencode(params)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400113
114 resp, body = self.get(url, self.headers)
Matthew Treinish4e1b3e62013-01-10 11:30:49 -0500115 body = self._parse_images(etree.fromstring(body))
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400116 return resp, body['images']
117
118 def list_images_with_detail(self, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500119 """Returns a detailed list of images filtered by any parameters."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400120 url = 'images/detail'
121 if params:
122 param_list = urllib.urlencode(params)
123
124 url = "images/detail?" + param_list
125
126 resp, body = self.get(url, self.headers)
Matthew Treinish4e1b3e62013-01-10 11:30:49 -0500127 body = self._parse_images(etree.fromstring(body))
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400128 return resp, body['images']
129
130 def get_image(self, image_id):
Sean Daguef237ccb2013-01-04 15:19:14 -0500131 """Returns the details of a single image."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400132 resp, body = self.get("images/%s" % str(image_id), self.headers)
Attila Fazekas54a42862013-07-28 22:31:06 +0200133 self.expected_success(200, resp)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400134 body = self._parse_image(etree.fromstring(body))
135 return resp, body
136
137 def delete_image(self, image_id):
Sean Daguef237ccb2013-01-04 15:19:14 -0500138 """Deletes the provided image."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400139 return self.delete("images/%s" % str(image_id), self.headers)
140
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400141 def wait_for_image_status(self, image_id, status):
142 """Waits for an image to reach a given status."""
143 resp, image = self.get_image(image_id)
144 start = int(time.time())
145
146 while image['status'] != status:
147 time.sleep(self.build_interval)
148 resp, image = self.get_image(image_id)
149 if image['status'] == 'ERROR':
150 raise exceptions.AddImageException(image_id=image_id)
151
152 if int(time.time()) - start >= self.build_timeout:
153 raise exceptions.TimeoutException
154
nayna-pateleda1d122013-03-20 14:44:31 +0000155 def _metadata_body(self, meta):
156 post_body = Element('metadata')
157 for k, v in meta.items():
158 data = Element('meta', key=k)
159 data.append(Text(v))
160 post_body.append(data)
161 return post_body
162
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400163 def list_image_metadata(self, image_id):
Sean Daguef237ccb2013-01-04 15:19:14 -0500164 """Lists all metadata items for an image."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400165 resp, body = self.get("images/%s/metadata" % str(image_id),
166 self.headers)
nayna-pateleda1d122013-03-20 14:44:31 +0000167 body = self._parse_key_value(etree.fromstring(body))
168 return resp, body
Attila Fazekas7b487be2013-02-12 11:14:41 +0100169
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400170 def set_image_metadata(self, image_id, meta):
Sean Daguef237ccb2013-01-04 15:19:14 -0500171 """Sets the metadata for an image."""
nayna-pateleda1d122013-03-20 14:44:31 +0000172 post_body = self._metadata_body(meta)
173 resp, body = self.put('images/%s/metadata' % image_id,
174 str(Document(post_body)), self.headers)
175 body = self._parse_key_value(etree.fromstring(body))
176 return resp, body
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400177
178 def update_image_metadata(self, image_id, meta):
Sean Daguef237ccb2013-01-04 15:19:14 -0500179 """Updates the metadata for an image."""
nayna-pateleda1d122013-03-20 14:44:31 +0000180 post_body = self._metadata_body(meta)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400181 resp, body = self.post('images/%s/metadata' % str(image_id),
nayna-pateleda1d122013-03-20 14:44:31 +0000182 str(Document(post_body)), self.headers)
183 body = self._parse_key_value(etree.fromstring(body))
184 return resp, body
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400185
186 def get_image_metadata_item(self, image_id, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500187 """Returns the value for a specific image metadata key."""
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400188 resp, body = self.get("images/%s/metadata/%s.xml" %
189 (str(image_id), key), self.headers)
nayna-pateleda1d122013-03-20 14:44:31 +0000190 body = self._parse_metadata(etree.fromstring(body))
191 return resp, body
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400192
193 def set_image_metadata_item(self, image_id, key, meta):
Sean Daguef237ccb2013-01-04 15:19:14 -0500194 """Sets the value for a specific image metadata key."""
nayna-pateleda1d122013-03-20 14:44:31 +0000195 for k, v in meta.items():
196 post_body = Element('meta', key=key)
197 post_body.append(Text(v))
198 resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
199 str(Document(post_body)), self.headers)
Attila Fazekas7b487be2013-02-12 11:14:41 +0100200 body = xml_to_json(etree.fromstring(body))
nayna-pateleda1d122013-03-20 14:44:31 +0000201 return resp, body
Attila Fazekas7b487be2013-02-12 11:14:41 +0100202
203 def update_image_metadata_item(self, image_id, key, meta):
204 """Sets the value for a specific image metadata key."""
205 post_body = Document('meta', Text(meta), key=key)
Mauro S. M. Rodrigues6e373242012-08-27 18:59:19 -0400206 resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
207 post_body, self.headers)
208 body = xml_to_json(etree.fromstring(body))
209 return resp, body['meta']
210
211 def delete_image_metadata_item(self, image_id, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500212 """Deletes a single image metadata key/value pair."""
nayna-pateleda1d122013-03-20 14:44:31 +0000213 return self.delete("images/%s/metadata/%s" % (str(image_id), key),
214 self.headers)
Matthew Treinish0d660492013-06-04 17:26:09 -0400215
216 def is_resource_deleted(self, id):
217 try:
218 self.get_image(id)
219 except exceptions.NotFound:
220 return True
221 return False