# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. # # 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; version 2 of the # License. # # 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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA import threading from threading import currentThread import sys import os import grt import mforms from wb_server_management import wbaOS from wb_common import OperationCancelledError, InvalidPasswordError, Users import re import socket #------------------------------------------------------------------------------- def get_local_ip_list(): cmd = "/bin/sh -c ifconfig" regexp = "inet addr:([0-9a-f:\.]+)" if hasattr(sys, 'getwindowsversion'): cmd = 'ipconfig' regexp = "IPv4.*: +([0-9a-f:\.]+)" elif 'darwin' in sys.platform: regexp = "inet ([0-9a-f\.:]+) netmask" out = None try: out = os.popen(cmd) except Exception, e: # We do not care if execution failed, the list will just be empty #print e out = None except: print "Unknown exception while running '%s'"%cmd try: ret = [] if out: result = out.read() m = re.findall(regexp, result) for ip in m: try: t_ip = socket.gethostbyname(ip) ret.append(ip) except socket.gaierror,e: print ip, str(e) except Exception, e: # We do not care if execution failed, the list will just be empty #print e ret = [] except: print "Unknown exception while running '%s'"%cmd ret = [] return ret local_ip_list = get_local_ip_list() #=============================================================================== class ServerProfile(object): def __init__(self, instance_profile, force_remote_admin_off=False): self.disable_remote_admin = force_remote_admin_off self.__settings = instance_profile self.expanded_config_file_path = None def get_settings_object(self): return self.__settings def _int_server_info(self, name, default_value=0): # always returns an int, a None value will be replaced with default_value try: value = self.__settings.serverInfo[name] except KeyError, exc: value = default_value if type(value) is not int: try: value= int(value) except: value= default_value if value is None: return default_value return value def _int_login_info(self, name, default_value=0): # always returns an int, a None value will be replaced with default_value try: value = self.__settings.loginInfo[name] except KeyError, exc: value = default_value if type(value) is not int: try: value= int(value) except: value= default_value if value is None: return default_value return value def _str_server_info(self, name, default_value=""): # always returns an str or unicode, a None value will be replaced with default_value try: value= self.__settings.serverInfo[name] except KeyError, exc: value = default_value if value is None: value= default_value elif type(value) is not str and type(value) is not unicode: value= str(value) return value def _str_login_info(self, name, default_value=""): # always returns an str or unicode, a None value will be replaced with default_value try: value= self.__settings.loginInfo[name] except KeyError, exc: value = default_value if value is None: value= default_value elif type(value) is not str and type(value) is not unicode: value= str(value) return value @property def name(self): return self.__settings.name @property def db_connection_params(self): return self.__settings.connection @property def host_os(self): if hasattr(sys, 'getwindowsversion'): return wbaOS.windows elif ('inux' in sys.platform): return wbaOS.linux elif ('arwin' in sys.platform): return wbaOS.darwin return wbaOS.unknown # Must return smth from wbaOS @property def target_os(self): # This call must return either value from profile for remote server # If remote server is not set, which means local server, then returned value # must be equal to self.host_os ret = wbaOS.unknown try: system = self.__settings.serverInfo['sys.system'] except: system = None if system is not None and type(system) is str or type(system) is unicode: system = system.strip(" \r\t\n").lower() if system == 'windows': ret = wbaOS.windows elif system in ('linux', 'freebsd', 'opensolaris'): ret = wbaOS.linux elif ('macos' in system): ret = wbaOS.darwin else: ret = self.host_os return ret @property def host_is_windows(self): return self.host_os == wbaOS.windows @property def target_is_windows(self): return self.target_os == wbaOS.windows @property def remote_admin_enabled(self): return (self.uses_ssh or (not self.is_local and self.uses_wmi)) and not self.disable_remote_admin @property def admin_enabled(self): return self.is_local or self.remote_admin_enabled @property def is_local(self): local_addrs = ["localhost", "", "0", "127.0.0.1"] + local_ip_list if self.uses_wmi: return self.wmi_hostname in local_addrs elif self.uses_ssh: return self.ssh_hostname in local_addrs else: if self.db_connection_params: params = self.db_connection_params.parameterValues if params: if self.db_connection_params.driver: if self.db_connection_params.driver.name == "MysqlNative": return params["hostName"] in local_addrs elif self.db_connection_params.driver.name == "MysqlNativeSocket": return True elif self.db_connection_params.driver.name == "MysqlNativeSSH": return False else: from wb_common import NoDriverInConnection raise NoDriverInConnection, """Workbench has not found a driver for the connection that is being used by this server instance. Please edit your connection settings and try again.""" return False @property def connect_method(self): # choices are ['local', 'ssh', 'wmi', 'none'] # 'none' is for remote server ip with "No Remote Management" selected ret = None if self.uses_wmi: ret = 'wmi' elif self.uses_ssh: ret = 'ssh' else: if self.is_local: ret = 'local' else: ret = 'none' return ret @property def host_name(self): host_name = None if self.db_connection_params: if self.db_connection_params.driver.name == "MysqlNativeSocket": host_name = "localhost" else: host_name = self.db_connection_params.parameterValues["hostName"] return host_name @property def uses_ssh(self): return self._int_server_info("remoteAdmin") # If WMI admin was selected as mgmt mean (can be for local or remote) @property def uses_wmi(self): return self._int_server_info("windowsAdmin") @property def ssh_username(self): return self._str_login_info("ssh.userName") @property def ssh_hostname(self): return self._str_login_info("ssh.hostName") @property def ssh_port(self): return self._int_login_info("ssh.port", 22) @property def ssh_usekey(self): return self._int_login_info("ssh.useKey") @property def ssh_key(self): return self._str_login_info("ssh.key") @property def wmi_hostname(self): return self._str_login_info("wmi.hostName") @property def wmi_username(self): return self._str_login_info("wmi.userName") @property def wmi_service_name(self): return self._str_server_info("sys.mysqld.service_name") @property def start_server_cmd(self): ret = None if self.uses_wmi: ret = "sc start " + self.wmi_service_name else: ret = self._str_server_info("sys.mysqld.start") return ret @property def stop_server_cmd(self): ret = None if self.uses_wmi: ret = "sc stop " + self.wmi_service_name else: ret = self._str_server_info("sys.mysqld.stop") return ret @property def check_server_status_cmd(self): return self._str_server_info("sys.mysqld.status") @property def use_sudo(self): return self._int_server_info("sys.usesudo") @property def sudo_prefix(self): return self._str_server_info("sys.sudo", "sudo") or "sudo" @property def use_sudo_for_status(self): return self._int_server_info("sys.usesudostatus") @property def config_file_path(self): """ path to configuration file (eg my.cnf) may contain path variables that need to be expanded, such as %ProgramPath% """ if self.expanded_config_file_path: return self.expanded_config_file_path return self._str_server_info('sys.config.path', '').strip(" \r\t\n\b") @property def config_file_section(self): return self._str_server_info('sys.config.section', '').strip(" \r\t\n\b") @property def sys_script(self): return self._str_server_info('sys.script', '') #------------------------------------------------------- def server_version(self): return self._str_server_info("serverVersion", None) def set_server_version(self, version): self.__settings.serverInfo["serverVersion"] = version server_version = property(server_version, set_server_version) #------------------------------------------------------- @property def server_version_tuple(self): if not self.server_version: return () import re version_tokens = self.server_version.split('.') ver = [] for token in version_tokens: match = re.match(r'(\d+).*', token) if match: ver.append(int(match.group(1))) return tuple(ver) #------------------------------------------------------- def log_output(self): return self._str_server_info("logOutput") def set_log_output(self, output): self.__settings.serverInfo['logOutput'] = output log_output = property(log_output, set_log_output) #------------------------------------------------------- def general_log_enabled(self): return self._int_server_info("generalLogEnabled") def set_general_log_enabled(self, value): self.__settings.serverInfo['generalLogEnabled'] = value general_log_enabled = property(general_log_enabled, set_general_log_enabled) #------------------------------------------------------- def slow_log_enabled(self): return self._int_server_info("slowLogEnabled") def set_slow_log_enabled(self, value): self.__settings.serverInfo['slowLogEnabled'] = value slow_log_enabled = property(slow_log_enabled, set_slow_log_enabled) #------------------------------------------------------- def general_log_file_path(self): return self._str_server_info("generalLogFilePath") def set_general_log_file_path(self, path): self.__settings.serverInfo['generalLogFilePath'] = path general_log_file_path = property(general_log_file_path, set_general_log_file_path) #------------------------------------------------------- def slow_log_file_path(self): return self._str_server_info("slowLogFilePath") def set_slow_log_file_path(self, path): self.__settings.serverInfo['slowLogFilePath'] = path slow_log_file_path = property(slow_log_file_path, set_slow_log_file_path) #------------------------------------------------------- def error_log_file_path(self): return self._str_server_info("errorLogFilePath") def set_error_log_file_path(self, path): self.__settings.serverInfo['errorLogFilePath'] = path error_log_file_path = property(error_log_file_path, set_error_log_file_path) #------------------------------------------------------- def datadir(self): return self._str_server_info('datadir') def set_datadir(self, path): self.__settings.serverInfo['datadir'] = path datadir = property(datadir, set_datadir) #------------------------------------------------------- def basedir(self): return self._str_server_info('basedir') def set_basedir(self, path): self.__settings.serverInfo['basedir'] = path basedir = property(basedir, set_basedir) class PasswordHandler(object): def __init__(self, server_profile): self.server_profile = server_profile self.pwd_store = {} def get_password_parameters(self, service_type): # known service types: remoteshell, ssh, sshkey, file, service.startstop, service.status # these are mapped to # known password types: ssh, sshkey, sudo, UAC, wmi # which are then mapped to (title, service, account) or "UAC" or None profile = self.server_profile password_type = None if service_type == "file": if profile.target_is_windows: # in windows, auth is handled by UAC which is done externally password_type = "UAC" else: password_type = "sudo" elif service_type == "service.startstop": if profile.target_is_windows: password_type = "wmi" else: if profile.use_sudo: password_type = "sudo" else: password_type = None elif service_type == "service.status": if profile.target_is_windows: password_type = None else: if profile.use_sudo_for_status: password_type = "sudo" else: password_type = None elif service_type == "remoteshell": if profile.uses_ssh: if profile.ssh_usekey: password_type = "sshkey" else: password_type = "ssh" elif service_type == "ssh": password_type = "ssh" elif service_type == "sshkey": password_type = "sshkey" else: raise Exception("Unknown password type: %s" % service_type) # sudo pwd may be either user's password, or root password. It depends on sudo setup if password_type == "sudo": sudo_type = "sudo" # if su is to be used instead of sudo, then we need the root password instead of login user if profile.sudo_prefix and "sudo" not in profile.sudo_prefix: sudo_type = "root" if profile.uses_ssh: if sudo_type == "sudo": account = profile.ssh_username else: account = "root" title = "%s Password Required"%sudo_type service = "%s@%s"%(sudo_type, profile.ssh_hostname) else: if sudo_type == "sudo": import pwd # os.getlogin() raises errno 25 if WB is started from ubuntu menu, so never use it #account = os.getlogin() try: account = pwd.getpwuid(os.getuid())[0] if not account: account = "sudo" except: account = "sudo" else: account = "root" title = "%s Password Required"% sudo_type service = "%s@localhost" % sudo_type return (title, service, account) elif password_type == "wmi": return ("WMI Password Required", "wmi@%s" % profile.wmi_hostname, profile.wmi_username) elif password_type == "UAC": return "UAC" elif password_type == "ssh": return ("SSH Login Password Required", "ssh@%s" % profile.ssh_hostname, profile.ssh_username) elif password_type == "sshkey": return ("SSH Private Key Password Required", "ssh_keyfile@%s" % profile.ssh_key, profile.ssh_username) else: return None # call this if a InvalidPasswordError is caught def reset_password_for(self, service_type): if self.pwd_store.has_key(service_type): # None means the stored password was bad self.pwd_store[service_type] = None def get_password_for(self, service_type): # Raises OperationCancelledError if user cancels pwd dlg. force_reset = False if self.pwd_store.has_key(service_type): if self.pwd_store[service_type] is None: force_reset = True else: return self.pwd_store[service_type] details = self.get_password_parameters(service_type) if not details: # No password needed for this service_type return None # special case for UAC used in windows if details == "UAC": return "UAC" title, service, account = details grt.log_debug2('%s:get_password_for()' % self.__class__.__name__, 'request password for "%s" => "%s", "%s", "%s"\n' % (service_type, title, service, account)) accepted, password = mforms.Utilities.find_or_ask_for_password(title, service, account, force_reset) if accepted: if not password: password = "" self.pwd_store[service_type] = password.encode("utf8") return password else: raise OperationCancelledError("Password input cancelled") class ServerControlBase(object): def __init__(self, serverProfile, helper, password_delegate): self.profile = serverProfile self.helper = helper self.password_delegate = password_delegate self.output_buffer = [] # default handler will just add the output to a list to be flushed later self.output_handler = lambda line, output_buffer= self.output_buffer: output_buffer.append(line) def set_output_handler(self, handler): # must be called by consumer of output (ie, the start/stop Tab in WB) self.output_handler = handler if self.output_buffer: for line in self.output_buffer: self.output_handler(line) self.output_buffer = None def info(self, text): self.output_handler(text) def close(self): # do cleanup, free up resources etc pass def start(self, password): # overriden by concrete subclasses "Starts the server using the method specified in the instance profile" assert 0 def start_async(self, finish_callback): # callback(status) where status can be success, bad_password or an error message try: # ask subclass to provide password password = self.get_password() except OperationCancelledError, exc: return False thread = threading.Thread(target=self.worker_thread, args=(self.start, password, finish_callback)) thread.run() return True def worker_thread(self, action, password, finish_callback): try: action(password) finish_callback("success") except InvalidPasswordError, err: finish_callback("bad_password") except Exception, err: import traceback traceback.print_exc() finish_callback(err) def stop(self, password): # overriden by concrete subclasses, must be non-interactive assert 0 def stop_async(self, finish_callback): try: password = self.get_password() except OperationCancelledError, exc: return False thread = threading.Thread(target=self.worker_thread, args=(self.stop, password, finish_callback)) thread.run() return True def get_status(self, verbose=0): # overriden by concrete subclasses return 0 class ServerControlShell(ServerControlBase): def __init__(self, profile, helper, password_delegate): """ Can also throw OperationCancelledError""" ServerControlBase.__init__(self, profile, helper, password_delegate) # Ask for password here, as this class is instantiated from the main thread # The other methods may be instantiated from other threads, which will not work # with the password dialogs # Ensure sudo password for status checks is cached. For other sudo uses, # the password should be requested when the action is started from the UI (ie, in the code called # by the Start/Stop button in Server startup tab) if self.profile.use_sudo_for_status: password_delegate.get_password_for("service.status") self.prepare_filter = None self.apply_filter = None def set_filter_functions(self, prepare_f, apply_f): self.prepare_filter = prepare_f self.apply_filter = apply_f def get_password(self): return self.password_delegate.get_password_for("service.startstop") def start(self, password): """ Can also throw InvalidPasswordError""" self.info("Starting server...") # TODO: This does not come to info output try: as_user = Users.ADMIN if self.profile.use_sudo else Users.CURRENT r = self.helper.execute_command(self.profile.start_server_cmd, as_user=as_user, user_password=password, output_handler=lambda s:self.info("Start server: %s"%(s if ((type(s) is str) or (type(s) is unicode)) else "").replace(password, ""))) except InvalidPasswordError, exc: # forget password, so that next time it will be requested self.password_delegate.reset_password_for("service.startstop") raise exc if r: return True return False def stop(self, password): """ Can also throw InvalidPasswordError""" self.info("Stopping server...") try: def strip_pwd(output): if output is not None and (type(output) is str or type(output) is unicode): if password is not None and password != "": output = output.replace(password, "") else: output = "" self.info("Stop server: %s"%str(output)) as_user = Users.ADMIN if self.profile.use_sudo else Users.CURRENT r = self.helper.execute_command(self.profile.stop_server_cmd, as_user=as_user, user_password=password, output_handler=strip_pwd) except InvalidPasswordError, exc: # forget password, so that next time it will be requested self.password_delegate.reset_password_for("service.startstop") raise exc if r: return True return False def get_status(self, verbose=1, password=None): """ Can also throw InvalidPasswordError""" if verbose > 1: output = lambda s:self.info("Check server: %s"%s) else: output = None password = None #unused user_gave_new_password = False script = self.profile.check_server_status_cmd if verbose > 1: self.info("Checking server status...") if self.profile.use_sudo_for_status: self.info("Executing: %s (using sudo)" % script) if not password: self.info("Note: no sudo password supplied") else: self.info("Executing: %s" % script) if self.prepare_filter: raw_script, filters = self.prepare_filter(script) else: raw_script = script lines = [] def collect_output(line, l=lines, dump=output): l.append(line) if dump: dump(line) try: as_user = Users.ADMIN if self.profile.use_sudo_for_status else Users.CURRENT result = self.helper.execute_command(raw_script, as_user=as_user, user_password=password, output_handler=collect_output) except InvalidPasswordError, exc: self.info("Invalid password") raise exc if self.apply_filter and filters: filters_text, filters_code = self.apply_filter("\n".join(lines), filters) if filters_code is not None: if result is not None: result = int(result) or filters_code else: result = filters_code else: #output_text = "\n".join(lines) pass if verbose > 1: self.info("Server check returned %s" % result) status = "unknown" if result == 0: if verbose: self.info("Checked server status: Server is running.") status = "running" elif result == 1: if verbose: self.info("Checked server status: Server is stopped.") status = "stopped" return status class ServerControlWMI(ServerControlBase): def __init__(self, profile, helper, password_delegate): ServerControlBase.__init__(self, profile, helper, password_delegate) self.wmi = grt.modules.Workbench self.wmi_session_ids = {} self.shell = None if self.profile.is_local: user = "" server = "" password = "" self.check_and_fix_profile_for_local_windows(profile) self.shell = ServerControlShell(profile, helper, password_delegate) # Force usage of Users.ADMIN for local windows self.info("Workbench will use cmd shell commands to start/stop this instance") else: user = self.profile.wmi_username server = self.profile.wmi_hostname password = self.get_password() try: sess = self.wmi.wmiOpenSession(server, user, password or "") self.wmi_session_ids[currentThread()] = sess except Exception, exc: import traceback traceback.print_exc() raise RuntimeError("Could not initialize WMI interface: %s"%exc) if self.wmi_session_id_for_current_thread <= 0: raise RuntimeError("Could not initialize WMI interface") def check_and_fix_profile_for_local_windows(self, profile): settings = profile.get_settings_object() serverInfo = settings.serverInfo serverInfo["sys.usesudo"] = 1 # Force this in any case # Check commands service = profile.wmi_service_name # return self._str_server_info("sys.mysqld.service_name") if service == "": service = serverInfo["sys.mysqld.service_name"] = "MySQL" self.info("MySQL service was empty. Set to 'MySQL'. Check this in 'Manage Server Instances' from 'Home'.") if profile.start_server_cmd == "" or (service not in profile.start_server_cmd): serverInfo["sys.mysqld.start"] = "sc start " + service print "WMIShell: start command set to '%s'"%profile.start_server_cmd if profile.stop_server_cmd == "" or (service not in profile.stop_server_cmd): serverInfo["sys.mysqld.stop"] = "sc stop " + service print "WMIShell: stop command set to '%s'"%profile.stop_server_cmd if profile.check_server_status_cmd == "" or (service not in profile.check_server_status_cmd): serverInfo["sys.mysqld.status"] = "sc query %s | wba_filter(RUNNING)"%service print "WMIShell: status command set to '%s'"%profile.check_server_status_cmd def set_filter_functions(self, prepare_f, apply_f): if self.profile.is_local and self.shell: print "WMIShell: Set filters for local windows cli" self.shell.prepare_filter = prepare_f self.shell.apply_filter = apply_f def get_password(self): if self.profile.is_local: return "" return self.password_delegate.get_password_for("service.startstop") # WMI sessions are thread specific. To make things easier, we create one for # each thread that uses it @property def wmi_session_id_for_current_thread(self): thr = currentThread() s = self.wmi_session_ids.get(thr) if s is not None: return s password = self.get_password() if self.profile.is_local: sess = self.wmi.wmiOpenSession('', '', '') else: sess = self.wmi.wmiOpenSession(self.profile.wmi_hostname, self.profile.wmi_username, password or "") if sess: self.wmi_session_ids[thr] = sess return sess def close(self): for s in self.wmi_session_ids.values(): self.wmi.wmiCloseSession(s) self.wmi_session_ids = {} ServerControlBase.close() # not certain if the call is needed. def start(self, password): self.info("Starting server..."); if self.shell: return self.shell.start(password) else: # Profile may hold the actual command passed service = self.profile.wmi_service_name action = "start" self.info("Starting service '%s' through WMI..." % service) result = self.wmi.wmiServiceControl(self.wmi_session_id_for_current_thread, service, action) self.info("Service start result: %s" % result) if result.startswith("error"): raise RuntimeError("Error stopping service %s through WMI\n%s" % (service, result)) return not result.startswith("error") def stop(self, password): if self.shell: return self.shell.stop(password) else: service = self.profile.wmi_service_name action = "stop" self.info("Stopping service '%s' through WMI..." % service) result = self.wmi.wmiServiceControl(self.wmi_session_id_for_current_thread, service, action) self.info("Service stop result: %s" % result) if result.startswith("error"): raise RuntimeError("Error stopping service %s through WMI\n%s" % (service, result)) return not result.startswith("error") def get_status(self, verbose=1, password=None): "Returned value is one of running, stopping, starting, stopped, unknown" service = self.profile.wmi_service_name action = "status" if verbose > 1: self.info("Checking service status of instance %s..." % service) result = self.wmi.wmiServiceControl(self.wmi_session_id_for_current_thread, service, action) if verbose: self.info("Status check of service '%s' returned %s" % (service, result)) # translate status to something more displayable if result == "stop pending": result = "stopping" elif result == "start pending": result = "starting" return result