Move Managed Entries into their own container in the replicated space.

Repoint cn=Managed Entries,cn=plugins,cn=config in common_setup
Create: cn=Managed Entries,cn=etc,$SUFFIX
Create: cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX
Create: cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
Create method for dynamically migrating any and all custom Managed Entries
from the cn=config space into the new container.
Separate the connection creation during update so that a restart can
be performed to initialize changes before performing a delete.
Add wait_for_open_socket() method in installutils

https://fedorahosted.org/freeipa/ticket/1708
This commit is contained in:
Jr Aquino 2011-09-08 12:07:26 -07:00 committed by Rob Crittenden
parent 7c50d17983
commit 3b633d559c
13 changed files with 238 additions and 47 deletions

View File

@ -42,6 +42,8 @@ app_DATA = \
schema_compat.uldif \ schema_compat.uldif \
ldapi.ldif \ ldapi.ldif \
wsgi.py \ wsgi.py \
repoint-managed-entries.ldif \
managed-entries.ldif \
user_private_groups.ldif \ user_private_groups.ldif \
host_nis_groups.ldif \ host_nis_groups.ldif \
uuid-ipauniqueid.ldif \ uuid-ipauniqueid.ldif \

View File

@ -1,4 +1,4 @@
dn: cn=NGP HGP Template,cn=etc,$SUFFIX dn: cn=NGP HGP Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
changetype: add changetype: add
objectclass: mepTemplateEntry objectclass: mepTemplateEntry
cn: NGP HGP Template cn: NGP HGP Template
@ -13,11 +13,11 @@ mepMappedAttr: description: ipaNetgroup $$cn
# Changes to this definition need to be reflected in # Changes to this definition need to be reflected in
# updates/20-host_nis_groups.update # updates/20-host_nis_groups.update
dn: cn=NGP Definition,cn=Managed Entries,cn=plugins,cn=config dn: cn=NGP Definition,cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX
changetype: add changetype: add
objectclass: extensibleObject objectclass: extensibleObject
cn: NGP Definition cn: NGP Definition
originScope: cn=hostgroups,cn=accounts,$SUFFIX originScope: cn=hostgroups,cn=accounts,$SUFFIX
originFilter: objectclass=ipahostgroup originFilter: objectclass=ipahostgroup
managedBase: cn=ng,cn=alt,$SUFFIX managedBase: cn=ng,cn=alt,$SUFFIX
managedTemplate: cn=NGP HGP Template,cn=etc,$SUFFIX managedTemplate: cn=NGP HGP Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX

View File

@ -0,0 +1,17 @@
dn: cn=Managed Entries,cn=etc,$SUFFIX
changetype: add
objectClass: nsContainer
objectClass: top
cn: Managed Entries
dn: cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
changetype: add
objectClass: nsContainer
objectClass: top
cn: Templates
dn: cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX
changetype: add
objectClass: nsContainer
objectClass: top
cn: Definitions

View File

@ -0,0 +1,5 @@
# Repoint Managed Entries to the replicated cn=etc space
dn: cn=Managed Entries,cn=plugins,cn=config
changetype: modify
add: nsslapd-pluginConfigArea
nsslapd-pluginConfigArea: cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX

View File

@ -1,4 +1,4 @@
dn: cn=UPG Template,cn=etc,$SUFFIX dn: cn=UPG Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
changetype: add changetype: add
objectclass: mepTemplateEntry objectclass: mepTemplateEntry
cn: UPG Template cn: UPG Template
@ -12,12 +12,12 @@ mepMappedAttr: description: User private group for $$uid
# Changes to this definition need to be reflected in # Changes to this definition need to be reflected in
# updates/20-user_private_groups.update # updates/20-user_private_groups.update
dn: cn=UPG Definition,cn=Managed Entries,cn=plugins,cn=config dn: cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX
changetype: add changetype: add
objectclass: extensibleObject objectclass: extensibleObject
cn: UPG Definition cn: UPG Definition
originScope: cn=users,cn=accounts,$SUFFIX originScope: cn=users,cn=accounts,$SUFFIX
originFilter: (&(objectclass=posixAccount)(!(description=__no_upg__))) originFilter: (&(objectclass=posixAccount)(!(description=__no_upg__)))
managedBase: cn=groups,cn=accounts,$SUFFIX managedBase: cn=groups,cn=accounts,$SUFFIX
managedTemplate: cn=UPG Template,cn=etc,$SUFFIX managedTemplate: cn=UPG Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX

