Use picked nodes for later cmd_* calls

Currently the later cmd_* calls -- umount, cleanup, delete -- all
recreate the node graph by parsing the config file using
create_graph()

There is some need, however, to have a sense of global state when
building the node list.  The problem is, this is a one time operation
-- we do not want to rebuild that state for these later calls (see the
"loaded" checks in proposed
Ic3b805f9258128d5233b21ff25579c03487c7fcc).

An insight here seems to be that these cmd_* calls do not actually
want to re-parse the configuration file and rebuild the node list;
they just want to walk the node list in reverse with the state as
provided after cmd_create().

So, rather than re-creating the node list, we might as well just
pickle it, save it to disk along side the state dictionary dump and
reload it for cmd_*.

After this, I think we can safely have PluginBase.__init__() be passed
the state.  We will now know that this will only be called once,
during initial creation.

Change-Id: I68840594a34af28d41d9522addcfd830bd203b97
This commit is contained in:
Ian Wienand 2017-05-31 15:09:37 +10:00
parent 9a8b135267
commit e82e0097a9
4 changed files with 30 additions and 8 deletions

View File

@ -17,6 +17,7 @@ import collections
import json import json
import logging import logging
import os import os
import pickle
import pprint import pprint
import shutil import shutil
import sys import sys
@ -217,6 +218,8 @@ class BlockDevice(object):
= os.path.join(self.state_dir, "state.json") = os.path.join(self.state_dir, "state.json")
self.config_json_file_name \ self.config_json_file_name \
= os.path.join(self.state_dir, "config.json") = os.path.join(self.state_dir, "config.json")
self.node_pickle_file_name \
= os.path.join(self.state_dir, "nodes.pickle")
self.config = _load_json(self.config_json_file_name) self.config = _load_json(self.config_json_file_name)
@ -379,7 +382,12 @@ class BlockDevice(object):
rollback_cb() rollback_cb()
sys.exit(1) sys.exit(1)
# dump state and nodes, in order
# XXX: we only dump the call_order (i.e. nodes) not the whole
# graph here, because later calls do not need the graph
# at this stage. might they?
state.save_state(self.state_json_file_name) state.save_state(self.state_json_file_name)
pickle.dump(call_order, open(self.node_pickle_file_name, 'wb'))
logger.info("create() finished") logger.info("create() finished")
return 0 return 0
@ -397,11 +405,9 @@ class BlockDevice(object):
return 0 return 0
# Deleting must be done in reverse order # Deleting must be done in reverse order
dg, call_order = create_graph(self.config, self.params) call_order = pickle.load(open(self.node_pickle_file_name, 'rb'))
reverse_order = reversed(call_order) reverse_order = reversed(call_order)
if dg is None:
return 0
for node in reverse_order: for node in reverse_order:
node.umount(state) node.umount(state)
@ -414,7 +420,7 @@ class BlockDevice(object):
state = BlockDeviceState(self.state_json_file_name) state = BlockDeviceState(self.state_json_file_name)
# Deleting must be done in reverse order # Deleting must be done in reverse order
dg, call_order = create_graph(self.config, self.params) call_order = pickle.load(open(self.node_pickle_file_name, 'rb'))
reverse_order = reversed(call_order) reverse_order = reversed(call_order)
for node in reverse_order: for node in reverse_order:
@ -432,7 +438,7 @@ class BlockDevice(object):
state = BlockDeviceState(self.state_json_file_name) state = BlockDeviceState(self.state_json_file_name)
# Deleting must be done in reverse order # Deleting must be done in reverse order
dg, call_order = create_graph(self.config, self.params) call_order = pickle.load(open(self.node_pickle_file_name, 'rb'))
reverse_order = reversed(call_order) reverse_order = reversed(call_order)
for node in reverse_order: for node in reverse_order:

View File

@ -38,6 +38,10 @@ class TestANode(NodeBase):
state['test_a']['value2'] = 'bar' state['test_a']['value2'] = 'bar'
return return
def umount(self, state):
# Umount is run in reverse. This key should exist from test_b
state['umount'].append('test_a')
class TestA(PluginBase): class TestA(PluginBase):

View File

@ -35,6 +35,12 @@ class TestBNode(NodeBase):
state['test_b']['value'] = 'baz' state['test_b']['value'] = 'baz'
return return
def umount(self, state):
# umount run in reverse. this should run before test_a
assert 'umount' not in state
state['umount'] = []
state['umount'].append('test_b')
class TestB(PluginBase): class TestB(PluginBase):

View File

@ -62,9 +62,7 @@ class TestState(TestStateBase):
bd_obj.cmd_create() bd_obj.cmd_create()
# cmd_create should have persisted this to disk # cmd_create should have persisted this to disk
state_file = os.path.join(self.build_dir.path, state_file = bd_obj.state_json_file_name
'states', 'block-device',
'state.json')
self.assertThat(state_file, FileExists()) self.assertThat(state_file, FileExists())
# ensure we see the values put in by the test extensions # ensure we see the values put in by the test extensions
@ -76,6 +74,14 @@ class TestState(TestStateBase):
'value2': 'bar'}, 'value2': 'bar'},
'test_b': {'value': 'baz'}}) 'test_b': {'value': 'baz'}})
pickle_file = bd_obj.node_pickle_file_name
self.assertThat(pickle_file, FileExists())
# run umount, which should load the picked nodes and run in
# reverse. This will create some state in "test_b" that it
# added to by "test_a" ... ensuring it was run backwards.
bd_obj.cmd_umount()
# Test state going missing between phases # Test state going missing between phases
def test_missing_state(self): def test_missing_state(self):
params = { params = {