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, pkey_only=True, no_members=True, raw=True)['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): self.api.Command.host_add(fqdn, force=True) self.api.Command.hostgroup_add_member(u'ipaservers', host=fqdn) 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 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)
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, pkey_only=True, no_members=True, raw=True) ['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): self.api.Command.host_add(fqdn, force=True) self.api.Command.hostgroup_add_member(u'ipaservers', host=fqdn) 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 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)