Extension for rebuild-for-ha
Adds an extension for blueprint rebuild-for-ha Evacuation of VM from a failed host for HA. New api supports recreate/rebuild modes. With shared storage, VM is recreated on the target from disk. Otherwise, rebuild from original image is performed. DocImpact Change-Id: Id7e3e68dbaaf74e9314440d98195a92454078fd9 Co-authored-by: Oshrit Feder <oshritf@il.ibm.com>
This commit is contained in:
parent
fe16fded3d
commit
01a25e9890
@ -184,6 +184,14 @@
|
||||
"namespace": "http://docs.openstack.org/compute/ext/deferred-delete/api/v1.1",
|
||||
"updated": "2011-09-01T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-evacuate",
|
||||
"description": "Enables server evacuation",
|
||||
"links": [],
|
||||
"name": "Evacuate",
|
||||
"namespace": "http://docs.openstack.org/compute/ext/evacuate/api/v2",
|
||||
"updated": "2012-12-05T00:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"alias": "os-fixed-ips",
|
||||
"description": "Fixed IPs support.",
|
||||
|
@ -84,6 +84,9 @@
|
||||
<extension alias="os-deferred-delete" updated="2011-09-01T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/deferred-delete/api/v1.1" name="DeferredDelete">
|
||||
<description>Instance deferred delete.</description>
|
||||
</extension>
|
||||
<extension alias="os-evacuate" updated="2012-12-05T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/evacuate/api/v2" name="Evacuate">
|
||||
<description>Enables server evacuation</description>
|
||||
</extension>
|
||||
<extension alias="os-fixed-ips" updated="2012-10-18T13:25:27-06:00" namespace="http://docs.openstack.org/compute/ext/fixed_ips/api/v2" name="FixedIPs">
|
||||
<description>Fixed IPs support.</description>
|
||||
</extension>
|
||||
|
7
doc/api_samples/os-evacuate/server-evacuate-req.json
Normal file
7
doc/api_samples/os-evacuate/server-evacuate-req.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"evacuate": {
|
||||
"host": "TargetHost",
|
||||
"adminPass": "MySecretPass",
|
||||
"onSharedStorage": "True"
|
||||
}
|
||||
}
|
5
doc/api_samples/os-evacuate/server-evacuate-req.xml
Normal file
5
doc/api_samples/os-evacuate/server-evacuate-req.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rescue xmlns="http://docs.openstack.org/compute/api/v2"
|
||||
host="TargetHost"
|
||||
adminPass="MySecretPass"
|
||||
onSharedStorage="True"/>
|
3
doc/api_samples/os-evacuate/server-evacuate-resp.json
Normal file
3
doc/api_samples/os-evacuate/server-evacuate-resp.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"adminPass": "MySecretPass"
|
||||
}
|
2
doc/api_samples/os-evacuate/server-evacuate-resp.xml
Normal file
2
doc/api_samples/os-evacuate/server-evacuate-resp.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<adminPass>MySecretPass</adminPass>
|
16
doc/api_samples/os-evacuate/server-post-req.json
Normal file
16
doc/api_samples/os-evacuate/server-post-req.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server" : {
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"flavorRef" : "http://openstack.example.com/openstack/flavors/1",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"personality" : [
|
||||
{
|
||||
"path" : "/etc/banner.txt",
|
||||
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
19
doc/api_samples/os-evacuate/server-post-req.xml
Normal file
19
doc/api_samples/os-evacuate/server-post-req.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b" flavorRef="http://openstack.example.com/openstack/flavors/1" name="new-server-test">
|
||||
<metadata>
|
||||
<meta key="My Server Name">Apache1</meta>
|
||||
</metadata>
|
||||
<personality>
|
||||
<file path="/etc/banner.txt">
|
||||
ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
|
||||
dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
|
||||
IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
|
||||
c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
|
||||
QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
|
||||
ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
|
||||
dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
|
||||
c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
|
||||
b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
|
||||
</file>
|
||||
</personality>
|
||||
</server>
|
16
doc/api_samples/os-evacuate/server-post-resp.json
Normal file
16
doc/api_samples/os-evacuate/server-post-resp.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "xjDVAYHmc34s",
|
||||
"id": "784f5005-bec9-4c22-8c42-5a7dcba88d82",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/openstack/servers/784f5005-bec9-4c22-8c42-5a7dcba88d82",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/openstack/servers/784f5005-bec9-4c22-8c42-5a7dcba88d82",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
6
doc/api_samples/os-evacuate/server-post-resp.xml
Normal file
6
doc/api_samples/os-evacuate/server-post-resp.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="70b4263f-bae1-4ac4-a1c5-e5bb193767bc" adminPass="ttv8YPD3tRPm">
|
||||
<metadata/>
|
||||
<atom:link href="http://openstack.example.com/v2/openstack/servers/70b4263f-bae1-4ac4-a1c5-e5bb193767bc" rel="self"/>
|
||||
<atom:link href="http://openstack.example.com/openstack/servers/70b4263f-bae1-4ac4-a1c5-e5bb193767bc" rel="bookmark"/>
|
||||
</server>
|
@ -40,6 +40,7 @@
|
||||
"compute_extension:createserverext": "",
|
||||
"compute_extension:deferred_delete": "",
|
||||
"compute_extension:disk_config": "",
|
||||
"compute_extension:evacuate": "rule:admin_api",
|
||||
"compute_extension:extended_server_attributes": "rule:admin_api",
|
||||
"compute_extension:extended_status": "",
|
||||
"compute_extension:extended_availability_zone": "",
|
||||
|
98
nova/api/openstack/compute/contrib/evacuate.py
Normal file
98
nova/api/openstack/compute/contrib/evacuate.py
Normal file
@ -0,0 +1,98 @@
|
||||
# Copyright 2013 OpenStack, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova import compute
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('compute', 'evacuate')
|
||||
|
||||
|
||||
class Controller(wsgi.Controller):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Controller, self).__init__(*args, **kwargs)
|
||||
self.compute_api = compute.API()
|
||||
|
||||
@wsgi.action('evacuate')
|
||||
def _evacuate(self, req, id, body):
|
||||
"""
|
||||
Permit admins to evacuate a server from a failed host
|
||||
to a new one.
|
||||
"""
|
||||
context = req.environ["nova.context"]
|
||||
if not context.is_admin:
|
||||
msg = _("Instance evacuate is admin only functionality")
|
||||
raise exc.HTTPForbidden(explanation=msg)
|
||||
authorize(context)
|
||||
|
||||
try:
|
||||
if len(body) != 1:
|
||||
raise exc.HTTPBadRequest(_("Malformed request body"))
|
||||
|
||||
evacuate_body = body["evacuate"]
|
||||
host = evacuate_body["host"]
|
||||
on_shared_storage = utils.bool_from_str(
|
||||
evacuate_body["onSharedStorage"])
|
||||
|
||||
if 'adminPass' in evacuate_body:
|
||||
# check that if requested to evacuate server on shared storage
|
||||
# password not specified
|
||||
if on_shared_storage:
|
||||
msg = _("admin password can't be changed on existing disk")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
password = evacuate_body['adminPass']
|
||||
elif not on_shared_storage:
|
||||
password = utils.generate_password()
|
||||
|
||||
except (TypeError, KeyError):
|
||||
msg = _("host and onSharedStorage must be specified.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
try:
|
||||
instance = self.compute_api.get(context, id)
|
||||
self.compute_api.evacuate(context, instance, host,
|
||||
on_shared_storage, password)
|
||||
except exception.InstanceInvalidState as state_error:
|
||||
common.raise_http_conflict_for_instance_invalid_state(state_error,
|
||||
'evacuate')
|
||||
except Exception as e:
|
||||
msg = _("Error in evacuate, %s") % e
|
||||
LOG.exception(msg, instance=instance)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if password:
|
||||
return {'adminPass': password}
|
||||
|
||||
|
||||
class Evacuate(extensions.ExtensionDescriptor):
|
||||
"""Enables server evacuation."""
|
||||
|
||||
name = "Evacuate"
|
||||
alias = "os-evacuate"
|
||||
namespace = "http://docs.openstack.org/compute/ext/evacuate/api/v2"
|
||||
updated = "2013-01-06T00:00:00+00:00"
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = Controller()
|
||||
extension = extensions.ControllerExtension(self, 'servers', controller)
|
||||
return [extension]
|
156
nova/tests/api/openstack/compute/contrib/test_evacuate.py
Normal file
156
nova/tests/api/openstack/compute/contrib/test_evacuate.py
Normal file
@ -0,0 +1,156 @@
|
||||
# Copyright 2013 OpenStack LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
import webob
|
||||
|
||||
from nova.compute import api as compute_api
|
||||
from nova.compute import vm_states
|
||||
from nova import context
|
||||
from nova.openstack.common import cfg
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova import test
|
||||
from nova.tests.api.openstack import fakes
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('password_length', 'nova.utils')
|
||||
|
||||
|
||||
def fake_compute_api(*args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def fake_compute_api_get(self, context, instance_id):
|
||||
return {
|
||||
'id': 1,
|
||||
'uuid': instance_id,
|
||||
'vm_state': vm_states.ACTIVE,
|
||||
'task_state': None, 'host': 'host1'
|
||||
}
|
||||
|
||||
|
||||
class EvacuateTest(test.TestCase):
|
||||
|
||||
_methods = ('resize', 'evacuate')
|
||||
|
||||
def setUp(self):
|
||||
super(EvacuateTest, self).setUp()
|
||||
self.stubs.Set(compute_api.API, 'get', fake_compute_api_get)
|
||||
self.UUID = uuid.uuid4()
|
||||
for _method in self._methods:
|
||||
self.stubs.Set(compute_api.API, _method, fake_compute_api)
|
||||
|
||||
def test_evacuate_instance_with_no_target(self):
|
||||
ctxt = context.get_admin_context()
|
||||
ctxt.user_id = 'fake'
|
||||
ctxt.project_id = 'fake'
|
||||
ctxt.is_admin = True
|
||||
app = fakes.wsgi_app(fake_auth_context=ctxt)
|
||||
req = webob.Request.blank('/v2/fake/servers/%s/action' % self.UUID)
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({
|
||||
'evacuate': {
|
||||
'onSharedStorage': 'False',
|
||||
'adminPass': 'MyNewPass'
|
||||
}
|
||||
})
|
||||
req.content_type = 'application/json'
|
||||
res = req.get_response(app)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_evacuate_instance_with_target(self):
|
||||
ctxt = context.get_admin_context()
|
||||
ctxt.user_id = 'fake'
|
||||
ctxt.project_id = 'fake'
|
||||
ctxt.is_admin = True
|
||||
app = fakes.wsgi_app(fake_auth_context=ctxt)
|
||||
uuid = self.UUID
|
||||
req = webob.Request.blank('/v2/fake/servers/%s/action' % uuid)
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({
|
||||
'evacuate': {
|
||||
'host': 'my_host',
|
||||
'onSharedStorage': 'false',
|
||||
'adminPass': 'MyNewPass'
|
||||
}
|
||||
})
|
||||
req.content_type = 'application/json'
|
||||
|
||||
def fake_update(inst, context, instance,
|
||||
task_state, expected_task_state):
|
||||
return None
|
||||
|
||||
self.stubs.Set(compute_api.API, 'update', fake_update)
|
||||
|
||||
resp = req.get_response(app)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
resp_json = jsonutils.loads(resp.body)
|
||||
self.assertEqual("MyNewPass", resp_json['adminPass'])
|
||||
|
||||
def test_evacuate_shared_and_pass(self):
|
||||
ctxt = context.get_admin_context()
|
||||
ctxt.user_id = 'fake'
|
||||
ctxt.project_id = 'fake'
|
||||
ctxt.is_admin = True
|
||||
app = fakes.wsgi_app(fake_auth_context=ctxt)
|
||||
uuid = self.UUID
|
||||
req = webob.Request.blank('/v2/fake/servers/%s/action' % uuid)
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({
|
||||
'evacuate': {
|
||||
'host': 'my_host',
|
||||
'onSharedStorage': 'True',
|
||||
'adminPass': 'MyNewPass'
|
||||
}
|
||||
})
|
||||
req.content_type = 'application/json'
|
||||
|
||||
def fake_update(inst, context, instance,
|
||||
task_state, expected_task_state):
|
||||
return None
|
||||
|
||||
self.stubs.Set(compute_api.API, 'update', fake_update)
|
||||
|
||||
res = req.get_response(app)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_evacuate_not_shared_pass_generated(self):
|
||||
ctxt = context.get_admin_context()
|
||||
ctxt.user_id = 'fake'
|
||||
ctxt.project_id = 'fake'
|
||||
ctxt.is_admin = True
|
||||
app = fakes.wsgi_app(fake_auth_context=ctxt)
|
||||
uuid = self.UUID
|
||||
req = webob.Request.blank('/v2/fake/servers/%s/action' % uuid)
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps({
|
||||
'evacuate': {
|
||||
'host': 'my_host',
|
||||
'onSharedStorage': 'False',
|
||||
}
|
||||
})
|
||||
|
||||
req.content_type = 'application/json'
|
||||
|
||||
def fake_update(inst, context, instance,
|
||||
task_state, expected_task_state):
|
||||
return None
|
||||
|
||||
self.stubs.Set(compute_api.API, 'update', fake_update)
|
||||
|
||||
resp = req.get_response(app)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
resp_json = jsonutils.loads(resp.body)
|
||||
self.assertEqual(CONF.password_length, len(resp_json['adminPass']))
|
@ -167,6 +167,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
||||
"DeferredDelete",
|
||||
"DiskConfig",
|
||||
"ExtendedAvailabilityZone",
|
||||
"Evacuate",
|
||||
"ExtendedStatus",
|
||||
"ExtendedServerAttributes",
|
||||
"FixedIPs",
|
||||
|
@ -117,6 +117,7 @@ policy_data = """
|
||||
"compute_extension:createserverext": "",
|
||||
"compute_extension:deferred_delete": "",
|
||||
"compute_extension:disk_config": "",
|
||||
"compute_extension:evacuate": "",
|
||||
"compute_extension:extended_server_attributes": "",
|
||||
"compute_extension:extended_status": "",
|
||||
"compute_extension:extended_availability_zone": "",
|
||||
|
@ -184,6 +184,14 @@
|
||||
"namespace": "http://docs.openstack.org/compute/ext/deferred-delete/api/v1.1",
|
||||
"updated": "%(timestamp)s"
|
||||
},
|
||||
{
|
||||
"alias": "os-evacuate",
|
||||
"description": "%(text)s",
|
||||
"links": [],
|
||||
"name": "Evacuate",
|
||||
"namespace": "http://docs.openstack.org/compute/ext/evacuate/api/v2",
|
||||
"updated": "%(timestamp)s"
|
||||
},
|
||||
{
|
||||
"alias": "os-fixed-ips",
|
||||
"description": "Fixed IPs support.",
|
||||
|
@ -69,6 +69,9 @@
|
||||
<extension alias="os-deferred-delete" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/deferred-delete/api/v1.1" name="DeferredDelete">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
<extension alias="os-evacuate" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/evacuate/api/v2" name="Evacuate">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
<extension alias="os-fixed-ips" name="FixedIPs" namespace="http://docs.openstack.org/compute/ext/fixed_ips/api/v2" updated="2012-10-18T13:25:27-06:00">
|
||||
<description>Fixed IPs support.</description>
|
||||
</extension>
|
||||
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"evacuate": {
|
||||
"host": "%(host)s",
|
||||
"adminPass": "%(adminPass)s",
|
||||
"onSharedStorage": "%(onSharedStorage)s"
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<evacuate xmlns="http://docs.openstack.org/compute/api/v2"
|
||||
host="%(host)s"
|
||||
adminPass="%(adminPass)s"
|
||||
onSharedStorage="%(onSharedStorage)s"/>
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"adminPass": "%(password)s"
|
||||
}
|
@ -0,0 +1 @@
|
||||
<adminPass>%(password)s</adminPass>
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server" : {
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "%(host)s/openstack/images/%(image_id)s",
|
||||
"flavorRef" : "%(host)s/openstack/flavors/1",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"personality" : [
|
||||
{
|
||||
"path" : "/etc/banner.txt",
|
||||
"contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<server xmlns="http://docs.openstack.org/compute/api/v1.1" imageRef="%(host)s/openstack/images/%(image_id)s" flavorRef="%(host)s/openstack/flavors/1" name="new-server-test">
|
||||
<metadata>
|
||||
<meta key="My Server Name">Apache1</meta>
|
||||
</metadata>
|
||||
<personality>
|
||||
<file path="/etc/banner.txt">
|
||||
ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
|
||||
dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
|
||||
IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
|
||||
c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
|
||||
QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
|
||||
ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
|
||||
dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
|
||||
c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
|
||||
b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
|
||||
</file>
|
||||
</personality>
|
||||
</server>
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"server": {
|
||||
"adminPass": "%(password)s",
|
||||
"id": "%(id)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(host)s/v2/openstack/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(host)s/openstack/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<server xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" adminPass="%(password)s">
|
||||
<metadata/>
|
||||
<atom:link href="%(host)s/v2/openstack/servers/%(uuid)s" rel="self"/>
|
||||
<atom:link href="%(host)s/openstack/servers/%(uuid)s" rel="bookmark"/>
|
||||
</server>
|
@ -42,6 +42,7 @@ from nova.openstack.common.log import logging
|
||||
from nova.openstack.common import timeutils
|
||||
import nova.quota
|
||||
from nova.scheduler import driver
|
||||
from nova.servicegroup import api as service_group_api
|
||||
from nova import test
|
||||
from nova.tests.api.openstack.compute.contrib import test_fping
|
||||
from nova.tests.api.openstack.compute.contrib import test_networks
|
||||
@ -2881,3 +2882,36 @@ class ExtendedAvailabilityZoneJsonTests(ServersSampleBase):
|
||||
|
||||
class ExtendedAvailabilityZoneXmlTests(ExtendedAvailabilityZoneJsonTests):
|
||||
ctype = 'xml'
|
||||
|
||||
|
||||
class EvacuateJsonTest(ServersSampleBase):
|
||||
|
||||
extension_name = ("nova.api.openstack.compute.contrib"
|
||||
".evacuate.Evacuate")
|
||||
|
||||
def test_server_evacuate(self):
|
||||
uuid = self._post_server()
|
||||
|
||||
req_subs = {
|
||||
'host': 'TargetHost',
|
||||
"adminPass": "MySecretPass",
|
||||
"onSharedStorage": 'False'
|
||||
}
|
||||
|
||||
def fake_service_is_up(self, service):
|
||||
"""Simulate validation of instance host is down."""
|
||||
return False
|
||||
|
||||
self.stubs.Set(service_group_api.API, 'service_is_up',
|
||||
fake_service_is_up)
|
||||
|
||||
response = self._do_post('servers/%s/action' % uuid,
|
||||
'server-evacuate-req', req_subs)
|
||||
self.assertEqual(response.status, 200)
|
||||
subs = self._get_regexes()
|
||||
return self._verify_response('server-evacuate-resp', subs,
|
||||
response)
|
||||
|
||||
|
||||
class EvacuateXmlTest(EvacuateJsonTest):
|
||||
ctype = 'xml'
|
||||
|
Loading…
Reference in New Issue
Block a user