View File

@ -0,0 +1,17 @@
dn: cn=Managed Entries,cn=plugins,cn=config
only: nsslapd-pluginConfigArea: 'cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX'
dn: cn=Managed Entries,cn=etc,$SUFFIX
default: objectClass: nsContainer
default: objectClass: top
default: cn: Managed Entries
dn: cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
default: objectClass: nsContainer
default: objectClass: top
default: cn: Templates
dn: cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX
default: objectClass: nsContainer
default: objectClass: top
default: cn: Definitions

View File

@ -2,14 +2,22 @@
# This is required for replication. The template entry will get # This is required for replication. The template entry will get
# replicated but the plugin configuration will not. # replicated but the plugin configuration will not.
dn: cn=NGP Definition,cn=Managed Entries,cn=plugins,cn=config dn: cn=NGP HGP Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
default:objectclass: mepTemplateEntry
default:cn: NGP HGP Template
default:mepRDNAttr: cn
default:mepStaticAttr: ipaUniqueId: autogenerate
default:mepStaticAttr: objectclass: ipanisnetgroup
default:mepStaticAttr: objectclass: ipaobject
default:mepStaticAttr: nisDomainName: $DOMAIN
default:mepMappedAttr: cn: $$cn
default:mepMappedAttr: memberHost: $$dn
default:mepMappedAttr: description: ipaNetgroup $$cn
dn: cn=NGP Definition,cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX
default:objectclass: extensibleObject default:objectclass: extensibleObject
default:cn: NGP Definition only:cn: NGP Definition
default:originScope: cn=hostgroups,cn=accounts,$SUFFIX default:originScope: cn=hostgroups,cn=accounts,$SUFFIX
default:originFilter: objectclass=ipahostgroup default:originFilter: objectclass=ipahostgroup
default:managedBase: cn=ng,cn=alt,$SUFFIX default:managedBase: cn=ng,cn=alt,$SUFFIX
default:managedTemplate: cn=NGP HGP Template,cn=etc,$SUFFIX default:managedTemplate: cn=NGP HGP Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
# Fix an existing configuration with the wrong cn
dn: cn=NGP Definition,cn=Managed Entries,cn=plugins,cn=config
only:cn: NGP Definition

View File

@ -2,10 +2,23 @@
# This is required for replication. The template entry will get # This is required for replication. The template entry will get
# replicated but the plugin configuration will not. # replicated but the plugin configuration will not.
dn: cn=UPG Definition,cn=Managed Entries,cn=plugins,cn=config dn: cn=UPG Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX
default:objectclass: mepTemplateEntry
default:cn: UPG Template
default:mepRDNAttr: cn
default:mepStaticAttr: objectclass: posixgroup
default:mepStaticAttr: objectclass: ipaobject
default:mepStaticAttr: ipaUniqueId: autogenerate
default:mepMappedAttr: cn: $$uid
default:mepMappedAttr: gidNumber: $$uidNumber
default:mepMappedAttr: description: User private group for $$uid
dn: cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,$SUFFIX
default:objectclass: extensibleObject default:objectclass: extensibleObject
replace:originFilter:objectclass=posixAccount::(&(objectclass=posixAccount)(!(description=__no_upg__)))
default:cn: UPG Definition default:cn: UPG Definition
default:originScope: cn=users,cn=accounts,$SUFFIX default:originScope: cn=users,cn=accounts,$SUFFIX
default:originFilter: (&(objectclass=posixAccount)(!(description=__no_upg__))) default:originFilter: objectclass=posixAccount
default:managedBase: cn=groups,cn=accounts,$SUFFIX default:managedBase: cn=groups,cn=accounts,$SUFFIX
default:managedTemplate: cn=UPG Template,cn=etc,$SUFFIX default:managedTemplate: cn=UPG Template,cn=Templates,cn=Managed Entries,cn=etc,$SUFFIX

View File

@ -1,2 +0,0 @@
dn: cn=UPG Definition,cn=Managed Entries,cn=plugins,cn=config
replace: originFilter:objectclass=posixAccount::(&(objectclass=posixAccount)(!(description=__no_upg__)))

View File

