Remove the old instance reservation plugin

The old instance reservation feature is not supported from the
Ocata release of Blazar, since it relies on the v2 API of Nova
with has been removed. Additionally, the Blazar project has
started to implement a new instance reservation feature which
doesn't rely on deprecated Nova APIs.

This patch removes the old instance reservation plugin. If a
deployer specifies 'basic.vm.plugin' in the plugins config
parameter, the blazar-manager service will fail to start.

Partially implements: blueprint new-instance-reservation

Change-Id: If5b0efb4910b05cc3c2881bb705953528895f277
This commit is contained in:
Masahito Muroi 2017-06-07 17:07:44 +09:00 committed by Pierre Riteau
parent ad40cd1e28
commit 31f88379b8
12 changed files with 35 additions and 289 deletions

View File

@ -78,7 +78,7 @@ class Lease(base._Base):
project_id=u'bd9431c18d694ad3803a8d4a6b89fd36',
trust_id=u'35b17138b3644e6aa1318f3099c5be68',
reservations=[{u'resource_id': u'1234',
u'resource_type': u'virtual:instance'}],
u'resource_type': u'physical:host'}],
events=[],
before_end_notification=u'2014-02-01 10:37',
action=u'START',

View File

@ -78,6 +78,13 @@ class ManagerService(service_utils.RPCServer):
invoke_on_load=False
)
invalid_plugins = (set(config_plugins) -
set([ext.name for ext
in extension_manager.extensions]))
if invalid_plugins:
raise common_ex.BlazarException('Invalid plugin names are '
'specified: %s' % invalid_plugins)
for ext in extension_manager.extensions:
try:
plugin_obj = ext.plugin()

View File

@ -22,7 +22,6 @@ import blazar.db.migration.cli
import blazar.manager
import blazar.manager.service
import blazar.notification.notifier
import blazar.plugins.instances.vm_plugin
import blazar.plugins.oshosts.host_plugin
import blazar.utils.openstack.keystone
import blazar.utils.openstack.nova
@ -46,8 +45,6 @@ def list_opts():
blazar.manager.service.manager_opts)),
('notifications', blazar.notification.notifier.notification_opts),
('nova', blazar.utils.openstack.nova.nova_opts),
(blazar.plugins.instances.RESOURCE_TYPE,
blazar.plugins.instances.vm_plugin.plugin_opts),
(blazar.plugins.oshosts.RESOURCE_TYPE,
blazar.plugins.oshosts.host_plugin.plugin_opts),
]

View File

@ -1 +0,0 @@
RESOURCE_TYPE = u'virtual:instance'

View File

