Derive Params (part 1): Initial workflow to start

This initial workflow which starts the derive parameters workflow is
responsible to get the list of role names from the flattened heat
resource tree. Once the role names list is obtained, derive parameters
per role workflow is invoked to get introspection data of first
matching node for all role names.
Implements: blueprint tripleo-derive-parameters

Co-Authored-By: Jaganathan Palanisamy <jpalanis@redhat.com>
Co-Authored-By: Alan Bishop <abishop@redhat.com>
Change-Id: I113f3e6f67c7dbdad74264afb17dfca0612008c4
This commit is contained in:
Saravanan KR 2017-04-13 10:10:21 +05:30 committed by Jaganathan Palanisamy
parent 58a385a238
commit 6bdc5fafb0
7 changed files with 342 additions and 0 deletions

View File

@ -86,6 +86,7 @@ mistral.actions =
tripleo.parameters.update_role = tripleo_common.actions.parameters:UpdateRoleParametersAction
tripleo.parameters.generate_passwords = tripleo_common.actions.parameters:GeneratePasswordsAction
tripleo.parameters.get_passwords = tripleo_common.actions.parameters:GetPasswordsAction
tripleo.parameters.get_profile_of_flavor = tripleo_common.actions.parameters:GetProfileOfFlavorAction
tripleo.parameters.generate_fencing = tripleo_common.actions.parameters:GenerateFencingParametersAction
tripleo.plan.create = tripleo_common.actions.plan:CreatePlanAction
tripleo.plan.update = tripleo_common.actions.plan:UpdatePlanAction

View File

@ -35,6 +35,7 @@ from mistral.workflow import utils as mistral_workflow_utils
from tripleo_common.actions import base
from tripleo_common.actions import templates
from tripleo_common import constants
from tripleo_common import exception
from tripleo_common.utils import nodes
from tripleo_common.utils import parameters
from tripleo_common.utils import passwords as password_utils
@ -396,3 +397,29 @@ class GetFlattenedParametersAction(GetParametersAction):
processed_data['heat_resource_tree'] = flattened
return processed_data
class GetProfileOfFlavorAction(base.TripleOAction):
"""Gets the profile name for a given flavor name.
Need flavor object to get profile name since get_keys method is
not available for external access. so we have created an action
to get profile name from flavor name.
:param flavor_name: Flavor name
:return: profile name
"""
def __init__(self, flavor_name):
super(GetProfileOfFlavorAction, self).__init__()
self.flavor_name = flavor_name
def run(self, context):
compute_client = self.get_compute_client(context)
try:
return parameters.get_profile_of_flavor(self.flavor_name,
compute_client)
except exception.DeriveParamsError as err:
LOG.error('Derive Params Error: %s', err)
return mistral_workflow_utils.Result(error=str(err))

View File

@ -106,3 +106,7 @@ class RootDeviceDetectionError(Exception):
class PlanOperationError(Exception):
"""Error while performing a deployment plan operation"""
class DeriveParamsError(Exception):
"""Error while performing a derive parameters operation"""

View File

@ -18,6 +18,7 @@ from swiftclient import exceptions as swiftexceptions
from tripleo_common.actions import parameters
from tripleo_common import constants
from tripleo_common import exception
from tripleo_common.tests import base
_EXISTING_PASSWORDS = {
@ -823,3 +824,33 @@ class GetFlattenedParametersActionTest(base.TestCase):
action = parameters.GetFlattenedParametersAction()
result = action.run(mock_ctx)
self.assertEqual(result, expected_value)
class GetProfileOfFlavorActionTest(base.TestCase):
@mock.patch('tripleo_common.utils.parameters.get_profile_of_flavor')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'get_compute_client')
@mock.patch('mistral.context.ctx')
def test_profile_found(self, mock_ctx, mock_get_compute_client,
mock_get_profile_of_flavor):
mock_ctx = mock.MagicMock()
mock_get_profile_of_flavor.return_value = 'compute'
action = parameters.GetProfileOfFlavorAction('oooq_compute')
result = action.run(mock_ctx)
expected_result = "compute"
self.assertEqual(result, expected_result)
@mock.patch('tripleo_common.utils.parameters.get_profile_of_flavor')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'get_compute_client')
@mock.patch('mistral.context.ctx')
def test_profile_not_found(self, mock_ctx, mock_get_compute_client,
mock_get_profile_of_flavor):
mock_ctx = mock.MagicMock()
profile = (exception.DeriveParamsError, )
mock_get_profile_of_flavor.side_effect = profile
action = parameters.GetProfileOfFlavorAction('no_profile')
result = action.run(mock_ctx)
self.assertTrue(result.is_error())
mock_get_profile_of_flavor.assert_called_once()

View File

