2017-05-30 02:06:41 +00:00
|
|
|
# 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 codecs
|
|
|
|
import fixtures
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
|
|
|
|
from stevedore import extension
|
|
|
|
from testtools.matchers import FileExists
|
|
|
|
|
|
|
|
import diskimage_builder.block_device.blockdevice as bd
|
|
|
|
import diskimage_builder.block_device.tests.test_base as tb
|
|
|
|
|
|
|
|
from diskimage_builder.block_device.exception import \
|
|
|
|
BlockDeviceSetupException
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class TestStateBase(tb.TestBase):
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super(TestStateBase, self).setUp()
|
|
|
|
|
|
|
|
# override the extensions to the test extensions
|
|
|
|
test_extensions = extension.ExtensionManager(
|
|
|
|
namespace='diskimage_builder.block_device.plugin_test',
|
|
|
|
invoke_on_load=False)
|
|
|
|
extensions_fixture = fixtures.MonkeyPatch(
|
|
|
|
'diskimage_builder.block_device.config._extensions',
|
|
|
|
test_extensions)
|
|
|
|
self.useFixture(extensions_fixture)
|
|
|
|
|
|
|
|
# status and other bits saved here
|
|
|
|
self.build_dir = fixtures.TempDir()
|
|
|
|
self.useFixture(self.build_dir)
|
|
|
|
|
|
|
|
|
|
|
|
class TestState(TestStateBase):
|
|
|
|
|
|
|
|
# The the state generation & saving methods
|
|
|
|
def test_state_create(self):
|
|
|
|
params = {
|
|
|
|
'build-dir': self.build_dir.path,
|
|
|
|
'config': self.get_config_file('cmd_create.yaml')
|
|
|
|
}
|
|
|
|
|
|
|
|
bd_obj = bd.BlockDevice(params)
|
|
|
|
|
|
|
|
bd_obj.cmd_init()
|
|
|
|
bd_obj.cmd_create()
|
|
|
|
|
|
|
|
# cmd_create should have persisted this to disk
|
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
2017-05-31 05:09:37 +00:00
|
|
|
state_file = bd_obj.state_json_file_name
|
2017-05-30 02:06:41 +00:00
|
|
|
self.assertThat(state_file, FileExists())
|
|
|
|
|
|
|
|
# ensure we see the values put in by the test extensions
|
|
|
|
# persisted
|
|
|
|
with codecs.open(state_file, encoding='utf-8', mode='r') as fd:
|
|
|
|
state = json.load(fd)
|
|
|
|
self.assertDictEqual(state,
|
|
|
|
{'test_a': {'value': 'foo',
|
|
|
|
'value2': 'bar'},
|
2017-06-01 04:31:49 +00:00
|
|
|
'test_b': {'value': 'baz'},
|
|
|
|
'test_init_state': 'here'})
|
2017-05-30 02:06:41 +00:00
|
|
|
|
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
2017-05-31 05:09:37 +00:00
|
|
|
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
|
2017-06-01 04:57:34 +00:00
|
|
|
# added to by "test_a" ... ensuring it was run backwards. It
|
|
|
|
# also checks the state was persisted through the pickling
|
|
|
|
# process.
|
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
2017-05-31 05:09:37 +00:00
|
|
|
bd_obj.cmd_umount()
|
|
|
|
|
2017-05-30 02:06:41 +00:00
|
|
|
# Test state going missing between phases
|
|
|
|
def test_missing_state(self):
|
|
|
|
params = {
|
|
|
|
'build-dir': self.build_dir.path,
|
|
|
|
'config': self.get_config_file('cmd_create.yaml')
|
|
|
|
}
|
|
|
|
|
|
|
|
bd_obj = bd.BlockDevice(params)
|
|
|
|
bd_obj.cmd_init()
|
|
|
|
bd_obj.cmd_create()
|
|
|
|
|
|
|
|
# cmd_create should have persisted this to disk
|
2017-06-01 04:57:34 +00:00
|
|
|
state_file = bd_obj.state_json_file_name
|
2017-05-30 02:06:41 +00:00
|
|
|
self.assertThat(state_file, FileExists())
|
2017-06-01 04:57:34 +00:00
|
|
|
pickle_file = bd_obj.node_pickle_file_name
|
|
|
|
self.assertThat(pickle_file, FileExists())
|
2017-05-30 02:06:41 +00:00
|
|
|
|
|
|
|
# simulate the state somehow going missing, and ensure that
|
|
|
|
# later calls notice
|
|
|
|
os.unlink(state_file)
|
2017-06-01 04:57:34 +00:00
|
|
|
os.unlink(pickle_file)
|
|
|
|
# This reads from the state dump json file
|
2017-06-03 05:57:24 +00:00
|
|
|
self.assertRaisesRegex(BlockDeviceSetupException,
|
|
|
|
"State dump not found",
|
2017-06-01 04:57:34 +00:00
|
|
|
bd_obj.cmd_getval, 'image-path')
|
2017-06-03 05:57:24 +00:00
|
|
|
self.assertRaisesRegex(BlockDeviceSetupException,
|
|
|
|
"State dump not found",
|
|
|
|
bd_obj.cmd_writefstab)
|
2017-06-01 04:57:34 +00:00
|
|
|
|
|
|
|
# this uses the pickled nodes
|
2017-06-03 05:57:24 +00:00
|
|
|
self.assertRaisesRegex(BlockDeviceSetupException,
|
2017-06-01 04:57:34 +00:00
|
|
|
"Pickle file not found",
|
2017-06-03 05:57:24 +00:00
|
|
|
bd_obj.cmd_delete)
|
2017-06-01 04:57:34 +00:00
|
|
|
self.assertRaisesRegex(BlockDeviceSetupException,
|
|
|
|
"Pickle file not found",
|
|
|
|
bd_obj.cmd_cleanup)
|
|
|
|
|
|
|
|
# XXX: figure out unit test for umount
|
2017-06-02 00:57:06 +00:00
|
|
|
|
|
|
|
# Test ordering of rollback calls if create() fails
|
|
|
|
def test_rollback(self):
|
|
|
|
params = {
|
|
|
|
'build-dir': self.build_dir.path,
|
|
|
|
'config': self.get_config_file('rollback.yaml'),
|
|
|
|
'test_rollback': True
|
|
|
|
}
|
|
|
|
|
|
|
|
bd_obj = bd.BlockDevice(params)
|
|
|
|
bd_obj.cmd_init()
|
|
|
|
|
|
|
|
# The config file has flags in that tell the last node to
|
|
|
|
# fail, which will trigger the rollback.
|
|
|
|
self.assertRaises(RuntimeError, bd_obj.cmd_create)
|
|
|
|
|
|
|
|
# cmd_create should have persisted this to disk even after the
|
|
|
|
# failure
|
|
|
|
state_file = bd_obj.state_json_file_name
|
|
|
|
self.assertThat(state_file, FileExists())
|
|
|
|
with codecs.open(state_file, encoding='utf-8', mode='r') as fd:
|
|
|
|
state = json.load(fd)
|
|
|
|
|
|
|
|
# ensure the rollback was called in order
|
|
|
|
self.assertListEqual(state['rollback_test'],
|
|
|
|
['never', 'gonna', 'give', 'you', 'up',
|
|
|
|
'never', 'gonna', 'let', 'you', 'down'])
|