@ -1,117 +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 eventlet
from novaclient import exceptions as nova_exceptions
from oslo_config import cfg
from oslo_log import log as logging
from blazar import exceptions as blazar_exceptions
from blazar.plugins import base
from blazar.plugins import instances as plugin
from blazar.utils.openstack import nova
LOG = logging.getLogger(__name__)
plugin_opts = [
cfg.StrOpt('on_end',
default='create_image, delete',
help='Actions which we will use in the end of the lease'),
cfg.StrOpt('on_start',
default='on_start',
help='Actions which we will use at the start of the lease'),
]
CONF = cfg.CONF
CONF.register_opts(plugin_opts, group=plugin.RESOURCE_TYPE)
class VMPlugin(base.BasePlugin, nova.NovaClientWrapper):
"""Base plugin for VM reservation."""
resource_type = plugin.RESOURCE_TYPE
title = "Basic VM Plugin"
description = ("This is basic plugin for VM management. "
"It can start, snapshot and suspend VMs")
def reserve_resource(self, reservation_id, values):
return None
def on_start(self, resource_id):
try:
self.nova.servers.unshelve(resource_id)
except nova_exceptions.Conflict:
LOG.error("Instance have been unshelved")
def on_end(self, resource_id):
actions = self._split_actions(CONF[plugin.RESOURCE_TYPE].on_end)
# actions will be processed in following order:
# - create image from VM
# - suspend VM
# - delete VM
# this order guarantees there will be no situations like
# creating snapshot or suspending already deleted instance
if 'create_image' in actions:
with eventlet.timeout.Timeout(600, blazar_exceptions.Timeout):
try:
self.nova.servers.create_image(resource_id)
eventlet.sleep(5)
while not self._check_active(resource_id):
eventlet.sleep(1)
except nova_exceptions.NotFound:
LOG.error('Instance %s has been already deleted. '
'Cannot create image.' % resource_id)
except blazar_exceptions.Timeout:
LOG.error('Image create failed with timeout. Take a look '
'at nova.')
except nova_exceptions.Conflict as e:
LOG.warning('Instance is in a invalid state for'
'create_image. Take a look at nova.'
'(Request-ID: %s)' % e.request_id)
if 'suspend' in actions:
try:
self.nova.servers.suspend(resource_id)
except nova_exceptions.NotFound:
LOG.error('Instance %s has been already deleted. '
'Cannot suspend instance.' % resource_id)
if 'delete' in actions:
try:
self.nova.servers.delete(resource_id)
except nova_exceptions.NotFound:
LOG.error('Instance %s has been already deleted. '
'Cannot delete instance.' % resource_id)
def _check_active(self, resource_id):
instance = self.nova.servers.get(resource_id)
task_state = getattr(instance, 'OS-EXT-STS:task_state', None)
if task_state is None:
return True
if task_state.upper() in ['IMAGE_SNAPSHOT', 'IMAGE_PENDING_UPLOAD',
'IMAGE_UPLOADING']:
return False
else:
LOG.error('Nova reported unexpected task status %s for '
'instance %s' % (task_state, resource_id))
raise blazar_exceptions.TaskFailed()
def _split_actions(self, actions):
try:
return actions.replace(' ', '').split(',')
except AttributeError:
raise blazar_exceptions.WrongFormat()

View File

