dbConfiguration.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. #!/usr/bin/env python
  2. '''
  3. Licensed to the Apache Software Foundation (ASF) under one
  4. or more contributor license agreements. See the NOTICE file
  5. distributed with this work for additional information
  6. regarding copyright ownership. The ASF licenses this file
  7. to you under the Apache License, Version 2.0 (the
  8. "License"); you may not use this file except in compliance
  9. with the License. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. '''
  17. import glob
  18. import os
  19. from ambari_commons import OSConst
  20. from ambari_commons.exceptions import FatalException
  21. from ambari_commons.logging_utils import get_silent, print_error_msg, print_info_msg, print_warning_msg, set_silent
  22. from ambari_commons.os_family_impl import OsFamilyImpl
  23. from ambari_commons.str_utils import cbool
  24. from ambari_server.serverConfiguration import decrypt_password_for_alias, get_ambari_properties, get_is_secure, \
  25. get_resources_location, get_value_from_properties, is_alias_string, \
  26. JDBC_PASSWORD_PROPERTY, JDBC_RCA_PASSWORD_ALIAS, PRESS_ENTER_MSG
  27. from ambari_server.userInput import get_validated_string_input
  28. #Database settings
  29. DB_STATUS_RUNNING_DEFAULT = "running"
  30. SETUP_DB_CONNECT_TIMEOUT = 5
  31. SETUP_DB_CONNECT_ATTEMPTS = 3
  32. USERNAME_PATTERN = "^[a-zA-Z_][a-zA-Z0-9_\-]*$"
  33. PASSWORD_PATTERN = "^[a-zA-Z0-9_-]*$"
  34. DATABASE_NAMES = ["postgres", "oracle", "mysql", "mssql"]
  35. DATABASE_FULL_NAMES = {"oracle": "Oracle", "mysql": "MySQL", "mssql": "Microsoft SQL Server", "postgres": "PostgreSQL"}
  36. AMBARI_DATABASE_NAME = "ambari"
  37. AMBARI_DATABASE_TITLE = "ambari"
  38. STORAGE_TYPE_LOCAL = 'local'
  39. STORAGE_TYPE_REMOTE = 'remote'
  40. DEFAULT_USERNAME = "ambari"
  41. DEFAULT_PASSWORD = "bigdata"
  42. #
  43. # Database configuration helper classes
  44. #
  45. class DBMSDesc:
  46. def __init__(self, i_dbms_key, i_storage_key, i_dbms_name, i_storage_name, i_fn_create_config):
  47. self.dbms_key = i_dbms_key
  48. self.storage_key = i_storage_key
  49. self.dbms_name = i_dbms_name
  50. self.storage_name = i_storage_name
  51. self.fn_create_config = i_fn_create_config
  52. def create_config(self, options, properties, dbId):
  53. return self.fn_create_config(options, properties, self.storage_key, dbId)
  54. class DbPropKeys:
  55. def __init__(self, i_dbms_key, i_driver_key, i_server_key, i_port_key, i_db_name_key, i_db_url_key):
  56. self.dbms_key = i_dbms_key
  57. self.driver_key = i_driver_key
  58. self.server_key = i_server_key
  59. self.port_key = i_port_key
  60. self.db_name_key = i_db_name_key
  61. self.db_url_key = i_db_url_key
  62. class DbAuthenticationKeys:
  63. def __init__(self, i_user_name_key, i_password_key, i_password_alias, i_password_filename):
  64. self.user_name_key = i_user_name_key
  65. self.password_key = i_password_key
  66. self.password_alias = i_password_alias
  67. self.password_filename = i_password_filename
  68. #
  69. # Database configuration base class
  70. #
  71. class DBMSConfig(object):
  72. def __init__(self, options, properties, storage_type):
  73. """
  74. #Just load the defaults. The derived classes will be able to modify them later
  75. """
  76. self.persistence_type = storage_type
  77. self.dbms = ""
  78. self.driver_class_name = ""
  79. self.driver_file_name = ""
  80. self.driver_symlink_name = ""
  81. self.database_host = ""
  82. self.database_port = ""
  83. self.database_name = ""
  84. self.database_username = ""
  85. self.db_title = AMBARI_DATABASE_TITLE
  86. self.must_set_database_options = DBMSConfig._init_member_with_default(options, "must_set_database_options", False)
  87. self.JDBC_DRIVER_INSTALL_MSG = 'Before starting Ambari Server, you must install the JDBC driver.'
  88. self.isSecure = get_is_secure(properties)
  89. pass
  90. #
  91. # Public methods
  92. #
  93. #
  94. # Main method. Configures the database according to the options and the existing properties.
  95. #
  96. def configure_database(self, properties):
  97. result = self._prompt_db_properties()
  98. if result:
  99. #DB setup should be done last after doing any setup.
  100. if self._is_local_database():
  101. self._setup_local_server(properties)
  102. else:
  103. self._setup_remote_server(properties)
  104. return result
  105. def setup_database(self):
  106. print 'Configuring {0} database...'.format(self.db_title)
  107. #DB setup should be done last after doing any setup.
  108. if self._is_local_database():
  109. self._setup_local_database()
  110. else:
  111. self._setup_remote_database()
  112. pass
  113. def reset_database(self):
  114. if self._is_local_database():
  115. self._reset_local_database()
  116. else:
  117. self._reset_remote_database()
  118. pass
  119. def ensure_jdbc_driver_installed(self, properties):
  120. (result, msg) = self._prompt_jdbc_driver_install(properties)
  121. if result == -1:
  122. print_error_msg(msg)
  123. raise FatalException(-1, msg)
  124. if result != 1:
  125. result = self._install_jdbc_driver(properties, result)
  126. return cbool(result)
  127. def change_db_files_owner(self):
  128. if self._is_local_database():
  129. retcode = self._change_db_files_owner()
  130. if not retcode == 0:
  131. raise FatalException(20, 'Unable to change owner of database objects')
  132. #
  133. # Private implementation
  134. #
  135. @staticmethod
  136. def _read_password_from_properties(properties):
  137. database_password = DEFAULT_PASSWORD
  138. password_file = get_value_from_properties(properties, JDBC_PASSWORD_PROPERTY, "")
  139. if password_file:
  140. if is_alias_string(password_file):
  141. database_password = decrypt_password_for_alias(properties, JDBC_RCA_PASSWORD_ALIAS)
  142. else:
  143. if os.path.isabs(password_file) and os.path.exists(password_file):
  144. with open(password_file, 'r') as file:
  145. database_password = file.read()
  146. return database_password
  147. @staticmethod
  148. def _init_member_with_default(options, attr_name, default_val):
  149. options_val = getattr(options, attr_name, None)
  150. val = options_val if options_val is not None and options_val is not "" else default_val
  151. return val
  152. @staticmethod
  153. def _init_member_with_properties(options, attr_name, properties, property_key):
  154. options_val = getattr(options, attr_name, None)
  155. if options_val is None or options_val is "":
  156. options_val = get_value_from_properties(properties, property_key, None)
  157. return options_val
  158. @staticmethod
  159. def _init_member_with_prop_default(options, attr_name, properties, property_key, default_val):
  160. val = DBMSConfig._init_member_with_properties(options, attr_name, properties, property_key)
  161. if val is None or val is "":
  162. val = default_val
  163. return val
  164. #
  165. # Checks if options determine local DB configuration
  166. #
  167. def _is_local_database(self):
  168. return self.persistence_type == STORAGE_TYPE_LOCAL
  169. def _prompt_db_properties(self):
  170. #if WINDOWS
  171. # prompt for SQL Server host and instance name
  172. #else
  173. # go the classic Linux way
  174. #linux_prompt_db_properties(args)
  175. return False
  176. def _setup_local_server(self, properties):
  177. pass
  178. def _setup_local_database(self):
  179. pass
  180. def _reset_local_database(self):
  181. pass
  182. def _setup_remote_server(self, properties):
  183. pass
  184. def _setup_remote_database(self):
  185. pass
  186. def _reset_remote_database(self):
  187. pass
  188. def _prompt_jdbc_driver_install(self, properties):
  189. result = self._is_jdbc_driver_installed(properties)
  190. if result == -1:
  191. if get_silent():
  192. print_error_msg(self.JDBC_DRIVER_INSTALL_MSG)
  193. else:
  194. print_warning_msg(self.JDBC_DRIVER_INSTALL_MSG)
  195. raw_input(PRESS_ENTER_MSG)
  196. result = self._is_jdbc_driver_installed(properties)
  197. return (result, self.JDBC_DRIVER_INSTALL_MSG)
  198. def _is_jdbc_driver_installed(self, properties):
  199. return 1
  200. def _install_jdbc_driver(self, properties, files_list):
  201. return False
  202. def ensure_dbms_is_running(self, options, properties, scmStatus=None):
  203. pass
  204. def _change_db_files_owner(self, args):
  205. return 0
  206. #
  207. # Database configuration factory base class
  208. #
  209. class DBMSConfigFactory(object):
  210. def select_dbms(self, options):
  211. '''
  212. # Base declaration of the DBMS selection method.
  213. :return: DBMS index in the descriptor table
  214. '''
  215. pass
  216. def create(self, options, properties, dbId = "Ambari"):
  217. """
  218. # Base declaration of the factory method. The outcome of the derived implementations
  219. # is expected to be a subclass of DBMSConfig.
  220. # properties = property bag that will ultimately define the type of database. Since
  221. # right now in Windows we only support SQL Server, this argument is not yet used.
  222. # dbId = additional information, that helps distinguish between various database connections, if applicable
  223. """
  224. pass
  225. def get_supported_dbms(self):
  226. return []
  227. def get_supported_jdbc_drivers(self):
  228. return []
  229. #
  230. # Database configuration factory for Windows
  231. #
  232. @OsFamilyImpl(os_family=OSConst.WINSRV_FAMILY)
  233. class DBMSConfigFactoryWindows(DBMSConfigFactory):
  234. def __init__(self):
  235. from ambari_server.dbConfiguration_windows import DATABASE_DBMS_MSSQL
  236. self.DBMS_KEYS_LIST = [
  237. DATABASE_DBMS_MSSQL
  238. ]
  239. def select_dbms(self, options):
  240. # For now, we only support SQL Server in Windows, in remote mode.
  241. return 0
  242. def create(self, options, properties, dbId = "Ambari"):
  243. """
  244. # Windows implementation of the factory method. The outcome of the derived implementations
  245. # is expected to be a subclass of DBMSConfig.
  246. # properties = property bag that will ultimately define the type of database. Since
  247. # right now in Windows we only support SQL Server, this argument is not yet used.
  248. # dbId = additional information, that helps distinguish between various database connections, if applicable
  249. """
  250. from ambari_server.dbConfiguration_windows import createMSSQLConfig
  251. return createMSSQLConfig(options, properties, STORAGE_TYPE_REMOTE, dbId)
  252. def get_supported_dbms(self):
  253. return self.DBMS_KEYS_LIST
  254. def get_supported_jdbc_drivers(self):
  255. return self.DBMS_KEYS_LIST
  256. #
  257. # Database configuration factory for Linux
  258. #
  259. @OsFamilyImpl(os_family=OsFamilyImpl.DEFAULT)
  260. class DBMSConfigFactoryLinux(DBMSConfigFactory):
  261. def __init__(self):
  262. from ambari_server.dbConfiguration_linux import createPGConfig, createOracleConfig, createMySQLConfig, createMSSQLConfig
  263. self.DBMS_KEYS_LIST = [
  264. 'embedded',
  265. 'oracle',
  266. 'mysql',
  267. 'postgres',
  268. 'mssql'
  269. ]
  270. self.DRIVER_KEYS_LIST = [
  271. 'oracle',
  272. 'mysql',
  273. 'postgres',
  274. 'mssql',
  275. 'hsqldb'
  276. ]
  277. self.DBMS_LIST = [
  278. DBMSDesc(self.DBMS_KEYS_LIST[3], STORAGE_TYPE_LOCAL, 'PostgreSQL', 'Embedded', createPGConfig),
  279. DBMSDesc(self.DBMS_KEYS_LIST[1], STORAGE_TYPE_REMOTE, 'Oracle', '', createOracleConfig),
  280. DBMSDesc(self.DBMS_KEYS_LIST[2], STORAGE_TYPE_REMOTE, 'MySQL', '', createMySQLConfig),
  281. DBMSDesc(self.DBMS_KEYS_LIST[3], STORAGE_TYPE_REMOTE, 'PostgreSQL', '', createPGConfig),
  282. DBMSDesc(self.DBMS_KEYS_LIST[4], STORAGE_TYPE_REMOTE, 'Microsoft SQL Server', 'Tech Preview', createMSSQLConfig)
  283. ]
  284. self.DBMS_DICT = \
  285. {
  286. '-' : 0,
  287. '-' + STORAGE_TYPE_LOCAL : 0,
  288. self.DBMS_KEYS_LIST[0] + '-' : 0,
  289. self.DBMS_KEYS_LIST[2] + '-' : 2,
  290. self.DBMS_KEYS_LIST[2] + '-' + STORAGE_TYPE_REMOTE : 2,
  291. self.DBMS_KEYS_LIST[4] + '-' : 4,
  292. self.DBMS_KEYS_LIST[4] + '-' + STORAGE_TYPE_REMOTE : 4,
  293. self.DBMS_KEYS_LIST[1] + '-' : 1,
  294. self.DBMS_KEYS_LIST[1] + '-' + STORAGE_TYPE_REMOTE : 1,
  295. self.DBMS_KEYS_LIST[3] + '-' : 3,
  296. self.DBMS_KEYS_LIST[3] + '-' + STORAGE_TYPE_LOCAL : 0,
  297. self.DBMS_KEYS_LIST[3] + '-' + STORAGE_TYPE_REMOTE : 3,
  298. }
  299. self.DBMS_PROMPT_PATTERN = "[{0}] - {1}{2}\n"
  300. self.DBMS_CHOICE_PROMPT_PATTERN = "==============================================================================\n" \
  301. "Enter choice ({0}): "
  302. self.JDK_VALID_CHOICES_PATTERN = "^[{0}]$"
  303. def select_dbms(self, options):
  304. try:
  305. dbms_index = options.database_index
  306. except AttributeError:
  307. dbms_index = self._get_default_dbms_index(options)
  308. if options.must_set_database_options:
  309. n_dbms = 1
  310. dbms_choice_prompt = "==============================================================================\n" \
  311. "Choose one of the following options:\n"
  312. dbms_choices = ''
  313. for desc in self.DBMS_LIST:
  314. if len(desc.storage_name) > 0:
  315. dbms_storage = " ({0})".format(desc.storage_name)
  316. else:
  317. dbms_storage = ""
  318. dbms_choice_prompt += self.DBMS_PROMPT_PATTERN.format(n_dbms, desc.dbms_name, dbms_storage)
  319. dbms_choices += str(n_dbms)
  320. n_dbms += 1
  321. database_num = str(dbms_index + 1)
  322. dbms_choice_prompt += self.DBMS_CHOICE_PROMPT_PATTERN.format(database_num)
  323. dbms_valid_choices = self.JDK_VALID_CHOICES_PATTERN.format(dbms_choices)
  324. database_num = get_validated_string_input(
  325. dbms_choice_prompt,
  326. database_num,
  327. dbms_valid_choices,
  328. "Invalid number.",
  329. False
  330. )
  331. dbms_index = int(database_num) - 1
  332. if dbms_index >= n_dbms:
  333. print_info_msg('Unknown db option, default to {0} {1}.'.format(
  334. self.DBMS_LIST[0].storage_name, self.DBMS_LIST[0].dbms_name))
  335. dbms_index = 0
  336. return dbms_index
  337. def create(self, options, properties, dbId = "Ambari"):
  338. """
  339. # Linux implementation of the factory method. The outcome of the derived implementations
  340. # is expected to be a subclass of DBMSConfig.
  341. # properties = property bag that will ultimately define the type of database. Supported types are
  342. # MySQL, MSSQL, Oracle and PostgreSQL.
  343. # dbId = additional information, that helps distinguish between various database connections, if applicable
  344. """
  345. try:
  346. index = options.database_index
  347. except AttributeError:
  348. index = options.database_index = self._get_default_dbms_index(options)
  349. desc = self.DBMS_LIST[index]
  350. options.persistence_type = desc.storage_key
  351. dbmsConfig = desc.create_config(options, properties, dbId)
  352. return dbmsConfig
  353. def get_supported_dbms(self):
  354. return self.DBMS_KEYS_LIST
  355. def get_supported_jdbc_drivers(self):
  356. return self.DRIVER_KEYS_LIST
  357. def _get_default_dbms_index(self, options):
  358. try:
  359. dbms_name = options.dbms
  360. if not dbms_name:
  361. dbms_name = ""
  362. except AttributeError:
  363. dbms_name = ""
  364. try:
  365. persistence_type = options.persistence_type
  366. if not persistence_type:
  367. persistence_type = ""
  368. except AttributeError:
  369. persistence_type = ""
  370. try:
  371. def_index = self.DBMS_DICT[dbms_name + "-" + persistence_type]
  372. except KeyError:
  373. # Unsupported database type (e.g. local Oracle, MySQL or MSSQL)
  374. raise FatalException(15, "Invalid database selection: {0} {1}".format(
  375. getattr(options, "persistence_type", ""), getattr(options, "options.dbms", "")))
  376. return def_index
  377. def check_jdbc_drivers(args):
  378. # create jdbc symlinks if jdbc drivers are available in resources
  379. properties = get_ambari_properties()
  380. if properties == -1:
  381. err = "Error getting ambari properties"
  382. print_error_msg(err)
  383. raise FatalException(-1, err)
  384. resources_dir = get_resources_location(properties)
  385. try:
  386. db_idx_orig = args.database_index
  387. except AttributeError:
  388. db_idx_orig = None
  389. factory = DBMSConfigFactory()
  390. # AMBARI-5696 Validate the symlinks for each supported driver, in case various back-end HDP services happen to
  391. # use different DBMSes
  392. # This is skipped on Windows
  393. db_idx = 1
  394. try:
  395. while db_idx < len(factory.get_supported_dbms()):
  396. args.database_index = db_idx
  397. dbms = factory.create(args, properties)
  398. if dbms.driver_symlink_name:
  399. jdbc_file_path = os.path.join(resources_dir, dbms.driver_file_name)
  400. if os.path.isfile(jdbc_file_path):
  401. jdbc_symlink = os.path.join(resources_dir, dbms.driver_symlink_name)
  402. if os.path.lexists(jdbc_symlink):
  403. os.remove(jdbc_symlink)
  404. os.symlink(jdbc_file_path, jdbc_symlink)
  405. db_idx += 1
  406. finally:
  407. args.database_index = db_idx_orig
  408. #Check the JDBC driver status
  409. #If not found abort
  410. #Get SQL Server service status from SCM
  411. #If 'stopped' then start it
  412. #Wait until the status is 'started' or a configured timeout elapses
  413. #If the timeout has been reached, bail out with exception
  414. def ensure_dbms_is_running(options, properties, scmStatus=None):
  415. factory = DBMSConfigFactory()
  416. dbms = factory.create(options, properties)
  417. result = dbms._is_jdbc_driver_installed(properties)
  418. if result == -1:
  419. raise FatalException(-1, "JDBC driver is not installed. Run ambari-server setup and try again.")
  420. dbms.ensure_dbms_is_running(options, properties, scmStatus)
  421. def ensure_jdbc_driver_is_installed(options, properties):
  422. factory = DBMSConfigFactory()
  423. dbms = factory.create(options, properties)
  424. result = dbms._is_jdbc_driver_installed(properties)
  425. if result == -1:
  426. raise FatalException(-1, dbms.JDBC_DRIVER_INSTALL_MSG)