441 lines
17 KiB
Python
441 lines
17 KiB
Python
# 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 copy
|
|
from unittest import mock
|
|
|
|
from osc_lib import exceptions as exc
|
|
|
|
from heatclient import exc as heat_exc
|
|
from heatclient.osc.v1 import software_deployment
|
|
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
|
|
from heatclient.v1 import software_configs
|
|
from heatclient.v1 import software_deployments
|
|
|
|
|
|
class TestDeployment(orchestration_fakes.TestOrchestrationv1):
|
|
def setUp(self):
|
|
super(TestDeployment, self).setUp()
|
|
self.mock_client = self.app.client_manager.orchestration
|
|
self.config_client = self.mock_client.software_configs
|
|
self.sd_client = self.mock_client.software_deployments
|
|
|
|
|
|
class TestDeploymentCreate(TestDeployment):
|
|
|
|
server_id = '1234'
|
|
config_id = '5678'
|
|
deploy_id = '910'
|
|
|
|
config = {
|
|
'name': 'my_deploy',
|
|
'group': 'strict',
|
|
'config': '#!/bin/bash',
|
|
'inputs': [],
|
|
'outputs': [],
|
|
'options': [],
|
|
'id': config_id,
|
|
}
|
|
|
|
deployment = {
|
|
'server_id': server_id,
|
|
'input_values': {},
|
|
'action': 'UPDATE',
|
|
'status': 'IN_PROGRESS',
|
|
'status_reason': None,
|
|
'signal_id': 'signal_id',
|
|
'config_id': config_id,
|
|
'id': deploy_id,
|
|
}
|
|
|
|
config_defaults = {
|
|
'group': 'Heat::Ungrouped',
|
|
'config': '',
|
|
'options': {},
|
|
'inputs': [
|
|
{
|
|
'name': 'deploy_server_id',
|
|
'description': 'ID of the server being deployed to',
|
|
'type': 'String',
|
|
'value': server_id,
|
|
},
|
|
{
|
|
'name': 'deploy_action',
|
|
'description': 'Name of the current action being deployed',
|
|
'type': 'String',
|
|
'value': 'UPDATE',
|
|
},
|
|
{
|
|
'name': 'deploy_signal_transport',
|
|
'description': 'How the server should signal to heat with the '
|
|
'deployment output values.',
|
|
'type': 'String',
|
|
'value': 'TEMP_URL_SIGNAL',
|
|
},
|
|
{
|
|
'name': 'deploy_signal_id',
|
|
'description': 'ID of signal to use for signaling output '
|
|
'values',
|
|
'type': 'String',
|
|
'value': 'signal_id',
|
|
},
|
|
{
|
|
'name': 'deploy_signal_verb',
|
|
'description': 'HTTP verb to use for signaling output values',
|
|
'type': 'String',
|
|
'value': 'PUT',
|
|
},
|
|
],
|
|
'outputs': [],
|
|
'name': 'my_deploy',
|
|
}
|
|
|
|
deploy_defaults = {
|
|
'config_id': config_id,
|
|
'server_id': server_id,
|
|
'action': 'UPDATE',
|
|
'status': 'IN_PROGRESS',
|
|
}
|
|
|
|
def setUp(self):
|
|
super(TestDeploymentCreate, self).setUp()
|
|
self.cmd = software_deployment.CreateDeployment(self.app, None)
|
|
self.config_client.create.return_value = \
|
|
software_configs.SoftwareConfig(None, self.config)
|
|
self.config_client.get.return_value = \
|
|
software_configs.SoftwareConfig(None, self.config)
|
|
self.sd_client.create.return_value = \
|
|
software_deployments.SoftwareDeployment(None, self.deployment)
|
|
|
|
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
|
|
return_value='signal_id')
|
|
def test_deployment_create(self, mock_build):
|
|
arglist = ['my_deploy', '--server', self.server_id]
|
|
expected_cols = ('action', 'config_id', 'id', 'input_values',
|
|
'server_id', 'signal_id', 'status', 'status_reason')
|
|
expected_data = ('UPDATE', self.config_id, self.deploy_id, {},
|
|
self.server_id, 'signal_id', 'IN_PROGRESS', None)
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
|
|
self.config_client.create.assert_called_with(**self.config_defaults)
|
|
self.sd_client.create.assert_called_with(
|
|
**self.deploy_defaults)
|
|
self.assertEqual(expected_cols, columns)
|
|
self.assertEqual(expected_data, data)
|
|
|
|
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
|
|
return_value='signal_id')
|
|
def test_deployment_create_with_config(self, mock_build):
|
|
arglist = ['my_deploy', '--server', self.server_id,
|
|
'--config', self.config_id]
|
|
config = copy.deepcopy(self.config_defaults)
|
|
config['config'] = '#!/bin/bash'
|
|
config['group'] = 'strict'
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
|
|
self.config_client.get.assert_called_with(self.config_id)
|
|
self.config_client.create.assert_called_with(**config)
|
|
self.sd_client.create.assert_called_with(
|
|
**self.deploy_defaults)
|
|
|
|
def test_deployment_create_config_not_found(self):
|
|
arglist = ['my_deploy', '--server', self.server_id,
|
|
'--config', 'bad_id']
|
|
self.config_client.get.side_effect = heat_exc.HTTPNotFound
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
|
|
|
|
def test_deployment_create_no_signal(self):
|
|
arglist = ['my_deploy', '--server', self.server_id,
|
|
'--signal-transport', 'NO_SIGNAL']
|
|
config = copy.deepcopy(self.config_defaults)
|
|
config['inputs'] = config['inputs'][:-2]
|
|
config['inputs'][2]['value'] = 'NO_SIGNAL'
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
|
|
self.config_client.create.assert_called_with(**config)
|
|
self.sd_client.create.assert_called_with(
|
|
**self.deploy_defaults)
|
|
|
|
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
|
|
return_value='signal_id')
|
|
def test_deployment_create_invalid_signal_transport(self, mock_build):
|
|
arglist = ['my_deploy', '--server', self.server_id,
|
|
'--signal-transport', 'A']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
self.assertRaises(heat_exc.CommandError,
|
|
self.cmd.take_action, parsed_args)
|
|
|
|
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
|
|
return_value='signal_id')
|
|
def test_deployment_create_input_value(self, mock_build):
|
|
arglist = ['my_deploy', '--server', self.server_id,
|
|
'--input-value', 'foo=bar']
|
|
config = copy.deepcopy(self.config_defaults)
|
|
config['inputs'].insert(
|
|
0, {'name': 'foo', 'type': 'String', 'value': 'bar'})
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
|
|
self.config_client.create.assert_called_with(**config)
|
|
self.sd_client.create.assert_called_with(
|
|
**self.deploy_defaults)
|
|
|
|
@mock.patch('heatclient.common.deployment_utils.build_signal_id',
|
|
return_value='signal_id')
|
|
def test_deployment_create_action(self, mock_build):
|
|
arglist = ['my_deploy', '--server', self.server_id,
|
|
'--action', 'DELETE']
|
|
config = copy.deepcopy(self.config_defaults)
|
|
config['inputs'][1]['value'] = 'DELETE'
|
|
deploy = copy.deepcopy(self.deploy_defaults)
|
|
deploy['action'] = 'DELETE'
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
|
|
self.config_client.create.assert_called_with(**config)
|
|
self.sd_client.create.assert_called_with(**deploy)
|
|
|
|
|
|
class TestDeploymentDelete(TestDeployment):
|
|
|
|
def setUp(self):
|
|
super(TestDeploymentDelete, self).setUp()
|
|
self.cmd = software_deployment.DeleteDeployment(self.app, None)
|
|
|
|
def test_deployment_delete_success(self):
|
|
arglist = ['test_deployment']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.cmd.take_action(parsed_args)
|
|
self.sd_client.delete.assert_called_with(
|
|
deployment_id='test_deployment')
|
|
|
|
def test_deployment_delete_multiple(self):
|
|
arglist = ['test_deployment', 'test_deployment2']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.cmd.take_action(parsed_args)
|
|
self.sd_client.delete.assert_has_calls(
|
|
[mock.call(deployment_id='test_deployment'),
|
|
mock.call(deployment_id='test_deployment2')])
|
|
|
|
def test_deployment_delete_not_found(self):
|
|
arglist = ['test_deployment', 'test_deployment2']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.sd_client.delete.side_effect = heat_exc.HTTPNotFound()
|
|
error = self.assertRaises(
|
|
exc.CommandError, self.cmd.take_action, parsed_args)
|
|
self.assertIn("Unable to delete 2 of the 2 deployments.", str(error))
|
|
|
|
def test_deployment_config_delete_failed(self):
|
|
arglist = ['test_deployment']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.config_client.delete.side_effect = heat_exc.HTTPNotFound()
|
|
self.assertIsNone(self.cmd.take_action(parsed_args))
|
|
|
|
|
|
class TestDeploymentList(TestDeployment):
|
|
|
|
columns = ['id', 'config_id', 'server_id', 'action', 'status']
|
|
|
|
data = {"software_deployments": [
|
|
{
|
|
"status": "COMPLETE",
|
|
"server_id": "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5",
|
|
"config_id": "8da95794-2ad9-4979-8ae5-739ce314c5cd",
|
|
"output_values": {
|
|
"deploy_stdout": "Writing to /tmp/barmy Written to /tmp/barmy",
|
|
"deploy_stderr": "+ echo Writing to /tmp/barmy\n+ echo fu\n+ c"
|
|
"at /tmp/barmy\n+ echo -n The file /tmp/barmy"
|
|
"contains for server ec14c864-096e-4e27-bb8a-"
|
|
"2c2b4dc6f3f5 during CREATE\n+"
|
|
"echo Output to stderr\nOutput to stderr\n",
|
|
"deploy_status_code": 0,
|
|
"result": "The file /tmp/barmy contains fu for server "
|
|
"ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5 during CREATE"
|
|
},
|
|
"input_values": None,
|
|
"action": "CREATE",
|
|
"status_reason": "Outputs received",
|
|
"id": "ef422fa5-719a-419e-a10c-72e3a367b0b8",
|
|
"creation_time": "2015-01-31T15:12:36Z",
|
|
"updated_time": "2015-01-31T15:18:21Z"
|
|
}
|
|
]
|
|
}
|
|
|
|
def setUp(self):
|
|
super(TestDeploymentList, self).setUp()
|
|
self.cmd = software_deployment.ListDeployment(self.app, None)
|
|
self.sd_client.list = mock.MagicMock(return_value=[self.data])
|
|
|
|
def test_deployment_list(self):
|
|
arglist = []
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
self.sd_client.list.assert_called_with()
|
|
self.assertEqual(self.columns, columns)
|
|
|
|
def test_deployment_list_server(self):
|
|
kwargs = {}
|
|
kwargs['server_id'] = 'ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5'
|
|
arglist = ['--server', 'ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
self.sd_client.list.assert_called_with(**kwargs)
|
|
self.assertEqual(self.columns, columns)
|
|
|
|
def test_deployment_list_long(self):
|
|
kwargs = {}
|
|
cols = ['id', 'config_id', 'server_id', 'action', 'status',
|
|
'creation_time', 'status_reason']
|
|
arglist = ['--long']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
|
|
self.sd_client.list.assert_called_with(**kwargs)
|
|
self.assertEqual(cols, columns)
|
|
|
|
|
|
class TestDeploymentShow(TestDeployment):
|
|
get_response = {"software_deployment": {
|
|
"status": "IN_PROGRESS",
|
|
"server_id": "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5",
|
|
"config_id": "3d5ec2a8-7004-43b6-a7f6-542bdbe9d434",
|
|
"output_values": 'null',
|
|
"input_values": 'null',
|
|
"action": "CREATE",
|
|
"status_reason": "Deploy data available",
|
|
"id": "06e87bcc-33a2-4bce-aebd-533e698282d3",
|
|
"creation_time": "2015-01-31T15:12:36Z",
|
|
"updated_time": "2015-01-31T15:18:21Z"
|
|
}}
|
|
|
|
def setUp(self):
|
|
super(TestDeploymentShow, self).setUp()
|
|
self.cmd = software_deployment.ShowDeployment(self.app, None)
|
|
|
|
def test_deployment_show(self):
|
|
arglist = ['my_deployment']
|
|
cols = ['id', 'server_id', 'config_id', 'creation_time',
|
|
'updated_time', 'status', 'status_reason',
|
|
'input_values', 'action']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.sd_client.get.return_value = \
|
|
software_deployments.SoftwareDeployment(
|
|
None, self.get_response)
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
self.sd_client.get.assert_called_with(**{
|
|
'deployment_id': 'my_deployment',
|
|
})
|
|
self.assertEqual(cols, columns)
|
|
|
|
def test_deployment_show_long(self):
|
|
arglist = ['my_deployment', '--long']
|
|
cols = ['id', 'server_id', 'config_id', 'creation_time',
|
|
'updated_time', 'status', 'status_reason',
|
|
'input_values', 'action', 'output_values']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.sd_client.get.return_value = \
|
|
software_deployments.SoftwareDeployment(
|
|
None, self.get_response)
|
|
columns, data = self.cmd.take_action(parsed_args)
|
|
self.sd_client.get.assert_called_once_with(**{
|
|
'deployment_id': 'my_deployment',
|
|
})
|
|
self.assertEqual(cols, columns)
|
|
|
|
def test_deployment_not_found(self):
|
|
arglist = ['my_deployment']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.sd_client.get.side_effect = heat_exc.HTTPNotFound()
|
|
self.assertRaises(
|
|
exc.CommandError,
|
|
self.cmd.take_action,
|
|
parsed_args)
|
|
|
|
|
|
class TestDeploymentMetadataShow(TestDeployment):
|
|
|
|
def setUp(self):
|
|
super(TestDeploymentMetadataShow, self).setUp()
|
|
self.cmd = software_deployment.ShowMetadataDeployment(self.app, None)
|
|
self.sd_client.metadata.return_value = {}
|
|
|
|
def test_deployment_show_metadata(self):
|
|
arglist = ['ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.cmd.take_action(parsed_args)
|
|
self.sd_client.metadata.assert_called_with(
|
|
server_id='ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5')
|
|
|
|
|
|
class TestDeploymentOutputShow(TestDeployment):
|
|
|
|
get_response = {
|
|
"status": "IN_PROGRESS",
|
|
"server_id": "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5",
|
|
"config_id": "3d5ec2a8-7004-43b6-a7f6-542bdbe9d434",
|
|
"output_values": None,
|
|
"input_values": None,
|
|
"action": "CREATE",
|
|
"status_reason": "Deploy data available",
|
|
"id": "06e87bcc-33a2-4bce-aebd-533e698282d3",
|
|
"creation_time": "2015-01-31T15:12:36Z",
|
|
"updated_time": "2015-01-31T15:18:21Z"
|
|
}
|
|
|
|
def setUp(self):
|
|
super(TestDeploymentOutputShow, self).setUp()
|
|
self.cmd = software_deployment.ShowOutputDeployment(self.app, None)
|
|
|
|
def test_deployment_output_show(self):
|
|
arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6', '--all', '--long']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.sd_client.get.return_value = \
|
|
software_deployments.SoftwareDeployment(
|
|
None, self.get_response)
|
|
self.cmd.take_action(parsed_args)
|
|
self.sd_client.get.assert_called_with(**{
|
|
'deployment_id': '85c3a507-351b-4b28-a7d8-531c8d53f4e6'
|
|
})
|
|
|
|
def test_deployment_output_show_invalid(self):
|
|
arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
error = self.assertRaises(
|
|
exc.CommandError,
|
|
self.cmd.take_action,
|
|
parsed_args)
|
|
self.assertIn('either <output-name> or --all argument is needed',
|
|
str(error))
|
|
|
|
def test_deployment_output_show_not_found(self):
|
|
arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6', '--all']
|
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
|
self.sd_client.get.side_effect = heat_exc.HTTPNotFound()
|
|
self.assertRaises(
|
|
exc.CommandError,
|
|
self.cmd.take_action,
|
|
parsed_args)
|