@ -7,6 +7,7 @@ app_DATA = \
10-RFC4876.update \ 10-RFC4876.update \
10-config.update \ 10-config.update \
10-sudo.update \ 10-sudo.update \
19-managed-entries.update \
20-aci.update \ 20-aci.update \
20-dna.update \ 20-dna.update \
20-host_nis_groups.update \ 20-host_nis_groups.update \
@ -22,7 +23,6 @@ app_DATA = \
50-lockout-policy.update \ 50-lockout-policy.update \
50-groupuuid.update \ 50-groupuuid.update \
50-hbacservice.update \ 50-hbacservice.update \
50-suppress-upg.update \
$(NULL) $(NULL)
EXTRA_DIST = \ EXTRA_DIST = \

View File

@ -201,6 +201,7 @@ class DsInstance(service.Service):
self.step("configuring ssl for ds instance", self.__enable_ssl) self.step("configuring ssl for ds instance", self.__enable_ssl)
self.step("configuring certmap.conf", self.__certmap_conf) self.step("configuring certmap.conf", self.__certmap_conf)
self.step("configure autobind for root", self.__root_autobind) self.step("configure autobind for root", self.__root_autobind)
self.step("configure new location for managed entries", self.__repoint_managed_entries)
self.step("restarting directory server", self.__restart_instance) self.step("restarting directory server", self.__restart_instance)
def __common_post_setup(self): def __common_post_setup(self):
@ -237,6 +238,7 @@ class DsInstance(service.Service):
self.step("adding default layout", self.__add_default_layout) self.step("adding default layout", self.__add_default_layout)
self.step("adding delegation layout", self.__add_delegation_layout) self.step("adding delegation layout", self.__add_delegation_layout)
self.step("adding replication acis", self.__add_replication_acis) self.step("adding replication acis", self.__add_replication_acis)
self.step("creating container for managed entries", self.__managed_entries)
self.step("configuring user private groups", self.__user_private_groups) self.step("configuring user private groups", self.__user_private_groups)
self.step("configuring netgroups from hostgroups", self.__host_nis_groups) self.step("configuring netgroups from hostgroups", self.__host_nis_groups)
self.step("creating default Sudo bind user", self.__add_sudo_binduser) self.step("creating default Sudo bind user", self.__add_sudo_binduser)
@ -277,8 +279,6 @@ class DsInstance(service.Service):
# See LDIFs for automember configuration during replica install # See LDIFs for automember configuration during replica install
self.step("setting Auto Member configuration", self.__add_replica_automember_config) self.step("setting Auto Member configuration", self.__add_replica_automember_config)
# Managed Entries configuration is done via update files
self.__common_post_setup() self.__common_post_setup()
self.start_creation("Configuring directory server", 60) self.start_creation("Configuring directory server", 60)
@ -485,6 +485,16 @@ class DsInstance(service.Service):
def __config_lockout_module(self): def __config_lockout_module(self):
self._ldap_mod("lockout-conf.ldif") self._ldap_mod("lockout-conf.ldif")
def __repoint_managed_entries(self):
if not has_managed_entries(self.fqdn, self.dm_password):
raise errors.NotFound(reason='Missing Managed Entries Plugin')
self._ldap_mod("repoint-managed-entries.ldif", self.sub_dict)
def __managed_entries(self):
if not has_managed_entries(self.fqdn, self.dm_password):
raise errors.NotFound(reason='Missing Managed Entries Plugin')
self._ldap_mod("managed-entries.ldif", self.sub_dict)
def __user_private_groups(self): def __user_private_groups(self):
if not has_managed_entries(self.fqdn, self.dm_password): if not has_managed_entries(self.fqdn, self.dm_password):
raise errors.NotFound(reason='Missing Managed Entries Plugin') raise errors.NotFound(reason='Missing Managed Entries Plugin')

View File

