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}')