@ -13,6 +13,7 @@
import mock
from tripleo_common import exception
from tripleo_common.tests import base
from tripleo_common.utils import parameters
@ -121,3 +122,68 @@ class ParametersTest(base.TestCase):
# the 'compute' flavor.
self.assertEqual(parameters.get_flavor('compute', compute_client),
'compute')
def test_profile_flavor_found(self):
compute_client = mock.MagicMock()
# Mock for a compute_client.flavors.find result item
flavor = mock.MagicMock()
flavor.id = 1
flavor.name = 'oooq_compute'
# Mock result of <flavor instance>.get_keys()
flavor_keys = mock.MagicMock()
flavor_keys.get.side_effect = ('compute', )
# Connecting the mock instances...
flavor.get_keys.side_effect = (flavor_keys, )
compute_client.flavors.find.side_effect = (flavor, )
# Calling `get_profile_of_flavor` with a 'oooq_compute' flavor
# should return profile 'compute'.
profile = parameters.get_profile_of_flavor('oooq_compute',
compute_client)
self.assertEqual(profile, 'compute')
def test_profile_flavor_not_found_exception(self):
compute_client = mock.MagicMock()
flavor = (Exception, )
compute_client.flavors.find.side_effect = flavor
# Calling `get_profile_of_flavor` with a 'oooq_compute' flavor
# should raises DeriveParamsError exception
self.assertRaises(exception.DeriveParamsError,
parameters.get_profile_of_flavor,
'oooq_compute', compute_client)
def test_profile_flavor_not_found(self):
compute_client = mock.MagicMock()
compute_client.flavors.find.return_value = None
# Calling `get_profile_of_flavor` with a 'oooq_compute' flavor
# should raises DeriveParamsError exception
self.assertRaises(exception.DeriveParamsError,
parameters.get_profile_of_flavor,
'oooq_compute', compute_client)
def test_profile_not_found_flavor_found(self):
compute_client = mock.MagicMock()
# Mock for a compute_client.flavors.find result item
flavor = mock.MagicMock()
flavor.id = 1
flavor.name = 'oooq_compute'
# Mock result of <flavor instance>.get_keys()
flavor_keys = mock.MagicMock()
flavor_keys.get.side_effect = (exception.DeriveParamsError, )
# Connecting the mock instances...
flavor.get_keys.side_effect = (flavor_keys, )
compute_client.flavors.find.side_effect = (flavor, )
# Calling `get_profile_of_flavor` with a 'no_profile' flavor
# should raises DeriveParamsError exception
self.assertRaises(exception.DeriveParamsError,
parameters.get_profile_of_flavor,
'no_profile', compute_client)

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from tripleo_common import exception
from tripleo_common.utils import nodes
@ -91,3 +92,34 @@ def set_count_and_flavor_params(role, baremetal_client, compute_client):
_get_count_key(role): node_count,
_get_flavor_key(role): flavor
}
def get_profile_of_flavor(flavor_name, compute_client):
"""Returns profile name for a given flavor name.
:param flavor_name: Flavor name
:param compute_client: Compute client object
:raises: exception.DeriveParamsError: Derive parameters error
:return: profile name
"""
try:
flavor = compute_client.flavors.find(name=flavor_name)
except Exception as err:
raise exception.DeriveParamsError(
'Unable to determine flavor for flavor name: '
'%(flavor_name)s. Error:%(err)s' % {'flavor_name': flavor_name,
'err': err})
if flavor:
profile = flavor.get_keys().get('capabilities:profile', '')
if profile:
return profile
else:
raise exception.DeriveParamsError(
'Unable to determine profile for flavor (flavor name: '
'%s)' % flavor_name)
else:
raise exception.DeriveParamsError(
'Unable to determine flavor for flavor name: '
'%s' % flavor_name)

View File