@ -20,7 +20,6 @@ import uuid
from blazar.db import exceptions as db_exceptions
from blazar.db.sqlalchemy import api as db_api
from blazar.db.sqlalchemy import models
from blazar.plugins import instances as vm_plugin
from blazar.plugins import oshosts as host_plugin
from blazar import tests
@ -47,13 +46,6 @@ def _get_fake_phys_reservation_values(id=_get_fake_random_uuid(),
'trust_id': 'exxee111qwwwwe'}
def _get_fake_virt_reservation_values(lease_id=_get_fake_lease_uuid(),
resource_id=None):
return {'lease_id': lease_id,
'resource_id': '5678' if not resource_id else resource_id,
'resource_type': vm_plugin.RESOURCE_TYPE}
def _get_fake_event_values(id=_get_fake_random_uuid(),
lease_id=_get_fake_lease_uuid(),
event_type='fake_event_type',
@ -70,25 +62,6 @@ def _get_datetime(value='2030-01-01 00:00'):
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M')
def _get_fake_virt_lease_values(id=_get_fake_lease_uuid(),
name='fake_virt_lease',
start_date=_get_datetime('2030-01-01 00:00'),
end_date=_get_datetime('2030-01-02 00:00'),
resource_id=None):
return {'id': id,
'name': name,
'user_id': _get_fake_random_uuid(),
'project_id': _get_fake_random_uuid(),
'start_date': start_date,
'end_date': end_date,
'trust': 'trust',
'reservations': [_get_fake_virt_reservation_values(
lease_id=id,
resource_id=resource_id)],
'events': []
}
def _get_fake_phys_lease_values(id=_get_fake_lease_uuid(),
name='fake_phys_lease',
start_date=_get_datetime('2030-01-01 00:00'),
@ -121,12 +94,6 @@ def _get_fake_host_allocation_values(
return values
def _create_virtual_lease(values=_get_fake_virt_lease_values(),
random=False):
"""Creating fake lease having a single virtual resource."""
return db_api.lease_create(values)
def _create_physical_lease(values=_get_fake_phys_lease_values(),
random=False):
"""Creating fake lease having a single physical resource."""
@ -208,23 +175,10 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
super(SQLAlchemyDBApiTestCase, self).setUp()
def test_model_query(self):
lease = db_api.lease_create(_get_fake_virt_lease_values())
lease = db_api.lease_create(_get_fake_phys_lease_values())
query = db_api.model_query(models.Lease)
self.assertEqual([lease.to_dict()], [l.to_dict() for l in query.all()])
def test_create_virt_lease(self):
"""Check virtual lease create
Create a virtual lease and verify that all tables have been
populated.
"""
result = db_api.lease_create(_get_fake_virt_lease_values())
self.assertEqual(result['name'],
_get_fake_virt_lease_values()['name'])
self.assertEqual(0, len(db_api.event_get_all()))
self.assertEqual(1, len(db_api.reservation_get_all()))
def test_create_phys_lease(self):
"""Check physical lease create
@ -373,12 +327,14 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
Create two reservations and verify that we can find reservation per
resource_id or resource_type.
"""
db_api.reservation_create(_get_fake_phys_reservation_values())
db_api.reservation_create(_get_fake_virt_reservation_values())
db_api.reservation_create(
_get_fake_phys_reservation_values(id='1', resource_id='1234'))
db_api.reservation_create(
_get_fake_phys_reservation_values(id='2', resource_id='5678'))
self.assertEqual(2, len(db_api.reservation_get_all_by_values()))
self.assertEqual(1, len(db_api.reservation_get_all_by_values(
resource_id='5678')))
self.assertEqual(1, len(db_api.reservation_get_all_by_values(
self.assertEqual(2, len(db_api.reservation_get_all_by_values(
resource_type=host_plugin.RESOURCE_TYPE)))
def test_reservation_update(self):

View File

@ -96,9 +96,13 @@ class ServiceTestCase(tests.TestCase):
'PhysicalHostPlugin')
self.ext_manager = self.patch(self.enabled, 'EnabledExtensionManager')
self.ext_manager.return_value.extensions = [
FakeExtension('dummy.vm.plugin', FakePlugin),
]
self.fake_notifier = self.patch(self.notifier_api,
'send_lease_notification')
cfg.CONF.set_override('plugins', ['dummy.vm.plugin'], group='manager')
self.manager = self.service.ManagerService()
self.lease_id = '11-22-33'
@ -150,8 +154,8 @@ class ServiceTestCase(tests.TestCase):
pass
def test_multiple_plugins_same_resource_type(self):
config = self.patch(cfg, "CONF")
config.manager.plugins = ['fake.plugin.1', 'fake.plugin.2']
config = self.patch(cfg.CONF, "manager")
config.plugins = ['fake.plugin.1', 'fake.plugin.2']
self.ext_manager.return_value.extensions = [
FakeExtension("fake.plugin.1", FakePlugin),
FakeExtension("fake.plugin.2", FakePlugin)]
@ -160,8 +164,8 @@ class ServiceTestCase(tests.TestCase):
self.manager._get_plugins)
def test_plugins_that_fail_to_init(self):
config = self.patch(cfg, "CONF")
config.manager.plugins = ['fake.plugin.1', 'fake.plugin.2']
config = self.patch(cfg.CONF, "manager")
config.plugins = ['fake.plugin.1', 'fake.plugin.2']
self.ext_manager.return_value.extensions = [
FakeExtension("fake.plugin.1", FakePlugin),
FakeExtension("fake.plugin.2", FakePluginRaisesException)]
@ -171,10 +175,11 @@ class ServiceTestCase(tests.TestCase):
self.assertNotIn("fake:plugin:raise", plugins)
def test_get_bad_config_plugins(self):
config = self.patch(cfg, "CONF")
config.manager.plugins = ['foo.plugin']
config = self.patch(cfg.CONF, "manager")
config.plugins = ['foo.plugin']
self.assertEqual({}, self.manager._get_plugins())
self.assertRaises(exceptions.BlazarException,
self.manager._get_plugins)
def test_setup_actions(self):
actions = {'virtual:instance':

View File

@ -1,108 +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 sys
import eventlet
from novaclient import exceptions as nova_exceptions
from oslo_log import log as logging
import testtools
from blazar import exceptions as blazar_exceptions
from blazar.plugins.instances import vm_plugin
from blazar import tests
from blazar.utils.openstack import nova
class VMPluginTestCase(tests.TestCase):
def setUp(self):
super(VMPluginTestCase, self).setUp()
self.nova = nova
self.exc = blazar_exceptions
self.logging = logging
self.sys = sys
# To speed up the test run
self.eventlet = eventlet
self.eventlet_sleep = self.patch(self.eventlet, 'sleep')
self.fake_id = '1'
self.nova_wrapper = self.patch(self.nova.NovaClientWrapper, 'nova')
self.plugin = vm_plugin.VMPlugin()
def test_on_start_ok(self):
self.plugin.on_start(self.fake_id)
self.nova_wrapper.servers.unshelve.assert_called_once_with('1')
@testtools.skip('Will be released later')
def test_on_start_fail(self):
def raise_exception(resource_id):
raise blazar_exceptions.Conflict(409)
self.nova_wrapper.servers.unshelve.side_effect = raise_exception
self.plugin.on_start(self.fake_id)
def test_on_end_create_image_ok(self):
self.patch(self.plugin, '_split_actions').return_value = (
['create_image'])
self.patch(self.plugin, '_check_active').return_value = True
self.plugin.on_end(self.fake_id)
self.nova_wrapper.servers.create_image.assert_called_once_with('1')
def test_on_end_suspend_ok(self):
self.patch(self.plugin, '_split_actions').return_value = ['suspend']
self.plugin.on_end(self.fake_id)
self.nova_wrapper.servers.suspend.assert_called_once_with('1')
def test_on_end_delete_ok(self):
self.patch(self.plugin, '_split_actions').return_value = ['delete']
self.plugin.on_end(self.fake_id)
self.nova_wrapper.servers.delete.assert_called_once_with('1')
def test_on_end_create_image_instance_or_not_found(self):
def raise_exception(resource_id):
raise nova_exceptions.NotFound(404)
self.nova_wrapper.servers.create_image.side_effect = raise_exception
self.plugin.on_end(self.fake_id)
self.nova_wrapper.servers.delete.assert_called_once_with('1')
def test_on_end_create_image_ko_invalid_vm_state(self):
def raise_exception(resource_id):
raise nova_exceptions.Conflict(409)
self.nova_wrapper.servers.create_image.side_effect = raise_exception
self.plugin.on_end(self.fake_id)
self.nova_wrapper.servers.delete.assert_called_once_with('1')
@testtools.skip('Will be released later')
def test_on_end_timeout(self):
self.patch(self.plugin, '_split_actions').return_value = (
['create_image'])
self.assertRaises(self.exc.Timeout,
self.plugin.on_end,
self.fake_id)
@testtools.skip('Will be released later')
def test_check_active(self):
pass

View File

@ -60,7 +60,7 @@ function configure_blazar {
iniset $BLAZAR_CONF_FILE DEFAULT debug $BLAZAR_DEBUG
iniset $BLAZAR_CONF_FILE DEFAULT verbose $BLAZAR_VERBOSE
iniset $BLAZAR_CONF_FILE manager plugins basic.vm.plugin,physical.host.plugin
iniset $BLAZAR_CONF_FILE manager plugins physical.host.plugin
iniset $BLAZAR_CONF_FILE api api_v2_controllers oshosts,leases

View File

@ -0,0 +1,8 @@
---
upgrade:
- |
The previous instance reservation plugin, named 'basic.vm.plugin' in
blazar.conf, is completely removed from Blazar. Deployments including
this plugin in blazar.conf must be updated by removing it from the
plugins list. Otherwise, the blazar-manager service will fail to start
with an exception about invalid plugin names.

View File

@ -41,7 +41,6 @@ console_scripts =
blazar.resource.plugins =
dummy.vm.plugin=blazar.plugins.dummy_vm_plugin:DummyVMPlugin
physical.host.plugin=blazar.plugins.oshosts.host_plugin:PhysicalHostPlugin
basic.vm.plugin=blazar.plugins.instances.vm_plugin:VMPlugin
# Remove this alias when the deprecation period of "climate" is over
climate.api.v2.controllers.extensions =