359 lines
12 KiB
Python
359 lines
12 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other 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.
|
|
"""Tests to validate behavior of the classes in the deployment_group_manager
|
|
module
|
|
"""
|
|
import pytest
|
|
import re
|
|
import yaml
|
|
|
|
from shipyard_airflow.common.deployment_group.deployment_group import (
|
|
Stage
|
|
)
|
|
from shipyard_airflow.common.deployment_group.deployment_group_manager import (
|
|
DeploymentGroupManager
|
|
)
|
|
|
|
from shipyard_airflow.common.deployment_group.errors import (
|
|
DeploymentGroupCycleError, DeploymentGroupStageError,
|
|
UnknownDeploymentGroupError, UnknownNodeError
|
|
)
|
|
|
|
from .node_lookup_stubs import node_lookup
|
|
|
|
GROUPS_YAML = """
|
|
- name: control-nodes
|
|
critical: true
|
|
depends_on:
|
|
- ntp-node
|
|
selectors:
|
|
- node_names:
|
|
- node1
|
|
- node2
|
|
node_labels: []
|
|
node_tags: []
|
|
rack_names:
|
|
- rack1
|
|
success_criteria:
|
|
percent_successful_nodes: 100
|
|
- name: compute-nodes-1
|
|
critical: false
|
|
depends_on:
|
|
- control-nodes
|
|
selectors:
|
|
- node_names: []
|
|
node_labels:
|
|
- compute:true
|
|
rack_names:
|
|
- rack2
|
|
node_tags: []
|
|
success_criteria:
|
|
percent_successful_nodes: 50
|
|
- name: compute-nodes-2
|
|
critical: false
|
|
depends_on:
|
|
- control-nodes
|
|
selectors:
|
|
- node_names: []
|
|
node_labels:
|
|
- compute:true
|
|
rack_names:
|
|
- rack3
|
|
node_tags: []
|
|
success_criteria:
|
|
percent_successful_nodes: 50
|
|
- name: spare-compute-nodes
|
|
critical: false
|
|
depends_on:
|
|
- compute-nodes-2
|
|
- compute-nodes-1
|
|
selectors:
|
|
- node_names: []
|
|
node_labels:
|
|
- compute:true
|
|
rack_names:
|
|
- rack4
|
|
node_tags: []
|
|
- name: all-compute-nodes
|
|
critical: false
|
|
depends_on:
|
|
- compute-nodes-2
|
|
- compute-nodes-1
|
|
- spare-compute-nodes
|
|
selectors:
|
|
- node_names: []
|
|
node_labels:
|
|
- compute:true
|
|
rack_names: []
|
|
node_tags: []
|
|
- name: monitoring-nodes
|
|
critical: false
|
|
depends_on: []
|
|
selectors:
|
|
- node_names: []
|
|
node_labels: []
|
|
node_tags:
|
|
- monitoring
|
|
rack_names: []
|
|
success_criteria:
|
|
minimum_successful_nodes: 3
|
|
- name: ntp-node
|
|
critical: true
|
|
depends_on: []
|
|
selectors:
|
|
- node_names:
|
|
- node3
|
|
node_labels: []
|
|
node_tags: []
|
|
rack_names:
|
|
- rack1
|
|
success_criteria:
|
|
minimum_successful_nodes: 1
|
|
"""
|
|
|
|
CYCLE_GROUPS_YAML = """
|
|
- name: group-a
|
|
critical: true
|
|
depends_on:
|
|
- group-c
|
|
selectors: []
|
|
- name: group-b
|
|
critical: true
|
|
depends_on:
|
|
- group-a
|
|
selectors: []
|
|
- name: group-c
|
|
critical: true
|
|
depends_on:
|
|
- group-d
|
|
selectors: []
|
|
- name: group-d
|
|
critical: true
|
|
depends_on:
|
|
- group-a
|
|
selectors: []
|
|
|
|
"""
|
|
|
|
|
|
class TestDeploymentGroupManager:
|
|
def test_basic_class(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
assert dgm is not None
|
|
# topological sort doesn't guarantee a specific order.
|
|
assert dgm.get_next_group(Stage.PREPARED).name in ['ntp-node',
|
|
'monitoring-nodes']
|
|
assert len(dgm._all_groups) == 7
|
|
assert len(dgm._all_nodes) == 12
|
|
for name, group in dgm._all_groups.items():
|
|
assert name == group.name
|
|
|
|
def test_cycle_error(self):
|
|
with pytest.raises(DeploymentGroupCycleError) as ce:
|
|
DeploymentGroupManager(yaml.safe_load(CYCLE_GROUPS_YAML),
|
|
node_lookup)
|
|
assert 'The following are involved' in str(ce)
|
|
for g in ['group-a', 'group-c', 'group-d']:
|
|
assert g in str(ce)
|
|
assert 'group-b' not in str(ce)
|
|
|
|
def test_no_next_group(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
assert dgm.get_next_group(Stage.DEPLOYED) is None
|
|
|
|
def test_group_list(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
assert len(dgm.group_list()) == 7
|
|
group_names = []
|
|
for group in dgm.group_list():
|
|
group_names.append(group.name)
|
|
assert group_names == dgm._group_order
|
|
|
|
def test_fail_unsuccessful_nodes(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
group = dgm._all_groups.get('control-nodes')
|
|
dgm.fail_unsuccessful_nodes(group, [])
|
|
assert not dgm.evaluate_group_succ_criteria('control-nodes',
|
|
Stage.DEPLOYED)
|
|
assert group.stage == Stage.FAILED
|
|
|
|
def test_reports(self, caplog):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
dgm.mark_node_deployed('node1')
|
|
dgm.mark_node_prepared('node2')
|
|
dgm.mark_node_failed('node3')
|
|
dgm.mark_group_prepared('control-nodes')
|
|
dgm.mark_group_deployed('control-nodes')
|
|
dgm.mark_group_prepared('compute-nodes-1')
|
|
dgm.mark_group_failed('compute-nodes-2')
|
|
dgm.report_group_summary()
|
|
ansi_re = re.compile(r'\x1b\[[0-9;]*m')
|
|
assert "===== Group Summary =====" in caplog.text
|
|
assert ("Group ntp-node [Critical] ended with stage: "
|
|
"Stage.NOT_STARTED") in re.sub(ansi_re, '', caplog.text)
|
|
caplog.clear()
|
|
dgm.report_node_summary()
|
|
assert "Nodes Stage.PREPARED: node2" in re.sub(ansi_re, '', caplog.text)
|
|
assert "Nodes Stage.FAILED: node3" in re.sub(ansi_re, '', caplog.text)
|
|
assert "===== End Node Summary =====" in caplog.text
|
|
assert "It was the best of times" not in caplog.text
|
|
|
|
def test_evaluate_group_succ_criteria(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
group = dgm._all_groups.get('control-nodes')
|
|
|
|
nodes = ["node{}".format(i) for i in range(1, 12)]
|
|
for node in nodes:
|
|
dgm.mark_node_prepared(node)
|
|
dgm.fail_unsuccessful_nodes(group, nodes)
|
|
assert dgm.evaluate_group_succ_criteria('control-nodes',
|
|
Stage.PREPARED)
|
|
assert group.stage == Stage.PREPARED
|
|
|
|
for node in nodes:
|
|
dgm.mark_node_deployed(node)
|
|
assert dgm.evaluate_group_succ_criteria('control-nodes',
|
|
Stage.DEPLOYED)
|
|
assert group.stage == Stage.DEPLOYED
|
|
|
|
def test_critical_groups_failed(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
assert not dgm.critical_groups_failed()
|
|
dgm.mark_group_failed('control-nodes')
|
|
assert dgm.critical_groups_failed()
|
|
|
|
def test_ordering_stages_flow_failure(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
|
|
group = dgm.get_next_group(Stage.PREPARED)
|
|
if group.name == 'monitoring-nodes':
|
|
dgm.mark_group_prepared(group.name)
|
|
dgm.mark_group_deployed(group.name)
|
|
group = dgm.get_next_group(Stage.PREPARED)
|
|
if group.name == 'ntp-node':
|
|
dgm.mark_group_failed(group.name)
|
|
|
|
group = dgm.get_next_group(Stage.PREPARED)
|
|
if group and group.name == 'monitoring-nodes':
|
|
dgm.mark_group_prepared(group.name)
|
|
dgm.mark_group_deployed(group.name)
|
|
group = dgm.get_next_group(Stage.PREPARED)
|
|
# all remaining groups should be failed, so no more to prepare
|
|
for name, grp in dgm._all_groups.items():
|
|
if (name == 'monitoring-nodes'):
|
|
assert grp.stage == Stage.DEPLOYED
|
|
else:
|
|
assert grp.stage == Stage.FAILED
|
|
assert group is None
|
|
|
|
def test_deduplication(self):
|
|
"""all-compute-nodes is a duplicate of things it's dependent on, it
|
|
should have no actionable nodes"""
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
acn = dgm._all_groups['all-compute-nodes']
|
|
assert len(acn.actionable_nodes) == 0
|
|
assert len(acn.full_nodes) == 6
|
|
|
|
def test_bad_group_name_lookup(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
with pytest.raises(UnknownDeploymentGroupError) as udge:
|
|
dgm.mark_group_prepared('Limburger Cheese')
|
|
assert "Group name Limburger Cheese does not refer" in str(udge)
|
|
|
|
def test_get_group_failures_for_stage_bad_input(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
with pytest.raises(DeploymentGroupStageError):
|
|
dgm.get_group_failures_for_stage('group1', Stage.FAILED)
|
|
|
|
def test_get_group_failures_for_stage(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
dgm._all_nodes = {'node%d' % x: Stage.DEPLOYED for x in range(1, 13)}
|
|
|
|
for group_name in dgm._all_groups:
|
|
assert not dgm.get_group_failures_for_stage(group_name,
|
|
Stage.DEPLOYED)
|
|
assert not dgm.get_group_failures_for_stage(group_name,
|
|
Stage.PREPARED)
|
|
|
|
dgm._all_nodes = {'node%d' % x: Stage.PREPARED for x in range(1, 13)}
|
|
|
|
for group_name in dgm._all_groups:
|
|
assert not dgm.get_group_failures_for_stage(group_name,
|
|
Stage.PREPARED)
|
|
|
|
for group_name in ['compute-nodes-1',
|
|
'monitoring-nodes',
|
|
'compute-nodes-2',
|
|
'control-nodes',
|
|
'ntp-node']:
|
|
# assert that these have a failure
|
|
assert dgm.get_group_failures_for_stage(group_name, Stage.DEPLOYED)
|
|
|
|
dgm._all_nodes = {
|
|
'node1': Stage.FAILED,
|
|
'node2': Stage.PREPARED,
|
|
'node3': Stage.FAILED,
|
|
'node4': Stage.PREPARED,
|
|
'node5': Stage.FAILED,
|
|
'node6': Stage.PREPARED,
|
|
'node7': Stage.FAILED,
|
|
'node8': Stage.PREPARED,
|
|
'node9': Stage.FAILED,
|
|
'node10': Stage.PREPARED,
|
|
'node11': Stage.FAILED,
|
|
'node12': Stage.PREPARED,
|
|
}
|
|
for group_name in dgm._all_groups:
|
|
scf = dgm.get_group_failures_for_stage(group_name,
|
|
Stage.PREPARED)
|
|
if group_name == 'monitoring-nodes':
|
|
assert scf[0] == {'criteria': 'minimum_successful_nodes',
|
|
'needed': 3,
|
|
'actual': 2}
|
|
if group_name == 'control-nodes':
|
|
assert scf[0] == {'criteria': 'percent_successful_nodes',
|
|
'needed': 100,
|
|
'actual': 50.0}
|
|
if group_name == 'ntp-node':
|
|
assert scf[0] == {'criteria': 'minimum_successful_nodes',
|
|
'needed': 1,
|
|
'actual': 0}
|
|
|
|
def test_mark_node_deployed(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
dgm.mark_node_deployed('node1')
|
|
assert dgm.get_nodes(Stage.DEPLOYED) == ['node1']
|
|
|
|
def test_mark_node_prepared(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
dgm.mark_node_prepared('node1')
|
|
assert dgm.get_nodes(Stage.PREPARED) == ['node1']
|
|
|
|
def test_mark_node_failed(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
dgm.mark_node_failed('node1')
|
|
assert dgm.get_nodes(Stage.FAILED) == ['node1']
|
|
|
|
def test_mark_node_failed_unknown(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
with pytest.raises(UnknownNodeError):
|
|
dgm.mark_node_failed('not_node')
|
|
|
|
def test_get_nodes_all(self):
|
|
dgm = DeploymentGroupManager(yaml.safe_load(GROUPS_YAML), node_lookup)
|
|
assert set(dgm.get_nodes()) == set(
|
|
['node1', 'node2', 'node3', 'node4', 'node5', 'node6', 'node7',
|
|
'node8', 'node9', 'node10', 'node11', 'node12']
|
|
)
|