BootstrapMysql.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. from jinja2 import Environment, FileSystemLoader
  2. from modules.BootstrapBase import BootstrapBase
  3. from pathlib import Path
  4. import os
  5. import sys
  6. import time
  7. import platform
  8. import subprocess
  9. class Bootstrap(BootstrapBase):
  10. def bootstrap(self):
  11. dbuser = "root"
  12. dbpass = os.getenv("MYSQL_ROOT_PASSWORD", "")
  13. socket = "/var/run/mysqld/mysqld.sock"
  14. print("Starting temporary mysqld for upgrade...")
  15. self.start_temporary(socket)
  16. self.connect_mysql()
  17. print("Running mysql_upgrade...")
  18. self.upgrade_mysql(dbuser, dbpass, socket)
  19. print("Shutting down temporary mysqld...")
  20. self.close_mysql()
  21. self.stop_temporary(dbuser, dbpass, socket)
  22. # Setup Jinja2 Environment and load vars
  23. self.env = Environment(
  24. loader=FileSystemLoader('./etc/mysql/conf.d/config_templates'),
  25. keep_trailing_newline=True,
  26. lstrip_blocks=True,
  27. trim_blocks=True
  28. )
  29. extra_vars = {
  30. }
  31. self.env_vars = self.prepare_template_vars('/overwrites.json', extra_vars)
  32. print("Set Timezone")
  33. self.set_timezone()
  34. print("Render config")
  35. self.render_config("my.cnf.j2", "/etc/mysql/conf.d/my.cnf")
  36. def start_temporary(self, socket):
  37. """
  38. Starts a temporary mysqld process in the background using the given UNIX socket.
  39. The server is started with networking disabled (--skip-networking).
  40. Args:
  41. socket (str): Path to the UNIX socket file for MySQL to listen on.
  42. Returns:
  43. subprocess.Popen: The running mysqld process object.
  44. """
  45. return subprocess.Popen([
  46. "mysqld",
  47. "--user=mysql",
  48. "--skip-networking",
  49. f"--socket={socket}"
  50. ])
  51. def stop_temporary(self, dbuser, dbpass, socket):
  52. """
  53. Shuts down the temporary mysqld instance gracefully.
  54. Uses mariadb-admin to issue a shutdown command to the running server.
  55. Args:
  56. dbuser (str): The MySQL username with shutdown privileges (typically 'root').
  57. dbpass (str): The password for the MySQL user.
  58. socket (str): Path to the UNIX socket the server is listening on.
  59. """
  60. self.run_command([
  61. "mariadb-admin",
  62. "shutdown",
  63. f"--socket={socket}",
  64. "-u", dbuser,
  65. f"-p{dbpass}"
  66. ])
  67. def upgrade_mysql(self, dbuser, dbpass, socket, max_retries=5, wait_interval=3):
  68. """
  69. Executes mysql_upgrade to check and fix any schema or table incompatibilities.
  70. Retries the upgrade command if it fails, up to a maximum number of attempts.
  71. Args:
  72. dbuser (str): MySQL username with privilege to perform the upgrade.
  73. dbpass (str): Password for the MySQL user.
  74. socket (str): Path to the MySQL UNIX socket for local communication.
  75. max_retries (int): Maximum number of attempts before giving up. Default is 5.
  76. wait_interval (int): Number of seconds to wait between retries. Default is 3.
  77. Returns:
  78. bool: True if upgrade succeeded, False if all attempts failed.
  79. """
  80. retries = 0
  81. while retries < max_retries:
  82. result = self.run_command([
  83. "mysql_upgrade",
  84. "-u", dbuser,
  85. f"-p{dbpass}",
  86. f"--socket={socket}"
  87. ], check=False)
  88. if result.returncode == 0:
  89. print("mysql_upgrade completed successfully.")
  90. break
  91. else:
  92. print(f"mysql_upgrade failed (try {retries+1}/{max_retries})")
  93. retries += 1
  94. time.sleep(wait_interval)
  95. else:
  96. print("mysql_upgrade failed after all retries.")
  97. return False