Support running actions before the end of a lease

This patch adds a before_end() method to the resource plugin. It is
called from the manager at the before_end_lease event. An actual action
is configurable for each resource plugin. The physical host plugin
currently supports 'snapshot'.

This patch also renames the API parameter 'before_end_notification'
'before_end_date' because actions other than notification get to use
this parameter.

Change-Id: Ifdb42e431f2b5134ed8a720ae040486aee2e4acc
Implements: blueprint on-end-options
This commit is contained in:
Hiroaki Kobayashi 2017-05-29 16:34:42 +09:00 committed by Pierre Riteau
parent 2ea4f6e8ef
commit a5fb773bcd
10 changed files with 130 additions and 60 deletions

View File

@ -56,8 +56,8 @@ class Lease(base._Base):
events = wtypes.ArrayType(wtypes.DictType(wtypes.text, wtypes.text))
"The list of events attached to the lease"
before_end_notification = types.Datetime(service.LEASE_DATE_FORMAT)
"Datetime when notifications will be sent before lease ending"
before_end_date = types.Datetime(service.LEASE_DATE_FORMAT)
"Datetime when some actions will be taken before lease ending"
action = wtypes.text
"The current action running"
@ -80,7 +80,7 @@ class Lease(base._Base):
reservations=[{u'resource_id': u'1234',
u'resource_type': u'physical:host'}],
events=[],
before_end_notification=u'2014-02-01 10:37',
before_end_date=u'2014-02-01 10:37',
action=u'START',
status=u'COMPLETE',
status_reason=u'Lease currently running',
@ -140,8 +140,8 @@ class LeasesController(extensions.BaseController):
new_name = sublease_dct.pop('name', None)
end_date = sublease_dct.pop('end_date', None)
start_date = sublease_dct.pop('start_date', None)
before_end_notification = sublease_dct.pop('before_end_notification',
None)
before_end_date = sublease_dct.pop('before_end_date',
None)
if sublease_dct != {}:
raise exceptions.BlazarException('Only name changing, '
@ -154,8 +154,8 @@ class LeasesController(extensions.BaseController):
sublease_dct['end_date'] = end_date
if start_date:
sublease_dct['start_date'] = start_date
if before_end_notification:
sublease_dct['before_end_notification'] = before_end_notification
if before_end_date:
sublease_dct['before_end_date'] = before_end_date
lease = pecan.request.rpcapi.update_lease(id, sublease_dct)

View File

@ -35,12 +35,11 @@ manager_opts = [
default=['dummy.vm.plugin'],
help='All plugins to use (one for every resource type to '
'support.)'),
cfg.IntOpt('notify_hours_before_lease_end',
default=48,
help='Number of hours prior to lease end in which a '
'notification of lease close to expire will be sent. If '
'this is set to 0, then this notification will '
'not be sent.')
cfg.IntOpt('minutes_before_end_lease',
default=60,
help='Minutes prior to the end of a lease in which actions '
'like notification and snapshot are taken. If this is '
'set to 0, then these actions are not taken.')
]
CONF = cfg.CONF
@ -119,6 +118,7 @@ class ManagerService(service_utils.RPCServer):
actions[resource_type] = {}
actions[resource_type]['on_start'] = plugin.on_start
actions[resource_type]['on_end'] = plugin.on_end
actions[resource_type]['before_end'] = plugin.before_end
plugin.setup(None)
return actions
@ -220,7 +220,7 @@ class ManagerService(service_utils.RPCServer):
'time': end_date,
'status': 'UNDONE'})
before_end_date = lease_values.get('before_end_notification', None)
before_end_date = lease_values.get('before_end_date', None)
if before_end_date:
# incoming param. Validation check
try:
@ -231,9 +231,9 @@ class ManagerService(service_utils.RPCServer):
except common_ex.BlazarException as e:
LOG.error("Invalid before_end_date param. %s" % e.message)
raise e
elif CONF.manager.notify_hours_before_lease_end > 0:
elif CONF.manager.minutes_before_end_lease > 0:
delta = datetime.timedelta(
hours=CONF.manager.notify_hours_before_lease_end)
minutes=CONF.manager.minutes_before_end_lease)
before_end_date = lease_values['end_date'] - delta
if before_end_date:
@ -302,7 +302,7 @@ class ManagerService(service_utils.RPCServer):
end_date = values.get(
'end_date',
datetime.datetime.strftime(lease['end_date'], LEASE_DATE_FORMAT))
before_end_date = values.get('before_end_notification', None)
before_end_date = values.get('before_end_date', None)
now = datetime.datetime.utcnow()
now = datetime.datetime(now.year,
@ -453,7 +453,9 @@ class ManagerService(service_utils.RPCServer):
self._basic_action(lease_id, event_id, 'on_end', 'deleted')
def before_end_lease(self, lease_id, event_id):
db_api.event_update(event_id, {'status': 'DONE'})
lease = self.get_lease(lease_id)
with trusts.create_ctx_from_trust(lease['trust_id']):
self._basic_action(lease_id, event_id, 'before_end')
def _basic_action(self, lease_id, event_id, action_time,
reservation_status=None):
@ -516,7 +518,7 @@ class ManagerService(service_utils.RPCServer):
def _update_before_end_event_date(self, event, before_end_date, lease):
event['time'] = before_end_date
if event['time'] < lease['start_date']:
LOG.warning("New start_date greater than before_end_date. "
LOG.warning("Start_date greater than before_end_date. "
"Setting before_end_date to %s for lease %s"
% (lease['start_date'], lease.get('id',
lease.get('name'))))

View File

@ -80,3 +80,7 @@ class BasePlugin(object):
def on_start(self, resource_id):
"""Wake up resource."""
pass
def before_end(self, resource_id):
"""Take actions before the end of a lease"""
pass

View File

@ -43,7 +43,11 @@ plugin_opts = [
cfg.StrOpt('blazar_az_prefix',
default='blazar_',
deprecated_name='climate_az_prefix',
help='Prefix for Availability Zones created by Blazar')
help='Prefix for Availability Zones created by Blazar'),
cfg.StrOpt('before_end',
default='',
help='Actions which we will be taken before the end of '
'the lease')
]
CONF = cfg.CONF
@ -173,6 +177,19 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
pool.add_computehost(host_reservation['aggregate_id'],
host['service_name'])
def before_end(self, resource_id):
"""Take an action before the end of a lease."""
action = CONF[plugin.RESOURCE_TYPE].before_end
if action == 'snapshot':
host_reservation = db_api.host_reservation_get(resource_id)
pool = nova.ReservationPool()
client = nova.BlazarNovaClient()
for host in pool.get_computehosts(
host_reservation['aggregate_id']):
for server in client.servers.list(
search_opts={"host": host, "all_tenants": 1}):
client.servers.create_image(server=server)
def on_end(self, resource_id):
"""Remove the hosts from the pool."""
host_reservation = db_api.host_reservation_get(resource_id)

View File

@ -23,7 +23,7 @@ lease_data = {'id': '1',
'reservations': [{u'resource_id': u'1234',
u'resource_type': u'virtual:instance'}],
'events': [],
'before_end_notification': u'2014-02-01 10:37',
'before_end_date': u'2014-02-01 10:37',
'action': None,
'status': None,
'status_reason': None}

View File

@ -154,7 +154,7 @@ class ServiceTestCase(tests.TestCase):
self.base_utils, 'url_for').return_value = 'http://www.foo.fake'
self.addCleanup(self.cfg.CONF.clear_override,
'notify_hours_before_lease_end',
'minutes_before_end_lease',
group='manager')
def tearDown(self):
@ -196,7 +196,8 @@ class ServiceTestCase(tests.TestCase):
def test_setup_actions(self):
actions = {'virtual:instance':
{'on_start': self.fake_plugin.on_start,
'on_end': self.fake_plugin.on_end}}
'on_end': self.fake_plugin.on_end,
'before_end': self.fake_plugin.before_end}}
self.assertEqual(actions, self.manager._setup_actions())
def test_no_events(self):
@ -320,7 +321,7 @@ class ServiceTestCase(tests.TestCase):
self.lease['end_date'] = '2026-12-13 13:13:00'
self.lease['events'][0]['time'] = '2026-11-13 13:13:00'
self.lease['events'][1]['time'] = '2026-12-13 13:13:00'
self.lease['events'][2]['time'] = '2026-12-11 13:13:00'
self.lease['events'][2]['time'] = '2026-12-13 12:13:00'
lease = self.manager.create_lease(lease_values)
@ -344,8 +345,8 @@ class ServiceTestCase(tests.TestCase):
event = lease['events'][2]
self.assertEqual('before_end_lease', event['event_type'])
delta = datetime.timedelta(
hours=self.cfg.CONF.manager.notify_hours_before_lease_end)
self.assertEqual(str(datetime.datetime(2026, 12, 13, 13, 13) - delta),
minutes=self.cfg.CONF.manager.minutes_before_end_lease)
self.assertEqual(str(lease_values['end_date'] - delta),
event['time'])
self.assertEqual('UNDONE', event['status'])
@ -357,7 +358,7 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'virtual:instance',
'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13',
'end_date': '2026-11-13 14:13',
'trust_id': 'exxee111qwwwwe'}
self.lease['start_date'] = '2026-11-13 13:13:00'
self.lease['end_date'] = '2026-12-13 13:13:00'
@ -365,7 +366,7 @@ class ServiceTestCase(tests.TestCase):
self.lease['events'][1]['time'] = '2026-12-13 13:13:00'
self.lease['events'][2]['time'] = '2026-11-13 13:13:00'
self.cfg.CONF.set_override('notify_hours_before_lease_end', 36,
self.cfg.CONF.set_override('minutes_before_end_lease', 120,
group='manager')
lease = self.manager.create_lease(lease_values)
@ -399,11 +400,11 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'virtual:instance',
'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13',
'end_date': '2026-11-13 14:13',
'trust_id': 'exxee111qwwwwe'}
self.lease['start_date'] = '2026-11-13 13:13'
self.cfg.CONF.set_override('notify_hours_before_lease_end', 36,
self.cfg.CONF.set_override('minutes_before_end_lease', 120,
group='manager')
lease = self.manager.create_lease(lease_values)
@ -413,7 +414,7 @@ class ServiceTestCase(tests.TestCase):
self.assertEqual(3, len(lease['events']))
def test_create_lease_before_end_param_is_before_lease_start(self):
before_end_notification = '2026-11-11 13:13'
before_end_date = '2026-11-11 13:13'
start_date = '2026-11-13 13:13'
lease_values = {
'id': self.lease_id,
@ -424,14 +425,14 @@ class ServiceTestCase(tests.TestCase):
'start_date': start_date,
'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe',
'before_end_notification': before_end_notification}
'before_end_date': before_end_date}
self.lease['start_date'] = '2026-11-13 13:13'
self.assertRaises(
exceptions.NotAuthorized, self.manager.create_lease, lease_values)
def test_create_lease_before_end_param_is_past_lease_ending(self):
before_end_notification = '2026-11-15 13:13'
before_end_date = '2026-11-15 13:13'
lease_values = {
'id': self.lease_id,
'reservations': [{'id': '111',
@ -441,7 +442,7 @@ class ServiceTestCase(tests.TestCase):
'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe',
'before_end_notification': before_end_notification}
'before_end_date': before_end_date}
self.lease['start_date'] = '2026-11-13 13:13'
self.assertRaises(
@ -463,7 +464,7 @@ class ServiceTestCase(tests.TestCase):
self.lease['events'][1]['time'] = '2026-11-14 13:13:00'
self.lease['events'].pop()
self.cfg.CONF.set_override('notify_hours_before_lease_end', 0,
self.cfg.CONF.set_override('minutes_before_end_lease', 0,
group='manager')
lease = self.manager.create_lease(lease_values)
@ -484,8 +485,8 @@ class ServiceTestCase(tests.TestCase):
self.assertEqual(lease['end_date'], event['time'])
self.assertEqual('UNDONE', event['status'])
def test_create_lease_with_before_end_notification_param(self):
before_end_notification = '2026-11-14 10:13'
def test_create_lease_with_before_end_date_param(self):
before_end_date = '2026-11-14 10:13'
lease_values = {
'id': self.lease_id,
'reservations': [{'id': '111',
@ -495,7 +496,7 @@ class ServiceTestCase(tests.TestCase):
'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe',
'before_end_notification': before_end_notification}
'before_end_date': before_end_date}
self.lease['start_date'] = '2026-11-13 13:13:00'
self.lease['end_date'] = '2026-11-14 13:13:00'
self.lease['events'][0]['time'] = '2026-11-13 13:13:00'
@ -524,7 +525,7 @@ class ServiceTestCase(tests.TestCase):
event = lease['events'][2]
self.assertEqual('before_end_lease', event['event_type'])
expected_before_end_time = datetime.datetime.strptime(
before_end_notification, service.LEASE_DATE_FORMAT)
before_end_date, service.LEASE_DATE_FORMAT)
self.assertEqual(str(expected_before_end_time), event['time'])
self.assertEqual('UNDONE', event['status'])
@ -537,10 +538,10 @@ class ServiceTestCase(tests.TestCase):
manager_ex.InvalidDate, self.manager.create_lease, lease_values)
def test_create_lease_wrong_format_before_end_date(self):
before_end_notification = '2026-14 10:13'
before_end_date = '2026-14 10:13'
lease_values = {'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13',
'before_end_notification': before_end_notification,
'before_end_date': before_end_date,
'trust_id': 'exxee111qwwwwe'}
self.assertRaises(
@ -771,7 +772,7 @@ class ServiceTestCase(tests.TestCase):
self.lease_update.assert_called_once_with(self.lease_id, lease_values)
def test_update_lease_started_modify_before_end_with_param(self):
before_end_notification = '2013-12-20 14:00'
before_end_date = '2013-12-20 14:00'
def fake_event_get(sort_key, sort_dir, filters):
if filters['event_type'] == 'start_lease':
@ -780,13 +781,13 @@ class ServiceTestCase(tests.TestCase):
return {'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}
elif filters['event_type'] == 'before_end_lease':
return {'id': u'452bf850-e223-4035-9d13-eb0b0197228f',
'time': before_end_notification,
'time': before_end_date,
'status': 'DONE'}
lease_values = {
'name': 'renamed',
'end_date': '2013-12-20 16:00',
'before_end_notification': before_end_notification
'before_end_date': before_end_date
}
reservation_get_all = (
self.patch(self.db_api, 'reservation_get_all_by_lease_id'))
@ -831,7 +832,7 @@ class ServiceTestCase(tests.TestCase):
{'time': datetime.datetime(2013, 12, 20, 16, 00)}),
mock.call('452bf850-e223-4035-9d13-eb0b0197228f',
{'time': datetime.datetime.strptime(
before_end_notification,
before_end_date,
service.LEASE_DATE_FORMAT),
'status': 'UNDONE'})
]
@ -840,7 +841,7 @@ class ServiceTestCase(tests.TestCase):
def test_update_lease_started_before_end_lower_date_than_start(self):
expected_start_date = datetime.datetime(2013, 12, 20, 13, 00)
before_end_notification = datetime.datetime.strftime(
before_end_date = datetime.datetime.strftime(
(expected_start_date - datetime.timedelta(hours=1)),
service.LEASE_DATE_FORMAT)
@ -851,13 +852,13 @@ class ServiceTestCase(tests.TestCase):
return {'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}
elif filters['event_type'] == 'before_end_lease':
return {'id': u'452bf850-e223-4035-9d13-eb0b0197228f',
'time': before_end_notification,
'time': before_end_date,
'status': 'DONE'}
lease_values = {
'name': 'renamed',
'end_date': '2013-12-20 16:00',
'before_end_notification': before_end_notification
'before_end_date': before_end_date
}
reservation_get_all = (
self.patch(self.db_api, 'reservation_get_all_by_lease_id'))
@ -882,7 +883,7 @@ class ServiceTestCase(tests.TestCase):
def test_update_lease_started_modify_before_end_with_invalid_date(self):
# before_end_date is greater than current end_date
before_end_notification = '2013-12-21 14:00'
before_end_date = '2013-12-21 14:00'
def fake_event_get(sort_key, sort_dir, filters):
if filters['event_type'] == 'start_lease':
@ -891,13 +892,13 @@ class ServiceTestCase(tests.TestCase):
return {'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}
elif filters['event_type'] == 'before_end_lease':
return {'id': u'452bf850-e223-4035-9d13-eb0b0197228f',
'time': before_end_notification,
'time': before_end_date,
'status': 'DONE'}
lease_values = {
'name': 'renamed',
'end_date': '2013-12-20 16:00',
'before_end_notification': before_end_notification
'before_end_date': before_end_date
}
reservation_get_all = (
self.patch(self.db_api, 'reservation_get_all_by_lease_id'))
@ -921,7 +922,7 @@ class ServiceTestCase(tests.TestCase):
self.lease_id, lease_values)
def test_update_lease_started_modify_before_end_with_wrong_format(self):
wrong_before_end_notification = '12-21 14:00'
wrong_before_end_date = '12-21 14:00'
def fake_event_get(sort_key, sort_dir, filters):
if filters['event_type'] == 'start_lease':
@ -930,13 +931,13 @@ class ServiceTestCase(tests.TestCase):
return {'id': u'7085381b-45e0-4e5d-b24a-f965f5e6e5d7'}
elif filters['event_type'] == 'before_end_lease':
return {'id': u'452bf850-e223-4035-9d13-eb0b0197228f',
'time': wrong_before_end_notification,
'time': wrong_before_end_date,
'status': 'DONE'}
lease_values = {
'name': 'renamed',
'end_date': '2013-12-20 16:00',
'before_end_notification': wrong_before_end_notification
'before_end_date': wrong_before_end_date
}
reservation_get_all = (
self.patch(self.db_api, 'reservation_get_all_by_lease_id'))
@ -1143,8 +1144,9 @@ class ServiceTestCase(tests.TestCase):
'deleted')
def test_before_end_lease(self):
basic_action = self.patch(self.manager, '_basic_action')
self.manager.before_end_lease(self.lease_id, '1')
self.event_update.assert_called_once_with('1', {'status': 'DONE'})
basic_action.assert_called_once_with(self.lease_id, '1', 'before_end')
def test_basic_action_no_res_status(self):
self.patch(self.manager, 'get_lease').return_value = self.lease

View File

@ -17,6 +17,7 @@ import datetime
import mock
from novaclient import client as nova_client
from oslo_config import cfg
import testtools
from blazar import context
@ -82,7 +83,7 @@ class PhysicalHostPluginTestCase(tests.TestCase):
def setUp(self):
super(PhysicalHostPluginTestCase, self).setUp()
self.cfg = cfg
self.context = context
self.patch(self.context, 'BlazarContext')
@ -661,6 +662,32 @@ class PhysicalHostPluginTestCase(tests.TestCase):
add_computehost.assert_called_with(
1, 'host1_hostname')
def test_before_end_with_no_action(self):
self.cfg.CONF.set_override('before_end', '',
group='physical:host')
host_reservation_get = self.patch(self.db_api, 'host_reservation_get')
self.fake_phys_plugin.before_end(
u'04de74e8-193a-49d2-9ab8-cba7b49e45e8')
host_reservation_get.assert_not_called()
def test_before_end_with_snapshot(self):
self.cfg.CONF.set_override('before_end', 'snapshot',
group='physical:host')
host_reservation_get = self.patch(self.db_api, 'host_reservation_get')
host_reservation_get.return_value = {
'aggregate_id': 1
}
get_computehosts = self.patch(self.nova.ReservationPool,
'get_computehosts')
get_computehosts.return_value = ['host']
list_servers = self.patch(self.ServerManager, 'list')
list_servers.return_value = ['server1', 'server2']
create_image = self.patch(self.ServerManager, 'create_image')
self.fake_phys_plugin.before_end(
u'04de74e8-193a-49d2-9ab8-cba7b49e45e8')
create_image.assert_any_call(server='server1')
create_image.assert_any_call(server='server2')
def test_on_end_with_instances(self):
host_reservation_get = self.patch(self.db_api, 'host_reservation_get')
host_reservation_get.return_value = {

View File

@ -169,12 +169,11 @@ class ServerManager(servers.ServerManager):
"""Unshelve the server."""
self._action('unshelve', server, None)
def create_image(self, server_id, image_name=None, metadata=None):
def create_image(self, server, image_name=None, metadata=None):
"""Snapshot a server."""
server_name = self.get(server_id).name
if image_name is None:
image_name = CONF.nova.image_prefix + server_name
return super(ServerManager, self).create_image(server_id,
image_name = CONF.nova.image_prefix + server.name
return super(ServerManager, self).create_image(server,
image_name=image_name,
metadata=metadata)

View File

@ -161,6 +161,7 @@ are mentioned.
"name": "lease_foo",
"start_date": "2017-2-21 20:00",
"end_date": "2017-2-24 20:00",
"before_end_date": "2017-02-24 19:00",
"reservations": [
{
"hypervisor_properties": "[\"==\", \"$hypervisor_hostname\", \"compute\"]",

View File

@ -0,0 +1,18 @@
---
features:
- Blazar gets to support before_end actions. Actions like notification and
snapshot can be taken at a specific time prior to the end of a lease.
The time which triggers actions can be specified by the API parameter
*before_end_date* and the default interval can be configured by a new
configuration option *minutes_before_end_lease* in the [manager] section.
An action *snapshot* is currently supported by the physical host plugin.
The action can be configured by the new configuration option *before_end*
in the [physical:host] section.
upgrade:
- The API parameter *before_end_notification* has been renamed
*before_end_date* which is used for setting the time for triggering actions
before the end of a lease.
- The configuration option *notify_hours_before_lease_end* in the [manager]
section has been removed. Use a new configuration option
*minutes_before_end_lease* instead. The default value for the configuration
option has been changed from 48 hours to 60 minutes.