nodepool/nodepool/tests/unit/test_commands.py

365 lines
15 KiB
Python

# Copyright (C) 2015 OpenStack Foundation
#
# 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 logging
import os.path
import sys # noqa making sure its available for monkey patching
import fixtures
import mock
import testtools
from nodepool.cmd import nodepoolcmd
from nodepool import tests
from nodepool import zk
from nodepool.nodeutils import iterate_timeout
class TestNodepoolCMD(tests.DBTestCase):
def setUp(self):
super(TestNodepoolCMD, self).setUp()
def patch_argv(self, *args):
argv = ["nodepool"]
argv.extend(args)
self.useFixture(fixtures.MonkeyPatch('sys.argv', argv))
def assert_listed(self, configfile, cmd, col, val, count, col_count=0):
log = logging.getLogger("tests.PrettyTableMock")
self.patch_argv("-c", configfile, *cmd)
for _ in iterate_timeout(10, AssertionError, 'assert listed'):
try:
with mock.patch('prettytable.PrettyTable.add_row') as \
m_add_row:
nodepoolcmd.main()
rows_with_val = 0
# Find add_rows with the status were looking for
for args, kwargs in m_add_row.call_args_list:
row = args[0]
if col_count:
self.assertEquals(len(row), col_count)
log.debug(row)
if row[col] == val:
rows_with_val += 1
self.assertEquals(rows_with_val, count)
break
except AssertionError:
# retry
pass
def assert_alien_images_listed(self, configfile, image_cnt, image_id):
self.assert_listed(configfile, ['alien-image-list'], 2, image_id,
image_cnt)
def assert_alien_images_empty(self, configfile):
self.assert_alien_images_listed(configfile, 0, 0)
def assert_images_listed(self, configfile, image_cnt, status="ready"):
self.assert_listed(configfile, ['image-list'], 6, status, image_cnt)
def assert_nodes_listed(self, configfile, node_cnt, status="ready",
detail=False, validate_col_count=False):
cmd = ['list']
col_count = 9
if detail:
cmd += ['--detail']
col_count = 18
if not validate_col_count:
col_count = 0
self.assert_listed(configfile, cmd, 6, status, node_cnt, col_count)
def test_image_list_empty(self):
self.assert_images_listed(self.setup_config("node_cmd.yaml"), 0)
def test_image_delete_invalid(self):
configfile = self.setup_config("node_cmd.yaml")
self.patch_argv("-c", configfile, "image-delete",
"--provider", "invalid-provider",
"--image", "invalid-image",
"--build-id", "invalid-build-id",
"--upload-id", "invalid-upload-id")
nodepoolcmd.main()
def test_image_delete(self):
configfile = self.setup_config("node.yaml")
self.useBuilder(configfile)
self.waitForImage('fake-provider', 'fake-image')
image = self.zk.getMostRecentImageUpload('fake-image', 'fake-provider')
self.patch_argv("-c", configfile, "image-delete",
"--provider", "fake-provider",
"--image", "fake-image",
"--build-id", image.build_id,
"--upload-id", image.id)
nodepoolcmd.main()
self.waitForUploadRecordDeletion('fake-provider', 'fake-image',
image.build_id, image.id)
def test_alien_image_list_empty(self):
configfile = self.setup_config("node.yaml")
self.useBuilder(configfile)
self.waitForImage('fake-provider', 'fake-image')
self.patch_argv("-c", configfile, "alien-image-list")
nodepoolcmd.main()
self.assert_alien_images_empty(configfile)
def test_alien_image_list_fail(self):
def fail_list(self):
raise RuntimeError('Fake list error')
self.useFixture(fixtures.MonkeyPatch(
'nodepool.driver.fake.provider.FakeOpenStackCloud.list_servers',
fail_list))
configfile = self.setup_config("node_cmd.yaml")
self.patch_argv("-c", configfile, "alien-image-list")
nodepoolcmd.main()
def test_list_nodes(self):
configfile = self.setup_config('node.yaml')
self.useBuilder(configfile)
pool = self.useNodepool(configfile, watermark_sleep=1)
pool.start()
self.waitForImage('fake-provider', 'fake-image')
self.waitForNodes('fake-label')
for _ in iterate_timeout(10, Exception, "assert nodes are listed"):
try:
self.assert_nodes_listed(configfile, 1, detail=False,
validate_col_count=True)
break
except AssertionError:
# node is not listed yet, retry later
pass
def test_list_nodes_detail(self):
configfile = self.setup_config('node.yaml')
self.useBuilder(configfile)
pool = self.useNodepool(configfile, watermark_sleep=1)
pool.start()
self.waitForImage('fake-provider', 'fake-image')
self.waitForNodes('fake-label')
for _ in iterate_timeout(10, Exception, "assert nodes are listed"):
try:
self.assert_nodes_listed(configfile, 1, detail=True,
validate_col_count=True)
break
except AssertionError:
# node is not listed yet, retry later
pass
def test_config_validate(self):
config = os.path.join(os.path.dirname(tests.__file__),
'fixtures', 'config_validate', 'good.yaml')
self.patch_argv('-c', config, 'config-validate')
nodepoolcmd.main()
def test_dib_image_list(self):
configfile = self.setup_config('node.yaml')
self.useBuilder(configfile)
self.waitForImage('fake-provider', 'fake-image')
self.assert_listed(configfile, ['dib-image-list'], 4, zk.READY, 1)
def test_dib_image_build_pause(self):
configfile = self.setup_config('node_diskimage_pause.yaml')
self.useBuilder(configfile)
self.patch_argv("-c", configfile, "image-build", "fake-image")
with testtools.ExpectedException(Exception):
nodepoolcmd.main()
self.assert_listed(configfile, ['dib-image-list'], 1, 'fake-image', 0)
def test_dib_image_pause(self):
configfile = self.setup_config('node_diskimage_pause.yaml')
self.useBuilder(configfile)
pool = self.useNodepool(configfile, watermark_sleep=1)
pool.start()
nodes = self.waitForNodes('fake-label2')
self.assertEqual(len(nodes), 1)
self.assert_listed(configfile, ['dib-image-list'], 1, 'fake-image', 0)
self.assert_listed(configfile, ['dib-image-list'], 1, 'fake-image2', 1)
def test_dib_image_upload_pause(self):
configfile = self.setup_config('node_image_upload_pause.yaml')
self.useBuilder(configfile)
pool = self.useNodepool(configfile, watermark_sleep=1)
pool.start()
nodes = self.waitForNodes('fake-label2')
self.assertEqual(len(nodes), 1)
# Make sure diskimages were built.
self.assert_listed(configfile, ['dib-image-list'], 1, 'fake-image', 1)
self.assert_listed(configfile, ['dib-image-list'], 1, 'fake-image2', 1)
# fake-image will be missing, since it is paused.
self.assert_listed(configfile, ['image-list'], 3, 'fake-image', 0)
self.assert_listed(configfile, ['image-list'], 3, 'fake-image2', 1)
def test_dib_image_delete(self):
configfile = self.setup_config('node.yaml')
pool = self.useNodepool(configfile, watermark_sleep=1)
self.useBuilder(configfile)
pool.start()
self.waitForImage('fake-provider', 'fake-image')
nodes = self.waitForNodes('fake-label')
self.assertEqual(len(nodes), 1)
# Check the image exists
self.assert_listed(configfile, ['dib-image-list'], 4, zk.READY, 1)
builds = self.zk.getMostRecentBuilds(1, 'fake-image', zk.READY)
# Delete the image
self.patch_argv('-c', configfile, 'dib-image-delete',
'fake-image-%s' % (builds[0].id))
nodepoolcmd.main()
self.waitForBuildDeletion('fake-image', '0000000001')
# Check that fake-image-0000000001 doesn't exist
self.assert_listed(
configfile, ['dib-image-list'], 0, 'fake-image-0000000001', 0)
def test_delete(self):
configfile = self.setup_config('node.yaml')
pool = self.useNodepool(configfile, watermark_sleep=1)
self.useBuilder(configfile)
pool.start()
self.waitForImage('fake-provider', 'fake-image')
nodes = self.waitForNodes('fake-label')
self.assertEqual(len(nodes), 1)
# Assert one node exists and it is nodes[0].id in a ready state.
self.assert_listed(configfile, ['list'], 0, nodes[0].id, 1)
self.assert_nodes_listed(configfile, 1, zk.READY)
# Delete node
self.patch_argv('-c', configfile, 'delete', nodes[0].id)
nodepoolcmd.main()
self.waitForNodeDeletion(nodes[0])
# Assert the node is gone
self.assert_listed(configfile, ['list'], 0, nodes[0].id, 0)
def test_delete_now(self):
configfile = self.setup_config('node.yaml')
pool = self.useNodepool(configfile, watermark_sleep=1)
self.useBuilder(configfile)
# (Shrews): This is a hack to avoid a race with the DeletedNodeWorker
# thread where it may see that our direct call to NodeDeleter.delete()
# has changed the node state to DELETING and lock the node during the
# act of deletion, but *after* the lock znode child has been deleted
# and *before* kazoo has fully removed the node znode itself. This race
# causes the rare kazoo.exceptions.NotEmptyError in this test because
# a new lock znode gets created (that the original delete does not see)
# preventing the node znode from being deleted.
pool.delete_interval = 5
pool.start()
self.waitForImage('fake-provider', 'fake-image')
nodes = self.waitForNodes('fake-label')
self.assertEqual(len(nodes), 1)
# Assert one node exists and it is node 1 in a ready state.
self.assert_listed(configfile, ['list'], 0, nodes[0].id, 1)
self.assert_nodes_listed(configfile, 1, zk.READY)
# Delete node
self.patch_argv('-c', configfile, 'delete', '--now', nodes[0].id)
nodepoolcmd.main()
self.waitForNodeDeletion(nodes[0])
# Assert the node is gone
self.assert_listed(configfile, ['list'], 0, nodes[0].id, 0)
def test_image_build(self):
configfile = self.setup_config('node.yaml')
self.useBuilder(configfile)
# wait for the scheduled build to arrive
self.waitForImage('fake-provider', 'fake-image')
self.assert_listed(configfile, ['dib-image-list'], 4, zk.READY, 1)
image = self.zk.getMostRecentImageUpload('fake-image', 'fake-provider')
# now do the manual build request
self.patch_argv("-c", configfile, "image-build", "fake-image")
nodepoolcmd.main()
self.waitForImage('fake-provider', 'fake-image', [image])
self.assert_listed(configfile, ['dib-image-list'], 4, zk.READY, 2)
def test_request_list(self):
configfile = self.setup_config('node.yaml')
pool = self.useNodepool(configfile, watermark_sleep=1)
self.useBuilder(configfile)
pool.start()
self.waitForImage('fake-provider', 'fake-image')
nodes = self.waitForNodes('fake-label')
self.assertEqual(len(nodes), 1)
req = zk.NodeRequest()
req.state = zk.PENDING # so it will be ignored
req.node_types = ['fake-label']
req.requestor = 'test_request_list'
self.zk.storeNodeRequest(req)
self.assert_listed(configfile, ['request-list'], 0, req.id, 1)
def test_without_argument(self):
configfile = self.setup_config("node_cmd.yaml")
self.patch_argv("-c", configfile)
result = nodepoolcmd.main()
self.assertEqual(1, result)
def test_info_and_erase(self):
configfile = self.setup_config('info_cmd_two_provider.yaml')
pool = self.useNodepool(configfile, watermark_sleep=1)
self.useBuilder(configfile)
pool.start()
p1_image = self.waitForImage('fake-provider', 'fake-image')
p1_nodes = self.waitForNodes('fake-label')
p2_nodes = self.waitForNodes('fake-label2')
# Get rid of the second provider so that when we remove its
# data from ZooKeeper, the builder and launcher don't attempt to
# recreate the data.
self.replace_config(configfile, 'info_cmd_two_provider_remove.yaml')
# Verify that the second provider image is listed
self.assert_listed(
configfile,
['info', 'fake-provider2'],
0, 'fake-image', 1)
# Verify that the second provider node is listed.
self.assert_listed(
configfile,
['info', 'fake-provider2'],
0, p2_nodes[0].id, 1)
# Erase the data for the second provider
self.patch_argv(
"-c", configfile, 'erase', 'fake-provider2', '--force')
nodepoolcmd.main()
# Verify that no build or node for the second provider is listed
# after the previous erase
self.assert_listed(
configfile,
['info', 'fake-provider2'],
0, 'fake-image', 0)
self.assert_listed(
configfile,
['info', 'fake-provider2'],
0, p2_nodes[0].id, 0)
# Verify that we did not affect the first provider
image = self.waitForImage('fake-provider', 'fake-image')
self.assertEqual(p1_image, image)
nodes = self.waitForNodes('fake-label')
self.assertEqual(1, len(nodes))
self.assertEqual(p1_nodes[0], nodes[0])