Dmitry Bogun 7468264200 New communication interface with bareon instance
Set of tools that represent some sort of API to communicate with bareon
instance. This API use vendor_passthru ironic API to "catch" request
from bareon instance to ironic API. So bareon-ironic driver can receive
bareon insnstance requests. This is existing communication channel.

Before it was used to receive notification from bareon instance about
successfull node load.

Now this channel is extended to send "generic" tasks(step) from
bareon-ironic driver to bareon instance. Right now only one task(step)
is used - step to inject SSH key into bareon instance.

This new "steps" interface allow to refuse from preinstalled SSH key
in bareon instance, right now. And in future it allow to refuse from SSH
communication between bareon-ironic and bareon instance...

Change-Id: I0791807c7cb3dba70c71c4f46e5eddf01da76cdd
2017-02-28 16:06:57 +02:00

339 lines
12 KiB
Python

#
# Copyright 2017 Cray Inc., All Rights Reserved
#
# 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 json
import os
import fixtures
import mock
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.tests.unit.objects import utils as test_utils
from bareon_ironic.modules import bareon_base
from bareon_ironic.modules import bareon_utils
from bareon_ironic.modules.resources import image_service
from bareon_ironic.modules.resources import resources
from bareon_ironic.tests import base
SWIFT_DEPLOY_IMAGE_MODE = resources.PullSwiftTempurlResource.MODE
class BareonBaseTestCase(base.AbstractDBTestCase):
@mock.patch.object(bareon_base.BareonDeploy,
"_get_deploy_driver",
mock.Mock(return_value="test_driver"))
@mock.patch.object(bareon_base.BareonDeploy,
"_get_image_resource_mode",
mock.Mock(return_value="test_mode"))
@mock.patch.object(image_service, "get_glance_image_uuid_name",
mock.Mock(return_value=('uuid1', 'name1')))
@mock.patch.object(bareon_base.BareonDeploy, '_fetch_images',
mock.Mock())
def test__add_image_deployment_config__missing_parameter(self):
origin_image = {
'name': 'dashes-in-name',
'url': 'name1',
'target': '/'
}
node = test_utils.create_test_node(
self.context,
driver_info={},
instance_info={'image_source': 'uuid1'})
for field in ('name', 'url'):
image = origin_image.copy()
image.pop(field)
provision_conf = {'images': [image]}
with task_manager.acquire(self.context, node.uuid,
driver_name='bare_swift_ssh') as task:
task.node = node
deploy = bareon_base.BareonDeploy()
self.assertRaises(
exception.InvalidParameterValue,
deploy._add_image_deployment_config, task, provision_conf)
@mock.patch.object(bareon_base.BareonDeploy,
"_get_deploy_driver",
mock.Mock(return_value="test_driver"))
@mock.patch.object(bareon_base.BareonDeploy,
"_get_image_resource_mode",
mock.Mock(return_value=SWIFT_DEPLOY_IMAGE_MODE))
@mock.patch.object(image_service, "get_glance_image_uuid_name",
mock.Mock(return_value=('uuid1', 'name1')))
@mock.patch.object(resources.ResourceList, "fetch_resources", mock.Mock())
def test__add_image_deployment_config_not_alphanum_name(self):
origin_image = {
'name': 'dashes-in-name',
'url': 'name1',
'target': '/'
}
provision_conf = {'images': [origin_image.copy()]}
node = test_utils.create_test_node(
self.context,
driver_info={},
instance_info={'image_source': 'uuid1'})
with task_manager.acquire(self.context, node.uuid,
driver_name='bare_swift_ssh') as task:
task.node = node
deploy = bareon_base.BareonDeploy()
with mock.patch.object(
bareon_base, 'get_tenant_images_json_path') as path:
tenant_images_path = self.temp_dir.join('tenant_images.json')
path.side_effect = lambda node: tenant_images_path
result = deploy._add_image_deployment_config(
task, provision_conf)
result_image = result['images']
result_image = result_image[0]
self.assertEqual(origin_image['name'], result_image['name'])
@mock.patch.object(bareon_base, 'DeploymentConfigValidator')
def test__get_deployment_config_validator(self, validator_cls):
validator_cls.return_value = mock.Mock()
deploy = bareon_base.BareonDeploy()
for name in ['driver-AAA'] + ['driver-BBB'] * 3 + ['driver-AAA']:
deploy._get_deployment_config_validator(name)
# NOTE(aostapenko) check that no more than one instance of the
# DeploymentConfigValidator is created for each driver
self.assertEqual(
[mock.call('driver-AAA'), mock.call('driver-BBB')],
validator_cls.call_args_list)
@mock.patch.object(bareon_base, 'get_provision_json_path')
@mock.patch.object(bareon_utils, 'node_data_driver')
@mock.patch.object(bareon_base, 'DeploymentConfigValidator')
def test__validate_deployment_config(
self,
get_deploy_config_validator_mock,
node_data_driver_mock,
get_provision_json_path_mock):
deploy = bareon_base.BareonDeploy()
driver_name = 'fake_driver_name'
provision_json = 'fake_provision_json'
task = mock.Mock()
validator_mock = mock.Mock()
get_provision_json_path_mock.return_value = provision_json
node_data_driver_mock.return_value = driver_name
get_deploy_config_validator_mock.return_value = validator_mock
deploy._validate_deployment_config(task)
node_data_driver_mock.assert_called_once_with(task.node)
get_deploy_config_validator_mock.assert_called_once_with(driver_name)
get_provision_json_path_mock.assert_called_once_with(task.node)
validator_mock.assert_called_once_with(provision_json)
class TestDeploymentConfigValidator(base.AbstractTestCase):
def setUp(self):
super(TestDeploymentConfigValidator, self).setUp()
self.tmpdir = fixtures.TempDir()
self.useFixture(self.tmpdir)
self.payload = {
'ok.json': {'ok': True},
'fail.json': {'ok': False, 'fail': True}}
for name in self.payload:
with open(self.tmpdir.join(name), 'wt') as stream:
json.dump(self.payload[name], stream)
with open(self.tmpdir.join('corrupted.json'), 'wt') as stream:
stream.write('{"corrupted-json-file')
self.data_driver = mock.Mock()
self.driver_manager = mock.Mock()
self.driver_manager.return_value = mock.MagicMock()
self.extension = mock.Mock()
self.extension.name = 'dummy'
self.extension.entry_point.dist.version = str(
bareon_base.DeploymentConfigValidator._min_version)
driver_manager_instance = self.driver_manager.return_value
driver_manager_instance.driver = self.data_driver
driver_manager_instance.__getitem__.return_value = self.extension
patch = mock.patch(
'stevedore.driver.DriverManager', self.driver_manager)
patch.start()
self.addCleanup(patch.stop)
def test_ok(self):
validator = bareon_base.DeploymentConfigValidator('dummy')
validator(self.tmpdir.join('ok.json'))
self.data_driver.validate_data.assert_called_once_with(
self.payload['ok.json'])
def test_fail(self):
self.data_driver.exc.WrongInputDataError = DummyError
self.data_driver.validate_data.side_effect = DummyError
validator = bareon_base.DeploymentConfigValidator('dummy')
self.assertRaises(
exception.InvalidParameterValue, validator,
self.tmpdir.join('fail.json'))
self.data_driver.validate_data.assert_called_once_with(
self.payload['fail.json'])
def test_minimal_version(self):
self.extension.entry_point.dist.version = '0.0.1'
validator = bareon_base.DeploymentConfigValidator('dummy')
validator(self.tmpdir.join('ok.json'))
self.assertEqual(0, self.data_driver.validate_data.call_count)
self.assertIsNone(validator._driver)
def test_load_error(self):
self.driver_manager.side_effect = RuntimeError
validator = bareon_base.DeploymentConfigValidator('dummy')
validator(self.tmpdir.join('ok.json'))
self.assertEqual(None, validator._driver)
def test_ioerror(self):
validator = bareon_base.DeploymentConfigValidator('dummy')
with mock.patch('__builtin__.open') as open_mock:
open_mock.side_effect = IOError
self.assertRaises(
exception.InvalidParameterValue, validator,
self.tmpdir.join('ok.json'))
def test_malformed_json(self):
validator = bareon_base.DeploymentConfigValidator('dummy')
self.assertRaises(
exception.InvalidParameterValue, validator,
self.tmpdir.join('corrupted.json'))
class TestVendorDeployment(base.AbstractDBTestCase):
temp_dir = None
ssh_key = ssh_key_pub = node = None
ssh_key_payload = 'SSH KEY (private)'
ssh_key_pub_payload = 'SSH KEY (public)'
def test_deploy_steps_get(self):
vendor_iface = bareon_base.BareonVendor()
args = {
'http_method': 'GET'}
with task_manager.acquire(
self.context, self.node.uuid,
driver_name='bare_swift_ssh') as task:
step = vendor_iface.deploy_steps(task, **args)
expected_step = {
'name': 'inject-ssh-keys',
'payload': {
'ssh-keys': {
'root': [self.ssh_key_pub_payload]}}}
self.assertEqual(expected_step, step)
@mock.patch.object(bareon_base._InjectSSHKeyStepResult, '_handle')
def test_deploy_steps_post(self, result_handler):
vendor_iface = bareon_base.BareonVendor()
args = {
'http_method': 'POST',
'name': 'inject-ssh-keys',
'status': True}
with task_manager.acquire(
self.context, self.node.uuid,
driver_name='bare_swift_ssh') as task:
step = vendor_iface.deploy_steps(task, **args)
expected_step = {'url': None}
self.assertEqual(expected_step, step)
self.assertEqual(1, result_handler.call_count)
@mock.patch('ironic.drivers.modules.deploy_utils.set_failed_state')
def test_deploy_steps_post_fail(self, set_failed_state):
vendor_iface = bareon_base.BareonVendor()
args = {
'http_method': 'POST',
'name': 'inject-ssh-keys',
'status': False,
'status-details': 'Error during step execution.'}
with task_manager.acquire(
self.context, self.node.uuid,
driver_name='bare_swift_ssh') as task:
step = vendor_iface.deploy_steps(task, **args)
expected_step = {'url': None}
self.assertEqual(expected_step, step)
self.assertEqual(1, set_failed_state.call_count)
@mock.patch('ironic.drivers.modules.deploy_utils.set_failed_state')
def test_deploy_steps_post_fail_unbinded(self, set_failed_state):
vendor_iface = bareon_base.BareonVendor()
args = {
'http_method': 'POST',
'name': None,
'status': False,
'status-details': 'Error during step execution.'}
with task_manager.acquire(
self.context, self.node.uuid,
driver_name='bare_swift_ssh') as task:
step = vendor_iface.deploy_steps(task, **args)
expected_step = {'url': None}
self.assertEqual(expected_step, step)
self.assertEqual(1, set_failed_state.call_count)
def setUp(self):
super(TestVendorDeployment, self).setUp()
self.ssh_key = self.temp_dir.join('bareon-ssh.key')
self.ssh_key_pub = self.ssh_key + '.pub'
open(self.ssh_key, 'wt').write('SSH KEY (private)')
open(self.ssh_key_pub, 'wt').write('SSH KEY (public)')
os.chmod(self.ssh_key, 0o600)
self.node = test_utils.create_test_node(
self.context,
driver_info={
'bareon_key_filename': self.ssh_key,
'bareon_username': 'root'})
class DummyError(Exception):
@property
def message(self):
return self.args[0] if self.args else None