blob: 6f38b6abc44155808f48a3c9f38ce2d725afe6a9 [file] [log] [blame]
ivan-zhu09111942013-08-01 08:09:16 +08001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2#
3# Copyright 2012 IBM Corp.
4# Copyright 2013 Hewlett-Packard Development Company, L.P.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19import time
20import urllib
21
22from lxml import etree
23
24from tempest.common.rest_client import RestClientXML
25from tempest.common import waiters
26from tempest import exceptions
27from tempest.openstack.common import log as logging
28from tempest.services.compute.xml.common import Document
29from tempest.services.compute.xml.common import Element
30from tempest.services.compute.xml.common import Text
31from tempest.services.compute.xml.common import xml_to_json
ivan-zhu8f992be2013-07-31 14:56:58 +080032from tempest.services.compute.xml.common import XMLNS_V3
ivan-zhu09111942013-08-01 08:09:16 +080033
34
35LOG = logging.getLogger(__name__)
36
37
38def _translate_ip_xml_json(ip):
39 """
40 Convert the address version to int.
41 """
42 ip = dict(ip)
43 version = ip.get('version')
44 if version:
45 ip['version'] = int(version)
ivan-zhu8f992be2013-07-31 14:56:58 +080046 if ip.get('type'):
47 ip['type'] = ip.get('type')
48 if ip.get('mac_addr'):
49 ip['mac_addr'] = ip.get('mac_addr')
ivan-zhu09111942013-08-01 08:09:16 +080050 return ip
51
52
53def _translate_network_xml_to_json(network):
54 return [_translate_ip_xml_json(ip.attrib)
ivan-zhu8f992be2013-07-31 14:56:58 +080055 for ip in network.findall('{%s}ip' % XMLNS_V3)]
ivan-zhu09111942013-08-01 08:09:16 +080056
57
58def _translate_addresses_xml_to_json(xml_addresses):
59 return dict((network.attrib['id'], _translate_network_xml_to_json(network))
ivan-zhu8f992be2013-07-31 14:56:58 +080060 for network in xml_addresses.findall('{%s}network' % XMLNS_V3))
ivan-zhu09111942013-08-01 08:09:16 +080061
62
63def _translate_server_xml_to_json(xml_dom):
64 """Convert server XML to server JSON.
65
66 The addresses collection does not convert well by the dumb xml_to_json.
67 This method does some pre and post-processing to deal with that.
68
69 Translate XML addresses subtree to JSON.
70
71 Having xml_doc similar to
ivan-zhu8f992be2013-07-31 14:56:58 +080072 <api:server xmlns:api="http://docs.openstack.org/compute/api/v3">
ivan-zhu09111942013-08-01 08:09:16 +080073 <api:addresses>
74 <api:network id="foo_novanetwork">
75 <api:ip version="4" addr="192.168.0.4"/>
76 </api:network>
77 <api:network id="bar_novanetwork">
78 <api:ip version="4" addr="10.1.0.4"/>
79 <api:ip version="6" addr="2001:0:0:1:2:3:4:5"/>
80 </api:network>
81 </api:addresses>
82 </api:server>
83
84 the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce
85 something like
86
87 {'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4},
88 {'addr': '2001:0:0:1:2:3:4:5',
89 'version': 6}],
90 'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}}
91 """
ivan-zhu8f992be2013-07-31 14:56:58 +080092 nsmap = {'api': XMLNS_V3}
ivan-zhu09111942013-08-01 08:09:16 +080093 addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap)
94 if addresses:
95 if len(addresses) > 1:
96 raise ValueError('Expected only single `addresses` element.')
97 json_addresses = _translate_addresses_xml_to_json(addresses[0])
98 json = xml_to_json(xml_dom)
99 json['addresses'] = json_addresses
100 else:
101 json = xml_to_json(xml_dom)
ivan-zhu8f992be2013-07-31 14:56:58 +0800102 disk_config = ('{http://docs.openstack.org'
103 '/compute/ext/disk_config/api/v3}disk_config')
ivan-zhu09111942013-08-01 08:09:16 +0800104 terminated_at = ('{http://docs.openstack.org/'
ivan-zhu8f992be2013-07-31 14:56:58 +0800105 'compute/ext/os-server-usage/api/v3}terminated_at')
ivan-zhu09111942013-08-01 08:09:16 +0800106 launched_at = ('{http://docs.openstack.org'
ivan-zhu8f992be2013-07-31 14:56:58 +0800107 '/compute/ext/os-server-usage/api/v3}launched_at')
ivan-zhu09111942013-08-01 08:09:16 +0800108 power_state = ('{http://docs.openstack.org'
ivan-zhu8f992be2013-07-31 14:56:58 +0800109 '/compute/ext/extended_status/api/v3}power_state')
ivan-zhu09111942013-08-01 08:09:16 +0800110 availability_zone = ('{http://docs.openstack.org'
ivan-zhu8f992be2013-07-31 14:56:58 +0800111 '/compute/ext/extended_availability_zone/api/v3}'
ivan-zhu09111942013-08-01 08:09:16 +0800112 'availability_zone')
113 vm_state = ('{http://docs.openstack.org'
ivan-zhu8f992be2013-07-31 14:56:58 +0800114 '/compute/ext/extended_status/api/v3}vm_state')
ivan-zhu09111942013-08-01 08:09:16 +0800115 task_state = ('{http://docs.openstack.org'
ivan-zhu8f992be2013-07-31 14:56:58 +0800116 '/compute/ext/extended_status/api/v3}task_state')
117 if disk_config in json:
118 json['os-disk-config:disk_config'] = json.pop(disk_config)
ivan-zhu09111942013-08-01 08:09:16 +0800119 if terminated_at in json:
ivan-zhu8f992be2013-07-31 14:56:58 +0800120 json['os-server-usage:terminated_at'] = json.pop(terminated_at)
ivan-zhu09111942013-08-01 08:09:16 +0800121 if launched_at in json:
ivan-zhu8f992be2013-07-31 14:56:58 +0800122 json['os-server-usage:launched_at'] = json.pop(launched_at)
ivan-zhu09111942013-08-01 08:09:16 +0800123 if power_state in json:
ivan-zhu8f992be2013-07-31 14:56:58 +0800124 json['os-extended-status:power_state'] = json.pop(power_state)
ivan-zhu09111942013-08-01 08:09:16 +0800125 if availability_zone in json:
ivan-zhu8f992be2013-07-31 14:56:58 +0800126 json['os-extended-availability-zone:availability_zone'] = json.pop(
127 availability_zone)
ivan-zhu09111942013-08-01 08:09:16 +0800128 if vm_state in json:
ivan-zhu8f992be2013-07-31 14:56:58 +0800129 json['os-extended-status:vm_state'] = json.pop(vm_state)
ivan-zhu09111942013-08-01 08:09:16 +0800130 if task_state in json:
ivan-zhu8f992be2013-07-31 14:56:58 +0800131 json['os-extended-status:task_state'] = json.pop(task_state)
ivan-zhu09111942013-08-01 08:09:16 +0800132 return json
133
134
ivan-zhu8f992be2013-07-31 14:56:58 +0800135class ServersV3ClientXML(RestClientXML):
ivan-zhu09111942013-08-01 08:09:16 +0800136
ivan-zhu8f992be2013-07-31 14:56:58 +0800137 def __init__(self, config, username, password, auth_url,
138 tenant_name=None, auth_version='v2'):
139 super(ServersV3ClientXML, self).__init__(config, username, password,
140 auth_url, tenant_name,
141 auth_version=auth_version)
142 self.service = self.config.compute.catalog_v3_type
ivan-zhu09111942013-08-01 08:09:16 +0800143
144 def _parse_key_value(self, node):
145 """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
146 data = {}
147 for node in node.getchildren():
148 data[node.get('key')] = node.text
149 return data
150
151 def _parse_links(self, node, json):
152 del json['link']
153 json['links'] = []
154 for linknode in node.findall('{http://www.w3.org/2005/Atom}link'):
155 json['links'].append(xml_to_json(linknode))
156
157 def _parse_server(self, body):
158 json = _translate_server_xml_to_json(body)
159
160 if 'metadata' in json and json['metadata']:
161 # NOTE(danms): if there was metadata, we need to re-parse
162 # that as a special type
ivan-zhu8f992be2013-07-31 14:56:58 +0800163 metadata_tag = body.find('{%s}metadata' % XMLNS_V3)
ivan-zhu09111942013-08-01 08:09:16 +0800164 json["metadata"] = self._parse_key_value(metadata_tag)
165 if 'link' in json:
166 self._parse_links(body, json)
167 for sub in ['image', 'flavor']:
168 if sub in json and 'link' in json[sub]:
169 self._parse_links(body, json[sub])
170 return json
171
172 def _parse_xml_virtual_interfaces(self, xml_dom):
173 """
174 Return server's virtual interfaces XML as JSON.
175 """
176 data = {"virtual_interfaces": []}
177 for iface in xml_dom.getchildren():
178 data["virtual_interfaces"].append(
179 {"id": iface.get("id"),
180 "mac_address": iface.get("mac_address")})
181 return data
182
183 def get_server(self, server_id):
184 """Returns the details of an existing server."""
185 resp, body = self.get("servers/%s" % str(server_id), self.headers)
186 server = self._parse_server(etree.fromstring(body))
187 return resp, server
188
189 def lock_server(self, server_id, **kwargs):
190 """Locks the given server."""
191 return self.action(server_id, 'lock', None, **kwargs)
192
193 def unlock_server(self, server_id, **kwargs):
194 """Unlocks the given server."""
195 return self.action(server_id, 'unlock', None, **kwargs)
196
197 def suspend_server(self, server_id, **kwargs):
198 """Suspends the provided server."""
199 return self.action(server_id, 'suspend', None, **kwargs)
200
201 def resume_server(self, server_id, **kwargs):
202 """Un-suspends the provided server."""
203 return self.action(server_id, 'resume', None, **kwargs)
204
205 def pause_server(self, server_id, **kwargs):
206 """Pauses the provided server."""
207 return self.action(server_id, 'pause', None, **kwargs)
208
209 def unpause_server(self, server_id, **kwargs):
210 """Un-pauses the provided server."""
211 return self.action(server_id, 'unpause', None, **kwargs)
212
213 def reset_state(self, server_id, state='error'):
214 """Resets the state of a server to active/error."""
ivan-zhu8f992be2013-07-31 14:56:58 +0800215 return self.action(server_id, 'reset_state', None, state=state)
ivan-zhu09111942013-08-01 08:09:16 +0800216
217 def delete_server(self, server_id):
218 """Deletes the given server."""
219 return self.delete("servers/%s" % str(server_id))
220
221 def _parse_array(self, node):
222 array = []
223 for child in node.getchildren():
224 array.append(xml_to_json(child))
225 return array
226
227 def list_servers(self, params=None):
228 url = 'servers'
229 if params:
230 url += '?%s' % urllib.urlencode(params)
231
232 resp, body = self.get(url, self.headers)
233 servers = self._parse_array(etree.fromstring(body))
234 return resp, {"servers": servers}
235
236 def list_servers_with_detail(self, params=None):
237 url = 'servers/detail'
238 if params:
239 url += '?%s' % urllib.urlencode(params)
240
241 resp, body = self.get(url, self.headers)
242 servers = self._parse_array(etree.fromstring(body))
243 return resp, {"servers": servers}
244
ivan-zhu8f992be2013-07-31 14:56:58 +0800245 def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
246 access_ip_v6=None, disk_config=None):
ivan-zhu09111942013-08-01 08:09:16 +0800247 doc = Document()
248 server = Element("server")
249 doc.append(server)
250
251 if name is not None:
252 server.add_attr("name", name)
ivan-zhu8f992be2013-07-31 14:56:58 +0800253 if access_ip_v4 is not None:
254 server.add_attr("access_ip_v4", access_ip_v4)
255 if access_ip_v6 is not None:
256 server.add_attr("access_ip_v6", access_ip_v6)
ivan-zhu09111942013-08-01 08:09:16 +0800257 if meta is not None:
258 metadata = Element("metadata")
259 server.append(metadata)
260 for k, v in meta:
261 meta = Element("meta", key=k)
262 meta.append(Text(v))
263 metadata.append(meta)
264
265 resp, body = self.put('servers/%s' % str(server_id),
266 str(doc), self.headers)
267 return resp, xml_to_json(etree.fromstring(body))
268
269 def create_server(self, name, image_ref, flavor_ref, **kwargs):
270 """
271 Creates an instance of a server.
272 name (Required): The name of the server.
273 image_ref (Required): Reference to the image used to build the server.
274 flavor_ref (Required): The flavor used to build the server.
275 Following optional keyword arguments are accepted:
ivan-zhu8f992be2013-07-31 14:56:58 +0800276 admin_password: Sets the initial root password.
ivan-zhu09111942013-08-01 08:09:16 +0800277 key_name: Key name of keypair that was created earlier.
278 meta: A dictionary of values to be used as metadata.
279 personality: A list of dictionaries for files to be injected into
280 the server.
281 security_groups: A list of security group dicts.
282 networks: A list of network dicts with UUID and fixed_ip.
283 user_data: User data for instance.
284 availability_zone: Availability zone in which to launch instance.
ivan-zhu8f992be2013-07-31 14:56:58 +0800285 access_ip_v4: The IPv4 access address for the server.
286 access_ip_v6: The IPv6 access address for the server.
ivan-zhu09111942013-08-01 08:09:16 +0800287 min_count: Count of minimum number of instances to launch.
288 max_count: Count of maximum number of instances to launch.
289 disk_config: Determines if user or admin controls disk configuration.
ivan-zhu8f992be2013-07-31 14:56:58 +0800290 return_reservation_id: Enable/Disable the return of reservation id.
ivan-zhu09111942013-08-01 08:09:16 +0800291 """
292 server = Element("server",
ivan-zhu09111942013-08-01 08:09:16 +0800293 imageRef=image_ref,
ivan-zhu8f992be2013-07-31 14:56:58 +0800294 xmlns=XMLNS_V3,
295 flavor_ref=flavor_ref,
296 image_ref=image_ref,
ivan-zhu09111942013-08-01 08:09:16 +0800297 name=name)
ivan-zhu8f992be2013-07-31 14:56:58 +0800298 attrs = ["admin_pass", "access_ip_v4", "access_ip_v6", "key_name",
299 ("os-user-data:user_data",
300 'user_data',
301 'xmlns:os-user-data',
302 "http://docs.openstack.org/compute/ext/userdata/api/v3"),
303 ("os-availability-zone:availability_zone",
304 'availability_zone',
305 'xmlns:os-availability-zone',
306 "http://docs.openstack.org/compute/ext/"
307 "availabilityzone/api/v3"),
308 ("os-multiple-create:min_count",
309 'min_count',
310 'xmlns:os-multiple-create',
311 "http://docs.openstack.org/compute/ext/"
312 "multiplecreate/api/v3"),
313 ("os-multiple-create:max_count",
314 'max_count',
315 'xmlns:os-multiple-create',
316 "http://docs.openstack.org/compute/ext/"
317 "multiplecreate/api/v3"),
318 ("os-multiple-create:return_reservation_id",
319 "return_reservation_id",
320 'xmlns:os-multiple-create',
321 "http://docs.openstack.org/compute/ext/"
322 "multiplecreate/api/v3"),
323 ("os-disk-config:disk_config",
324 "disk_config",
325 "xmlns:os-disk-config",
326 "http://docs.openstack.org/"
327 "compute/ext/disk_config/api/v3")]
ivan-zhu09111942013-08-01 08:09:16 +0800328
ivan-zhu8f992be2013-07-31 14:56:58 +0800329 for attr in attrs:
330 if isinstance(attr, tuple):
331 post_param = attr[0]
332 key = attr[1]
333 value = kwargs.get(key)
334 if value is not None:
335 server.add_attr(attr[2], attr[3])
336 server.add_attr(post_param, value)
337 else:
338 post_param = attr
339 key = attr
340 value = kwargs.get(key)
341 if value is not None:
342 server.add_attr(post_param, value)
ivan-zhu09111942013-08-01 08:09:16 +0800343
344 if 'security_groups' in kwargs:
345 secgroups = Element("security_groups")
346 server.append(secgroups)
347 for secgroup in kwargs['security_groups']:
348 s = Element("security_group", name=secgroup['name'])
349 secgroups.append(s)
350
351 if 'networks' in kwargs:
352 networks = Element("networks")
353 server.append(networks)
354 for network in kwargs['networks']:
355 s = Element("network", uuid=network['uuid'],
356 fixed_ip=network['fixed_ip'])
357 networks.append(s)
358
359 if 'meta' in kwargs:
360 metadata = Element("metadata")
361 server.append(metadata)
362 for k, v in kwargs['meta'].items():
363 meta = Element("meta", key=k)
364 meta.append(Text(v))
365 metadata.append(meta)
366
367 if 'personality' in kwargs:
368 personality = Element('personality')
369 server.append(personality)
370 for k in kwargs['personality']:
371 temp = Element('file', path=k['path'])
372 temp.append(Text(k['contents']))
373 personality.append(temp)
374
375 resp, body = self.post('servers', str(Document(server)), self.headers)
376 server = self._parse_server(etree.fromstring(body))
377 return resp, server
378
379 def wait_for_server_status(self, server_id, status):
380 """Waits for a server to reach a given status."""
381 return waiters.wait_for_server_status(self, server_id, status)
382
383 def wait_for_server_termination(self, server_id, ignore_error=False):
384 """Waits for server to reach termination."""
385 start_time = int(time.time())
386 while True:
387 try:
388 resp, body = self.get_server(server_id)
389 except exceptions.NotFound:
390 return
391
392 server_status = body['status']
393 if server_status == 'ERROR' and not ignore_error:
394 raise exceptions.BuildErrorException
395
396 if int(time.time()) - start_time >= self.build_timeout:
397 raise exceptions.TimeoutException
398
399 time.sleep(self.build_interval)
400
401 def _parse_network(self, node):
402 addrs = []
403 for child in node.getchildren():
404 addrs.append({'version': int(child.get('version')),
405 'addr': child.get('addr')})
406 return {node.get('id'): addrs}
407
408 def list_addresses(self, server_id):
409 """Lists all addresses for a server."""
410 resp, body = self.get("servers/%s/ips" % str(server_id), self.headers)
411
412 networks = {}
413 xml_list = etree.fromstring(body)
414 for child in xml_list.getchildren():
415 network = self._parse_network(child)
416 networks.update(**network)
417
418 return resp, networks
419
420 def list_addresses_by_network(self, server_id, network_id):
421 """Lists all addresses of a specific network type for a server."""
422 resp, body = self.get("servers/%s/ips/%s" % (str(server_id),
423 network_id),
424 self.headers)
425 network = self._parse_network(etree.fromstring(body))
426
427 return resp, network
428
429 def action(self, server_id, action_name, response_key, **kwargs):
430 if 'xmlns' not in kwargs:
ivan-zhu8f992be2013-07-31 14:56:58 +0800431 kwargs['xmlns'] = XMLNS_V3
ivan-zhu09111942013-08-01 08:09:16 +0800432 doc = Document((Element(action_name, **kwargs)))
433 resp, body = self.post("servers/%s/action" % server_id,
434 str(doc), self.headers)
435 if response_key is not None:
436 body = xml_to_json(etree.fromstring(body))
437 return resp, body
438
439 def change_password(self, server_id, password):
ivan-zhu8f992be2013-07-31 14:56:58 +0800440 return self.action(server_id, "change_password", None,
441 admin_pass=password)
ivan-zhu09111942013-08-01 08:09:16 +0800442
443 def reboot(self, server_id, reboot_type):
444 return self.action(server_id, "reboot", None, type=reboot_type)
445
446 def rebuild(self, server_id, image_ref, **kwargs):
ivan-zhu8f992be2013-07-31 14:56:58 +0800447 kwargs['image_ref'] = image_ref
ivan-zhu09111942013-08-01 08:09:16 +0800448 if 'disk_config' in kwargs:
ivan-zhu8f992be2013-07-31 14:56:58 +0800449 kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
ivan-zhu09111942013-08-01 08:09:16 +0800450 del kwargs['disk_config']
ivan-zhu8f992be2013-07-31 14:56:58 +0800451 kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
452 "compute/ext/disk_config/api/v3"
ivan-zhu09111942013-08-01 08:09:16 +0800453 kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
454 if 'xmlns' not in kwargs:
ivan-zhu8f992be2013-07-31 14:56:58 +0800455 kwargs['xmlns'] = XMLNS_V3
ivan-zhu09111942013-08-01 08:09:16 +0800456
457 attrs = kwargs.copy()
458 if 'metadata' in attrs:
459 del attrs['metadata']
460 rebuild = Element("rebuild",
461 **attrs)
462
463 if 'metadata' in kwargs:
464 metadata = Element("metadata")
465 rebuild.append(metadata)
466 for k, v in kwargs['metadata'].items():
467 meta = Element("meta", key=k)
468 meta.append(Text(v))
469 metadata.append(meta)
470
471 resp, body = self.post('servers/%s/action' % server_id,
472 str(Document(rebuild)), self.headers)
473 server = self._parse_server(etree.fromstring(body))
474 return resp, server
475
476 def resize(self, server_id, flavor_ref, **kwargs):
477 if 'disk_config' in kwargs:
ivan-zhu8f992be2013-07-31 14:56:58 +0800478 kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
ivan-zhu09111942013-08-01 08:09:16 +0800479 del kwargs['disk_config']
ivan-zhu8f992be2013-07-31 14:56:58 +0800480 kwargs['xmlns:os-disk-config'] = "http://docs.openstack.org/"\
481 "compute/ext/disk_config/api/v3"
ivan-zhu09111942013-08-01 08:09:16 +0800482 kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
ivan-zhu8f992be2013-07-31 14:56:58 +0800483 kwargs['flavor_ref'] = flavor_ref
ivan-zhu09111942013-08-01 08:09:16 +0800484 return self.action(server_id, 'resize', None, **kwargs)
485
486 def confirm_resize(self, server_id, **kwargs):
ivan-zhu8f992be2013-07-31 14:56:58 +0800487 return self.action(server_id, 'confirm_resize', None, **kwargs)
ivan-zhu09111942013-08-01 08:09:16 +0800488
489 def revert_resize(self, server_id, **kwargs):
ivan-zhu8f992be2013-07-31 14:56:58 +0800490 return self.action(server_id, 'revert_resize', None, **kwargs)
ivan-zhu09111942013-08-01 08:09:16 +0800491
492 def stop(self, server_id, **kwargs):
ivan-zhu8f992be2013-07-31 14:56:58 +0800493 return self.action(server_id, 'stop', None, **kwargs)
ivan-zhu09111942013-08-01 08:09:16 +0800494
495 def start(self, server_id, **kwargs):
ivan-zhu8f992be2013-07-31 14:56:58 +0800496 return self.action(server_id, 'start', None, **kwargs)
ivan-zhu09111942013-08-01 08:09:16 +0800497
ivan-zhu8f992be2013-07-31 14:56:58 +0800498 def create_image(self, server_id, name, meta=None):
499 """Creates an image of the original server."""
500 post_body = Element('create_image', name=name)
501
502 if meta:
503 metadata = Element('metadata')
504 post_body.append(metadata)
505 for k, v in meta.items():
506 data = Element('meta', key=k)
507 data.append(Text(v))
508 metadata.append(data)
509 resp, body = self.post('servers/%s/action' % str(server_id),
510 str(Document(post_body)), self.headers)
511 return resp, body
ivan-zhu09111942013-08-01 08:09:16 +0800512
513 def add_security_group(self, server_id, name):
ivan-zhu8f992be2013-07-31 14:56:58 +0800514 return self.action(server_id, 'add_security_group', None, name=name)
ivan-zhu09111942013-08-01 08:09:16 +0800515
516 def remove_security_group(self, server_id, name):
ivan-zhu8f992be2013-07-31 14:56:58 +0800517 return self.action(server_id, 'remove_security_group', None, name=name)
ivan-zhu09111942013-08-01 08:09:16 +0800518
519 def live_migrate_server(self, server_id, dest_host, use_block_migration):
520 """This should be called with administrator privileges ."""
521
ivan-zhu8f992be2013-07-31 14:56:58 +0800522 req_body = Element("migrate_live",
523 xmlns=XMLNS_V3,
ivan-zhu09111942013-08-01 08:09:16 +0800524 disk_over_commit=False,
525 block_migration=use_block_migration,
526 host=dest_host)
527
528 resp, body = self.post("servers/%s/action" % str(server_id),
529 str(Document(req_body)), self.headers)
530 return resp, body
531
532 def list_server_metadata(self, server_id):
533 resp, body = self.get("servers/%s/metadata" % str(server_id),
534 self.headers)
535 body = self._parse_key_value(etree.fromstring(body))
536 return resp, body
537
538 def set_server_metadata(self, server_id, meta, no_metadata_field=False):
539 doc = Document()
540 if not no_metadata_field:
541 metadata = Element("metadata")
542 doc.append(metadata)
543 for k, v in meta.items():
544 meta_element = Element("meta", key=k)
545 meta_element.append(Text(v))
546 metadata.append(meta_element)
547 resp, body = self.put('servers/%s/metadata' % str(server_id),
548 str(doc), self.headers)
549 return resp, xml_to_json(etree.fromstring(body))
550
551 def update_server_metadata(self, server_id, meta):
552 doc = Document()
553 metadata = Element("metadata")
554 doc.append(metadata)
555 for k, v in meta.items():
556 meta_element = Element("meta", key=k)
557 meta_element.append(Text(v))
558 metadata.append(meta_element)
559 resp, body = self.post("/servers/%s/metadata" % str(server_id),
560 str(doc), headers=self.headers)
561 body = xml_to_json(etree.fromstring(body))
562 return resp, body
563
564 def get_server_metadata_item(self, server_id, key):
565 resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key),
566 headers=self.headers)
567 return resp, dict([(etree.fromstring(body).attrib['key'],
568 xml_to_json(etree.fromstring(body)))])
569
570 def set_server_metadata_item(self, server_id, key, meta):
571 doc = Document()
572 for k, v in meta.items():
573 meta_element = Element("meta", key=k)
574 meta_element.append(Text(v))
575 doc.append(meta_element)
576 resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
577 str(doc), self.headers)
578 return resp, xml_to_json(etree.fromstring(body))
579
580 def delete_server_metadata_item(self, server_id, key):
581 resp, body = self.delete("servers/%s/metadata/%s" %
582 (str(server_id), key))
583 return resp, body
584
585 def get_console_output(self, server_id, length):
ivan-zhu8f992be2013-07-31 14:56:58 +0800586 return self.action(server_id, 'get_console_output', 'output',
ivan-zhu09111942013-08-01 08:09:16 +0800587 length=length)
588
ivan-zhu8f992be2013-07-31 14:56:58 +0800589 def rescue_server(self, server_id, admin_pass=None):
ivan-zhu09111942013-08-01 08:09:16 +0800590 """Rescue the provided server."""
ivan-zhu8f992be2013-07-31 14:56:58 +0800591 return self.action(server_id, 'rescue', None, admin_pass=admin_pass)
ivan-zhu09111942013-08-01 08:09:16 +0800592
593 def unrescue_server(self, server_id):
594 """Unrescue the provided server."""
595 return self.action(server_id, 'unrescue', None)
596
597 def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
ivan-zhu8f992be2013-07-31 14:56:58 +0800598 return self.action(server_id, "attach", None, volume_id=volume_id,
599 device=device)
ivan-zhu09111942013-08-01 08:09:16 +0800600
601 def detach_volume(self, server_id, volume_id):
ivan-zhu8f992be2013-07-31 14:56:58 +0800602 return self.action(server_id, "detach", None, volume_id=volume_id)
ivan-zhu09111942013-08-01 08:09:16 +0800603
604 def get_server_diagnostics(self, server_id):
605 """Get the usage data for a server."""
ivan-zhu8f992be2013-07-31 14:56:58 +0800606 resp, body = self.get("servers/%s/os-server-diagnostics" % server_id,
ivan-zhu09111942013-08-01 08:09:16 +0800607 self.headers)
608 body = xml_to_json(etree.fromstring(body))
609 return resp, body
610
611 def list_instance_actions(self, server_id):
612 """List the provided server action."""
613 resp, body = self.get("servers/%s/os-instance-actions" % server_id,
614 self.headers)
615 body = self._parse_array(etree.fromstring(body))
616 return resp, body
617
618 def get_instance_action(self, server_id, request_id):
619 """Returns the action details of the provided server."""
620 resp, body = self.get("servers/%s/os-instance-actions/%s" %
621 (server_id, request_id), self.headers)
622 body = xml_to_json(etree.fromstring(body))
623 return resp, body