Introduced policies to resolve nodes by its role
NullResolver the fake resolver PatternBasedRoleResolver allows to use pattern in name of role implements blueprint: task-based-deployment-astute Change-Id: I5bfb135fe95ed8faee6df81e31748e0143c568e6
This commit is contained in:
parent
f2aec0a6c7
commit
eea95621e0
|
@ -440,4 +440,9 @@ OPENSTACK_CONFIG_TYPES = Enum(
|
|||
'node',
|
||||
)
|
||||
|
||||
NODE_RESOLVE_POLICY = Enum(
|
||||
"all",
|
||||
"any"
|
||||
)
|
||||
|
||||
OVERRIDE_CONFIG_BASE_PATH = '/etc/hiera/override/configuration/'
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 mock
|
||||
import six
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun.test.base import BaseUnitTest
|
||||
from nailgun.utils import role_resolver
|
||||
|
||||
|
||||
class TestNameMatchPolicy(BaseUnitTest):
|
||||
def test_exact_match(self):
|
||||
match_policy = role_resolver.NameMatchPolicy.create("controller")
|
||||
self.assertIsInstance(match_policy, role_resolver.ExactMatch)
|
||||
self.assertTrue(match_policy.match("controller"))
|
||||
self.assertFalse(match_policy.match("controller1"))
|
||||
|
||||
def test_pattern_match(self):
|
||||
match_policy = role_resolver.NameMatchPolicy.create("/controller/")
|
||||
self.assertIsInstance(match_policy, role_resolver.PatternMatch)
|
||||
self.assertTrue(match_policy.match("controller"))
|
||||
self.assertTrue(match_policy.match("controller1"))
|
||||
|
||||
|
||||
class TestPatternBasedRoleResolver(BaseUnitTest):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.roles_of_nodes = [
|
||||
["primary-controller"],
|
||||
["cinder"],
|
||||
["controller", "compute"],
|
||||
["controller", "cinder"],
|
||||
["compute"],
|
||||
]
|
||||
cls.nodes = [
|
||||
mock.MagicMock(uid=str(i))
|
||||
for i in six.moves.range(len(cls.roles_of_nodes))
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
objs_mock = mock.patch('nailgun.utils.role_resolver.objects').start()
|
||||
objs_mock.Node.all_roles.side_effect = self.roles_of_nodes
|
||||
self.addCleanup(objs_mock.stop)
|
||||
|
||||
def test_resolve_by_pattern(self):
|
||||
resolver = role_resolver.RoleResolver(self.nodes)
|
||||
self.assertItemsEqual(
|
||||
["0", "2", "3"],
|
||||
resolver.resolve(["/.*controller/"])
|
||||
)
|
||||
self.assertItemsEqual(
|
||||
["2", "3"],
|
||||
resolver.resolve(["controller"])
|
||||
)
|
||||
self.assertItemsEqual(
|
||||
["1", "2", "3", "4"],
|
||||
resolver.resolve(["/c.+/"])
|
||||
)
|
||||
|
||||
def test_resolve_all(self):
|
||||
resolver = role_resolver.RoleResolver(self.nodes)
|
||||
self.assertItemsEqual(
|
||||
(x.uid for x in self.nodes),
|
||||
resolver.resolve("*")
|
||||
)
|
||||
|
||||
def test_resolve_master(self):
|
||||
resolver = role_resolver.RoleResolver(self.nodes)
|
||||
self.assertEqual(
|
||||
[consts.MASTER_ROLE],
|
||||
resolver.resolve(consts.MASTER_ROLE)
|
||||
)
|
||||
|
||||
def test_resolve_any(self):
|
||||
resolver = role_resolver.RoleResolver(self.nodes)
|
||||
all_nodes = resolver.resolve("*", consts.NODE_RESOLVE_POLICY.all)
|
||||
any_node = resolver.resolve("*", consts.NODE_RESOLVE_POLICY.any)
|
||||
self.assertEqual(1, len(any_node))
|
||||
self.assertIn(any_node[0], all_nodes)
|
||||
|
||||
|
||||
class TestNullResolver(BaseUnitTest):
|
||||
def test_resolve(self):
|
||||
node_ids = ['1', '2', '3']
|
||||
self.assertIs(
|
||||
node_ids,
|
||||
role_resolver.NullResolver(node_ids).resolve("controller")
|
||||
)
|
|
@ -0,0 +1,167 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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 abc
|
||||
from collections import defaultdict
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun.logger import logger
|
||||
from nailgun import objects
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class NameMatchPolicy(object):
|
||||
@abc.abstractmethod
|
||||
def match(self, name):
|
||||
"""Tests that name is acceptable.
|
||||
|
||||
:param name: the name to test
|
||||
:type name: str
|
||||
:returns: True if yes otherwise False
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def create(pattern):
|
||||
"""Makes name match policy.
|
||||
|
||||
the string wrapped with '/' treats as pattern
|
||||
'/abc/' - pattern
|
||||
'abc' - the string for exact match
|
||||
|
||||
:param pattern: the pattern to match
|
||||
:return: the NameMatchPolicy instance
|
||||
"""
|
||||
if pattern.startswith("/") and pattern.endswith("/"):
|
||||
return PatternMatch(pattern[1:-1])
|
||||
return ExactMatch(pattern)
|
||||
|
||||
|
||||
class ExactMatch(NameMatchPolicy):
|
||||
"""Tests that name exact match to argument."""
|
||||
|
||||
def __init__(self, name):
|
||||
"""Initializes.
|
||||
|
||||
:param name: the name to match
|
||||
"""
|
||||
self.name = name
|
||||
|
||||
def match(self, name):
|
||||
return self.name == name
|
||||
|
||||
|
||||
class PatternMatch(NameMatchPolicy):
|
||||
"""Tests that pattern matches to argument."""
|
||||
|
||||
def __init__(self, patten):
|
||||
self.pattern = re.compile(patten)
|
||||
|
||||
def match(self, name):
|
||||
return self.pattern.match(name)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseRoleResolver(object):
|
||||
"""Helper class to find nodes by role."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def resolve(self, roles, policy=None):
|
||||
"""Resolve roles to IDs of nodes.
|
||||
|
||||
:param roles: the required roles
|
||||
:type roles: list|str
|
||||
:param policy: the policy to filter the list of resolved nodes
|
||||
can be any|all
|
||||
any means need to return any node from resolved
|
||||
all means need to return all resolved nodes
|
||||
:type policy: str
|
||||
:return: the list of nodes
|
||||
"""
|
||||
|
||||
|
||||
class NullResolver(BaseRoleResolver):
|
||||
"""The implementation of RoleResolver
|
||||
|
||||
that returns only specified IDs.
|
||||
"""
|
||||
def __init__(self, nodes_ids):
|
||||
self.nodes_ids = nodes_ids
|
||||
|
||||
def resolve(self, roles, policy=None):
|
||||
return self.nodes_ids
|
||||
|
||||
|
||||
class RoleResolver(BaseRoleResolver):
|
||||
"""The general role resolver.
|
||||
|
||||
Allows to use patterns in name of role
|
||||
"""
|
||||
|
||||
# the mapping roles, those are resolved to known list of IDs
|
||||
# master is used to run tasks on master node
|
||||
SPECIAL_ROLES = {
|
||||
consts.MASTER_ROLE: [consts.MASTER_ROLE]
|
||||
}
|
||||
|
||||
def __init__(self, nodes):
|
||||
"""Initializes.
|
||||
|
||||
:param nodes: the sequence of node objects
|
||||
"""
|
||||
self.__mapping = defaultdict(set)
|
||||
for node in nodes:
|
||||
for r in objects.Node.all_roles(node):
|
||||
self.__mapping[r].add(node.uid)
|
||||
|
||||
def resolve(self, roles, policy=None):
|
||||
if isinstance(roles, six.string_types) and roles in self.SPECIAL_ROLES:
|
||||
result = self.SPECIAL_ROLES[roles]
|
||||
elif roles == consts.ALL_ROLES:
|
||||
result = list(set(
|
||||
uid for nodes in six.itervalues(self.__mapping)
|
||||
for uid in nodes
|
||||
))
|
||||
elif isinstance(roles, (list, tuple)):
|
||||
result = set()
|
||||
for role in roles:
|
||||
pattern = NameMatchPolicy.create(role)
|
||||
for node_role, nodes_ids in six.iteritems(self.__mapping):
|
||||
if pattern.match(node_role):
|
||||
result.update(nodes_ids)
|
||||
result = list(result)
|
||||
else:
|
||||
# TODO(fix using wrong format for roles in tasks.yaml)
|
||||
# After it will be allowed to raise exception here
|
||||
logger.warn(
|
||||
'Wrong roles format, `roles` should be a list or "*": %s',
|
||||
roles
|
||||
)
|
||||
return []
|
||||
|
||||
# in some cases need only one any node from pool
|
||||
# for example if need only one any controller.
|
||||
# to distribute load select first node from pool
|
||||
if result and policy == consts.NODE_RESOLVE_POLICY.any:
|
||||
result = result[0:1]
|
||||
|
||||
logger.debug(
|
||||
"Role '%s' and policy '%s' was resolved to: %s",
|
||||
roles, policy, result
|
||||
)
|
||||
return result
|
Loading…
Reference in New Issue