Add min-retention-time to metastatic driver
This adds the ability to configure the minimum retention time for the backing node of a metastatic resource. This is useful for cloud resources with minimum billing intervals (e.g., you are billed for 24 hours of an instance even if you use it less). Change-Id: Ibb0588f244fc6697950f0401145e0ec5ad2482c3
This commit is contained in:
parent
5ff7dd8289
commit
c253ebfef3
@ -193,6 +193,16 @@ itself, which is "meta".
|
|||||||
arrive for this label before deleting the backing node.
|
arrive for this label before deleting the backing node.
|
||||||
Set this value to the amount of time in seconds to wait.
|
Set this value to the amount of time in seconds to wait.
|
||||||
|
|
||||||
|
.. attr:: min-retention-time
|
||||||
|
:type: int
|
||||||
|
|
||||||
|
If this value is set, the backing node will not be
|
||||||
|
deleted unless this amount of time (in seconds) has
|
||||||
|
passed since the backing node was launched. For backing
|
||||||
|
node resources with minimum billing times, this can be
|
||||||
|
used to ensure that the backing node is retained for at
|
||||||
|
least the minimum billing interval.
|
||||||
|
|
||||||
.. attr:: host-key-checking
|
.. attr:: host-key-checking
|
||||||
:type: bool
|
:type: bool
|
||||||
:default: False
|
:default: False
|
||||||
|
@ -218,6 +218,7 @@ class BackingNodeRecord:
|
|||||||
self.request_id = None
|
self.request_id = None
|
||||||
self.allocated_nodes = [None for x in range(slot_count)]
|
self.allocated_nodes = [None for x in range(slot_count)]
|
||||||
self.failed = False
|
self.failed = False
|
||||||
|
self.launched = time.time()
|
||||||
self.last_used = time.time()
|
self.last_used = time.time()
|
||||||
|
|
||||||
def hasAvailableSlot(self):
|
def hasAvailableSlot(self):
|
||||||
@ -296,14 +297,18 @@ class MetastaticAdapter(statemachine.Adapter):
|
|||||||
label_config = self.provider._getLabel(bnr.label_name)
|
label_config = self.provider._getLabel(bnr.label_name)
|
||||||
if label_config:
|
if label_config:
|
||||||
grace_time = label_config.grace_time
|
grace_time = label_config.grace_time
|
||||||
|
min_time = label_config.min_retention_time
|
||||||
else:
|
else:
|
||||||
# The label doesn't exist in our config any more,
|
# The label doesn't exist in our config any more,
|
||||||
# it must have been removed.
|
# it must have been removed.
|
||||||
grace_time = 0
|
grace_time = 0
|
||||||
|
min_time = 0
|
||||||
if bnr.failed:
|
if bnr.failed:
|
||||||
grace_time = 0
|
grace_time = 0
|
||||||
|
min_time = 0
|
||||||
if (bnr.isEmpty() and
|
if (bnr.isEmpty() and
|
||||||
now - bnr.last_used > grace_time):
|
now - bnr.last_used > grace_time and
|
||||||
|
now - bnr.launched > min_time):
|
||||||
self.log.info("Backing node %s has been idle for "
|
self.log.info("Backing node %s has been idle for "
|
||||||
"%s seconds, releasing",
|
"%s seconds, releasing",
|
||||||
bnr.node_id, now - bnr.last_used)
|
bnr.node_id, now - bnr.last_used)
|
||||||
@ -373,6 +378,7 @@ class MetastaticAdapter(statemachine.Adapter):
|
|||||||
user_data['slots'])
|
user_data['slots'])
|
||||||
backing_node_record.node_id = node.id
|
backing_node_record.node_id = node.id
|
||||||
backing_node_record.failed = user_data.get('failed', False)
|
backing_node_record.failed = user_data.get('failed', False)
|
||||||
|
backing_node_record.launched = user_data.get('launched', 0)
|
||||||
self.log.info("Found backing node %s for %s",
|
self.log.info("Found backing node %s for %s",
|
||||||
node.id, user_data['label'])
|
node.id, user_data['label'])
|
||||||
self._addBackingNode(user_data['label'],
|
self._addBackingNode(user_data['label'],
|
||||||
@ -454,6 +460,7 @@ class MetastaticAdapter(statemachine.Adapter):
|
|||||||
'label': bnr.label_name,
|
'label': bnr.label_name,
|
||||||
'slots': bnr.slot_count,
|
'slots': bnr.slot_count,
|
||||||
'failed': bnr.failed,
|
'failed': bnr.failed,
|
||||||
|
'launched': bnr.launched,
|
||||||
})
|
})
|
||||||
|
|
||||||
def _checkBackingNodeRequests(self):
|
def _checkBackingNodeRequests(self):
|
||||||
|
@ -45,6 +45,7 @@ class MetastaticLabel(ConfigValue):
|
|||||||
self.cloud_image = MetastaticCloudImage()
|
self.cloud_image = MetastaticCloudImage()
|
||||||
self.max_parallel_jobs = label.get('max-parallel-jobs', 1)
|
self.max_parallel_jobs = label.get('max-parallel-jobs', 1)
|
||||||
self.grace_time = label.get('grace-time', 60)
|
self.grace_time = label.get('grace-time', 60)
|
||||||
|
self.min_retention_time = label.get('min-retention-time', 0)
|
||||||
self.host_key_checking = label.get('host-key-checking',
|
self.host_key_checking = label.get('host-key-checking',
|
||||||
self.pool.host_key_checking)
|
self.pool.host_key_checking)
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ class MetastaticLabel(ConfigValue):
|
|||||||
v.Required('backing-label'): str,
|
v.Required('backing-label'): str,
|
||||||
'max-parallel-jobs': int,
|
'max-parallel-jobs': int,
|
||||||
'grace-time': int,
|
'grace-time': int,
|
||||||
|
'min-retention-time': int,
|
||||||
'host-key-checking': bool,
|
'host-key-checking': bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +65,8 @@ class MetastaticLabel(ConfigValue):
|
|||||||
return (
|
return (
|
||||||
self.backing_label == other.backing_label and
|
self.backing_label == other.backing_label and
|
||||||
self.max_parallel_jobs == other.max_parallel_jobs and
|
self.max_parallel_jobs == other.max_parallel_jobs and
|
||||||
self.grace_time == other.grace_time
|
self.grace_time == other.grace_time and
|
||||||
|
self.min_retention_time == other.min_retention_time
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
13
nodepool/tests/fixtures/metastatic.yaml
vendored
13
nodepool/tests/fixtures/metastatic.yaml
vendored
@ -21,6 +21,10 @@ labels:
|
|||||||
min-ready: 0
|
min-ready: 0
|
||||||
- name: user-label
|
- name: user-label
|
||||||
min-ready: 0
|
min-ready: 0
|
||||||
|
- name: backing-label-min-retention
|
||||||
|
min-ready: 0
|
||||||
|
- name: user-label-min-retention
|
||||||
|
min-ready: 0
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
# The backing node provider: a cloud
|
# The backing node provider: a cloud
|
||||||
@ -42,6 +46,10 @@ providers:
|
|||||||
cloud-image: fake-image
|
cloud-image: fake-image
|
||||||
min-ram: 8192
|
min-ram: 8192
|
||||||
flavor-name: 'Fake'
|
flavor-name: 'Fake'
|
||||||
|
- name: backing-label-min-retention
|
||||||
|
cloud-image: fake-image
|
||||||
|
min-ram: 8192
|
||||||
|
flavor-name: 'Fake'
|
||||||
|
|
||||||
- name: meta-provider
|
- name: meta-provider
|
||||||
driver: metastatic
|
driver: metastatic
|
||||||
@ -59,3 +67,8 @@ providers:
|
|||||||
max-parallel-jobs: 2
|
max-parallel-jobs: 2
|
||||||
grace-time: 2
|
grace-time: 2
|
||||||
host-key-checking: true
|
host-key-checking: true
|
||||||
|
- name: user-label-min-retention
|
||||||
|
backing-label: backing-label-min-retention
|
||||||
|
grace-time: 2
|
||||||
|
min-retention-time: 300
|
||||||
|
host-key-checking: true
|
||||||
|
@ -35,11 +35,11 @@ class TestDriverMetastatic(tests.DBTestCase):
|
|||||||
StateMachineProvider.MINIMUM_SLEEP = 0.1
|
StateMachineProvider.MINIMUM_SLEEP = 0.1
|
||||||
StateMachineProvider.MAXIMUM_SLEEP = 1
|
StateMachineProvider.MAXIMUM_SLEEP = 1
|
||||||
|
|
||||||
def _requestNode(self):
|
def _requestNode(self, label='user-label'):
|
||||||
req = zk.NodeRequest()
|
req = zk.NodeRequest()
|
||||||
req.state = zk.REQUESTED
|
req.state = zk.REQUESTED
|
||||||
req.tenant_name = 'tenant-1'
|
req.tenant_name = 'tenant-1'
|
||||||
req.node_types.append('user-label')
|
req.node_types.append(label)
|
||||||
|
|
||||||
self.zk.storeNodeRequest(req)
|
self.zk.storeNodeRequest(req)
|
||||||
req = self.waitForNodeRequest(req)
|
req = self.waitForNodeRequest(req)
|
||||||
@ -307,3 +307,68 @@ class TestDriverMetastatic(tests.DBTestCase):
|
|||||||
self.waitForNodeDeletion(bn1)
|
self.waitForNodeDeletion(bn1)
|
||||||
nodes = self._getNodes()
|
nodes = self._getNodes()
|
||||||
self.assertEqual(nodes, [])
|
self.assertEqual(nodes, [])
|
||||||
|
|
||||||
|
def test_metastatic_min_retention(self):
|
||||||
|
# Test that the metastatic driver honors min-retention
|
||||||
|
configfile = self.setup_config('metastatic.yaml')
|
||||||
|
pool = self.useNodepool(configfile, watermark_sleep=1)
|
||||||
|
self.startPool(pool)
|
||||||
|
manager = pool.getProviderManager('fake-provider')
|
||||||
|
|
||||||
|
pool_worker = pool.getPoolWorkers('meta-provider')[0]
|
||||||
|
pool_config = pool_worker.getPoolConfig()
|
||||||
|
self.assertEqual(pool_config.max_servers, 10)
|
||||||
|
self.assertEqual(pool_config.priority, 1)
|
||||||
|
|
||||||
|
manager.adapter._client.create_image(name="fake-image")
|
||||||
|
|
||||||
|
# Request a node, verify that there is a backing node, and it
|
||||||
|
# has the same connection info
|
||||||
|
node1 = self._requestNode('user-label-min-retention')
|
||||||
|
nodes = self._getNodes()
|
||||||
|
self.assertEqual(len(nodes), 2)
|
||||||
|
self.assertEqual(nodes[0], node1)
|
||||||
|
self.assertNotEqual(nodes[1], node1)
|
||||||
|
bn1 = nodes[1]
|
||||||
|
self.assertEqual(bn1.provider, 'fake-provider')
|
||||||
|
self.assertEqual(bn1.interface_ip, node1.interface_ip)
|
||||||
|
self.assertEqual(bn1.python_path, node1.python_path)
|
||||||
|
self.assertEqual('auto', node1.python_path)
|
||||||
|
self.assertEqual(bn1.shell_type, node1.shell_type)
|
||||||
|
self.assertEqual(bn1.cloud, node1.cloud)
|
||||||
|
self.assertEqual(None, node1.shell_type)
|
||||||
|
self.assertEqual(bn1.host_keys, node1.host_keys)
|
||||||
|
self.assertEqual(['ssh-rsa FAKEKEY'], node1.host_keys)
|
||||||
|
self.assertEqual(bn1.id, node1.driver_data['backing_node'])
|
||||||
|
self.assertEqual(bn1.attributes, {
|
||||||
|
'backattr': 'back',
|
||||||
|
'testattr': 'backing',
|
||||||
|
})
|
||||||
|
self.assertEqual(node1.attributes, {
|
||||||
|
'backattr': 'back',
|
||||||
|
'metaattr': 'meta',
|
||||||
|
'testattr': 'metastatic',
|
||||||
|
})
|
||||||
|
|
||||||
|
# Delete node, verify that backing node still exists
|
||||||
|
node1.state = zk.DELETING
|
||||||
|
self.zk.storeNode(node1)
|
||||||
|
self.waitForNodeDeletion(node1)
|
||||||
|
|
||||||
|
# This has the side effect of deleting the backing node when
|
||||||
|
# idle, but it should not in this case because the
|
||||||
|
# min-retention time is not met.
|
||||||
|
meta_manager = pool.getProviderManager('meta-provider')
|
||||||
|
meta_manager.adapter.listResources()
|
||||||
|
nodes = self._getNodes()
|
||||||
|
self.assertEqual(nodes, [bn1])
|
||||||
|
self.assertEqual(nodes[0].state, zk.IN_USE)
|
||||||
|
|
||||||
|
# Falsify the launch time to trigger cleanup:
|
||||||
|
bnr = meta_manager.adapter.backing_node_records[
|
||||||
|
'user-label-min-retention'][0]
|
||||||
|
bnr.launched = 0
|
||||||
|
|
||||||
|
meta_manager.adapter.listResources()
|
||||||
|
nodes = self._getNodes()
|
||||||
|
self.waitForNodeDeletion(bn1)
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The metastatic driver now supports a
|
||||||
|
:attr:`providers.[metastatic].pools.labels.min-retention-time`
|
||||||
|
attribute to set the minimum retention time for a backing node
|
||||||
|
(useful for cloud resources with minimum billing intervals).
|
Loading…
x
Reference in New Issue
Block a user