@ -0,0 +1,181 @@
---
version: '2.0'
name: tripleo.derive_params.v1
description: TripleO Workflows to derive deployment parameters from the introspected data
workflows:
derive_parameters:
description: The main workflow for deriving parameters from the introspected data
input:
- plan: overcloud
- queue_name: tripleo
tasks:
get_flattened_parameters:
action: tripleo.parameters.get_flatten container=<% $.plan %>
publish:
environment_parameters: <% task().result.mistral_environment_parameters %>
heat_resource_tree: <% task().result.heat_resource_tree %>
on-success:
- get_roles: <% $.environment_parameters and $.heat_resource_tree %>
- set_status_failed_get_flattened_parameters: <% (not $.environment_parameters) or (not $.heat_resource_tree) %>
on-error: set_status_failed_get_flattened_parameters
get_roles:
action: tripleo.role.list container=<% $.plan %>
publish:
role_name_list: <% task().result %>
on-success:
- get_valid_roles: <% $.role_name_list %>
- set_status_failed_get_roles: <% not $.role_name_list %>
on-error: set_status_failed_on_error_get_roles
# Obtain only the roles which has count > 0, by checking <RoleName>Count parameter, like ComputeCount
get_valid_roles:
publish:
valid_role_name_list: <% let(hr => $.heat_resource_tree.parameters) -> $.role_name_list.where(int($hr.get(concat($, 'Count'), {}).get('default', 0)) > 0) %>
on-success:
- for_each_role: <% $.valid_role_name_list %>
- set_status_failed_get_valid_roles: <% not $.valid_role_name_list %>
# Execute the basic preparation workflow for each role to get introspection data
for_each_role:
with-items: role_name in <% $.valid_role_name_list %>
concurrency: 1
workflow: _derive_parameters_per_role
input:
role_name: <% $.role_name %>
environment_parameters: <% $.environment_parameters %>
heat_resource_tree: <% $.heat_resource_tree %>
on-success: send_message
on-error: set_status_failed_for_each_role
set_status_failed_get_flattened_parameters:
on-success: send_message
publish:
status: FAILED
message: <% task(get_flattened_parameters).result %>
set_status_failed_get_roles:
on-success: send_message
publish:
status: FAILED
message: "Unable to determine the list of roles in the deployment plan"
set_status_failed_on_error_get_roles:
on-success: send_message
publish:
status: FAILED
message: <% task(get_roles).result %>
set_status_failed_get_valid_roles:
on-success: send_message
publish:
status: FAILED
message: 'Unable to determine the list of valid roles in the deployment plan.'
set_status_failed_for_each_role:
on-success: send_message
publish:
status: FAILED
message: <% task(for_each_role).result.select(dict('role_name' => $.role_name, 'status' => $.get('status', 'SUCCESS'), 'message' => $.get('message', ''))) %>
send_message:
action: zaqar.queue_post
retry: count=5 delay=1
input:
queue_name: <% $.queue_name %>
messages:
body:
type: tripleo.derive_params.v1.derive_parameters
payload:
status: <% $.get('status', 'SUCCESS') %>
message: <% $.get('message', '') %>
execution: <% execution() %>
on-success:
- fail: <% $.get('status') = 'FAILED' %>
_derive_parameters_per_role:
description: >
Workflow which runs per role to get the introspection data on the first matching node assigned to role.
Once introspection data is fetched, this worklow will trigger the actual derive parameters workflow
input:
- role_name
- environment_parameters
- heat_resource_tree
tasks:
# Getting introspection data workflow, which will take care of
# 1) profile and flavor based mapping
# 2) Nova placement api based mapping
# Currently we have implemented profile and flavor based mapping
# TODO-Nova placement api based mapping is pending, we will enchance it later.
get_flavor_name:
publish:
flavor_name: <% let(param_name => concat('Overcloud', $.role_name, 'Flavor').replace('OvercloudControllerFlavor', 'OvercloudControlFlavor')) -> $.heat_resource_tree.parameters.get($param_name, {}).get('default', '') %>
on-success:
- get_profile_name: <% $.flavor_name %>
- set_status_failed_get_flavor_name: <% not $.flavor_name %>
get_profile_name:
action: tripleo.parameters.get_profile_of_flavor flavor_name=<% $.flavor_name %>
publish:
profile_name: <% task().result %>
on-success: get_profile_node
on-error: set_status_failed_get_profile_name
get_profile_node:
workflow: tripleo.baremetal.v1.nodes_with_profile
input:
profile: <% $.profile_name %>
publish:
profile_node_uuid: <% task().result.matching_nodes.first('') %>
on-success:
- get_introspection_data: <% $.profile_node_uuid %>
- set_status_failed_no_matching_node_get_profile_node: <% not $.profile_node_uuid %>
on-error: set_status_failed_on_error_get_profile_node
get_introspection_data:
on-error: set_status_failed_get_introspection_data
action: baremetal_introspection.get_data uuid=<% $.profile_node_uuid %>
publish:
introspection_data: <% task().result %>
# TODO-Follow up patches workflows will actually be used here to derive parameters for each role
set_status_failed_get_flavor_name:
publish:
role_name: <% $.role_name %>
status: FAILED
message: <% "Unable to determine flavor for role '{0}'".format($.role_name) %>
on-success: fail
set_status_failed_get_profile_name:
publish:
role_name: <% $.role_name %>
status: FAILED
message: <% task(get_profile_name).result %>
on-success: fail
set_status_failed_no_matching_node_get_profile_node:
publish:
role_name: <% $.role_name %>
status: FAILED
message: <% "Unable to determine matching node for profile '{0}'".format($.profile_name) %>
on-success: fail
set_status_failed_on_error_get_profile_node:
publish:
role_name: <% $.role_name %>
status: FAILED
message: <% task(get_profile_node).result %>
on-success: fail
set_status_failed_get_introspection_data:
publish:
role_name: <% $.role_name %>
status: FAILED
message: <% task(get_introspection_data).result %>
on-success: fail