@ -440,6 +440,27 @@ def wait_for_open_ports(host, ports, timeout=0):
else: else:
raise e raise e
def wait_for_open_socket(socket_name, timeout=0):
"""
Wait until the specified socket on the local host is open. Timeout
in seconds may be specified to limit the wait.
"""
op_timeout = time.time() + timeout
while True:
try:
s = socket.socket(socket.AF_UNIX)
s.connect(socket_name)
s.close()
break;
except socket.error, e:
if e.errno == 111: # 111: Connection refused
if timeout and time.time() > op_timeout: # timeout exceeded
raise e
time.sleep(1)
else:
raise e
def resolve_host(host_name): def resolve_host(host_name):
try: try:
addrinfos = socket.getaddrinfo(host_name, None, addrinfos = socket.getaddrinfo(host_name, None,

View File

@ -26,6 +26,7 @@ UPDATES_DIR="/usr/share/ipa/updates/"
import sys import sys
from ipaserver.install import installutils from ipaserver.install import installutils
from ipaserver.install import service
from ipaserver import ipaldap from ipaserver import ipaldap
from ipapython import entity, ipautil from ipapython import entity, ipautil
from ipalib import util from ipalib import util
@ -48,6 +49,24 @@ class BadSyntax(Exception):
def __str__(self): def __str__(self):
return repr(self.value) return repr(self.value)
class IPARestart(service.Service):
"""
Restart the 389 DS service prior to performing deletions.
"""
def __init__(self, live_run=True):
"""
This class is present to provide ldapupdate the means to
restart 389 DS to apply updates prior to performing deletes.
"""
service.Service.__init__(self, "dirsrv")
self.live_run = live_run
def create_instance(self):
self.step("stopping directory server", self.stop)
self.step("starting directory server", self.start)
self.start_creation("Restarting IPA to initialize updates before performing deletes:")
class LDAPUpdate: class LDAPUpdate:
def __init__(self, dm_password, sub_dict={}, live_run=True, def __init__(self, dm_password, sub_dict={}, live_run=True,
online=True, ldapi=False): online=True, ldapi=False):
@ -64,7 +83,6 @@ class LDAPUpdate:
self.modified = False self.modified = False
self.online = online self.online = online
self.ldapi = ldapi self.ldapi = ldapi
self.pw_name = pwd.getpwuid(os.geteuid()).pw_name self.pw_name = pwd.getpwuid(os.geteuid()).pw_name
if sub_dict.get("REALM"): if sub_dict.get("REALM"):
@ -418,6 +436,54 @@ class LDAPUpdate:
return self.conn.getList(dn, scope, searchfilter, sattrs) return self.conn.getList(dn, scope, searchfilter, sattrs)
def __update_managed_entries(self):
"""Update and move legacy Managed Entry Plugins."""
suffix = ipautil.realm_to_suffix(self.realm)
searchfilter = '(objectclass=*)'
definitions_managed_entries = []
old_template_container = 'cn=etc,%s' % suffix
old_definition_container = 'cn=Managed Entries,cn=plugins,cn=config'
new = 'cn=Managed Entries,cn=etc,%s' % suffix
sub = ['cn=Definitions,', 'cn=Templates,']
new_managed_entries = []
old_templates = []
template = None
try:
definitions_managed_entries = self.conn.getList(old_definition_container, ldap.SCOPE_ONELEVEL, searchfilter,[])
except errors.NotFound, e:
return new_managed_entries
for entry in definitions_managed_entries:
new_definition = {}
definition_managed_entry_updates = {}
definitions_managed_entries
old_definition = {'dn': entry.dn, 'deleteentry': ['dn: %s' % entry.dn]}
old_template = entry.getValue('managedtemplate')
entry.setValues('managedtemplate', entry.getValue('managedtemplate').replace(old_template_container, sub[1] + new))
new_definition['dn'] = entry.dn.replace(old_definition_container, sub[0] + new)
new_definition['default'] = str(entry).strip().replace(': ', ':').split('\n')[1:]
definition_managed_entry_updates[new_definition['dn']] = new_definition
definition_managed_entry_updates[old_definition['dn']] = old_definition
old_templates.append(old_template)
new_managed_entries.append(definition_managed_entry_updates)
for old_template in old_templates:
try:
template = self.conn.getEntry(old_template, ldap.SCOPE_BASE, searchfilter,[])
new_template = {}
template_managed_entry_updates = {}
old_template = {'dn': template.dn, 'deleteentry': ['dn: %s' % template.dn]}
new_template['dn'] = template.dn.replace(old_template_container, sub[1] + new)
new_template['default'] = str(template).strip().replace(': ', ':').split('\n')[1:]
template_managed_entry_updates[new_template['dn']] = new_template
template_managed_entry_updates[old_template['dn']] = old_template
new_managed_entries.append(template_managed_entry_updates)
except errors.NotFound, e:
pass
if len(new_managed_entries) > 0:
new_managed_entries.sort(reverse=True)
return new_managed_entries
def __apply_updates(self, updates, entry): def __apply_updates(self, updates, entry):
"""updates is a list of changes to apply """updates is a list of changes to apply
entry is the thing to apply them to entry is the thing to apply them to
@ -431,7 +497,6 @@ class LDAPUpdate:
for u in updates: for u in updates:
# We already do syntax-parsing so this is safe # We already do syntax-parsing so this is safe
(utype, k, values) = u.split(':',2) (utype, k, values) = u.split(':',2)
values = self.__parse_values(values) values = self.__parse_values(values)
e = entry.getValues(k) e = entry.getValues(k)
@ -440,7 +505,6 @@ class LDAPUpdate:
e = [] e = []
else: else:
e = [e] e = [e]
for v in values: for v in values:
if utype == 'remove': if utype == 'remove':
logging.debug("remove: '%s' from %s, current value %s", v, k, e) logging.debug("remove: '%s' from %s, current value %s", v, k, e)
@ -629,6 +693,18 @@ class LDAPUpdate:
and child in the wrong order. and child in the wrong order.
""" """
dn = updates['dn'] dn = updates['dn']
deletes = updates.get('deleteentry', [])
for d in deletes:
try:
if self.live_run:
self.conn.deleteEntry(dn)
self.modified = True
except errors.NotFound, e:
logging.info("Deleting non-existent entry %s", e)
self.modified = True
except errors.DatabaseError, e:
logging.error("Delete failed: %s", e)
updates = updates.get('updates', []) updates = updates.get('updates', [])
for u in updates: for u in updates:
# We already do syntax-parsing so this is safe # We already do syntax-parsing so this is safe
@ -659,13 +735,7 @@ class LDAPUpdate:
f.sort() f.sort()
return f return f
def update(self, files): def create_connection(self):
"""Execute the update. files is a list of the update files to use.
returns True if anything was changed, otherwise False
"""
try:
if self.online: if self.online:
if self.ldapi: if self.ldapi:
self.conn = ipaldap.IPAdmin(ldapi=True, realm=self.realm) self.conn = ipaldap.IPAdmin(ldapi=True, realm=self.realm)
@ -689,6 +759,15 @@ class LDAPUpdate:
raise RuntimeError('%s' % e.args[0].get('info', '').strip()) raise RuntimeError('%s' % e.args[0].get('info', '').strip())
else: else:
raise RuntimeError("Offline updates are not supported.") raise RuntimeError("Offline updates are not supported.")
def update(self, files):
"""Execute the update. files is a list of the update files to use.
returns True if anything was changed, otherwise False
"""
try:
self.create_connection()
all_updates = {} all_updates = {}
dn_list = {} dn_list = {}
for f in files: for f in files:
@ -701,6 +780,20 @@ class LDAPUpdate:
(all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list) (all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list)
# Process Managed Entry Updates
managed_entries = self.__update_managed_entries()
if managed_entries:
managed_entry_dns = [[m[entry]['dn'] for entry in m] for m in managed_entries]
l = len(dn_list.keys())
# Add Managed Entry DN's to the DN List
for dn in managed_entry_dns:
l+=1
dn_list[l] = dn
# Add Managed Entry Updates to All Updates List
for managed_entry in managed_entries:
all_updates.update(managed_entry)
# For adds and updates we want to apply updates from shortest # For adds and updates we want to apply updates from shortest
# to greatest length of the DN. For deletes we want the reverse. # to greatest length of the DN. For deletes we want the reverse.
sortedkeys = dn_list.keys() sortedkeys = dn_list.keys()
@ -709,6 +802,13 @@ class LDAPUpdate:
for dn in dn_list[k]: for dn in dn_list[k]:
self.__update_record(all_updates[dn]) self.__update_record(all_updates[dn])
# Restart 389 Directory Service
socket_name = '/var/run/slapd-%s.socket' % self.realm.replace('.','-')
iparestart = IPARestart()
iparestart.create_instance()
installutils.wait_for_open_socket(socket_name)
self.create_connection()
sortedkeys.reverse() sortedkeys.reverse()
for k in sortedkeys: for k in sortedkeys:
for dn in dn_list[k]: for dn in dn_list[k]: