On 05/27/2016 05:30 PM, Martin Babinsky wrote:
On 05/19/2016 04:59 PM, Martin Babinsky wrote:
Patch 0146 implements lower-lever infrastructure for querying server
roles/attributes
Patch 0147 are some basic tests slapped together for the `serverroles`
backend to ensure that it works as expected.
The new/modified CLI commands specified in the design page [1] will be
coming soon.
Also coming soon will be an interface to construct DNS records for each
role requested by Petr^2 and Martin^2 which I will start implement when
we agree on the backend implementation.
https://fedorahosted.org/freeipa/ticket/5181
[1] https://www.freeipa.org/page/V4/Server_Roles#CLI
Thanks Martin and Honza for review.
I have reworked the patches based on your comments. I have split patch
146 into two (role/attribute definitions and backend code) so patches
146-147 are for code and 148 hosts test suite.
I hope that I will send API patches on monday after I resolve some
framework-related questions with the local guru.
Another revision of backend patches attached.
--
Martin^3 Babinsky
From 27e5dbcab36d8c9155cbc8dab695fa31dbf715c1 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Thu, 26 May 2016 19:24:22 +0200
Subject: [PATCH 1/3] Server roles: definitions of server roles and attributes
This patch introduces classes which define the properties of server roles and
attributes and their relationship to LDAP attributes representing the
role/attribute.
A brief documentation about defining and using roles is given at the beginning
of the module.
http://www.freeipa.org/page/V4/Server_Roles
https://fedorahosted.org/freeipa/ticket/5181
---
ipaserver/servroles.py | 612 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 612 insertions(+)
create mode 100644 ipaserver/servroles.py
diff --git a/ipaserver/servroles.py b/ipaserver/servroles.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f732f71a3bcaa0c08fad94c529979873fdcc429
--- /dev/null
+++ b/ipaserver/servroles.py
@@ -0,0 +1,612 @@
+#
+# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
+#
+
+
+"""
+This module contains the set of classes which abstract various bits and pieces
+of information present in the LDAP tree about functionalities such as DNS
+server, Active Directory trust controller etc. These properties come in two
+distinct groups:
+
+ server roles
+ this group represents a genral functionality provided by one or more
+ IPA servers, such as DNS server, certificate authority and such. In
+ this case there is a many-to-many mapping between the roles and the
+ masters which provide them.
+
+ server attributes
+ these represent a functionality associated with the whole topology,
+ such as CA renewal master or DNSSec key master.
+
+See the corresponding design page (http://www.freeipa.org/page/V4/Server_Roles)
+for more info.
+
+Both of these groups use `LDAPBasedProperty` class as a base.
+
+Server Roles
+============
+
+Server role objects are usually consuming information from the master's service
+container (cn=FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX) are represented by
+`ServiceBasedRole`class. To create an instance of such role, you only need to
+specify role name and individual services comprising the role (more systemd
+services may be enabled to provide some function):
+
+>>> example_role = ServiceBasedRole(
+... "Example Role",
+... component_services = ['SERVICE1', 'SERVICE2'])
+>>> example_role.name
+'Example Role'
+
+Each role object has an `attr_name` property which returns its name transformed
+into an LDAP-like attribute name useful in higher-level commands implemented in
+API framework
+
+>>> example_role.attr_name
+'ipaexamplerole'
+
+The role object can then be queried for the status of the role in the whole
+topology or on a single master by using its `status` method. This method
+returns a list of dictionaries akin to LDAP entries comprised from server name,
+role name and role status (enabled if role is enabled, configured if the
+service entries are present but not marked as enabled by 'enabledService'
+config string, absent if the service entries are not present).
+
+Note that 'AD trust agent' role is based on membership of the master in the
+'adtrust agents' sysaccount group and is thus an instance of different class
+(`ADTrustBasedRole`). This role also does not have 'configured' status, since
+the master is either member of the group ('enabled') or not ('absent')
+
+Server Attributes
+=================
+
+Server attributes are implemented as instances of `ServerAttribute` class. The
+attribute is defined by some flag set ornf 'ipaConfigString' attribute of some
+service entry. To create your own server attribute, see the following example:
+
+>>> example_attribute = ServerAttribute("Example Attribute", example_role,
+... "SERVICE1", "roleMaster")
+>>> example_attribute.name
+'Example Attribute'
+
+As in the case of serverroles, each instance has the `attr_name` property
+giving LDAP-like naming of the attribute:
+
+>>> example_attribute.attr_name
+'ipaexampleattribute'
+
+The list of all masters with this attribute set can be accessed via `get()`
+method. The attribute masters can be added/removed by the `update()` method
+which accepts a list on new masters as a parameter.
+
+Publicly accesible roles/attributes should be added to the
+role_instances/attribute_instances list
+"""
+
+import abc
+from collections import namedtuple, defaultdict
+
+from ldap import SCOPE_ONELEVEL
+import six
+
+from ipalib import _, errors
+from ipapython.dn import DN
+
+
+if six.PY3:
+ unicode = str
+
+
+ENABLED = u'enabled'
+CONFIGURED = u'configured'
+ABSENT = u'absent'
+
+
+@six.add_metaclass(abc.ABCMeta)
+class LDAPBasedProperty(object):
+ """
+ base class for all master properties defined by LDAP content
+ :param name: user-friendly name of the property
+ :param attrs_list: list of attributes to retrieve during search, defaults
+ to all
+ """
+ attrs_list = ['*']
+
+ def __init__(self, name):
+ self.name = name
+
+ @property
+ def attr_name(self):
+ """
+ return the attribute name suitable for use as entry attribute in API
+ framework
+ """
+ return 'ipa{}'.format(
+ ''.join(p.lower() for p in self.name.split())
+ )
+
+ def search(self, ldap, search_base, search_filter="(objectclass=*)"):
+ """
+ retrieve LDAP entries used for further processing. Returns nothing if
+ errors.EmptyModlist is raised by `ldap.get_entries()`. NotFound
+ exception (search base does not exist) is not handled.
+ """
+ try:
+ entries = ldap.get_entries(
+ search_base,
+ filter=search_filter,
+ attrs_list=self.attrs_list
+ )
+ return entries
+ except errors.EmptyResult:
+ return []
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseServerRole(LDAPBasedProperty):
+ """
+ Server role hierarchy apex. All other server role definition should either
+ inherit from it or at least provide the 'status' method and 'providers'
+ property
+ """
+
+ def create_role_status_dict(self, server, status):
+ """
+ the output of `status()` method should be a list of dictionaries having
+ the following keys:
+ * servrole: name of role
+ * server: server FQDN
+ * status: role status on server
+
+ this methods returns such a dict given server and role status
+ """
+ return {
+ u'servrole': self.name,
+ u'server': server,
+ u'status': status}
+
+ @abc.abstractmethod
+ def create_search_params(self, ldap, api_instance, server=None):
+ """
+ create search base and filter
+ :param ldap: ldap connection
+ :param api_instance: API instance
+ :param server: server FQDN. if given, the method should generate
+ filter and search base matching only the status on this server
+ :returns: tuple of search base (a DN) and search filter
+ """
+ pass
+
+ @abc.abstractmethod
+ def get_result_from_entries(self, entries):
+ """
+ Get role status from returned LDAP entries
+
+ `create_role_status_dict` method
+ :param entries: LDAPEntry objects returned by `search()`
+ :returns: list of dicts generated by `create_role_status_dict()`
+ method
+ """
+ pass
+
+ def _fill_in_absent_masters(self, ldap2, api_instance, result):
+ """
+ get all masters on which the role is absent
+
+ :param ldap2: LDAP connection
+ :param api_instance: API instance
+ :param result: output of `get_result_from_entries` method
+
+ :returns: list of masters on which the role is absent
+ """
+ search_base = DN(api_instance.env.container_masters,
+ api_instance.env.basedn)
+ search_filter = '(objectclass=ipaConfigObject)'
+ attrs_list = ['cn']
+
+ all_masters = ldap2.get_entries(
+ search_base,
+ filter=search_filter,
+ scope=SCOPE_ONELEVEL,
+ attrs_list=attrs_list)
+
+ all_master_cns = set(m['cn'][0] for m in all_masters)
+ enabled_configured_masters = set(r[u'server'] for r in result)
+
+ absent_masters = all_master_cns.difference(enabled_configured_masters)
+
+ return [self.create_role_status_dict(m, ABSENT) for m in
+ absent_masters]
+
+ def status(self, api_instance, server=None):
+ """
+ probe and return status of the role either on single server or on the
+ whole topology
+
+ :param api_instance: API instance
+ :param server: server FQDN. If given, only the status of the role on
+ this master will be returned
+ :returns: * 'enabled' if the role is enabled on the master
+ * 'configured' if it is not enabled but has
+ been configured by installer
+ * 'absent' otherwise
+ """
+ ldap = api_instance.Backend.ldap2
+ search_base, search_filter = self.create_search_params(
+ ldap, api_instance, server=server)
+
+ entries = self.search(ldap, search_base, search_filter=search_filter)
+
+ if not entries and server is not None:
+ return [self.create_role_status_dict(server, ABSENT)]
+
+ result = self.get_result_from_entries(entries)
+
+ if server is None:
+ result.extend(
+ self._fill_in_absent_masters(ldap, api_instance, result))
+
+ return sorted(result, key=lambda x: x[u'server'])
+
+
+class ServerAttribute(LDAPBasedProperty):
+ """
+ Class from which server attributes should be instantiated
+
+ :param associated_role: instance of a role which must be enabled
+ on the provider
+ :param associated_service_name: name of LDAP service on which the
+ attribute is set. Does not need to belong to the service entries
+ of associate role
+ :param ipa_config_string_value: value of `ipaConfigString` attribute
+ associated with the presence of server attribute
+ """
+
+ def __init__(self, name, associated_role, associated_service_name,
+ ipa_config_string_value):
+ super(ServerAttribute, self).__init__(name)
+
+ self.associated_role = associated_role
+ self.associated_service_name = associated_service_name
+ self.ipa_config_string_value = ipa_config_string_value
+
+ def create_search_filter(self, ldap):
+ """
+ Create search filter which matches LDAP data corresponding to the
+ attribute
+ """
+ svc_filter = ldap.make_filter_from_attr(
+ 'cn', self.associated_service_name)
+
+ configstring_filter = ldap.make_filter_from_attr(
+ 'ipaConfigString', self.ipa_config_string_value)
+ return ldap.combine_filters(
+ [svc_filter, configstring_filter], rules=ldap.MATCH_ALL)
+
+ def get(self, api_instance):
+ """
+ get all masters which have the attribute set
+ :param api_instance: API instance
+ :returns: list of masters providing the attribute
+ """
+ ldap = api_instance.Backend.ldap2
+ search_base = DN(api_instance.env.container_masters,
+ api_instance.env.basedn)
+
+ search_filter = self.create_search_filter(ldap)
+
+ entries = self.search(ldap, search_base, search_filter=search_filter)
+
+ masters = set(e.dn[1]['cn'] for e in entries)
+
+ associated_role_providers = set(
+ self._get_assoc_role_providers(api_instance))
+
+ if not masters.issubset(associated_role_providers):
+ raise errors.ValidationError(
+ name=self.name,
+ error=_("all masters must have %(role)s role enabled" %
+ {'role': self.associated_role.name})
+ )
+
+ return sorted(masters)
+
+ def _get_master_dn(self, api_instance, server):
+ return DN(('cn', server), api_instance.env.container_masters,
+ api_instance.env.basedn)
+
+ def _get_masters_service_entry(self, ldap, master_dn):
+ service_dn = DN(('cn', self.associated_service_name), master_dn)
+ return ldap.get_entry(service_dn)
+
+ def _add_attribute_to_svc_entry(self, ldap, service_entry):
+ """
+ add the server attribute to the entry of associated service
+
+ :param ldap: LDAP connection object
+ :param service_entry: associated service entry
+ """
+ ipa_config_string = service_entry.get('ipaConfigString', [])
+
+ ipa_config_string.append(self.ipa_config_string_value)
+
+ service_entry['ipaConfigString'] = ipa_config_string
+ ldap.update_entry(service_entry)
+
+ def _remove_attribute_from_svc_entry(self, ldap, service_entry):
+ """
+ remove the server attribute to the entry of associated service
+
+ single ipaConfigString attribute is case-insensitive, we must handle
+ arbitrary case of target value
+
+ :param ldap: LDAP connection object
+ :param service_entry: associated service entry
+ """
+ ipa_config_string = service_entry.get('ipaConfigString', [])
+
+ for value in ipa_config_string:
+ if value.lower() == self.ipa_config_string_value.lower():
+ service_entry['ipaConfigString'].remove(value)
+
+ ldap.update_entry(service_entry)
+
+ def _get_assoc_role_providers(self, api_instance):
+ """
+ get list of all servers on which the associated role is enabled
+ """
+ return [
+ r[u'server'] for r in self.associated_role.status(api_instance)
+ if r[u'status'] == ENABLED]
+
+ def _remove(self, api_instance, masters):
+ """
+ Remove masters from the attribute
+
+ :param api_instance: API instance
+ :param masters: list of masters to remove
+
+ :raises: AttrValueNotFound if there is an attempt to remove masters
+ which don't have the attribute set
+
+ """
+
+ masters_to_remove = set(masters)
+ curr_masters = set(self.get(api_instance))
+
+ if not masters_to_remove.issubset(curr_masters):
+ raise errors.AttrValueNotFound(
+ attr=self.attr_name, value=','.join(
+ masters_to_remove.difference(curr_masters)))
+
+ ldap = api_instance.Backend.ldap2
+
+ for server in masters:
+ master_dn = self._get_master_dn(api_instance, server)
+ service_entry = self._get_masters_service_entry(ldap, master_dn)
+ self._remove_attribute_from_svc_entry(ldap, service_entry)
+
+ def _add(self, api_instance, masters):
+ """
+ add IPA masters as the attribute provider
+ :param api_instance: API instance
+ :param masters: list of masters to add
+
+ :raises: * errors.ValidationError if the associated role is not enabled
+ on the master
+ """
+
+ assoc_role_providers = self._get_assoc_role_providers(api_instance)
+ ldap = api_instance.Backend.ldap2
+
+ for server in masters:
+ if server not in assoc_role_providers:
+ raise errors.ValidationError(
+ name=server,
+ error=_("must have %(role)s role enabled" %
+ {'role': self.associated_role.name})
+ )
+
+ master_dn = self._get_master_dn(api_instance, server)
+ service_entry = self._get_masters_service_entry(ldap, master_dn)
+ self._add_attribute_to_svc_entry(ldap, service_entry)
+
+ def update(self, api_instance, masters):
+ """
+ update attribute masters
+
+ :param api_instance: API instance
+ :param masters: list of new masters
+
+ :returns: new list of masters
+ :raises: errors.EmptyModlist if the new masters are the same as
+ original ones
+ """
+ old_masters = set(self.get(api_instance))
+ new_masters = set(masters)
+
+ if old_masters == new_masters:
+ raise errors.EmptyModlist
+
+ masters_to_remove = old_masters.difference(new_masters)
+ masters_to_add = new_masters.difference(old_masters)
+
+ self._add(api_instance, masters_to_add)
+
+ self._remove(api_instance, masters_to_remove)
+
+ return self.get(api_instance)
+
+
+_Service = namedtuple('Service', ['name', 'enabled'])
+
+
+class ServiceBasedRole(BaseServerRole):
+ """
+ class for all role instances whose status is defined by presence of one or
+ more entries in LDAP and/or their attributes
+ """
+
+ attrs_list = ('ipaConfigString', 'cn')
+
+ def __init__(self, name, component_services):
+ super(ServiceBasedRole, self).__init__(name)
+
+ self.component_services = component_services
+
+ def _validate_component_services(self, services):
+ svc_set = {s.name for s in services}
+ if svc_set != set(self.component_services):
+ raise ValueError(
+ "{}: Mismatch between component services and search result "
+ "(expected: {}, got: {})".format(
+ self.__class__.__name__,
+ ', '.join(sorted(self.component_services)),
+ ', '.join(sorted(s.name for s in services))))
+
+ def _get_service(self, entry):
+ entry_cn = entry['cn'][0]
+
+ enabled = self._is_service_enabled(entry)
+
+ return _Service(name=entry_cn, enabled=enabled)
+
+ def _is_service_enabled(self, entry):
+ """
+ determine whether the service is enabled based on the presence of
+ enabledService attribute in ipaConfigString attribute.
+ Since the attribute is case-insensitive, we must first lowercase its
+ values and do the comparison afterwards.
+
+ :param entry: LDAPEntry of the service
+ :returns: True if the service entry is enabled, False otherwise
+ """
+ enabled_value = 'enabledservice'
+ ipaconfigstring_values = set(
+ e.lower() for e in entry.get('ipaConfigString', []))
+
+ return enabled_value in ipaconfigstring_values
+
+ def _get_services_by_masters(self, entries):
+ """
+ given list of entries, return a dictionary keyed by master FQDNs which
+ contains list of service entries belonging to the master
+ """
+ services_by_master = defaultdict(list)
+ for e in entries:
+ service = self._get_service(e)
+ master_cn = e.dn[1]['cn']
+
+ services_by_master[master_cn].append(service)
+
+ return services_by_master
+
+ def get_result_from_entries(self, entries):
+ result = []
+ services_by_master = self._get_services_by_masters(entries)
+ for master, services in services_by_master.items():
+ self._validate_component_services(services)
+ status = (
+ ENABLED if all(s.enabled for s in services) else
+ CONFIGURED)
+
+ result.append(self.create_role_status_dict(master, status))
+
+ return result
+
+ def create_search_params(self, ldap, api_instance, server=None):
+ search_base = DN(api_instance.env.container_masters,
+ api_instance.env.basedn)
+
+ search_filter = ldap.make_filter_from_attr(
+ 'cn',
+ self.component_services,
+ rules=ldap.MATCH_ANY,
+ exact=True
+ )
+
+ if server is not None:
+ search_base = DN(('cn', server), search_base)
+
+ return search_base, search_filter
+
+
+class ADtrustBasedRole(BaseServerRole):
+ """
+ Class which should instantiate roles besed on membership in 'adtrust agent'
+ sysaccount group.
+ """
+
+ def get_result_from_entries(self, entries):
+ result = []
+
+ for e in entries:
+ result.append(
+ self.create_role_status_dict(e['fqdn'][0], ENABLED)
+ )
+ return result
+
+ def create_search_params(self, ldap, api_instance, server=None):
+ search_base = DN(
+ api_instance.env.container_host, api_instance.env.basedn)
+
+ search_filter = ldap.make_filter_from_attr(
+ "memberof",
+ DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'),
+ ('cn', 'etc'), api_instance.env.basedn)
+ )
+ if server is not None:
+ server_filter = ldap.make_filter_from_attr(
+ 'fqdn',
+ server,
+ exact=True
+ )
+ search_filter = ldap.combine_filters(
+ [search_filter, server_filter],
+ rules=ldap.MATCH_ALL
+ )
+
+ return search_base, search_filter
+
+
+ad_trust_agent = ADtrustBasedRole(u"AD trust agent")
+
+ad_trust_controller = ServiceBasedRole(
+ u"AD trust controller", component_services=['ADTRUST'])
+
+ca_server = ServiceBasedRole(u"CA server", component_services=['CA'])
+
+dns_server = ServiceBasedRole(
+ u"DNS server", component_services=['DNS', 'DNSKeySync'])
+
+ipa_master = ServiceBasedRole(
+ u"master", component_services=['HTTP', 'KDC', 'KPASSWD'])
+
+kra_server = ServiceBasedRole(u"KRA server", component_services=['KRA'])
+
+ca_renewal_master = ServerAttribute(
+ u"CA renewal master",
+ ca_server,
+ u"CA",
+ u"caRenewalMaster",
+)
+
+dnssec_key_master = ServerAttribute(
+ u"DNSSec key master",
+ dns_server,
+ u"DNSSEC",
+ u"dnssecKeyMaster",
+)
+
+
+role_instances = [
+ ad_trust_agent,
+ ad_trust_controller,
+ ca_server,
+ dns_server,
+ kra_server,
+ ipa_master
+]
+
+attribute_instances = [ca_renewal_master, dnssec_key_master]
--
2.5.5
From a6cf56e7e24515c127921962aa3cceea09109584 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Thu, 19 May 2016 11:24:18 +0200
Subject: [PATCH 2/3] Server Roles: Backend plugin to query roles and
attributes
`serverroles` backend consumes the role/attribute instances defined in
`ipaserver/servroles.py` module to provide low-level API for querying
role/attribute status in the topology. This plugin shall be used to implement
higher-level API commands.
https://www.freeipa.org/page/V4/Server_Roles
https://fedorahosted.org/freeipa/ticket/5181
---
ipaserver/plugins/serverroles.py | 155 +++++++++++++++++++++++++++++++++++++++
1 file changed, 155 insertions(+)
create mode 100644 ipaserver/plugins/serverroles.py
diff --git a/ipaserver/plugins/serverroles.py b/ipaserver/plugins/serverroles.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd514bb9716f6234d0e1e16f0ee1a56f66ec8c42
--- /dev/null
+++ b/ipaserver/plugins/serverroles.py
@@ -0,0 +1,155 @@
+#
+# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
+#
+
+
+"""
+serverroles backend
+=======================================
+
+The `serverroles` backend has access to all roles and attributes stored in
+module-level lists exposed in `ipaserver/servroles.py` module. It uses these
+lists to populate populate its internal stores with instances of the
+role/attribute classes. The information contained in them can be accessed by
+the following methods:
+
+ *api.Backend.serverroles.server_role_search(server=None, servrole=None)
+ search for roles matching the given substrings and return the status of
+ the matched roles. If `server` is not None, the search is limited to a
+ single master. Otherwise, the status is computed for all masters in the
+ topology. If `servrole` is None, the all configured roled are queried
+
+ *api.Backend.serverroles.server_role_retrieve(server, servrole)
+ retrieve the status of a single role on a given master
+
+ *api.Backend.serverroles.config_retrieve(servrole)
+ return a configuration object given role name. This object is a
+ dictionary containing a list of enabled masters and all attributes
+ associated with the role along with master(s) on which they are set.
+
+ *api.Backend.serverroles.config_update(servrole, **attrs_values)
+ update configuration associated with the role. Since the roles are
+ currently considered immutable, only server attributes associated with
+ the role can be modified
+
+Note that attribute/role names are searched/matched case-insensitively. Also
+note that the `serverroles` backend does not create/destroy any LDAP connection
+by itself, so make sure `ldap2` backend connections are taken care of
+in the calling code
+"""
+
+
+import six
+
+from ipalib import errors, _
+from ipalib.backend import Backend
+from ipalib.plugable import Registry
+from ipaserver.servroles import (
+ attribute_instances, ENABLED, role_instances)
+
+
+if six.PY3:
+ unicode = str
+
+
+register = Registry()
+
+
+@register()
+class serverroles(Backend):
+ """
+ This Backend can be used to query various information about server roles
+ and attributes configured in the topology.
+ """
+
+ def __init__(self, api_instance):
+ super(serverroles, self).__init__(api_instance)
+
+ self.roles = {r.name.lower(): r for r in role_instances}
+ self.attributes = attribute_instances
+
+ def _get_role(self, role_name):
+ key = role_name.lower()
+
+ try:
+ return self.roles[key]
+ except KeyError:
+ raise errors.NotFound(
+ reason=_("{role}: role not found".format(role=role_name)))
+
+ def _get_enabled_masters(self, role):
+ enabled_masters = [
+ r[u'server'] for r in role.status(self.api, server=None) if
+ r[u'status'] == ENABLED]
+
+ return {role.attr_name: enabled_masters}
+
+ def _get_assoc_attributes(self, role):
+ assoc_attributes = [attr for attr in self.attributes if
+ attr.associated_role is role]
+
+ if not assoc_attributes:
+ raise NotImplementedError(
+ "Role {} has no associated attribute to set".format(role.name))
+
+ return assoc_attributes
+
+ def server_role_search(self, server=None, servrole=None):
+ if servrole is None:
+ found_roles = self.roles.values()
+ else:
+ found_roles = []
+
+ key = servrole.lower()
+ for role_name, role_inst in self.roles.items():
+ if key in role_name:
+ found_roles.append(role_inst)
+
+ result = []
+ for found_role in found_roles:
+ status = found_role.status(
+ self.api, server=server)
+
+ result.extend(status)
+
+ return result
+
+ def server_role_retrieve(self, server, servrole):
+ return self._get_role(servrole).status(self.api, server=server)
+
+ def config_retrieve(self, servrole):
+ role = self._get_role(servrole)
+ result = self._get_enabled_masters(role)
+
+ try:
+ assoc_attributes = self._get_assoc_attributes(role)
+ except NotImplementedError:
+ return result
+
+ result.update(
+ {attr.attr_name: attr.get(self.api) for attr in
+ assoc_attributes})
+
+ return result
+
+ def config_update(self, servrole, **attrs_values):
+ role = self._get_role(servrole)
+ result = self._get_enabled_masters(role)
+
+ assoc_attributes = self._get_assoc_attributes(role)
+
+ for attr in assoc_attributes:
+ try:
+ masters = attrs_values.pop(attr.attr_name)
+ except KeyError:
+ continue
+
+ result.update({attr.attr_name: attr.update(self.api, masters)})
+
+ if attrs_values:
+ raise errors.NotFound(
+ reason=_(
+ 'No such attributes: {}'.format(
+ ', '.join(sorted(a for a in attrs_values)))))
+
+ return result
--
2.5.5
From d9f9a0c655c9c39095524830da1e9aabdca9675e Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Thu, 19 May 2016 13:40:12 +0200
Subject: [PATCH 3/3] Test suite for `serverroles` backend
Tests retrieving roles/attributes and setting server attributes in various
scenarios.
https://fedorahosted.org/freeipa/ticket/5181
---
ipatests/test_ipaserver/test_serverroles.py | 758 ++++++++++++++++++++++++++++
1 file changed, 758 insertions(+)
create mode 100644 ipatests/test_ipaserver/test_serverroles.py
diff --git a/ipatests/test_ipaserver/test_serverroles.py b/ipatests/test_ipaserver/test_serverroles.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e15b8f413d604fcf1592b0802e555698c0304ef
--- /dev/null
+++ b/ipatests/test_ipaserver/test_serverroles.py
@@ -0,0 +1,758 @@
+#
+# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
+#
+
+"""
+Tests for the serverroles backend
+"""
+
+from collections import namedtuple
+
+import ldap
+import pytest
+
+from ipalib import api, create_api, errors
+from ipapython.dn import DN
+from ipatests.util import MockLDAP
+
+
+def _make_service_entry_mods(enabled=True, other_config=None):
+ mods = {
+ b'objectClass': [b'top', b'nsContainer', b'ipaConfigObject'],
+ }
+ if enabled:
+ mods.update({b'ipaConfigString': [b'enabledService']})
+
+ if other_config is not None:
+ mods.setdefault(b'ipaConfigString', [])
+ mods[b'ipaConfigString'].extend(other_config)
+
+ return mods
+
+
+def _make_master_entry_mods(ca=False):
+ mods = {
+ b'objectClass': [
+ b'top',
+ b'nsContainer',
+ b'ipaReplTopoManagedServer',
+ b'ipaSupportedDomainLevelConfig',
+ b'ipaConfigObject',
+ ],
+ b'ipaMaxDomainLevel': [b'1'],
+ b'ipaMinDomainLevel': [b'0'],
+ b'ipaReplTopoManagedsuffix': [str(api.env.basedn)]
+ }
+ if ca:
+ mods[b'ipaReplTopoManagedsuffix'].append(b'o=ipaca')
+
+ return mods
+
+_adtrust_agents = DN(
+ ('cn', 'adtrust agents'),
+ ('cn', 'sysaccounts'),
+ ('cn', 'etc'),
+ api.env.basedn
+)
+
+
+master_data = {
+ 'ca-dns-dnssec-keymaster': {
+ 'services': {
+ 'CA': {
+ 'enabled': True,
+ },
+ 'DNS': {
+ 'enabled': True,
+ },
+ 'DNSKeySync': {
+ 'enabled': True,
+ },
+ 'DNSSEC': {
+ 'enabled': True,
+ 'config': ['DNSSecKeyMaster']
+ }
+ },
+ 'expected_roles': {
+ 'enabled': ['master', 'CA server', 'DNS server']
+ },
+ 'expected_attributes': {'DNS server': 'ipadnsseckeymaster'}
+ },
+ 'ca-kra-renewal-master': {
+ 'services': {
+ 'CA': {
+ 'enabled': True,
+ 'config': ['caRenewalMaster']
+ },
+ 'KRA': {
+ 'enabled': True,
+ },
+ },
+ 'expected_roles': {
+ 'enabled': ['master', 'CA server', 'KRA server']
+ },
+ 'expected_attributes': {'CA server': 'ipacarenewalmaster'}
+ },
+ 'dns-trust-agent': {
+ 'services': {
+ 'DNS': {
+ 'enabled': True,
+ },
+ 'DNSKeySync': {
+ 'enabled': True,
+ }
+ },
+ 'attributes': {
+ _adtrust_agents: {
+ 'member': ['host']
+ }
+ },
+ 'expected_roles': {
+ 'enabled': ['master', 'DNS server', 'AD trust agent']
+ }
+ },
+ 'trust-agent': {
+ 'attributes': {
+ _adtrust_agents: {
+ 'member': ['host']
+ }
+ },
+ 'expected_roles': {
+ 'enabled': ['master', 'AD trust agent']
+ }
+ },
+ 'trust-controller-dns': {
+ 'services': {
+ 'ADTRUST': {
+ 'enabled': True,
+ },
+ 'DNS': {
+ 'enabled': True,
+ },
+ 'DNSKeySync': {
+ 'enabled': True,
+ }
+ },
+ 'attributes': {
+ _adtrust_agents: {
+ 'member': ['host', 'cifs']
+ }
+ },
+ 'expected_roles': {
+ 'enabled': ['master', 'AD trust agent', 'AD trust controller',
+ 'DNS server']
+ }
+ },
+ 'trust-controller-ca': {
+ 'services': {
+ 'ADTRUST': {
+ 'enabled': True,
+ },
+ 'CA': {
+ 'enabled': True,
+ },
+ },
+ 'attributes': {
+ _adtrust_agents: {
+ 'member': ['host', 'cifs']
+ }
+ },
+ 'expected_roles': {
+ 'enabled': ['master', 'AD trust agent', 'AD trust controller',
+ 'CA server']
+ }
+ },
+ 'configured-ca': {
+ 'services': {
+ 'CA': {
+ 'enabled': False,
+ },
+ },
+ 'expected_roles': {
+ 'enabled': ['master'],
+ 'configured': ['CA server']
+ }
+ },
+ 'configured-dns': {
+ 'services': {
+ 'DNS': {
+ 'enabled': False,
+ },
+ 'DNSKeySync': {
+ 'enabled': False,
+ }
+ },
+ 'expected_roles': {
+ 'enabled': ['master'],
+ 'configured': ['DNS server']
+ }
+ },
+ 'mixed-state-dns': {
+ 'services': {
+ 'DNS': {
+ 'enabled': False
+ },
+ 'DNSKeySync': {
+ 'enabled': True
+ }
+ },
+ 'expected_roles': {
+ 'enabled': ['master'],
+ 'configured': ['DNS server']
+ }
+ },
+}
+
+
+class MockMasterTopology(object):
+ """
+ object that will set up and tear down entries in LDAP backend to mimic
+ a presence of real IPA masters with services running on them.
+ """
+
+ ipamaster_services = [u'KDC', u'HTTP', u'KPASSWD']
+
+ def __init__(self, api_instance, domain_data):
+ self.api = api_instance
+
+ self.domain = self.api.env.domain
+ self.domain_data = domain_data
+ self.masters_base = DN(
+ self.api.env.container_masters, self.api.env.basedn)
+
+ self.test_master_dn = DN(
+ ('cn', self.api.env.host), self.api.env.container_masters,
+ self.api.env.basedn)
+
+ self.ldap = MockLDAP()
+
+ self.existing_masters = {
+ m['cn'][0] for m in self.api.Command.server_find(
+ u'', sizelimit=0)['result']}
+
+ self.existing_attributes = self._check_test_host_attributes()
+
+ def iter_domain_data(self):
+ MasterData = namedtuple('MasterData',
+ ['dn', 'fqdn', 'services', 'attrs'])
+ for name in self.domain_data:
+ fqdn = self.get_fqdn(name)
+ master_dn = self.get_master_dn(name)
+ master_services = self.domain_data[name].get('services', {})
+
+ master_attributes = self.domain_data[name].get('attributes', {})
+
+ yield MasterData(
+ dn=master_dn,
+ fqdn=fqdn,
+ services=master_services,
+ attrs=master_attributes
+ )
+
+ def get_fqdn(self, name):
+ return '.'.join([name, self.domain])
+
+ def get_master_dn(self, name):
+ return DN(('cn', self.get_fqdn(name)), self.masters_base)
+
+ def get_service_dn(self, name, master_dn):
+ return DN(('cn', name), master_dn)
+
+ def _add_host_entry(self, fqdn):
+ try:
+ self.api.Command.host_add(fqdn, force=True)
+ self.api.Command.hostgroup_add_member(u'ipaservers', host=fqdn)
+ except errors.DuplicateEntry:
+ pass
+
+ def _del_host_entry(self, fqdn):
+ try:
+ self.api.Command.host_del(fqdn)
+ except errors.NotFound:
+ pass
+
+ def _add_service_entry(self, service, fqdn):
+ return self.api.Command.service_add(
+ '/'.join([service, fqdn]),
+ force=True
+ )
+
+ def _del_service_entry(self, service, fqdn):
+ try:
+ self.api.Command.service_del(
+ '/'.join([service, fqdn]),
+ )
+ except errors.NotFound:
+ pass
+
+ def _add_svc_entries(self, master_dn, svc_desc):
+ self._add_ipamaster_services(master_dn)
+ for name in svc_desc:
+ svc_dn = self.get_service_dn(name, master_dn)
+ svc_mods = svc_desc[name]
+
+ self.ldap.add_entry(
+ str(svc_dn),
+ _make_service_entry_mods(
+ enabled=svc_mods['enabled'],
+ other_config=svc_mods.get('config', None)))
+
+ def _remove_svc_master_entries(self, master_dn):
+ try:
+ entries = self.ldap.connection.search_s(
+ str(master_dn), ldap.SCOPE_SUBTREE
+ )
+ except ldap.NO_SUCH_OBJECT:
+ return
+
+ if entries:
+ entries.sort(key=lambda x: len(x[0]), reverse=True)
+ for entry_dn, attrs in entries:
+ self.ldap.del_entry(str(entry_dn))
+
+ def _add_ipamaster_services(self, master_dn):
+ """
+ add all the service entries which are part of the IPA Master role
+ """
+ for svc_name in self.ipamaster_services:
+ svc_dn = self.get_service_dn(svc_name, master_dn)
+ self.ldap.add_entry(str(svc_dn), _make_service_entry_mods())
+
+ def _add_members(self, dn, fqdn, member_attrs):
+ entry, attrs = self.ldap.connection.search_s(
+ str(dn), ldap.SCOPE_SUBTREE)[0]
+ mods = []
+ value = attrs.get('member', [])
+ mod_op = ldap.MOD_REPLACE
+ if not value:
+ mod_op = ldap.MOD_ADD
+
+ for a in member_attrs:
+
+ if a == 'host':
+ value.append(
+ str(self.api.Object.host.get_dn(fqdn)))
+ else:
+ result = self._add_service_entry(a, fqdn)['result']
+ value.append(str(result['dn']))
+
+ mods.append(
+ (mod_op, 'member', value)
+ )
+
+ self.ldap.connection.modify_s(str(dn), mods)
+
+ def _remove_members(self, dn, fqdn, member_attrs):
+ entry, attrs = self.ldap.connection.search_s(
+ str(dn), ldap.SCOPE_SUBTREE)[0]
+ mods = []
+ for a in member_attrs:
+ value = set(attrs.get('member', []))
+ if not value:
+ continue
+
+ if a == 'host':
+ try:
+ value.remove(
+ str(self.api.Object.host.get_dn(fqdn)))
+ except KeyError:
+ pass
+ else:
+ try:
+ value.remove(
+ str(self.api.Object.service.get_dn(
+ '/'.join([a, fqdn]))))
+ except KeyError:
+ pass
+ self._del_service_entry(a, fqdn)
+
+ mods.append(
+ (ldap.MOD_REPLACE, 'member', list(value))
+ )
+
+ try:
+ self.ldap.connection.modify_s(str(dn), mods)
+ except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE):
+ pass
+
+ def _check_test_host_attributes(self):
+ existing_attributes = set()
+
+ for service, value, attr_name in (
+ ('CA', 'caRenewalMaster', 'ca renewal master'),
+ ('DNSSEC', 'DNSSecKeyMaster', 'dnssec key master')):
+
+ svc_dn = DN(('cn', service), self.test_master_dn)
+ try:
+ svc_entry = self.api.Backend.ldap2.get_entry(svc_dn)
+ except errors.NotFound:
+ continue
+ else:
+ config_string_val = svc_entry.get('ipaConfigString', [])
+
+ if value in config_string_val:
+ existing_attributes.add(attr_name)
+
+ return existing_attributes
+
+ def _remove_ca_renewal_master(self):
+ if 'ca renewal master' not in self.existing_attributes:
+ return
+
+ ca_dn = DN(('cn', 'CA'), self.test_master_dn)
+ ca_entry = self.api.Backend.ldap2.get_entry(ca_dn)
+
+ config_string_val = ca_entry.get('ipaConfigString', [])
+ try:
+ config_string_val.remove('caRenewalMaster')
+ except KeyError:
+ return
+
+ ca_entry.update({'ipaConfigString': config_string_val})
+ self.api.Backend.ldap2.update_entry(ca_entry)
+
+ def _restore_ca_renewal_master(self):
+ if 'ca renewal master' not in self.existing_attributes:
+ return
+
+ ca_dn = DN(('cn', 'CA'), self.test_master_dn)
+ ca_entry = self.api.Backend.ldap2.get_entry(ca_dn)
+
+ config_string_val = ca_entry.get('ipaConfigString', [])
+ config_string_val.append('caRenewalMaster')
+
+ ca_entry.update({'ipaConfigString': config_string_val})
+ self.api.Backend.ldap2.update_entry(ca_entry)
+
+ def setup_data(self):
+ self._remove_ca_renewal_master()
+ for master_data in self.iter_domain_data():
+ # create host
+ self._add_host_entry(master_data.fqdn)
+
+ # create master entry first
+ self.ldap.add_entry(
+ str(master_data.dn), _make_master_entry_mods(
+ ca='CA' in master_data.services))
+
+ # now add service entries
+ self._add_svc_entries(master_data.dn, master_data.services)
+
+ # optionally add some attributes required e.g. by AD trust roles
+ for entry_dn, attrs in master_data.attrs.items():
+ if 'member' in attrs:
+ self._add_members(
+ entry_dn,
+ master_data.fqdn,
+ attrs['member']
+ )
+
+ def teardown_data(self):
+ self._restore_ca_renewal_master()
+ for master_data in self.iter_domain_data():
+ # first remove the master entries and service containers
+ self._remove_svc_master_entries(master_data.dn)
+
+ # optionally clean up leftover attributes
+ for entry_dn, attrs in master_data.attrs.items():
+ if 'member' in attrs:
+ self._remove_members(
+ entry_dn,
+ master_data.fqdn,
+ attrs['member'],
+ )
+
+ # finally remove host entry
+ self._del_host_entry(master_data.fqdn)
+
+
+@pytest.fixture(scope='module')
+def mock_api(request):
+ test_api = create_api(mode=None)
+ test_api.bootstrap(in_server=True, in_tree=True)
+ test_api.finalize()
+
+ if not test_api.Backend.ldap2.isconnected():
+ test_api.Backend.ldap2.connect()
+
+ return test_api
+
+
+@pytest.fixture(scope='module')
+def mock_masters(request, mock_api):
+ """
+ Populate the LDAP backend with test data
+ """
+
+ if not api.Backend.rpcclient.isconnected():
+ api.Backend.rpcclient.connect()
+
+ master_topo = MockMasterTopology(mock_api, master_data)
+
+ def finalize():
+ master_topo.teardown_data()
+ if api.Backend.rpcclient.isconnected():
+ api.Backend.rpcclient.disconnect()
+
+ request.addfinalizer(finalize)
+
+ master_topo.setup_data()
+
+ return master_topo
+
+
+def enabled_role_iter(master_data):
+ for m, data in master_data.items():
+ for role in data['expected_roles']['enabled']:
+ yield m, role
+
+
+def provided_role_iter(master_data):
+ for m, data in master_data.items():
+ yield m, data['expected_roles']['enabled']
+
+
+def configured_role_iter(master_data):
+ for m, data in master_data.items():
+ if 'configured' in data['expected_roles']:
+ for role in data['expected_roles']['configured']:
+ yield m, role
+
+
+def role_provider_iter(master_data):
+ result = {}
+ for m, data in master_data.items():
+ for role in data['expected_roles']['enabled']:
+ if role not in result:
+ result[role] = []
+
+ result[role].append(m)
+
+ for role_name, masters in result.items():
+ yield role_name, masters
+
+
+def attribute_masters_iter(master_data):
+ for m, data in master_data.items():
+ if 'expected_attributes' in data:
+ for assoc_role, attr in data['expected_attributes'].items():
+ yield m, assoc_role, attr
+
+
+def dns_servers_iter(master_data):
+ for m, data in master_data.items():
+ if "DNS server" in data['expected_roles']['enabled']:
+ yield m
+
+
+@pytest.fixture(params=list(enabled_role_iter(master_data)),
+ ids=['role: {}, master: {}, enabled'.format(role, m)
+ for m, role in enabled_role_iter(master_data)])
+def enabled_role(request):
+ return request.param
+
+
+@pytest.fixture(params=list(provided_role_iter(master_data)),
+ ids=["{}: {}".format(m, ', '.join(roles)) for m, roles in
+ provided_role_iter(master_data)])
+def provided_roles(request):
+ return request.param
+
+
+@pytest.fixture(params=list(configured_role_iter(master_data)),
+ ids=['role: {}, master: {}, configured'.format(role, m)
+ for m, role in configured_role_iter(master_data)])
+def configured_role(request):
+ return request.param
+
+
+@pytest.fixture(params=list(role_provider_iter(master_data)),
+ ids=['{} providers'.format(role_name)
+ for role_name, m in
+ role_provider_iter(master_data)])
+def role_providers(request):
+ return request.param
+
+
+@pytest.fixture(params=list(attribute_masters_iter(master_data)),
+ ids=['{} of {}: {}'.format(attr, role, m) for m, role, attr in
+ attribute_masters_iter(master_data)])
+def attribute_providers(request):
+ return request.param
+
+
+@pytest.fixture(params=list(dns_servers_iter(master_data)),
+ ids=list(dns_servers_iter(master_data)))
+def dns_server(request):
+ return request.param
+
+
+class TestServerRoleStatusRetrieval(object):
+ def retrieve_role(self, master, role, mock_api, mock_masters):
+ fqdn = mock_masters.get_fqdn(master)
+ return mock_api.Backend.serverroles.server_role_retrieve(fqdn, role)
+
+ def find_role(self, role_name, mock_api, mock_masters, master=None):
+ if master is not None:
+ hostname = mock_masters.get_fqdn(master)
+ else:
+ hostname = None
+
+ result = mock_api.Backend.serverroles.server_role_search(
+ server=hostname,
+ servrole=role_name)
+
+ return [
+ r for r in result if r[u'server'] not in
+ mock_masters.existing_masters]
+
+ def get_enabled_roles_on_master(self, master, mock_api, mock_masters):
+ fqdn = mock_masters.get_fqdn(master)
+ result = mock_api.Backend.serverroles.server_role_search(
+ server=fqdn, servrole=None
+ )
+ return sorted(
+ set(r[u'servrole'] for r in result
+ if r[u'status'] == u'enabled'))
+
+ def get_masters_with_enabled_role(self, role_name, mock_api, mock_masters):
+ result = mock_api.Backend.serverroles.server_role_search(
+ server=None, servrole=role_name)
+
+ return sorted(
+ r[u'server'] for r in result if r[u'status'] == u'enabled' and
+ r[u'server'] not in mock_masters.existing_masters)
+
+ def test_listing_of_enabled_role(
+ self, mock_api, mock_masters, enabled_role):
+ master, role_name = enabled_role
+ result = self.retrieve_role(master, role_name, mock_api, mock_masters)
+
+ assert result[0][u'status'] == u'enabled'
+
+ def test_listing_of_configured_role(
+ self, mock_api, mock_masters, configured_role):
+ master, role_name = configured_role
+ result = self.retrieve_role(master, role_name, mock_api, mock_masters)
+
+ assert result[0][u'status'] == u'configured'
+
+ def test_role_providers(
+ self, mock_api, mock_masters, role_providers):
+ role_name, providers = role_providers
+ expected_masters = sorted(mock_masters.get_fqdn(m) for m in providers)
+
+ actual_masters = self.get_masters_with_enabled_role(
+ role_name, mock_api, mock_masters)
+
+ assert expected_masters == actual_masters
+
+ def test_provided_roles_on_master(
+ self, mock_api, mock_masters, provided_roles):
+ master, expected_roles = provided_roles
+ expected_roles.sort()
+ actual_roles = self.get_enabled_roles_on_master(
+ master, mock_api, mock_masters)
+ assert expected_roles == actual_roles
+
+ def test_unknown_role_status_raises_notfound(self, mock_api, mock_masters):
+ unknown_role = 'IAP maestr'
+ fqdn = mock_masters.get_fqdn('ca-dns-dnssec-keymaster')
+ with pytest.raises(errors.NotFound):
+ mock_api.Backend.serverroles.server_role_retrieve(
+ fqdn, unknown_role)
+
+ def test_substring_search_for_dns_server(self, mock_api, mock_masters,
+ dns_server):
+ substring = 'dns'
+
+ result = self.find_role(substring, mock_api, mock_masters,
+ master=dns_server)
+
+ assert result[0]['status'] == u'enabled'
+
+ def test_empty_substring_queries_all_roles_on_server(self, mock_api,
+ mock_masters):
+ master_name = 'ca-dns-dnssec-keymaster'
+ enabled_roles = master_data[master_name]['expected_roles']['enabled']
+ result = self.find_role('', mock_api, mock_masters,
+ master=master_name)
+
+ for r in result:
+ if r[u'servrole'] in enabled_roles:
+ assert r[u'status'] == u'enabled'
+ else:
+ assert r[u'status'] == u'absent'
+
+ def test_invalid_substring_search_returns_nothing(self, mock_api,
+ mock_masters):
+ invalid_substr = 'fwfgbb'
+
+ assert (self.find_role(invalid_substr, mock_api, mock_masters,
+ 'ca-dns-dnssec-keymaster') == [])
+
+
+class TestServerAttributes(object):
+ def config_retrieve(self, assoc_role_name, mock_api):
+ return mock_api.Backend.serverroles.config_retrieve(
+ assoc_role_name)
+
+ def config_update(self, assoc_role_name, mock_api, **attrs_values):
+ return mock_api.Backend.serverroles.config_update(
+ assoc_role_name, **attrs_values
+ )
+
+ def test_attribute_master(self, mock_api, mock_masters,
+ attribute_providers):
+ master, assoc_role, attr_name = attribute_providers
+ fqdn = mock_masters.get_fqdn(master)
+ actual_attr_masters = self.config_retrieve(
+ assoc_role, mock_api)[attr_name]
+
+ assert actual_attr_masters == [fqdn]
+
+ def test_set_attribute_on_the_same_provider_raises_emptymodlist(
+ self, mock_api, mock_masters):
+ attr_name = "ipacarenewalmaster"
+ role_name = "CA server"
+
+ existing_renewal_master = self.config_retrieve(
+ role_name, mock_api)[attr_name]
+
+ with pytest.raises(errors.EmptyModlist):
+ self.config_update(
+ role_name, mock_api, **{attr_name: existing_renewal_master})
+
+ def test_set_attribute_on_master_without_assoc_role_raises_validationerror(
+ self, mock_api, mock_masters):
+ attr_name = "ipacarenewalmaster"
+ role_name = "CA server"
+ non_ca_fqdn = mock_masters.get_fqdn('trust-controller-dns')
+
+ with pytest.raises(errors.ValidationError):
+ self.config_update(role_name, mock_api,
+ **{attr_name: [non_ca_fqdn]})
+
+ def test_set_unknown_attribute_on_master_raises_notfound(
+ self, mock_api, mock_masters):
+ attr_name = "ipacarenuwalmaztah"
+ role_name = "CA server"
+ fqdn = mock_masters.get_fqdn('trust-controller-ca')
+
+ with pytest.raises(errors.NotFound):
+ self.config_update(role_name, mock_api, **{attr_name: [fqdn]})
+
+ def test_set_ca_renewal_master_on_other_ca_and_back(self, mock_api,
+ mock_masters):
+ attr_name = "ipacarenewalmaster"
+ role_name = "CA server"
+ original_renewal_master = self.config_retrieve(
+ role_name, mock_api)[attr_name][0]
+
+ other_ca_server = mock_masters.get_fqdn('trust-controller-ca')
+
+ for host in (other_ca_server, original_renewal_master):
+ result = self.config_update(role_name, mock_api,
+ **{attr_name: [host]})
+
+ assert result[attr_name] == [host]
--
2.5.5
--
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code