Remove old instance reservation code
Change-Id: Ie95c814268cb46f81ee120acb749748c0b309a1f
This commit is contained in:
parent
9b7362b341
commit
fa5192b20f
|
@ -2,10 +2,6 @@
|
|||
blazar-nova
|
||||
============
|
||||
|
||||
Blazar Nova related changes. Includes filters for Host reservation and Nova API
|
||||
extensions for the instance reservation feature.
|
||||
This package contains the Blazar filter - a Nova scheduler filter used for the
|
||||
host reservation feature.
|
||||
|
||||
NOTE: The instance reservation feature does not work currently because it
|
||||
depends on Nova API extensions. Support for these extensions was completely
|
||||
removed from Nova in Newton. Expect the instance reservation feature to be
|
||||
implemented differently in a future release.
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
# Copyright (c) 2013 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Default reservation extension may be used if there is need to reserve all
|
||||
VMs by default (like in the developer clouds: every VM may be not just booted,
|
||||
but reserved for some amount of time, after which VM should be deleted or
|
||||
suspended or whatever).
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova import compute
|
||||
from nova import utils
|
||||
from oslo_config import cfg
|
||||
from webob import exc
|
||||
|
||||
|
||||
reservation_opts = [
|
||||
cfg.StrOpt('reservation_start_date',
|
||||
default='now',
|
||||
help='Specify date for all leases to be started for every VM'),
|
||||
cfg.IntOpt('reservation_length_days',
|
||||
default=30,
|
||||
help='Number of days for VM to be reserved.'),
|
||||
cfg.IntOpt('reservation_length_hours',
|
||||
default=0,
|
||||
help='Number of hours for VM to be reserved.'),
|
||||
cfg.IntOpt('reservation_length_minutes',
|
||||
default=0,
|
||||
help='Number of minutes for VM to be reserved.')
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(reservation_opts)
|
||||
|
||||
|
||||
class DefaultReservationController(wsgi.Controller):
|
||||
"""Add default reservation flags to every VM started."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DefaultReservationController, self).__init__(*args, **kwargs)
|
||||
self.compute_api = compute.API()
|
||||
|
||||
@wsgi.extends
|
||||
def create(self, req, body):
|
||||
"""Add additional hints to the create server request.
|
||||
|
||||
They will be managed by Reservation Extension.
|
||||
"""
|
||||
|
||||
if not self.is_valid_body(body, 'server'):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
if 'os:scheduler_hints' in body:
|
||||
scheduler_hints = body['os:scheduler_hints']
|
||||
else:
|
||||
scheduler_hints = body.get('OS-SCH-HNT:scheduler_hints', {})
|
||||
|
||||
lease_params = json.loads(scheduler_hints.get('lease_params', '{}'))
|
||||
|
||||
delta_days = datetime.timedelta(days=CONF.reservation_length_days)
|
||||
delta_hours = datetime.timedelta(hours=CONF.reservation_length_hours)
|
||||
delta_minutes = datetime.timedelta(
|
||||
minutes=CONF.reservation_length_minutes
|
||||
)
|
||||
|
||||
if CONF.reservation_start_date == 'now':
|
||||
base = datetime.datetime.utcnow()
|
||||
else:
|
||||
base = datetime.datetime.strptime(CONF.reservation_start_date,
|
||||
"%Y-%m-%d %H:%M")
|
||||
|
||||
lease_params.setdefault('name', utils.generate_uid('lease', size=6))
|
||||
lease_params.setdefault('start', CONF.reservation_start_date)
|
||||
lease_params.setdefault(
|
||||
'end', (base + delta_days + delta_hours + delta_minutes).
|
||||
strftime('%Y-%m-%d %H:%M'))
|
||||
|
||||
default_hints = {'lease_params': json.dumps(lease_params)}
|
||||
|
||||
if 'os:scheduler_hints' in body:
|
||||
body['os:scheduler_hints'].update(default_hints)
|
||||
elif 'OS-SCH-HNT:scheduler_hints' in body:
|
||||
body['OS-SCH-HNT:scheduler_hints'].update(default_hints)
|
||||
else:
|
||||
body['os:scheduler_hints'] = default_hints
|
||||
|
||||
|
||||
class Default_reservation(extensions.V21APIExtensionBase):
|
||||
"""Instance reservation system."""
|
||||
|
||||
name = "DefaultReservation"
|
||||
alias = "os-default-instance-reservation"
|
||||
updated = "2016-11-30T00:00:00Z"
|
||||
version = 1
|
||||
|
||||
def get_controller_extensions(self):
|
||||
return []
|
||||
|
||||
def get_resources(self):
|
||||
controller = DefaultReservationController()
|
||||
extension = extensions.ResourceExtension(
|
||||
self.alias, controller, member_actions={"action": "POST"})
|
||||
return [extension]
|
|
@ -1,191 +0,0 @@
|
|||
# Copyright (c) 2013 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Reservation extension parses instance creation request to find hints referring
|
||||
to use Blazar. If finds, create instance, shelve it not to use compute
|
||||
capacity while instance is not used and sent lease creation request to Blazar.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import traceback
|
||||
|
||||
try:
|
||||
from blazarclient import client as blazar_client
|
||||
except ImportError:
|
||||
blazar_client = None
|
||||
|
||||
from blazarnova.i18n import _ # noqa
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova import compute
|
||||
from nova import exception
|
||||
from oslo_log import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ReservationController(wsgi.Controller):
|
||||
"""Reservation controller to support VMs wake up."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ReservationController, self).__init__(*args, **kwargs)
|
||||
self.compute_api = compute.API()
|
||||
|
||||
@wsgi.extends
|
||||
def create(self, req, resp_obj, body):
|
||||
"""Support Blazar usage for Nova VMs."""
|
||||
|
||||
if 'os:scheduler_hints' in body:
|
||||
scheduler_hints = body['os:scheduler_hints']
|
||||
else:
|
||||
scheduler_hints = body.get('OS-SCH-HNT:scheduler_hints', {})
|
||||
|
||||
lease_params = scheduler_hints.get('lease_params', {})
|
||||
|
||||
if lease_params:
|
||||
try:
|
||||
lease_params = json.loads(lease_params)
|
||||
except ValueError:
|
||||
raise ValueError(_('Wrong JSON format for lease parameters '
|
||||
'passed %s') % lease_params)
|
||||
|
||||
if 'events' not in lease_params:
|
||||
lease_params.update({'events': []})
|
||||
|
||||
instance_id = resp_obj.obj['server']['id']
|
||||
lease_params.update({
|
||||
'reservations': [{'resource_id': instance_id,
|
||||
'resource_type': 'virtual:instance'}],
|
||||
})
|
||||
|
||||
service_catalog = json.loads(req.environ['HTTP_X_SERVICE_CATALOG'])
|
||||
user_roles = req.environ['HTTP_X_ROLES'].split(',')
|
||||
auth_token = req.environ['HTTP_X_AUTH_TOKEN']
|
||||
nova_ctx = req.environ["nova.context"]
|
||||
instance = self.compute_api.get(nova_ctx, instance_id,
|
||||
want_objects=True)
|
||||
|
||||
lease_transaction = LeaseTransaction()
|
||||
|
||||
with lease_transaction:
|
||||
while instance.vm_state != 'active':
|
||||
if instance.vm_state == 'error':
|
||||
raise exception.InstanceNotRunning(
|
||||
_("Instance %s responded with error state") %
|
||||
instance_id
|
||||
)
|
||||
instance = self.compute_api.get(nova_ctx, instance_id,
|
||||
want_objects=True)
|
||||
time.sleep(1)
|
||||
self.compute_api.shelve(nova_ctx, instance)
|
||||
|
||||
# shelve instance
|
||||
while instance.vm_state != 'shelved_offloaded':
|
||||
instance = self.compute_api.get(nova_ctx, instance_id,
|
||||
want_objects=True)
|
||||
time.sleep(1)
|
||||
|
||||
# send lease creation request to Blazar
|
||||
# this operation should be last, because otherwise Blazar
|
||||
# Manager may try unshelve instance when it's still active
|
||||
blazar_cl = self.get_blazar_client(service_catalog,
|
||||
user_roles, auth_token)
|
||||
lease_transaction.set_params(instance_id, blazar_cl, nova_ctx)
|
||||
lease = blazar_cl.lease.create(**lease_params)
|
||||
|
||||
try:
|
||||
lease_transaction.set_lease_id(lease['id'])
|
||||
except Exception:
|
||||
raise exception.InternalError(
|
||||
_('Lease creation request failed.')
|
||||
)
|
||||
|
||||
def get_blazar_client(self, catalog, user_roles, auth_token):
|
||||
if not blazar_client:
|
||||
raise ImportError(_('No Blazar client installed to the '
|
||||
'environment. Please install it to use '
|
||||
'reservation for Nova instances.'))
|
||||
|
||||
blazar_endpoints = None
|
||||
for service in catalog:
|
||||
if service['type'] == 'reservation':
|
||||
blazar_endpoints = service['endpoints'][0]
|
||||
if not blazar_endpoints:
|
||||
raise exception.NotFound(_('No Blazar endpoint found in service '
|
||||
'catalog.'))
|
||||
|
||||
blazar_url = None
|
||||
if 'admin' in user_roles:
|
||||
blazar_url = blazar_endpoints.get('adminURL')
|
||||
if blazar_url is None:
|
||||
blazar_url = blazar_endpoints.get('publicURL')
|
||||
if blazar_url is None:
|
||||
raise exception.NotFound(_('No Blazar URL found in service '
|
||||
'catalog.'))
|
||||
|
||||
blazar_cl = blazar_client.Client(blazar_url=blazar_url,
|
||||
auth_token=auth_token)
|
||||
return blazar_cl
|
||||
|
||||
|
||||
class LeaseTransaction(object):
|
||||
def __init__(self):
|
||||
self.lease_id = None
|
||||
self.instance_id = None
|
||||
self.blazar_cl = None
|
||||
self.nova_ctx = None
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
if exc_type and self.instance_id:
|
||||
msg = '\n'.join(traceback.format_exception(
|
||||
exc_type, exc_value, exc_traceback))
|
||||
LOG.error(_('Error occurred while lease creation. '
|
||||
'Traceback: \n%s') % msg)
|
||||
if self.lease_id:
|
||||
self.blazar_cl.lease.delete(self.lease_id)
|
||||
|
||||
api = compute.API()
|
||||
api.delete(self.nova_ctx, api.get(self.nova_ctx, self.instance_id,
|
||||
want_objects=True))
|
||||
|
||||
def set_lease_id(self, lease_id):
|
||||
self.lease_id = lease_id
|
||||
|
||||
def set_params(self, instance_id, blazar_cl, nova_ctx):
|
||||
self.instance_id = instance_id
|
||||
self.blazar_cl = blazar_cl
|
||||
self.nova_ctx = nova_ctx
|
||||
|
||||
|
||||
class Reservation(extensions.V21APIExtensionBase):
|
||||
"""Instance reservation system."""
|
||||
|
||||
name = "Reservation"
|
||||
alias = "os-instance-reservation"
|
||||
updated = "2016-11-30T00:00:00Z"
|
||||
version = 1
|
||||
|
||||
def get_controller_extensions(self):
|
||||
controller = ReservationController()
|
||||
extension = extensions.ControllerExtension(self, 'servers', controller)
|
||||
return [extension]
|
||||
|
||||
def get_resources(self):
|
||||
return []
|
|
@ -1,96 +0,0 @@
|
|||
# Copyright (c) 2013 Mirantis Inc.
|
||||
#
|
||||
# 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 datetime
|
||||
|
||||
import mock
|
||||
|
||||
from nova.api.openstack import compute
|
||||
from nova.compute import api as compute_api
|
||||
from nova import context
|
||||
from nova import test
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
from nova import utils
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
UUID = fakes.FAKE_UUID
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('reservation_start_date',
|
||||
'blazarnova.api.extensions.default_reservation')
|
||||
CONF.import_opt('reservation_length_hours',
|
||||
'blazarnova.api.extensions.default_reservation')
|
||||
CONF.import_opt('reservation_length_days',
|
||||
'blazarnova.api.extensions.default_reservation')
|
||||
CONF.import_opt('reservation_length_minutes',
|
||||
'blazarnova.api.extensions.default_reservation')
|
||||
|
||||
|
||||
class InstanceWrapper(object):
|
||||
# this wrapper is needed to make dict look like object with fields and
|
||||
# dictionary at the same time
|
||||
|
||||
def __init__(self, dct):
|
||||
self._dct = dct
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._dct.get(item)
|
||||
|
||||
__getitem__ = __getattr__
|
||||
|
||||
|
||||
class BaseExtensionTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Set up testing environment."""
|
||||
super(BaseExtensionTestCase, self).setUp()
|
||||
self.fake_instance = fakes.stub_instance(1, uuid=UUID)
|
||||
|
||||
self.lease_controller = mock.MagicMock()
|
||||
self.mock_client = mock.MagicMock()
|
||||
self.mock_client.lease = self.lease_controller
|
||||
|
||||
self.req = fakes.HTTPRequestV21.blank('/fake/servers')
|
||||
self.req.method = 'POST'
|
||||
self.req.content_type = 'application/json'
|
||||
self.req.environ.update({
|
||||
'HTTP_X_SERVICE_CATALOG': '[{"type": "reservation", '
|
||||
'"endpoints": [{"publicURL": "fake"}]}]',
|
||||
'HTTP_X_ROLES': '12,34',
|
||||
'HTTP_X_AUTH_TOKEN': 'fake_token',
|
||||
'nova.context': context.RequestContext('fake', 'fake'),
|
||||
})
|
||||
|
||||
self.l_name = 'lease_123'
|
||||
self.app = compute.APIRouterV21()
|
||||
self.stubs.Set(utils, 'generate_uid', lambda name, size: self.l_name)
|
||||
self.stubs.Set(compute_api.API, 'get', self._fake_get)
|
||||
self.stubs.Set(compute_api.API, 'shelve', self._fake_shelve)
|
||||
|
||||
delta_days = datetime.timedelta(days=CONF.reservation_length_days)
|
||||
delta_hours = datetime.timedelta(hours=CONF.reservation_length_hours)
|
||||
delta_minutes = datetime.timedelta(
|
||||
minutes=CONF.reservation_length_minutes)
|
||||
self.delta = delta_days + delta_hours + delta_minutes
|
||||
lease_end = datetime.datetime.utcnow() + self.delta
|
||||
self.default_lease_end = lease_end.strftime('%Y-%m-%d %H:%M')
|
||||
self.default_lease_start = 'now'
|
||||
|
||||
def _fake_get(self, *args, **kwargs):
|
||||
self.fake_instance['vm_state'] = 'active'
|
||||
return InstanceWrapper(self.fake_instance)
|
||||
|
||||
def _fake_shelve(self, *args):
|
||||
self.fake_instance['vm_state'] = 'shelved_offloaded'
|
|
@ -1,106 +0,0 @@
|
|||
# Copyright (c) 2013 Mirantis Inc.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from blazarnova.api.extensions import default_reservation
|
||||
from blazarnova.api.extensions import reservation
|
||||
from blazarnova.tests.api import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
|
||||
class BlazarDefaultReservationTestCase(extensions.BaseExtensionTestCase):
|
||||
"""Blazar API extensions test case.
|
||||
|
||||
This test case provides tests for Default_reservation extension working
|
||||
together with Reservation extension passing hints to Nova and
|
||||
sending lease creation request to Blazar.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up testing environment."""
|
||||
super(BlazarDefaultReservationTestCase, self).setUp()
|
||||
self.rsrv_controller = reservation.ReservationController()
|
||||
self.default_rsrv_controller = default_reservation\
|
||||
.DefaultReservationController()
|
||||
|
||||
@mock.patch('blazarnova.api.extensions.reservation.blazar_client')
|
||||
def test_create_with_default(self, mock_module):
|
||||
"""Test extension work with default lease parameters."""
|
||||
|
||||
mock_module.Client.return_value = self.mock_client
|
||||
|
||||
# here we set no Blazar related hints, so all lease info should be
|
||||
# default one - dates got from CONF and auto generated name
|
||||
body = {
|
||||
'server': {
|
||||
'name': 'server_test',
|
||||
'imageRef': 'cedef40a-ed67-4d10-800e-17455edce175',
|
||||
'flavorRef': '1',
|
||||
}
|
||||
}
|
||||
|
||||
self.req.body = jsonutils.dumps(body)
|
||||
self.default_rsrv_controller.create(self.req, body)
|
||||
resp_obj = wsgi.ResponseObject({'server': {'id': 'fakeId'}})
|
||||
self.rsrv_controller.create(self.req, resp_obj, body)
|
||||
|
||||
mock_module.Client.assert_called_once_with(blazar_url='fake',
|
||||
auth_token='fake_token')
|
||||
|
||||
self.lease_controller.create.assert_called_once_with(
|
||||
reservations=[
|
||||
{'resource_type': 'virtual:instance',
|
||||
'resource_id': 'fakeId'}],
|
||||
end=self.default_lease_end,
|
||||
events=[],
|
||||
start='now',
|
||||
name=self.l_name)
|
||||
|
||||
@mock.patch('blazarnova.api.extensions.reservation.blazar_client')
|
||||
def test_create_with_passed_args(self, mock_module):
|
||||
"""Test extension work if some lease param would be passed."""
|
||||
# here we pass non default lease name to Nova
|
||||
# Default_reservation extension should not rewrite it,
|
||||
# Reservation extension should pass it to Blazar
|
||||
|
||||
mock_module.Client.return_value = self.mock_client
|
||||
|
||||
body = {
|
||||
'server': {
|
||||
'name': 'server_test',
|
||||
'imageRef': 'cedef40a-ed67-4d10-800e-17455edce175',
|
||||
'flavorRef': '1',
|
||||
},
|
||||
'os:scheduler_hints': {'lease_params': '{"name": "other_name"}'},
|
||||
}
|
||||
|
||||
self.req.body = jsonutils.dumps(body)
|
||||
self.default_rsrv_controller.create(self.req, body)
|
||||
resp_obj = wsgi.ResponseObject({'server': {'id': 'fakeId'}})
|
||||
self.rsrv_controller.create(self.req, resp_obj, body)
|
||||
|
||||
mock_module.Client.assert_called_once_with(blazar_url='fake',
|
||||
auth_token='fake_token')
|
||||
|
||||
self.lease_controller.create.assert_called_once_with(
|
||||
reservations=[
|
||||
{'resource_type': 'virtual:instance',
|
||||
'resource_id': 'fakeId'}],
|
||||
end=self.default_lease_end,
|
||||
events=[],
|
||||
start='now',
|
||||
name='other_name')
|
|
@ -1,69 +0,0 @@
|
|||
# Copyright (c) 2013 Mirantis Inc.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from blazarnova.api.extensions import reservation
|
||||
from blazarnova.tests.api import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
|
||||
class BlazarReservationTestCase(extensions.BaseExtensionTestCase):
|
||||
"""Blazar API extensions test case.
|
||||
|
||||
This test case provides tests for Reservation extension working
|
||||
together with Reservation extension passing hints to Nova and
|
||||
sending lease creation request to Blazar.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up testing environment."""
|
||||
super(BlazarReservationTestCase, self).setUp()
|
||||
self.controller = reservation.ReservationController()
|
||||
|
||||
@mock.patch('blazarnova.api.extensions.reservation.blazar_client')
|
||||
def test_create(self, mock_module):
|
||||
"""Test extension work with passed lease parameters."""
|
||||
|
||||
mock_module.Client.return_value = self.mock_client
|
||||
|
||||
body = {
|
||||
'server': {
|
||||
'name': 'server_test',
|
||||
'imageRef': 'cedef40a-ed67-4d10-800e-17455edce175',
|
||||
'flavorRef': '1',
|
||||
},
|
||||
'os:scheduler_hints': {
|
||||
'lease_params': '{"name": "some_name", '
|
||||
'"start": "2014-02-09 12:00", '
|
||||
'"end": "2014-02-10 12:00"}'}
|
||||
}
|
||||
|
||||
self.req.body = jsonutils.dumps(body)
|
||||
resp_obj = wsgi.ResponseObject({'server': {'id': 'fakeId'}})
|
||||
self.controller.create(self.req, resp_obj, body)
|
||||
|
||||
mock_module.Client.assert_called_once_with(blazar_url='fake',
|
||||
auth_token='fake_token')
|
||||
|
||||
self.lease_controller.create.assert_called_once_with(
|
||||
reservations=[
|
||||
{'resource_type': 'virtual:instance',
|
||||
'resource_id': 'fakeId'}],
|
||||
end='2014-02-10 12:00',
|
||||
events=[],
|
||||
start='2014-02-09 12:00',
|
||||
name='some_name')
|
|
@ -1,5 +1,3 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
kombu!=4.0.2,>=4.0.0 # BSD
|
||||
|
||||
|
|
Loading…
Reference in New Issue