mds_plugin/mysql_database_service.py (1,254 lines of code) (raw):

# Copyright (c) 2021, 2024, Oracle and/or its affiliates. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, # as published by the Free Software Foundation. # # This program is designed to work with certain software (including # but not limited to OpenSSL) that is licensed under separate terms, as # designated in a particular file or component or in included license # documentation. The authors of MySQL hereby grant you an additional # permission to link the program and your derivative works with the # separately licensed software that they have either included with # the program or referenced in the documentation. # # 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, version 2.0, 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 """Sub-Module to manage the MySQL DbSystems""" from mds_plugin import core, configuration, util from mysqlsh.plugin_manager import plugin_function DB_SYSTEM_ACTION_START = 1 DB_SYSTEM_ACTION_STOP = 2 DB_SYSTEM_ACTION_RESTART = 3 HW_CLUSTER_ACTION_START = 4 HW_CLUSTER_ACTION_STOP = 5 HW_CLUSTER_ACTION_RESTART = 6 def format_db_systems(items, current=None) -> str: """Formats a given list of objects in a human readable form Args: items: Either a list of objects or a single object current (str): OCID of the current item Returns: The db_systems formatted as str """ # If a single db_system was given, wrap it in a list if not type(items) is list: items = [items] # return objects in READABLE text output out = "" id = 1 for i in items: index = f"*{id:>3} " if current == i.id else f"{id:>4} " kind = " " if hasattr(i, "is_supported_for_hw_cluster") and i.is_supported_for_hw_cluster: kind = "HW" elif hasattr(i, "is_supported_for_analytics_cluster") and i.is_supported_for_analytics_cluster: kind = "AN" out += (index + core.fixed_len(i.display_name, 24, ' ', True) + core.fixed_len(i.description, 42, ' ', True) + core.fixed_len(i.mysql_version, 11, ' ') + core.fixed_len(i.lifecycle_state, 11, ' ' + kind + '\n')) id += 1 return out def format_db_system_config(items) -> str: """Formats a given list of objects in a human readable form Args: items: Either a list of objects or a single object Returns: The objects formatted as str """ # If a single db_system was given, wrap it in a list if not type(items) is list: items = [items] # return objects in READABLE text output out = "" id = 1 for i in items: out += (f"{id:>4} " + core.fixed_len(i.display_name, 32, ' ', True) + core.fixed_len(i.description, 35, ' ', True) + core.fixed_len(i.shape_name, 20, ' ') + core.fixed_len(i.lifecycle_state, 11, '\n')) id += 1 return out def format_mysql_shapes(items) -> str: """Formats a given list of objects in a human readable form Args: items: Either a list of objects or a single object Returns: The objects formatted as str """ # If a single db_system was given, wrap it in a list if not type(items) is list: items = [items] # return objects in READABLE text output out = (core.fixed_len('Shape Name', 32, ' ') + core.fixed_len('CPU Count', 12, ' ', align_right=True) + core.fixed_len('Memory Size', 12, '\n', align_right=True)) id = 1 for i in items: out += (f"{id:>4} " + core.fixed_len(i.name, 32, ' ', True) + core.fixed_len(str(i.cpu_core_count), 12, ' ', align_right=True) + core.fixed_len(str(i.memory_size_in_gbs) + ' GB', 12, '\n', align_right=True)) id += 1 return out def get_db_system_by_id(db_system_id, config=None): """Gets a DbSystem with the given id Args: db_system_id (str): OCID of the DbSystem. config (object): An OCI config object or None. Returns: None """ import oci.mysql # Initialize the DbSystem client db_sys = core.get_oci_db_system_client(config=config) # Get the DbSystems with the given db_system_id db_system = db_sys.get_db_system(db_system_id=db_system_id).data return db_system @plugin_function('mds.get.dbSystemConfiguration', shell=True, cli=True, web=True) def get_db_system_configuration(**kwargs): """Gets a DB System Config If no name is given, the user can select from a list. Args: **kwargs: Optional parameters Keyword Args: config_name (str): The name of the config. configuration_id (str): The OCID of the configuration. shape (str): The name of the compute shape. availability_domain (str): The name of the availability domain. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised return_formatted (bool): If true a human readable string is returned return_python_object (bool): Used for internal plugin calls Returns: The MySQL configuration object """ config_name = kwargs.get("config_name") configuration_id = kwargs.get("configuration_id") availability_domain = kwargs.get("availability_domain") shape = kwargs.get("shape") compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) return_formatted = kwargs.get("return_formatted", interactive) return_python_object = kwargs.get("return_python_object", False) # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) import oci.mysql from mds_plugin import compartment, compute try: # Get MDS Client mds_client = core.get_oci_mds_client(config=config) # If an configuration_id was given, look it up if configuration_id is not None: return core.return_oci_object( oci_object=mds_client.get_configuration( configuration_id=configuration_id), return_python_object=return_python_object, return_formatted=return_formatted, format_function=format_db_system_config) if not availability_domain: # Get the availability_domain name availability_domain_obj = compartment.get_availability_domain( random_selection=not interactive, compartment_id=compartment_id, availability_domain=availability_domain, config=config, interactive=interactive, return_python_object=True) if availability_domain_obj is None: raise ValueError("No availability domain specified.") availability_domain = availability_domain_obj.name # Get the shapes shape_id = compute.get_shape_name( shape_name=shape, compartment_id=compartment_id, availability_domain=availability_domain, config=config, interactive=interactive) if shape_id is None or shape_id == "": raise ValueError("No shape specified.") # Get list of configs configs = mds_client.list_configurations(compartment_id).data # Only consider configs that support the given shape configs = [c for c in configs if c.shape_name == shape_id] for config in configs: if config.display_name == config_name: return core.return_oci_object( oci_object=config, return_python_object=return_python_object, return_formatted=return_formatted, format_function=format_db_system_config) # If there is only a single config, use it if len(configs) == 1 or not interactive: return core.return_oci_object( oci_object=configs[0], return_python_object=return_python_object, return_formatted=return_formatted, format_function=format_db_system_config) # Let the user choose from the list print(f"\nPlease choose a MySQL Configuration from this list.\n") config = core.prompt_for_list_item( item_list=configs, prompt_caption="Please enter the name or index of the MySQL config: ", item_name_property="display_name", given_value=config_name, print_list=True) return core.return_oci_object( oci_object=config, return_python_object=return_python_object, return_formatted=return_formatted, format_function=format_db_system_config) except oci.exceptions.ServiceError as e: if raise_exceptions: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') except Exception as e: if raise_exceptions: raise print(f'ERROR: {e}') def get_mysql_version(mysql_version=None, compartment_id=None, config=None): """Gets a MySQL version If a mysql_version is given, it is checked if that mysql_version is available. Otherwise the user can select a different version Args: mysql_version (str): The mysql_version. compartment_id (str): The OCID of the compartment. config (object): An OCI config object or None. Returns: None """ # Get the active config and compartment try: config = configuration.get_current_config(config=config) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) except ValueError as e: print(f"ERROR: {str(e)}") return import oci.mysql from mds_plugin import compartment, compute # Get MDS Client mds_client = core.get_oci_mds_client(config=config) # Get list of versions version_summaries = mds_client.list_versions(compartment_id).data versions = [] for vs in version_summaries: versions += vs.versions if len(versions) == 1: return versions[0].version # Let the user choose from the list if mysql_version is None: print(f"\nPlease choose a MySQL Version from this list.\n") version = core.prompt_for_list_item( item_list=versions, prompt_caption="Please enter the MySQL version or index: ", item_name_property="version", given_value=mysql_version, print_list=True) return None if version is None else version.version def validate_mysql_password(password): """Validates the given password using the default MDS MySQL password rules - Should have at least one number. - Should have at least one uppercase and one lowercase character. - Should have at least one special symbol. - Should be between 8 to 20 characters long. Args: password (str): The password to validate Returns: True if the given password is valid, False otherwise """ import re reg = (r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])" r"[A-Za-z\d@$!#%*?&]{6,20}$") # compiling regex pattern = re.compile(reg) # searching regex return re.search(pattern, password) def get_validated_mysql_password(password_caption, print_password_rules=True): """Gets a validated password using the default MDS MySQL password rules - Should have at least one number. - Should have at least one uppercase and one lowercase character. - Should have at least one special symbol. - Should be between 8 to 20 characters long. Args: prompt (str): The password prompt that should be printed Returns: The password as string """ import mysqlsh if print_password_rules: print( f"When specifying the {password_caption} password, please use:\n" " - a minimum of 8 characters\n - one uppercase letter\n" " - one lowercase letter\n - one number\n - one special char") # Loop until a valid password is given while True: password = mysqlsh.globals.shell.prompt( f"Please enter the {password_caption} password: ", {'defaultValue': '', 'type': 'password'}).strip() if password == "": return "" if validate_mysql_password(password): break else: print( "The given password does not comply to the password rules. " "Please try again.\n") i = 0 while i < 2: password_2 = mysqlsh.globals.shell.prompt( f"Please confirm the {password_caption} password: ", {'defaultValue': '', 'type': 'password'}) if password_2 == "": return "" if password == password_2: break else: print( "The given passwords do not match. Please try again.\n") i += 1 if i >= 2: return "" else: return password @plugin_function('mds.list.dbSystemShapes', shell=True, cli=True, web=True) def list_db_system_shapes(**kwargs): """Lists Shapes available for MySQL DB Systems Lists all shapes of a given compartment. Args: **kwargs: Optional parameters Keyword Args: is_supported_for (str): Either DBSYSTEM (default), HEATWAVECLUSTER or "DBSYSTEM, HEATWAVECLUSTER" availability_domain (str): The name of the availability_domain to use compartment_id (str): OCID of the parent compartment. config (object): An OCI config object or None. config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If set to true exceptions are raised return_formatted (bool): If set to true, a list object is returned. return_python_object (bool): Used for internal plugin calls Returns: A list of DB Systems Shapes """ is_supported_for = kwargs.get("is_supported_for", "DBSYSTEM") availability_domain = kwargs.get("availability_domain") compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) return_formatted = kwargs.get("return_formatted", interactive) return_python_object = kwargs.get("return_python_object", False) import oci.mysql # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) support_list = [] if 'DBSYSTEM' in is_supported_for: support_list.append('DBSYSTEM') if 'HEATWAVECLUSTER' in is_supported_for: support_list.append('HEATWAVECLUSTER') # Initialize the DbSystem client mds_client = core.get_oci_mds_client(config=config) # List the DbSystems of the current compartment data = mds_client.list_shapes(compartment_id=compartment_id, is_supported_for=support_list, availability_domain=availability_domain).data return core.return_oci_object( oci_object=data, return_python_object=return_python_object, return_formatted=return_formatted, format_function=format_mysql_shapes) except oci.exceptions.ServiceError as e: if raise_exceptions: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') except (ValueError, oci.exceptions.ClientError) as e: if raise_exceptions: raise print(f'ERROR: {e}') @plugin_function('mds.get.dbSystemShape') def get_db_system_shape(**kwargs): """Gets a certain shape specified by name The shape is specific for the given compartment and availability_domain Args: **kwargs: Additional options Keyword Args: is_supported_for (str): Either DBSYSTEM (default), HEATWAVECLUSTER or "DBSYSTEM, HEATWAVECLUSTER" availability_domain (str): The name of the availability_domain to use compartment_id (str): OCID of the parent compartment. config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised return_formatted (bool): If true a human readable string is returned return_python_object (bool): Used for internal plugin calls Returns: An shape object or None """ is_supported_for = kwargs.get("is_supported_for", "DBSYSTEM") availability_domain = kwargs.get("availability_domain") compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) return_formatted = kwargs.get("return_formatted", interactive) return_python_object = kwargs.get("return_python_object", False) try: # Get the active config and compartment config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) # Get the list of available shapes shapes = list_db_system_shapes( is_supported_for=is_supported_for, availability_domain=availability_domain, compartment_id=compartment_id, config=config, interactive=interactive, raise_exceptions=True, return_python_object=True) if not shapes: raise Exception("No shapes found.") # Let the user choose from the list shape = core.prompt_for_list_item( item_list=shapes, prompt_caption="Please enter the name or index of the shape: ", item_name_property="name", print_list=True) return core.return_oci_object( oci_object=shape, return_formatted=return_formatted, return_python_object=return_python_object, format_function=format_mysql_shapes) except Exception as e: if raise_exceptions: raise print(f'ERROR: {str(e)}') @plugin_function('mds.list.dbSystems', shell=True, cli=True, web=True) def list_db_systems(**kwargs): """Lists MySQL DB Systems Lists all DB Systems of a given compartment. Args: **kwargs: Optional parameters Keyword Args: compartment_id (str): OCID of the parent compartment. config (object): An OCI config object or None. config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If set to true exceptions are raised return_formatted (bool): If set to true, a list object is returned. return_python_object (bool): Used for internal plugin calls Returns: A list of DB Systems """ compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) return_formatted = kwargs.get("return_formatted", interactive) return_python_object = kwargs.get("return_python_object", False) import oci.mysql # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) current_db_system_id = configuration.get_current_db_system_id( config=config) mds_client = core.get_oci_mds_client(config=config) # Get the list of shapes, cSpell:ignore ANALYTICSCLUSTER all_shapes = mds_client.list_shapes(compartment_id=compartment_id, is_supported_for=['DBSYSTEM','HEATWAVECLUSTER']).data hw_shapes = [s.name for s in all_shapes if "HEATWAVECLUSTER" in s.is_supported_for] # Support for "ANALYTICSCLUSTER" has been removed from the SDK analytics_shapes = [] #[s.name for s in all_shapes if "ANALYTICSCLUSTER" in s.is_supported_for] # Initialize the DbSystem client db_sys = core.get_oci_db_system_client(config=config) # List the DbSystems of the current compartment data = db_sys.list_db_systems(compartment_id=compartment_id).data # Filter out all deleted db_systems data = [d for d in data if d.lifecycle_state != "DELETED"] # Add supported HW flags for d in data: setattr(d, "is_supported_for_hw_cluster", d.shape_name in hw_shapes) d.swagger_types["is_supported_for_hw_cluster"] = "bool" setattr(d, "is_supported_for_analytics_cluster", d.shape_name in analytics_shapes) d.swagger_types["is_supported_for_analytics_cluster"] = "bool" return core.return_oci_object( oci_object=data, return_python_object=return_python_object, return_formatted=return_formatted, format_function=format_db_systems, current=current_db_system_id) except oci.exceptions.ServiceError as e: if raise_exceptions: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') except (ValueError, oci.exceptions.ClientError) as e: if raise_exceptions: raise print(f'ERROR: {e}') @plugin_function('mds.get.dbSystem', shell=True, cli=True, web=True) def get_db_system(**kwargs): """Gets a DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The new name of the compartment. db_system_id (str): OCID of the DbSystem. ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised return_formatted (bool): If true a human readable string is returned return_python_object (bool): Used for internal plugin calls Returns: None """ db_system_name = kwargs.get("db_system_name") db_system_id = kwargs.get("db_system_id") ignore_current = kwargs.get("ignore_current", False) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) return_formatted = kwargs.get("return_formatted", interactive) return_python_object = kwargs.get("return_python_object", False) try: # Get the active config and compartment config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) current_db_system_id = configuration.get_current_db_system_id( config=config) if (not ignore_current and db_system_name is None and db_system_id is None and current_db_system_id): db_system_id = current_db_system_id import oci.identity import oci.util try: if db_system_id: return core.return_oci_object( oci_object=get_db_system_by_id( db_system_id=db_system_id, config=config), return_formatted=return_formatted, return_python_object=return_python_object, format_function=format_db_systems, current=current_db_system_id) # Initialize the DbSystem client db_sys = core.get_oci_db_system_client(config=config) # List the DbSystems of the current compartment db_systems = db_sys.list_db_systems( compartment_id=compartment_id).data # Filter out all deleted compartments db_systems = [u for u in db_systems if u.lifecycle_state != "DELETED"] if len(db_systems) == 0: if interactive: print("No MySQL DB Systems available in this compartment.") return return None # If a name was given, look it up in the list and return it if db_system_name is not None: for d in db_systems: if d.display_name.lower() == db_system_name.lower(): return core.return_oci_object( oci_object=get_db_system_by_id( db_system_id=d.id, config=config), return_formatted=return_formatted, return_python_object=return_python_object, format_function=format_db_systems, current=current_db_system_id) # If the db_systems was not found by id or name, return None if # not in interactive mode if not interactive: return None # Let the user choose from the list db_system = core.prompt_for_list_item( item_list=db_systems, prompt_caption=("Please enter the name or index " "of the MySQL DB System: "), item_name_property="display_name", given_value=db_system_name, print_list=True) if db_system: return core.return_oci_object( oci_object=get_db_system_by_id( db_system_id=db_system.id, config=config), return_formatted=return_formatted, return_python_object=return_python_object, format_function=format_db_systems, current=current_db_system_id) except oci.exceptions.ServiceError as e: if raise_exceptions: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') except Exception as e: if raise_exceptions: raise print(f'ERROR: {e}') @plugin_function('mds.get.dbSystemId', shell=True, cli=True, web=True) def get_db_system_id(**kwargs): """Gets information about the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The new name of the compartment. ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the compartment. config (object): An OCI config object or None. config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ db_system_name = kwargs.get("db_system_name") ignore_current = kwargs.get("ignore_current", False) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) db_system = get_db_system( db_system_name=db_system_name, ignore_current=ignore_current, compartment_id=compartment_id, config=config, config_profile=config_profile, interactive=interactive, raise_exceptions=raise_exceptions, return_formatted=False, return_python_object=True) return None if db_system is None else db_system.id @plugin_function('mds.update.dbSystem', shell=True, cli=True, web=True) def update_db_system(**kwargs): """Updates the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. ignore_current (bool): Whether to not default to the current bastion. new_name (str): The new name new_description (str): The new description new_freeform_tags (str): The new freeform_tags formatted as string compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ db_system_name = kwargs.get("db_system_name") db_system_id = kwargs.get("db_system_id") ignore_current = kwargs.get("ignore_current", False) new_name = kwargs.get("new_name") new_description = kwargs.get("new_description") new_freeform_tags = kwargs.get("new_freeform_tags") compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) current_db_system_id = configuration.get_current_db_system_id( config=config) if (not ignore_current and db_system_name is None and db_system_id is None and current_db_system_id): db_system_id = current_db_system_id import oci.identity import oci.mysql import mysqlsh import json try: # Get the db_system based on input params db_system = get_db_system( db_system_name=db_system_name, db_system_id=db_system_id, compartment_id=compartment_id, config=config, interactive=interactive, raise_exceptions=True, return_python_object=True) if db_system is None: if db_system_name or db_system_id: raise ValueError("DB System not found.") else: raise Exception("Cancelling operation.") if not new_name and interactive: # Prompt the user for the new values new_name = mysqlsh.globals.shell.prompt( f"Please enter a new name for the DbSystem " f"[{db_system.display_name}]: ", {'defaultValue': db_system.display_name}).strip() if not new_description and interactive: new_description = mysqlsh.globals.shell.prompt( "Please enter a new description for the DbSystem " "[current description]: ", {'defaultValue': db_system.description if db_system.description is not None else ''}).strip() if not new_freeform_tags and interactive: new_freeform_tags = mysqlsh.globals.shell.prompt( f"Please enter new freeform_tags for the DbSystem " f"[{str(db_system.freeform_tags)}]: ", {'defaultValue': json.dumps(db_system.freeform_tags) if db_system.freeform_tags is not None else ''}).strip() if new_freeform_tags and isinstance(new_freeform_tags, str): new_freeform_tags = json.loads(new_freeform_tags) if not new_name and not new_freeform_tags and not new_freeform_tags: raise ValueError("Nothing to update.") # Initialize the DbSystem client db_sys = core.get_oci_db_system_client(config=config) update_details = oci.mysql.models.UpdateDbSystemDetails( display_name=new_name, description=new_description, freeform_tags=new_freeform_tags ) db_sys.update_db_system(db_system.id, update_details) if interactive: print(f"DbSystem {db_system.display_name} is being updated.") except oci.exceptions.ServiceError as e: if raise_exceptions: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') except (ValueError, oci.exceptions.ClientError) as e: if raise_exceptions: raise print(f'ERROR: {e}') @plugin_function('mds.create.dbSystem', shell=True, cli=True, web=True) def create_db_system(**kwargs): """Creates a DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The new name of the DB System. description (str): The new description of the DB System. availability_domain (str): The name of the availability_domain shape (str): The compute shape name to use for the instance subnet_id (str): The OCID of the subnet to use configuration_id (str): The OCID of the MySQL configuration data_storage_size_in_gbs (int): The data storage size in gigabytes mysql_version (str): The MySQL version admin_username (str): The name of the administrator user account admin_password (str): The password of the administrator account private_key_file_path (str): The file path to an SSH private key par_url (str): The PAR url used for initial data import perform_cleanup_after_import (bool): Whether the bucket and PARs should be kept or deleted if an import took place source_mysql_uri (str): The MySQL Connection URI if data should be imported from an existing MySQL Server instance source_mysql_password (str): The password to use when data should be imported from an existing MySQL Server instance source_local_dump_dir (str): The path to a local directory that contains a dump source_bucket (str): The name of the source bucket that contains a dump host_image_id (str): OCID of the host image to use for this Instance. Private API only. defined_tags (dict): The defined_tags of the dynamic group. freeform_tags (dict): The freeform_tags of the dynamic group compartment_id (str): The OCID of the compartment config (object): An OCI config object or None. interactive (bool): Ask the user for input if needed return_object (bool): Whether to return the object when created Returns: None or the new DB System object if return_object is set to true """ db_system_name = kwargs.get("db_system_name") description = kwargs.get("description") availability_domain = kwargs.get("availability_domain") shape = kwargs.get("shape") subnet_id = kwargs.get("subnet_id") configuration_id = kwargs.get("configuration_id") data_storage_size_in_gbs = kwargs.get("data_storage_size_in_gbs") mysql_version = kwargs.get("mysql_version") admin_username = kwargs.get("admin_username") admin_password = kwargs.get("admin_password") private_key_file_path = kwargs.get( "private_key_file_path", "~/.ssh/id_rsa") par_url = kwargs.get("par_url") perform_cleanup_after_import = kwargs.get( "perform_cleanup_after_import") source_mysql_uri = kwargs.get("source_mysql_uri") source_mysql_password = kwargs.get("source_mysql_password") source_local_dump_dir = kwargs.get("source_local_dump_dir") source_bucket = kwargs.get("source_bucket") #host_image_id = kwargs.get("host_image_id") defined_tags = kwargs.get("defined_tags") # Conversion from Shell Dict type if defined_tags: defined_tags = dict(defined_tags) freeform_tags = kwargs.get("freeform_tags") # Conversion from Shell Dict type if freeform_tags: freeform_tags = dict(freeform_tags) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") interactive = kwargs.get("interactive", True) return_object = kwargs.get("return_object", False) try: # Get the active config and compartment config = configuration.get_current_config(config=config) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) import oci.mysql from pathlib import Path import mysqlsh from mds_plugin import compartment, compute, network, object_store import datetime import time # Set the import_source_type to 0 to default to a clean new DB System import_source_type = 0 # Check if source_* parameters are given and if so, set the correct # import_source_type if source_mysql_uri is not None: # Import from an existing MySQL Server instance import_source_type = 1 elif source_local_dump_dir is not None: # Import from a local data dir import_source_type = 2 elif source_bucket is not None: # Import from an existing bucket import_source_type = 3 # If the user did not specify a par_url, or other source paremeter, # let him choose if he wants to import data from a given source if interactive and import_source_type == 0 and par_url is None: print("Choose one of the following options of how to create the " "MySQL DB System:\n") import_sources = [ "Create a clean MySQL DB System", ("Create a MySQL DB System from an existing MySQL Server " "instance"), "Create a MySQL DB System from a local dump", ("Create a MySQL DB System from a dump stored on OCI " "Object Storage") ] import_source = core.prompt_for_list_item( item_list=import_sources, prompt_caption=("Please enter the index of an option listed " "above: "), prompt_default_value='', print_list=True) if import_source == "": print("Operation cancelled.") return import_source_type = import_sources.index(import_source) # Get a name if not db_system_name and interactive: db_system_name = core.prompt( "Please enter the name for the new DB System: ").strip() if not db_system_name: raise Exception("No name given. " "Operation cancelled.") # Get a description if not description and interactive: description = core.prompt( "Please enter a description for the new DB System: ").strip() # Get an admin_username if not admin_username and interactive: admin_username = core.prompt( "MySQL Administrator account name [admin]: ", {'defaultValue': 'admin'}).strip() if not admin_username: raise Exception("No admin username given. " "Operation cancelled.") # Get an admin_password if not admin_password and interactive: admin_password = get_validated_mysql_password( password_caption="MySQL Administrator account") if not admin_password: raise Exception("No admin password given. " "Operation cancelled.") # Get data_storage_size_in_gbs if not data_storage_size_in_gbs and interactive: data_storage_size_in_gbs = core.prompt( "Please enter the amount of data storage size in gigabytes " "with a minimum of 50 GB [50]: ", {'defaultValue': '50'}).strip() try: data_storage_size_in_gbs = int(data_storage_size_in_gbs) except ValueError: ValueError("Please enter a number for data storage size.\n") if not data_storage_size_in_gbs: raise Exception("No data storage size given. " "Operation cancelled.") # Get the availability_domain name availability_domain_obj = compartment.get_availability_domain( random_selection=not interactive, compartment_id=compartment_id, availability_domain=availability_domain, config=config, interactive=interactive, return_python_object=True) if not availability_domain_obj: raise Exception("No availability domain selected. " "Operation cancelled.") availability_domain = availability_domain_obj.name if interactive: print(f"Using availability domain {availability_domain}.") # Get the shapes shape_id = compute.get_shape_name( shape_name=shape, limit_shapes_to=[ "VM.Standard.E2.1", "VM.Standard.E2.2", "VM.Standard.E2.4", "VM.Standard.E2.8"], compartment_id=compartment_id, availability_domain=availability_domain, config=config, interactive=interactive) if shape_id is None or shape_id == "": print("Compute Shape not set or found. Operation cancelled.") return if interactive: print(f"Using shape {shape_id}.") # Get private subnet subnet = network.get_subnet( subnet_id=subnet_id, public_subnet=False, compartment_id=compartment_id, config=config, interactive=interactive, availability_domain=availability_domain) if subnet is None: print("Operation cancelled.") return if interactive: print(f"Using subnet {subnet.display_name}.") # Get mysql_version mysql_version = get_mysql_version(compartment_id=compartment_id, config=config) if mysql_version is None: print("Operation cancelled.") return print(f"Using MySQL version {mysql_version}.") # Get mysql_configuration mysql_configuration = get_db_system_configuration( configuration_id=configuration_id, shape=shape_id, availability_domain=availability_domain, compartment_id=compartment_id, config=config, return_python_object=True) if mysql_configuration is None: print("Operation cancelled.") return print(f"Using MySQL configuration {mysql_configuration.display_name}.") # TODO Check Limits # limits.list_limit_values(config["tenancy"], "mysql").data # limits.get_resource_availability( # service_name="mysql", limit_name="vm-standard-e2-4-count", # compartment_id=config["tenancy"], # availability_domain="fblN:US-ASHBURN-AD-1").data # limits.get_resource_availability( # service_name="compute", limit_name="standard-e2-core-ad-count", # compartment_id=config["tenancy"], # availability_domain="fblN:US-ASHBURN-AD-1").data # If requested, prepare import if import_source_type > 0: # If a bucket needs to be created, define a name for it if import_source_type == 1 or import_source_type == 2: # Take all alphanumeric chars from the DB System name # to create the bucket_name bucket_name = ( f"{''.join(e for e in db_system_name if e.isalnum())}_import_" f"{datetime.datetime.now():%Y%m%d%H%M%S}") print(f"\nCreating bucket {bucket_name}...") bucket = object_store.create_bucket( bucket_name=bucket_name, compartment_id=compartment_id, config=config, return_object=True) if bucket is None: print("Cancelling operation") return if perform_cleanup_after_import is None: perform_cleanup_after_import = True # Create a MySQL DB System from an existing MySQL Server instance if import_source_type == 1: # Start the dump process if not util.dump_to_bucket(bucket_name=bucket.name, connection_uri=source_mysql_uri, connection_password=source_mysql_password, create_bucket_if_not_exists=True, object_name_prefix="", interactive=interactive, return_true_on_success=True): print(f"Could not dump the given instance to the object " f"store bucket {bucket.name}") return # Create a MySQL DB System from local dir elif import_source_type == 2: if interactive and source_local_dump_dir is None: source_local_dump_dir = mysqlsh.globals.shell.prompt( "Please specify the directory path that contains the " "dump: ", {'defaultValue': ''}).strip() if source_local_dump_dir == "": print("Operation cancelled.") return elif source_local_dump_dir is None: print("No directory path given. Operation cancelled.") return # Upload the files from the given directory to the bucket file_count = object_store.create_bucket_objects_from_local_dir( local_dir_path=source_local_dump_dir, bucket_name=bucket.name, object_name_prefix="", compartment_id=compartment_id, config=config, interactive=False) if file_count is None: print("Cancelling operation") return elif import_source_type == 3: # Create a MySQL DB System from a bucket bucket = object_store.get_bucket( bucket_name=source_bucket, compartment_id=compartment_id, config=config) if bucket is None: print("Cancelling operation") return bucket_name = bucket.name if perform_cleanup_after_import is None: perform_cleanup_after_import = False # Create PAR for import manifest and progress files par, progress_par = util.create_bucket_import_pars( object_name_prefix="", bucket_name=bucket.name, db_system_name=db_system_name, compartment_id=compartment_id, config=config) if par is None or progress_par is None: return # Build URLs par_url_prefix = object_store.get_par_url_prefix(config=config) par_url = par_url_prefix + par.access_uri # progress_par_url = par_url_prefix + progress_par.access_uri # Once the API supports the new PAR based import, build the # import_details using the given par_url # if par_url: # import urllib.parse # import_details = oci.mysql.models.\ # CreateDbSystemSourceImportFromUrlDetails( # source_type=oci.mysql.models. # CreateDbSystemSourceImportFromUrlDetails. # SOURCE_TYPE_IMPORTURL, # source_url=(f'{par_url}?progressPar=' # f'{urllib.parse.quote(progress_par_url)}')) db_system_details = oci.mysql.models.CreateDbSystemDetails( description=description, admin_username=admin_username, admin_password=admin_password, compartment_id=compartment_id, configuration_id=mysql_configuration.id, data_storage_size_in_gbs=data_storage_size_in_gbs, display_name=db_system_name, mysql_version=mysql_version, shape_name=shape_id, availability_domain=availability_domain, subnet_id=subnet.id, defined_tags=defined_tags, freeform_tags=freeform_tags # host_image_id=host_image_id # source=import_details ) # Get DbSystem Client db_sys = core.get_oci_db_system_client(config=config) # Create DB System new_db_system = db_sys.create_db_system(db_system_details).data # If there was a PAR URL given, wait till the system becomes # ACTIVE and then perform the clean up work if par_url is not None: print("Waiting for MySQL DB System to become active.\n" "This can take up to 20 minutes or more...", end="") # Wait until the lifecycle_state == ACTIVE, 20 minutes max cycles = 0 while cycles < 240: db_system = db_sys.get_db_system(new_db_system.id).data if db_system.lifecycle_state == "ACTIVE" or \ db_system.lifecycle_state == "FAILED": break else: time.sleep(10) print(".", end="") cycles += 1 print("") # Until the API is ready to directly import at deployment time, # also start the import from here if db_system.lifecycle_state == "ACTIVE": util.import_from_bucket( bucket_name=bucket_name, db_system_id=new_db_system.id, db_system_name=db_system_name, object_name_prefix="", admin_username=admin_username, admin_password=admin_password, private_key_file_path=private_key_file_path, perform_cleanup=perform_cleanup_after_import, compartment_id=compartment_id, config=config, interactive=False ) else: if return_object: return new_db_system else: if new_db_system.lifecycle_state == "CREATING": print(f"\nMySQL DB System {db_system_name} is being created.\n" f"Use mds.ls() to check it's provisioning state.\n") else: print(f"\nThe creation of the MySQL DB System {db_system_name} " "failed.\n") except oci.exceptions.ServiceError as e: if not interactive: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') return except (ValueError, oci.exceptions.ClientError) as e: if not interactive: raise print(f'ERROR: {e}') return @plugin_function('mds.delete.dbSystem', shell=True, cli=True, web=True) def delete_db_system(**kwargs): """Updates the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ db_system_name = kwargs.get("db_system_name") db_system_id = kwargs.get("db_system_id") await_completion = kwargs.get("await_completion") ignore_current = kwargs.get("ignore_current", False) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) # Get the active config and compartment try: import oci.mysql import mysqlsh db_system = get_db_system( db_system_name=db_system_name, db_system_id=db_system_id, compartment_id=compartment_id, config=config, interactive=interactive, raise_exceptions=raise_exceptions, ignore_current=ignore_current, return_python_object=True) if db_system is None: if db_system_name or db_system_id: raise ValueError("DB System not found.") else: raise Exception("Cancelling operation.") if interactive: # Prompt the user for specifying a compartment prompt = mysqlsh.globals.shell.prompt( f"Are you sure you want to delete the MySQL DB System " f"{db_system.display_name} [yes/NO]: ", {'defaultValue': 'no'}).strip().lower() if prompt != "yes": print("Deletion aborted.\n") return # Get DbSystem Client db_sys = core.get_oci_db_system_client(config=config) # Delete the DB System work_request_id = db_sys.delete_db_system(db_system.id).headers["opc-work-request-id"] # If the function should wait till the bastion reaches the correct # lifecycle state if await_completion: await_lifecycle_state( db_system.id, "DELETED", "complete the deletion process", config, interactive, work_request_id) elif interactive: print(f"MySQL DB System '{db_system.display_name}' is being " "deleted.") except oci.exceptions.ServiceError as e: if interactive: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') return except Exception as e: if raise_exceptions: raise print(f'ERROR: {e}') @plugin_function('mds.stop.dbSystem', shell=True, cli=True, web=True) def stop_db_system(**kwargs): """Stops the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ change_lifecycle_state(**kwargs, action=DB_SYSTEM_ACTION_STOP) @plugin_function('mds.start.dbSystem', shell=True, cli=True, web=True) def start_db_system(**kwargs): """Starts the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ change_lifecycle_state(**kwargs, action=DB_SYSTEM_ACTION_START) @plugin_function('mds.restart.dbSystem', shell=True, cli=True, web=True) def restart_db_system(**kwargs): """Restarts the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ change_lifecycle_state(**kwargs, action=DB_SYSTEM_ACTION_RESTART) @plugin_function('mds.stop.heatWaveCluster', shell=True, cli=True, web=True) def stop_hw_cluster(**kwargs): """Stops the HeatWave cluster with the given DBSystem id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ change_lifecycle_state(**kwargs, action=HW_CLUSTER_ACTION_STOP) @plugin_function('mds.start.heatWaveCluster', shell=True, cli=True, web=True) def start_hw_cluster(**kwargs): """Starts the HeatWave cluster with the given DBSystem id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ change_lifecycle_state(**kwargs, action=HW_CLUSTER_ACTION_START) @plugin_function('mds.restart.heatWaveCluster', shell=True, cli=True, web=True) def restart_hw_cluster(**kwargs): """Restarts the HeatWave cluster with the given DBSystem id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ change_lifecycle_state(**kwargs, action=HW_CLUSTER_ACTION_RESTART) def change_lifecycle_state(**kwargs): """Starts or stops the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised action (int): The action to execute Returns: None """ db_system_name = kwargs.get("db_system_name") db_system_id = kwargs.get("db_system_id") await_completion = kwargs.get("await_completion") ignore_current = kwargs.get("ignore_current", False) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) action = kwargs.get("action") if action == DB_SYSTEM_ACTION_START or action == HW_CLUSTER_ACTION_START: action_name = "start" action_state = "ACTIVE" elif action == DB_SYSTEM_ACTION_STOP or action == HW_CLUSTER_ACTION_STOP: action_name = "stop" action_state = "INACTIVE" elif action == DB_SYSTEM_ACTION_RESTART or action == HW_CLUSTER_ACTION_RESTART: action_name = "restart" action_state = "ACTIVE" else: raise ValueError("Unknown action given.") db_system_action = ( action == DB_SYSTEM_ACTION_START or action == DB_SYSTEM_ACTION_STOP or action == DB_SYSTEM_ACTION_RESTART ) action_obj = "DB System" if db_system_action else "HeatWave Cluster" # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) # Get the active config and compartment try: import oci.mysql import mysqlsh db_system = get_db_system( db_system_name=db_system_name, db_system_id=db_system_id, compartment_id=compartment_id, config=config, interactive=interactive, raise_exceptions=raise_exceptions, ignore_current=ignore_current, return_python_object=True) if db_system is None: if db_system_name or db_system_id: raise ValueError("DB System not found.") else: raise Exception("Cancelling operation.") else: db_system_id = db_system.id if interactive: # Prompt the user for specifying a compartment prompt = mysqlsh.globals.shell.prompt( f"Are you sure you want to {action_name} the {action_obj} " f"{db_system.display_name} [yes/NO]: ", {'defaultValue': 'no'}).strip().lower() if prompt != "yes": print("Operation cancelled.\n") return # Get DbSystem Client db_sys = core.get_oci_db_system_client(config=config) work_request_id = None if action == DB_SYSTEM_ACTION_STOP: # Stop the DB System work_request_id = db_sys.stop_db_system( db_system_id, oci.mysql.models.StopDbSystemDetails( shutdown_type="IMMEDIATE" )).headers["opc-work-request-id"] elif action == DB_SYSTEM_ACTION_START: # Start the DB System work_request_id = db_sys.start_db_system(db_system_id).headers["opc-work-request-id"] elif action == DB_SYSTEM_ACTION_RESTART: # Restart the DB System work_request_id = db_sys.restart_db_system( db_system_id, oci.mysql.models.RestartDbSystemDetails( shutdown_type="IMMEDIATE" )) elif action == HW_CLUSTER_ACTION_STOP: # Stop the HW Cluster work_request_id = db_sys.stop_heat_wave_cluster(db_system_id).headers["opc-work-request-id"] elif action == HW_CLUSTER_ACTION_START: # Start the HW Cluster work_request_id = db_sys.start_heat_wave_cluster(db_system_id).headers["opc-work-request-id"] elif action == HW_CLUSTER_ACTION_RESTART: # Restart the HW Cluster work_request_id = db_sys.restart_heat_wave_cluster(db_system_id).headers["opc-work-request-id"] # If the function should wait till the bastion reaches the correct # lifecycle state if await_completion: if db_system_action: await_lifecycle_state( db_system_id, action_state, action_name, config, interactive, work_request_id) else: await_hw_cluster_lifecycle_state( db_system_id, action_state, action_name, config, interactive, work_request_id) elif interactive: print(f"MySQL {action_obj} '{db_system.display_name}' is being " f"{action_name}{'p' if action_name == 'stop' else ''}ed.") except oci.exceptions.ServiceError as e: if interactive: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') return except Exception as e: if raise_exceptions: raise print(f'ERROR: {e}') def await_lifecycle_state(db_system_id, action_state, action_name, config, interactive, work_request_id): """Waits of the db_system to reach the desired lifecycle state Args: db_system_id (str): OCID of the DbSystem. action_state (str): The lifecycle state to reach action_name (str): The name of the action to be performed config (dict): An OCI config object or None interactive (bool): Indicates whether to execute in interactive mode request_id (bool): The request_id of the action Returns: None """ import time # Get DbSystem Client db_sys = core.get_oci_db_system_client(config=config) # Get WorkRequest Client req_client = core.get_oci_work_requests_client(config=config) # Wait for the lifecycle to reach desired state cycles = 0 while cycles < 120: db_system = db_sys.get_db_system( db_system_id=db_system_id).data if db_system.lifecycle_state == action_state: break else: if interactive: s = "." * (cycles + 1) try: if work_request_id: req = req_client.get_work_request(work_request_id=work_request_id).data s = f" {req.percent_complete:.0f}% completed." except: pass print(f'Waiting for DB System to {action_name}...{s}', end='\r') time.sleep(5) cycles += 1 if interactive: print("") if db_system.lifecycle_state != action_state: raise Exception("The DB System did not reach the correct " "state within 10 minutes.") if interactive: print(f"DB System '{db_system.display_name}' did " f"{action_name} successfully.") def await_hw_cluster_lifecycle_state(db_system_id, action_state, action_name, config, interactive, work_request_id): """Waits of the db_system to reach the desired lifecycle state Args: db_system_id (str): OCID of the DbSystem. action_state (str): The lifecycle state to reach action_name (str): The name of the action to be performed config (dict): An OCI config object or None interactive (bool): Indicates whether to execute in interactive mode Returns: None """ import time # Get DbSystem Client db_sys = core.get_oci_db_system_client(config=config) # Get WorkRequest Client req_client = core.get_oci_work_requests_client(config=config) # Wait for the lifecycle to reach desired state cycles = 0 while cycles < 240: db_system = db_sys.get_db_system( db_system_id=db_system_id).data if db_system.heat_wave_cluster and db_system.heat_wave_cluster.lifecycle_state == action_state: break else: if interactive: s = "." * (cycles + 1) try: if work_request_id: req = req_client.get_work_request(work_request_id=work_request_id).data s = f" {req.percent_complete:.0f}% completed." except: pass print(f'Waiting for HeatWave Cluster to {action_name}...{s}') time.sleep(5) cycles += 1 if interactive: print("") if (not db_system.heat_wave_cluster) or db_system.heat_wave_cluster.lifecycle_state != action_state: raise Exception("The HeatWave Cluster did not reach the correct " "state within 20 minutes.") if interactive: print(f"The HeatWave Cluster of DB System '{db_system.display_name}' did " f"{action_name} successfully.") @plugin_function('mds.create.heatWaveCluster', shell=True, cli=True, web=True) def create_hw_cluster(**kwargs): """Adds a HeatWave cluster to the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. ignore_current (bool): Whether to not default to the current DB System. cluster_size (int): The size of the cluster shape_name (str): The name of the shape to use await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state compartment_id (str): OCID of the parent compartment. config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ db_system_name = kwargs.get("db_system_name") db_system_id = kwargs.get("db_system_id") ignore_current = kwargs.get("ignore_current", False) cluster_size = kwargs.get("cluster_size") shape_name = kwargs.get("shape_name") await_completion = kwargs.get("await_completion", False) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) current_db_system_id = configuration.get_current_db_system_id( config=config) if (not ignore_current and db_system_name is None and db_system_id is None and current_db_system_id): db_system_id = current_db_system_id import oci.identity import oci.mysql import mysqlsh import json try: # Get the db_system based on input params db_system = get_db_system( db_system_name=db_system_name, db_system_id=db_system_id, compartment_id=compartment_id, config=config, interactive=interactive, raise_exceptions=True, return_python_object=True) if db_system is None: if db_system_name or db_system_id: raise ValueError("DB System not found.") else: raise Exception("Cancelling operation.") if not cluster_size and interactive: # Prompt the user for the new values cluster_size = mysqlsh.globals.shell.prompt( f"Please enter the number of nodes for the HeatWave cluster " f"(1 - 64): ", {'defaultValue': '1'}).strip() if cluster_size is None: raise ValueError("The cluster_size was not specified.") if cluster_size == "": cluster_size = '1' try: cluster_size = int(cluster_size) except: raise ValueError(f"'{cluster_size}' is not a valid number.") if cluster_size < 1 or cluster_size > 64: raise ValueError(f"The cluster size must be between 1 and 64. A size of {cluster_size} was given.") if not shape_name and interactive: shape = get_db_system_shape( is_supported_for="HEATWAVECLUSTER", compartment_id=db_system.compartment_id, config=config, config_profile=config_profile, interactive=True, raise_exceptions=True, return_python_object=True) if shape: shape_name = shape.name if not shape_name: raise ValueError("No shape name given.") # Initialize the DbSystem client db_sys = core.get_oci_db_system_client(config=config) details = oci.mysql.models.AddHeatWaveClusterDetails( cluster_size=cluster_size, shape_name=shape_name, ) work_request_id = db_sys.add_heat_wave_cluster( db_system.id, add_heat_wave_cluster_details=details).headers["opc-work-request-id"] if await_completion: await_hw_cluster_lifecycle_state(db_system_id=db_system.id, action_state='ACTIVE', action_name="start", config=config, interactive=interactive, work_request_id=work_request_id) elif interactive: print(f"The HeatWave Cluster of the MySQL DB System '{db_system.display_name}' is being " "created.") except oci.exceptions.ServiceError as e: if raise_exceptions: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') except (ValueError, oci.exceptions.ClientError) as e: if raise_exceptions: raise print(f'ERROR: {e}') @plugin_function('mds.update.heatWaveCluster', shell=True, cli=True, web=True) def update_hw_cluster(**kwargs): """Update the HeatWave cluster for a DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. ignore_current (bool): Whether to not default to the current DB System. cluster_size (int): The size of the cluster shape_name (str): The name of the shape to use await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state compartment_id (str): OCID of the parent compartment. config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ db_system_name = kwargs.get("db_system_name") db_system_id = kwargs.get("db_system_id") ignore_current = kwargs.get("ignore_current", False) cluster_size = kwargs.get("cluster_size") shape_name = kwargs.get("shape_name") await_completion = kwargs.get("await_completion", False) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) current_db_system_id = configuration.get_current_db_system_id( config=config) if (not ignore_current and db_system_name is None and db_system_id is None and current_db_system_id): db_system_id = current_db_system_id import oci.identity import oci.mysql import mysqlsh import json try: # Get the db_system based on input params db_system = get_db_system( db_system_name=db_system_name, db_system_id=db_system_id, compartment_id=compartment_id, config=config, interactive=interactive, raise_exceptions=True, return_python_object=True) if db_system is None: if db_system_name or db_system_id: raise ValueError("DB System not found.") else: raise Exception("Cancelling operation.") if not cluster_size and interactive: # Prompt the user for the new values cluster_size = mysqlsh.globals.shell.prompt( f"Please enter the number of nodes for the HeatWave cluster " f"(1 - 64): ", {'defaultValue': '1'}).strip() if cluster_size is None: raise ValueError("The cluster_size was not specified.") if cluster_size == "": cluster_size = '1' try: cluster_size = int(cluster_size) except: raise ValueError(f"'{cluster_size}' is not a valid number.") if cluster_size < 1 or cluster_size > 64: raise ValueError(f"The cluster size must be between 1 and 64. A size of {cluster_size} was given.") if not shape_name and interactive: shape = get_db_system_shape( is_supported_for="HEATWAVECLUSTER", compartment_id=db_system.compartment_id, config=config, config_profile=config_profile, interactive=True, raise_exceptions=True, return_python_object=True) if shape: shape_name = shape.name if not shape_name: raise ValueError("No shape name given.") # Initialize the DbSystem client db_sys = core.get_oci_db_system_client(config=config) details = oci.mysql.models.UpdateHeatWaveClusterDetails( cluster_size=cluster_size, shape_name=shape_name, ) work_request_id = db_sys.update_heat_wave_cluster( db_system.id, update_heat_wave_cluster_details=details).headers["opc-work-request-id"] if await_completion: await_hw_cluster_lifecycle_state(db_system_id=db_system.id, action_state='ACTIVE', action_name="rescale", config=config, interactive=interactive, work_request_id=work_request_id) elif interactive: print(f"The HeatWave Cluster of the MySQL DB System '{db_system.display_name}' is being " "rescaled.") except oci.exceptions.ServiceError as e: if raise_exceptions: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') except (ValueError, oci.exceptions.ClientError) as e: if raise_exceptions: raise print(f'ERROR: {e}') @plugin_function('mds.delete.heatWaveCluster', shell=True, cli=True, web=True) def delete_hw_cluster(**kwargs): """Deletes the DbSystem with the given id If no id is given, it will prompt the user for the id. Args: **kwargs: Optional parameters Keyword Args: db_system_name (str): The name of the DB System. db_system_id (str): OCID of the DbSystem. await_completion (bool): Whether to wait till the DbSystem reaches the desired lifecycle state ignore_current (bool): Whether to not default to the current bastion. compartment_id (str): OCID of the parent compartment config (dict): An OCI config object or None config_profile (str): The name of an OCI config profile interactive (bool): Indicates whether to execute in interactive mode raise_exceptions (bool): If true exceptions are raised Returns: None """ db_system_name = kwargs.get("db_system_name") db_system_id = kwargs.get("db_system_id") await_completion = kwargs.get("await_completion") ignore_current = kwargs.get("ignore_current", False) compartment_id = kwargs.get("compartment_id") config = kwargs.get("config") config_profile = kwargs.get("config_profile") interactive = kwargs.get("interactive", core.get_interactive_default()) raise_exceptions = kwargs.get("raise_exceptions", not interactive) # Get the active config and compartment try: config = configuration.get_current_config( config=config, config_profile=config_profile, interactive=interactive) compartment_id = configuration.get_current_compartment_id( compartment_id=compartment_id, config=config) # Get the active config and compartment try: import oci.mysql import mysqlsh db_system = get_db_system( db_system_name=db_system_name, db_system_id=db_system_id, compartment_id=compartment_id, config=config, interactive=interactive, raise_exceptions=raise_exceptions, ignore_current=ignore_current, return_python_object=True) if db_system is None: if db_system_name or db_system_id: raise ValueError("DB System not found.") else: raise Exception("Cancelling operation.") if interactive: # Prompt the user for specifying a compartment prompt = mysqlsh.globals.shell.prompt( f"Are you sure you want to delete the HeatWave Cluster of the MySQL DB System " f"{db_system.display_name} [yes/NO]: ", {'defaultValue': 'no'}).strip().lower() if prompt != "yes": print("Deletion aborted.\n") return # Get DbSystem Client db_sys = core.get_oci_db_system_client(config=config) # Delete the HW Cluster work_request_id = db_sys.delete_heat_wave_cluster(db_system.id).headers["opc-work-request-id"] # If the function should wait till the bastion reaches the correct # lifecycle state if await_completion: await_hw_cluster_lifecycle_state( db_system.id, "DELETED", "complete the deletion process", config, interactive, work_request_id=work_request_id) elif interactive: print(f"The HeatWave Cluster of the MySQL DB System '{db_system.display_name}' is being " "deleted.") except oci.exceptions.ServiceError as e: if interactive: raise print(f'ERROR: {e.message}. (Code: {e.code}; Status: {e.status})') return except Exception as e: if raise_exceptions: raise print(f'ERROR: {e}')