#!/usr/bin/python2 # Authors: Simo Sorce # # Copyright (C) 2008-2010 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # from __future__ import print_function import sys import os import json import ldapurl from ipaserver.install import service, installutils from ipaserver.install.dsinstance import config_dirname from ipaserver.install.installutils import is_ipa_configured, ScriptError from ipalib import api, errors from ipapython.ipaldap import IPAdmin from ipapython.ipautil import wait_for_open_ports, wait_for_open_socket from ipapython import config from ipaplatform.tasks import tasks from ipapython.dn import DN from ipaplatform import services from ipaplatform.paths import paths class IpactlError(ScriptError): pass def check_IPA_configuration(): if not is_ipa_configured(): # LSB status code 6: program is not configured raise IpactlError("IPA is not configured " + "(see man pages of ipa-server-install for help)", 6) def deduplicate(lst): """Remove duplicates and preserve order. Returns copy of list with preserved order and removed duplicates. """ new_lst = [] s = set(lst) for i in lst: if i in s: s.remove(i) new_lst.append(i) return new_lst def is_dirsrv_debugging_enabled(): """ Check the 389-ds instance to see if debugging is enabled. If so we suppress that in our output. returns True or False """ debugging = False serverid = installutils.realm_to_serverid(api.env.realm) dselist = [config_dirname(serverid)] for dse in dselist: try: fd = open(dse + 'dse.ldif', 'r') except IOError: continue lines = fd.readlines() fd.close() for line in lines: if line.lower().startswith('nsslapd-errorlog-level'): (option, value) = line.split(':') if int(value) > 0: debugging = True return debugging def get_capture_output(service, debug): """ We want to display any output of a start/stop command with the exception of 389-ds when debugging is enabled because it outputs tons and tons of information. """ if service == 'dirsrv' and not debug and is_dirsrv_debugging_enabled(): print(' debugging enabled, suppressing output.') return True else: return False def parse_options(): usage = "%prog start|stop|restart|status\n" parser = config.IPAOptionParser(usage=usage, formatter=config.IPAFormatter()) parser.add_option("-d", "--debug", action="store_true", dest="debug", help="Display debugging information") parser.add_option("-f", "--force", action="store_true", dest="force", help="Force IPA to start. Combine options " "--skip-version-check and --ignore-service-failures") parser.add_option("--ignore-service-failures", action="store_true", dest="ignore_service_failures", help="If any service start fails, do not rollback the " "services, continue with the operation") parser.add_option("--skip-version-check", action="store_true", dest="skip_version_check", default=False, help="skip version check") options, args = parser.parse_args() safe_options = parser.get_safe_opts(options) if options.force: options.ignore_service_failures = True options.skip_version_check = True return safe_options, options, args def emit_err(err): sys.stderr.write(err + '\n') def version_check(): try: installutils.check_version() except (installutils.UpgradeMissingVersionError, installutils.UpgradeDataOlderVersionError): emit_err("Upgrade required: please run ipa-server-upgrade command") raise IpactlError("Aborting ipactl") except installutils.UpgradeVersionError as e: emit_err("IPA version error: %s" % e) raise IpactlError("Aborting ipactl") def get_config(dirsrv): base = DN(('cn', api.env.host), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) srcfilter = '(ipaConfigString=enabledService)' attrs = ['cn', 'ipaConfigString'] if not dirsrv.is_running(): raise IpactlError("Failed to get list of services to probe status:\n" + "Directory Server is stopped", 3) try: # The start/restart functions already wait for the server to be # started. What we are doing with this wait is really checking to see # if the server is listening at all. lurl = ldapurl.LDAPUrl(api.env.ldap_uri) if lurl.urlscheme == 'ldapi': wait_for_open_socket(lurl.hostport, timeout=api.env.startup_timeout) else: (host, port) = lurl.hostport.split(':') wait_for_open_ports(host, [int(port)], timeout=api.env.startup_timeout) con = IPAdmin(ldap_uri=api.env.ldap_uri) con.do_external_bind() res, truncated = con.find_entries( filter=srcfilter, attrs_list=attrs, base_dn=base, scope=con.SCOPE_SUBTREE, time_limit=10) if truncated: raise errors.LimitsExceeded() except errors.NetworkError: # LSB status code 3: program is not running raise IpactlError("Failed to get list of services to probe status:\n" + "Directory Server is stopped", 3) except errors.NotFound: masters_list = [] dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) attrs = ['cn'] try: entries = con.get_entries(dn, con.SCOPE_ONELEVEL, attrs_list=attrs) except Exception as e: masters_list.append("No master found because of error: %s" % str(e)) else: for master_entry in entries: masters_list.append(master_entry.single_value['cn']) masters = "\n".join(masters_list) raise IpactlError("Failed to get list of services to probe status!\n" "Configured hostname '%s' does not match any master server in LDAP:\n%s" % (api.env.host, masters)) except Exception as e: raise IpactlError("Unknown error when retrieving list of services from LDAP: " + str(e)) svc_list = [] for entry in res: name = entry.single_value['cn'] for p in entry['ipaConfigString']: if p.startswith('startOrder '): try: order = int(p.split()[1]) except ValueError: raise IpactlError("Expected order as integer in: %s:%s" % ( name, p)) svc_list.append([order, name]) ordered_list = [] for (order, svc) in sorted(svc_list): if svc in service.SERVICE_LIST: ordered_list.append(service.SERVICE_LIST[svc][0]) return ordered_list def get_config_from_file(): svc_list = [] try: f = open(tasks.get_svc_list_file(), 'r') svc_list = json.load(f) except Exception as e: raise IpactlError("Unknown error when retrieving list of services from file: " + str(e)) # the framework can start/stop a number of related services we are not # authoritative for, so filter the list through SERVICES_LIST and order it # accordingly too. def_svc_list = [] for svc in service.SERVICE_LIST: s = service.SERVICE_LIST[svc] def_svc_list.append([s[1], s[0]]) ordered_list = [] for (order, svc) in sorted(def_svc_list): if svc in svc_list: ordered_list.append(svc) return ordered_list def stop_services(svc_list): for svc in svc_list: svc_off = services.service(svc) try: svc_off.stop(capture_output=False) except Exception: pass def stop_dirsrv(dirsrv): try: dirsrv.stop(capture_output=False) except Exception: pass def ipa_start(options): if not options.skip_version_check: version_check() else: print("Skipping version check") if os.path.isfile(tasks.get_svc_list_file()): emit_err("Existing service file detected!") emit_err("Assuming stale, cleaning and proceeding") # remove file with list of started services # This is ok as systemd will just skip services # that are already running and just return, so that the # stop() method of the base class will simply fill in the # service file again os.unlink(paths.SVC_LIST_FILE) dirsrv = services.knownservices.dirsrv try: print("Starting Directory Service") dirsrv.start(capture_output=get_capture_output('dirsrv', options.debug)) except Exception as e: raise IpactlError("Failed to start Directory Service: " + str(e)) ldap_list = [] try: svc_list = get_config(dirsrv) except Exception as e: emit_err("Failed to read data from service file: " + str(e)) emit_err("Shutting down") if not options.ignore_service_failures: stop_dirsrv(dirsrv) if isinstance(e, IpactlError): # do not display any other error message raise IpactlError(rval=e.rval) # pylint: disable=no-member else: raise IpactlError() if len(svc_list) == 0: # no service to start return svc_list = deduplicate(svc_list) for svc in svc_list: svchandle = services.service(svc) try: print("Starting %s Service" % svc) svchandle.start(capture_output=get_capture_output(svc, options.debug)) except Exception: emit_err("Failed to start %s Service" % svc) # if ignore_service_failures is specified, skip rollback and # continue with the next service if options.ignore_service_failures: emit_err("Forced start, ignoring %s Service, continuing normal operation" % svc) continue emit_err("Shutting down") stop_services(svc_list) stop_dirsrv(dirsrv) raise IpactlError("Aborting ipactl") def ipa_stop(options): dirsrv = services.knownservices.dirsrv try: svc_list = get_config_from_file() except Exception as e: # Issue reading the file ? Let's try to get data from LDAP as a # fallback try: dirsrv.start(capture_output=False) svc_list = get_config(dirsrv) except Exception as e: emit_err("Failed to read data from Directory Service: " + str(e)) emit_err("Shutting down") try: # just try to stop it, do not read a result dirsrv.stop() finally: raise IpactlError() svc_list = deduplicate(svc_list) for svc in reversed(svc_list): svchandle = services.service(svc) try: print("Stopping %s Service" % svc) svchandle.stop(capture_output=False) except: emit_err("Failed to stop %s Service" % svc) try: print("Stopping Directory Service") dirsrv.stop(capture_output=False) except: raise IpactlError("Failed to stop Directory Service") # remove file with list of started services try: os.unlink(paths.SVC_LIST_FILE) except OSError: pass def ipa_restart(options): if not options.skip_version_check: version_check() else: print("Skipping version check") dirsrv = services.knownservices.dirsrv new_svc_list = [] dirsrv_restart = True if not dirsrv.is_running(): try: print("Starting Directory Service") dirsrv.start(capture_output=get_capture_output('dirsrv', options.debug)) dirsrv_restart = False except Exception as e: raise IpactlError("Failed to start Directory Service: " + str(e)) try: new_svc_list = get_config(dirsrv) except Exception as e: emit_err("Failed to read data from Directory Service: " + str(e)) emit_err("Shutting down") try: dirsrv.stop(capture_output=False) except: pass if isinstance(e, IpactlError): # do not display any other error message raise IpactlError(rval=e.rval) # pylint: disable=no-member else: raise IpactlError() old_svc_list = [] try: old_svc_list = get_config_from_file() except Exception as e: emit_err("Failed to get service list from file: " + str(e)) # fallback to what's in LDAP old_svc_list = new_svc_list # match service to start/stop svc_list = [] for s in new_svc_list: if s in old_svc_list: svc_list.append(s) #remove commons for s in svc_list: if s in old_svc_list: old_svc_list.remove(s) for s in svc_list: if s in new_svc_list: new_svc_list.remove(s) if len(old_svc_list) != 0: # we need to definitely stop some services old_svc_list = deduplicate(old_svc_list) for svc in reversed(old_svc_list): svchandle = services.service(svc) try: print("Stopping %s Service" % svc) svchandle.stop(capture_output=False) except: emit_err("Failed to stop %s Service" % svc) try: if dirsrv_restart: print("Restarting Directory Service") dirsrv.restart(capture_output=get_capture_output('dirsrv', options.debug)) except Exception as e: emit_err("Failed to restart Directory Service: " + str(e)) emit_err("Shutting down") if not options.ignore_service_failures: stop_services(reversed(svc_list)) stop_dirsrv(dirsrv) raise IpactlError("Aborting ipactl") if len(svc_list) != 0: # there are services to restart svc_list = deduplicate(svc_list) for svc in svc_list: svchandle = services.service(svc) try: print("Restarting %s Service" % svc) svchandle.restart(capture_output=get_capture_output(svc, options.debug)) except Exception: emit_err("Failed to restart %s Service" % svc) # if ignore_service_failures is specified, # skip rollback and continue with the next service if options.ignore_service_failures: emit_err("Forced restart, ignoring %s Service, continuing normal operation" % svc) continue emit_err("Shutting down") stop_services(svc_list) stop_dirsrv(dirsrv) raise IpactlError("Aborting ipactl") if len(new_svc_list) != 0: # we still need to start some services new_svc_list = deduplicate(new_svc_list) for svc in new_svc_list: svchandle = services.service(svc) try: print("Starting %s Service" % svc) svchandle.start(capture_output=get_capture_output(svc, options.debug)) except Exception: emit_err("Failed to start %s Service" % svc) # if ignore_service_failures is specified, skip rollback and # continue with the next service if options.ignore_service_failures: emit_err("Forced start, ignoring %s Service, continuing normal operation" % svc) continue emit_err("Shutting down") stop_services(svc_list) stop_dirsrv(dirsrv) raise IpactlError("Aborting ipactl") def ipa_status(options): try: dirsrv = services.knownservices.dirsrv if dirsrv.is_running(): svc_list = get_config(dirsrv) else: svc_list = get_config_from_file() except IpactlError as e: if os.path.exists(tasks.get_svc_list_file()): raise e else: svc_list = [] except Exception as e: raise IpactlError("Failed to get list of services to probe status: " + str(e)) dirsrv = services.knownservices.dirsrv try: if dirsrv.is_running(): print("Directory Service: RUNNING") else: print("Directory Service: STOPPED") if len(svc_list) == 0: print(("Directory Service must be running in order to " + "obtain status of other services")) except: raise IpactlError("Failed to get Directory Service status") if len(svc_list) == 0: return svc_list = deduplicate(svc_list) for svc in svc_list: svchandle = services.service(svc) try: if svchandle.is_running(): print("%s Service: RUNNING" % svc) else: print("%s Service: STOPPED" % svc) except: emit_err("Failed to get %s Service status" % svc) def main(): if not os.getegid() == 0: # LSB status code 4: user had insufficient privilege raise IpactlError("You must be root to run ipactl.", 4) safe_options, options, args = parse_options() if len(args) != 1: # LSB status code 2: invalid or excess argument(s) raise IpactlError("You must specify one action", 2) elif args[0] != "start" and args[0] != "stop" and args[0] != "restart" and args[0] != "status": raise IpactlError("Unrecognized action [" + args[0] + "]", 2) # check if IPA is configured at all try: check_IPA_configuration() except IpactlError as e: if args[0].lower() == "status": # Different LSB return code for status command: # 4 - program or service status is unknown # This should differentiate uninstalled IPA from status # code 3 - program is not running e.rval = 4 raise e else: raise e api.bootstrap(context='ipactl', debug=options.debug) api.finalize() if '.' not in api.env.host: raise IpactlError("Invalid hostname '%s' in IPA configuration!\n" "The hostname must be fully-qualified" % api.env.host) if args[0].lower() == "start": ipa_start(options) elif args[0].lower() == "stop": ipa_stop(options) elif args[0].lower() == "restart": ipa_restart(options) elif args[0].lower() == "status": ipa_status(options) if __name__ == '__main__': installutils.run_script(main, operation_name='ipactl')