Adds UpdateRoles Workflow
This patch adds a workflow and associated actions to update roles in a deployment plan. Implements: blueprint update-roles-action Change-Id: I42d66da37dcb07c1be112623d89769377bb23284
This commit is contained in:
parent
fceaf440b4
commit
a9e26a8d77
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds a workflow and associated actions to update roles in a deployment
|
||||
plan.
|
@ -113,6 +113,8 @@ mistral.actions =
|
||||
tripleo.plan.update_from_dir = tripleo_common.actions.plan:UpdatePlanFromDirAction
|
||||
tripleo.plan.update_networks = tripleo_common.actions.plan:UpdateNetworksAction
|
||||
tripleo.plan.update_plan_environment = tripleo_common.actions.plan:UpdatePlanEnvironmentAction
|
||||
tripleo.plan.update_roles = tripleo_common.actions.plan:UpdateRolesAction
|
||||
tripleo.plan.validate_roles = tripleo_common.actions.plan:ValidateRolesDataAction
|
||||
tripleo.logging_to_swift.format_messages = tripleo_common.actions.logging_to_swift:FormatMessagesAction
|
||||
tripleo.logging_to_swift.publish_ui_log_to_swift = tripleo_common.actions.logging_to_swift:PublishUILogToSwiftAction
|
||||
tripleo.logging_to_swift.prepare_log_download = tripleo_common.actions.logging_to_swift:PrepareLogDownloadAction
|
||||
|
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import logging
|
||||
from operator import itemgetter
|
||||
import shutil
|
||||
import tempfile
|
||||
import yaml
|
||||
@ -27,6 +28,7 @@ from tripleo_common.actions import base
|
||||
from tripleo_common import constants
|
||||
from tripleo_common import exception
|
||||
from tripleo_common.utils import plan as plan_utils
|
||||
from tripleo_common.utils import roles as roles_utils
|
||||
from tripleo_common.utils import swift as swiftutils
|
||||
from tripleo_common.utils import tarball
|
||||
from tripleo_common.utils.validations import pattern_validator
|
||||
@ -144,16 +146,18 @@ class ListRolesAction(base.TripleOAction):
|
||||
"""
|
||||
|
||||
def __init__(self, container=constants.DEFAULT_CONTAINER_NAME,
|
||||
role_file_name=constants.OVERCLOUD_J2_ROLES_NAME,
|
||||
detail=False):
|
||||
super(ListRolesAction, self).__init__()
|
||||
self.container = container
|
||||
self.role_file_name = role_file_name
|
||||
self.detail = detail
|
||||
|
||||
def run(self, context):
|
||||
try:
|
||||
swift = self.get_object_client(context)
|
||||
roles_data = yaml.safe_load(swift.get_object(
|
||||
self.container, constants.OVERCLOUD_J2_ROLES_NAME)[1])
|
||||
self.container, self.role_file_name)[1])
|
||||
except Exception as err:
|
||||
err_msg = ("Error retrieving roles data from deployment plan: %s"
|
||||
% err)
|
||||
@ -346,3 +350,87 @@ class UpdateNetworksAction(base.TripleOAction):
|
||||
}.values()]
|
||||
|
||||
return actions.Result(data={'network_data': network_data_to_save})
|
||||
|
||||
|
||||
class ValidateRolesDataAction(base.TripleOAction):
|
||||
"""Validates Roles Data
|
||||
|
||||
Validates the format of input (verify that each role in input has the
|
||||
required attributes set. see README in roles directory in t-h-t),
|
||||
validates that roles in input exist in roles directory in deployment plan
|
||||
"""
|
||||
|
||||
def __init__(self, roles, available_roles,
|
||||
container=constants.DEFAULT_CONTAINER_NAME):
|
||||
super(ValidateRolesDataAction, self).__init__()
|
||||
self.container = container
|
||||
self.roles = roles
|
||||
self.available_roles = available_roles
|
||||
|
||||
def run(self, context):
|
||||
err_msg = ""
|
||||
# validate roles in input exist in roles directory in t-h-t
|
||||
try:
|
||||
roles_utils.check_role_exists(
|
||||
[role['name'] for role in self.available_roles],
|
||||
[role['name'] for role in self.roles])
|
||||
except Exception as chk_err:
|
||||
err_msg = str(chk_err)
|
||||
|
||||
# validate role yaml
|
||||
for role in self.roles:
|
||||
try:
|
||||
roles_utils.validate_role_yaml(yaml.safe_dump([role]))
|
||||
except exception.RoleMetadataError as rme:
|
||||
if 'name' in role:
|
||||
err_msg += "\n%s for %s" % (str(rme), role['name'])
|
||||
else:
|
||||
err_msg += "\n%s" % str(rme)
|
||||
|
||||
if err_msg:
|
||||
return actions.Result(error=err_msg)
|
||||
return actions.Result(data=True)
|
||||
|
||||
|
||||
class UpdateRolesAction(base.TripleOAction):
|
||||
"""Updates roles_data.yaml object in plan with given roles.
|
||||
|
||||
:param roles: role input data (json)
|
||||
:param current_roles: data from roles_data.yaml file in plan (json)
|
||||
:param replace_all: boolean value indicating if input roles should merge
|
||||
with or replace data from roles_data.yaml. Defaults to False (merge)
|
||||
:param container: name of the Swift container / plan name
|
||||
"""
|
||||
|
||||
def __init__(self, roles, current_roles, replace_all=False,
|
||||
container=constants.DEFAULT_CONTAINER_NAME):
|
||||
super(UpdateRolesAction, self).__init__()
|
||||
self.container = container
|
||||
self.roles = roles
|
||||
self.current_roles = current_roles
|
||||
self.replace_all = replace_all
|
||||
|
||||
def run(self, context):
|
||||
role_data_to_save = self.roles
|
||||
|
||||
# if replace_all flag is true, discard current roles and save input
|
||||
# if replace_all flag is false, merge input into current roles
|
||||
if not self.replace_all:
|
||||
# merge the roles_data and the role_input into roles to be saved
|
||||
role_data_to_save = [role for role in {
|
||||
x['name']: x for x in
|
||||
self.current_roles + self.roles
|
||||
}.values()]
|
||||
|
||||
# ensure required primary tag exists in roles to be saved
|
||||
primary = [role for role in role_data_to_save if
|
||||
'tags' in role and 'primary' in role['tags']]
|
||||
if len(primary) < 1:
|
||||
# throw error
|
||||
raise exception.RoleMetadataError("At least one role must contain"
|
||||
" a 'primary' tag.")
|
||||
|
||||
# sort the data to have a predictable result
|
||||
save_roles = sorted(role_data_to_save, key=itemgetter('name'),
|
||||
reverse=True)
|
||||
return actions.Result(data={'roles': save_roles})
|
||||
|
@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
import mock
|
||||
|
||||
|
||||
from heatclient import exc as heatexceptions
|
||||
from mistral_lib import actions
|
||||
from oslo_concurrency import processutils
|
||||
@ -70,6 +71,77 @@ ROLES_DATA_YAML_CONTENTS = """
|
||||
- OS::TripleO::Services::Kernel
|
||||
"""
|
||||
|
||||
SAMPLE_ROLE = """
|
||||
###############################################################################
|
||||
# Role: sample #
|
||||
###############################################################################
|
||||
- name: sample
|
||||
description: |
|
||||
Sample!
|
||||
networks:
|
||||
- InternalApi
|
||||
HostnameFormatDefault: '%stackname%-sample-%index%'
|
||||
ServicesDefault:
|
||||
- OS::TripleO::Services::Ntp
|
||||
"""
|
||||
|
||||
SAMPLE_ROLE_OBJ = {
|
||||
'HostnameFormatDefault': '%stackname%-sample-%index%',
|
||||
'ServicesDefault': ['OS::TripleO::Services::Ntp'],
|
||||
'description': 'Sample!\n',
|
||||
'name': 'sample',
|
||||
'networks': ['InternalApi']
|
||||
}
|
||||
|
||||
|
||||
SAMPLE_ROLE_2 = """
|
||||
###############################################################################
|
||||
# Role: sample2 #
|
||||
###############################################################################
|
||||
- name: sample2
|
||||
description: |
|
||||
Sample2!
|
||||
networks:
|
||||
- InternalApi
|
||||
HostnameFormatDefault: '%stackname%-sample-%index%'
|
||||
ServicesDefault:
|
||||
- OS::TripleO::Services::Ntp
|
||||
"""
|
||||
|
||||
SAMPLE_ROLE_2_OBJ = {
|
||||
'HostnameFormatDefault': '%stackname%-sample-%index%',
|
||||
'ServicesDefault': ['OS::TripleO::Services::Ntp'],
|
||||
'description': 'Sample2!\n',
|
||||
'name': 'sample2',
|
||||
'networks': ['InternalApi']
|
||||
}
|
||||
|
||||
UPDATED_ROLE = """
|
||||
###############################################################################
|
||||
# Role: sample #
|
||||
###############################################################################
|
||||
- name: sample
|
||||
description: |
|
||||
Sample!
|
||||
networks:
|
||||
- InternalApi
|
||||
- ExternalApi
|
||||
tags:
|
||||
- primary
|
||||
HostnameFormatDefault: '%stackname%-sample-%index%'
|
||||
ServicesDefault:
|
||||
- OS::TripleO::Services::Ntp
|
||||
"""
|
||||
|
||||
UPDATED_ROLE_OBJ = {
|
||||
'HostnameFormatDefault': '%stackname%-sample-%index%',
|
||||
'ServicesDefault': ['OS::TripleO::Services::Ntp'],
|
||||
'description': 'Sample!\n',
|
||||
'name': 'sample',
|
||||
'networks': ['InternalApi', 'ExternalApi'],
|
||||
'tags': ['primary']
|
||||
}
|
||||
|
||||
|
||||
class CreateContainerActionTest(base.TestCase):
|
||||
|
||||
@ -446,3 +518,88 @@ class UpdateNetworksActionTest(base.TestCase):
|
||||
{'name': 'MyReplacementNetwork'}
|
||||
]
|
||||
self.assertEqual({"network_data": expected}, result.data)
|
||||
|
||||
|
||||
class ValidateRolesDataActionTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ValidateRolesDataActionTest, self).setUp()
|
||||
self.container = 'overcloud'
|
||||
self.ctx = mock.MagicMock()
|
||||
|
||||
def test_valid_roles(self):
|
||||
current_roles = [SAMPLE_ROLE_OBJ]
|
||||
requested_roles = [SAMPLE_ROLE_OBJ]
|
||||
action = plan.ValidateRolesDataAction(requested_roles, current_roles)
|
||||
result = action.run(self.ctx)
|
||||
self.assertTrue(result.data)
|
||||
|
||||
def test_invalid_roles(self):
|
||||
current_roles = [SAMPLE_ROLE_2_OBJ]
|
||||
requested_roles = [SAMPLE_ROLE_OBJ, ]
|
||||
action = plan.ValidateRolesDataAction(requested_roles, current_roles)
|
||||
result = action.run(self.ctx)
|
||||
self.assertTrue(result.error)
|
||||
|
||||
def test_validate_role_yaml_missing_name(self):
|
||||
role = SAMPLE_ROLE_OBJ.copy()
|
||||
del role['name']
|
||||
current_roles = [SAMPLE_ROLE_OBJ]
|
||||
requested_roles = [role, ]
|
||||
action = plan.ValidateRolesDataAction(requested_roles, current_roles)
|
||||
result = action.run(self.ctx)
|
||||
self.assertTrue(result.error)
|
||||
|
||||
def test_validate_role_yaml_invalid_type(self):
|
||||
role = SAMPLE_ROLE_OBJ.copy()
|
||||
role['CountDefault'] = 'should not be a string'
|
||||
current_roles = [SAMPLE_ROLE_OBJ]
|
||||
requested_roles = [role, ]
|
||||
action = plan.ValidateRolesDataAction(requested_roles, current_roles)
|
||||
result = action.run(self.ctx)
|
||||
self.assertTrue(result.error)
|
||||
|
||||
|
||||
class UpdateRolesActionTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(UpdateRolesActionTest, self).setUp()
|
||||
self.container = 'overcloud'
|
||||
self.ctx = mock.MagicMock()
|
||||
self.current_roles = [SAMPLE_ROLE_OBJ, SAMPLE_ROLE_2_OBJ]
|
||||
|
||||
def test_no_primary_roles(self):
|
||||
updated_role = UPDATED_ROLE_OBJ.copy()
|
||||
del updated_role['tags']
|
||||
action = plan.UpdateRolesAction([updated_role],
|
||||
self.current_roles,
|
||||
True, self.container)
|
||||
|
||||
self.assertRaises(exception.RoleMetadataError, action.run, self.ctx)
|
||||
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
|
||||
def test_update_some_roles(self, get_obj_client_mock):
|
||||
# Setup
|
||||
swift = mock.MagicMock()
|
||||
get_obj_client_mock.return_value = swift
|
||||
|
||||
action = plan.UpdateRolesAction([UPDATED_ROLE_OBJ],
|
||||
self.current_roles,
|
||||
False, self.container)
|
||||
result = action.run(self.ctx)
|
||||
|
||||
self.assertEqual(result.data,
|
||||
{'roles': [SAMPLE_ROLE_2_OBJ, UPDATED_ROLE_OBJ]})
|
||||
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
|
||||
def test_update_replace_roles(self, get_obj_client_mock):
|
||||
# Setup
|
||||
swift = mock.MagicMock()
|
||||
get_obj_client_mock.return_value = swift
|
||||
|
||||
action = plan.UpdateRolesAction([UPDATED_ROLE_OBJ],
|
||||
self.current_roles,
|
||||
True, self.container)
|
||||
result = action.run(self.ctx)
|
||||
|
||||
self.assertEqual(result.data, {'roles': [UPDATED_ROLE_OBJ, ]})
|
||||
|
@ -112,10 +112,10 @@ class TestRolesUtils(base.TestCase):
|
||||
role = yaml.safe_load(SAMPLE_ROLE)
|
||||
del role[0]['name']
|
||||
self.assertRaises(RoleMetadataError, rolesutils.validate_role_yaml,
|
||||
yaml.dump(role))
|
||||
yaml.safe_dump(role))
|
||||
|
||||
def test_validate_role_yaml_invalid_type(self):
|
||||
role = yaml.safe_load(SAMPLE_ROLE)
|
||||
role[0]['CountDefault'] = 'should not be a string'
|
||||
self.assertRaises(RoleMetadataError, rolesutils.validate_role_yaml,
|
||||
yaml.dump(role))
|
||||
yaml.safe_dump(role))
|
||||
|
@ -1141,3 +1141,152 @@ workflows:
|
||||
available_roles: <% $.get('available_roles', []) %>
|
||||
on-success:
|
||||
- fail: <% $.get('status') = "FAILED" %>
|
||||
|
||||
update_roles:
|
||||
description: >
|
||||
takes data in json format validates its contents and persists them in
|
||||
roles_data.yaml, after successful update, templates are regenerated.
|
||||
input:
|
||||
- container
|
||||
- roles
|
||||
- roles_data_file: 'roles_data.yaml'
|
||||
- replace_all: false
|
||||
- queue_name: tripleo
|
||||
tags:
|
||||
- tripleo-common-managed
|
||||
tasks:
|
||||
get_available_roles:
|
||||
workflow: list_available_roles
|
||||
publish:
|
||||
available_roles: <% task().result.available_roles %>
|
||||
on-success: validate_input
|
||||
on-error: notify_zaqar
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
|
||||
validate_input:
|
||||
description: >
|
||||
validate the format of input (verify that each role in input has the
|
||||
required attributes set. check README in roles directory in t-h-t),
|
||||
validate that roles in input exist in roles directory in t-h-t
|
||||
action: tripleo.plan.validate_roles
|
||||
input:
|
||||
container: <% $.container %>
|
||||
roles: <% $.roles %>
|
||||
available_roles: <% $.available_roles %>
|
||||
on-success: get_network_data
|
||||
on-error: notify_zaqar
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
|
||||
get_network_data:
|
||||
workflow: list_networks
|
||||
input:
|
||||
container: <% $.container %>
|
||||
queue_name: <% $.queue_name %>
|
||||
publish:
|
||||
network_data: <% task().result.network_data %>
|
||||
on-success: validate_network_names
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
on-error: notify_zaqar
|
||||
|
||||
validate_network_names:
|
||||
description: >
|
||||
validate that Network names assigned to Role exist in
|
||||
network-data.yaml object in Swift container
|
||||
workflow: _validate_networks_from_roles
|
||||
input:
|
||||
container: <% $.container %>
|
||||
defined_networks: <% $.network_data.name %>
|
||||
networks_in_roles: <% $.roles.networks.flatten().distinct() %>
|
||||
queue_name: <% $.queue_name %>
|
||||
on-success: get_current_roles
|
||||
on-error: notify_zaqar
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result.message %>
|
||||
|
||||
get_current_roles:
|
||||
workflow: list_roles
|
||||
input:
|
||||
container: <% $.container %>
|
||||
roles_data_file: <% $.roles_data_file %>
|
||||
queue_name: <% $.queue_name %>
|
||||
publish:
|
||||
current_roles: <% task().result.roles_data %>
|
||||
on-success: update_roles_data
|
||||
on-error: notify_zaqar
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
|
||||
update_roles_data:
|
||||
description: >
|
||||
update roles_data.yaml object in Swift with roles from workflow input
|
||||
action: tripleo.plan.update_roles
|
||||
input:
|
||||
container: <% $.container %>
|
||||
roles: <% $.roles %>
|
||||
current_roles: <% $.current_roles %>
|
||||
replace_all: <% $.replace_all %>
|
||||
publish:
|
||||
updated_roles_data: <% task().result.roles %>
|
||||
on-success: update_roles_data_in_swift
|
||||
on-error: notify_zaqar
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
|
||||
update_roles_data_in_swift:
|
||||
description: >
|
||||
update roles_data.yaml object in Swift with data from workflow input
|
||||
action: swift.put_object
|
||||
input:
|
||||
container: <% $.container %>
|
||||
obj: <% $.roles_data_file %>
|
||||
contents: <% yaml_dump($.updated_roles_data) %>
|
||||
on-success: regenerate_templates
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
on-error: notify_zaqar
|
||||
|
||||
regenerate_templates:
|
||||
action: tripleo.templates.process container=<% $.container %>
|
||||
on-success: get_updated_roles
|
||||
on-error: notify_zaqar
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
|
||||
get_updated_roles:
|
||||
workflow: list_roles
|
||||
input:
|
||||
container: <% $.container %>
|
||||
roles_data_file: <% $.roles_data_file %>
|
||||
publish:
|
||||
updated_roles: <% task().result.roles_data %>
|
||||
status: SUCCESS
|
||||
on-complete: notify_zaqar
|
||||
publish-on-error:
|
||||
status: FAILED
|
||||
message: <% task().result %>
|
||||
|
||||
notify_zaqar:
|
||||
action: zaqar.queue_post
|
||||
input:
|
||||
queue_name: <% $.queue_name %>
|
||||
messages:
|
||||
body:
|
||||
type: tripleo.roles.v1.update_roles
|
||||
payload:
|
||||
status: <% $.status %>
|
||||
message: <% $.get('message', '') %>
|
||||
execution: <% execution() %>
|
||||
updated_roles: <% $.get('updated_roles', []) %>
|
||||
on-success:
|
||||
- fail: <% $.get('status') = "FAILED" %>
|
Loading…
Reference in New Issue
Block a user