Remove old instance reservation code

Change-Id: Ie95c814268cb46f81ee120acb749748c0b309a1f
This commit is contained in:
Hiroaki Kobayashi 2017-06-08 16:19:11 +09:00
parent 9b7362b341
commit fa5192b20f
10 changed files with 2 additions and 590 deletions

View File

@ -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.

View File

@ -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]

View File

@ -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 []

View File

@ -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'

View File

@ -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')

View File

@ -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')

View File

@ -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