1
# Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
3
# This program is free software; you can redistribute it and/or
4
# modify it under the terms of the GNU General Public License as
5
# published by the Free Software Foundation; version 2 of the
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19
from threading import currentThread
27
from wb_server_management import wbaOS
28
from wb_common import OperationCancelledError, InvalidPasswordError, dprint_ex
33
#-------------------------------------------------------------------------------
34
def get_local_ip_list():
35
cmd = "/bin/sh -c ifconfig"
36
regexp = "inet addr:([0-9a-f:\.]+)"
38
if hasattr(sys, 'getwindowsversion'):
40
regexp = "IPv4.*: +([0-9a-f:\.]+)"
41
elif 'darwin' in sys.platform:
42
regexp = "inet ([0-9a-f\.:]+) netmask"
48
# We do not care if execution failed, the list will just be empty
52
print "Unknown exception while running '%s'"%cmd
58
m = re.findall(regexp, result)
62
t_ip = socket.gethostbyname(ip)
64
except socket.gaierror,e:
67
# We do not care if execution failed, the list will just be empty
71
print "Unknown exception while running '%s'"%cmd
76
local_ip_list = get_local_ip_list()
78
#===============================================================================
79
class ServerProfile(object):
80
def __init__(self, instance_profile, force_remote_admin_off=False):
81
self.disable_remote_admin = force_remote_admin_off
82
self.__settings = instance_profile
83
self.expanded_config_file_path = None
85
def get_settings_object(self):
86
return self.__settings
88
def _int_server_info(self, name, default_value=0):
89
# always returns an int, a None value will be replaced with default_value
91
value = self.__settings.serverInfo[name]
94
if type(value) is not int:
103
def _int_login_info(self, name, default_value=0):
104
# always returns an int, a None value will be replaced with default_value
106
value = self.__settings.loginInfo[name]
107
except KeyError, exc:
108
value = default_value
109
if type(value) is not int:
118
def _str_server_info(self, name, default_value=""):
119
# always returns an str or unicode, a None value will be replaced with default_value
121
value= self.__settings.serverInfo[name]
122
except KeyError, exc:
123
value = default_value
126
elif type(value) is not str and type(value) is not unicode:
130
def _str_login_info(self, name, default_value=""):
131
# always returns an str or unicode, a None value will be replaced with default_value
133
value= self.__settings.loginInfo[name]
134
except KeyError, exc:
135
value = default_value
138
elif type(value) is not str and type(value) is not unicode:
144
return self.__settings.name
147
def db_connection_params(self):
148
return self.__settings.connection
152
if hasattr(sys, 'getwindowsversion'):
154
elif ('inux' in sys.platform):
156
elif ('arwin' in sys.platform):
160
# Must return smth from wbaOS
163
# This call must return either value from profile for remote server
164
# If remote server is not set, which means local server, then returned value
165
# must be equal to self.host_os
169
system = self.__settings.serverInfo['sys.system']
172
if system is not None and type(system) is str or type(system) is unicode:
173
system = system.strip(" \r\t\n").lower()
174
if system == 'windows':
176
elif system in ('linux', 'freebsd', 'opensolaris'):
178
elif ('macos' in system):
186
def host_is_windows(self):
187
return self.host_os == wbaOS.windows
190
def target_is_windows(self):
191
return self.target_os == wbaOS.windows
195
def remote_admin_enabled(self):
196
return (self.uses_ssh or (not self.is_local and self.uses_wmi)) and not self.disable_remote_admin
199
def admin_enabled(self):
200
return self.is_local or self.remote_admin_enabled
204
local_addrs = ["localhost", "", "0", "127.0.0.1"] + local_ip_list
206
return self.wmi_hostname in local_addrs
208
return self.ssh_hostname in local_addrs
210
if self.db_connection_params:
211
params = self.db_connection_params.parameterValues
213
if self.db_connection_params.driver:
214
if self.db_connection_params.driver.name == "MysqlNative":
215
return params["hostName"] in local_addrs
216
elif self.db_connection_params.driver.name == "MysqlNativeSocket":
218
elif self.db_connection_params.driver.name == "MysqlNativeSSH":
221
from wb_common import NoDriverInConnection
222
raise NoDriverInConnection, """Workbench has not found a driver for the connection
223
that is being used by this server instance.
224
Please edit your connection settings and try again."""
229
def connect_method(self):
230
# choices are ['local', 'ssh', 'wmi', 'none']
231
# 'none' is for remote server ip with "No Remote Management" selected
247
return self._int_server_info("remoteAdmin")
249
# If WMI admin was selected as mgmt mean (can be for local or remote)
252
return self._int_server_info("windowsAdmin")
255
def ssh_username(self):
256
return self._str_login_info("ssh.userName")
259
def ssh_hostname(self):
260
return self._str_login_info("ssh.hostName")
264
return self._int_login_info("ssh.port", 22)
267
def ssh_usekey(self):
268
return self._int_login_info("ssh.useKey")
272
return self._str_login_info("ssh.key")
275
def wmi_hostname(self):
276
return self._str_login_info("wmi.hostName")
279
def wmi_username(self):
280
return self._str_login_info("wmi.userName")
283
def wmi_service_name(self):
284
return self._str_server_info("sys.mysqld.service_name")
287
def start_server_cmd(self):
288
return self._str_server_info("sys.mysqld.start")
291
def stop_server_cmd(self):
292
return self._str_server_info("sys.mysqld.stop")
295
def check_server_status_cmd(self):
296
return self._str_server_info("sys.mysqld.status")
300
return self._int_server_info("sys.usesudo")
303
def sudo_prefix(self):
304
return self._str_server_info("sys.sudo", "sudo") or "sudo"
307
def use_sudo_for_status(self):
308
return self._int_server_info("sys.usesudostatus")
312
def config_file_path(self):
313
""" path to configuration file (eg my.cnf)
314
may contain path variables that need to be expanded, such as %ProgramPath%
316
if self.expanded_config_file_path:
317
return self.expanded_config_file_path
318
return self._str_server_info('sys.config.path', '').strip(" \r\t\n\b")
322
def config_file_section(self):
323
return self._str_server_info('sys.config.section', '').strip(" \r\t\n\b")
326
def sys_script(self):
327
return self._str_server_info('sys.script', '')
329
#-------------------------------------------------------
330
def server_version(self):
331
return self._str_server_info("serverVersion", None)
333
def set_server_version(self, version):
334
self.__settings.serverInfo["serverVersion"] = version
336
server_version = property(server_version, set_server_version)
338
#-------------------------------------------------------
339
def log_output(self):
340
return self._str_server_info("logOutput")
342
def set_log_output(self, output):
343
self.__settings.serverInfo['logOutput'] = output
345
log_output = property(log_output, set_log_output)
347
#-------------------------------------------------------
348
def general_log_enabled(self):
349
return self._int_server_info("generalLogEnabled")
351
def set_general_log_enabled(self, value):
352
self.__settings.serverInfo['generalLogEnabled'] = value
354
general_log_enabled = property(general_log_enabled, set_general_log_enabled)
356
#-------------------------------------------------------
357
def slow_log_enabled(self):
358
return self._int_server_info("slowLogEnabled")
360
def set_slow_log_enabled(self, value):
361
self.__settings.serverInfo['slowLogEnabled'] = value
363
slow_log_enabled = property(slow_log_enabled, set_slow_log_enabled)
365
#-------------------------------------------------------
366
def general_log_file_path(self):
367
return self._str_server_info("generalLogFilePath")
369
def set_general_log_file_path(self, path):
370
self.__settings.serverInfo['generalLogFilePath'] = path
372
general_log_file_path = property(general_log_file_path, set_general_log_file_path)
374
#-------------------------------------------------------
375
def slow_log_file_path(self):
376
return self._str_server_info("slowLogFilePath")
378
def set_slow_log_file_path(self, path):
379
self.__settings.serverInfo['slowLogFilePath'] = path
381
slow_log_file_path = property(slow_log_file_path, set_slow_log_file_path)
383
#-------------------------------------------------------
384
def error_log_file_path(self):
385
return self._str_server_info("errorLogFilePath")
387
def set_error_log_file_path(self, path):
388
self.__settings.serverInfo['errorLogFilePath'] = path
390
error_log_file_path = property(error_log_file_path, set_error_log_file_path)
393
class PasswordHandler(object):
394
def __init__(self, server_profile):
395
self.server_profile = server_profile
398
def get_password_parameters(self, service_type):
399
# known service types: remoteshell, ssh, sshkey, file, service.startstop, service.status
400
# these are mapped to
401
# known password types: ssh, sshkey, sudo, UAC, wmi
402
# which are then mapped to (title, service, account) or "UAC" or None
404
profile = self.server_profile
407
if service_type == "file":
408
if profile.target_is_windows:
409
# in windows, auth is handled by UAC which is done externally
410
password_type = "UAC"
412
password_type = "sudo"
413
elif service_type == "service.startstop":
414
if profile.target_is_windows:
415
password_type = "wmi"
418
password_type = "sudo"
421
elif service_type == "service.status":
422
if profile.target_is_windows:
425
if profile.use_sudo_for_status:
426
password_type = "sudo"
429
elif service_type == "remoteshell":
431
if profile.ssh_usekey:
432
password_type = "sshkey"
434
password_type = "ssh"
435
elif service_type == "ssh":
436
password_type = "ssh"
437
elif service_type == "sshkey":
438
password_type = "sshkey"
440
raise Exception("Unknown password type: %s" % service_type)
443
# sudo pwd may be either user's password, or root password. It depends on sudo setup
444
if password_type == "sudo":
446
# if su is to be used instead of sudo, then we need the root password instead of login user
447
if profile.sudo_prefix and "sudo" not in profile.sudo_prefix:
451
if sudo_type == "sudo":
452
account = profile.ssh_username
455
title = "%s Password Required"%sudo_type
456
service = "%s@%s"%(sudo_type, profile.ssh_hostname)
458
if sudo_type == "sudo":
460
# os.getlogin() raises errno 25 if WB is started from ubuntu menu, so never use it
461
#account = os.getlogin()
463
account = pwd.getpwuid(os.getuid())[0]
470
title = "%s Password Required"% sudo_type
471
service = "%s@localhost" % sudo_type
473
return (title, service, account)
475
elif password_type == "wmi":
476
return ("WMI Password Required", "wmi@%s" % profile.wmi_hostname, profile.wmi_username)
478
elif password_type == "UAC":
481
elif password_type == "ssh":
482
return ("SSH Login Password Required", "ssh@%s" % profile.ssh_hostname, profile.ssh_username)
484
elif password_type == "sshkey":
485
return ("SSH Private Key Password Required", "ssh_keyfile@%s" % profile.ssh_key, profile.ssh_username)
490
# call this if a InvalidPasswordError is caught
491
def reset_password_for(self, service_type):
492
if self.pwd_store.has_key(service_type):
493
# None means the stored password was bad
494
self.pwd_store[service_type] = None
496
def get_password_for(self, service_type): # Raises OperationCancelledError if user cancels pwd dlg.
498
if self.pwd_store.has_key(service_type):
499
if self.pwd_store[service_type] is None:
502
return self.pwd_store[service_type]
504
details = self.get_password_parameters(service_type)
505
if not details: # No password needed for this service_type
508
# special case for UAC used in windows
512
title, service, account = details
514
dprint_ex(2, "request password for %s => %s, %s, %s" % (service_type, title, service, account))
516
accepted, password = mforms.Utilities.find_or_ask_for_password(title, service, account, force_reset)
520
self.pwd_store[service_type] = password
523
raise OperationCancelledError("Password input cancelled")
526
class ServerControlBase(object):
527
def __init__(self, serverProfile, helper, password_delegate):
528
self.profile = serverProfile
530
self.password_delegate = password_delegate
531
self.output_buffer = []
532
# default handler will just add the output to a list to be flushed later
533
self.output_handler = lambda line, output_buffer= self.output_buffer: output_buffer.append(line)
535
def set_output_handler(self, handler): # must be called by consumer of output (ie, the start/stop Tab in WB)
536
self.output_handler = handler
537
if self.output_buffer:
538
for line in self.output_buffer:
539
self.output_handler(line)
540
self.output_buffer = None
542
def info(self, text):
543
self.output_handler(text)
546
# do cleanup, free up resources etc
549
def start(self, password): # overriden by concrete subclasses
550
"Starts the server using the method specified in the instance profile"
553
def start_async(self, finish_callback): # callback(status) where status can be success, bad_password or an error message
555
# ask subclass to provide password
556
password = self.get_password()
557
except OperationCancelledError, exc:
559
thread = threading.Thread(target=self.worker_thread, args=(self.start, password, finish_callback))
563
def worker_thread(self, action, password, finish_callback):
566
finish_callback("success")
567
except InvalidPasswordError, err:
568
finish_callback("bad_password")
569
except Exception, err:
571
traceback.print_exc()
575
def stop(self, password): # overriden by concrete subclasses, must be non-interactive
578
def stop_async(self, finish_callback):
580
password = self.get_password()
581
except OperationCancelledError, exc:
583
thread = threading.Thread(target=self.worker_thread, args=(self.stop, password, finish_callback))
587
def get_status(self, verbose=0): # overriden by concrete subclasses
591
class ServerControlShell(ServerControlBase):
592
def __init__(self, profile, helper, password_delegate):
593
""" Can also throw OperationCancelledError"""
594
ServerControlBase.__init__(self, profile, helper, password_delegate)
596
# Ask for password here, as this class is instantiated from the main thread
597
# The other methods may be instantiated from other threads, which will not work
598
# with the password dialogs
600
# Ensure sudo password for status checks is cached. For other sudo uses,
601
# the password should be requested when the action is started from the UI (ie, in the code called
602
# by the Start/Stop button in Server startup tab)
603
if self.profile.use_sudo_for_status:
604
password_delegate.get_password_for("service.status")
606
self.prepare_filter = None
607
self.apply_filter = None
610
def set_filter_functions(self, prepare_f, apply_f):
611
self.prepare_filter = prepare_f
612
self.apply_filter = apply_f
615
def get_password(self):
616
return self.password_delegate.get_password_for("service.startstop")
619
def start(self, password):
620
""" Can also throw InvalidPasswordError"""
621
self.info("Starting server...") # TODO: This does not come to info output
623
r = self.helper.execute_command(self.profile.start_server_cmd,
624
as_admin=self.profile.use_sudo,
625
admin_password=password,
626
output_handler=lambda s:self.info("Start server: %s"%(s if ((type(s) is str) or (type(s) is unicode)) else "").replace(password, "")))
627
except InvalidPasswordError, exc:
628
# forget password, so that next time it will be requested
629
self.password_delegate.reset_password_for("service.startstop")
637
def stop(self, password):
638
""" Can also throw InvalidPasswordError"""
639
self.info("Stopping server...")
641
def strip_pwd(output):
642
if output is not None and (type(output) is str or type(output) is unicode):
643
if password is not None and password != "":
644
output = output.replace(password, "")
647
self.info("Stop server: %s"%str(output))
649
r = self.helper.execute_command(self.profile.stop_server_cmd,
650
as_admin=self.profile.use_sudo,
651
admin_password=password,
652
output_handler=strip_pwd)
653
except InvalidPasswordError, exc:
654
# forget password, so that next time it will be requested
655
self.password_delegate.reset_password_for("service.startstop")
663
def get_status(self, verbose=1, password=None):
664
""" Can also throw InvalidPasswordError"""
666
output = lambda s:self.info("Check server: %s"%s)
671
#unused user_gave_new_password = False
673
script = self.profile.check_server_status_cmd
676
self.info("Checking server status...")
677
if self.profile.use_sudo_for_status:
678
self.info("Executing: %s (using sudo)" % script)
680
self.info("Note: no sudo password supplied")
682
self.info("Executing: %s" % script)
684
if self.prepare_filter:
685
raw_script, filters = self.prepare_filter(script)
690
def collect_output(line, l=lines, dump=output):
695
result = self.helper.execute_command(raw_script,
696
as_admin=self.profile.use_sudo_for_status,
697
admin_password=password,
698
output_handler=collect_output)
699
except InvalidPasswordError, exc:
700
self.info("Invalid password")
703
if self.apply_filter and filters:
704
filters_text, filters_code = self.apply_filter("\n".join(lines), filters)
706
if filters_code is not None:
707
if result is not None:
708
result = int(result) or filters_code
710
result = filters_code
712
#output_text = "\n".join(lines)
717
self.info("Server check returned %s" % result)
722
self.info("Checked server status: Server is running.")
726
self.info("Checked server status: Server is stopped.")
732
class ServerControlWMI(ServerControlBase):
733
def __init__(self, profile, helper, password_delegate):
734
ServerControlBase.__init__(self, profile, helper, password_delegate)
735
self.wmi = grt.modules.Workbench
736
self.wmi_session_ids = {}
738
if self.profile.is_local:
742
self.check_and_fix_profile_for_local_windows(profile)
743
self.shell = ServerControlShell(profile, helper, password_delegate)
744
# Forse usage of as_admin for local windows
745
self.info("Workbench will use cmd shell commands to start/stop this instance")
747
user = self.profile.wmi_username
748
server = self.profile.wmi_hostname
749
password = self.get_password()
751
sess = self.wmi.wmiOpenSession(server, user, password or "")
752
self.wmi_session_ids[currentThread()] = sess
753
except Exception, exc:
755
traceback.print_exc()
756
raise RuntimeError("Could not initialize WMI interface: %s"%exc)
757
if self.wmi_session_id_for_current_thread <= 0:
758
raise RuntimeError("Could not initialize WMI interface")
761
def check_and_fix_profile_for_local_windows(self, profile):
762
settings = profile.get_settings_object()
763
serverInfo = settings.serverInfo
765
serverInfo["sys.usesudo"] = 1 # Force this in any case
767
service = profile.wmi_service_name
768
# return self._str_server_info("sys.mysqld.service_name")
770
service = serverInfo["sys.mysqld.service_name"] = "MySQL"
771
self.info("MySQL service was empty. Set to 'MySQL'. Check this in 'Manage Server Instances' from 'Home'.")
773
if profile.start_server_cmd == "" or (service not in profile.start_server_cmd):
774
serverInfo["sys.mysqld.start"] = "sc start " + service
775
print "WMIShell: start command set to '%s'"%profile.start_server_cmd
777
if profile.stop_server_cmd == "" or (service not in profile.stop_server_cmd):
778
serverInfo["sys.mysqld.stop"] = "sc stop " + service
779
print "WMIShell: stop command set to '%s'"%profile.stop_server_cmd
781
if profile.check_server_status_cmd == "" or (service not in profile.check_server_status_cmd):
782
serverInfo["sys.mysqld.status"] = "sc query %s | wba_filter(RUNNING)"%service
783
print "WMIShell: status command set to '%s'"%profile.check_server_status_cmd
785
def set_filter_functions(self, prepare_f, apply_f):
786
if self.profile.is_local and self.shell:
787
print "WMIShell: Set filters for local windows cli"
788
self.shell.prepare_filter = prepare_f
789
self.shell.apply_filter = apply_f
791
def get_password(self):
792
if self.profile.is_local:
794
return self.password_delegate.get_password_for("service.startstop")
796
# WMI sessions are thread specific. To make things easier, we create one for
797
# each thread that uses it
799
def wmi_session_id_for_current_thread(self):
800
thr = currentThread()
801
s = self.wmi_session_ids.get(thr)
804
password = self.get_password()
806
if self.profile.is_local:
807
sess = self.wmi.wmiOpenSession('', '', '')
809
sess = self.wmi.wmiOpenSession(self.profile.wmi_hostname, self.profile.wmi_username, password or "")
812
self.wmi_session_ids[thr] = sess
817
for s in self.wmi_session_ids.values():
818
self.wmi.wmiCloseSession(s)
819
self.wmi_session_ids = {}
820
ServerControlBase.close() # not certain if the call is needed.
822
def start(self, password):
823
self.info("Starting server...");
825
return self.shell.start(password)
827
# Profile may hold the actual command passed
828
service = self.profile.wmi_service_name
830
self.info("Starting service '%s' through WMI..." % service)
831
result = self.wmi.wmiServiceControl(self.wmi_session_id_for_current_thread, service, action)
832
self.info("Service start result: %s" % result)
833
if result.startswith("error"):
834
raise RuntimeError("Error stopping service %s through WMI\n%s" % (service, result))
835
return not result.startswith("error")
838
def stop(self, password):
840
return self.shell.stop(password)
842
service = self.profile.wmi_service_name
844
self.info("Stopping service '%s' through WMI..." % service)
845
result = self.wmi.wmiServiceControl(self.wmi_session_id_for_current_thread, service, action)
846
self.info("Service stop result: %s" % result)
847
if result.startswith("error"):
848
raise RuntimeError("Error stopping service %s through WMI\n%s" % (service, result))
849
return not result.startswith("error")
851
def get_status(self, verbose=1, password=None):
852
"Returned value is one of running, stopping, starting, stopped, unknown"
853
service = self.profile.wmi_service_name
856
self.info("Checking service status of instance %s..." % service)
857
result = self.wmi.wmiServiceControl(self.wmi_session_id_for_current_thread, service, action)
859
self.info("Status check of service '%s' returned %s" % (service, result))
860
# translate status to something more displayable
861
if result == "stop pending":
863
elif result == "start pending":