Fix unknown label in metastatic driver
If a label is removed from the configuration while a node still exists, the periodic cleanup performed by the metastatic driver will raise an AttributeError exception when trying to access the grace_time attribute, since the label pointer is None. To address this, treat a missing label as if it has a grace time of zero seconds. This adds a test which simulates the issue. This also adds an extra log entry for when a metastatic backing node slot is deallocated is added so that we log both the allocation and deallocation. Change-Id: I0c104a2fe9874e2cd30e2bf2f2227569a73be243
This commit is contained in:
parent
5c4ca2f8a1
commit
1b1eab77b0
@ -245,6 +245,7 @@ class BackingNodeRecord:
|
|||||||
idx = self.allocated_nodes.index(node_id)
|
idx = self.allocated_nodes.index(node_id)
|
||||||
self.allocated_nodes[idx] = None
|
self.allocated_nodes[idx] = None
|
||||||
self.last_used = time.time()
|
self.last_used = time.time()
|
||||||
|
return idx
|
||||||
|
|
||||||
def backsNode(self, node_id):
|
def backsNode(self, node_id):
|
||||||
return node_id in self.allocated_nodes
|
return node_id in self.allocated_nodes
|
||||||
@ -287,8 +288,14 @@ class MetastaticAdapter(statemachine.Adapter):
|
|||||||
self.backing_node_records.items():
|
self.backing_node_records.items():
|
||||||
for bnr in backing_node_records[:]:
|
for bnr in backing_node_records[:]:
|
||||||
label_config = self.provider._getLabel(bnr.label_name)
|
label_config = self.provider._getLabel(bnr.label_name)
|
||||||
|
if label_config:
|
||||||
|
grace_time = label_config.grace_time
|
||||||
|
else:
|
||||||
|
# The label doesn't exist in our config any more,
|
||||||
|
# it must have been removed.
|
||||||
|
grace_time = 0
|
||||||
if (bnr.isEmpty() and
|
if (bnr.isEmpty() and
|
||||||
now - bnr.last_used > label_config.grace_time):
|
now - bnr.last_used > grace_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)
|
||||||
@ -396,7 +403,10 @@ class MetastaticAdapter(statemachine.Adapter):
|
|||||||
self.backing_node_records.items():
|
self.backing_node_records.items():
|
||||||
for bn in backing_node_records:
|
for bn in backing_node_records:
|
||||||
if bn.backsNode(node_id):
|
if bn.backsNode(node_id):
|
||||||
bn.deallocateSlot(node_id)
|
slot = bn.deallocateSlot(node_id)
|
||||||
|
self.log.info(
|
||||||
|
"Unassigned node %s from backing node %s slot %s",
|
||||||
|
node_id, bn.node_id, slot)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _checkBackingNodeRequests(self):
|
def _checkBackingNodeRequests(self):
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import os
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
@ -180,3 +181,40 @@ class TestDriverMetastatic(tests.DBTestCase):
|
|||||||
self.assertEqual(nodes, [node1, bn1, node2, node3, bn2])
|
self.assertEqual(nodes, [node1, bn1, node2, node3, bn2])
|
||||||
self.assertEqual(bn2.id, node3.driver_data['backing_node'])
|
self.assertEqual(bn2.id, node3.driver_data['backing_node'])
|
||||||
self.assertNotEqual(bn1.id, bn2.id)
|
self.assertNotEqual(bn1.id, bn2.id)
|
||||||
|
|
||||||
|
def test_metastatic_config_change(self):
|
||||||
|
configfile = self.setup_config('metastatic.yaml')
|
||||||
|
pool = self.useNodepool(configfile, watermark_sleep=1)
|
||||||
|
pool.start()
|
||||||
|
self.wait_for_config(pool)
|
||||||
|
manager = pool.getProviderManager('fake-provider')
|
||||||
|
manager._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()
|
||||||
|
nodes = self._getNodes()
|
||||||
|
bn1 = nodes[1]
|
||||||
|
self.assertEqual(nodes, [node1, bn1])
|
||||||
|
|
||||||
|
# Update the node to indicate it was for a non-existent label
|
||||||
|
user_data = json.loads(bn1.user_data)
|
||||||
|
user_data['label'] = 'old-label'
|
||||||
|
bn1.user_data = json.dumps(user_data)
|
||||||
|
self.zk.storeNode(bn1)
|
||||||
|
|
||||||
|
# Restart the provider and make sure we load data correctly
|
||||||
|
pool.stop()
|
||||||
|
pool = self.useNodepool(configfile, watermark_sleep=1)
|
||||||
|
pool.start()
|
||||||
|
self.wait_for_config(pool)
|
||||||
|
manager = pool.getProviderManager('fake-provider')
|
||||||
|
manager._client.create_image(name="fake-image")
|
||||||
|
|
||||||
|
# Delete the metastatic node and verify that backing is deleted
|
||||||
|
node1.state = zk.DELETING
|
||||||
|
self.zk.storeNode(node1)
|
||||||
|
self.waitForNodeDeletion(node1)
|
||||||
|
self.waitForNodeDeletion(bn1)
|
||||||
|
nodes = self._getNodes()
|
||||||
|
self.assertEqual(nodes, [])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user