blob: 4ef813c2d2ef9cc709f8e59572286456f6d374b4 [file] [log] [blame]
Dan Smithcf8fab62012-08-14 08:03:48 -07001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2#
3# Copyright 2012 IBM
4# 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 logging
19from lxml import etree
20from tempest import exceptions
21from tempest.common.rest_client import RestClientXML
22from tempest.services.nova.xml.common import Document
23from tempest.services.nova.xml.common import Element
24from tempest.services.nova.xml.common import Text
25from tempest.services.nova.xml.common import xml_to_json
26from tempest.services.nova.xml.common import XMLNS_11
27import time
28
29LOG = logging.getLogger(__name__)
30
31
32class ServersClientXML(RestClientXML):
33
34 def __init__(self, config, username, password, auth_url, tenant_name=None):
35 super(ServersClientXML, self).__init__(config, username, password,
36 auth_url, tenant_name)
37 self.service = self.config.compute.catalog_type
38
39 def _parse_key_value(self, node):
40 """Parse <foo key='key'>value</foo> data into {'key': 'value'}"""
41 data = {}
42 for node in node.getchildren():
43 data[node.get('key')] = node.text
44 return data
45
46 def _parse_links(self, node, json):
47 del json['link']
48 json['links'] = []
49 for linknode in node.findall('{http://www.w3.org/2005/Atom}link'):
50 json['links'].append(xml_to_json(linknode))
51
52 def _parse_server(self, body):
53 json = xml_to_json(body)
54 if 'metadata' in json and json['metadata']:
55 # NOTE(danms): if there was metadata, we need to re-parse
56 # that as a special type
57 metadata_tag = body.find('{%s}metadata' % XMLNS_11)
58 json["metadata"] = self._parse_key_value(metadata_tag)
59 if 'link' in json:
60 self._parse_links(body, json)
61 for sub in ['image', 'flavor']:
62 if sub in json and 'link' in json[sub]:
63 self._parse_links(body, json[sub])
64
65 return json
66
67 def get_server(self, server_id):
68 """Returns the details of an existing server"""
69 resp, body = self.get("servers/%s" % str(server_id), self.headers)
70 server = self._parse_server(etree.fromstring(body))
71 return resp, server
72
73 def delete_server(self, server_id):
74 """Deletes the given server"""
75 return self.delete("servers/%s" % str(server_id))
76
77 def _parse_array(self, node):
78 array = []
79 for child in node.getchildren():
80 array.append(xml_to_json(child))
81 return array
82
83 def list_servers(self, params=None):
84 url = 'servers/detail'
85 if params != None:
86 param_list = []
87 for param, value in params.iteritems():
88 param_list.append("%s=%s" % (param, value))
89
90 url += "?" + "&".join(param_list)
91 resp, body = self.get(url, self.headers)
92 servers = self._parse_array(etree.fromstring(body))
93 return resp, {"servers": servers}
94
95 def list_servers_with_detail(self, params=None):
96 url = 'servers/detail'
97 if params != None:
98 param_list = []
99 for param, value in params.iteritems():
100 param_list.append("%s=%s" % (param, value))
101
102 url += "?" + "&".join(param_list)
103 resp, body = self.get(url, self.headers)
104 servers = self._parse_array(etree.fromstring(body))
105 return resp, {"servers": servers}
106
107 def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
108 accessIPv6=None):
109 doc = Document()
110 server = Element("server")
111 doc.append(server)
112
113 if name:
114 server.add_attr("name", name)
115 if accessIPv4:
116 server.add_attr("accessIPv4", accessIPv4)
117 if accessIPv6:
118 server.add_attr("accessIPv6", accessIPv6)
119 if meta:
120 metadata = Element("metadata")
121 server.append(metadata)
122 for k, v in meta:
123 meta = Element("meta", key=k)
124 meta.append(Text(v))
125 metadata.append(meta)
126
127 resp, body = self.put('servers/%s' % str(server_id),
128 str(doc), self.headers)
129 return resp, xml_to_json(etree.fromstring(body))
130
131 def create_server(self, name, image_ref, flavor_ref, **kwargs):
132 """
133 Creates an instance of a server.
134 name (Required): The name of the server.
135 image_ref (Required): Reference to the image used to build the server.
136 flavor_ref (Required): The flavor used to build the server.
137 Following optional keyword arguments are accepted:
138 adminPass: Sets the initial root password.
139 key_name: Key name of keypair that was created earlier.
140 meta: A dictionary of values to be used as metadata.
141 personality: A list of dictionaries for files to be injected into
142 the server.
143 security_groups: A list of security group dicts.
144 networks: A list of network dicts with UUID and fixed_ip.
145 user_data: User data for instance.
146 availability_zone: Availability zone in which to launch instance.
147 accessIPv4: The IPv4 access address for the server.
148 accessIPv6: The IPv6 access address for the server.
149 min_count: Count of minimum number of instances to launch.
150 max_count: Count of maximum number of instances to launch.
151 disk_config: Determines if user or admin controls disk configuration.
152 """
153 server = Element("server",
154 xmlns=XMLNS_11,
155 imageRef=image_ref,
156 flavorRef=flavor_ref,
157 name=name)
158
159 for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name"]:
160 if attr in kwargs:
161 server.add_attr(attr, kwargs[attr])
162
163 if 'meta' in kwargs:
164 metadata = Element("metadata")
165 server.append(metadata)
166 for k, v in kwargs['meta'].items():
167 meta = Element("meta", key=k)
168 meta.append(Text(v))
169 metadata.append(meta)
170
Matthew Treinish2dfc2822012-08-16 16:57:08 -0400171 if 'personality' in kwargs:
172 personality = Element('personality')
173 server.append(personality)
174 for k in kwargs['personality']:
175 temp = Element('file', path=k['path'])
176 temp.append(Text(k['contents']))
177 personality.append(temp)
Dan Smithcf8fab62012-08-14 08:03:48 -0700178
179 resp, body = self.post('servers', str(Document(server)), self.headers)
180 server = self._parse_server(etree.fromstring(body))
181 return resp, server
182
183 def wait_for_server_status(self, server_id, status):
184 """Waits for a server to reach a given status"""
185 resp, body = self.get_server(server_id)
186 server_status = body['status']
187 start = int(time.time())
188
189 while(server_status != status):
190 time.sleep(self.build_interval)
191 resp, body = self.get_server(server_id)
192 server_status = body['status']
193
194 if server_status == 'ERROR':
195 raise exceptions.BuildErrorException(server_id=server_id)
196
197 timed_out = int(time.time()) - start >= self.build_timeout
198
199 if server_status != status and timed_out:
200 message = 'Server %s failed to reach %s status within the '\
201 'required time (%s s).' % (server_id, status,
202 self.build_timeout)
203 message += ' Current status: %s.' % server_status
204 raise exceptions.TimeoutException(message)
205
206 def wait_for_server_termination(self, server_id, ignore_error=False):
207 """Waits for server to reach termination"""
208 start_time = int(time.time())
209 while True:
210 try:
211 resp, body = self.get_server(server_id)
212 except exceptions.NotFound:
213 return
214
215 server_status = body['status']
216 if server_status == 'ERROR' and not ignore_error:
217 raise exceptions.BuildErrorException
218
219 if int(time.time()) - start_time >= self.build_timeout:
220 raise exceptions.TimeoutException
221
222 time.sleep(self.build_interval)
223
224 def _parse_network(self, node):
225 addrs = []
226 for child in node.getchildren():
227 addrs.append({'version': int(child.get('version')),
228 'addr': child.get('version')})
229 return {node.get('id'): addrs}
230
231 def list_addresses(self, server_id):
232 """Lists all addresses for a server"""
233 resp, body = self.get("servers/%s/ips" % str(server_id), self.headers)
234
235 networks = {}
236 for child in etree.fromstring(body.getchildren()):
237 network = self._parse_network(child)
238 networks.update(**network)
239
240 return resp, networks
241
242 def list_addresses_by_network(self, server_id, network_id):
243 """Lists all addresses of a specific network type for a server"""
244 resp, body = self.get("servers/%s/ips/%s" %
245 (str(server_id), network_id), self.headers)
246 network = self._parse_network(etree.fromstring(body))
247
248 return resp, network
249
250 def change_password(self, server_id, password):
251 cpw = Element("changePassword",
252 xmlns=XMLNS_11,
253 adminPass=password)
254 return self.post("servers/%s/action" % server_id,
255 str(Document(cpw)), self.headers)
256
257 def reboot(self, server_id, reboot_type):
258 reboot = Element("reboot",
259 xmlns=XMLNS_11,
260 type=reboot_type)
261 return self.post("servers/%s/action" % server_id,
262 str(Document(reboot)), self.headers)
263
264 def rebuild(self, server_id, image_ref, name=None, meta=None,
265 personality=None, adminPass=None, disk_config=None):
266 rebuild = Element("rebuild",
267 xmlns=XMLNS_11,
268 imageRef=image_ref)
269
270 if name:
271 rebuild.add_attr("name", name)
272 if adminPass:
273 rebuild.add_attr("adminPass", adminPass)
274 if meta:
275 metadata = Element("metadata")
276 rebuild.append(metadata)
277 for k, v in meta.items():
278 meta = Element("meta", key=k)
279 meta.append(Text(v))
280 metadata.append(meta)
281
282 resp, body = self.post('servers/%s/action' % server_id,
283 str(Document(rebuild)), self.headers)
284 server = self._parse_server(etree.fromstring(body))
285 return resp, server
286
287 def resize(self, server_id, flavor_ref, disk_config=None):
288 resize = Element("resize",
289 xmlns=XMLNS_11,
290 flavorRef=flavor_ref)
291
292 if disk_config is not None:
293 raise Exception("Sorry, disk_config not supported via XML yet")
294
295 return self.post('servers/%s/action' % server_id,
296 str(Document(resize)), self.headers)
297
298 def confirm_resize(self, server_id):
299 conf = Element('confirmResize')
300 return self.post('servers/%s/action' % server_id,
301 str(Document(conf)), self.headers)
302
303 def revert_resize(self, server_id):
304 revert = Element('revertResize')
305 return self.post('servers/%s/action' % server_id,
306 str(Document(revert)), self.headers)
307
308 def create_image(self, server_id, image_name):
309 metadata = element('metadata')
310 image = element('createImage',
311 metadata,
312 xmlns=XMLNS_11,
313 name=image_name)
314 return self.post('servers/%s/action' % server_id,
315 str(Document(image)), self.headers)
316
317 def add_security_group(self, server_id, security_group_name):
318 secgrp = Element('addSecurityGroup', name=security_group_name)
319 return self.post('servers/%s/action' % server_id,
320 str(Document(secgrp)), self.headers)
321
322 def remove_security_group(self, server_id, security_group_name):
323 secgrp = Element('removeSecurityGroup', name=security_group_name)
324 return self.post('servers/%s/action' % server_id,
325 str(Document(secgrp)), self.headers)