mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
server-del: perform full master removal in managed topology
This patch implements most of the del_master_managed() functionality as a part
of `server-del` command.
`server-del` nows performs these actions:
* check topology connectivity
* check that at least one CA/DNS server and DNSSec masters are left
after removal
* cleanup all LDAP entries/attributes exposing information about the master
* cleanup master DNS records
* remove master and service principals
* remove master entry from LDAP
* check that all segments pointing to the master were removed
`server-del` now accepts the following options:
* `--force`: force master removal even if it doesn't exist
* `--ignore-topology-disconnect`: ignore errors arising from disconnected
topology before and after master removal
* `--ignore-last-of-role`: remove master even if it is last DNS server,
and DNSSec key master. The last CA will *not* be removed regardless of
this option.
https://fedorahosted.org/freeipa/ticket/5588
Reviewed-By: Martin Basti <mbasti@redhat.com>
This commit is contained in:
committed by
Martin Basti
parent
db882ae8d6
commit
a6eb87bd68
5
API.txt
5
API.txt
@@ -4164,9 +4164,12 @@ output: Output('result', type=[<type 'bool'>])
|
|||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
output: PrimaryKey('value')
|
output: PrimaryKey('value')
|
||||||
command: server_del
|
command: server_del
|
||||||
args: 1,2,3
|
args: 1,5,3
|
||||||
arg: Str('cn+', cli_name='name')
|
arg: Str('cn+', cli_name='name')
|
||||||
option: Flag('continue', autofill=True, cli_name='continue', default=False)
|
option: Flag('continue', autofill=True, cli_name='continue', default=False)
|
||||||
|
option: Flag('force?', autofill=True, default=False)
|
||||||
|
option: Flag('ignore_last_of_role?', autofill=True, default=False)
|
||||||
|
option: Flag('ignore_topology_disconnect?', autofill=True, default=False)
|
||||||
option: Str('version?')
|
option: Str('version?')
|
||||||
output: Output('result', type=[<type 'dict'>])
|
output: Output('result', type=[<type 'dict'>])
|
||||||
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
|
||||||
|
|||||||
4
VERSION
4
VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
|
|||||||
# #
|
# #
|
||||||
########################################################
|
########################################################
|
||||||
IPA_API_VERSION_MAJOR=2
|
IPA_API_VERSION_MAJOR=2
|
||||||
IPA_API_VERSION_MINOR=187
|
IPA_API_VERSION_MINOR=188
|
||||||
# Last change: mbasti - rename ipalocationweight to ipaserviceweight
|
# Last change: mbabinsk - extend server-del to perform full master removal
|
||||||
|
|||||||
17
ipaclient/plugins/server.py
Normal file
17
ipaclient/plugins/server.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||||
|
#
|
||||||
|
|
||||||
|
from ipaclient.frontend import MethodOverride
|
||||||
|
from ipalib import _
|
||||||
|
from ipalib.plugable import Registry
|
||||||
|
|
||||||
|
register = Registry()
|
||||||
|
|
||||||
|
|
||||||
|
@register(override=True)
|
||||||
|
class server_del(MethodOverride):
|
||||||
|
def interactive_prompt_callback(self, kw):
|
||||||
|
self.api.Backend.textui.print_plain(
|
||||||
|
_("Removing %(servers)s from replication topology, "
|
||||||
|
"please wait...") % {'servers': ', '.join(kw['cn'])})
|
||||||
@@ -1379,6 +1379,24 @@ class InvalidDomainLevelError(ExecutionError):
|
|||||||
errno = 4032
|
errno = 4032
|
||||||
format = _('%(reason)s')
|
format = _('%(reason)s')
|
||||||
|
|
||||||
|
|
||||||
|
class ServerRemovalError(ExecutionError):
|
||||||
|
"""
|
||||||
|
**4033** Raised when a removal of IPA server from managed topology fails
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> raise ServerRemovalError(reason='Removal disconnects topology')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ServerRemovalError: Server removal aborted: Removal disconnects topology
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
errno = 4033
|
||||||
|
format = _('Server removal aborted: %(reason)s.')
|
||||||
|
|
||||||
|
|
||||||
class BuiltinError(ExecutionError):
|
class BuiltinError(ExecutionError):
|
||||||
"""
|
"""
|
||||||
**4100** Base class for builtin execution errors (*4100 - 4199*).
|
**4100** Base class for builtin execution errors (*4100 - 4199*).
|
||||||
|
|||||||
@@ -364,7 +364,6 @@ class ResultFormattingError(PublicMessage):
|
|||||||
**13019** Unable to correctly format some part of the result
|
**13019** Unable to correctly format some part of the result
|
||||||
"""
|
"""
|
||||||
errno = 13019
|
errno = 13019
|
||||||
type = "warning"
|
|
||||||
|
|
||||||
|
|
||||||
class FailedToRemoveHostDNSRecords(PublicMessage):
|
class FailedToRemoveHostDNSRecords(PublicMessage):
|
||||||
@@ -446,6 +445,22 @@ class LocationWithoutDNSServer(PublicMessage):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerRemovalInfo(PublicMessage):
|
||||||
|
"""
|
||||||
|
**13027** Informative message printed during removal of IPA server
|
||||||
|
"""
|
||||||
|
errno = 13027
|
||||||
|
type = "info"
|
||||||
|
|
||||||
|
|
||||||
|
class ServerRemovalWarning(PublicMessage):
|
||||||
|
"""
|
||||||
|
**13028** Warning raised during removal of IPA server
|
||||||
|
"""
|
||||||
|
errno = 13028
|
||||||
|
type = "warning"
|
||||||
|
|
||||||
|
|
||||||
def iter_messages(variables, base):
|
def iter_messages(variables, base):
|
||||||
"""Return a tuple with all subclasses
|
"""Return a tuple with all subclasses
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
import dbus
|
import dbus
|
||||||
import dbus.mainloop.glib
|
import dbus.mainloop.glib
|
||||||
|
import ldap
|
||||||
|
import time
|
||||||
|
|
||||||
from ipalib import api, crud, errors, messages
|
from ipalib import api, crud, errors, messages
|
||||||
from ipalib import Int, Str, DNSNameParam
|
from ipalib import Int, Flag, Str, DNSNameParam
|
||||||
from ipalib.plugable import Registry
|
from ipalib.plugable import Registry
|
||||||
from .baseldap import (
|
from .baseldap import (
|
||||||
LDAPSearch,
|
LDAPSearch,
|
||||||
@@ -21,7 +23,9 @@ from ipalib import output
|
|||||||
from ipaplatform import services
|
from ipaplatform import services
|
||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
from ipapython.dnsutil import DNSName
|
from ipapython.dnsutil import DNSName
|
||||||
|
from ipaserver import topology
|
||||||
from ipaserver.servroles import ENABLED
|
from ipaserver.servroles import ENABLED
|
||||||
|
from ipaserver.install import bindinstance, dnskeysyncinstance
|
||||||
|
|
||||||
__doc__ = _("""
|
__doc__ = _("""
|
||||||
IPA servers
|
IPA servers
|
||||||
@@ -421,9 +425,380 @@ class server_show(LDAPRetrieve):
|
|||||||
@register()
|
@register()
|
||||||
class server_del(LDAPDelete):
|
class server_del(LDAPDelete):
|
||||||
__doc__ = _('Delete IPA server.')
|
__doc__ = _('Delete IPA server.')
|
||||||
NO_CLI = True
|
|
||||||
msg_summary = _('Deleted IPA server "%(value)s"')
|
msg_summary = _('Deleted IPA server "%(value)s"')
|
||||||
|
|
||||||
|
takes_options = LDAPDelete.takes_options + (
|
||||||
|
Flag(
|
||||||
|
'ignore_topology_disconnect?',
|
||||||
|
label=_('Ignore topology errors'),
|
||||||
|
doc=_('Ignore topology connectivity problems after removal'),
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
Flag(
|
||||||
|
'ignore_last_of_role?',
|
||||||
|
label=_('Ignore check for last remaining CA or DNS server'),
|
||||||
|
doc=_('Skip a check whether the last CA master or DNS server is '
|
||||||
|
'removed'),
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
Flag(
|
||||||
|
'force?',
|
||||||
|
label=_('Force server removal'),
|
||||||
|
doc=_('Force server removal even if it does not exist'),
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _ensure_last_of_role(self, hostname, ignore_last_of_role=False):
|
||||||
|
"""
|
||||||
|
1. When deleting server, check if there will be at least one remaining
|
||||||
|
DNS and CA server.
|
||||||
|
2. Pick CA renewal master
|
||||||
|
"""
|
||||||
|
def handler(msg, ignore_last_of_role):
|
||||||
|
if ignore_last_of_role:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=msg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise errors.ServerRemovalError(reason=_(msg))
|
||||||
|
|
||||||
|
ipa_config = self.api.Command.config_show()['result']
|
||||||
|
dns_config = self.api.Command.dnsconfig_show()['result']
|
||||||
|
|
||||||
|
ipa_masters = ipa_config['ipa_master_server']
|
||||||
|
|
||||||
|
# skip these checks if the last master is being removed
|
||||||
|
if ipa_masters == [hostname]:
|
||||||
|
return
|
||||||
|
|
||||||
|
ca_servers = ipa_config['ca_server_server']
|
||||||
|
ca_renewal_master = ipa_config['ca_renewal_master_server']
|
||||||
|
dns_servers = dns_config['dns_server_server']
|
||||||
|
dnssec_keymaster = dns_config['dnssec_key_master_server']
|
||||||
|
|
||||||
|
if ca_servers == [hostname]:
|
||||||
|
raise errors.ServerRemovalError(
|
||||||
|
reason=_("Deleting this server is not allowed as it would "
|
||||||
|
"leave your installation without a CA."))
|
||||||
|
|
||||||
|
if dnssec_keymaster == hostname:
|
||||||
|
handler(
|
||||||
|
_("Replica is active DNSSEC key master. Uninstall "
|
||||||
|
"could break your DNS system. Please disable or "
|
||||||
|
"replace DNSSEC key master first."), ignore_last_of_role)
|
||||||
|
|
||||||
|
if dns_servers == [hostname]:
|
||||||
|
handler(
|
||||||
|
_("Deleting this server will leave your installation "
|
||||||
|
"without a DNS."), ignore_last_of_role)
|
||||||
|
|
||||||
|
if ignore_last_of_role:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_("Ignoring these warnings and proceeding with "
|
||||||
|
"removal")))
|
||||||
|
|
||||||
|
if ca_renewal_master == hostname:
|
||||||
|
other_cas = [ca for ca in ca_servers if ca != hostname]
|
||||||
|
|
||||||
|
# if this is the last CA there is no other server to become renewal
|
||||||
|
# master
|
||||||
|
if not other_cas:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.api.Command.config_mod(ca_renewal_master_server=other_cas[0])
|
||||||
|
|
||||||
|
def _check_topology_connectivity(self, topology_connectivity, master_cn):
|
||||||
|
try:
|
||||||
|
topology_connectivity.check_current_state()
|
||||||
|
except ValueError as e:
|
||||||
|
raise errors.ServerRemovalError(reason=e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
topology_connectivity.check_state_after_removal(master_cn)
|
||||||
|
except ValueError as e:
|
||||||
|
raise errors.ServerRemovalError(reason=e)
|
||||||
|
|
||||||
|
def _remove_server_principal_references(self, master):
|
||||||
|
"""
|
||||||
|
This method removes information about the replica in parts
|
||||||
|
of the shared tree that expose it, so clients stop trying to
|
||||||
|
use this replica.
|
||||||
|
"""
|
||||||
|
conn = self.Backend.ldap2
|
||||||
|
env = self.api.env
|
||||||
|
|
||||||
|
master_principal = "{}@{}".format(master, env)
|
||||||
|
|
||||||
|
# remove replica memberPrincipal from s4u2proxy configuration
|
||||||
|
s4u2proxy_subtree = DN(env.container_s4u2proxy,
|
||||||
|
env.basedn)
|
||||||
|
dn1 = DN(('cn', 'ipa-http-delegation'), s4u2proxy_subtree)
|
||||||
|
member_principal1 = "HTTP/{}".format(master_principal)
|
||||||
|
|
||||||
|
dn2 = DN(('cn', 'ipa-ldap-delegation-targets'), s4u2proxy_subtree)
|
||||||
|
member_principal2 = "ldap/{}".format(master_principal)
|
||||||
|
|
||||||
|
dn3 = DN(('cn', 'ipa-cifs-delegation-targets'), s4u2proxy_subtree)
|
||||||
|
member_principal3 = "cifs/{}".format(master_principal)
|
||||||
|
|
||||||
|
for (dn, member_principal) in ((dn1, member_principal1),
|
||||||
|
(dn2, member_principal2),
|
||||||
|
(dn3, member_principal3)):
|
||||||
|
try:
|
||||||
|
mod = [(ldap.MOD_DELETE, 'memberPrincipal', member_principal)]
|
||||||
|
conn.conn.modify_s(str(dn), mod)
|
||||||
|
except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE):
|
||||||
|
self.log.debug(
|
||||||
|
"Replica (%s) memberPrincipal (%s) not found in %s" %
|
||||||
|
(master, member_principal, dn))
|
||||||
|
except Exception as e:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_("Failed to clean memberPrincipal "
|
||||||
|
"%(principal)s from s4u2proxy entry %(dn)s: "
|
||||||
|
"%(err)s") % dict(
|
||||||
|
principal=member_principal,
|
||||||
|
dn=dn, err=e)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
etc_basedn = DN(('cn', 'etc'), env.basedn)
|
||||||
|
filter = '(dnaHostname=%s)' % master
|
||||||
|
entries = conn.get_entries(
|
||||||
|
etc_basedn, ldap.SCOPE_SUBTREE, filter=filter)
|
||||||
|
if len(entries) != 0:
|
||||||
|
for entry in entries:
|
||||||
|
conn.delete_entry(entry)
|
||||||
|
except errors.NotFound:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_(
|
||||||
|
"Failed to clean up DNA hostname entries for "
|
||||||
|
"%(master)s: %(err)s") % dict(master=master, err=e)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
dn = DN(('cn', 'default'), ('ou', 'profile'), env.basedn)
|
||||||
|
ret = conn.get_entry(dn)
|
||||||
|
srvlist = ret.single_value.get('defaultServerList', '')
|
||||||
|
srvlist = srvlist[0].split()
|
||||||
|
if master in srvlist:
|
||||||
|
srvlist.remove(master)
|
||||||
|
attr = ' '.join(srvlist)
|
||||||
|
mod = [(ldap.MOD_REPLACE, 'defaultServerList', attr)]
|
||||||
|
conn.conn.modify_s(str(dn), mod)
|
||||||
|
except (errors.NotFound, ldap.NO_SUCH_ATTRIBUTE,
|
||||||
|
ldap.TYPE_OR_VALUE_EXISTS):
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_("Failed to remove server %(master)s from server "
|
||||||
|
"list: %(err)s") % dict(master=master, err=e)))
|
||||||
|
|
||||||
|
def _remove_server_host_services(self, ldap, master):
|
||||||
|
"""
|
||||||
|
delete server kerberos key and all its svc principals
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
entries = ldap.get_entries(
|
||||||
|
self.api.env.basedn, ldap.SCOPE_SUBTREE,
|
||||||
|
filter='(krbprincipalname=*/{}@{})'.format(
|
||||||
|
master, self.api.env.realm))
|
||||||
|
|
||||||
|
if entries:
|
||||||
|
entries.sort(key=lambda x: len(x.dn), reverse=True)
|
||||||
|
for entry in entries:
|
||||||
|
ldap.delete_entry(entry)
|
||||||
|
except errors.NotFound:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_("Failed to cleanup server principals/keys: "
|
||||||
|
"%(err)s") % dict(err=e)))
|
||||||
|
|
||||||
|
def _cleanup_server_dns_records(self, hostname, **options):
|
||||||
|
if not self.api.Command.dns_is_enabled(
|
||||||
|
**options):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
bindinstance.remove_master_dns_records(
|
||||||
|
hostname, self.api.env.realm)
|
||||||
|
dnskeysyncinstance.remove_replica_public_keys(hostname)
|
||||||
|
except Exception as e:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_(
|
||||||
|
"Failed to cleanup %(hostname)s DNS entries: "
|
||||||
|
"%(err)s") % dict(hostname=hostname, err=e)))
|
||||||
|
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_("You may need to manually remove them from the "
|
||||||
|
"tree")))
|
||||||
|
|
||||||
|
def pre_callback(self, ldap, dn, *keys, **options):
|
||||||
|
pkey = self.obj.get_primary_key_from_dn(dn)
|
||||||
|
|
||||||
|
if options.get('force', False):
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_("Forcing removal of %(hostname)s") % dict(
|
||||||
|
hostname=pkey)))
|
||||||
|
|
||||||
|
# check the topology errors before and after removal
|
||||||
|
self.context.topology_connectivity = topology.TopologyConnectivity(
|
||||||
|
self.api)
|
||||||
|
|
||||||
|
if options.get('ignore_topology_disconnect', False):
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalWarning(
|
||||||
|
message=_("Ignoring topology connectivity errors.")))
|
||||||
|
else:
|
||||||
|
self._check_topology_connectivity(
|
||||||
|
self.context.topology_connectivity, pkey)
|
||||||
|
|
||||||
|
# ensure that we are not removing last CA/DNS server, DNSSec master and
|
||||||
|
# CA renewal master
|
||||||
|
self._ensure_last_of_role(
|
||||||
|
pkey, ignore_last_of_role=options.get('ignore_last_of_role', False)
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove the references to master's ldap/http principals
|
||||||
|
self._remove_server_principal_references(pkey)
|
||||||
|
|
||||||
|
# try to clean up the leftover DNS entries
|
||||||
|
self._cleanup_server_dns_records(pkey)
|
||||||
|
|
||||||
|
# finally destroy all Kerberos principals
|
||||||
|
self._remove_server_host_services(ldap, pkey)
|
||||||
|
|
||||||
|
return dn
|
||||||
|
|
||||||
|
def exc_callback(self, keys, options, exc, call_func, *call_args,
|
||||||
|
**call_kwargs):
|
||||||
|
if (options.get('force', False) and isinstance(exc, errors.NotFound)
|
||||||
|
and call_func.__name__ == 'delete_entry'):
|
||||||
|
self.add_message(
|
||||||
|
message=messages.ServerRemovalWarning(
|
||||||
|
message=_("Server has already been deleted")))
|
||||||
|
return
|
||||||
|
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
def _check_deleted_segments(self, hostname, topology_connectivity,
|
||||||
|
starting_host):
|
||||||
|
|
||||||
|
def wait_for_segment_removal(hostname, master_cns, suffix_name,
|
||||||
|
orig_errors, new_errors):
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
left = self.api.Command.topologysegment_find(
|
||||||
|
suffix_name,
|
||||||
|
iparepltoposegmentleftnode=hostname,
|
||||||
|
sizelimit=0
|
||||||
|
)['result']
|
||||||
|
right = self.api.Command.topologysegment_find(
|
||||||
|
suffix_name,
|
||||||
|
iparepltoposegmentrightnode=hostname,
|
||||||
|
sizelimit=0
|
||||||
|
)['result']
|
||||||
|
|
||||||
|
# Relax check if topology was or is disconnected. Disconnected
|
||||||
|
# topology can contain segments with already deleted servers
|
||||||
|
# Check only if segments of servers, which can contact this
|
||||||
|
# server, and the deleted server were removed.
|
||||||
|
# This code should handle a case where there was a topology
|
||||||
|
# with a central node(B): A <-> B <-> C, where A is current
|
||||||
|
# server. After removal of B, topology will be disconnected and
|
||||||
|
# removal of segment B <-> C won't be replicated back to server
|
||||||
|
# A, therefore presence of the segment has to be ignored.
|
||||||
|
if orig_errors or new_errors:
|
||||||
|
# use errors after deletion because we don't care if some
|
||||||
|
# server can't contact the deleted one
|
||||||
|
cant_contact_me = [e[0] for e in new_errors
|
||||||
|
if starting_host in e[2]]
|
||||||
|
can_contact_me = set(master_cns) - set(cant_contact_me)
|
||||||
|
left = [
|
||||||
|
s for s in left if s['iparepltoposegmentrightnode'][0]
|
||||||
|
in can_contact_me
|
||||||
|
]
|
||||||
|
right = [
|
||||||
|
s for s in right if s['iparepltoposegmentleftnode'][0]
|
||||||
|
in can_contact_me
|
||||||
|
]
|
||||||
|
|
||||||
|
if not left and not right:
|
||||||
|
self.add_message(
|
||||||
|
messages.ServerRemovalInfo(
|
||||||
|
message=_("Agreements deleted")
|
||||||
|
))
|
||||||
|
return
|
||||||
|
time.sleep(2)
|
||||||
|
if i == 2: # taking too long, something is wrong, report
|
||||||
|
self.log.info(
|
||||||
|
"Waiting for removal of replication agreements")
|
||||||
|
if i > 90:
|
||||||
|
self.log.info("Taking too long, skipping")
|
||||||
|
self.log.info("Following segments were not deleted:")
|
||||||
|
self.add_message(messages.ServerRemovalWarning(
|
||||||
|
message=_("Following segments were not deleted:")))
|
||||||
|
for s in left:
|
||||||
|
self.add_message(messages.ServerRemovalWarning(
|
||||||
|
message=u" %s" % s['cn'][0]))
|
||||||
|
for s in right:
|
||||||
|
self.add_message(messages.ServerRemovalWarning(
|
||||||
|
message=u" %s" % s['cn'][0]))
|
||||||
|
return
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
topology_graphs = topology_connectivity.graphs
|
||||||
|
|
||||||
|
orig_errors = topology_connectivity.errors
|
||||||
|
new_errors = topology_connectivity.errors_after_master_removal(
|
||||||
|
hostname
|
||||||
|
)
|
||||||
|
|
||||||
|
for suffix_name in topology_graphs:
|
||||||
|
suffix_members = topology_graphs[suffix_name].vertices
|
||||||
|
|
||||||
|
if hostname not in suffix_members:
|
||||||
|
# If the server was already deleted, we can expect that all
|
||||||
|
# removals had been done in previous run and dangling segments
|
||||||
|
# were not deleted.
|
||||||
|
self.log.info(
|
||||||
|
"Skipping replication agreement deletion check for "
|
||||||
|
"suffix '{0}'".format(suffix_name))
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Checking for deleted segments in suffix '{0}'".format(
|
||||||
|
suffix_name))
|
||||||
|
|
||||||
|
wait_for_segment_removal(
|
||||||
|
hostname,
|
||||||
|
list(suffix_members),
|
||||||
|
suffix_name,
|
||||||
|
orig_errors[suffix_name],
|
||||||
|
new_errors[suffix_name])
|
||||||
|
|
||||||
|
def post_callback(self, ldap, dn, *keys, **options):
|
||||||
|
# there is no point in checking deleted segment on local host
|
||||||
|
# we should do this only when removing other masters
|
||||||
|
if self.api.env.host != keys[-1]:
|
||||||
|
self._check_deleted_segments(
|
||||||
|
keys[-1], self.context.topology_connectivity,
|
||||||
|
self.api.env.host)
|
||||||
|
|
||||||
|
return super(server_del, self).post_callback(
|
||||||
|
ldap, dn, *keys, **options)
|
||||||
|
|
||||||
|
|
||||||
@register()
|
@register()
|
||||||
class server_conncheck(crud.PKQuery):
|
class server_conncheck(crud.PKQuery):
|
||||||
|
|||||||
Reference in New Issue
Block a user