diff --git a/_cmd.py b/_cmd.py
index 36b82fd..df55e20 100644
--- a/_cmd.py
+++ b/_cmd.py
@@ -258,6 +258,15 @@ def do_command(self):
def _do_command(self, obd):
raise NotImplementedError
+ def get_white_ip_list(self):
+ if self.opts.white:
+ return self.opts.white.split(',')
+ ROOT_IO.warn("Security Risk: the whitelist is empty and anyone can request this program!")
+ if ROOT_IO.confirm("Do you want to continue?"):
+ return []
+ wthite_ip_list = ROOT_IO.read("Please enter the whitelist, eq: '192.168.1.1'")
+ raise wthite_ip_list.split(',')
+
class MajorCommand(BaseCommand):
@@ -690,6 +699,22 @@ def _do_command(self, obd):
else:
return self._show_help()
+class ClusterExportToOCPCommand(ClusterMirrorCommand):
+
+ def __init__(self):
+ super(ClusterExportToOCPCommand, self).__init__('export-to-ocp', 'Export obcluster to OCP')
+ self.parser.add_option('-a', '--address', type='string', help="OCP address, example http://127.0.0.1:8080, you can find it in OCP system parameters with Key='ocp.site.url'")
+ self.parser.add_option('-u', '--user', type='string', help="OCP user, this user should have create cluster privilege.")
+ self.parser.add_option('-p', '--password', type='string', help="OCP user password.")
+ self.parser.add_option('--host_type', type='string', help="Host type of observer, a host type will be created when there's no host type exists in ocp, the first host type will be used if this parameter is empty.", default="")
+ self.parser.add_option('--credential_name', type='string', help="Credential used to connect hosts, a credential will be created if credential_name is empty or no credential with this name exists in ocp.", default="")
+
+ def _do_command(self, obd):
+ if self.cmds:
+ return obd.export_to_ocp(self.cmds[0])
+ else:
+ return self._show_help()
+
class DemoCommand(ClusterMirrorCommand):
@@ -718,14 +743,18 @@ class WebCommand(ObdCommand):
def __init__(self):
super(WebCommand, self).__init__('web', 'Start obd deploy application as web.')
self.parser.add_option('-p', '--port', type='int', help="web server listen port", default=8680)
+ self.parser.add_option('-w', '--white', type='str', help="ip white list, eq: '127.0.0.1, 192.168.1.1'.", default='')
def _do_command(self, obd):
from service.app import OBDWeb
+ # white_ip_list = self.get_white_ip_list()
+ url = '/#/updateWelcome' if self.cmds and self.cmds[0] in ('upgrade', 'update') else ''
+
ROOT_IO.print('start OBD WEB in 0.0.0.0:%s' % self.opts.port)
- ROOT_IO.print('please open http://{0}:{1}'.format(NetUtil.get_host_ip(), self.opts.port))
+ ROOT_IO.print('please open http://{0}:{1}{2}'.format(NetUtil.get_host_ip(), self.opts.port, url))
try:
COMMAND_ENV.set(ENV.ENV_DISABLE_PARALLER_EXTRACT, True, stdio=obd.stdio)
- OBDWeb(obd, self.OBD_INSTALL_PATH).start(self.opts.port)
+ OBDWeb(obd, None, self.OBD_INSTALL_PATH).start(self.opts.port)
except KeyboardInterrupt:
ROOT_IO.print('Keyboard Interrupt')
except BaseException as e:
@@ -926,6 +955,7 @@ def __init__(self):
def _do_command(self, obd):
if self.cmds:
+ ROOT_IO.default_confirm = False
return obd.edit_deploy_config(self.cmds[0])
else:
return self._show_help()
@@ -1137,6 +1167,7 @@ class ClusterMajorCommand(MajorCommand):
def __init__(self):
super(ClusterMajorCommand, self).__init__('cluster', 'Deploy and manage a cluster.')
self.register_command(ClusterCheckForOCPChange())
+ self.register_command(ClusterExportToOCPCommand())
self.register_command(ClusterConfigStyleChange())
self.register_command(ClusterAutoDeployCommand())
self.register_command(ClusterDeployCommand())
@@ -1525,6 +1556,8 @@ def __init__(self):
super(ObdiagCommand, self).__init__('obdiag', 'Oceanbase Diagnostic Tool')
self.register_command(ObdiagDeployCommand())
self.register_command(ObdiagGatherCommand())
+ self.register_command(ObdiagAnalyzeCommand())
+ self.register_command(ObdiagCheckCommand())
class ObdiagDeployCommand(ObdCommand):
@@ -1549,7 +1582,7 @@ def init(self, cmd, args):
def _do_command(self, obd):
if self.cmds:
- return obd.obdiag_gather(self.cmds[0], "gather_%s" % self.name, self.opts)
+ return obd.obdiag_online_func(self.cmds[0], "gather_%s" % self.name, self.opts)
else:
return self._show_help()
@@ -1741,6 +1774,72 @@ def __init__(self):
self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./')
self.parser.add_option('--obdiag_dir', type='string', help="obdiag install dir",default=OBDIAG_HOME_PATH)
+class ObdiagAnalyzeMirrorCommand(ObdCommand):
+
+ def init(self, cmd, args):
+ super(ObdiagAnalyzeMirrorCommand, self).init(cmd, args)
+ self.parser.set_usage('%s [options]' % self.prev_cmd)
+ return self
+
+ def _do_command(self, obd):
+ offline_args_sign = '--files'
+ if self.args and (offline_args_sign in self.args):
+ return obd.obdiag_offline_func("analyze_%s" % self.name, self.opts)
+ if self.cmds:
+ return obd.obdiag_online_func(self.cmds[0], "analyze_%s" % self.name, self.opts)
+ else:
+ return self._show_help()
+
+
+class ObdiagAnalyzeCommand(MajorCommand):
+
+ def __init__(self):
+ super(ObdiagAnalyzeCommand, self).__init__('analyze', 'Analyze oceanbase diagnostic info')
+ self.register_command(ObdiagAnalyzeLogCommand())
+
+class ObdiagAnalyzeLogCommand(ObdiagAnalyzeMirrorCommand):
+
+ def init(self, cmd, args):
+ super(ObdiagAnalyzeLogCommand, self).init(cmd, args)
+ return self
+
+ @property
+ def lock_mode(self):
+ return LockMode.NO_LOCK
+
+ def __init__(self):
+ super(ObdiagAnalyzeLogCommand, self).__init__('log', 'Analyze oceanbase log from online observer machines or offline oceanbase log files')
+ self.parser.add_option('--from', type='string', help="specify the start of the time range. format: yyyy-mm-dd hh:mm:ss")
+ self.parser.add_option('--to', type='string', help="specify the end of the time range. format: yyyy-mm-dd hh:mm:ss")
+ self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.",default='30m')
+ self.parser.add_option('--scope', type='string', help="log type constrains, choices=[observer, election, rootservice, all]",default='all')
+ self.parser.add_option('--grep', type='string', help="specify keywords constrain")
+ self.parser.add_option('--log_level', type='string', help="oceanbase logs greater than or equal to this level will be analyze, choices=[DEBUG, TRACE, INFO, WDIAG, WARN, EDIAG, ERROR]")
+ self.parser.add_option('--files', type='string', help="specify files")
+ self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./')
+ self.parser.add_option('--obdiag_dir', type='string', help="obdiag install dir",default=OBDIAG_HOME_PATH)
+
+
+class ObdiagCheckCommand(ObdCommand):
+
+ def __init__(self):
+ super(ObdiagCheckCommand, self).__init__('check', 'check oceanbase cluster')
+ self.parser.add_option('--cases', type='string', help="The name of the check task set that needs to be executed")
+ self.parser.add_option('--report_path', type='string', help='ouput report path', default='./check_report/')
+ self.parser.add_option('--obdiag_dir', type='string', help="obdiag install dir", default=OBDIAG_HOME_PATH)
+
+ def init(self, cmd, args):
+ super(ObdiagCheckCommand, self).init(cmd, args)
+ self.parser.set_usage(
+ '%s [options]' % self.prev_cmd)
+ return self
+
+ def _do_command(self, obd):
+ if len(self.cmds) > 0:
+ return obd.obdiag_online_func(self.cmds[0], "checker", self.opts)
+ else:
+ return self._show_help()
+
class MainCommand(MajorCommand):
diff --git a/_deploy.py b/_deploy.py
index 3be249f..5c519fb 100644
--- a/_deploy.py
+++ b/_deploy.py
@@ -942,10 +942,10 @@ class DeployInstallMode(object):
class DeployInfo(object):
- def __init__(self, name, status, components=OrderedDict(), config_status=DeployConfigStatus.UNCHNAGE):
+ def __init__(self, name, status, components=None, config_status=DeployConfigStatus.UNCHNAGE):
self.status = status
self.name = name
- self.components = components
+ self.components = components if components else {}
self.config_status = config_status
def __str__(self):
@@ -1012,24 +1012,28 @@ def set_auto_create_tenant(self, status):
return self._dump()
return True
- def update_component_package_hash(self, component, package_hash, version=None):
+ def update_component_info(self, repository):
+ component = repository.name
if component not in self.components:
return False
ori_data = self._src_data[component]
src_data = deepcopy(ori_data)
- src_data['package_hash'] = package_hash
- if version:
- src_data['version'] = version
- elif 'version' in src_data:
- del src_data['version']
+
+ if 'package_hash' in src_data:
+ src_data['package_hash'] = repository.hash
+ if 'version' in src_data:
+ src_data['version'] = repository.version
+ if 'release' in src_data:
+ src_data['release'] = repository.release
if 'tag' in src_data:
del src_data['tag']
self._src_data[component] = src_data
if self._dump():
cluster_config = self.components[component]
- cluster_config.package_hash = src_data.get('package_hash')
- cluster_config.version = src_data.get('version')
+ cluster_config.package_hash = repository.hash
+ cluster_config.version = repository.version
+ cluster_config.release = repository.release
cluster_config.tag = None
return True
self._src_data[component] = ori_data
@@ -1446,7 +1450,7 @@ def update_upgrade_ctx(self, **uprade_ctx):
return False
def update_component_repository(self, repository):
- if not self.deploy_config.update_component_package_hash(repository.name, repository.hash, repository.version):
+ if not self.deploy_config.update_component_info(repository):
return False
self.use_model(repository.name, repository)
return True
diff --git a/_environ.py b/_environ.py
index 473658e..4df5f0c 100644
--- a/_environ.py
+++ b/_environ.py
@@ -44,4 +44,10 @@
TELEMETRY_LOG_MODE = "TELEMETRY_LOG_MODE"
# ROOT IO DEFAULT CONFIRM. 0 - disable, 1 - enable.
-ENV_DEFAULT_CONFIRM = "IO_DEFAULT_CONFIRM"
\ No newline at end of file
+ENV_DEFAULT_CONFIRM = "IO_DEFAULT_CONFIRM"
+
+# Disable ssh ALGORITHMS. 1 - disable algorithms, 0 - enable algorithms.
+ENV_DISABLE_RSA_ALGORITHMS = 'OBD_DISABLE_RSA_ALGORITHMS'
+
+# set local connection when using host ip. {0/1} 0 - no local connection. 1 - local connection.
+ENV_HOST_IP_MODE = "HOST_IP_MODE"
\ No newline at end of file
diff --git a/_errno.py b/_errno.py
index 58830f3..796c08e 100644
--- a/_errno.py
+++ b/_errno.py
@@ -120,7 +120,7 @@ class InitDirFailedErrorMessage(object):
EC_CONFIG_CONFLICT_DIR = OBDErrorCodeTemplate(1004, 'Configuration conflict {server1}: {path} is used for {server2}\'s {key}')
EC_SOME_SERVER_STOPED = OBDErrorCodeTemplate(1005, 'Some of the servers in the cluster have been stopped')
EC_FAIL_TO_CONNECT = OBDErrorCodeTemplate(1006, 'Failed to connect to {component}')
-EC_ULIMIT_CHECK = OBDErrorCodeTemplate(1007, '({server}) {key} must not be less than {need} (Current value: {now})')
+EC_ULIMIT_CHECK = OBDErrorCodeTemplate(1007, '({server}) The value of the ulimit parameter "{key}" must not be less than {need} (Current value: {now})')
EC_FAILED_TO_GET_AIO_NR = OBDErrorCodeTemplate(1008, '({ip}) failed to get fs.aio-max-nr and fs.aio-nr')
EC_NEED_CONFIG = OBDErrorCodeTemplate(1009, '{server} {component} need config: {miss_keys}')
EC_NO_SUCH_NET_DEVICE = OBDErrorCodeTemplate(1010, '{server} No such net interface: {devname}')
@@ -128,6 +128,8 @@ class InitDirFailedErrorMessage(object):
EC_PARAM_CHECK = OBDErrorCodeTemplate(1012, '{errors}')
EC_SSH_CONNECT = OBDErrorCodeTemplate(1013, '{user}@{ip} connect failed: {message}')
EC_CHECK_STANDBY = OBDErrorCodeTemplate(1015, 'Unable to confirm the primary-standby relationship, rerun with "--ignore-standby" option if you want to proceed despite the risks.')
+EC_FAILED_TO_GET_PARAM = OBDErrorCodeTemplate(1016, '({ip}) failed to get {key} using command "{cmd}"')
+EC_PARAM_NOT_IN_NEED = OBDErrorCodeTemplate(1017, '({ip}) The value of the "{check_item}" must be {need} (Current value: {now}, Recommended value: {recommend})')
# error code for observer
EC_OBSERVER_NOT_ENOUGH_MEMORY = OBDErrorCodeTemplate(2000, '({ip}) not enough memory. (Free: {free}, Need: {need})')
@@ -143,6 +145,7 @@ class InitDirFailedErrorMessage(object):
EC_OBSERVER_FAILED_TO_REGISTER_WITH_DETAILS = OBDErrorCodeTemplate(2005, 'Failed to register cluster. {appname} may have been registered in {obconfig_url}.')
EC_OBSERVER_MULTI_NET_DEVICE = OBDErrorCodeTemplate(2006, '{ip} has more than one network interface. Please set `devname` for ({server})')
EC_OBSERVER_PING_FAILED = OBDErrorCodeTemplate(2007, '{ip1} {devname} fail to ping {ip2}. Please check configuration `devname`')
+EC_OBSERVER_PING_FAILED_WITH_NO_DEVNAME = OBDErrorCodeTemplate(2007, '{ip1} fail to ping {ip2}. Please check your network')
EC_OBSERVER_TIME_OUT_OF_SYNC = OBDErrorCodeTemplate(2008, 'Cluster clocks are out of sync')
EC_OBSERVER_PRODUCTION_MODE_LIMIT = OBDErrorCodeTemplate(2009, '({server}): when production_mode is True, {key} can not be less then {limit}')
EC_OBSERVER_SYS_MEM_TOO_LARGE = OBDErrorCodeTemplate(2010, '({server}): system_memory too large. system_memory must be less than memory_limit/memory_limit_percentage.')
@@ -176,7 +179,23 @@ class InitDirFailedErrorMessage(object):
EC_OCP_EXPRESS_META_DB_NOT_ENOUGH_LOG_DISK = OBDErrorCodeTemplate(4305, 'There is not enough log disk for ocp meta tenant.')
EC_OCP_EXPRESS_META_DB_NOT_ENOUGH_MEM = OBDErrorCodeTemplate(4305, 'There is not enough memory for ocp meta tenant')
EC_OCP_EXPRESS_ADMIN_PASSWD_ERROR = OBDErrorCodeTemplate(4306, '({ip}) ocp-express admin_passwd invalid.(Current :{current})')
-# 4350-4399 had been used by ocp
+
+
+# ocp-server
+EC_OCP_SERVER_RUNNING_TASK = OBDErrorCodeTemplate(4350, 'The Server have running task')
+EC_OCP_SERVER_MACHINE_STATUS = OBDErrorCodeTemplate(4351, 'The Server have gone')
+EC_OCP_SERVER_METADB_VERSION = OBDErrorCodeTemplate(4352, 'Metadb version not fewer than V2.2.50')
+EC_OCP_SERVER_TIME_SHIFT = OBDErrorCodeTemplate(4353, '{server}: Excessive deviation between machine time and ob time')
+EC_OCP_SERVER_LAUNCH_USER_NOT_EXIST = OBDErrorCodeTemplate(4354, '{user}@{server}: Not exist')
+EC_SUDO_NOPASSWD = OBDErrorCodeTemplate(4355, '{user}@{ip}: user {user} not in sudoers or sudoers file not exist')
+EC_CONNECT_METADB = OBDErrorCodeTemplate(4356, 'failed to connect meta db')
+EC_DB_NOT_IN_JDBC_URL = OBDErrorCodeTemplate(4357, 'database in jdbc_url is not exist')
+EC_ERROR_JDBC_URL = OBDErrorCodeTemplate(4358, 'unmatched jdbc url, skip meta db connection check')
+EC_OCP_SERVER_JAVA_VERSION_ERROR = OBDErrorCodeTemplate(4359, "{server}: ocp-server need java with version {version} and update release must greater than 161")
+EC_OCP_SERVER_CLOCKDIFF_NOT_EXISTS = OBDErrorCodeTemplate(4360, "{server}: clockdiff not exists. Please install clockdiff manually")
+EC_OCP_SERVER_TENANT_ALREADY_EXISTS = OBDErrorCodeTemplate(4361, "tenant({tenant_name}) alread exist")
+EC_OCP_SERVER_DIR_ACCESS_FORBIDE = OBDErrorCodeTemplate(4362, "{server}:{path} access failed for current user, {server}:{cur_path} access succeed, please run `chmod -R 755 {cur_path}` ")
+
#ob-configserver
EC_OBC_PROGRAM_START_ERROR = OBDErrorCodeTemplate(4401, 'Failed to start {server} ob-configserver.')
@@ -188,7 +207,6 @@ class InitDirFailedErrorMessage(object):
EC_OBC_DATABASE_CONNECT_ERROR = OBDErrorCodeTemplate(4404, 'ob-configserver connect to mysql failed: {server}: failed url to connect to database: {url}')
-
# sql
EC_SQL_EXECUTE_FAILED = OBDErrorCodeTemplate(5000, "{sql} execute failed")
@@ -196,6 +214,7 @@ class InitDirFailedErrorMessage(object):
EC_OBDIAG_NOT_FOUND = OBDErrorCodeTemplate(6000, 'Failed to executable obdiag command, you may not have obdiag installed')
EC_OBDIAG_NOT_CONTAIN_DEPEND_COMPONENT = OBDErrorCodeTemplate(6001, 'obdiag must contain depend components {components}')
EC_OBDIAG_OPTIONS_FORMAT_ERROR = OBDErrorCodeTemplate(6002, 'obdiag options {option} format error, please check the value : {value}')
+EC_OBDIAG_FUCYION_FAILED = OBDErrorCodeTemplate(6003, 'Failed to excute obdiag function {fuction}')
# Unexpected exceptions code
EC_UNEXPECTED_EXCEPTION = OBDErrorCodeTemplate(9999, 'Unexpected exception: need to be posted on "https://ask.oceanbase.com", and we will help you resolve them.')
@@ -226,7 +245,7 @@ class InitDirFailedErrorMessage(object):
SUG_GRAFANA_PWD = OBDErrorSuggestionTemplate('Grafana password length must be greater than 4 and not "admin"', True, [FixEval(FixEval.DEL, 'login_password', is_global=True)])
SUG_PARAM_CHECK = OBDErrorSuggestionTemplate('Please check your config')
SUG_SSH_FAILED = OBDErrorSuggestionTemplate('Please check user config and network')
-SUG_SYSCTL = OBDErrorSuggestionTemplate('Please execute `echo ‘{var}={value}’ >> /etc/sysctl.conf; sysctl -p` as root in {ip}.')
+SUG_SYSCTL = OBDErrorSuggestionTemplate('Please execute `echo "{var}={value}" >> /etc/sysctl.conf; sysctl -p` as root in {ip}.')
SUG_ULIMIT = OBDErrorSuggestionTemplate('Please execute `echo -e "* soft {name} {value}\\n* hard {name} {value}" >> /etc/security/limits.d/{name}.conf` as root in {ip}. if it dosen\'t work, please check whether UsePAM is yes in /etc/ssh/sshd_config.')
SUG_CONNECT_EXCEPT = OBDErrorSuggestionTemplate('Connection exception or unsupported OS. Please retry or contact us.')
SUG_UNSUPPORT_OS = OBDErrorSuggestionTemplate('It may be an unsupported OS, please contact us for assistance')
@@ -245,4 +264,6 @@ class InitDirFailedErrorMessage(object):
SUG_OCP_EXPRESS_COMP_VERSION = OBDErrorSuggestionTemplate('Please use {comp} with version {version} or above')
SUG_OCP_EXPRESS_REDUCE_META_DB_MEM = OBDErrorSuggestionTemplate('Please reduce the `ocp_meta_tenant_memory_size`', fix_eval=[FixEval(FixEval.DEL, 'ocp_meta_tenant_memory_size')])
SUG_OCP_EXPRESS_REDUCE_META_DB_LOG_DISK = OBDErrorSuggestionTemplate('Please reduce the `ocp_meta_tenant_log_disk_size`', fix_eval=[FixEval(FixEval.DEL, 'ocp_meta_tenant_log_disk_size')])
-SUG_OCP_EXPRESS_EDIT_ADMIN_PASSWD_ERROR = OBDErrorSuggestionTemplate('Please edit the `admin_passwd`, must be 8 to 32 characters in length, and must contain at least two digits, two uppercase letters, two lowercase letters, and two of the following special characters:~!@#%^&*_-+=|(){{}}[]:;,.?/)', fix_eval=[FixEval(FixEval.DEL, 'admin_passwd')], auto_fix=True)
\ No newline at end of file
+SUG_OCP_EXPRESS_EDIT_ADMIN_PASSWD_ERROR = OBDErrorSuggestionTemplate('Please edit the `admin_passwd`, must be 8 to 32 characters in length, and must contain at least two digits, two uppercase letters, two lowercase letters, and two of the following special characters:~!@#%^&*_-+=|(){{}}[]:;,.?/)', fix_eval=[FixEval(FixEval.DEL, 'admin_passwd')], auto_fix=True)
+SUG_OCP_SERVER_JDBC_URL_CONFIG_ERROR = OBDErrorSuggestionTemplate('Please ensure that the `jdbc_url` in the `config.yaml` configuration file is set correctly to establish a successful connection with your database')
+SUG_SUDO_NOPASSWD = OBDErrorSuggestionTemplate('Please execute `bash -c \'echo "{user} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`\' as root in {ip}.')
diff --git a/_mirror.py b/_mirror.py
index 2ac336d..5d26a3b 100644
--- a/_mirror.py
+++ b/_mirror.py
@@ -41,7 +41,7 @@
from configparser import ConfigParser
from _arch import getArchList, getBaseArch
-from _rpm import Package, PackageInfo
+from _rpm import Version, Package, PackageInfo
from tool import ConfigUtil, FileUtil, var_replace
from _manager import Manager
from tool import timeout
@@ -50,14 +50,15 @@
_ARCH = getArchList()
_RELEASE = None
SUP_MAP = {
- 'ubuntu': (([16], 7), ),
- 'debian': (([9], 7), ),
- 'opensuse-leap': (([15], 7), ),
- 'sles': (([15, 2], 7), ),
- 'fedora': (([33], 7), ),
- 'uos': (([20], 8), ),
- 'anolis': (([23], 7), ),
- 'openEuler': (([22, 3], 7), ),
+ 'ubuntu': {'16': 7},
+ 'debian': {'9': 7},
+ 'opensuse-leap': {'15': 7},
+ 'sles': {'15.2': 7},
+ 'fedora': {'33': 7},
+ 'uos': {'20': 8},
+ 'anolis': {'23': 7},
+ 'openEuler': {'22.03': 7},
+ 'kylin': {'V10': 8},
}
_SERVER_VARS = {
'basearch': getBaseArch(),
@@ -938,12 +939,14 @@ def get_remote_mirrors(self, is_enabled=True):
server_vars = deepcopy(_SERVER_VARS)
linux_id = server_vars.get('ID')
if linux_id in SUP_MAP:
- version = [int(vnum) for vnum in server_vars.get('VERSION_ID', '').split('.')]
- for vid, elvid in SUP_MAP[linux_id]:
- if version < vid:
+ version_id = server_vars.get('VERSION_ID', '')
+ sorted_versions = sorted([Version(key) for key in SUP_MAP[linux_id]], reverse=True)
+ for version in sorted_versions:
+ if Version(version_id) >= version:
+ server_vars['releasever'] = SUP_MAP[linux_id][str(version)]
break
- server_vars['releasever'] = elvid
- server_vars['releasever'] = str(elvid)
+ else:
+ server_vars['releasever'] = SUP_MAP[linux_id][str(version)]
self.stdio and getattr(self.stdio, 'warn', print)('Use centos %s remote mirror repository for %s %s' % (
server_vars['releasever'], linux_id, server_vars.get('VERSION_ID')))
for mirror_section in self._get_sections():
diff --git a/_plugin.py b/_plugin.py
index c1861b3..d9d253c 100644
--- a/_plugin.py
+++ b/_plugin.py
@@ -649,6 +649,7 @@ def file_map(self, package_info):
'name': package_info.name,
'version': package_info.version,
'release': package_info.release,
+ 'release_simple': package_info.release.simple(),
'arch': package_info.arch,
'md5': package_info.md5,
}
diff --git a/_rpm.py b/_rpm.py
index 17cd2e1..edf9593 100644
--- a/_rpm.py
+++ b/_rpm.py
@@ -78,6 +78,9 @@ def __cmp_value__(self):
m = re.search('(\d+)', self.__str__())
return int(m.group(0)) if m else -1
+ def simple(self):
+ m = re.search('(\d+)', self.__str__())
+ return m.group(0) if m else ""
class PackageInfo(object):
diff --git a/_types.py b/_types.py
index 7656789..8f163c6 100644
--- a/_types.py
+++ b/_types.py
@@ -18,10 +18,13 @@
# along with OceanBase Deploy. If not, see .
from __future__ import absolute_import, division, print_function
+
import re
+
__all__ = ("Moment", "Time", "Capacity", "CapacityMB", "StringList", "Dict", "List", "StringOrKvList", "Double", "Boolean", "Integer", "String")
+
class Null(object):
def __init__(self):
@@ -143,8 +146,8 @@ def _format(self):
n = self._origin
unit = self.UNITS['M']
else:
- r = re.match('^(\d+)(\w)B?$', self._origin.upper())
- n, u = r.groups()
+ r = re.match('^(\d+)(\w)(I?B)?$', self._origin.upper())
+ n, u, _ = r.groups()
unit = self.UNITS.get(u.upper())
if unit:
self._value = int(n) * unit
diff --git a/core.py b/core.py
index 885abf6..10e3e4b 100644
--- a/core.py
+++ b/core.py
@@ -32,7 +32,7 @@
from ssh import SshClient, SshConfig
from tool import FileUtil, DirectoryUtil, YamlLoader, timeout, COMMAND_ENV, OrderedDict
-from _stdio import MsgLevel
+from _stdio import MsgLevel, FormtatText
from _rpm import Version
from _mirror import MirrorRepositoryManager, PackageInfo
from _plugin import PluginManager, PluginType, InstallPlugin, PluginContextNamespace
@@ -1081,6 +1081,77 @@ def genconfig(self, name):
self.deploy_manager.remove_deploy_config(name)
return False
+ def export_to_ocp(self, name):
+ # extract ocp info from options
+ ocp_address = getattr(self.options, 'address', '')
+ ocp_user = getattr(self.options, 'user', '')
+ ocp_password = getattr(self.options, 'password', '')
+ if ocp_address is None or ocp_address == '':
+ self._call_stdio('error', 'address is required, pass it using -a or --address')
+ return False
+ if ocp_user is None or ocp_user == '':
+ self._call_stdio('error', 'user is required, pass it using -u or --user')
+ return False
+ if ocp_password is None or ocp_password == '':
+ self._call_stdio('error', 'password is required, pass it using -p or --password')
+ return False
+ self._call_stdio('verbose', 'Get Deploy by name')
+ deploy = self.deploy_manager.get_deploy_config(name)
+ if not deploy:
+ self._call_stdio('error', 'No such deploy: %s.' % name)
+ return False
+
+ deploy_info = deploy.deploy_info
+ self._call_stdio('verbose', 'Deploy status judge')
+ if deploy_info.status != DeployStatus.STATUS_RUNNING:
+ self._call_stdio('error', 'Deploy "%s" not RUNNING' % (name))
+ return False
+
+ deploy_config = deploy.deploy_config
+ if "oceanbase-ce" not in deploy_config.components:
+ self._call_stdio("error", "no oceanbase-ce in deployment %s" % name)
+ cluster_config = deploy_config.components["oceanbase-ce"]
+ repositories = self.load_local_repositories(deploy_info)
+ self.set_repositories(repositories)
+ ssh_clients = self.get_clients(deploy_config, repositories)
+
+ self._call_stdio('verbose', 'get plugins by mocking an ocp repository.')
+ # search and get all related plugins using a mock ocp repository
+ mock_ocp_repository = Repository("ocp-server-ce", "/")
+ mock_ocp_repository.version = "4.2.1"
+ repositories = [mock_ocp_repository]
+ takeover_precheck_plugins = self.search_py_script_plugin(repositories, "takeover_precheck")
+ self._call_stdio('verbose', 'successfully get takeover precheck plugin.')
+ takeover_plugins = self.search_py_script_plugin(repositories, "takeover")
+ self._call_stdio('verbose', 'successfully get takeover plugin.')
+
+ # do take over cluster by call takeover precheck plugins
+ self._call_stdio('print', 'precheck for export obcluster to ocp.')
+ precheck_ret = self.call_plugin(takeover_precheck_plugins[mock_ocp_repository], mock_ocp_repository, cluster_config=cluster_config, clients=ssh_clients)
+ if not precheck_ret:
+ self._call_stdio("error", precheck_ret.get_return('exception'))
+ return False
+ else:
+ # set version and component option
+ ocp_version = precheck_ret.get_return("ocp_version")
+ self.options._update_loose({"version": ocp_version, "components": "oceanbase-ce"})
+ self._call_stdio('verbose', 'do takeover precheck by calling ocp finished')
+ # check obcluster can be takeover by ocp
+ check_ocp_result = self.check_for_ocp(name)
+ if not check_ocp_result:
+ self._call_stdio("error", "check obcluster to ocp takeover failed")
+ return False
+ self.set_deploy(None)
+ self.set_repositories(None)
+ takeover_ret = self.call_plugin(takeover_plugins[mock_ocp_repository], mock_ocp_repository, cluster_config=cluster_config, deploy_config = deploy_config, clients=ssh_clients)
+ if not takeover_ret:
+ self._call_stdio('error', 'export obcluster to ocp failed')
+ return False
+ else:
+ task_id = takeover_ret.get_return("task_id")
+ self._call_stdio("print", "takeover task successfully submitted to ocp, you can check task at %s/task/%d" % (ocp_address, task_id))
+ return True
+
def check_for_ocp(self, name):
self._call_stdio('verbose', 'Get Deploy by name')
deploy = self.deploy_manager.get_deploy_config(name)
@@ -1143,6 +1214,7 @@ def check_for_ocp(self, name):
component_num = len(repositories)
for repository in repositories:
if repository.name not in components:
+ component_num -= 1
continue
if repository not in ocp_check:
component_num -= 1
@@ -1163,7 +1235,13 @@ def check_for_ocp(self, name):
if self.call_plugin(ocp_check[repository], repository, cursor=cursor, ocp_version=version, new_cluster_config=new_cluster_config, new_clients=new_ssh_clients):
component_num -= 1
self._call_stdio('print', '%s Check passed.' % repository.name)
-
+ # search and install oceanbase-ce-utils, just log warning when failed since it can be installed after takeover
+ repositories_utils_map = self.get_repositories_utils(repositories)
+ if not repositories_utils_map:
+ self._call_stdio('warn', 'Failed to get utils package')
+ else:
+ if not self.install_utils_to_servers(repositories, repositories_utils_map):
+ self._call_stdio('warn', 'Failed to install utils to servers')
return component_num == 0
def sort_repository_by_depend(self, repositories, deploy_config):
@@ -1268,6 +1346,9 @@ def demo(self):
self._call_stdio('error', 'Deploy "%s" is %s. You could not deploy an %s cluster.' % (name, deploy_info.status.value, deploy_info.status.value))
return False
+ if 'ocp-server' in getattr(self.options, 'components', ''):
+ self._call_stdio('error', 'Not support ocp-server.')
+ return
components = set()
for component_name in getattr(self.options, 'components', '').split(','):
if component_name:
@@ -2391,7 +2472,7 @@ def restart_cluster(self, name):
sub_io = None
if getattr(self.stdio, 'sub_io'):
sub_io = self.stdio.sub_io(msg_lv=MsgLevel.ERROR)
- obd = self.fork(options=Values({'without_parameter': True}), stdio=sub_io)
+ obd = self.fork(options=Values({'without_parameter': True, 'skip_create_tenant': 'True'}), stdio=sub_io)
if not obd._start_cluster(deploy, repositories):
if self.stdio:
self._call_stdio('error', err.EC_SOME_SERVER_STOPED.format())
@@ -2421,6 +2502,11 @@ def restart_cluster(self, name):
start_all = cluster_servers == cluster_config.servers
update_deploy_status = update_deploy_status and start_all
+ ret = self.call_plugin(start_check_plugins[repository], repository, source_option='restart')
+ if not ret:
+ return False
+
+ setattr(self.options, "skip_create_tenant", True)
if self.call_plugin(
restart_plugins[repository],
repository,
@@ -2491,7 +2577,7 @@ def redeploy_cluster(self, name, search_repo=True, need_confirm=False):
self._call_stdio('error', 'No such deploy: %s.' % name)
return False
- if need_confirm and not self._call_stdio('confirm', 'Are you sure to destroy the "%s" cluster and rebuild it?' % name):
+ if need_confirm and not self._call_stdio('confirm', FormtatText.warning('Are you sure to destroy the "%s" cluster and rebuild it?' % name)):
return False
deploy_info = deploy.deploy_info
@@ -2551,6 +2637,7 @@ def redeploy_cluster(self, name, search_repo=True, need_confirm=False):
if not repositories or not install_plugins:
return False
self.set_repositories(repositories)
+ setattr(self.options, "skip_create_tenant", True)
return self._deploy_cluster(deploy, repositories) and self._start_cluster(deploy, repositories)
def destroy_cluster(self, name):
@@ -2902,6 +2989,11 @@ def upgrade_cluster(self, name):
self._call_stdio('print', '%s %s is stopped' % (server, repository.name))
return False
+ start_check_plugins = self.search_py_script_plugin(repositories, 'start_check')
+ ret = self.call_plugin(start_check_plugins[dest_repository], dest_repository, source_option='upgrade')
+ if not ret:
+ return False
+
route = []
use_images = []
upgrade_route_plugins = self.search_py_script_plugin([dest_repository], 'upgrade_route', no_found_act='warn')
@@ -4244,7 +4336,7 @@ def telemetry_post(self, name):
return self.call_plugin(telemetry_post_plugin, repository, spacename='telemetry')
- def obdiag_gather(self, name, gather_type, opts):
+ def obdiag_online_func(self, name, fuction_type, opts):
self._global_ex_lock()
self._call_stdio('verbose', 'Get Deploy by name')
deploy = self.deploy_manager.get_deploy_config(name, read_only=True)
@@ -4261,7 +4353,7 @@ def obdiag_gather(self, name, gather_type, opts):
return False
allow_components = []
- if gather_type.startswith("gather_obproxy"):
+ if fuction_type.startswith("gather_obproxy") or fuction_type.startswith("analyze_obproxy"):
allow_components = ['obproxy-ce', 'obproxy']
else:
allow_components = ['oceanbase-ce', 'oceanbase']
@@ -4289,48 +4381,44 @@ def obdiag_gather(self, name, gather_type, opts):
if repository.name == component_name:
target_repository = repository
break
- if gather_type in ['gather_plan_monitor']:
+ if fuction_type in ['gather_plan_monitor']:
setattr(opts, 'connect_cluster', True)
- obdiag_path = getattr(opts, 'obdiag_dir', None)
diagnostic_component_name = 'oceanbase-diagnostic-tool'
- obdiag_version = '1.0'
- pre_check_plugin = self.plugin_manager.get_best_py_script_plugin('pre_check', diagnostic_component_name, obdiag_version)
- check_pass = self.call_plugin(pre_check_plugin,
- target_repository,
- gather_type = gather_type,
- obdiag_path = obdiag_path,
- version_check = True,
- utils_work_dir_check = True)
- if not check_pass:
- # obdiag checker return False
- if not check_pass.get_return('obdiag_found'):
- if not self._call_stdio('confirm', 'Could not find the obdiag, please confirm whether to install it' ):
- return False
- self.obdiag_deploy(auto_deploy=True, install_prefix=obdiag_path)
- # utils checker return False
- if not check_pass.get_return('utils_status'):
- repositories_utils_map = self.get_repositories_utils(repositories)
- if repositories_utils_map is False:
- self._call_stdio('error', 'Failed to get utils package')
- else:
- if not self._call_stdio('confirm', 'obdiag gather clog/slog need to install ob_admin\nDo you want to install ob_admin?'):
- if not check_pass.get_return('skip'):
- return False
- else:
- self._call_stdio('warn', 'Just skip gather clog/slog')
- else:
- if not self.install_utils_to_servers(repositories, repositories_utils_map):
- self._call_stdio('error', 'Failed to install utils to servers')
- obdiag_version = check_pass.get_return('obdiag_version')
- generate_config_plugin = self.plugin_manager.get_best_py_script_plugin('generate_config', diagnostic_component_name, obdiag_version)
- self.call_plugin(generate_config_plugin, target_repository, deploy_config=deploy_config)
- self._call_stdio('generate_config', 'succeed')
- obdiag_plugin = self.plugin_manager.get_best_py_script_plugin(gather_type, diagnostic_component_name, obdiag_version)
- return self.call_plugin(obdiag_plugin, target_repository)
+ diagnostic_component_version = '1.0'
+ if fuction_type in ['analyze_log']:
+ diagnostic_component_version = '1.3'
+ elif fuction_type in ['checker']:
+ diagnostic_component_version = '1.4'
+ deployed = self.obdiag_deploy(auto_deploy=True, version=diagnostic_component_version)
+ if deployed:
+ generate_config_plugin = self.plugin_manager.get_best_py_script_plugin('generate_config', diagnostic_component_name, diagnostic_component_version)
+ self.call_plugin(generate_config_plugin, target_repository, deploy_config=deploy_config)
+ self._call_stdio('generate_config', 'succeed')
+ obdiag_plugin = self.plugin_manager.get_best_py_script_plugin(fuction_type, diagnostic_component_name, diagnostic_component_version)
+ return self.call_plugin(obdiag_plugin, target_repository)
+ else:
+ self._call_stdio('error', err.EC_OBDIAG_FUCYION_FAILED.format(fuction=fuction_type))
+ return False
- def obdiag_deploy(self, auto_deploy=False, install_prefix=None):
+ def obdiag_offline_func(self, fuction_type, opts):
+ component_name = 'oceanbase-diagnostic-tool'
+ pkg = self.mirror_manager.get_best_pkg(name=component_name)
+ if not pkg:
+ self._call_stdio('critical', '%s package not found' % component_name)
+ return False
+ repository = self.repository_manager.create_instance_repository(pkg.name, pkg.version, pkg.md5)
+ deployed = self.obdiag_deploy(auto_deploy=True, version='1.3')
+ if deployed:
+ obdiag_plugin = self.plugin_manager.get_best_py_script_plugin(fuction_type, component_name, )
+ return self.call_plugin(obdiag_plugin, repository, clients={})
+ else:
+ self._call_stdio('error', err.EC_OBDIAG_FUCYION_FAILED.format(fuction=fuction_type))
+ return False
+
+
+ def obdiag_deploy(self, auto_deploy=False, install_prefix=None, version='1.3'):
self._global_ex_lock()
component_name = 'oceanbase-diagnostic-tool'
if install_prefix is None:
@@ -4341,29 +4429,29 @@ def obdiag_deploy(self, auto_deploy=False, install_prefix=None):
return False
plugin = self.plugin_manager.get_best_plugin(PluginType.INSTALL, component_name, pkg.version)
self._call_stdio('print', 'obdiag plugin : %s' % plugin)
-
repository = self.repository_manager.create_instance_repository(pkg.name, pkg.version, pkg.md5)
- check_plugin = self.plugin_manager.get_best_py_script_plugin('pre_check', component_name, pkg.version)
- if not auto_deploy:
- ret = self.call_plugin(check_plugin,
- repository,
- clients={},
- obdiag_path = install_prefix,
- obdiag_new_version = pkg.version,
- version_check = True)
- if not ret and ret.get_return('obdiag_found'):
+ check_plugin = self.plugin_manager.get_best_py_script_plugin('pre_check', component_name, version)
+ obd = self.fork()
+ obd.set_deploy(deploy=None)
+ ret = obd.call_plugin(check_plugin, repository, clients={}, obdiag_path = install_prefix, obdiag_new_version = version, version_check = True)
+ if ret.get_return('obdiag_found'):
+ if ret.get_return('version_status'):
self._call_stdio('print', 'No updates detected. obdiag is already up to date.')
- return False
- if not self._call_stdio('confirm', 'Found a higher version\n%s\nDo you want to use it?' % pkg):
- return False
+ return True
+ else:
+ if not auto_deploy:
+ if not self._call_stdio('confirm', 'Found a higher version\n%s\nDo you want to use it?' % pkg):
+ return False
self._call_stdio('start_loading', 'Get local repositories and plugins')
+ repository = self.repository_manager.create_instance_repository(pkg.name, pkg.version, pkg.md5)
+ plugin = self.plugin_manager.get_best_plugin(PluginType.INSTALL, component_name, pkg.version)
repository.load_pkg(pkg, plugin)
src_path = os.path.join(repository.repository_dir, component_name)
if FileUtil.symlink(src_path, install_prefix, self.stdio):
self._call_stdio('stop_loading', 'succeed')
self._call_stdio('print', 'Deploy obdiag successful.\nCurrent version : %s. \nPath of obdiag : %s' % (pkg.version, install_prefix))
- return True
-
+ return True
+ return False
def get_repositories_utils(self, repositories):
all_data = []
diff --git a/example/all-components-min.yaml b/example/all-components-min.yaml
index d9f5587..5ece143 100644
--- a/example/all-components-min.yaml
+++ b/example/all-components-min.yaml
@@ -17,10 +17,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
memory_limit: 6G # The maximum running memory for an observer
diff --git a/example/all-components.yaml b/example/all-components.yaml
index fb07a47..699cc92 100644
--- a/example/all-components.yaml
+++ b/example/all-components.yaml
@@ -17,10 +17,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G.
diff --git a/example/autodeploy/all-components.yaml b/example/autodeploy/all-components.yaml
index 3aba9aa..540f2e4 100644
--- a/example/autodeploy/all-components.yaml
+++ b/example/autodeploy/all-components.yaml
@@ -21,9 +21,10 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
# devname: eth0
# External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
# mysql_port: 2881
diff --git a/example/autodeploy/default-example.yaml b/example/autodeploy/default-example.yaml
index e080991..b332638 100644
--- a/example/autodeploy/default-example.yaml
+++ b/example/autodeploy/default-example.yaml
@@ -21,9 +21,10 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
# devname: eth0
# External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
# mysql_port: 2881
diff --git a/example/autodeploy/distributed-example.yaml b/example/autodeploy/distributed-example.yaml
index 3999dde..84905f3 100644
--- a/example/autodeploy/distributed-example.yaml
+++ b/example/autodeploy/distributed-example.yaml
@@ -21,9 +21,10 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
# devname: eth0
# External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
# mysql_port: 2881
diff --git a/example/autodeploy/distributed-with-obproxy-and-obagent-example.yaml b/example/autodeploy/distributed-with-obproxy-and-obagent-example.yaml
index f9691b4..a0604f2 100644
--- a/example/autodeploy/distributed-with-obproxy-and-obagent-example.yaml
+++ b/example/autodeploy/distributed-with-obproxy-and-obagent-example.yaml
@@ -21,9 +21,10 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
# devname: eth0
# External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
# mysql_port: 2881
diff --git a/example/autodeploy/distributed-with-obproxy-example.yaml b/example/autodeploy/distributed-with-obproxy-example.yaml
index 203e9d8..811f3fc 100644
--- a/example/autodeploy/distributed-with-obproxy-example.yaml
+++ b/example/autodeploy/distributed-with-obproxy-example.yaml
@@ -21,9 +21,10 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
# devname: eth0
# External port for OceanBase Database. The default value is 2881.DO NOT change this value after the cluster is started.
# mysql_port: 2881
diff --git a/example/autodeploy/single-example.yaml b/example/autodeploy/single-example.yaml
index 7a94311..20ae863 100644
--- a/example/autodeploy/single-example.yaml
+++ b/example/autodeploy/single-example.yaml
@@ -16,9 +16,10 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
# devname: eth0
# External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
# mysql_port: 2881
diff --git a/example/autodeploy/single-with-obproxy-example.yaml b/example/autodeploy/single-with-obproxy-example.yaml
index 185688d..bdc4b6d 100644
--- a/example/autodeploy/single-with-obproxy-example.yaml
+++ b/example/autodeploy/single-with-obproxy-example.yaml
@@ -16,9 +16,10 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
# devname: eth0
# External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
# mysql_port: 2881
diff --git a/example/default-components-min.yaml b/example/default-components-min.yaml
index 39a7326..98bf9c6 100644
--- a/example/default-components-min.yaml
+++ b/example/default-components-min.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
memory_limit: 6G # The maximum running memory for an observer
diff --git a/example/default-components.yaml b/example/default-components.yaml
index f0db65b..65edfd2 100644
--- a/example/default-components.yaml
+++ b/example/default-components.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G.
diff --git a/example/distributed-example.yaml b/example/distributed-example.yaml
index be05ac5..06aefaa 100644
--- a/example/distributed-example.yaml
+++ b/example/distributed-example.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G.
diff --git a/example/distributed-with-obproxy-example.yaml b/example/distributed-with-obproxy-example.yaml
index 447237a..bd34782 100644
--- a/example/distributed-with-obproxy-example.yaml
+++ b/example/distributed-with-obproxy-example.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 192.168.1.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G.
diff --git a/example/grafana/all-components-with-prometheus-and-grafana.yaml b/example/grafana/all-components-with-prometheus-and-grafana.yaml
index 97f852e..2b3676c 100644
--- a/example/grafana/all-components-with-prometheus-and-grafana.yaml
+++ b/example/grafana/all-components-with-prometheus-and-grafana.yaml
@@ -21,10 +21,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
memory_limit: 6G # The maximum running memory for an observer
diff --git a/example/local-example.yaml b/example/local-example.yaml
index dd15e63..43d501c 100644
--- a/example/local-example.yaml
+++ b/example/local-example.yaml
@@ -9,10 +9,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: lo
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
zone: zone1
diff --git a/example/mini-distributed-example.yaml b/example/mini-distributed-example.yaml
index fb5dd47..50b34fe 100644
--- a/example/mini-distributed-example.yaml
+++ b/example/mini-distributed-example.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
memory_limit: 6G # The maximum running memory for an observer
diff --git a/example/mini-distributed-with-obproxy-example.yaml b/example/mini-distributed-with-obproxy-example.yaml
index 60a0ce1..f9184bf 100644
--- a/example/mini-distributed-with-obproxy-example.yaml
+++ b/example/mini-distributed-with-obproxy-example.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 192.168.1.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
memory_limit: 6G # The maximum running memory for an observer
diff --git a/example/mini-local-example.yaml b/example/mini-local-example.yaml
index 0e9eac6..41f4380 100755
--- a/example/mini-local-example.yaml
+++ b/example/mini-local-example.yaml
@@ -9,10 +9,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: lo
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
zone: zone1
diff --git a/example/mini-single-example.yaml b/example/mini-single-example.yaml
index a78b139..a97fe3f 100755
--- a/example/mini-single-example.yaml
+++ b/example/mini-single-example.yaml
@@ -16,10 +16,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
zone: zone1
diff --git a/example/mini-single-with-obproxy-example.yaml b/example/mini-single-with-obproxy-example.yaml
index 99adb29..16d2df4 100644
--- a/example/mini-single-with-obproxy-example.yaml
+++ b/example/mini-single-with-obproxy-example.yaml
@@ -16,10 +16,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
zone: zone1
diff --git a/example/ob-configserver/distributed-with-obproxy-and-configserver-example.yaml b/example/ob-configserver/distributed-with-obproxy-and-configserver-example.yaml
index 19188b0..0414d70 100644
--- a/example/ob-configserver/distributed-with-obproxy-and-configserver-example.yaml
+++ b/example/ob-configserver/distributed-with-obproxy-and-configserver-example.yaml
@@ -24,10 +24,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
memory_limit: 6G # The maximum running memory for an observer
diff --git a/example/obagent/distributed-with-obproxy-and-obagent-example.yaml b/example/obagent/distributed-with-obproxy-and-obagent-example.yaml
index 55d1b30..a4b1b36 100644
--- a/example/obagent/distributed-with-obproxy-and-obagent-example.yaml
+++ b/example/obagent/distributed-with-obproxy-and-obagent-example.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G.
diff --git a/example/obproxy/distributed-with-obproxy-example.yaml b/example/obproxy/distributed-with-obproxy-example.yaml
index 447237a..bd34782 100644
--- a/example/obproxy/distributed-with-obproxy-example.yaml
+++ b/example/obproxy/distributed-with-obproxy-example.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 192.168.1.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G.
diff --git a/example/oceanbase-3.x/distributed-example.yaml b/example/oceanbase-3.x/distributed-example.yaml
index fb109d7..eb07fba 100644
--- a/example/oceanbase-3.x/distributed-example.yaml
+++ b/example/oceanbase-3.x/distributed-example.yaml
@@ -16,9 +16,6 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
diff --git a/example/oceanbase-3.x/distributed-with-obproxy-and-obagent-example.yaml b/example/oceanbase-3.x/distributed-with-obproxy-and-obagent-example.yaml
index c8caf56..706bd18 100644
--- a/example/oceanbase-3.x/distributed-with-obproxy-and-obagent-example.yaml
+++ b/example/oceanbase-3.x/distributed-with-obproxy-and-obagent-example.yaml
@@ -16,9 +16,6 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
diff --git a/example/oceanbase-3.x/distributed-with-obproxy-example.yaml b/example/oceanbase-3.x/distributed-with-obproxy-example.yaml
index 07d59ef..ef783d1 100644
--- a/example/oceanbase-3.x/distributed-with-obproxy-example.yaml
+++ b/example/oceanbase-3.x/distributed-with-obproxy-example.yaml
@@ -16,9 +16,6 @@ oceanbase-ce:
- name: server3
ip: 192.168.1.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
diff --git a/example/oceanbase-3.x/local-example.yaml b/example/oceanbase-3.x/local-example.yaml
index b180d93..720aa37 100644
--- a/example/oceanbase-3.x/local-example.yaml
+++ b/example/oceanbase-3.x/local-example.yaml
@@ -10,9 +10,6 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: lo
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
diff --git a/example/oceanbase-3.x/mini-distributed-example.yaml b/example/oceanbase-3.x/mini-distributed-example.yaml
index 380c8d2..d7a7704 100644
--- a/example/oceanbase-3.x/mini-distributed-example.yaml
+++ b/example/oceanbase-3.x/mini-distributed-example.yaml
@@ -16,9 +16,6 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
diff --git a/example/oceanbase-3.x/mini-distributed-with-obproxy-example.yaml b/example/oceanbase-3.x/mini-distributed-with-obproxy-example.yaml
index dfa8876..d2056e3 100644
--- a/example/oceanbase-3.x/mini-distributed-with-obproxy-example.yaml
+++ b/example/oceanbase-3.x/mini-distributed-with-obproxy-example.yaml
@@ -16,9 +16,6 @@ oceanbase-ce:
- name: server3
ip: 192.168.1.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
cluster_id: 1
# please set memory limit to a suitable value which is matching resource.
diff --git a/example/oceanbase-3.x/mini-local-example.yaml b/example/oceanbase-3.x/mini-local-example.yaml
index c23a54e..037fa37 100755
--- a/example/oceanbase-3.x/mini-local-example.yaml
+++ b/example/oceanbase-3.x/mini-local-example.yaml
@@ -10,9 +10,6 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: lo
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
diff --git a/example/oceanbase-3.x/mini-single-example.yaml b/example/oceanbase-3.x/mini-single-example.yaml
index 2d6790c..09f2959 100755
--- a/example/oceanbase-3.x/mini-single-example.yaml
+++ b/example/oceanbase-3.x/mini-single-example.yaml
@@ -17,9 +17,6 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
diff --git a/example/oceanbase-3.x/mini-single-with-obproxy-example.yaml b/example/oceanbase-3.x/mini-single-with-obproxy-example.yaml
index 0e82ea8..f5b5dad 100644
--- a/example/oceanbase-3.x/mini-single-with-obproxy-example.yaml
+++ b/example/oceanbase-3.x/mini-single-with-obproxy-example.yaml
@@ -17,9 +17,6 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
diff --git a/example/oceanbase-3.x/single-example.yaml b/example/oceanbase-3.x/single-example.yaml
index e91cd58..eaf1ee5 100644
--- a/example/oceanbase-3.x/single-example.yaml
+++ b/example/oceanbase-3.x/single-example.yaml
@@ -17,9 +17,6 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
diff --git a/example/oceanbase-3.x/single-with-obproxy-example.yaml b/example/oceanbase-3.x/single-with-obproxy-example.yaml
index 579c738..a7f481a 100644
--- a/example/oceanbase-3.x/single-with-obproxy-example.yaml
+++ b/example/oceanbase-3.x/single-with-obproxy-example.yaml
@@ -17,9 +17,6 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
diff --git a/example/prometheus/distributed-with-obagent-and-prometheus-example.yaml b/example/prometheus/distributed-with-obagent-and-prometheus-example.yaml
index 1eff5af..58756f6 100644
--- a/example/prometheus/distributed-with-obagent-and-prometheus-example.yaml
+++ b/example/prometheus/distributed-with-obagent-and-prometheus-example.yaml
@@ -15,10 +15,11 @@ oceanbase-ce:
- name: server3
ip: 172.19.33.4
global:
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
# if current hardware's memory capacity is smaller than 50G, please use the setting of "mini-single-example.yaml" and do a small adjustment.
memory_limit: 64G # The maximum running memory for an observer
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G.
diff --git a/example/single-example.yaml b/example/single-example.yaml
index 4c5cecf..527f5ac 100644
--- a/example/single-example.yaml
+++ b/example/single-example.yaml
@@ -16,10 +16,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
zone: zone1
diff --git a/example/single-with-obproxy-example.yaml b/example/single-with-obproxy-example.yaml
index b39a4ac..76fadf8 100644
--- a/example/single-with-obproxy-example.yaml
+++ b/example/single-with-obproxy-example.yaml
@@ -16,10 +16,11 @@ oceanbase-ce:
# data_dir: /data
# The directory for clog, ilog, and slog. The default value is the same as the data_dir value.
# redo_dir: /redo
- # Please set devname as the network adaptor's name whose ip is in the setting of severs.
- # if set severs as "127.0.0.1", please set devname as "lo"
- # if current ip is 192.168.1.10, and the ip's network adaptor's name is "eth0", please use "eth0"
- devname: eth0
+ # Starting from observer version 4.2, the network selection for the observer is based on the 'local_ip' parameter, and the 'devname' parameter is no longer mandatory.
+ # If the 'local_ip' parameter is set, the observer will first use this parameter for the configuration, regardless of the 'devname' parameter.
+ # If only the 'devname' parameter is set, the observer will use the 'devname' parameter for the configuration.
+ # If neither the 'devname' nor the 'local_ip' parameters are set, the 'local_ip' parameter will be automatically assigned the IP address configured above.
+ # devname: eth0
mysql_port: 2881 # External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
rpc_port: 2882 # Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
zone: zone1
diff --git a/init.sh b/init.sh
index 487ef20..27d303c 100644
--- a/init.sh
+++ b/init.sh
@@ -65,6 +65,8 @@ for DIR in plugins config_parser; do
fi
done
+echo '' >> ${OBD_HOME}/version
+
echo "============update .bashrc============"
ALIAS_OBD_EXIST=$(grep "alias obd=" ~/.bashrc | head -n 1)
diff --git a/plugins/ob-configserver/1.0.0/display.py b/plugins/ob-configserver/1.0.0/display.py
index bc0b7b7..191a209 100644
--- a/plugins/ob-configserver/1.0.0/display.py
+++ b/plugins/ob-configserver/1.0.0/display.py
@@ -54,7 +54,7 @@ def display(plugin_context, cursor, *args, **kwargs):
lambda x: [x['server'], x['port'], x['vip_address'], x['vip_port'], x['status'], x['pid']],
title='ob-configserver')
if result:
- cmd = "curl -s 'http://{0}:{1}/services?Action=GetObProxyConfig' |jq .".format(result[0]['server'], result[0]['port'])
+ cmd = "curl -s 'http://{0}:{1}/services?Action=GetObProxyConfig'".format(result[0]['server'], result[0]['port'])
stdio.print(cmd)
plugin_context.return_true()
diff --git a/plugins/obproxy/3.1.0/ocp_check.py b/plugins/obproxy/3.1.0/ocp_check.py
index 055be99..144ca67 100644
--- a/plugins/obproxy/3.1.0/ocp_check.py
+++ b/plugins/obproxy/3.1.0/ocp_check.py
@@ -33,19 +33,16 @@ def ocp_check(plugin_context, ocp_version, cursor, new_cluster_config=None, new_
only_one = True
min_version = Version('3.1.1')
- max_version = min_version
+ ocp_version_420 = Version("4.2.0")
ocp_version = Version(ocp_version)
if ocp_version < min_version:
stdio.error('The current plugin version does not support OCP V%s' % ocp_version)
return
- if ocp_version > max_version:
- stdio.warn('The plugin library does not support OCP V%s. The takeover requirements are not applicable to the current check.' % ocp_version)
-
for server in cluster_config.servers:
client = clients[server]
- if is_admin and client.config.username != 'admin':
+ if is_admin and client.config.username != 'admin' and ocp_version < ocp_version_420:
is_admin = False
stdio.error('The current user must be the admin user. Run the edit-config command to modify the user.username field')
if can_sudo and not client.execute_command('sudo whoami'):
@@ -55,6 +52,6 @@ def ocp_check(plugin_context, ocp_version, cursor, new_cluster_config=None, new_
only_one = False
stdio.error('%s Multiple OBProxies exist.' % server)
- if is_admin and can_sudo and only_one:
+ if (is_admin or ocp_version >= ocp_version_420) and can_sudo and only_one:
stdio.print('Configurations of the OBProxy can be taken over by OCP after they take effect.' if new_cluster_config else 'Configurations of the OBProxy can be taken over by OCP.')
- return plugin_context.return_true()
\ No newline at end of file
+ return plugin_context.return_true()
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/analyze_log.py b/plugins/oceanbase-diagnostic-tool/1.0/analyze_log.py
new file mode 100644
index 0000000..2e1abfe
--- /dev/null
+++ b/plugins/oceanbase-diagnostic-tool/1.0/analyze_log.py
@@ -0,0 +1,105 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+from ssh import LocalClient
+import datetime
+import os
+from tool import TimeUtils
+from subprocess import call, Popen, PIPE
+import _errno as err
+
+
+def analyze_log(plugin_context, *args, **kwargs):
+ def get_option(key, default=''):
+ value = getattr(options, key)
+ if value is None:
+ value = default
+ stdio.verbose('get option: %s value %s' % (key, value))
+ return value
+
+ def local_execute_command(command, env=None, timeout=None):
+ command = r"cd {install_dir} && sh ".format(install_dir=obdiag_install_dir) + command
+ return LocalClient.execute_command(command, env, timeout, stdio)
+
+ def get_obdiag_cmd():
+ base_commond=r"cd {install_dir} && ./obdiag analyze log".format(install_dir=obdiag_install_dir)
+ if files_option_path:
+ cmd = r"{base} --files {files_option_path}".format(
+ base = base_commond,
+ files_option_path = files_option_path,
+ )
+ else:
+ cmd = r"{base} --from {from_option} --to {to_option} --scope {scope_option}".format(
+ base = base_commond,
+ from_option = from_option,
+ to_option = to_option,
+ scope_option = scope_option,
+ )
+ if ob_install_dir_option:
+ cmd = cmd + r" --ob_install_dir {ob_install_dir_option}".format(ob_install_dir_option=ob_install_dir_option)
+ if store_dir_option:
+ cmd = cmd + r" --store_dir {store_dir_option}".format(store_dir_option=store_dir_option)
+ if grep_option:
+ cmd = cmd + r" --grep '{grep_option}'".format(grep_option=grep_option)
+ if log_level_option:
+ cmd = cmd + r" --log_level '{log_level_option}'".format(log_level_option=log_level_option)
+ return cmd
+
+ def run():
+ obdiag_cmd = get_obdiag_cmd()
+ stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
+
+ options = plugin_context.options
+ obdiag_bin = "obdiag"
+ ob_install_dir_option = None
+ files_option_path = None
+ cluster_config = plugin_context.cluster_config
+ stdio = plugin_context.stdio
+ from_option = get_option('from')
+ to_option = get_option('to')
+ scope_option = get_option('scope')
+ since_option = get_option('since')
+ grep_option = get_option('grep')
+ log_level_option = get_option('log_level')
+ files_option = get_option('files')
+ if files_option:
+ files_option_path = os.path.abspath(get_option('files'))
+ store_dir_option = os.path.abspath(get_option('store_dir'))
+ obdiag_install_dir = get_option('obdiag_dir')
+ if not files_option:
+ global_conf = cluster_config.get_global_conf()
+ ob_install_dir_option = global_conf.get('home_path')
+
+ from_option, to_option, ok = TimeUtils().parse_time_from_to(from_time=from_option, to_time=to_option, stdio=stdio)
+ if not ok:
+ from_option, to_option = TimeUtils().parse_time_since(since=since_option)
+
+ ret = local_execute_command('%s --help' % obdiag_bin)
+ if not ret:
+ stdio.error(err.EC_OBDIAG_NOT_FOUND.format())
+ return plugin_context.return_false()
+ try:
+ if run():
+ plugin_context.return_true()
+ except KeyboardInterrupt:
+ stdio.exception("obdiag analyze log failded")
+ return plugin_context.return_false()
\ No newline at end of file
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/checker.py b/plugins/oceanbase-diagnostic-tool/1.0/checker.py
new file mode 100644
index 0000000..26dc068
--- /dev/null
+++ b/plugins/oceanbase-diagnostic-tool/1.0/checker.py
@@ -0,0 +1,96 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+from ssh import LocalClient
+import _errno as err
+import sys
+from tool import FileUtil, YamlLoader
+
+if sys.version_info.major == 2:
+ import MySQLdb as mysql
+else:
+ import pymysql as mysql
+
+def checker(plugin_context, *args, **kwargs):
+ stdio = plugin_context.stdio
+ options = plugin_context.options
+ def get_option(key, default=''):
+ value = getattr(options, key)
+ if value is None:
+ value = default
+ stdio.verbose('get option: %s value %s' % (key, value))
+ return value
+
+ def local_execute_command(command, env=None, timeout=None):
+ command = r"cd {install_dir} && sh ".format(install_dir=obdiag_install_dir) + command
+ return LocalClient.execute_command(command, env, timeout, stdio)
+
+ def get_obdiag_cmd():
+ base_commond = r"{install_dir}/obdiag check".format(install_dir=obdiag_install_dir)
+ # check options
+ if cases:
+ cmd = r"{base} --cases {cases}".format(
+ base=base_commond,
+ cases=cases,
+ )
+ else:
+ cmd = r"{base}".format(base=base_commond)
+ return cmd
+
+ def run():
+ obdiag_cmd = get_obdiag_cmd()
+ stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
+
+ obdiag_bin = "obdiag"
+ cases = get_option('cases')
+ obdiag_install_dir = get_option('obdiag_dir')
+ # get obdiag_conf
+ obdiag_conf_yaml_path = obdiag_install_dir + "/conf/config.yml"
+ with FileUtil.open(obdiag_conf_yaml_path) as f:
+ obdiag_info = YamlLoader(stdio=stdio).load(f)
+ obdiag_obcluster_info = obdiag_info['OBCLUSTER']
+ db_host = obdiag_obcluster_info["host"]
+ db_port = obdiag_obcluster_info["port"]
+ db_user = obdiag_obcluster_info["user"]
+ db_password = obdiag_obcluster_info["password"]
+
+ ret = local_execute_command('%s --help' % obdiag_bin)
+ if not ret:
+ stdio.error(err.EC_OBDIAG_NOT_FOUND.format())
+ return plugin_context.return_false()
+
+ stdio.start_loading('Check database connectivity')
+ try:
+ mysql.connect(host=db_host, user=db_user, port=int(db_port), password=str(db_password), cursorclass=mysql.cursors.DictCursor)
+ except Exception:
+ stdio.stop_loading('fail')
+ stdio.error('cluster :{0} . Invalid cluster information. Please check the conf in OBD'.format(obdiag_obcluster_info))
+ return plugin_context.return_false()
+ stdio.stop_loading('succeed')
+
+ try:
+ if run():
+ plugin_context.return_true()
+ except KeyboardInterrupt:
+ stdio.exception("obdiag check failded")
+ return plugin_context.return_false()
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_all.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_all.py
index 11ee81d..a0a8c44 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_all.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_all.py
@@ -44,7 +44,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond=r"cd {install_dir} && sh obdiag gather all".format(install_dir=obdiag_install_dir)
+ base_commond=r"cd {install_dir} && ./obdiag gather all".format(install_dir=obdiag_install_dir)
cmd = r"{base} --cluster_name {cluster_name} --from {from_option} --to {to_option} --scope {scope_option} --encrypt {encrypt_option}".format(
base=base_commond,
cluster_name=cluster_name,
@@ -68,17 +68,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
@@ -112,14 +102,9 @@ def run():
clog_dir = server_config['clog_dir']
slog_dir = server_config['slog_dir']
- try:
- if (not from_option) and (not to_option) and since_option:
- now_time = datetime.datetime.now()
- to_option = (now_time + datetime.timedelta(minutes=10)).strftime('%Y-%m-%d %H:%M:%S')
- from_option = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S')
- except:
- stdio.error(err.EC_OBDIAG_OPTIONS_FORMAT_ERROR.format(option="since", value=since_option))
- return plugin_context.return_false()
+ from_option, to_option, ok = TimeUtils.parse_time_from_to(from_time=from_option, to_time=to_option, stdio=stdio)
+ if not ok:
+ from_option, to_option = TimeUtils.parse_time_since(since=since_option)
ret = local_execute_command('%s --help' % obdiag_bin)
if not ret:
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_clog.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_clog.py
index 70ff35d..eaf4987 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_clog.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_clog.py
@@ -40,7 +40,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond = r"cd {install_dir} && sh obdiag gather clog".format(install_dir=obdiag_install_dir)
+ base_commond = r"cd {install_dir} && ./obdiag gather clog".format(install_dir=obdiag_install_dir)
cmd = r"{base} --clog_dir {data_dir} --from {from_option} --to {to_option} --encrypt {encrypt_option}".format(
base = base_commond,
data_dir = data_dir,
@@ -57,17 +57,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
@@ -93,14 +83,9 @@ def run():
server_config['clog_dir'] = '%s/clog' % server_config['redo_dir']
data_dir = server_config['clog_dir']
- try:
- if (not from_option) and (not to_option) and since_option:
- now_time = datetime.datetime.now()
- to_option = (now_time + datetime.timedelta(minutes=10)).strftime('%Y-%m-%d %H:%M:%S')
- from_option = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S')
- except:
- stdio.error(err.EC_OBDIAG_OPTIONS_FORMAT_ERROR.format(option="since", value=since_option))
- return plugin_context.return_false()
+ from_option, to_option, ok = TimeUtils.parse_time_from_to(from_time=from_option, to_time=to_option, stdio=stdio)
+ if not ok:
+ from_option, to_option = TimeUtils.parse_time_since(since=since_option)
ret = local_execute_command('%s --help' % obdiag_bin)
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_log.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_log.py
index c4a8bce..2adb1c1 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_log.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_log.py
@@ -40,7 +40,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond=r"cd {install_dir} && sh obdiag gather log".format(install_dir=obdiag_install_dir)
+ base_commond=r"cd {install_dir} && ./obdiag gather log".format(install_dir=obdiag_install_dir)
cmd = r"{base} --from {from_option} --to {to_option} --scope {scope_option} --encrypt {encrypt_option}".format(
base = base_commond,
from_option = from_option,
@@ -59,17 +59,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
@@ -85,14 +75,9 @@ def run():
store_dir_option = os.path.abspath(get_option('store_dir'))
ob_install_dir_option = global_conf.get('home_path')
obdiag_install_dir = get_option('obdiag_dir')
- try:
- if (not from_option) and (not to_option) and since_option:
- now_time = datetime.datetime.now()
- to_option = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
- from_option = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S')
- except:
- stdio.error(err.EC_OBDIAG_OPTIONS_FORMAT_ERROR.format(option="since", value=since_option))
- return plugin_context.return_false()
+ from_option, to_option, ok = TimeUtils.parse_time_from_to(from_time=from_option, to_time=to_option, stdio=stdio)
+ if not ok:
+ from_option, to_option = TimeUtils.parse_time_since(since=since_option)
ret = local_execute_command('%s --help' % obdiag_bin)
if not ret:
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_obproxy_log.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_obproxy_log.py
index 597dbb8..7bfcf20 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_obproxy_log.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_obproxy_log.py
@@ -40,7 +40,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond=r"cd {install_dir} && sh obdiag gather obproxy_log".format(install_dir=obdiag_install_dir)
+ base_commond=r"cd {install_dir} && ./obdiag gather obproxy_log".format(install_dir=obdiag_install_dir)
cmd = r"{base} --from {from_option} --to {to_option} --scope {scope_option} --encrypt {encrypt_option}".format(
base = base_commond,
from_option = from_option,
@@ -59,17 +59,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
@@ -86,14 +76,9 @@ def run():
obproxy_install_dir_option=global_conf.get('home_path')
obdiag_install_dir = get_option('obdiag_dir')
- try:
- if (not from_option) and (not to_option) and since_option:
- now_time = datetime.datetime.now()
- to_option = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
- from_option = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S')
- except:
- stdio.error(err.EC_OBDIAG_OPTIONS_FORMAT_ERROR.format(option="since", value=since_option))
- return plugin_context.return_false()
+ from_option, to_option, ok = TimeUtils.parse_time_from_to(from_time=from_option, to_time=to_option, stdio=stdio)
+ if not ok:
+ from_option, to_option = TimeUtils.parse_time_since(since=since_option)
ret = local_execute_command('%s --help' % obdiag_bin)
if not ret:
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_perf.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_perf.py
index 0ab01ee..a9132d9 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_perf.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_perf.py
@@ -38,7 +38,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond=r"cd {install_dir} && sh obdiag gather perf".format(install_dir=obdiag_install_dir)
+ base_commond=r"cd {install_dir} && ./obdiag gather perf".format(install_dir=obdiag_install_dir)
cmd = r"{base} --scope {scope_option} ".format(
base = base_commond,
scope_option = scope_option
@@ -52,17 +52,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_plan_monitor.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_plan_monitor.py
index 0f4b2b5..e09485f 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_plan_monitor.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_plan_monitor.py
@@ -38,7 +38,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond=r"cd {install_dir} && sh obdiag gather plan_monitor".format(install_dir=obdiag_install_dir)
+ base_commond=r"cd {install_dir} && ./obdiag gather plan_monitor".format(install_dir=obdiag_install_dir)
cmd = r"{base} --trace_id {trace_id}".format(
base=base_commond,
trace_id=trace_id,
@@ -50,17 +50,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_slog.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_slog.py
index 056d4cb..ac49b57 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_slog.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_slog.py
@@ -44,7 +44,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond = r"cd {install_dir} && sh obdiag gather slog".format(install_dir=obdiag_install_dir)
+ base_commond = r"cd {install_dir} && ./obdiag gather slog".format(install_dir=obdiag_install_dir)
cmd = r"{base} --slog_dir {data_dir} --from {from_option} --to {to_option} --encrypt {encrypt_option}".format(
base = base_commond,
data_dir = data_dir,
@@ -61,17 +61,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
@@ -98,14 +88,9 @@ def run():
server_config['slog_dir'] = '%s/slog' % server_config[mount_key]
data_dir = server_config['slog_dir']
- try:
- if (not from_option) and (not to_option) and since_option:
- now_time = datetime.datetime.now()
- to_option = (now_time + datetime.timedelta(minutes=10)).strftime('%Y-%m-%d %H:%M:%S')
- from_option = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S')
- except:
- stdio.error(err.EC_OBDIAG_OPTIONS_FORMAT_ERROR.format(option="since", value=since_option))
- return plugin_context.return_false()
+ from_option, to_option, ok = TimeUtils.parse_time_from_to(from_time=from_option, to_time=to_option, stdio=stdio)
+ if not ok:
+ from_option, to_option = TimeUtils.parse_time_since(since=since_option)
ret = local_execute_command('%s --help' % obdiag_bin)
if not ret:
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_stack.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_stack.py
index c4c5571..320f1ef 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_stack.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_stack.py
@@ -38,7 +38,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond = r"cd {install_dir} && sh obdiag gather stack".format(install_dir=obdiag_install_dir)
+ base_commond = r"cd {install_dir} && ./obdiag gather stack".format(install_dir=obdiag_install_dir)
cmd = r"{base} ".format(
base = base_commond
)
@@ -51,17 +51,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/gather_sysstat.py b/plugins/oceanbase-diagnostic-tool/1.0/gather_sysstat.py
index 79bd78c..98a28a6 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/gather_sysstat.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/gather_sysstat.py
@@ -38,7 +38,7 @@ def local_execute_command(command, env=None, timeout=None):
return LocalClient.execute_command(command, env, timeout, stdio)
def get_obdiag_cmd():
- base_commond=r"cd {install_dir} && sh obdiag gather sysstat".format(install_dir=obdiag_install_dir)
+ base_commond=r"cd {install_dir} && ./obdiag gather sysstat".format(install_dir=obdiag_install_dir)
cmd = r"{base}".format(
base=base_commond,
)
@@ -49,17 +49,7 @@ def get_obdiag_cmd():
def run():
obdiag_cmd = get_obdiag_cmd()
stdio.verbose('execute cmd: {}'.format(obdiag_cmd))
- p = None
- return_code = 255
- try:
- p = Popen(obdiag_cmd, shell=True)
- return_code = p.wait()
- except:
- stdio.exception("")
- if p:
- p.kill()
- stdio.verbose('exit code: {}'.format(return_code))
- return return_code == 0
+ return LocalClient.run_command(obdiag_cmd, env=None, stdio=stdio)
options = plugin_context.options
obdiag_bin = "obdiag"
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/generate_config.py b/plugins/oceanbase-diagnostic-tool/1.0/generate_config.py
index fc51c43..497535d 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/generate_config.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/generate_config.py
@@ -36,7 +36,7 @@
"log_filename": "obdiag.log",
"log_level": "INFO",
"mode": "obdiag",
- "stdout_handler_log_level": "DEBUG"
+ "stdout_handler_log_level": "INFO"
}
}
}
@@ -81,6 +81,7 @@ def get_obdiag_config():
nodeItem["user"] = parse_empty(user_config.username)
nodeItem["password"] = parse_empty(user_config.password)
nodeItem["private_key"] = parse_empty(user_config.key_file)
+ nodeItem["home_path"] = cluster_config.get_server_conf(server).get("home_path")
nodes.append(nodeItem)
nodes_config = nodes
@@ -114,11 +115,22 @@ def get_obdiag_config():
obcluster_config["password"] = global_conf.get('root_password')
if global_conf.get('mysql_port') is not None:
obcluster_config["port"] = global_conf.get('mysql_port')
+ checker_config = {}
+ checker_config["ignore_obversion"]=False
+ checker_config["report"] = {}
+ if "report_path" in checker_config["report"]:
+ checker_config["report"]["report_path"] = get_option('report_path')
+ else:
+ checker_config["report"]["report_path"]= "./check_report/"
+ checker_config["report"]["export_type"] = "table"
+ checker_config["package_file"] = obdiag_install_dir+"/check_package.yaml"
+ checker_config["tasks_base_path"] = obdiag_install_dir+"/handler/checker/tasks/"
config={
"OBDIAG": base_config,
"OCP": ocp_config,
"OBCLUSTER": obcluster_config,
- "NODES":nodes_config
+ "NODES":nodes_config,
+ "CHECK": checker_config
}
return config
diff --git a/plugins/oceanbase-diagnostic-tool/1.0/pre_check.py b/plugins/oceanbase-diagnostic-tool/1.0/pre_check.py
index cf8d1e7..2969d54 100644
--- a/plugins/oceanbase-diagnostic-tool/1.0/pre_check.py
+++ b/plugins/oceanbase-diagnostic-tool/1.0/pre_check.py
@@ -52,7 +52,7 @@ def utils_work_dir_checker(util_name):
def version_checker():
client = LocalClient
check_status = {}
- ret = client.execute_command('cd {} && sh obdiag version'.format(obdiag_path))
+ ret = client.execute_command('cd {} && ./obdiag version'.format(obdiag_path))
if not ret:
check_status = {'version_checker_status': False, 'obdiag_version': obdiag_new_version, 'obdiag_found': False}
return check_status
@@ -63,7 +63,7 @@ def version_checker():
return check_status
else:
major_version = found.group(1)
- if Version(major_version) < Version(obdiag_new_version):
+ if Version(major_version) > Version(obdiag_new_version):
check_status = {'version_checker_status': True, 'obdiag_version': major_version, 'obdiag_found': True}
return check_status
else:
diff --git a/plugins/oceanbase/3.1.0/connect.py b/plugins/oceanbase/3.1.0/connect.py
index 2db330c..578ed93 100644
--- a/plugins/oceanbase/3.1.0/connect.py
+++ b/plugins/oceanbase/3.1.0/connect.py
@@ -22,6 +22,7 @@
import sys
import time
+import re
from copy import copy
if sys.version_info.major == 2:
import MySQLdb as mysql
@@ -96,6 +97,10 @@ def execute(self, sql, args=None, execute_func=None, raise_exception=None, exc_l
return getattr(self.cursor, execute_func)()
except Exception as e:
getattr(stdio, exc_level)(EC_SQL_EXECUTE_FAILED.format(sql=sql))
+ pattern = r'\n\[(.*?)\]\s+\[(.*?)\]\s+\[(.*?)\]$'
+ error_matches = re.findall(pattern, str(e.args[-1]))
+ if len(error_matches) > 0 and len(error_matches[-1]) == 3:
+ getattr(stdio, exc_level)("observer error trace [%s] from [%s]" % (error_matches[-1][2], error_matches[-1][0]))
if raise_exception is None:
raise_exception = self._raise_exception
if raise_exception:
diff --git a/plugins/oceanbase/3.1.0/ocp_check.py b/plugins/oceanbase/3.1.0/ocp_check.py
index d0af70b..7aebcaf 100644
--- a/plugins/oceanbase/3.1.0/ocp_check.py
+++ b/plugins/oceanbase/3.1.0/ocp_check.py
@@ -35,19 +35,16 @@ def ocp_check(plugin_context, ocp_version, cursor, new_cluster_config=None, new_
pwd_not_empty = True
min_version = Version('3.1.1')
- max_version = min_version
+ ocp_version_420 = Version("4.2.0")
ocp_version = Version(ocp_version)
if ocp_version < min_version:
stdio.error('The current plugin version does not support OCP V%s' % ocp_version)
return
- if ocp_version > max_version:
- stdio.warn('The plugin library does not support OCP V%s. The takeover requirements are not applicable to the current check.' % ocp_version)
-
for server in cluster_config.servers:
client = clients[server]
- if is_admin and client.config.username != 'admin':
+ if is_admin and client.config.username != 'admin' and ocp_version < ocp_version_420:
is_admin = False
stdio.error('The current user must be the admin user. Run the edit-config command to modify the user.username field')
if can_sudo and not client.execute_command('sudo whoami'):
@@ -79,12 +76,15 @@ def ocp_check(plugin_context, ocp_version, cursor, new_cluster_config=None, new_
keys = list(config.keys())
if '$_zone_idc' in keys and isinstance(keys[keys.index('$_zone_idc')], InnerConfigItem):
del zones[zone]
- if zones:
+ if zones and ocp_version < ocp_version_420:
if not cluster_config.parser or cluster_config.parser.STYLE == 'default':
stdio.error('Zone: IDC information is missing for %s. Run the chst command to change the configuration style of %s to cluster, and then run the edit-config command to add IDC information.' % (','.join(zones.keys()), cluster_config.name))
else:
stdio.error('Zone: IDC information is missing for %s. Run the edit-config command to add IDC information.' % ','.join(zones.keys()))
+ else:
+ zones = {}
- if is_admin and can_sudo and only_one and pwd_not_empty and not zones:
+ # if ocp version is greater than 4.2.0, then admin and zone idc check is not needed
+ if can_sudo and only_one and pwd_not_empty and is_admin and not zones:
stdio.print('Configurations of the %s can be taken over by OCP after they take effect.' % cluster_config.name if new_cluster_config else 'Configurations of the %s can be taken over by OCP.' % cluster_config.name)
- return plugin_context.return_true()
\ No newline at end of file
+ return plugin_context.return_true()
diff --git a/plugins/oceanbase/3.1.0/reload.py b/plugins/oceanbase/3.1.0/reload.py
index 385ed9f..3360a60 100644
--- a/plugins/oceanbase/3.1.0/reload.py
+++ b/plugins/oceanbase/3.1.0/reload.py
@@ -31,6 +31,7 @@ def reload(plugin_context, cursor, new_cluster_config, *args, **kwargs):
inner_config = {
InnerConfigItem('$_zone_idc'): 'idc'
}
+ not_paramters = ['production_mode']
inner_keys = inner_config.keys()
zones_config = {}
cluster_server = {}
@@ -47,6 +48,9 @@ def reload(plugin_context, cursor, new_cluster_config, *args, **kwargs):
cluster_server[server] = '%s:%s' % (server.ip, config['rpc_port'])
stdio.verbose('compare configuration of %s' % (server))
for key in new_config:
+ if key in not_paramters:
+ stdio.verbose('%s is not a oceanbase parameter. skip' % key)
+ continue
n_value = new_config[key]
if key not in config or config[key] != n_value:
if isinstance(key, InnerConfigItem) and key in inner_keys:
diff --git a/plugins/oceanbase/3.1.0/start_check.py b/plugins/oceanbase/3.1.0/start_check.py
index 0b0824f..8211dc1 100644
--- a/plugins/oceanbase/3.1.0/start_check.py
+++ b/plugins/oceanbase/3.1.0/start_check.py
@@ -29,7 +29,7 @@
stdio = None
success = True
-
+production_mode = False
def get_port_socket_inode(client, port):
port = hex(port)[2:].zfill(4).upper()
@@ -105,7 +105,7 @@ def get_disk_info(all_paths, client, stdio):
return disk_info
-def start_check(plugin_context, init_check_status=False, strict_check=False, work_dir_check=False, work_dir_empty_check=True, generate_configs={}, precheck=False, *args, **kwargs):
+def start_check(plugin_context, init_check_status=False, strict_check=False, work_dir_check=False, work_dir_empty_check=True, generate_configs={}, precheck=False, source_option='start', *args, **kwargs):
def check_pass(item):
status = check_status[server]
if status[item].status == err.CheckStatus.WAIT:
@@ -125,9 +125,19 @@ def alert(item, error, suggests=[]):
if strict_check:
success = False
check_fail(item, error, suggests)
- stdio.error(error)
+ print_with_suggests(error, suggests)
+ else:
+ stdio.warn(error)
+
+ def alert_strict(item, error, suggests=[]):
+ global success
+ if strict_check or production_mode:
+ success = False
+ check_fail(item, error, suggests)
+ print_with_suggests(error, suggests)
else:
stdio.warn(error)
+
def error(item, _error, suggests=[]):
global success
if plugin_context.dev_mode:
@@ -135,12 +145,16 @@ def error(item, _error, suggests=[]):
else:
success = False
check_fail(item, _error, suggests)
- stdio.error(_error)
+ print_with_suggests(_error, suggests)
+
def critical(item, error, suggests=[]):
global success
success = False
check_fail(item, error, suggests)
- stdio.error(error)
+ print_with_suggests(error, suggests)
+
+ def print_with_suggests(error, suggests=[]):
+ stdio.error('{}, {}'.format(error, suggests[0].msg if suggests else ''))
def system_memory_check():
server_memory_config = server_memory_stat['servers']
@@ -162,7 +176,21 @@ def system_memory_check():
success = True
check_status = {}
cluster_config = plugin_context.cluster_config
+ INF = float('inf')
plugin_context.set_variable('start_check_status', check_status)
+
+ kernel_check_items = [
+ {'check_item': 'vm.max_map_count', 'need': [327600, 1310720], 'recommend': 655360},
+ {'check_item': 'vm.min_free_kbytes', 'need': [32768, 2097152], 'recommend': 2097152},
+ {'check_item': 'vm.overcommit_memory', 'need': 0, 'recommend': 0},
+ {'check_item': 'fs.file-max', 'need': [6573688, INF], 'recommend': 6573688},
+ ]
+
+ kernel_check_status = {}
+ for kernel_param in kernel_check_items:
+ check_item = kernel_param['check_item']
+ kernel_check_status[check_item] = err.CheckStatus()
+
for server in cluster_config.servers:
check_status[server] = {
'port': err.CheckStatus(),
@@ -173,6 +201,7 @@ def system_memory_check():
'net': err.CheckStatus(),
'ntp': err.CheckStatus(),
}
+ check_status[server].update(kernel_check_status)
if work_dir_check:
check_status[server]['dir'] = err.CheckStatus()
@@ -181,18 +210,22 @@ def system_memory_check():
clients = plugin_context.clients
stdio = plugin_context.stdio
- stdio.start_loading('Check before start observer')
servers_clients = {}
servers_port = {}
servers_memory = {}
servers_disk = {}
servers_clog_mount = {}
- servers_net_inferface = {}
+ servers_net_interface = {}
servers_dirs = {}
servers_check_dirs = {}
START_NEED_MEMORY = 3 << 30
global_generate_config = generate_configs.get('global', {})
- stdio.start_loading('Check before start observer')
+ stdio.start_loading('Check before {} observer'.format(source_option))
+
+ parameter_check = True
+ port_check = True
+ kernel_check = True
+ is_running_opt = source_option in ['restart', 'upgrade']
for server in cluster_config.servers:
ip = server.ip
client = clients[server]
@@ -200,6 +233,7 @@ def system_memory_check():
servers_clients[ip] = client
server_config = cluster_config.get_server_conf_with_default(server)
home_path = server_config['home_path']
+ production_mode = server_config.get('production_mode', False)
if not precheck:
remote_pid_path = '%s/run/observer.pid' % home_path
remote_pid = client.execute_command('cat %s' % remote_pid_path).stdout.strip()
@@ -207,7 +241,10 @@ def system_memory_check():
if client.execute_command('ls /proc/%s' % remote_pid):
stdio.verbose('%s is runnning, skip' % server)
wait_2_pass()
- continue
+ work_dir_check = False
+ port_check = False
+ parameter_check = False
+ kernel_check = is_running_opt
if work_dir_check:
stdio.verbose('%s dir check' % server)
@@ -276,77 +313,79 @@ def system_memory_check():
servers_disk[ip] = {}
servers_port[ip] = {}
servers_clog_mount[ip] = {}
- servers_net_inferface[ip] = {}
+ servers_net_interface[ip] = {}
servers_memory[ip] = {'num': 0, 'percentage': 0, 'servers': {}}
memory = servers_memory[ip]
ports = servers_port[ip]
disk = servers_disk[ip]
clog_mount = servers_clog_mount[ip]
- inferfaces = servers_net_inferface[ip]
- stdio.verbose('%s port check' % server)
- for key in ['mysql_port', 'rpc_port']:
- port = int(server_config[key])
- if port in ports:
- critical(
- 'port',
- err.EC_CONFIG_CONFLICT_PORT.format(server1=server, port=port, server2=ports[port]['server'], key=ports[port]['key']),
- [err.SUG_PORT_CONFLICTS.format()]
- )
- continue
- ports[port] = {
- 'server': server,
- 'key': key
- }
- if get_port_socket_inode(client, port):
- critical(
- 'port',
- err.EC_CONFLICT_PORT.format(server=ip, port=port),
- [err.SUG_USE_OTHER_PORT.format()]
- )
-
- memory_limit = 0
- percentage = 0
- if server_config.get('memory_limit'):
- memory_limit = parse_size(server_config['memory_limit'])
- memory['num'] += memory_limit
- elif 'memory_limit_percentage' in server_config:
- percentage = int(parse_size(server_config['memory_limit_percentage']))
- memory['percentage'] += percentage
- else:
- percentage = 80
- memory['percentage'] += percentage
- memory['servers'][server] = {
- 'num': memory_limit,
- 'percentage': percentage,
- 'system_memory': parse_size(server_config.get('system_memory', 0))
- }
-
- data_path = server_config['data_dir'] if server_config.get('data_dir') else os.path.join(server_config['home_path'], 'store')
- redo_dir = server_config['redo_dir'] if server_config.get('redo_dir') else data_path
- clog_dir = server_config['clog_dir'] if server_config.get('clog_dir') else os.path.join(redo_dir, 'clog')
- if not client.execute_command('ls %s/sstable/block_file' % data_path):
- disk[data_path] = {
- 'need': 90,
- 'server': server
- }
- clog_mount[clog_dir] = {
- 'threshold': server_config.get('clog_disk_utilization_threshold', 80) / 100.0,
- 'server': server
+ interfaces = servers_net_interface[ip]
+ if port_check:
+ stdio.verbose('%s port check' % server)
+ for key in ['mysql_port', 'rpc_port']:
+ port = int(server_config[key])
+ if port in ports:
+ critical(
+ 'port',
+ err.EC_CONFIG_CONFLICT_PORT.format(server1=server, port=port, server2=ports[port]['server'], key=ports[port]['key']),
+ [err.SUG_PORT_CONFLICTS.format()]
+ )
+ continue
+ ports[port] = {
+ 'server': server,
+ 'key': key
+ }
+ if get_port_socket_inode(client, port):
+ critical(
+ 'port',
+ err.EC_CONFLICT_PORT.format(server=ip, port=port),
+ [err.SUG_USE_OTHER_PORT.format()]
+ )
+
+ if parameter_check:
+ memory_limit = 0
+ percentage = 0
+ if server_config.get('memory_limit'):
+ memory_limit = parse_size(server_config['memory_limit'])
+ memory['num'] += memory_limit
+ elif 'memory_limit_percentage' in server_config:
+ percentage = int(parse_size(server_config['memory_limit_percentage']))
+ memory['percentage'] += percentage
+ else:
+ percentage = 80
+ memory['percentage'] += percentage
+ memory['servers'][server] = {
+ 'num': memory_limit,
+ 'percentage': percentage,
+ 'system_memory': parse_size(server_config.get('system_memory', 0))
}
- if 'datafile_size' in server_config and server_config['datafile_size']:
- disk[data_path]['need'] = server_config['datafile_size']
- elif 'datafile_disk_percentage' in server_config and server_config['datafile_disk_percentage']:
- disk[data_path]['need'] = int(server_config['datafile_disk_percentage'])
-
- devname = server_config.get('devname')
- if devname:
- if not client.execute_command("grep -e '^ *%s:' /proc/net/dev" % devname):
- suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
- suggest.auto_fix = 'devname' not in global_generate_config and 'devname' not in server_generate_config
- critical('net', err.EC_NO_SUCH_NET_DEVICE.format(server=server, devname=devname), suggests=[suggest])
- if devname not in inferfaces:
- inferfaces[devname] = []
- inferfaces[devname].append(ip)
+
+ data_path = server_config['data_dir'] if server_config.get('data_dir') else os.path.join(server_config['home_path'], 'store')
+ redo_dir = server_config['redo_dir'] if server_config.get('redo_dir') else data_path
+ clog_dir = server_config['clog_dir'] if server_config.get('clog_dir') else os.path.join(redo_dir, 'clog')
+ if not client.execute_command('ls %s/sstable/block_file' % data_path):
+ disk[data_path] = {
+ 'need': 90,
+ 'server': server
+ }
+ clog_mount[clog_dir] = {
+ 'threshold': server_config.get('clog_disk_utilization_threshold', 80) / 100.0,
+ 'server': server
+ }
+ if 'datafile_size' in server_config and server_config['datafile_size']:
+ disk[data_path]['need'] = server_config['datafile_size']
+ elif 'datafile_disk_percentage' in server_config and server_config['datafile_disk_percentage']:
+ disk[data_path]['need'] = int(server_config['datafile_disk_percentage'])
+
+ devname = server_config.get('devname')
+ if devname:
+ if not client.execute_command("grep -e '^ *%s:' /proc/net/dev" % devname):
+ suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
+ suggest.auto_fix = 'devname' not in global_generate_config and 'devname' not in server_generate_config
+ critical('net', err.EC_NO_SUCH_NET_DEVICE.format(server=server, devname=devname), suggests=[suggest])
+ if devname not in interfaces:
+ interfaces[devname] = []
+ interfaces[devname].append(ip)
for ip in servers_disk:
client = servers_clients[ip]
@@ -386,6 +425,19 @@ def system_memory_check():
'recd': lambda x: 4096 * x,
'name': 'nproc'
},
+ 'core file size': {
+ 'need': lambda x: 0,
+ 'recd': lambda x: INF,
+ 'below_need_error': False,
+ 'below_recd_error_strict': False,
+ 'name': 'core'
+ },
+ 'stack size': {
+ 'need': lambda x: 1024,
+ 'recd': lambda x: INF,
+ 'below_recd_error_strict': False,
+ 'name': 'stack'
+ },
}
ulimits = {}
src_data = re.findall('\s?([a-zA-Z\s]+[a-zA-Z])\s+\([a-zA-Z\-,\s]+\)\s+([\d[a-zA-Z]+)', ret.stdout) if ret else []
@@ -397,18 +449,68 @@ def system_memory_check():
continue
if not value or not (value.strip().isdigit()):
for server in ip_servers:
- alert('ulimit', '(%s) failed to get %s' % (ip, key), [err.SUG_UNSUPPORT_OS.format()])
+ alert('ulimit', '(%s) failed to get %s' % (ip, key), suggests=[err.SUG_UNSUPPORT_OS.format()])
else:
value = int(value)
need = ulimits_min[key]['need'](server_num)
if need > value:
+ if (strict_check or production_mode) and ulimits_min[key].get('below_recd_error_strict', True) and value < ulimits_min[key]['recd'](server_num):
+ need = ulimits_min[key]['recd'](server_num)
+ need = need if need != INF else 'unlimited'
for server in ip_servers:
- critical('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ if ulimits_min[key].get('below_need_error', True):
+ critical('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ else:
+ alert('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), suggests=[err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
else:
need = ulimits_min[key]['recd'](server_num)
if need > value:
+ need = need if need != INF else 'unlimited'
for server in ip_servers:
- alert('ulimit', err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ if ulimits_min[key].get('below_recd_error_strict', True):
+ alert('ulimit', err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), suggests=[err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ else:
+ stdio.warn(err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value))
+
+ if kernel_check:
+ # check kernel params
+ try:
+ cmd = 'sysctl -a'
+ ret = client.execute_command(cmd)
+ if not ret:
+ alert_strict('kernel', err.EC_FAILED_TO_GET_PARAM.format(key='kernel parameter ', cmd=cmd), [err.SUG_CONNECT_EXCEPT.format(ip=ip)])
+ continue
+ kernel_params = {}
+ kernel_param_src = ret.stdout.split('\n')
+ for kernel in kernel_param_src:
+ if not kernel:
+ continue
+ kernel = kernel.split('=')
+ kernel_params[kernel[0].strip()] = re.findall(r"[-+]?\d+", kernel[1])
+
+ for kernel_param in kernel_check_items:
+ check_item = kernel_param['check_item']
+ if check_item not in kernel_params:
+ continue
+ values = kernel_params[check_item]
+ needs = kernel_param['need']
+ recommends = kernel_param['recommend']
+ for i in range(len(values)):
+ # This case is not handling the value of 'default'. Additional handling is required for 'default' in the future.
+ item_value = int(values[i])
+ need = needs[i] if isinstance(needs, tuple) else needs
+ recommend = recommends[i] if isinstance(recommends, tuple) else recommends
+ if isinstance(need, list):
+ if item_value < need[0] or item_value > need[1]:
+ suggest = [err.SUG_SYSCTL.format(var=check_item, value=' '.join(str(i) for i in recommend) if isinstance(recommend, list) else recommend, ip=ip)]
+ need = 'within {}'.format(needs) if needs[-1] != INF else 'greater than {}'.format(needs[0])
+ now = '[{}]'.format(', '.join(values)) if len(values) > 1 else item_value
+ alert_strict(check_item, err.EC_PARAM_NOT_IN_NEED.format(ip=ip, check_item=check_item, need=need, now=now, recommend=recommends), suggest)
+ break
+ elif item_value != need:
+ alert_strict(check_item, err.EC_PARAM_NOT_IN_NEED.format(ip=ip, check_item=check_item, need=needs, recommend=recommend, now=item_value), [err.SUG_SYSCTL.format(var=check_item, value=recommend, ip=ip)])
+ except:
+ stdio.exception('')
# memory
ret = client.execute_command('cat /proc/meminfo')
@@ -427,7 +529,7 @@ def system_memory_check():
if k in memory_key_map:
key = memory_key_map[k]
server_memory_stats[key] = parse_size(str(v))
-
+
server_memory_stat = servers_memory[ip]
min_start_need = server_num * START_NEED_MEMORY
total_use = server_memory_stat['percentage'] * server_memory_stats['total'] / 100 + server_memory_stat['num']
@@ -474,7 +576,7 @@ def system_memory_check():
if len(p) > len(kp):
kp = p
disk[kp]['threshold'] = min(disk[kp]['threshold'], servers_clog_mount[ip][path]['threshold'])
-
+
for p in disk:
total = disk[p]['total']
avail = disk[p]['avail']
@@ -510,8 +612,8 @@ def system_memory_check():
critical('disk', err.EC_OBSERVER_NOT_ENOUGH_DISK_4_CLOG.format(ip=ip, path=p), [suggest] + suggests)
if success:
- for ip in servers_net_inferface:
- if servers_net_inferface[ip].get(None):
+ for ip in servers_net_interface:
+ if servers_net_interface[ip].get(None):
devinfo = client.execute_command('cat /proc/net/dev').stdout
interfaces = []
for interface in re.findall('\n\s+(\w+):', devinfo):
@@ -520,17 +622,17 @@ def system_memory_check():
if not interfaces:
interfaces = ['lo']
if len(interfaces) > 1:
- servers = ','.join(str(server) for server in servers_net_inferface[ip][None])
+ servers = ','.join(str(server) for server in servers_net_interface[ip][None])
suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
for server in ip_servers:
critical('net', err.EC_OBSERVER_MULTI_NET_DEVICE.format(ip=ip, server=servers), [suggest])
else:
- servers_net_inferface[ip][interfaces[0]] = servers_net_inferface[ip][None]
- del servers_net_inferface[ip][None]
+ servers_net_interface[ip][interfaces[0]] = servers_net_interface[ip][None]
+ del servers_net_interface[ip][None]
if success:
- for ip in servers_net_inferface:
+ for ip in servers_net_interface:
client = servers_clients[ip]
- for devname in servers_net_inferface[ip]:
+ for devname in servers_net_interface[ip]:
if client.is_localhost() and devname != 'lo' or (not client.is_localhost() and devname == 'lo'):
suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
suggest.auto_fix = client.is_localhost() and 'devname' not in global_generate_config and 'devname' not in server_generate_config
diff --git a/plugins/oceanbase/4.0.0.0/start_check.py b/plugins/oceanbase/4.0.0.0/start_check.py
index e9ece5f..a8a73b4 100644
--- a/plugins/oceanbase/4.0.0.0/start_check.py
+++ b/plugins/oceanbase/4.0.0.0/start_check.py
@@ -31,7 +31,7 @@
stdio = None
success = True
-
+production_mode = False
def get_port_socket_inode(client, port):
port = hex(port)[2:].zfill(4).upper()
@@ -76,7 +76,7 @@ def time_delta(client):
def get_mount_path(disk, _path):
_mount_path = '/'
for p in disk:
- if p in _path:
+ if p in _path and _path.startswith(p):
if len(p) > len(_mount_path):
_mount_path = p
return _mount_path
@@ -133,7 +133,7 @@ def get_disk_info(all_paths, client, stdio):
return disk_info
-def start_check(plugin_context, init_check_status=False, strict_check=False, work_dir_check=False, work_dir_empty_check=True, generate_configs={}, precheck=False, *args, **kwargs):
+def start_check(plugin_context, init_check_status=False, strict_check=False, work_dir_check=False, work_dir_empty_check=True, generate_configs={}, precheck=False, source_option='start', *args, **kwargs):
def check_pass(item):
status = check_status[server]
if status[item].status == err.CheckStatus.WAIT:
@@ -153,9 +153,19 @@ def alert(item, error, suggests=[]):
if strict_check:
success = False
check_fail(item, error, suggests)
- stdio.error(error)
+ print_with_suggests(error, suggests)
else:
stdio.warn(error)
+
+ def alert_strict(item, error, suggests=[]):
+ global success
+ if strict_check or production_mode:
+ success = False
+ check_fail(item, error, suggests)
+ print_with_suggests(error, suggests)
+ else:
+ stdio.warn(error)
+
def error(item, _error, suggests=[]):
global success
if plugin_context.dev_mode:
@@ -163,12 +173,16 @@ def error(item, _error, suggests=[]):
else:
success = False
check_fail(item, _error, suggests)
- stdio.error(_error)
+ print_with_suggests(_error, suggests)
+
def critical(item, error, suggests=[]):
global success
success = False
check_fail(item, error, suggests)
- stdio.error(error)
+ print_with_suggests(error, suggests)
+
+ def print_with_suggests(error, suggests=[]):
+ stdio.error('{}, {}'.format(error, suggests[0].msg if suggests else ''))
def system_memory_check():
server_memory_config = server_memory_stat['servers']
@@ -189,8 +203,21 @@ def system_memory_check():
success = True
check_status = {}
cluster_config = plugin_context.cluster_config
+ INF = float('inf')
plugin_context.set_variable('start_check_status', check_status)
+ kernel_check_items = [
+ {'check_item': 'vm.max_map_count', 'need': [327600, 1310720], 'recommend': 655360},
+ {'check_item': 'vm.min_free_kbytes', 'need': [32768, 2097152], 'recommend': 2097152},
+ {'check_item': 'vm.overcommit_memory', 'need': 0, 'recommend': 0},
+ {'check_item': 'fs.file-max', 'need': [6573688, INF], 'recommend': 6573688},
+ ]
+
+ kernel_check_status = {}
+ for kernel_param in kernel_check_items:
+ check_item = kernel_param['check_item']
+ kernel_check_status[check_item] = err.CheckStatus()
+
for server in cluster_config.servers:
check_status[server] = {
'port': err.CheckStatus(),
@@ -202,6 +229,7 @@ def system_memory_check():
'ntp': err.CheckStatus(),
'ocp meta db': err.CheckStatus()
}
+ check_status[server].update(kernel_check_status)
if work_dir_check:
check_status[server]['dir'] = err.CheckStatus()
@@ -215,7 +243,7 @@ def system_memory_check():
servers_memory = {}
servers_disk = {}
servers_clog_mount = {}
- servers_net_inferface = {}
+ servers_net_interface = {}
servers_dirs = {}
servers_check_dirs = {}
servers_log_disk_size = {}
@@ -223,10 +251,15 @@ def system_memory_check():
PRO_MEMORY_MIN = 16 << 30
PRO_POOL_MEM_MIN = 2147483648
START_NEED_MEMORY = 3 << 30
- global_generate_config = generate_configs.get('global', {})
- stdio.start_loading('Check before start observer')
+ global_generate_config = plugin_context.get_variable('global_generate_config', default=generate_configs.get('global', {}))
+ plugin_context.set_variable('global_generate_config', global_generate_config)
+ stdio.start_loading('Check before {} observer'.format(source_option))
need_bootstrap = True
+ parameter_check = True
+ port_check = True
+ kernel_check = True
+ is_running_opt = source_option in ['restart', 'upgrade']
for server in cluster_config.servers:
ip = server.ip
client = clients[server]
@@ -234,6 +267,7 @@ def system_memory_check():
servers_clients[ip] = client
server_config = cluster_config.get_server_conf_with_default(server)
home_path = server_config['home_path']
+ production_mode = server_config.get('production_mode', False)
if not precheck:
if need_bootstrap:
data_dir = server_config['data_dir'] if server_config.get('data_dir') else '%s/store' % home_path
@@ -244,7 +278,10 @@ def system_memory_check():
if remote_pid:
if client.execute_command('ls /proc/%s' % remote_pid):
stdio.verbose('%s is runnning, skip' % server)
- continue
+ work_dir_check = False
+ port_check = False
+ parameter_check = False
+ kernel_check = is_running_opt
if work_dir_check:
stdio.verbose('%s dir check' % server)
@@ -313,82 +350,84 @@ def system_memory_check():
servers_disk[ip] = {}
servers_port[ip] = {}
servers_clog_mount[ip] = {}
- servers_net_inferface[ip] = {}
+ servers_net_interface[ip] = {}
servers_memory[ip] = {'num': 0, 'percentage': 0, 'servers': {}}
memory = servers_memory[ip]
ports = servers_port[ip]
disk = servers_disk[ip]
clog_mount = servers_clog_mount[ip]
- inferfaces = servers_net_inferface[ip]
- stdio.verbose('%s port check' % server)
- for key in ['mysql_port', 'rpc_port']:
- port = int(server_config[key])
- if port in ports:
- critical(
- 'port',
- err.EC_CONFIG_CONFLICT_PORT.format(server1=server, port=port, server2=ports[port]['server'], key=ports[port]['key']),
- [err.SUG_PORT_CONFLICTS.format()]
- )
- continue
- ports[port] = {
- 'server': server,
- 'key': key
+ interfaces = servers_net_interface[ip]
+ if port_check:
+ stdio.verbose('%s port check' % server)
+ for key in ['mysql_port', 'rpc_port']:
+ port = int(server_config[key])
+ if port in ports:
+ critical(
+ 'port',
+ err.EC_CONFIG_CONFLICT_PORT.format(server1=server, port=port, server2=ports[port]['server'], key=ports[port]['key']),
+ [err.SUG_PORT_CONFLICTS.format()]
+ )
+ continue
+ ports[port] = {
+ 'server': server,
+ 'key': key
+ }
+ if get_port_socket_inode(client, port):
+ critical('port', err.EC_CONFLICT_PORT.format(server=ip, port=port), [err.SUG_USE_OTHER_PORT.format()])
+
+ if parameter_check:
+ servers_min_pool_memory[server] = __min_full_resource_pool_memory = server_config.get('__min_full_resource_pool_memory')
+ if production_mode and __min_full_resource_pool_memory < PRO_POOL_MEM_MIN:
+ error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key="__min_full_resource_pool_memory", limit=PRO_POOL_MEM_MIN), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
+
+ memory_limit = 0
+ percentage = 0
+ if server_config.get('memory_limit'):
+ memory_limit = parse_size(server_config['memory_limit'])
+ if production_mode and memory_limit < PRO_MEMORY_MIN:
+ error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key='memory_limit', limit=format_size(PRO_MEMORY_MIN)), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
+ memory['num'] += memory_limit
+ elif 'memory_limit_percentage' in server_config:
+ percentage = int(parse_size(server_config['memory_limit_percentage']))
+ memory['percentage'] += percentage
+ else:
+ percentage = 80
+ memory['percentage'] += percentage
+ memory['servers'][server] = {
+ 'num': memory_limit,
+ 'percentage': percentage,
+ 'system_memory': parse_size(server_config.get('system_memory', 0))
}
- if get_port_socket_inode(client, port):
- critical('port', err.EC_CONFLICT_PORT.format(server=ip, port=port), [err.SUG_USE_OTHER_PORT.format()])
-
- servers_min_pool_memory[server] = __min_full_resource_pool_memory = server_config.get('__min_full_resource_pool_memory')
- if server_config.get('production_mode') and __min_full_resource_pool_memory < PRO_POOL_MEM_MIN:
- error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key="__min_full_resource_pool_memory", limit=PRO_POOL_MEM_MIN), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
-
- memory_limit = 0
- percentage = 0
- if server_config.get('memory_limit'):
- memory_limit = parse_size(server_config['memory_limit'])
- if server_config.get('production_mode') and memory_limit < PRO_MEMORY_MIN:
- error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key='memory_limit', limit=format_size(PRO_MEMORY_MIN)), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
- memory['num'] += memory_limit
- elif 'memory_limit_percentage' in server_config:
- percentage = int(parse_size(server_config['memory_limit_percentage']))
- memory['percentage'] += percentage
- else:
- percentage = 80
- memory['percentage'] += percentage
- memory['servers'][server] = {
- 'num': memory_limit,
- 'percentage': percentage,
- 'system_memory': parse_size(server_config.get('system_memory', 0))
- }
- data_path = server_config['data_dir'] if server_config.get('data_dir') else os.path.join(server_config['home_path'], 'store')
- redo_dir = server_config['redo_dir'] if server_config.get('redo_dir') else data_path
- clog_dir = server_config['clog_dir'] if server_config.get('clog_dir') else os.path.join(redo_dir, 'clog')
- if not client.execute_command('ls %s/sstable/block_file' % data_path):
- disk[data_path] = {'server': server}
- clog_mount[clog_dir] = {'server': server}
- if 'datafile_size' in server_config and server_config['datafile_size'] and parse_size(server_config['datafile_size']):
- # if need is string, it means use datafile_size
- disk[data_path]['need'] = server_config['datafile_size']
- elif 'datafile_disk_percentage' in server_config and server_config['datafile_disk_percentage']:
- # if need is integer, it means use datafile_disk_percentage
- disk[data_path]['need'] = int(server_config['datafile_disk_percentage'])
-
- if 'log_disk_size' in server_config and server_config['log_disk_size'] and parse_size(server_config['log_disk_size']):
- # if need is string, it means use log_disk_size
- clog_mount[clog_dir]['need'] = server_config['log_disk_size']
- elif 'log_disk_percentage' in server_config and server_config['log_disk_percentage']:
- # if need is integer, it means use log_disk_percentage
- clog_mount[clog_dir]['need'] = int(server_config['log_disk_percentage'])
-
- devname = server_config.get('devname')
- if devname:
- if not client.execute_command("grep -e '^ *%s:' /proc/net/dev" % devname):
- suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
- suggest.auto_fix = 'devname' not in global_generate_config and 'devname' not in server_generate_config
- critical('net', err.EC_NO_SUCH_NET_DEVICE.format(server=server, devname=devname), suggests=[suggest])
- if devname not in inferfaces:
- inferfaces[devname] = []
- inferfaces[devname].append(ip)
+ data_path = server_config['data_dir'] if server_config.get('data_dir') else os.path.join(server_config['home_path'], 'store')
+ redo_dir = server_config['redo_dir'] if server_config.get('redo_dir') else data_path
+ clog_dir = server_config['clog_dir'] if server_config.get('clog_dir') else os.path.join(redo_dir, 'clog')
+ if not client.execute_command('ls %s/sstable/block_file' % data_path):
+ disk[data_path] = {'server': server}
+ clog_mount[clog_dir] = {'server': server}
+ if 'datafile_size' in server_config and server_config['datafile_size'] and parse_size(server_config['datafile_size']):
+ # if need is string, it means use datafile_size
+ disk[data_path]['need'] = server_config['datafile_size']
+ elif 'datafile_disk_percentage' in server_config and server_config['datafile_disk_percentage']:
+ # if need is integer, it means use datafile_disk_percentage
+ disk[data_path]['need'] = int(server_config['datafile_disk_percentage'])
+
+ if 'log_disk_size' in server_config and server_config['log_disk_size'] and parse_size(server_config['log_disk_size']):
+ # if need is string, it means use log_disk_size
+ clog_mount[clog_dir]['need'] = server_config['log_disk_size']
+ elif 'log_disk_percentage' in server_config and server_config['log_disk_percentage']:
+ # if need is integer, it means use log_disk_percentage
+ clog_mount[clog_dir]['need'] = int(server_config['log_disk_percentage'])
+
+ devname = server_config.get('devname')
+ if devname:
+ if not client.execute_command("grep -e '^ *%s:' /proc/net/dev" % devname):
+ suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
+ suggest.auto_fix = 'devname' not in global_generate_config and 'devname' not in server_generate_config
+ critical('net', err.EC_NO_SUCH_NET_DEVICE.format(server=server, devname=devname), suggests=[suggest])
+ if devname not in interfaces:
+ interfaces[devname] = []
+ interfaces[devname].append(ip)
ip_server_memory_info = {}
@@ -425,10 +464,23 @@ def system_memory_check():
'name': 'nofile'
},
'max user processes': {
- 'need': lambda x: 4096,
- 'recd': lambda x: 4096 * x,
+ 'need': lambda x: 120000,
+ 'recd': lambda x: 655350,
'name': 'nproc'
},
+ 'core file size': {
+ 'need': lambda x: 0,
+ 'recd': lambda x: INF,
+ 'below_need_error': False,
+ 'below_recd_error_strict': False,
+ 'name': 'core'
+ },
+ 'stack size': {
+ 'need': lambda x: 1024,
+ 'recd': lambda x: INF,
+ 'below_recd_error_strict': False,
+ 'name': 'stack'
+ },
}
ulimits = {}
src_data = re.findall('\s?([a-zA-Z\s]+[a-zA-Z])\s+\([a-zA-Z\-,\s]+\)\s+([\d[a-zA-Z]+)', ret.stdout) if ret else []
@@ -440,19 +492,68 @@ def system_memory_check():
continue
if not value or not (value.strip().isdigit()):
for server in ip_servers:
- alert('ulimit', '(%s) failed to get %s' % (ip, key), [err.SUG_UNSUPPORT_OS.format()])
+ alert('ulimit', '(%s) failed to get %s' % (ip, key), suggests=[err.SUG_UNSUPPORT_OS.format()])
else:
value = int(value)
need = ulimits_min[key]['need'](server_num)
if need > value:
+ if (strict_check or production_mode) and ulimits_min[key].get('below_recd_error_strict', True) and value < ulimits_min[key]['recd'](server_num):
+ need = ulimits_min[key]['recd'](server_num)
+ need = need if need != INF else 'unlimited'
for server in ip_servers:
- critical('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ if ulimits_min[key].get('below_need_error', True):
+ critical('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ else:
+ alert('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), suggests=[err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
else:
need = ulimits_min[key]['recd'](server_num)
if need > value:
+ need = need if need != INF else 'unlimited'
for server in ip_servers:
- alert('ulimit', err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ if ulimits_min[key].get('below_recd_error_strict', True):
+ alert('ulimit', err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), suggests=[err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ else:
+ stdio.warn(err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value))
+
+ if kernel_check:
+ # check kernel params
+ try:
+ cmd = 'sysctl -a'
+ ret = client.execute_command(cmd)
+ if not ret:
+ alert_strict('kernel', err.EC_FAILED_TO_GET_PARAM.format(key='kernel parameter ', cmd=cmd), [err.SUG_CONNECT_EXCEPT.format(ip=ip)])
+ continue
+ kernel_params = {}
+ kernel_param_src = ret.stdout.split('\n')
+ for kernel in kernel_param_src:
+ if not kernel:
+ continue
+ kernel = kernel.split('=')
+ kernel_params[kernel[0].strip()] = re.findall(r"[-+]?\d+", kernel[1])
+ for kernel_param in kernel_check_items:
+ check_item = kernel_param['check_item']
+ if check_item not in kernel_params:
+ continue
+ values = kernel_params[check_item]
+ needs = kernel_param['need']
+ recommends = kernel_param['recommend']
+ for i in range(len(values)):
+ # This case is not handling the value of 'default'. Additional handling is required for 'default' in the future.
+ item_value = int(values[i])
+ need = needs[i] if isinstance(needs, tuple) else needs
+ recommend = recommends[i] if isinstance(recommends, tuple) else recommends
+ if isinstance(need, list):
+ if item_value < need[0] or item_value > need[1]:
+ suggest = [err.SUG_SYSCTL.format(var=check_item, value=' '.join(str(i) for i in recommend) if isinstance(recommend, list) else recommend, ip=ip)]
+ need = 'within {}'.format(needs) if needs[-1] != INF else 'greater than {}'.format(needs[0])
+ now = '[{}]'.format(', '.join(values)) if len(values) > 1 else item_value
+ alert_strict(check_item, err.EC_PARAM_NOT_IN_NEED.format(ip=ip, check_item=check_item, need=need, now=now, recommend=recommends), suggest)
+ break
+ elif item_value != need:
+ alert_strict(check_item, err.EC_PARAM_NOT_IN_NEED.format(ip=ip, check_item=check_item, need=needs, recommend=recommend, now=item_value), [err.SUG_SYSCTL.format(var=check_item, value=recommend, ip=ip)])
+ except:
+ stdio.exception('')
# memory
ret = client.execute_command('cat /proc/meminfo')
@@ -636,8 +737,8 @@ def system_memory_check():
if success:
- for ip in servers_net_inferface:
- if servers_net_inferface[ip].get(None):
+ for ip in servers_net_interface:
+ if servers_net_interface[ip].get(None):
devinfo = client.execute_command('cat /proc/net/dev').stdout
interfaces = []
for interface in re.findall('\n\s+(\w+):', devinfo):
@@ -646,18 +747,18 @@ def system_memory_check():
if not interfaces:
interfaces = ['lo']
if len(interfaces) > 1:
- servers = ','.join(str(server) for server in servers_net_inferface[ip][None])
+ servers = ','.join(str(server) for server in servers_net_interface[ip][None])
suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
for server in ip_servers:
critical('net', err.EC_OBSERVER_MULTI_NET_DEVICE.format(ip=ip, server=servers), [suggest])
else:
- servers_net_inferface[ip][interfaces[0]] = servers_net_inferface[ip][None]
- del servers_net_inferface[ip][None]
+ servers_net_interface[ip][interfaces[0]] = servers_net_interface[ip][None]
+ del servers_net_interface[ip][None]
if success:
- for ip in servers_net_inferface:
+ for ip in servers_net_interface:
client = servers_clients[ip]
- for devname in servers_net_inferface[ip]:
+ for devname in servers_net_interface[ip]:
if client.is_localhost() and devname != 'lo' or (not client.is_localhost() and devname == 'lo'):
suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
suggest.auto_fix = client.is_localhost() and 'devname' not in global_generate_config and 'devname' not in server_generate_config
diff --git a/plugins/oceanbase/4.2.0.0/generate_config.py b/plugins/oceanbase/4.2.0.0/generate_config.py
index 87f3f94..11531b7 100644
--- a/plugins/oceanbase/4.2.0.0/generate_config.py
+++ b/plugins/oceanbase/4.2.0.0/generate_config.py
@@ -83,7 +83,7 @@ def generate_config(plugin_context, generate_config_mini=False, generate_check=T
generate_keys = []
if not only_generate_password:
generate_keys += [
- 'memory_limit', 'datafile_size', 'log_disk_size', 'devname', 'system_memory', 'cpu_count', 'production_mode',
+ 'memory_limit', 'datafile_size', 'log_disk_size', 'system_memory', 'cpu_count', 'production_mode',
'syslog_level', 'enable_syslog_recycle', 'enable_syslog_wf', 'max_syslog_file_count', 'cluster_id', 'ocp_meta_tenant_log_disk_size',
'datafile_next', 'datafile_maxsize'
]
@@ -156,19 +156,6 @@ def summit_config():
server_config = cluster_config.get_server_conf_with_default(server)
user_server_config = cluster_config.get_original_server_conf_with_global(server, format_conf=True)
- if user_server_config.get('devname') is None:
- if client.is_localhost():
- update_server_conf(server, 'devname', 'lo')
- else:
- devinfo = client.execute_command('cat /proc/net/dev').stdout
- interfaces = re.findall('\n\s+(\w+):', devinfo)
- for interface in interfaces:
- if interface == 'lo':
- continue
- if client.execute_command('ping -W 1 -c 1 -I %s %s' % (interface, ip)):
- update_server_conf(server, 'devname', interface)
- break
-
dirs = {"home_path": server_config['home_path']}
dirs["data_dir"] = server_config['data_dir'] if server_config.get('data_dir') else os.path.join(server_config['home_path'], 'store')
dirs["redo_dir"] = server_config['redo_dir'] if server_config.get('redo_dir') else dirs["data_dir"]
diff --git a/plugins/oceanbase/4.2.0.0/parameter.yaml b/plugins/oceanbase/4.2.0.0/parameter.yaml
index b490fbd..f8f3b86 100644
--- a/plugins/oceanbase/4.2.0.0/parameter.yaml
+++ b/plugins/oceanbase/4.2.0.0/parameter.yaml
@@ -68,6 +68,14 @@
need_restart: true
description_en: name of network adapter
description_local: 服务进程绑定的网卡设备名
+- name: local_ip
+ name_local: 本机ip
+ type: STRING
+ min_value: NULL
+ max_value: NULL
+ need_restart: true
+ description_en: local ip address
+ description_local: 本机ip地址
- name: rpc_port
name_local: 内部通信端口
require: true
diff --git a/plugins/oceanbase/4.2.0.0/start.py b/plugins/oceanbase/4.2.0.0/start.py
index 77b5600..be17f6c 100644
--- a/plugins/oceanbase/4.2.0.0/start.py
+++ b/plugins/oceanbase/4.2.0.0/start.py
@@ -29,14 +29,22 @@
from collections import OrderedDict
+from tool import NetUtil
+
def config_url(ocp_config_server, appname, cid):
- cfg_url = '%s&Action=ObRootServiceInfo&ObCluster=%s' % (ocp_config_server, appname)
- proxy_cfg_url = '%s&Action=GetObProxyConfig&ObRegionGroup=%s' % (ocp_config_server, appname)
+ if ocp_config_server[-1] == '?':
+ link_char = ''
+ elif ocp_config_server.find('?') == -1:
+ link_char = '?'
+ else:
+ link_char = '&'
+ cfg_url = '%s%sAction=ObRootServiceInfo&ObCluster=%s' % (ocp_config_server, link_char, appname)
+ proxy_cfg_url = '%s%sAction=GetObProxyConfig&ObRegionGroup=%s' % (ocp_config_server, link_char, appname)
# Command that clears the URL content for the cluster
- cleanup_config_url_content = '%s&Action=DeleteObRootServiceInfoByClusterName&ClusterName=%s' % (ocp_config_server, appname)
+ cleanup_config_url_content = '%s%sAction=DeleteObRootServiceInfoByClusterName&ClusterName=%s' % (ocp_config_server, link_char, appname)
# Command that register the cluster information to the Config URL
- register_to_config_url = '%s&Action=ObRootServiceRegister&ObCluster=%s&ObClusterId=%s' % (ocp_config_server, appname, cid)
+ register_to_config_url = '%s%sAction=ObRootServiceRegister&ObCluster=%s&ObClusterId=%s' % (ocp_config_server, link_char, appname, cid)
return cfg_url, cleanup_config_url_content, register_to_config_url
@@ -100,11 +108,21 @@ def start(plugin_context, *args, **kwargs):
try:
cfg_url = init_config_server(obconfig_url, appname, cluster_id, getattr(options, 'force_delete', False), stdio)
if not cfg_url:
- stdio.error(EC_OBSERVER_FAILED_TO_REGISTER_WITH_DETAILS.format(appname, obconfig_url))
- return
+ stdio.warn(EC_OBSERVER_FAILED_TO_REGISTER_WITH_DETAILS.format(appname, obconfig_url))
except:
- stdio.exception(EC_OBSERVER_FAILED_TO_REGISTER.format())
- return
+ stdio.warn(EC_OBSERVER_FAILED_TO_REGISTER.format())
+ elif 'ob-configserver' in cluster_config.depends and appname:
+ obc_cluster_config = cluster_config.get_depend_config('ob-configserver')
+ vip_address = obc_cluster_config.get('vip_address')
+ if vip_address:
+ obc_ip = vip_address
+ obc_port = obc_cluster_config.get('vip_port')
+ else:
+ server = cluster_config.get_depend_servers('ob-configserver')[0]
+ client = clients[server]
+ obc_ip = NetUtil.get_host_ip() if client.is_localhost() else server.ip
+ obc_port = obc_cluster_config.get('listen_port')
+ cfg_url = "http://{0}:{1}/services?Action=ObRootServiceInfo&ObCluster={2}".format(obc_ip, obc_port, appname)
stdio.start_loading('Start observer')
for server in cluster_config.original_servers:
@@ -121,6 +139,9 @@ def start(plugin_context, *args, **kwargs):
if not server_config.get('data_dir'):
server_config['data_dir'] = '%s/store' % home_path
+
+ if not server_config.get('local_ip') and not server_config.get('devname'):
+ server_config['local_ip'] = server.ip
if client.execute_command('ls %s/clog/tenant_1/' % server_config['data_dir']).stdout.strip():
need_bootstrap = False
@@ -147,10 +168,12 @@ def start(plugin_context, *args, **kwargs):
'appname': '-n',
'cluster_id': '-c',
'data_dir': '-d',
+ 'devname': '-i',
'syslog_level': '-l',
'ipv6': '-6',
'mode': '-m',
- 'scn': '-f'
+ 'scn': '-f',
+ 'local_ip': '-I'
})
not_cmd_opt = [
'home_path', 'obconfig_url', 'root_password', 'proxyro_password',
@@ -171,7 +194,6 @@ def start(plugin_context, *args, **kwargs):
if key in server_config:
value = get_value(key)
cmd.append('%s %s' % (not_opt_str[key], value))
- cmd.append('-I %s' % server.ip)
cmd.append('-o %s' % ','.join(opt_str))
else:
cmd.append('-p %s' % server_config['mysql_port'])
diff --git a/plugins/oceanbase/4.2.0.0/start_check.py b/plugins/oceanbase/4.2.0.0/start_check.py
index 324d641..a674d04 100644
--- a/plugins/oceanbase/4.2.0.0/start_check.py
+++ b/plugins/oceanbase/4.2.0.0/start_check.py
@@ -31,7 +31,7 @@
stdio = None
success = True
-
+production_mode = False
def get_port_socket_inode(client, port):
port = hex(port)[2:].zfill(4).upper()
@@ -76,7 +76,7 @@ def time_delta(client):
def get_mount_path(disk, _path):
_mount_path = '/'
for p in disk:
- if p in _path:
+ if p in _path and _path.startswith(p):
if len(p) > len(_mount_path):
_mount_path = p
return _mount_path
@@ -133,7 +133,7 @@ def get_disk_info(all_paths, client, stdio):
return disk_info
-def start_check(plugin_context, init_check_status=False, strict_check=False, work_dir_check=False, work_dir_empty_check=True, generate_configs={}, precheck=False, *args, **kwargs):
+def start_check(plugin_context, init_check_status=False, strict_check=False, work_dir_check=False, work_dir_empty_check=True, generate_configs={}, precheck=False, source_option='start', *args, **kwargs):
def check_pass(item):
status = check_status[server]
if status[item].status == err.CheckStatus.WAIT:
@@ -153,9 +153,19 @@ def alert(item, error, suggests=[]):
if strict_check:
success = False
check_fail(item, error, suggests)
- stdio.error(error)
+ print_with_suggests(error, suggests)
+ else:
+ stdio.warn(error)
+
+ def alert_strict(item, error, suggests=[]):
+ global success
+ if strict_check or production_mode:
+ success = False
+ check_fail(item, error, suggests)
+ print_with_suggests(error, suggests)
else:
stdio.warn(error)
+
def error(item, _error, suggests=[]):
global success
if plugin_context.dev_mode:
@@ -163,12 +173,16 @@ def error(item, _error, suggests=[]):
else:
success = False
check_fail(item, _error, suggests)
- stdio.error(_error)
+ print_with_suggests(_error, suggests)
+
def critical(item, error, suggests=[]):
global success
success = False
check_fail(item, error, suggests)
- stdio.error(error)
+ print_with_suggests(error, suggests)
+
+ def print_with_suggests(error, suggests=[]):
+ stdio.error('{}, {}'.format(error, suggests[0].msg if suggests else ''))
def system_memory_check():
server_memory_config = server_memory_stat['servers']
@@ -189,8 +203,21 @@ def system_memory_check():
success = True
check_status = {}
cluster_config = plugin_context.cluster_config
+ INF = float('inf')
plugin_context.set_variable('start_check_status', check_status)
+ kernel_check_items = [
+ {'check_item': 'vm.max_map_count', 'need': [327600, 1310720], 'recommend': 655360},
+ {'check_item': 'vm.min_free_kbytes', 'need': [32768, 2097152], 'recommend': 2097152},
+ {'check_item': 'vm.overcommit_memory', 'need': 0, 'recommend': 0},
+ {'check_item': 'fs.file-max', 'need': [6573688, INF], 'recommend': 6573688},
+ ]
+
+ kernel_check_status = {}
+ for kernel_param in kernel_check_items:
+ check_item = kernel_param['check_item']
+ kernel_check_status[check_item] = err.CheckStatus()
+
for server in cluster_config.servers:
check_status[server] = {
'port': err.CheckStatus(),
@@ -202,9 +229,10 @@ def system_memory_check():
'ntp': err.CheckStatus(),
'ocp meta db': err.CheckStatus()
}
+ check_status[server].update(kernel_check_status)
if work_dir_check:
check_status[server]['dir'] = err.CheckStatus()
-
+
if init_check_status:
return plugin_context.return_true(start_check_status=check_status)
@@ -215,7 +243,7 @@ def system_memory_check():
servers_memory = {}
servers_disk = {}
servers_clog_mount = {}
- servers_net_inferface = {}
+ servers_net_interface = {}
servers_dirs = {}
servers_check_dirs = {}
servers_log_disk_size = {}
@@ -223,10 +251,15 @@ def system_memory_check():
PRO_MEMORY_MIN = 16 << 30
PRO_POOL_MEM_MIN = 2147483648
START_NEED_MEMORY = 3 << 30
- global_generate_config = generate_configs.get('global', {})
- stdio.start_loading('Check before start observer')
+ global_generate_config = plugin_context.get_variable('global_generate_config', default=generate_configs.get('global', {}))
+ plugin_context.set_variable('global_generate_config', global_generate_config)
+ stdio.start_loading('Check before {} observer'.format(source_option))
need_bootstrap = True
+ parameter_check = True
+ port_check = True
+ kernel_check = True
+ is_running_opt = source_option in ['restart', 'upgrade']
for server in cluster_config.servers:
ip = server.ip
client = clients[server]
@@ -234,6 +267,7 @@ def system_memory_check():
servers_clients[ip] = client
server_config = cluster_config.get_server_conf_with_default(server)
home_path = server_config['home_path']
+ production_mode = server_config.get('production_mode', False)
if not precheck:
if need_bootstrap:
data_dir = server_config['data_dir'] if server_config.get('data_dir') else '%s/store' % home_path
@@ -244,7 +278,10 @@ def system_memory_check():
if remote_pid:
if client.execute_command('ls /proc/%s' % remote_pid):
stdio.verbose('%s is runnning, skip' % server)
- continue
+ work_dir_check = False
+ port_check = False
+ parameter_check = False
+ kernel_check = is_running_opt
if work_dir_check:
stdio.verbose('%s dir check' % server)
@@ -313,83 +350,84 @@ def system_memory_check():
servers_disk[ip] = {}
servers_port[ip] = {}
servers_clog_mount[ip] = {}
- servers_net_inferface[ip] = {}
+ servers_net_interface[ip] = {}
servers_memory[ip] = {'num': 0, 'percentage': 0, 'servers': {}}
memory = servers_memory[ip]
ports = servers_port[ip]
disk = servers_disk[ip]
clog_mount = servers_clog_mount[ip]
- inferfaces = servers_net_inferface[ip]
- stdio.verbose('%s port check' % server)
- for key in ['mysql_port', 'rpc_port']:
- port = int(server_config[key])
- if port in ports:
- critical(
- 'port',
- err.EC_CONFIG_CONFLICT_PORT.format(server1=server, port=port, server2=ports[port]['server'], key=ports[port]['key']),
- [err.SUG_PORT_CONFLICTS.format()]
- )
- continue
- ports[port] = {
- 'server': server,
- 'key': key
+ interfaces = servers_net_interface[ip]
+ if port_check:
+ stdio.verbose('%s port check' % server)
+ for key in ['mysql_port', 'rpc_port']:
+ port = int(server_config[key])
+ if port in ports:
+ critical(
+ 'port',
+ err.EC_CONFIG_CONFLICT_PORT.format(server1=server, port=port, server2=ports[port]['server'], key=ports[port]['key']),
+ [err.SUG_PORT_CONFLICTS.format()]
+ )
+ continue
+ ports[port] = {
+ 'server': server,
+ 'key': key
+ }
+ if get_port_socket_inode(client, port):
+ critical('port', err.EC_CONFLICT_PORT.format(server=ip, port=port), [err.SUG_USE_OTHER_PORT.format()])
+
+ if parameter_check:
+ servers_min_pool_memory[server] = __min_full_resource_pool_memory = server_config.get('__min_full_resource_pool_memory')
+ if production_mode and __min_full_resource_pool_memory < PRO_POOL_MEM_MIN:
+ error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key="__min_full_resource_pool_memory", limit=PRO_POOL_MEM_MIN), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
+
+ memory_limit = 0
+ percentage = 0
+ if server_config.get('memory_limit'):
+ memory_limit = parse_size(server_config['memory_limit'])
+ if production_mode and memory_limit < PRO_MEMORY_MIN:
+ error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key='memory_limit', limit=format_size(PRO_MEMORY_MIN)), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
+ memory['num'] += memory_limit
+ elif 'memory_limit_percentage' in server_config:
+ percentage = int(parse_size(server_config['memory_limit_percentage']))
+ memory['percentage'] += percentage
+ else:
+ percentage = 80
+ memory['percentage'] += percentage
+ memory['servers'][server] = {
+ 'num': memory_limit,
+ 'percentage': percentage,
+ 'system_memory': parse_size(server_config.get('system_memory', 0))
}
- if get_port_socket_inode(client, port):
- critical('port', err.EC_CONFLICT_PORT.format(server=ip, port=port), [err.SUG_USE_OTHER_PORT.format()])
-
- servers_min_pool_memory[server] = __min_full_resource_pool_memory = server_config.get('__min_full_resource_pool_memory')
- if server_config.get('production_mode') and __min_full_resource_pool_memory < PRO_POOL_MEM_MIN:
- error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key="__min_full_resource_pool_memory", limit=PRO_POOL_MEM_MIN), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
-
- memory_limit = 0
- percentage = 0
- if server_config.get('memory_limit'):
- memory_limit = parse_size(server_config['memory_limit'])
- if server_config.get('production_mode') and memory_limit < PRO_MEMORY_MIN:
- error('mem', err.EC_OBSERVER_PRODUCTION_MODE_LIMIT.format(server=server, key='memory_limit', limit=format_size(PRO_MEMORY_MIN)), [err.SUB_SET_NO_PRODUCTION_MODE.format()])
- memory['num'] += memory_limit
- elif 'memory_limit_percentage' in server_config:
- percentage = int(parse_size(server_config['memory_limit_percentage']))
- memory['percentage'] += percentage
- else:
- percentage = 80
- memory['percentage'] += percentage
- memory['servers'][server] = {
- 'num': memory_limit,
- 'percentage': percentage,
- 'system_memory': parse_size(server_config.get('system_memory', 0))
- }
-
- data_path = server_config['data_dir'] if server_config.get('data_dir') else os.path.join(server_config['home_path'], 'store')
- redo_dir = server_config['redo_dir'] if server_config.get('redo_dir') else data_path
- clog_dir = server_config['clog_dir'] if server_config.get('clog_dir') else os.path.join(redo_dir, 'clog')
- if not client.execute_command('ls %s/sstable/block_file' % data_path):
- disk[data_path] = {'server': server}
- clog_mount[clog_dir] = {'server': server}
- if 'datafile_size' in server_config and server_config['datafile_size'] and parse_size(server_config['datafile_size']):
- # if need is string, it means use datafile_size
- disk[data_path]['need'] = server_config['datafile_size']
- elif 'datafile_disk_percentage' in server_config and server_config['datafile_disk_percentage']:
- # if need is integer, it means use datafile_disk_percentage
- disk[data_path]['need'] = int(server_config['datafile_disk_percentage'])
-
- if 'log_disk_size' in server_config and server_config['log_disk_size'] and parse_size(server_config['log_disk_size']):
- # if need is string, it means use log_disk_size
- clog_mount[clog_dir]['need'] = server_config['log_disk_size']
- elif 'log_disk_percentage' in server_config and server_config['log_disk_percentage']:
- # if need is integer, it means use log_disk_percentage
- clog_mount[clog_dir]['need'] = int(server_config['log_disk_percentage'])
-
- devname = server_config.get('devname')
- if devname:
- if not client.execute_command("grep -e '^ *%s:' /proc/net/dev" % devname):
- suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
- suggest.auto_fix = 'devname' not in global_generate_config and 'devname' not in server_generate_config
- critical('net', err.EC_NO_SUCH_NET_DEVICE.format(server=server, devname=devname), suggests=[suggest])
- if devname not in inferfaces:
- inferfaces[devname] = []
- inferfaces[devname].append(ip)
+ data_path = server_config['data_dir'] if server_config.get('data_dir') else os.path.join(server_config['home_path'], 'store')
+ redo_dir = server_config['redo_dir'] if server_config.get('redo_dir') else data_path
+ clog_dir = server_config['clog_dir'] if server_config.get('clog_dir') else os.path.join(redo_dir, 'clog')
+ if not client.execute_command('ls %s/sstable/block_file' % data_path):
+ disk[data_path] = {'server': server}
+ clog_mount[clog_dir] = {'server': server}
+ if 'datafile_size' in server_config and server_config['datafile_size'] and parse_size(server_config['datafile_size']):
+ # if need is string, it means use datafile_size
+ disk[data_path]['need'] = server_config['datafile_size']
+ elif 'datafile_disk_percentage' in server_config and server_config['datafile_disk_percentage']:
+ # if need is integer, it means use datafile_disk_percentage
+ disk[data_path]['need'] = int(server_config['datafile_disk_percentage'])
+
+ if 'log_disk_size' in server_config and server_config['log_disk_size'] and parse_size(server_config['log_disk_size']):
+ # if need is string, it means use log_disk_size
+ clog_mount[clog_dir]['need'] = server_config['log_disk_size']
+ elif 'log_disk_percentage' in server_config and server_config['log_disk_percentage']:
+ # if need is integer, it means use log_disk_percentage
+ clog_mount[clog_dir]['need'] = int(server_config['log_disk_percentage'])
+
+ devname = server_config.get('devname')
+ if devname:
+ if not client.execute_command("grep -e '^ *%s:' /proc/net/dev" % devname):
+ suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
+ suggest.auto_fix = 'devname' not in global_generate_config and 'devname' not in server_generate_config
+ critical('net', err.EC_NO_SUCH_NET_DEVICE.format(server=server, devname=devname), suggests=[suggest])
+ if devname not in interfaces:
+ interfaces[devname] = []
+ interfaces[devname].append(ip)
ip_server_memory_info = {}
for ip in servers_disk:
@@ -425,10 +463,23 @@ def system_memory_check():
'name': 'nofile'
},
'max user processes': {
- 'need': lambda x: 4096,
- 'recd': lambda x: 4096 * x,
+ 'need': lambda x: 120000,
+ 'recd': lambda x: 655350,
'name': 'nproc'
},
+ 'core file size': {
+ 'need': lambda x: 0,
+ 'recd': lambda x: INF,
+ 'below_need_error': False,
+ 'below_recd_error_strict': False,
+ 'name': 'core'
+ },
+ 'stack size': {
+ 'need': lambda x: 1024,
+ 'recd': lambda x: INF,
+ 'below_recd_error_strict': False,
+ 'name': 'stack'
+ },
}
ulimits = {}
src_data = re.findall('\s?([a-zA-Z\s]+[a-zA-Z])\s+\([a-zA-Z\-,\s]+\)\s+([\d[a-zA-Z]+)', ret.stdout) if ret else []
@@ -440,19 +491,68 @@ def system_memory_check():
continue
if not value or not (value.strip().isdigit()):
for server in ip_servers:
- alert('ulimit', '(%s) failed to get %s' % (ip, key), [err.SUG_UNSUPPORT_OS.format()])
+ alert('ulimit', '(%s) failed to get %s' % (ip, key), suggests=[err.SUG_UNSUPPORT_OS.format()])
else:
value = int(value)
need = ulimits_min[key]['need'](server_num)
if need > value:
+ if (strict_check or production_mode) and ulimits_min[key].get('below_recd_error_strict', True) and value < ulimits_min[key]['recd'](server_num):
+ need = ulimits_min[key]['recd'](server_num)
+ need = need if need != INF else 'unlimited'
for server in ip_servers:
- critical('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ if ulimits_min[key].get('below_need_error', True):
+ critical('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ else:
+ alert('ulimit', err.EC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), suggests=[err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
else:
need = ulimits_min[key]['recd'](server_num)
if need > value:
+ need = need if need != INF else 'unlimited'
for server in ip_servers:
- alert('ulimit', err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), [err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ if ulimits_min[key].get('below_recd_error_strict', True):
+ alert('ulimit', err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value), suggests=[err.SUG_ULIMIT.format(name=ulimits_min[key]['name'], value=need, ip=ip)])
+ else:
+ stdio.warn(err.WC_ULIMIT_CHECK.format(server=ip, key=key, need=need, now=value))
+
+ if kernel_check:
+ # check kernel params
+ try:
+ cmd = 'sysctl -a'
+ ret = client.execute_command(cmd)
+ if not ret:
+ alert_strict('kernel', err.EC_FAILED_TO_GET_PARAM.format(key='kernel parameter ', cmd=cmd), [err.SUG_CONNECT_EXCEPT.format(ip=ip)])
+ continue
+ kernel_params = {}
+ kernel_param_src = ret.stdout.split('\n')
+ for kernel in kernel_param_src:
+ if not kernel:
+ continue
+ kernel = kernel.split('=')
+ kernel_params[kernel[0].strip()] = re.findall(r"[-+]?\d+", kernel[1])
+ for kernel_param in kernel_check_items:
+ check_item = kernel_param['check_item']
+ if check_item not in kernel_params:
+ continue
+ values = kernel_params[check_item]
+ needs = kernel_param['need']
+ recommends = kernel_param['recommend']
+ for i in range(len(values)):
+ # This case is not handling the value of 'default'. Additional handling is required for 'default' in the future.
+ item_value = int(values[i])
+ need = needs[i] if isinstance(needs, tuple) else needs
+ recommend = recommends[i] if isinstance(recommends, tuple) else recommends
+ if isinstance(need, list):
+ if item_value < need[0] or item_value > need[1]:
+ suggest = [err.SUG_SYSCTL.format(var=check_item, value=' '.join(str(i) for i in recommend) if isinstance(recommend, list) else recommend, ip=ip)]
+ need = 'within {}'.format(needs) if needs[-1] != INF else 'greater than {}'.format(needs[0])
+ now = '[{}]'.format(', '.join(values)) if len(values) > 1 else item_value
+ alert_strict(check_item, err.EC_PARAM_NOT_IN_NEED.format(ip=ip, check_item=check_item, need=need, now=now, recommend=recommends), suggest)
+ break
+ elif item_value != need:
+ alert_strict(check_item, err.EC_PARAM_NOT_IN_NEED.format(ip=ip, check_item=check_item, need=needs, recommend=recommend, now=item_value), [err.SUG_SYSCTL.format(var=check_item, value=recommend, ip=ip)])
+ except:
+ stdio.exception('')
# memory
ret = client.execute_command('cat /proc/meminfo')
@@ -634,31 +734,11 @@ def system_memory_check():
suggest.auto_fix = False
error('ocp meta db', err.EC_OCP_EXPRESS_META_DB_NOT_ENOUGH_LOG_DISK.format(), [suggest])
-
if success:
- for ip in servers_net_inferface:
- if servers_net_inferface[ip].get(None):
- devinfo = client.execute_command('cat /proc/net/dev').stdout
- interfaces = []
- for interface in re.findall('\n\s+(\w+):', devinfo):
- if interface != 'lo':
- interfaces.append(interface)
- if not interfaces:
- interfaces = ['lo']
- if len(interfaces) > 1:
- servers = ','.join(str(server) for server in servers_net_inferface[ip][None])
- suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
- for server in ip_servers:
- critical('net', err.EC_OBSERVER_MULTI_NET_DEVICE.format(ip=ip, server=servers), [suggest])
- else:
- servers_net_inferface[ip][interfaces[0]] = servers_net_inferface[ip][None]
- del servers_net_inferface[ip][None]
-
- if success:
- for ip in servers_net_inferface:
+ for ip in servers_net_interface:
client = servers_clients[ip]
- for devname in servers_net_inferface[ip]:
- if client.is_localhost() and devname != 'lo' or (not client.is_localhost() and devname == 'lo'):
+ for devname in servers_net_interface[ip]:
+ if client.is_localhost() and (devname != 'lo' and devname is not None) or (not client.is_localhost() and devname == 'lo'):
suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
suggest.auto_fix = client.is_localhost() and 'devname' not in global_generate_config and 'devname' not in server_generate_config
for server in ip_servers:
@@ -667,11 +747,15 @@ def system_memory_check():
for _ip in servers_clients:
if ip == _ip:
continue
- if not client.execute_command('ping -W 1 -c 1 -I %s %s' % (devname, _ip)):
+ ping_cmd = 'ping -W 1 -c 1 -I %s %s' % (devname, _ip) if devname is not None else 'ping -W 1 -c 1 %s' % _ip
+ if not client.execute_command(ping_cmd):
suggest = err.SUG_NO_SUCH_NET_DEVIC.format(ip=ip)
suggest.auto_fix = 'devname' not in global_generate_config and 'devname' not in server_generate_config
for server in ip_servers:
- critical('net', err.EC_OBSERVER_PING_FAILED.format(ip1=ip, devname=devname, ip2=_ip), [suggest])
+ if devname is not None:
+ critical('net', err.EC_OBSERVER_PING_FAILED.format(ip1=ip, devname=devname, ip2=_ip), [suggest])
+ else:
+ critical('net', err.EC_OBSERVER_PING_FAILED_WITH_NO_DEVNAME.format(ip1=ip, ip2=_ip), [suggest])
break
diff --git a/plugins/ocp-server/4.2.1/bootstrap.py b/plugins/ocp-server/4.2.1/bootstrap.py
new file mode 100644
index 0000000..3b88250
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/bootstrap.py
@@ -0,0 +1,25 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+
+def bootstrap(plugin_context, *args, **kwargs):
+ return True
diff --git a/plugins/ocp-server/4.2.1/connect.py b/plugins/ocp-server/4.2.1/connect.py
new file mode 100644
index 0000000..acfbc7a
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/connect.py
@@ -0,0 +1,152 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import json
+import requests
+from requests.auth import HTTPBasicAuth
+import time
+
+import _errno as err
+
+
+class OcpServerCursor(object):
+
+ class Response(object):
+
+ def __init__(self, code, content):
+ self.code = code
+ self.content = content
+
+ def __bool__(self):
+ return self.code == 200
+
+ def __init__(self, ip, port, username=None, password=None):
+ self.ip = ip
+ self.port = port
+ self.username = username
+ self.password = password
+ self.url_prefix = "http://{ip}:{port}/".format(ip=self.ip, port=self.port)
+ if self.username:
+ self.auth = HTTPBasicAuth(username=username, password=password)
+ else:
+ self.auth = None
+
+ def status(self, stdio=None):
+ ocp_status_ok = False
+ now = time.time()
+ check_wait_time = 300
+ count = 0
+ while time.time() - now < check_wait_time:
+ stdio.verbose("query ocp to check...")
+ count += 1
+ resp = self._request('GET', 'api/v2/time', stdio=stdio)
+ try:
+ if resp.code == 200 or count >= 10:
+ ocp_status_ok = True
+ break
+ except Exception:
+ stdio.verbose("ocp still not active")
+ time.sleep(3)
+ if ocp_status_ok:
+ stdio.verbose("check ocp server status ok")
+ return True
+ else:
+ stdio.verbose("OCP is still not working properly, check failed.")
+ return True
+
+ def info(self, stdio=None):
+ resp = self._request('GET', 'api/v2/info', stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def task_over_precheck(self, data, stdio=None):
+ resp = self._request('POST', 'api/v2/ob/clusters/takeOverPreCheck', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def compute_host_types(self, data, stdio=None):
+ resp = self._request('POST', 'api/v2/compute/hostTypes', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def profiles_credentials(self, data, stdio=None):
+ resp = self._request('POST', 'api/v2/profiles/me/credentials', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def task_over(self, data, stdio=None):
+ resp = self._request('POST', 'api/v2/ob/clusters/takeOver', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def _request(self, method, api, data=None, retry=5, stdio=None):
+ url = self.url_prefix + api
+ headers = {"Content-Type": "application/json"}
+ try:
+ if data is not None:
+ data = json.dumps(data)
+ stdio.verbose('send http request method: {}, url: {}, data: {}'.format(method, url, data))
+ resp = requests.request(method, url, data=data, verify=False, headers=headers, auth=self.auth)
+ return_code = resp.status_code
+ content = resp.content
+ except Exception as e:
+ if retry:
+ retry -= 1
+ return self._request(method=method, api=api, data=data, retry=retry, stdio=stdio)
+ stdio.exception("")
+ return_code = 500
+ content = str(e)
+ if return_code != 200:
+ stdio.verbose("request ocp-server failed: %s" % content)
+ try:
+ content = json.loads(content.decode())
+ except:
+ pass
+ return self.Response(code=return_code, content=content)
+
+
+def connect(plugin_context, target_server=None, *args, **kwargs):
+ cluster_config = plugin_context.cluster_config
+ stdio = plugin_context.stdio
+ if target_server:
+ servers = [target_server]
+ stdio.start_loading('Connect to ocp-server ({})'.format(target_server))
+ else:
+ servers = cluster_config.servers
+ stdio.start_loading('Connect to ocp-server')
+ cursors = {}
+ for server in servers:
+ config = cluster_config.get_server_conf(server)
+ username = 'admin'
+ password = config['admin_password']
+ stdio.verbose('connect ocp-server ({}:{} by user {})'.format(server.ip, config['port'], username))
+ cursor = OcpServerCursor(ip=server.ip, port=config['port'], username=username, password=password)
+ if cursor.status(stdio=stdio):
+ cursors[server] = cursor
+ if not cursors:
+ stdio.error(err.EC_FAIL_TO_CONNECT.format(component=cluster_config.name))
+ stdio.stop_loading('fail')
+ return plugin_context.return_false()
+
+ stdio.stop_loading('succeed')
+ return plugin_context.return_true(connect=cursors, cursor=cursors)
+
diff --git a/plugins/ocp-server/4.2.1/destroy.py b/plugins/ocp-server/4.2.1/destroy.py
new file mode 100644
index 0000000..9e4932d
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/destroy.py
@@ -0,0 +1,59 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import _errno as err
+
+global_ret = True
+
+
+def destroy(plugin_context, *args, **kwargs):
+ def clean(path):
+ client = clients[server]
+ ret = client.execute_command('rm -fr %s/*' % path, timeout=-1)
+ if not ret:
+ global global_ret
+ global_ret = False
+ stdio.warn(err.EC_CLEAN_PATH_FAILED.format(server=server, path=path))
+ else:
+ stdio.verbose('%s:%s cleaned' % (server, path))
+
+ cluster_config = plugin_context.cluster_config
+ clients = plugin_context.clients
+ stdio = plugin_context.stdio
+ global global_ret
+ stdio.start_loading('ocp-server work dir cleaning')
+ for server in cluster_config.servers:
+ server_config = cluster_config.get_server_conf(server)
+ stdio.verbose('%s work path cleaning', server)
+ home_path = server_config['home_path']
+ clean(home_path)
+
+ for key in ['log_dir', 'soft_dir']:
+ path = server_config.get(key)
+ if path:
+ clean(path)
+ if global_ret:
+ stdio.stop_loading('succeed')
+ return plugin_context.return_true()
+ else:
+ stdio.stop_loading('fail')
+ return plugin_context.return_false()
diff --git a/plugins/ocp-server/4.2.1/display.py b/plugins/ocp-server/4.2.1/display.py
new file mode 100644
index 0000000..5733c4e
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/display.py
@@ -0,0 +1,128 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+from tool import NetUtil
+from copy import deepcopy
+
+
+def get_missing_required_parameters(parameters):
+ results = []
+ for key in ["jdbc_url", "jdbc_password", "jdbc_username", "cluster_name", "ob_cluster_id", "root_sys_password",
+ "server_addresses", "agent_username", "agent_password"]:
+ if parameters.get(key) is None:
+ results.append(key)
+ return results
+
+
+def prepare_parameters(cluster_config, stdio):
+ # depends config
+ env = {}
+ depend_observer = False
+ depend_info = {}
+ ob_servers_conf = {}
+ root_servers = []
+ for comp in ["oceanbase", "oceanbase-ce"]:
+ ob_zones = {}
+ if comp in cluster_config.depends:
+ depend_observer = True
+ observer_globals = cluster_config.get_depend_config(comp)
+ ocp_meta_keys = [
+ "ocp_meta_tenant", "ocp_meta_db", "ocp_meta_username", "ocp_meta_password", "appname", "cluster_id", "root_password"
+ ]
+ for key in ocp_meta_keys:
+ value = observer_globals.get(key)
+ if value is not None:
+ depend_info[key] = value
+ ob_servers = cluster_config.get_depend_servers(comp)
+ for ob_server in ob_servers:
+ ob_servers_conf[ob_server] = ob_server_conf = cluster_config.get_depend_config(comp, ob_server)
+ if 'server_ip' not in depend_info:
+ depend_info['server_ip'] = ob_server.ip
+ depend_info['mysql_port'] = ob_server_conf['mysql_port']
+ zone = ob_server_conf['zone']
+ if zone not in ob_zones:
+ ob_zones[zone] = ob_server
+ root_servers = ob_zones.values()
+ break
+ for comp in ['obproxy', 'obproxy-ce']:
+ if comp in cluster_config.depends:
+ obproxy_servers = cluster_config.get_depend_servers(comp)
+ obproxy_server = obproxy_servers[0]
+ obproxy_server_config = cluster_config.get_depend_config(comp, obproxy_server)
+ depend_info['server_ip'] = obproxy_server.ip
+ depend_info['mysql_port'] = obproxy_server_config['listen_port']
+ break
+
+ for server in cluster_config.servers:
+ server_config = deepcopy(cluster_config.get_server_conf_with_default(server))
+ original_server_config = cluster_config.get_original_server_conf(server)
+ missed_keys = get_missing_required_parameters(original_server_config)
+ if missed_keys:
+ if 'jdbc_url' in missed_keys and depend_observer:
+ server_config['jdbc_url'] = 'jdbc:oceanbase://{}:{}/{}'.format(depend_info['server_ip'], depend_info['mysql_port'], depend_info['ocp_meta_db'])
+ if 'jdbc_username' in missed_keys and depend_observer:
+ server_config['jdbc_username'] = "{}@{}".format(depend_info['ocp_meta_username'],
+ depend_info.get('ocp_meta_tenant', {}).get("tenant_name"))
+ depends_key_maps = {
+ "jdbc_password": "ocp_meta_password",
+ "cluster_name": "appname",
+ "ob_cluster_id": "cluster_id",
+ "root_sys_password": "root_password",
+ "agent_username": "obagent_username",
+ "agent_password": "obagent_password",
+ "server_addresses": "server_addresses"
+ }
+ for key in depends_key_maps:
+ if key in missed_keys:
+ if depend_info.get(depends_key_maps[key]) is not None:
+ server_config[key] = depend_info[depends_key_maps[key]]
+ env[server] = server_config
+ return env
+
+
+def display(plugin_context, cursor, *args, **kwargs):
+ cluster_config = plugin_context.cluster_config
+ stdio = plugin_context.stdio
+ servers = cluster_config.servers
+ results = []
+ start_env = prepare_parameters(cluster_config, stdio)
+ for server in servers:
+ api_cursor = cursor.get(server)
+ server_config = start_env[server]
+ ip = server.ip
+ if ip == '127.0.0.1':
+ ip = NetUtil.get_host_ip()
+ url = 'http://{}:{}'.format(ip, api_cursor.port)
+ results.append({
+ 'ip': ip,
+ 'port': api_cursor.port,
+ 'user': "admin",
+ 'password': server_config['admin_password'],
+ 'url': url,
+ 'status': 'active' if api_cursor and api_cursor.status(stdio) else 'inactive'
+ })
+ stdio.print_list(results, ['url', 'username', 'password', 'status'], lambda x: [x['url'], 'admin', server_config['admin_password'], x['status']], title='ocp-server')
+ active_result = [r for r in results if r['status'] == 'active']
+ info_dict = active_result[0] if len(active_result) > 0 else None
+ if info_dict is not None:
+ info_dict['type'] = 'web'
+ return plugin_context.return_true(info=info_dict)
diff --git a/plugins/ocp-server/4.2.1/file_map.yaml b/plugins/ocp-server/4.2.1/file_map.yaml
new file mode 100644
index 0000000..c2e8fc3
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/file_map.yaml
@@ -0,0 +1,4 @@
+- src_path: ./home/admin/ocp-server/lib/ocp-server-ce-$version-$release_simple.jar
+ target_path: lib/ocp-server.jar
+ type: file
+ install_method: cp
diff --git a/plugins/ocp-server/4.2.1/generate_config.py b/plugins/ocp-server/4.2.1/generate_config.py
new file mode 100644
index 0000000..6cd6327
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/generate_config.py
@@ -0,0 +1,64 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+
+def generate_config(plugin_context, auto_depend=False, generate_config_mini=False, return_generate_keys=False, *args, **kwargs):
+ if return_generate_keys:
+ return plugin_context.return_true(generate_keys=['memory_size', 'log_dir', 'logging_file_max_history'])
+
+ cluster_config = plugin_context.cluster_config
+ stdio = plugin_context.stdio
+ depend_comps = [['obagent'], ['oceanbase', 'oceanbase-ce'], ['obproxy', 'obproxy-ce']]
+ generate_configs = {'global': {}}
+ plugin_context.set_variable('generate_configs', generate_configs)
+ stdio.start_loading('Generate ocp server configuration')
+ min_memory_size = '752M'
+
+ if auto_depend:
+ for comps in depend_comps:
+ for comp in comps:
+ if cluster_config.add_depend_component(comp):
+ break
+ global_config = cluster_config.get_global_conf()
+ if generate_config_mini:
+ if 'memory_size' not in global_config:
+ cluster_config.update_global_conf('memory_size', min_memory_size)
+
+ auto_set_memory = False
+ if 'memory_size' not in global_config:
+ for server in cluster_config.servers:
+ server_config = cluster_config.get_server_conf(server)
+ if 'memory_size' not in server_config:
+ auto_set_memory = True
+ if auto_set_memory:
+ observer_num = 0
+ for comp in ['oceanbase', 'oceanbase-ce']:
+ if comp in cluster_config.depends:
+ observer_num = len(cluster_config.get_depend_servers(comp))
+ if not observer_num:
+ stdio.warn('The component oceanbase/oceanbase-ce is not in the depends, the memory size cannot be calculated, and a fixed value of {} is used'.format(min_memory_size))
+ cluster_config.update_global_conf('memory_size', min_memory_size)
+ else:
+ cluster_config.update_global_conf('memory_size', '%dM' % (512 + (observer_num + 3) * 60))
+
+ stdio.stop_loading('succeed')
+ return plugin_context.return_true()
\ No newline at end of file
diff --git a/plugins/ocp-server/4.2.1/init.py b/plugins/ocp-server/4.2.1/init.py
new file mode 100644
index 0000000..1bcd5d3
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/init.py
@@ -0,0 +1,162 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import os.path
+from glob import glob
+
+import _errno as err
+from const import CONST_OBD_HOME
+
+
+OBD_INSTALL_PRE = os.environ.get('OBD_INSTALL_PRE', '/')
+
+
+def _clean(server, client, path, stdio=None):
+ ret = client.execute_command('rm -fr %s' % path, timeout=-1)
+ if not ret:
+ stdio.warn(err.EC_CLEAN_PATH_FAILED.format(server=server, path=path))
+ return False
+ else:
+ stdio.verbose('%s:%s cleaned' % (server, path))
+ return True
+
+
+def _ocp_lib(client, home_path, soft_dir='', stdio=None):
+ stdio.verbose('cp rpm & pos')
+ if soft_dir:
+ client.execute_command('mkdir -p -m 755 %s' % soft_dir, timeout=-1)
+ else:
+ client.execute_command('mkdir -p -m 755 %s/data/files/' % home_path, timeout=-1)
+ client.execute_command('mkdir -p -m 755 %s/ocp-server/lib/' % home_path, timeout=-1)
+ client.execute_command('mkdir -p -m 755 %s/logs/ocp/' % home_path, timeout=-1)
+
+ OBD_HOME = os.path.join(os.environ.get(CONST_OBD_HOME, os.getenv('HOME')), '.obd')
+ for rpm in glob(os.path.join(OBD_HOME, 'mirror/local/*ocp-agent-*.rpm')):
+ name = os.path.basename(rpm)
+ client.put_file(rpm, os.path.join(home_path, 'ocp-server/lib/', name))
+ if soft_dir:
+ client.put_file(rpm, os.path.join(soft_dir, name))
+
+
+def init(plugin_context, upgrade=False, *args, **kwargs):
+ cluster_config = plugin_context.cluster_config
+ clients = plugin_context.clients
+ stdio = plugin_context.stdio
+
+ global_ret = True
+ force = getattr(plugin_context.options, 'force', False)
+ clean = getattr(plugin_context.options, 'clean', False)
+ if upgrade:
+ for server in cluster_config.servers:
+ _ocp_lib(client, home_path, soft_dir, stdio)
+ plugin_context.return_true()
+ return
+
+ stdio.start_loading('Initializes ocp-server work home')
+ servers_dirs = {}
+ for server in cluster_config.servers:
+ server_config = cluster_config.get_server_conf(server)
+ client = clients[server]
+ ip = server.ip
+ if ip not in servers_dirs:
+ servers_dirs[ip] = {}
+ dirs = servers_dirs[ip]
+ home_path = server_config['home_path']
+ launch_user = server_config.get('launch_user', None)
+ soft_dir = server_config.get('soft_dir', '')
+ keys = ['home_path', 'log_dir', 'soft_dir']
+ for key in keys:
+ if key not in server_config:
+ continue
+ path = server_config[key]
+ if path in dirs:
+ global_ret = False
+ stdio.error(err.EC_CONFIG_CONFLICT_DIR.format(server1=server, path=path, server2=dirs[path]['server'], key=dirs[path]['key']))
+ continue
+ dirs[path] = {
+ 'server': server,
+ 'key': key,
+ }
+ need_clean = force
+ if clean and not force:
+ if client.execute_command(
+ 'bash -c \'if [[ "$(ls -d {0} 2>/dev/null)" != "" && ! -O {0} ]]; then exit 0; else exit 1; fi\''.format(
+ home_path)):
+ owner = client.execute_command("ls -ld %s | awk '{print $3}'" % home_path).stdout.strip()
+ global_ret = False
+ err_msg = ' {} is not empty, and the owner is {}'.format(home_path, owner)
+ stdio.error(err.EC_FAIL_TO_INIT_PATH.format(server=server, key='home path', msg=err_msg))
+ continue
+ need_clean = True
+ if need_clean:
+ port = server_config['port']
+ client.execute_command("pkill -9 -u `whoami` -f 'java -jar {home_path}/lib/ocp-server.jar --port {port}'".format(home_path=home_path, port=port))
+ if not _clean(server, client, home_path, stdio=stdio):
+ global_ret = False
+ continue
+ if client.execute_command('mkdir -p -m 755 %s' % home_path):
+ ret = client.execute_command('ls %s' % (home_path))
+ if not ret or ret.stdout.strip():
+ global_ret = False
+ stdio.error(err.EC_FAIL_TO_INIT_PATH.format(server=server, key='home path', msg=err.InitDirFailedErrorMessage.NOT_EMPTY.format(path=home_path)))
+ continue
+ else:
+ global_ret = False
+ stdio.error(err.EC_FAIL_TO_INIT_PATH.format(server=server, key='home path', msg=err.InitDirFailedErrorMessage.CREATE_FAILED.format(path=home_path)))
+ continue
+ if not client.execute_command("bash -c 'mkdir -p -m 755 %s/{run,bin,lib}'" % home_path):
+ global_ret = False
+ stdio.error(err.EC_FAIL_TO_INIT_PATH.format(server=server, key='home path', msg=err.InitDirFailedErrorMessage.PERMISSION_DENIED.format(path=home_path)))
+ if 'log_dir' in server_config:
+ log_dir = server_config['log_dir']
+ if client.execute_command('mkdir -p -m 755 %s' % log_dir):
+ ret = client.execute_command('ls %s' % log_dir)
+ if not ret or ret.stdout.strip():
+ global_ret = False
+ stdio.error(err.EC_FAIL_TO_INIT_PATH.format(server=server, key='log dir', msg=err.InitDirFailedErrorMessage.NOT_EMPTY.format(path=log_dir)))
+ continue
+ else:
+ global_ret = False
+ stdio.error(err.EC_FAIL_TO_INIT_PATH.format(server=server, key='log dir', msg=err.InitDirFailedErrorMessage.CREATE_FAILED.format(path=log_dir)))
+ continue
+ else:
+ log_dir = os.path.join(home_path, 'log')
+ if not client.execute_command('mkdir -p -m 755 %s' % log_dir):
+ global_ret = False
+ stdio.error(err.EC_FAIL_TO_INIT_PATH.format(server=server, key='log dir', msg=err.InitDirFailedErrorMessage.NOT_EMPTY.format(path=log_dir)))
+ continue
+ link_path = os.path.join(home_path, 'log')
+ client.execute_command("if [ ! '%s' -ef '%s' ]; then ln -sf %s %s; fi" % (log_dir, link_path, log_dir, link_path))
+ _ocp_lib(client, home_path, soft_dir, stdio)
+ if launch_user:
+ res_home = client.execute_command("sudo chown -R %s %s" % (launch_user, home_path))
+ res_log = client.execute_command("sudo chown -R %s %s" % (launch_user, log_dir))
+ res_soft = client.execute_command("sudo chown -R %s %s" % (launch_user, soft_dir))
+ if not (res_home and res_log and res_soft):
+ global_ret = False
+ if global_ret:
+ stdio.stop_loading('succeed')
+ plugin_context.return_true()
+ else:
+ stdio.stop_loading('fail')
+ plugin_context.return_false()
+
diff --git a/plugins/ocp-server/4.2.1/parameter.yaml b/plugins/ocp-server/4.2.1/parameter.yaml
new file mode 100644
index 0000000..bf7816e
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/parameter.yaml
@@ -0,0 +1,363 @@
+- name: home_path
+ name_local: 工作目录
+ require: true
+ essential: true
+ type: STRING
+ need_redeploy: true
+ description_en: the directory for the work data
+ description_local: OCP server server工作目录
+- name: port
+ name_local: 端口
+ require: true
+ essential: true
+ type: INT
+ default: 8180
+ need_restart: true
+ description_en: the port of ocp server.
+ description_local: OCP server使用的端口
+- name: log_dir
+ name_local: 日志目录
+ type: STRING
+ require: false
+ essential: true
+ need_redeploy: true
+ description_en: The directory for logging file. The default value is $home_path/log.
+ description_local: OCP server server日志目录, 默认为工作目录下的log
+- name: soft_dir
+ name_local: 软件包目录
+ type: STRING
+ require: false
+ need_redeploy: true
+ description_en: The directory for software. The default value is ~/ocp-server/lib.
+ description_local: OCP server 软件包安装目录, ~/ocp-server/lib
+- name: java_bin
+ name_local: java路径
+ type: STRING
+ require: true
+ essential: true
+ default: java
+ need_restart: true
+ description_en: The path of java binary
+ description_local: OCP server 使用的java可执行文件的路径
+- name: memory_size
+ name_local: 进程内存
+ require: false
+ essential: true
+ type: CAPACITY
+ min_value: 512M
+ need_restart: true
+ description_en: the memroy size of ocp server server. Please enter an capacity, such as 2G
+ description_local: OCP server server进程内存大小。请输入带容量带单位的整数,如2G
+- name: admin_password
+ name_local: ocp login password
+ type: STRING
+ require: true
+ essential: true
+ default: aaAA11__
+ need_restart: true
+ description_local: ocp 登录密码
+ description_en: When logining ocp, use it
+- name: logging_file_max_size
+ name_local: 单个日志文件大小
+ type: STRING
+ require: false
+ essential: true
+ default: 100MB
+ need_restart: true
+ description_local: 单个日志文件大小
+ description_en: When logging_file_name is configured, specify the log file size through this configuration
+- name: logging_file_total_size_cap
+ name_local: 日志总大小
+ type: STRING
+ require: true
+ essential: true
+ default: 1GB
+ need_restart: true
+ description_local: 日志文件总大小
+ description_en: When logging_file_name is configured, specify the total log file size through this configuration
+- name: jdbc_url
+ require: false
+ type: STRING
+ need_redeploy: true
+ description_en: The jdbc connection url for ocp meta db
+ description_local: OCP使用的元数据库的jdbc连接串
+- name: jdbc_username
+ require: false
+ type: STRING
+ need_redeploy: true
+ description_en: The username name for ocp meta dbx
+ description_local: OCP使用的元数据库的用户名
+- name: jdbc_password
+ require: false
+ type: STRING
+ default:
+ need_redeploy: true
+ description_en: The password name for ocp meta db
+ description_local: OCP使用的元数据库的密码
+- name: system_password
+ require: true
+ type: STRING
+ default: oceanbase
+ need_restart: true
+ description_en: The password name for ocp server
+ description_local: OCP server中system用户的密码
+- name: launch_user
+ require: false
+ type: STRING
+ default: ''
+ need_redeploy: true
+ description_en: Start user of OCP process
+ description_local: OCP进程的启动用户
+- name: 'session_timeout'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '登陆会话/Session超时的时间,默认是30m,最少60s。如果不加后缀单位,则默认是秒。重启生效。'
+ description_en: 'Session timeout interval, default is 30m, at least 60s. If the suffix unit is not added, the default is seconds. Restart OCP to take effect.'
+- name: 'login_encrypt_enabled'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '登录信息是否开启加密传输,默认开启,重启生效'
+ description_en: 'Switch to enable encrypted transmission of login information, enabled by default. Restart OCP to take effect.'
+- name: 'login_encrypt_public_key'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '加密登录信息的公钥,建议部署后修改此配置,修改后重启生效'
+ description_en: 'The public key for login encryption, It is recommended to modify this configuration after deployment. Restart OCP to take effect.'
+- name: 'login_encrypt_private_key'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '加密登录信息的私钥,建议部署后修改此配置,修改后重启生效'
+ description_en: 'The private key for encryption. It is recommended to modify this configuration after deployment. Restart OCP to take effect.'
+- name: 'enable_basic_auth'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '是否启用Basic Auth登陆模式,通常供程序和SDK等客户端场景使用,默认true。本配置与ocp.iam.auth可同时开启。重启生效。'
+ description_en: 'Whether to enable Basic Authentication, usually for client programs and SDKs to call server APIs. The default is true. This configuration and ocp.iam.auth can be enabled together. Restart OCP to take effect.'
+- name: 'enable_csrf'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '是否启用CSRF跨站点请求伪造安全保护,通常基于网页登陆的方式都推荐要启用,默认true。重启生效。'
+ description_en: 'Whether to enable CSRF cross-site request forgery security protection. It is recommended to enable it, the default is true. Restart OCP to take effect.'
+- name: 'vault_key'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '密码箱加密密钥'
+ description_en: 'vault secret key'
+- name: 'druid_name'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: 'metadb的druid连接池名称。重启生效'
+ description_en: 'metadb druid connection pool name. Restart to take effect'
+- name: 'druid_init_size'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '初始化时建立物理连接的个数。重启生效'
+ description_en: 'The number of physical connections established during initialization. Restart to take effect'
+- name: 'druid_min_idle'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '最小连接池数量。重启生效'
+ description_en: 'Minimum number of connections. Restart to take effect'
+- name: 'druid_max_active'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '最大连接池数量。重启生效'
+ description_en: 'The maximum number of connections. Restart to take effect'
+- name: 'druid_test_while_idle'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测。重启生效'
+ description_en: 'It is recommended to set it to true, which will not affect performance and ensure safety. Detect when applying for connection. Restart to take effect'
+- name: 'druid_validation_query'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '用来检测连接是否有效的sql。重启生效'
+ description_en: 'SQL used to detect whether the connection is valid. Restart to take effect'
+- name: 'druid_max_wait'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '获取连接时最大等待时间,单位毫秒。重启生效'
+ description_en: 'Maximum waiting time when getting a connection, in milliseconds. Restart to take effect'
+- name: 'druid_keep_alive'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis(缺省值1800秒),则会执行keepAlive操作。重启生效'
+ description_en: 'For connections within the number of minIdle in the connection pool, if the idle time exceeds minEvictableIdleTimeMillis (the default value is 1800 seconds), the keepAlive operation will be performed. Restart to take effect'
+- name: 'logging_pattern_console'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '用于控制台输出的日志格式'
+ description_en: 'Log format for console output'
+- name: 'logging_pattern_file'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '用于文件输出的日志格式'
+ description_en: 'Log format used for file output'
+- name: 'logging_file_clean_when_start'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '启动时删除压缩的日志文件'
+ description_en: 'Clean the archive log files on startup'
+- name: 'logging_file_max_history'
+ name_local: 日志保留天数
+ type: INT
+ require: false
+ essential: true
+ need_restart: true
+ min_value: 1
+ max_value: 2147483647
+ description_local: '最多保留的归档日志文件的天数,默认不限制'
+ description_en: 'When logging.file is configured, set the maximum of retention days the log archive log files to keep. The default value is unlimited'
+- name: 'default_timezone'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '系统默认时区,若不设置则使用 system default time zone,重启生效'
+ description_en: 'System default time zone, if not set, use system default time zone, restart to take effect'
+- name: 'default_lang'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '系统默认语言(非前端语言设置),若不设置则使用 zh-CN,重启生效'
+ description_en: 'System default language (non-front-end language setting), if not set, use zh-CN, restart to take effect'
+- name: 'ocp.idempotent.client-token.expire.time'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '幂等请求token的缓存过期时间,默认14d'
+ description_en: 'Expire time of idempotent client token, the default is 14d'
+- name: 'exporter_inactive_threshold'
+ type: 'INT'
+ require: false
+ need_restart: true
+ description_local: 'exporter地址判定为失效的连续不可用时间(秒)'
+ description_en: 'consecutive failure time of exporter address that is regarded as inactive (seconds)'
+- name: 'ocp.monitor.host.exporters'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '主机监控exporter'
+ description_en: 'exporters of ocp host'
+- name: 'ocp.monitor.ob.exporters'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: 'OB监控exporter'
+ description_en: 'exporters of ob'
+- name: 'monitor_collect_interval'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '秒级别监控采集间隔,默认 1s,支持配置选项是 1s, 5s, 10s, 15s'
+ description_en: 'The parameter determines the second-level monitoring and collection interval. The supported configuration options are 1s, 5s, 10s, 15s. Default value is 1s'
+- name: 'montior_retention_days'
+ type: 'STRING'
+ require: false
+ need_restart: true
+ description_local: '监控数据保存天数,key 是监控数据的表名,value 是保存的天数,修改后重启生效.'
+ description_en: 'Retention days for monitor data, key is table name for monitor data, value is the retention days. Restart to take effect.'
+- name: ocp_meta_tenant
+ require: true
+ type: DICT
+ default:
+ tenant_name: meta_tenant
+ max_cpu: 1
+ memory_size: 2147483648
+ need_redeploy: true
+ description_en: The tenant specifications for ocp meta db
+ description_local: ocp server的元数据库使用的租户规格
+- name: ocp_monitor_tenant
+ require: false
+ type: DICT
+ default:
+ tenant_name: monitor_tenant
+ max_cpu: 1
+ memory_size: 2147483648
+ need_redeploy: true
+ description_en: The tenant specifications for ocp monitor
+ description_local: ocp server的监控租户使用的租户规格
+- name: ocp_meta_tenant_log_disk_size
+ name_local: OCP server元数据库租户日志磁盘大小
+ essential: true
+ require: false
+ type: CAPACITY_MB
+ default: 2048M
+ need_redeploy: true
+ description_en: The tenant log disk size for ocp meta db
+ description_local: ocp server的元数据库使用的租户日志磁盘大小
+- name: ocp_monitor_tenant_log_disk_size
+ name_local: OCP server监控租户日志磁盘大小
+ essential: true
+ require: false
+ type: CAPACITY_MB
+ default: 2048M
+ need_redeploy: true
+ description_en: The tenant log disk size for ocp monitor db
+ description_local: ocp server的监控租户使用的租户日志磁盘大小
+- name: ocp_meta_username
+ require: false
+ type: STRING
+ default: meta
+ need_redeploy: true
+ description_en: The user name for ocp meta db
+ description_local: ocp server的元数据库使用的用户名
+- name: ocp_meta_password
+ require: true
+ type: STRING
+ default: oceanbase
+ need_redeploy: true
+ description_en: The password for ocp meta db
+ description_local: ocp server的元数据库使用的密码
+- name: ocp_meta_db
+ require: false
+ type: STRING
+ default: meta_database
+ need_redeploy: true
+ description_en: The database name for ocp meta db
+ description_local: ocp server的元数据库使用的数据库名
+- name: ocp_monitor_username
+ require: false
+ type: STRING
+ default: monitor_user
+ description_en: The username for obagent monitor user
+ description_local: obagent 监控用户的用户名
+- name: ocp_monitor_password
+ require: false
+ type: STRING
+ default: oceanbase
+ need_redeploy: true
+ description_en: The password for obagent monitor password
+ description_local: obagent 监控用户的密码
+- name: ocp_monitor_db
+ require: false
+ type: STRING
+ default: monitor_database
+ need_redeploy: true
+ description_en: The database name for ocp meta db
+ description_local: ocp server的监控数据库使用的数据库名
+- name: ocp_site_url
+ require: false
+ type: STRING
+ default: ''
+ need_restart: true
+ need_redeploy: true
+ description_en: The url for ocp server
+ description_local: ocp server的连接串
diff --git a/plugins/ocp-server/4.2.1/reload.py b/plugins/ocp-server/4.2.1/reload.py
new file mode 100644
index 0000000..83a8411
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/reload.py
@@ -0,0 +1,25 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+
+def reload(plugin_context, *args, **kwargs):
+ return plugin_context.return_true()
diff --git a/plugins/ocp-server/4.2.1/restart.py b/plugins/ocp-server/4.2.1/restart.py
new file mode 100644
index 0000000..9de2d40
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/restart.py
@@ -0,0 +1,157 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import os
+
+
+class Restart(object):
+
+ def __init__(self, plugin_context, local_home_path, start_check_plugin, start_plugin, reload_plugin, stop_plugin, connect_plugin,
+ display_plugin, repository, new_cluster_config=None, new_clients=None, bootstrap_plugin=None,
+ repository_dir_map=None):
+ self.local_home_path = local_home_path
+
+ self.namespace = plugin_context.namespace
+ self.namespaces = plugin_context.namespaces
+ self.deploy_name = plugin_context.deploy_name
+ self.repositories = plugin_context.repositories
+ self.plugin_name = plugin_context.plugin_name
+
+ self.components = plugin_context.components
+ self.clients = plugin_context.clients
+ self.cluster_config = plugin_context.cluster_config
+ self.cmds = plugin_context.cmds
+ self.options = plugin_context.options
+ self.dev_mode = plugin_context.dev_mode
+ self.stdio = plugin_context.stdio
+
+ self.plugin_context = plugin_context
+ self.repository = repository
+ self.start_check_plugin = start_check_plugin
+ self.start_plugin = start_plugin
+ self.reload_plugin = reload_plugin
+ self.connect_plugin = connect_plugin
+ self.display_plugin = display_plugin
+ self.bootstrap_plugin = bootstrap_plugin
+ self.stop_plugin = stop_plugin
+ self.new_clients = new_clients
+ self.new_cluster_config = new_cluster_config
+ self.sub_io = self.stdio.sub_io()
+ self.dbs = None
+ self.cursors = None
+ self.repository_dir_map = repository_dir_map
+
+ def call_plugin(self, plugin, **kwargs):
+ args = {
+ 'namespace': self.namespace,
+ 'namespaces': self.namespaces,
+ 'deploy_name': self.deploy_name,
+ 'cluster_config': self.cluster_config,
+ 'repositories': self.repositories,
+ 'repository': self.repository,
+ 'components': self.components,
+ 'clients': self.clients,
+ 'cmd': self.cmds,
+ 'options': self.options,
+ 'stdio': self.sub_io
+ }
+ args.update(kwargs)
+
+ self.stdio.verbose('Call %s for %s' % (plugin, self.repository))
+ return plugin(**args)
+
+ def connect(self, cluster_config):
+ if self.cursors is None:
+ self.sub_io.start_loading('Connect to ocp express')
+ ret = self.call_plugin(self.connect_plugin, cluster_config=cluster_config)
+ if not ret:
+ self.sub_io.stop_loading('fail')
+ return False
+ self.sub_io.stop_loading('succeed')
+ self.cursors = ret.get_return('cursor')
+ self.dbs = ret.get_return('connect')
+ return True
+
+ def dir_read_check(self, client, path):
+ if not client.execute_command('cd %s' % path):
+ dirpath, name = os.path.split(path)
+ return self.dir_read_check(client, dirpath) and client.execute_command('sudo chmod +1 %s' % path)
+ return True
+
+ def restart(self):
+ clients = self.clients
+ if not self.call_plugin(self.stop_plugin, clients=clients):
+ self.stdio.stop_loading('stop_loading', 'fail')
+ return False
+
+ if self.new_clients:
+ self.stdio.verbose('use new clients')
+ for server in self.cluster_config.servers:
+ new_client = self.new_clients[server]
+ server_config = self.cluster_config.get_server_conf(server)
+ for key in ['home_path', 'data_dir']:
+ if key in server_config:
+ path = server_config[key]
+ if not new_client.execute_command('sudo chown -R %s: %s' % (new_client.config.username, path)):
+ self.stdio.stop_loading('stop_loading', 'fail')
+ return False
+ self.dir_read_check(new_client, path)
+ clients = self.new_clients
+
+ cluster_config = self.new_cluster_config if self.new_cluster_config else self.cluster_config
+
+ need_bootstrap = self.bootstrap_plugin is not None
+ if not self.call_plugin(self.start_check_plugin, clients=clients, cluster_config=cluster_config):
+ self.stdio.stop_loading('stop_loading', 'fail')
+ return False
+ if not self.call_plugin(self.start_plugin, clients=clients, cluster_config=cluster_config, local_home_path=self.local_home_path, need_bootstrap=need_bootstrap, repository_dir_map=self.repository_dir_map):
+ self.rollback()
+ self.stdio.stop_loading('stop_loading', 'fail')
+ return False
+
+ if self.connect(cluster_config):
+ if self.bootstrap_plugin:
+ self.call_plugin(self.bootstrap_plugin, cursor=self.cursors)
+ return self.call_plugin(self.display_plugin, cursor=self.cursors)
+ return False
+
+ def rollback(self):
+ if self.new_clients:
+ cluster_config = self.new_cluster_config if self.new_cluster_config else self.cluster_config
+ self.call_plugin(self.stop_plugin, clients=self.new_clients, cluster_config=cluster_config)
+ for server in self.cluster_config.servers:
+ client = self.clients[server]
+ new_client = self.new_clients[server]
+ server_config = self.cluster_config.get_server_conf(server)
+ home_path = server_config['home_path']
+ new_client.execute_command('sudo chown -R %s: %s' % (client.config.username, home_path))
+
+
+def restart(plugin_context, local_home_path, start_check_plugin, start_plugin, reload_plugin, stop_plugin, connect_plugin, display_plugin,
+ new_cluster_config=None, new_clients=None, rollback=False, bootstrap_plugin=None, repository_dir_map=None, *args,
+ **kwargs):
+ repository = kwargs.get('repository')
+ task = Restart(plugin_context=plugin_context, local_home_path=local_home_path, start_check_plugin=start_check_plugin, start_plugin=start_plugin, bootstrap_plugin=bootstrap_plugin, reload_plugin=reload_plugin, stop_plugin=stop_plugin, connect_plugin=connect_plugin,
+ display_plugin=display_plugin, repository=repository, new_cluster_config=new_cluster_config, new_clients=new_clients, repository_dir_map=repository_dir_map)
+ call = task.rollback if rollback else task.restart
+ if call():
+ plugin_context.return_true()
diff --git a/plugins/ocp-server/4.2.1/start.py b/plugins/ocp-server/4.2.1/start.py
new file mode 100644
index 0000000..cdd6284
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/start.py
@@ -0,0 +1,715 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import copy
+import os
+import re
+import time
+from glob import glob
+from copy import deepcopy
+from const import CONST_OBD_HOME
+
+from tool import Cursor, FileUtil, YamlLoader
+from _errno import EC_OBSERVER_CAN_NOT_MIGRATE_IN
+
+
+OBD_INSTALL_PRE = os.environ.get('OBD_INSTALL_PRE', '/')
+
+PRI_KEY_FILE = '.ocp-server'
+PUB_KEY_FILE = '.ocp-server.pub'
+
+
+EXCLUDE_KEYS = [
+ "home_path", "cluster_name", "ob_cluster_id", "admin_password", "memory_xms", "memory_xmx", "ocpCPU",
+ "root_sys_password", "server_addresses", "system_password", "memory_size", 'jdbc_url', 'jdbc_username',
+ 'jdbc_password', "ocp_meta_tenant", "ocp_meta_tenant_log_disk_size", "ocp_meta_username", "ocp_meta_password",
+
+ ]
+
+CONFIG_MAPPER = {
+ "port": "server.port",
+ "session_timeout": "server.servlet.session.timeout",
+ "login_encrypt_enabled": "ocp.login.encryption.enabled",
+ "login_encrypt_public_key": "ocp.login.encryption.public-key",
+ "login_encrypt_private_key": "ocp.login.encryption.private-key",
+ "enable_basic_auth": "ocp.iam.auth.basic.enabled",
+ "enable_csrf": "ocp.iam.csrf.enabled",
+ "vault_key": "ocp.express.vault.secret-key",
+ "druid_name": "spring.datasource.druid.name",
+ "druid_init_size": "spring.datasource.druid.initial-size",
+ "druid_min_idle": "spring.datasource.druid.min-idle",
+ "druid_max_active": "spring.datasource.druid.max-active",
+ "druid_test_while_idle": "spring.datasource.druid.test-while-idle",
+ "druid_validation_query": "spring.datasource.druid.validation-query",
+ "druid_max_wait": "spring.datasource.druid.max-wait",
+ "druid_keep_alive": "spring.datasource.druid.keep-alive",
+ "logging_pattern_console": "logging.pattern.console",
+ "logging_pattern_file": "logging.pattern.file",
+ "logging_file_name": "logging.file.name",
+ "logging_file_max_size": "logging.file.max-size",
+ "logging_file_total_size_cap": "logging.file.total-size-cap",
+ "logging_file_clean_when_start": "logging.file.clean-history-on-start",
+ "logging_file_max_history": "logging.file.max-history",
+ "logging_level_web": "logging.level.web",
+ "default_timezone": "ocp.system.default.timezone",
+ "default_lang": "ocp.system.default.language",
+ "obsdk_sql_query_limit": "ocp.monitor.collect.obsdk.sql-query-row-limit",
+ "exporter_inactive_threshold": "ocp.monitor.exporter.inactive.threshold.seconds",
+ "monitor_collect_interval": "ocp.metric.collect.interval.second",
+ "montior_retention_days": "ocp.monitor.data.retention-days",
+ "obsdk_cache_size": "obsdk.connector.holder.capacity",
+ "obsdk_max_idle": "obsdk.connector.max-idle.seconds",
+ "obsdk_cleanup_period": "obsdk.connector.cleanup.period.seconds",
+ "obsdk_print_sql": "obsdk.print.sql",
+ "obsdk_slow_query_threshold": "obsdk.slow.query.threshold.millis",
+ "obsdk_init_timeout": "obsdk.connector.init.timeout.millis",
+ "obsdk_init_core_size": "obsdk.connector.init.executor.thread-count",
+ "obsdk_global_timeout": "obsdk.operation.global.timeout.millis",
+ "obsdk_connect_timeout": "obsdk.socket.connect.timeout.millis",
+ "obsdk_read_timeout": "obsdk.socket.read.timeout.millis"
+ }
+
+
+def parse_size(size):
+ _bytes = 0
+ if isinstance(size, str):
+ size = size.strip()
+ if not isinstance(size, str) or size.isdigit():
+ _bytes = int(size)
+ else:
+ units = {"B": 1, "K": 1 << 10, "M": 1 << 20, "G": 1 << 30, "T": 1 << 40}
+ match = re.match(r'^(0|[1-9][0-9]*)\s*([B,K,M,G,T])$', size.upper())
+ _bytes = int(match.group(1)) * units[match.group(2)]
+ return _bytes
+
+
+def format_size(size, precision=1):
+ units = ['B', 'K', 'M', 'G', 'T', 'P']
+ idx = 0
+ if precision:
+ div = 1024.0
+ format = '%.' + str(precision) + 'f%s'
+ else:
+ div = 1024
+ format = '%d%s'
+ while idx < 5 and size >= 1024:
+ size /= 1024.0
+ idx += 1
+ return format % (size, units[idx])
+
+
+def exec_sql_in_tenant(sql, cursor, tenant, password, mode, retries=10, args=None):
+ user = 'SYS' if mode == 'oracle' else 'root'
+ tenant_cursor = cursor.new_cursor(tenant=tenant, user=user, password=password)
+ while not tenant_cursor and retries:
+ retries -= 1
+ time.sleep(2)
+ tenant_cursor = cursor.new_cursor(tenant=tenant, user=user, password=password)
+ return tenant_cursor.execute(sql, args)
+
+
+def get_port_socket_inode(client, port, stdio):
+ port = hex(port)[2:].zfill(4).upper()
+ cmd = "bash -c 'cat /proc/net/{tcp*,udp*}' | awk -F' ' '{print $2,$10}' | grep '00000000:%s' | awk -F' ' '{print $2}' | uniq" % port
+ res = client.execute_command(cmd)
+ if not res or not res.stdout.strip():
+ return False
+ stdio.verbose(res.stdout)
+ return res.stdout.strip().split('\n')
+
+
+def confirm_port(client, pid, port, stdio, launch_user=None):
+ socket_inodes = get_port_socket_inode(client, port, stdio)
+ if not socket_inodes:
+ return False
+ if launch_user:
+ ret = client.execute_command("""su - %s -c 'ls -l /proc/%s/fd/ |grep -E "socket:\[(%s)\]"'""" % (launch_user, pid, '|'.join(socket_inodes)))
+ else:
+ ret = client.execute_command("ls -l /proc/%s/fd/ |grep -E 'socket:\[(%s)\]'" % (pid, '|'.join(socket_inodes)))
+ if ret and ret.stdout.strip():
+ return True
+ return False
+
+
+def get_missing_required_parameters(parameters):
+ results = []
+ for key in ["jdbc_url", "jdbc_password", "jdbc_username", "cluster_name", "ob_cluster_id", "root_sys_password",
+ "server_addresses", "agent_username", "agent_password"]:
+ if parameters.get(key) is None:
+ results.append(key)
+ return results
+
+
+def get_ocp_depend_config(cluster_config, stdio):
+ # depends config
+ env = {}
+ depend_observer = False
+ depend_info = {}
+ ob_servers_conf = {}
+ root_servers = []
+ for comp in ["oceanbase", "oceanbase-ce"]:
+ ob_zones = {}
+ if comp in cluster_config.depends:
+ depend_observer = True
+ ob_servers = cluster_config.get_depend_servers(comp)
+ for ob_server in ob_servers:
+ ob_servers_conf[ob_server] = ob_server_conf = cluster_config.get_depend_config(comp, ob_server)
+ if 'server_ip' not in depend_info:
+ depend_info['server_ip'] = ob_server.ip
+ depend_info['mysql_port'] = ob_server_conf['mysql_port']
+ zone = ob_server_conf['zone']
+ if zone not in ob_zones:
+ ob_zones[zone] = ob_server
+ break
+ for comp in ['obproxy', 'obproxy-ce']:
+ if comp in cluster_config.depends:
+ obproxy_servers = cluster_config.get_depend_servers(comp)
+ obproxy_server = obproxy_servers[0]
+ obproxy_server_config = cluster_config.get_depend_config(comp, obproxy_server)
+ depend_info['server_ip'] = obproxy_server.ip
+ depend_info['mysql_port'] = obproxy_server_config['listen_port']
+ break
+
+ for server in cluster_config.servers:
+ server_config = deepcopy(cluster_config.get_server_conf_with_default(server))
+ original_server_config = cluster_config.get_original_server_conf(server)
+ missed_keys = get_missing_required_parameters(original_server_config)
+ if missed_keys:
+ if 'jdbc_url' in missed_keys and depend_observer:
+ server_config['jdbc_url'] = 'jdbc:oceanbase://{}:{}/{}'.format(depend_info['server_ip'],
+ depend_info['mysql_port'],
+ server_config['ocp_meta_db'])
+ server_config['jdbc_username'] = '%s@%s' % (
+ server_config['ocp_meta_username'], server_config['ocp_meta_tenant']['tenant_name'])
+ server_config['jdbc_password'] = server_config['ocp_meta_password']
+ server_config['root_password'] = depend_info.get('root_password', '')
+ env[server] = server_config
+ return env
+
+
+def start(plugin_context, start_env=None, cursor='', sys_cursor1='', without_ocp_parameter=False, *args, **kwargs):
+ def get_option(key, default=''):
+ value = getattr(options, key, default)
+ if not value:
+ value = default
+ return value
+
+ def get_parsed_option(key, default=''):
+ value = get_option(key=key, default=default)
+ if value is None:
+ return value
+ try:
+ parsed_value = parse_size(value)
+ except:
+ stdio.exception("")
+ raise Exception("Invalid option {}: {}".format(key, value))
+ return parsed_value
+
+ def error(*arg, **kwargs):
+ stdio.error(*arg, **kwargs)
+ stdio.stop_loading('fail')
+
+ def create_tenant(cursor, name, max_cpu, memory_size, db_username, tenant_password, database, stdio):
+ mode = get_option('mode', 'mysql').lower()
+ if not mode in ['mysql', 'oracle']:
+ error('No such tenant mode: %s.\n--mode must be `mysql` or `oracle`' % mode)
+ return plugin_context.return_false()
+
+ unit_name = '%s_unit' % name
+ sql = 'select * from oceanbase.DBA_OB_UNIT_CONFIGS order by name'
+ res = cursor.fetchall(sql)
+ if res is False:
+ return plugin_context.return_false()
+ for row in res:
+ if str(row['NAME']) == unit_name:
+ unit_name += '1'
+
+ pool_name = '%s_pool' % name
+
+ sql = "select * from oceanbase.DBA_OB_TENANTS where TENANT_NAME = %s"
+ tenant_exists = False
+ res = cursor.fetchone(sql, [name])
+ if res:
+ if create_if_not_exists:
+ tenant_exists = True
+ else:
+ error('Tenant %s already exists' % name)
+ return plugin_context.return_false()
+ elif res is False:
+ return plugin_context.return_false()
+ if not tenant_exists:
+ stdio.start_loading('Create tenant %s' % name)
+ zone_list = get_option('zone_list', set())
+ MIN_CPU = 1
+ MIN_MEMORY = 1073741824
+ MIN_LOG_DISK_SIZE = 2147483648
+ MIN_IOPS = 1024
+ zone_obs_num = {}
+ sql = "select zone, count(*) num from oceanbase.__all_server where status = 'active' group by zone"
+ res = cursor.fetchall(sql, raise_exception=True)
+
+ for row in res:
+ zone_obs_num[str(row['zone'])] = row['num']
+
+ if not zone_list:
+ zone_list = zone_obs_num.keys()
+ if isinstance(zone_list, str):
+ zones = zone_list.replace(';', ',').split(',')
+ else:
+ zones = zone_list
+ zone_list = "('%s')" % "','".join(zones)
+
+ min_unit_num = min(zone_obs_num.items(), key=lambda x: x[1])[1]
+ unit_num = get_option('unit_num', min_unit_num)
+ if unit_num > min_unit_num:
+ error('resource pool unit num is bigger than zone server count')
+ return plugin_context.return_false()
+
+ sql = "select count(*) num from oceanbase.__all_server where status = 'active' and start_service_time > 0"
+ count = 30
+ while count:
+ num = cursor.fetchone(sql)
+ if num is False:
+ error('%s : execute failed' % sql)
+ return plugin_context.return_false()
+ num = num['num']
+ if num >= unit_num:
+ break
+ count -= 1
+ time.sleep(1)
+ if count == 0:
+ stdio.error(EC_OBSERVER_CAN_NOT_MIGRATE_IN)
+ return plugin_context.return_false()
+
+ sql = "SELECT * FROM oceanbase.GV$OB_SERVERS where zone in %s" % zone_list
+ servers_stats = cursor.fetchall(sql, raise_exception=True)
+ cpu_available = servers_stats[0]['CPU_CAPACITY_MAX'] - servers_stats[0]['CPU_ASSIGNED_MAX']
+ mem_available = servers_stats[0]['MEM_CAPACITY'] - servers_stats[0]['MEM_ASSIGNED']
+ disk_available = servers_stats[0]['DATA_DISK_CAPACITY'] - servers_stats[0]['DATA_DISK_IN_USE']
+ log_disk_available = servers_stats[0]['LOG_DISK_CAPACITY'] - servers_stats[0]['LOG_DISK_ASSIGNED']
+ for servers_stat in servers_stats[1:]:
+ cpu_available = min(servers_stat['CPU_CAPACITY_MAX'] - servers_stat['CPU_ASSIGNED_MAX'], cpu_available)
+ mem_available = min(servers_stat['MEM_CAPACITY'] - servers_stat['MEM_ASSIGNED'], mem_available)
+ disk_available = min(servers_stat['DATA_DISK_CAPACITY'] - servers_stat['DATA_DISK_IN_USE'],
+ disk_available)
+ log_disk_available = min(servers_stat['LOG_DISK_CAPACITY'] - servers_stat['LOG_DISK_ASSIGNED'],
+ log_disk_available)
+
+ if cpu_available < MIN_CPU:
+ error('%s: resource not enough: cpu count less than %s' % (zone_list, MIN_CPU))
+ return plugin_context.return_false()
+ if mem_available < MIN_MEMORY:
+ error('%s: resource not enough: memory less than %s' % (zone_list, format_size(MIN_MEMORY)))
+ return plugin_context.return_false()
+ if log_disk_available < MIN_LOG_DISK_SIZE:
+ error(
+ '%s: resource not enough: log disk size less than %s' % (zone_list, format_size(MIN_MEMORY)))
+ return plugin_context.return_false()
+
+ # cpu options
+ min_cpu = get_option('min_cpu', max_cpu)
+ if cpu_available < max_cpu:
+ error('resource not enough: cpu (Avail: %s, Need: %s)' % (cpu_available, max_cpu))
+ return plugin_context.return_false()
+ if max_cpu < min_cpu:
+ error('min_cpu must less then max_cpu')
+ return plugin_context.return_false()
+
+ # memory options
+ log_disk_size = get_parsed_option('log_disk_size', None)
+
+ if memory_size is None:
+ memory_size = mem_available
+ if log_disk_size is None:
+ log_disk_size = log_disk_available
+
+ if mem_available < memory_size:
+ error('resource not enough: memory (Avail: %s, Need: %s)' % (
+ format_size(mem_available), format_size(memory_size)))
+ return plugin_context.return_false()
+
+ # log disk size options
+ if log_disk_size is not None and log_disk_available < log_disk_size:
+ error('resource not enough: log disk space (Avail: %s, Need: %s)' % (
+ format_size(disk_available), format_size(log_disk_size)))
+ return plugin_context.return_false()
+
+ # iops options
+ max_iops = get_option('max_iops', None)
+ min_iops = get_option('min_iops', None)
+ iops_weight = get_option('iops_weight', None)
+ if max_iops is not None and max_iops < MIN_IOPS:
+ error('max_iops must greater than %d' % MIN_IOPS)
+ return plugin_context.return_false()
+ if max_iops is not None and min_iops is not None and max_iops < min_iops:
+ error('min_iops must less then max_iops')
+ return plugin_context.return_false()
+
+ zone_num = len(zones)
+ charset = get_option('charset', '')
+ collate = get_option('collate', '')
+ replica_num = get_option('replica_num', zone_num)
+ logonly_replica_num = get_option('logonly_replica_num', 0)
+ tablegroup = get_option('tablegroup', '')
+ primary_zone = get_option('primary_zone', 'RANDOM')
+ locality = get_option('locality', '')
+ variables = get_option('variables', "ob_tcp_invited_nodes='%'")
+
+ if replica_num == 0:
+ replica_num = zone_num
+ elif replica_num > zone_num:
+ error('replica_num cannot be greater than zone num (%s)' % zone_num)
+ return plugin_context.return_false()
+ if not primary_zone:
+ primary_zone = 'RANDOM'
+ if logonly_replica_num > replica_num:
+ error('logonly_replica_num cannot be greater than replica_num (%s)' % replica_num)
+ return plugin_context.return_false()
+
+ # create resource unit
+ sql = "create resource unit %s max_cpu %.1f, memory_size %d" % (unit_name, max_cpu, memory_size)
+ if min_cpu is not None:
+ sql += ', min_cpu %.1f' % min_cpu
+ if max_iops is not None:
+ sql += ', max_iops %d' % max_iops
+ if min_iops is not None:
+ sql += ', min_iops %d' % min_iops
+ if iops_weight is not None:
+ sql += ', iops_weight %d' % iops_weight
+ if log_disk_size is not None:
+ sql += ', log_disk_size %d' % log_disk_size
+
+ res = cursor.execute(sql, raise_exception=True)
+
+ # create resource pool
+ sql = "create resource pool %s unit='%s', unit_num=%d, zone_list=%s" % (
+ pool_name, unit_name, unit_num, zone_list)
+ res = cursor.execute(sql, raise_exception=True)
+
+ # create tenant
+ sql = "create tenant %s replica_num=%d,zone_list=%s,primary_zone='%s',resource_pool_list=('%s')"
+ sql = sql % (name, replica_num, zone_list, primary_zone, pool_name)
+ if charset:
+ sql += ", charset = '%s'" % charset
+ if collate:
+ sql += ", collate = '%s'" % collate
+ if logonly_replica_num:
+ sql += ", logonly_replica_num = %d" % logonly_replica_num
+ if tablegroup:
+ sql += ", default tablegroup ='%s'" % tablegroup
+ if locality:
+ sql += ", locality = '%s'" % locality
+
+ set_mode = "ob_compatibility_mode = '%s'" % mode
+ if variables:
+ sql += "set %s, %s" % (variables, set_mode)
+ else:
+ sql += "set %s" % set_mode
+ res = cursor.execute(sql, raise_exception=True)
+ stdio.stop_loading('succeed')
+ if database:
+ sql = 'create database if not exists {}'.format(database)
+ if not exec_sql_in_tenant(sql=sql, cursor=cursor, tenant=name, password=tenant_password if tenant_exists else '', mode=mode) and not create_if_not_exists:
+ stdio.error('failed to create database {}'.format(database))
+ return plugin_context.return_false()
+
+ db_password = tenant_password
+ if db_username:
+ sql = "create user if not exists '{username}' IDENTIFIED BY %s".format(username=db_username)
+ sargs = [db_password]
+ if exec_sql_in_tenant(sql=sql, cursor=cursor, tenant=name, password=tenant_password if tenant_exists else '', mode=mode, args=sargs):
+ sql = "grant all on *.* to '{username}' WITH GRANT OPTION".format(username=db_username)
+ if exec_sql_in_tenant(sql=sql, cursor=cursor, tenant=name, password=tenant_password if tenant_exists else '', mode=mode):
+ sql = 'alter user root IDENTIFIED BY %s'
+ if exec_sql_in_tenant(sql=sql, cursor=cursor, tenant=name, password=tenant_password if tenant_exists else '', mode=mode, args=sargs):
+ return True
+ stdio.error('failed to create user {}'.format(db_username))
+ return plugin_context.return_false()
+ return True
+
+ def _ocp_lib(client, home_path, soft_dir='', stdio=None):
+ stdio.verbose('cp rpm & pos')
+ OBD_HOME = os.path.join(os.environ.get(CONST_OBD_HOME, os.getenv('HOME')), '.obd')
+ for rpm in glob(os.path.join(OBD_HOME, 'mirror/local/*ocp-agent-*.rpm')):
+ name = os.path.basename(rpm)
+ client.put_file(rpm, os.path.join(home_path, 'ocp-server/lib/', name))
+ if soft_dir:
+ client.put_file(rpm, os.path.join(soft_dir, name))
+
+ def start_cluster(times=0):
+ jdbc_host = jdbc_port = jdbc_url = jdbc_username = jdbc_password = jdbc_public_key = cursor = monitor_user = monitor_tenant = monitor_memory_size = monitor_max_cpu = monitor_password = monitor_db = meta_password = ''
+ for server in cluster_config.servers:
+ server_config = start_env[server]
+ # check meta db connect before start
+ jdbc_url = server_config['jdbc_url']
+ jdbc_username = server_config['jdbc_username']
+ jdbc_password = server_config['jdbc_password']
+ root_password = server_config.get('root_password', '')
+ cursor = get_option('metadb_cursor', '')
+ cursor = kwargs.get('metadb_cursor', '') if cursor == '' else cursor
+ matched = re.match(r"^jdbc:\S+://(\S+?)(|:\d+)/(\S+)", jdbc_url)
+ stdio.verbose('metadb connect check')
+ if matched:
+ jdbc_host = matched.group(1)
+ jdbc_port = matched.group(2)[1:]
+ jdbc_database = matched.group(3)
+ password = root_password if root_password else jdbc_password
+ retries = 10
+ while not cursor and retries and get_option("skip_create_tenant", 'False') == 'False':
+ try:
+ retries -= 1
+ time.sleep(2)
+ cursor = Cursor(ip=jdbc_host, port=jdbc_port, user='root@sys', password=password, stdio=stdio)
+ except:
+ pass
+
+ global_config = cluster_config.get_global_conf()
+ site_url = global_config.get('ocp_site_url', '')
+ soft_dir = global_config.get('soft_dir', '')
+ meta_user = global_config.get('ocp_meta_username', 'meta_user')
+ meta_tenant = global_config.get('ocp_meta_tenant')['tenant_name']
+ meta_max_cpu = global_config['ocp_meta_tenant'].get('max_cpu', 2)
+ meta_memory_size = global_config['ocp_meta_tenant'].get('memory_size', '2G')
+ meta_password = global_config.get('ocp_meta_password', '')
+ meta_db = global_config.get('ocp_meta_db', 'meta_database')
+ if global_config.get('ocp_monitor_tenant'):
+ monitor_user = global_config.get('ocp_monitor_username', 'monitor_user')
+ monitor_tenant = global_config['ocp_monitor_tenant']['tenant_name']
+ monitor_max_cpu = global_config['ocp_monitor_tenant'].get('max_cpu', 2)
+ monitor_memory_size = global_config['ocp_monitor_tenant'].get('memory_size', '4G')
+ monitor_password = global_config.get('ocp_monitor_password', '')
+ monitor_db = global_config.get('ocp_monitor_db', 'monitor_database')
+ if get_option("skip_create_tenant", 'False') == 'False':
+ if not times:
+ if not create_tenant(cursor, meta_tenant, meta_max_cpu, parse_size(meta_memory_size), meta_user,
+ meta_password,
+ meta_db, stdio):
+ return plugin_context.return_false()
+ meta_cursor = Cursor(jdbc_host, jdbc_port, meta_user, meta_tenant, meta_password, stdio)
+ plugin_context.set_variable('meta_cursor', meta_cursor)
+
+ if not times:
+ if not create_tenant(cursor, monitor_tenant, monitor_max_cpu, parse_size(monitor_memory_size),
+ monitor_user,
+ monitor_password, monitor_db, stdio):
+ return plugin_context.return_false()
+ if meta_tenant not in jdbc_username:
+ jdbc_username = meta_user + '@' + meta_tenant
+ jdbc_url = jdbc_url.rsplit('/', 1)[0] + '/' + meta_db
+ jdbc_password = meta_password
+
+ server_pid = {}
+ success = True
+ stdio.start_loading("Start ocp-server")
+ for server in cluster_config.servers:
+ client = clients[server]
+ server_config = start_env[server]
+ home_path = server_config['home_path']
+ launch_user = server_config.get('launch_user', None)
+ _ocp_lib(client, home_path, soft_dir, stdio)
+ system_password = server_config["system_password"]
+ port = server_config['port']
+ if not site_url:
+ site_url = 'http://{}:{}'.format(server.ip, port)
+ pid_path = os.path.join(home_path, 'run/ocp-server.pid')
+ pids = client.execute_command("cat %s" % pid_path).stdout.strip()
+ if not times and pids and all([client.execute_command('ls /proc/%s' % pid) for pid in pids.split('\n')]):
+ server_pid[server] = pids
+ continue
+
+ memory_xms = server_config.get('memory_xms', None)
+ memory_xmx = server_config.get('memory_xmx', None)
+ if memory_xms or memory_xmx:
+ jvm_memory_option = "-Xms{0} -Xmx{1}".format(memory_xms, memory_xmx)
+ else:
+ memory_size = server_config.get('memory_size', '1G')
+ jvm_memory_option = "-Xms{0} -Xmx{0}".format(format_size(parse_size(memory_size), 0).lower())
+ extra_options = {
+ "ocp.iam.encrypted-system-password": system_password
+ }
+ extra_options_str = ' '.join(["-D{}={}".format(k, v) for k, v in extra_options.items()])
+ java_bin = server_config['java_bin']
+ cmd = f'{java_bin} -jar {jvm_memory_option} {extra_options_str} {home_path}/lib/ocp-server.jar --bootstrap'
+ jar_cmd = copy.deepcopy(cmd)
+ if "log_dir" not in server_config:
+ log_dir = os.path.join(home_path, 'log')
+ else:
+ log_dir = server_config["log_dir"]
+ server_config["logging_file_name"] = os.path.join(log_dir, 'ocp-server.log')
+ jdbc_password_to_str = jdbc_password.replace("'", """'"'"'""")
+ environ_variable = 'export JDBC_URL=%s; export JDBC_USERNAME=%s;' \
+ 'export JDBC_PASSWORD=\'%s\'; ' \
+ 'export JDBC_PUBLIC_KEY=%s;' % (
+ jdbc_url, jdbc_username, jdbc_password_to_str, jdbc_public_key
+ )
+ if not times:
+ cmd += ' --progress-log={}'.format(os.path.join(log_dir, 'bootstrap.log'))
+ for key in server_config:
+ if key == 'jdbc_url' and monitor_user:
+ monitor_password = monitor_password.replace("'", """'"'"'""")
+ cmd += f' --with-property=ocp.monitordb.host:{jdbc_host}' \
+ f' --with-property=ocp.monitordb.username:{monitor_user + "@" + monitor_tenant}' \
+ f' --with-property=ocp.monitordb.port:{jdbc_port}' \
+ f' --with-property=ocp.monitordb.password:\'{monitor_password}\'' \
+ f' --with-property=ocp.monitordb.database:{monitor_db}'
+ if key not in EXCLUDE_KEYS and key in CONFIG_MAPPER:
+ cmd += ' --with-property={}:{}'.format(CONFIG_MAPPER[key], server_config[key])
+ cmd += ' --with-property=ocp.site.url:{}'.format(site_url)
+ # set connection mode to direct to avoid obclient issue
+ cmd += ' --with-property=obsdk.ob.connection.mode:direct'
+ if server_config['admin_password'] != '********':
+ admin_password = server_config['admin_password'].replace("'", """'"'"'""")
+ environ_variable += "export OCP_INITIAL_ADMIN_PASSWORD=\'%s\';" % admin_password
+ cmd += f' --with-property=ocp.file.local.built-in.dir:{home_path}/ocp-server/lib'
+ cmd += f' --with-property=ocp.log.download.tmp.dir:{home_path}/logs/ocp'
+ cmd += ' --with-property=ocp.file.local.dir:{}'.format(soft_dir) if soft_dir else f' --with-property=ocp.file.local.dir:{home_path}/data/files'
+ real_cmd = environ_variable + cmd
+ execute_cmd = "cd {}; {} > /dev/null 2>&1 &".format(home_path, real_cmd)
+ if server_config.get('launch_user'):
+ cmd_file = os.path.join(home_path, 'cmd.sh')
+ client.write_file(execute_cmd, cmd_file)
+ execute_cmd = "chmod +x {0};sudo chown -R {1} {0};sudo su - {1} -c '{0}' &".format(cmd_file, server_config['launch_user'])
+ client.execute_command(execute_cmd, timeout=3600)
+ time.sleep(10)
+ ret = client.execute_command(
+ "ps -aux | grep -F '%s' | grep -v grep | awk '{print $2}' " % jar_cmd)
+ if ret:
+ server_pid[server] = ret.stdout.strip()
+ if not server_pid[server]:
+ stdio.error("failed to start {} ocp server".format(server))
+ success = False
+ continue
+ client.write_file(server_pid[server], os.path.join(home_path, 'run/ocp-server.pid'))
+ if times == 0 and len(cluster_config.servers) > 1:
+ break
+
+ if success:
+ stdio.stop_loading('succeed')
+ else:
+ stdio.stop_loading('fail')
+ return plugin_context.return_false()
+
+ stdio.start_loading("ocp-server program health check")
+ failed = []
+ servers = server_pid.keys()
+ count = 40
+ while servers and count:
+ count -= 1
+ tmp_servers = []
+ for server in servers:
+ server_config = cluster_config.get_server_conf(server)
+ client = clients[server]
+ stdio.verbose('%s program health check' % server)
+ pids_stat = {}
+ launch_user = server_config.get('launch_user', None)
+ if server in server_pid:
+ for pid in server_pid[server].split("\n"):
+ pids_stat[pid] = None
+ cmd = 'ls /proc/{}'.format(pid) if not launch_user else 'sudo ls /proc/{}'.format(pid)
+ if not client.execute_command(cmd):
+ pids_stat[pid] = False
+ continue
+ confirm = confirm_port(client, pid, int(server_config["port"]), stdio, launch_user)
+ if confirm:
+ pids_stat[pid] = True
+ break
+ if any(pids_stat.values()):
+ for pid in pids_stat:
+ if pids_stat[pid]:
+ stdio.verbose('%s ocp-server[pid: %s] started', server, pid)
+ continue
+ if all([stat is False for stat in pids_stat.values()]):
+ failed.append('failed to start {} ocp-server'.format(server))
+ elif count:
+ tmp_servers.append(server)
+ stdio.verbose('failed to start %s ocp-server, remaining retries: %d' % (server, count))
+ else:
+ failed.append('failed to start {} ocp-server'.format(server))
+ servers = tmp_servers
+ if servers and count:
+ time.sleep(15)
+
+ if failed:
+ stdio.stop_loading('failed')
+ for msg in failed:
+ stdio.error(msg)
+ return plugin_context.return_false()
+ else:
+ stdio.stop_loading('succeed')
+ plugin_context.return_true(need_bootstrap=False)
+ return True
+
+ def stop_cluster():
+ success = True
+ for server in cluster_config.servers:
+ server_config = cluster_config.get_server_conf(server)
+ client = clients[server]
+ home_path = server_config['home_path']
+ pid_path = os.path.join(home_path, 'run/ocp-server.pid')
+ launch_user = server_config.get('launch_user', None)
+ cmd = 'cat {}'.format(pid_path)
+ pids = client.execute_command('sudo ' + cmd if launch_user else cmd).stdout.strip().split('\n')
+ success = False
+ for pid in pids:
+ cmd = 'ls /proc/{}'.format(pid)
+ if pid and client.execute_command('sudo ' + cmd if launch_user else cmd):
+ cmd = 'ls /proc/{}/fd'.format(pid)
+ if client.execute_command('sudo ' + cmd if launch_user else cmd):
+ stdio.verbose('{} ocp-server[pid: {}] stopping...'.format(server, pid))
+ cmd = 'kill -9 {}'.format(pid)
+ client.execute_command('sudo ' + cmd if launch_user else cmd)
+ return True
+ else:
+ stdio.verbose('failed to stop ocp-server[pid:{}] in {}, permission deny'.format(pid, server))
+ success = False
+ else:
+ stdio.verbose('{} ocp-server is not running'.format(server))
+ if not success:
+ stdio.stop_loading('fail')
+ return plugin_context.return_true()
+
+ cluster_config = plugin_context.cluster_config
+ options = plugin_context.options
+ clients = plugin_context.clients
+ stdio = plugin_context.stdio
+ create_if_not_exists = get_option('create_if_not_exists', True)
+ sys_cursor = kwargs.get('sys_cursor')
+ global tenant_cursor
+ tenant_cursor = None
+
+ if not start_env:
+ start_env = get_ocp_depend_config(cluster_config, stdio)
+ if not start_env:
+ return plugin_context.return_false()
+
+ if not without_ocp_parameter and not get_option('without_ocp_parameter', ''):
+ if not start_cluster():
+ stdio.error('start ocp-server failed')
+ return plugin_context.return_false()
+ if not stop_cluster():
+ stdio.error('stop ocp-server failed')
+ return plugin_context.return_false()
+ if not start_cluster(1):
+ stdio.error('start ocp-server failed')
+ return plugin_context.return_false()
+ time.sleep(10)
+ return plugin_context.return_true()
diff --git a/plugins/ocp-server/4.2.1/start_check.py b/plugins/ocp-server/4.2.1/start_check.py
new file mode 100644
index 0000000..184c242
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/start_check.py
@@ -0,0 +1,590 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import re
+import os
+import time
+import datetime
+
+from copy import deepcopy
+from _rpm import Version
+import _errno as err
+from ssh import SshConfig, SshClient
+from tool import Cursor
+from const import CONST_OBD_HOME
+
+success = True
+
+
+def get_missing_required_parameters(parameters):
+ results = []
+ for key in ["jdbc_url", "jdbc_password", "jdbc_username"]:
+ if parameters.get(key) is None:
+ results.append(key)
+ return results
+
+
+def get_port_socket_inode(client, port):
+ port = hex(port)[2:].zfill(4).upper()
+ cmd = "bash -c 'cat /proc/net/{udp*,tcp*}' | awk -F' ' '{print $2,$10}' | grep '00000000:%s' | awk -F' ' '{print $2}' | uniq" % port
+ res = client.execute_command(cmd)
+ if not res or not res.stdout.strip():
+ return False
+ return res.stdout.strip().split('\n')
+
+
+def password_check(passwd):
+ pattern = r"((?=(.*\d){2,})(?=(.*[a-z]){2,})(?=(.*[A-Z]){2,})(?=(.*[~!@#%^&*_\-+=|(){}\[\]:;,.?/]){2,})[0-9a-zA-Z~!@#%^&*_\-+=|(){}\[\]:;,.?/]{8,32})"
+ if re.match(pattern, passwd):
+ return True
+
+
+def parse_size(size):
+ _bytes = 0
+ if not isinstance(size, str) or size.isdigit():
+ _bytes = int(size)
+ else:
+ units = {"B": 1, "K": 1<<10, "M": 1<<20, "G": 1<<30, "T": 1<<40}
+ match = re.match(r'(0|[1-9][0-9]*)\s*([B,K,M,G,T])', size.upper())
+ _bytes = int(match.group(1)) * units[match.group(2)]
+ return _bytes
+
+
+def format_size(size, precision=1):
+ units = ['B', 'K', 'M', 'G']
+ units_num = len(units) - 1
+ idx = 0
+ if precision:
+ div = 1024.0
+ format = '%.' + str(precision) + 'f%s'
+ limit = 1024
+ else:
+ div = 1024
+ limit = 1024
+ format = '%d%s'
+ while idx < units_num and size >= limit:
+ size /= div
+ idx += 1
+ return format % (size, units[idx])
+
+
+def get_mount_path(disk, _path):
+ _mount_path = '/'
+ for p in disk:
+ if p in _path:
+ if len(p) > len(_mount_path):
+ _mount_path = p
+ return _mount_path
+
+
+def get_disk_info_by_path(path, client, stdio):
+ disk_info = {}
+ ret = client.execute_command('df --block-size=1024 {}'.format(path))
+ if ret:
+ for total, used, avail, puse, path in re.findall(r'(\d+)\s+(\d+)\s+(\d+)\s+(\d+%)\s+(.+)', ret.stdout):
+ disk_info[path] = {'total': int(total) << 10, 'avail': int(avail) << 10, 'need': 0}
+ stdio.verbose('get disk info for path {}, total: {} avail: {}'.format(path, disk_info[path]['total'], disk_info[path]['avail']))
+ return disk_info
+
+
+def get_disk_info(all_paths, client, stdio):
+ overview_ret = True
+ disk_info = get_disk_info_by_path('', client, stdio)
+ if not disk_info:
+ overview_ret = False
+ disk_info = get_disk_info_by_path('/', client, stdio)
+ if not disk_info:
+ disk_info['/'] = {'total': 0, 'avail': 0, 'need': 0}
+ all_path_success = {}
+ for path in all_paths:
+ all_path_success[path] = False
+ cur_path = path
+ while cur_path not in disk_info:
+ disk_info_for_current_path = get_disk_info_by_path(cur_path, client, stdio)
+ if disk_info_for_current_path:
+ disk_info.update(disk_info_for_current_path)
+ all_path_success[path] = True
+ break
+ else:
+ cur_path = os.path.dirname(cur_path)
+ if overview_ret or all(all_path_success.values()):
+ return disk_info
+
+
+def get_ocp_depend_config(cluster_config, stdio):
+ # depends config
+ env = {}
+ depend_observer = False
+ depend_info = {}
+ ob_servers_conf = {}
+ for comp in ["oceanbase", "oceanbase-ce"]:
+ ob_zones = {}
+ if comp in cluster_config.depends:
+ depend_observer = True
+ ob_servers = cluster_config.get_depend_servers(comp)
+ for ob_server in ob_servers:
+ ob_servers_conf[ob_server] = ob_server_conf = cluster_config.get_depend_config(comp, ob_server)
+ if 'server_ip' not in depend_info:
+ depend_info['server_ip'] = ob_server.ip
+ depend_info['mysql_port'] = ob_server_conf['mysql_port']
+ depend_info['root_password'] = ob_server_conf['root_password']
+ zone = ob_server_conf['zone']
+ if zone not in ob_zones:
+ ob_zones[zone] = ob_server
+ break
+ for comp in ['obproxy', 'obproxy-ce']:
+ if comp in cluster_config.depends:
+ obproxy_servers = cluster_config.get_depend_servers(comp)
+ obproxy_server = obproxy_servers[0]
+ obproxy_server_config = cluster_config.get_depend_config(comp, obproxy_server)
+ depend_info['server_ip'] = obproxy_server.ip
+ depend_info['mysql_port'] = obproxy_server_config['listen_port']
+ break
+
+ for server in cluster_config.servers:
+ server_config = deepcopy(cluster_config.get_server_conf_with_default(server))
+ original_server_config = cluster_config.get_original_server_conf(server)
+ missed_keys = get_missing_required_parameters(original_server_config)
+ if missed_keys:
+ if 'jdbc_url' in missed_keys and depend_observer:
+ server_config['jdbc_url'] = 'jdbc:oceanbase://{}:{}/{}'.format(depend_info['server_ip'],
+ depend_info['mysql_port'],
+ server_config['ocp_meta_db'])
+ server_config['jdbc_username'] = '%s@%s' % (
+ server_config['ocp_meta_username'], server_config['ocp_meta_tenant']['tenant_name'])
+ server_config['jdbc_password'] = server_config['ocp_meta_password']
+ server_config['root_password'] = depend_info['root_password']
+ env[server] = server_config
+ return env
+
+
+def execute_cmd(server_config, cmd):
+ return cmd if not server_config.get('launch_user', None) else 'sudo ' + cmd
+
+
+def start_check(plugin_context, init_check_status=False, work_dir_check=False, work_dir_empty_check=True, strict_check=False, precheck=False, *args, **kwargs):
+
+ def check_pass(item):
+ status = check_status[server]
+ if status[item].status == err.CheckStatus.WAIT:
+ status[item].status = err.CheckStatus.PASS
+ def check_fail(item, error, suggests=[]):
+ status = check_status[server][item]
+ if status.status == err.CheckStatus.WAIT:
+ status.error = error
+ status.suggests = suggests
+ status.status = err.CheckStatus.FAIL
+ def wait_2_pass():
+ status = check_status[server]
+ for item in status:
+ check_pass(item)
+ def alert(item, error, suggests=[]):
+ global success
+ if strict_check:
+ success = False
+ check_fail(item, error, suggests)
+ stdio.error(error)
+ else:
+ stdio.warn(error)
+ def error(item, _error, suggests=[]):
+ global success
+ if plugin_context.dev_mode:
+ stdio.warn(_error)
+ else:
+ success = False
+ check_fail(item, _error, suggests)
+ stdio.error(_error)
+ def critical(item, error, suggests=[]):
+ global success
+ success = False
+ check_fail(item, error, suggests)
+ stdio.error(error)
+
+ cluster_config = plugin_context.cluster_config
+ options = plugin_context.options
+ clients = plugin_context.clients
+ stdio = plugin_context.stdio
+ global success
+ success = True
+
+ check_status = {}
+ plugin_context.set_variable('start_check_status', check_status)
+ for server in cluster_config.servers:
+ check_status[server] = {
+ 'metadb connect': err.CheckStatus(),
+ 'port': err.CheckStatus(),
+ 'java': err.CheckStatus(),
+ 'disk': err.CheckStatus(),
+ 'mem': err.CheckStatus(),
+ 'oceanbase version': err.CheckStatus(),
+ 'time check': err.CheckStatus(),
+ 'launch user': err.CheckStatus(),
+ 'sudo nopasswd': err.CheckStatus(),
+ 'tenant': err.CheckStatus(),
+ 'clockdiff': err.CheckStatus()
+ }
+ if work_dir_check:
+ check_status[server]['dir'] = err.CheckStatus()
+ if init_check_status:
+ return plugin_context.return_true(start_check_status=check_status)
+
+ stdio.start_loading('Check before start ocp-server')
+ env = get_ocp_depend_config(cluster_config, stdio)
+ if not env:
+ return plugin_context.return_false()
+
+ if not cluster_config.depends:
+ for server in cluster_config.servers:
+ server_config = env[server]
+ # check meta db connect before start
+ jdbc_url = server_config['jdbc_url']
+ jdbc_username = server_config['jdbc_username']
+ jdbc_password = server_config['jdbc_password']
+ root_password = server_config.get('root_password', '')
+ matched = re.match(r"^jdbc:\S+://(\S+?)(|:\d+)/(\S+)", jdbc_url)
+ cursor = getattr(options, 'metadb_cursor', '')
+ cursor = kwargs.get('metadb_cursor', '') if cursor == '' else cursor
+ stdio.verbose('metadb connect check')
+ if matched:
+ jdbc_host = matched.group(1)
+ jdbc_port = matched.group(2)[1:]
+ jdbc_database = matched.group(3)
+ password = root_password if root_password else jdbc_password
+ connected = False
+ retries = 10
+ while not connected and retries:
+ retries -= 1
+ try:
+ cursor = Cursor(ip=jdbc_host, port=jdbc_port, user=jdbc_username, password=jdbc_password,
+ stdio=stdio)
+ connected = True
+ except:
+ jdbc_username = 'root'
+ time.sleep(1)
+ if not connected:
+ success = False
+ error('metadb connect', err.EC_CONNECT_METADB, err.SUG_OCP_SERVER_JDBC_URL_CONFIG_ERROR)
+ if cursor and not cursor.fetchone('show DATABASES like "%s"' % jdbc_database):
+ critical('metadb connect', err.EC_DB_NOT_IN_JDBC_URL, err.SUG_OCP_SERVER_JDBC_URL_CONFIG_ERROR)
+ else:
+ critical('metadb connect', err.EC_ERROR_JDBC_URL, err.SUG_OCP_SERVER_JDBC_URL_CONFIG_ERROR)
+ client = clients[server]
+ # time check
+ stdio.verbose('time check ')
+ now = client.execute_command('date +"%Y-%m-%d %H:%M:%S"').stdout.strip()
+ now = datetime.datetime.strptime(now, '%Y-%m-%d %H:%M:%S')
+ stdio.verbose('now: %s' % now)
+ stdio.verbose('cursor: %s' % cursor)
+ if cursor:
+ ob_time = cursor.fetchone("SELECT NOW() now")['now']
+ stdio.verbose('ob_time: %s' % ob_time)
+ if not abs((now - ob_time).total_seconds()) < 180:
+ critical('time check', err.EC_OCP_SERVER_TIME_SHIFT.format(server=server))
+
+ # tenant check
+ skip_create_tenant = 'False'
+ skip_create_tenant = getattr(options, "skip_create_tenant", "False")
+ if skip_create_tenant == 'False':
+ sql = "select * from oceanbase.DBA_OB_TENANTS where TENANT_NAME = %s"
+ meta_tenant = server_config.get('ocp_meta_tenant')['tenant_name']
+ meta_max_cpu = server_config['ocp_meta_tenant'].get('max_cpu', 2)
+ meta_memory_size = server_config['ocp_meta_tenant'].get('memory_size', '2G')
+ if server_config.get('ocp_monitor_tenant'):
+ monitor_user = server_config.get('ocp_monitor_username', 'monitor_user')
+ monitor_tenant = server_config['ocp_monitor_tenant']['tenant_name']
+ monitor_max_cpu = server_config['ocp_monitor_tenant'].get('max_cpu', 2)
+ monitor_memory_size = server_config['ocp_monitor_tenant'].get('memory_size', '4G')
+ res = cursor.fetchone(sql, [monitor_tenant])
+ if res:
+ error('tenant', err.EC_OCP_SERVER_TENANT_ALREADY_EXISTS.format({"tenant_name":monitor_tenant}))
+ res = cursor.fetchone(sql, [meta_tenant])
+ if res:
+ error('tenant', err.EC_OCP_SERVER_TENANT_ALREADY_EXISTS.format({"tenant_name":meta_tenant}))
+ break
+
+ stdio.verbose('oceanbase version check')
+ versions_check = {
+ "oceanbase version": {
+ 'comps': ['oceanbase', 'oceanbase-ce'],
+ 'min_version': Version('4.0')
+ },
+ }
+ repo_versions = {}
+ for repository in plugin_context.repositories:
+ repo_versions[repository.name] = repository.version
+
+ for check_item in versions_check:
+ for comp in versions_check[check_item]['comps']:
+ if comp not in cluster_config.depends:
+ continue
+ depend_comp_version = repo_versions.get(comp)
+ if depend_comp_version is None:
+ stdio.verbose('failed to get {} version, skip version check'.format(comp))
+ continue
+ min_version = versions_check[check_item]['min_version']
+ if depend_comp_version < min_version:
+ critical(check_item, err.EC_OCP_EXPRESS_DEPENDS_COMP_VERSION.format(ocp_express_version=cluster_config.version, comp=comp, comp_version=min_version))
+
+ server_port = {}
+ servers_dirs = {}
+ servers_check_dirs = {}
+ for server in cluster_config.servers:
+ client = clients[server]
+
+ if not (client.execute_command('sudo -n true') or client.execute_command('[ `id -u` == "0" ]')):
+ critical('sudo nopasswd', err.EC_SUDO_NOPASSWD.format(ip=str(server), user=client.config.username),
+ [err.SUG_SUDO_NOPASSWD.format(ip=str(server), user=client.config.username)])
+ server_config = env[server]
+ missed_keys = get_missing_required_parameters(server_config)
+ if missed_keys:
+ stdio.error(err.EC_NEED_CONFIG.format(server=server, component=cluster_config.name, miss_keys=missed_keys))
+ success = False
+ home_path = server_config['home_path']
+ if not precheck:
+ remote_pid_path = '%s/run/ocp-server.pid' % home_path
+ remote_pid = client.execute_command(execute_cmd(server_config, 'cat %s' % remote_pid_path)).stdout.strip()
+ if remote_pid:
+ if client.execute_command(execute_cmd(server_config, 'ls /proc/%s' % remote_pid)):
+ stdio.verbose('%s is running, skip' % server)
+ wait_2_pass()
+
+ # user check
+ for server in cluster_config.servers:
+ server_config = env[server]
+ ocp_user = server_config.get('launch_user', '')
+ if ocp_user:
+ client = clients[server]
+ if not client.execute_command(execute_cmd(server_config, "id -u %s" % ocp_user)):
+ critical('launch user', err.EC_OCP_SERVER_LAUNCH_USER_NOT_EXIST.format(server=server, user=ocp_user))
+
+ for server in cluster_config.servers:
+ client = clients[server]
+ server_config = env[server]
+ if work_dir_check:
+ ip = server.ip
+ stdio.verbose('%s dir check' % server)
+ if ip not in servers_dirs:
+ servers_dirs[ip] = {}
+ servers_check_dirs[ip] = {}
+ dirs = servers_dirs[ip]
+ check_dirs = servers_check_dirs[ip]
+ original_server_conf = cluster_config.get_server_conf(server)
+
+ keys = ['home_path', 'log_dir', 'soft_dir']
+ for key in keys:
+ path = server_config.get(key)
+ suggests = [err.SUG_CONFIG_CONFLICT_DIR.format(key=key, server=server)]
+ if path in dirs and dirs[path]:
+ critical('dir', err.EC_CONFIG_CONFLICT_DIR.format(server1=server, path=path, server2=dirs[path]['server'], key=dirs[path]['key']), suggests)
+ dirs[path] = {
+ 'server': server,
+ 'key': key,
+ }
+ if key not in original_server_conf:
+ continue
+ empty_check = work_dir_empty_check
+ while True:
+ if path in check_dirs:
+ if check_dirs[path] != True:
+ critical('dir', check_dirs[path], suggests)
+ break
+
+ if client.execute_command(execute_cmd(server_config, 'bash -c "[ -a %s ]"' % path)):
+ is_dir = client.execute_command(execute_cmd(server_config, '[ -d {} ]'.format(path)))
+ has_write_permission = client.execute_command(execute_cmd(server_config, '[ -w {} ]'.format(path)))
+ if is_dir and has_write_permission:
+ if empty_check:
+ check_privilege_cmd = "ls %s" % path
+ if server_config.get('launch_user', ''):
+ check_privilege_cmd = "sudo su - %s -c 'ls %s'" % (server_config['launch_user'], path)
+ ret = client.execute_command(check_privilege_cmd)
+ if not ret:
+ check_dirs[path] = err.EC_OCP_SERVER_DIR_ACCESS_FORBIDE.format(server=server, path=path, cur_path=path)
+ elif ret.stdout.strip():
+ check_dirs[path] = err.EC_FAIL_TO_INIT_PATH.format(server=server, key=key, msg=err.InitDirFailedErrorMessage.NOT_EMPTY.format(path=path))
+ else:
+ check_dirs[path] = True
+ else:
+ check_dirs[path] = True
+ else:
+ if not is_dir:
+ check_dirs[path] = err.EC_FAIL_TO_INIT_PATH.format(server=server, key=key, msg=err.InitDirFailedErrorMessage.NOT_DIR.format(path=path))
+ else:
+ check_dirs[path] = err.EC_FAIL_TO_INIT_PATH.format(server=server, key=key, msg=err.InitDirFailedErrorMessage.PERMISSION_DENIED.format(path=path))
+ else:
+ path = os.path.dirname(path)
+ empty_check = False
+
+ port = server_config['port']
+ ip = server.ip
+ if ip not in server_port:
+ server_port[ip] = {}
+ ports = server_port[ip]
+ if port in server_port[ip]:
+ critical(
+ 'port',
+ err.EC_CONFIG_CONFLICT_PORT.format(server1=server, port=port, server2=ports[port]['server'],
+ key=ports[port]['key']),
+ [err.SUG_PORT_CONFLICTS.format()]
+ )
+ ports[port] = {
+ 'server': server,
+ 'key': 'port'
+ }
+ if get_port_socket_inode(client, port):
+ critical(
+ 'port',
+ err.EC_CONFLICT_PORT.format(server=ip, port=port),
+ [err.SUG_USE_OTHER_PORT.format()]
+ )
+ check_pass('port')
+
+ try:
+ # java version check
+ java_bin = server_config.get('java_bin', '/usr/bin/java')
+ ret = client.execute_command(execute_cmd(server_config, '{} -version'.format(java_bin)))
+ stdio.verbose('java version %s' % ret)
+ if not ret:
+ critical('java', err.EC_OCP_EXPRESS_JAVA_NOT_FOUND.format(server=server), [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0')])
+ version_pattern = r'version\s+\"(\d+\.\d+\.\d+)(\_\d+)'
+ found = re.search(version_pattern, ret.stdout) or re.search(version_pattern, ret.stderr)
+ if not found:
+ error('java', err.EC_OCP_EXPRESS_JAVA_VERSION_ERROR.format(server=server, version='1.8.0'), [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0'),])
+ java_major_version = found.group(1)
+ stdio.verbose('java_major_version %s' % java_major_version)
+ java_update_version = found.group(2)[1:]
+ stdio.verbose('java_update_version %s' % java_update_version)
+ if Version(java_major_version) != Version('1.8.0') or int(java_update_version) < 161:
+ critical('java', err.EC_OCP_SERVER_JAVA_VERSION_ERROR.format(server=server, version='1.8.0'), [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0'),])
+ except Exception as e:
+ stdio.error(e)
+ error('java', err.EC_OCP_EXPRESS_JAVA_VERSION_ERROR.format(server=server, version='1.8.0'),
+ [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0'), ])
+
+ try:
+ # clockdiff status check
+ clockdiff_bin = 'which clockdiff'
+ if client.execute_command(clockdiff_bin):
+ check_pass('clockdiff')
+ else:
+ if not client.execute_command('sudo -n true'):
+ critical('clockdiff', err.EC_OCP_SERVER_CLOCKDIFF_NOT_EXISTS.format(server=server))
+ ret = client.execute_command('sudo' + clockdiff_bin)
+ if not ret:
+ critical('clockdiff', err.EC_OCP_SERVER_CLOCKDIFF_NOT_EXISTS.format(server=server))
+
+ client.execute_command('which clockdiff | xargs sudo chmod u+s')
+ client.execute_command("which clockdiff | xargs sudo setcap 'cap_net_raw+ep'")
+ except Exception as e:
+ stdio.error(e)
+ critical('clockdiff', err.EC_OCP_SERVER_CLOCKDIFF_NOT_EXISTS.format(server=server))
+
+ servers_memory = {}
+ servers_disk = {}
+ servers_client = {}
+ ip_servers = {}
+
+ for server in cluster_config.servers:
+ client = clients[server]
+ server_config = env[server]
+ memory_size = parse_size(server_config.get('memory_size', '1G'))
+ if server_config.get('log_dir'):
+ log_dir = server_config['log_dir']
+ else:
+ log_dir = os.path.join(server_config['home_path'], 'log')
+ need_size = parse_size(server_config.get('logging_file_total_size_cap', '1G'))
+ ip = server.ip
+ if ip not in servers_client:
+ servers_client[ip] = client
+ if ip not in servers_memory:
+ servers_memory[ip] = {
+ 'need': memory_size,
+ 'server_num': 1
+ }
+ else:
+ servers_memory[ip]['need'] += memory_size
+ servers_memory[ip]['server_num'] += 1
+ if ip not in servers_disk:
+ servers_disk[ip] = {}
+ if log_dir not in servers_disk[ip]:
+ servers_disk[ip][log_dir] = need_size
+ else:
+ servers_disk[ip][log_dir] += need_size
+ if ip not in ip_servers:
+ ip_servers[ip] = [server]
+ else:
+ ip_servers[ip].append(server)
+ # memory check
+ for ip in servers_memory:
+ client = servers_client[ip]
+ memory_needed = servers_memory[ip]['need']
+ ret = client.execute_command('cat /proc/meminfo')
+ if ret:
+ server_memory_stats = {}
+ memory_key_map = {
+ 'MemTotal': 'total',
+ 'MemFree': 'free',
+ 'MemAvailable': 'available',
+ 'Buffers': 'buffers',
+ 'Cached': 'cached'
+ }
+ for key in memory_key_map:
+ server_memory_stats[memory_key_map[key]] = 0
+
+ for k, v in re.findall('(\w+)\s*:\s*(\d+\s*\w+)', ret.stdout):
+ if k in memory_key_map:
+ key = memory_key_map[k]
+ server_memory_stats[key] = parse_size(str(v))
+ mem_suggests = [err.SUG_OCP_EXPRESS_REDUCE_MEM.format()]
+ if memory_needed * 0.5 > server_memory_stats['available']:
+ for server in ip_servers[ip]:
+ error('mem', err.EC_OCP_EXPRESS_NOT_ENOUGH_MEMORY_AVAILABLE.format(ip=ip, available=format_size(server_memory_stats['available']), need=format_size(memory_needed)), suggests=mem_suggests)
+ elif memory_needed > server_memory_stats['free'] + server_memory_stats['buffers'] + server_memory_stats['cached']:
+ for server in ip_servers[ip]:
+ error('mem', err.EC_OCP_EXPRESS_NOT_ENOUGH_MEMORY_CACHED.format(ip=ip, free=format_size(server_memory_stats['free']), cached=format_size(server_memory_stats['buffers'] + server_memory_stats['cached']), need=format_size(memory_needed)), suggests=mem_suggests)
+ elif memory_needed > server_memory_stats['free']:
+ for server in ip_servers[ip]:
+ alert('mem', err.EC_OCP_EXPRESS_NOT_ENOUGH_MEMORY.format(ip=ip, free=format_size(server_memory_stats['free']), need=format_size(memory_needed)), suggests=mem_suggests)
+ # disk check
+ for ip in servers_disk:
+ client = servers_client[ip]
+ disk_info = get_disk_info(all_paths=servers_disk[ip], client=client, stdio=stdio)
+ if disk_info:
+ for path in servers_disk[ip]:
+ disk_needed = servers_disk[ip][path]
+ mount_path = get_mount_path(disk_info, path)
+ if disk_needed > disk_info[mount_path]['avail']:
+ for server in ip_servers[ip]:
+ error('disk', err.EC_OCP_EXPRESS_NOT_ENOUGH_DISK.format(ip=ip, disk=mount_path, need=format_size(disk_needed), avail=format_size(disk_info[mount_path]['avail'])), suggests=[err.SUG_OCP_EXPRESS_REDUCE_DISK.format()])
+ else:
+ stdio.warn(err.WC_OCP_EXPRESS_FAILED_TO_GET_DISK_INFO.format(ip))
+ plugin_context.set_variable('start_env', env)
+
+ for server in cluster_config.servers:
+ wait_2_pass()
+
+ if success:
+ stdio.stop_loading('succeed')
+ return plugin_context.return_true()
+ else:
+ stdio.stop_loading('fail')
+ return plugin_context.return_false()
diff --git a/plugins/ocp-server/4.2.1/status.py b/plugins/ocp-server/4.2.1/status.py
new file mode 100644
index 0000000..a92311c
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/status.py
@@ -0,0 +1,39 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import os
+
+
+def status(plugin_context, *args, **kwargs):
+ cluster_config = plugin_context.cluster_config
+ clients = plugin_context.clients
+ cluster_status = {}
+ for server in cluster_config.servers:
+ server_config = cluster_config.get_server_conf(server)
+ client = clients[server]
+ cluster_status[server] = 0
+ pid_path = os.path.join(server_config['home_path'], 'run/ocp-server.pid')
+ pids = client.execute_command('cat {}'.format(pid_path)).stdout.strip().split('\n')
+ for pid in pids:
+ if pid and client.execute_command('ls /proc/{}'.format(pid)):
+ cluster_status[server] = 1
+ return plugin_context.return_true(cluster_status=cluster_status)
\ No newline at end of file
diff --git a/plugins/ocp-server/4.2.1/stop.py b/plugins/ocp-server/4.2.1/stop.py
new file mode 100644
index 0000000..1711442
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/stop.py
@@ -0,0 +1,107 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import os
+import time
+
+
+def get_port_socket_inode(client, port, stdio):
+ port = hex(port)[2:].zfill(4).upper()
+ cmd = "bash -c 'cat /proc/net/{tcp*,udp*}' | awk -F' ' '{print $2,$10}' | grep '00000000:%s' | awk -F' ' '{print $2}' | uniq" % port
+ res = client.execute_command(cmd)
+ if not res or not res.stdout.strip():
+ return False
+ stdio.verbose(res.stdout)
+ return res.stdout.strip().split('\n')
+
+
+def confirm_port(client, pid, port, stdio):
+ socket_inodes = get_port_socket_inode(client, port, stdio)
+ if not socket_inodes:
+ return False
+ ret = client.execute_command("ls -l /proc/%s/fd/ |grep -E 'socket:\[(%s)\]'" % (pid, '|'.join(socket_inodes)))
+ if ret and ret.stdout.strip():
+ return True
+ return False
+
+
+def stop(plugin_context, *args, **kwargs):
+ cluster_config = plugin_context.cluster_config
+ clients = plugin_context.clients
+ stdio = plugin_context.stdio
+ servers = {}
+ stdio.start_loading('Stop ocp-server')
+ success = True
+ for server in cluster_config.servers:
+ server_config = cluster_config.get_server_conf(server)
+ client = clients[server]
+ home_path = server_config['home_path']
+ pid_path = os.path.join(home_path, 'run/ocp-server.pid')
+ launch_user = server_config.get('launch_user', None)
+ cmd = 'cat {}'.format(pid_path)
+ pids = client.execute_command('sudo ' + cmd if launch_user else cmd).stdout.strip().split('\n')
+ success = False
+ for pid in pids:
+ cmd = 'ls /proc/{}'.format(pid)
+ if pid and client.execute_command('sudo ' + cmd if launch_user else cmd):
+ cmd = 'ls /proc/{}/fd'.format(pid)
+ if client.execute_command('sudo ' + cmd if launch_user else cmd):
+ stdio.verbose('{} ocp-server[pid: {}] stopping...'.format(server, pid))
+ cmd = 'kill -9 {}'.format(pid)
+ client.execute_command('sudo ' + cmd if launch_user else cmd)
+ else:
+ stdio.verbose('failed to stop ocp-server[pid:{}] in {}, permission deny'.format(pid, server))
+ success = False
+ else:
+ stdio.verbose('{} ocp-server is not running'.format(server))
+ if not success:
+ stdio.stop_loading('fail')
+ return plugin_context.return_true()
+
+ count = 10
+ check = lambda client, pid, port: confirm_port(client, pid, port, stdio) if count < 5 else get_port_socket_inode(client, port, stdio)
+ time.sleep(1)
+ while count and servers:
+ tmp_servers = {}
+ for server in servers:
+ data = servers[server]
+ client = clients[server]
+ stdio.verbose('%s check whether the port is released' % server)
+ for key in ['port']:
+ if data[key] and check(data['client'], data['pid'], data[key]):
+ tmp_servers[server] = data
+ break
+ data[key] = ''
+ else:
+ client.execute_command('rm -rf %s' % data['path'])
+ stdio.verbose('%s ocp-server is stopped', server)
+ servers = tmp_servers
+ count -= 1
+ if count and servers:
+ time.sleep(3)
+ if servers:
+ stdio.stop_loading('fail')
+ for server in servers:
+ stdio.warn('%s port not released'% server)
+ else:
+ stdio.stop_loading('succeed')
+ return plugin_context.return_true()
diff --git a/plugins/ocp-server/4.2.1/takeover.py b/plugins/ocp-server/4.2.1/takeover.py
new file mode 100644
index 0000000..8551fb3
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/takeover.py
@@ -0,0 +1,214 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2023 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import os
+import re
+import time
+import json
+import uuid
+import requests
+from _rpm import Version
+from copy import deepcopy
+from requests.auth import HTTPBasicAuth
+
+from tool import Cursor, FileUtil, YamlLoader
+from _errno import EC_OBSERVER_CAN_NOT_MIGRATE_IN
+
+class OcpCursor(object):
+
+ class Response(object):
+
+ def __init__(self, code, content):
+ self.code = code
+ self.content = content
+
+ def __bool__(self):
+ return self.code == 200
+
+ def __init__(self, base_url="http://localhost:8080", username=None, password=None):
+ self.base_url = base_url.strip("/")
+ self.auth = None
+ self.username=username
+ self.password=password
+ if self.username:
+ self.auth = HTTPBasicAuth(username=username, password=password)
+
+ def status(self, stdio=None):
+ resp = self._request('GET', '/api/v2/time', stdio=stdio)
+ ocp_status_ok = False
+ now = time.time()
+ check_wait_time = 180
+ while time.time() - now < check_wait_time:
+ stdio.verbose("query ocp to check...")
+ try:
+ if resp.code == 200:
+ ocp_status_ok = True
+ break
+ except Exception:
+ stdio.verbose("ocp still not active")
+ time.sleep(5)
+ if ocp_status_ok:
+ stdio.verbose("check ocp server status ok")
+ return True
+ else:
+ stdio.verbose("ocp still not ok, check failed")
+ raise Exception("ocp still not ok, check failed")
+
+ def info(self, stdio=None):
+ resp = self._request('GET', '/api/v2/info', stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ raise Exception("failed to query ocp info")
+
+ def take_over_precheck(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/ob/clusters/takeOverPreCheck', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ raise Exception("takeover precheck failed")
+ def get_host_types(self, stdio=None):
+ resp = self._request('GET', '/api/v2/compute/hostTypes', stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ raise Exception("failed to query host types from ocp")
+
+ def create_host_type(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/compute/hostTypes', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ raise Exception("failed to create host types in ocp")
+
+ def list_credentials(self, stdio=None):
+ resp = self._request('GET', '/api/v2/profiles/me/credentials', stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ raise Exception("failed to query credentials from ocp")
+
+ def create_credential(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/profiles/me/credentials', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ raise Exception("failed to create credentials in ocp")
+
+ def take_over(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/ob/clusters/takeOver', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def _request(self, method, api, data=None, retry=5, stdio=None):
+ url = self.base_url + api
+ headers = {"Content-Type": "application/json"}
+ try:
+ if data is not None:
+ data = json.dumps(data)
+ stdio.verbose('send http request method: {}, url: {}, data: {}'.format(method, url, data))
+ resp = requests.request(method, url, data=data, verify=False, headers=headers, auth=self.auth)
+ return_code = resp.status_code
+ content = resp.content
+ except Exception as e:
+ if retry:
+ retry -= 1
+ return self._request(method=method, api=api, data=data, retry=retry, stdio=stdio)
+ stdio.exception("")
+ return_code = 500
+ content = str(e)
+ if return_code != 200:
+ stdio.verbose("request ocp-server failed: %s" % content)
+ try:
+ content = json.loads(content.decode())
+ except:
+ pass
+ return self.Response(code=return_code, content=content)
+
+def takeover(plugin_context, *args, **kwargs):
+ # init variables, include get obcluster info from deploy config
+ cluster_config = plugin_context.cluster_config
+ clients = plugin_context.clients
+ options = plugin_context.options
+ stdio = plugin_context.stdio
+ stdio.verbose(vars(cluster_config))
+ address = getattr(options, 'address', '')
+ user = getattr(options, 'user', '')
+ password = getattr(options, 'password', '')
+ host_type = getattr(options, 'host_type', '')
+ credential_name = getattr(options, 'credential_name', '')
+ ocp_cursor = OcpCursor(base_url=address, username=user, password=password)
+ if len(clients) == 0:
+ stdio.error("no available clients")
+ return plugin_context.return_false()
+ ssh_client = None
+ for ssh_client in clients.values():
+ if ssh_client != None:
+ break
+ ssh_config = ssh_client.config
+ # query host types, add host type if current host_type is not empty and no matched record in ocp, otherwise use the first one
+ host_types = ocp_cursor.get_host_types(stdio=stdio)['data']['contents']
+ host_type_id = None
+ if host_type == "":
+ if len(host_types) > 0:
+ host_type_id = host_types[0]['id']
+ else:
+ for t in host_types:
+ if host_type == t['name']:
+ host_type_id = t['id']
+ break
+ if host_type_id is None:
+ create_host_type_data = {'name': host_type if host_type is not None else str(uuid.uuid4()).split('-')[-1]}
+ host_type_id = ocp_cursor.create_host_type(create_host_type_data, stdio=stdio)['data']['id']
+ # query credentials
+ credential_id = None
+ if credential_name != "":
+ credentials = ocp_cursor.list_credentials(stdio=stdio)['data']['contents']
+ for credential in credentials:
+ if credential['targetType'] == "HOST" and credential['name'] == credential_name:
+ stdio.verbose("found credential with id %d", credential['id'])
+ credential_id = credential['id']
+ break
+ if credential_id is None:
+ name = credential_name if credential_name != "" else str(uuid.uuid4()).split('-')[-1]
+ credential_type = "PRIVATE_KEY"
+ if ssh_config.password is not None and ssh_config.password != "":
+ credential_type = "PASSWORD"
+ pass_phrase = ssh_config.password
+ else:
+ key_file = ssh_config.key_filename if ssh_config.key_filename is not None else '{0}/.ssh/id_rsa'.format(os.path.expanduser("~"))
+ with open(key_file, 'r') as fd:
+ pass_phrase = fd.read()
+ create_credential_data = {"targetType":"HOST","name":name,"sshCredentialProperty":{"type":credential_type, "username":ssh_config.username,"passphrase":pass_phrase}}
+ credential_id = ocp_cursor.create_credential(create_credential_data, stdio=stdio)['data']['id']
+ server = cluster_config.servers[0]
+ mysql_port = cluster_config.get_global_conf().get("mysql_port")
+ root_password = cluster_config.get_global_conf().get("root_password")
+ takeover_data = {"switchConfigUrl":True,"connectionMode":"direct","rootSysPassword":root_password,"address":server.ip,"port":mysql_port,"hostInfo":{"kind":"DEDICATED_PHYSICAL_MACHINE","hostTypeId":host_type_id,"sshPort":22,"credentialId":credential_id}}
+ proxyro_password = cluster_config.get_global_conf().get("proxyro_password")
+ if proxyro_password is not None and proxyro_password != "":
+ takeover_data.update({"proxyroPassword": proxyro_password})
+ takeover_result = ocp_cursor.take_over(takeover_data, stdio=stdio)
+ stdio.verbose("takeover result %s" % takeover_result)
+ task_id = takeover_result['data']['id']
+ cluster_id = takeover_result['data']['clusterId']
+ return plugin_context.return_true(task_id=task_id, cluster_id = cluster_id)
diff --git a/plugins/ocp-server/4.2.1/takeover_precheck.py b/plugins/ocp-server/4.2.1/takeover_precheck.py
new file mode 100644
index 0000000..93e088c
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/takeover_precheck.py
@@ -0,0 +1,162 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2023 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import os
+import re
+import time
+import json
+import requests
+from _rpm import Version
+from copy import deepcopy
+from requests.auth import HTTPBasicAuth
+
+from tool import Cursor, FileUtil, YamlLoader
+from _errno import EC_OBSERVER_CAN_NOT_MIGRATE_IN
+
+class OcpCursor(object):
+
+ class Response(object):
+
+ def __init__(self, code, content):
+ self.code = code
+ self.content = content
+
+ def __bool__(self):
+ return self.code == 200
+
+ def __init__(self, base_url="http://localhost:8080", username=None, password=None):
+ self.base_url = base_url.strip('/')
+ self.auth = None
+ self.username=username
+ self.password=password
+ if self.username:
+ self.auth = HTTPBasicAuth(username=username, password=password)
+
+ def status(self, stdio=None):
+ resp = self._request('GET', '/api/v2/time', stdio=stdio)
+ ocp_status_ok = False
+ now = time.time()
+ check_wait_time = 180
+ while time.time() - now < check_wait_time:
+ stdio.verbose("query ocp to check...")
+ try:
+ if resp.code == 200:
+ ocp_status_ok = True
+ break
+ except Exception:
+ stdio.verbose("ocp still not active")
+ time.sleep(5)
+ if ocp_status_ok:
+ stdio.verbose("check ocp server status ok")
+ return True
+ else:
+ stdio.verbose("ocp still not ok, check failed")
+ raise Exception("ocp still not ok, check failed")
+
+ def info(self, stdio=None):
+ resp = self._request('GET', '/api/v2/info', stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ raise Exception("failed to query ocp info")
+
+ def take_over_precheck(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/ob/clusters/takeOverPreCheck', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+ else:
+ msg = resp.content['error']['message']
+ raise Exception("takeover precheck failed %s" % msg)
+
+ def compute_host_types(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/compute/hostTypes', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def profiles_credentials(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/profiles/me/credentials', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def take_over(self, data, stdio=None):
+ resp = self._request('POST', '/api/v2/ob/clusters/takeOver', data=data, stdio=stdio)
+ if resp.code == 200:
+ return resp.content
+
+ def _request(self, method, api, data=None, retry=5, stdio=None):
+ url = self.base_url + api
+ headers = {"Content-Type": "application/json"}
+ try:
+ if data is not None:
+ data = json.dumps(data)
+ stdio.verbose('send http request method: {}, url: {}, data: {}'.format(method, url, data))
+ resp = requests.request(method, url, data=data, verify=False, headers=headers, auth=self.auth)
+ return_code = resp.status_code
+ content = resp.content
+ except Exception as e:
+ if retry:
+ retry -= 1
+ return self._request(method=method, api=api, data=data, retry=retry, stdio=stdio)
+ stdio.exception("")
+ return_code = 500
+ content = str(e)
+ if return_code != 200:
+ stdio.verbose("request ocp-server failed: %s" % content)
+ try:
+ content = json.loads(content.decode())
+ except:
+ pass
+ return self.Response(code=return_code, content=content)
+
+def takeover_precheck(plugin_context, *args, **kwargs):
+ # init variables, include get obcluster info from deploy config
+ cluster_config = plugin_context.cluster_config
+ clients = plugin_context.clients
+ options = plugin_context.options
+ stdio = plugin_context.stdio
+ stdio.verbose(vars(cluster_config))
+ address = getattr(options, 'address', '')
+ user = getattr(options, 'user', '')
+ password = getattr(options, 'password', '')
+ ocp_cursor = OcpCursor(base_url=address, username=user, password=password)
+ ocp_info = ocp_cursor.info(stdio=stdio)
+ stdio.verbose("get ocp info %s", ocp_info)
+ ocp_version = Version(ocp_info['buildVersion'].split("_")[0])
+ if ocp_version < Version("4.2.0"):
+ stdio.error("unable to export obcluster to ocp, ocp version must be at least 4.2.0")
+ return plugin_context.return_false(ocp_version=ocp_version)
+ server = cluster_config.servers[0]
+ mysql_port = cluster_config.get_global_conf().get("mysql_port")
+ root_password = cluster_config.get_global_conf().get("root_password")
+ if root_password is None or root_password == "":
+ stdio.error("unable to export obcluster to ocp, root password is empty")
+ return plugin_context.return_false(ocp_version=ocp_version)
+ precheck_data = {"connectionMode":"direct","address":server.ip,"port":mysql_port,"rootSysPassword":root_password}
+ proxyro_password = cluster_config.get_global_conf().get("proxyro_password")
+ if proxyro_password is not None and proxyro_password != "":
+ precheck_data.update({"proxyroPassword": proxyro_password})
+ try:
+ precheck_result = ocp_cursor.take_over_precheck(precheck_data, stdio=stdio)
+ stdio.verbose("precheck result %s" % precheck_result)
+ except Exception as ex:
+ return plugin_context.return_false(exception=ex)
+ return plugin_context.return_true(ocp_version=ocp_version)
diff --git a/plugins/ocp-server/4.2.1/upgrade.py b/plugins/ocp-server/4.2.1/upgrade.py
new file mode 100644
index 0000000..de5c719
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/upgrade.py
@@ -0,0 +1,78 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+
+def upgrade(plugin_context, search_py_script_plugin, apply_param_plugin, *args, **kwargs):
+ namespace = plugin_context.namespace
+ namespaces = plugin_context.namespaces
+ deploy_name = plugin_context.deploy_name
+ repositories = plugin_context.repositories
+ plugin_name = plugin_context.plugin_name
+
+ components = plugin_context.components
+ clients = plugin_context.clients
+ cluster_config = plugin_context.cluster_config
+ cmds = plugin_context.cmds
+ options = plugin_context.options
+ dev_mode = plugin_context.dev_mode
+ stdio = plugin_context.stdio
+ upgrade_repositories = kwargs.get('upgrade_repositories')
+ sys_cursor = kwargs.get('sys_cursor')
+ metadb_cursor = kwargs.get('metadb_cursor')
+
+ cur_repository = upgrade_repositories[0]
+ dest_repository = upgrade_repositories[-1]
+ repository_dir = dest_repository.repository_dir
+ kwargs['repository_dir'] = repository_dir
+
+ stop_plugin = search_py_script_plugin([cur_repository], 'stop')[cur_repository]
+ init_plugin = search_py_script_plugin([dest_repository], 'init')[dest_repository]
+ start_plugin = search_py_script_plugin([dest_repository], 'start')[dest_repository]
+ connect_plugin = search_py_script_plugin([dest_repository], 'connect')[dest_repository]
+ display_plugin = search_py_script_plugin([dest_repository], 'display')[dest_repository]
+
+ apply_param_plugin(cur_repository)
+ if not stop_plugin(namespace, namespaces, deploy_name, repositories, components, clients, cluster_config, cmds, options, stdio, *args, **kwargs):
+ return plugin_context.return_false()
+
+ try:
+ servers = cluster_config.servers
+ for server in servers:
+ client = clients[server]
+ res = client.execute_command("sudo docker ps | grep ocp-all-in-one | awk '{print 1}'").stdout.strip()
+ client.execute_command('sudo docker stop %s' % res)
+ except:
+ pass
+
+ apply_param_plugin(dest_repository)
+ if not init_plugin(namespace, namespaces, deploy_name, repositories, components, clients, cluster_config, cmds, options, stdio, upgrade=True, *args, **kwargs):
+ return plugin_context.return_false()
+
+ if not start_plugin(namespace, namespaces, deploy_name, repositories, components, clients, cluster_config, cmds, options, stdio, sys_cursor1=sys_cursor, cursor=metadb_cursor, without_ocp_parameter=True, *args, **kwargs):
+ return plugin_context.return_false()
+
+ ret = connect_plugin(namespace, namespaces, deploy_name, repositories, components, clients, cluster_config, cmds, options, stdio, *args, **kwargs)
+ if ret:
+ cursor = ret.get_return('cursor')
+ if display_plugin(namespace, namespaces, deploy_name, repositories, components, clients, cluster_config, cmds, options, stdio, cursor=cursor, *args, **kwargs):
+ return plugin_context.return_true()
+ return plugin_context.return_false()
diff --git a/plugins/ocp-server/4.2.1/upgrade_check.py b/plugins/ocp-server/4.2.1/upgrade_check.py
new file mode 100644
index 0000000..7189031
--- /dev/null
+++ b/plugins/ocp-server/4.2.1/upgrade_check.py
@@ -0,0 +1,134 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+
+from __future__ import absolute_import, division, print_function
+
+import re
+import time
+
+import _errno as err
+from _rpm import Version
+
+
+def upgrade_check(plugin_context, meta_cursor, database='meta_database', init_check_status=False, *args, **kwargs):
+ def check_pass(item):
+ status = check_status[server]
+ if status[item].status == err.CheckStatus.WAIT:
+ status[item].status = err.CheckStatus.PASS
+ def check_fail(item, error, suggests=[]):
+ status = check_status[server][item]
+ status.error = error
+ status.suggests = suggests
+ status.status = err.CheckStatus.FAIL
+ def wait_2_pass():
+ status = check_status[server]
+ for item in status:
+ check_pass(item)
+ def alert(item, error, suggests=[]):
+ global success
+ stdio.warn(error)
+ def error(item, _error, suggests=[]):
+ global success
+ success = False
+ check_fail(item, _error, suggests)
+ stdio.error(_error)
+ def critical(item, error, suggests=[]):
+ global success
+ success = False
+ check_fail(item, error, suggests)
+ stdio.error(error)
+
+
+ check_status = {}
+ repositories = plugin_context.repositories
+ options = plugin_context.options
+ stdio = plugin_context.stdio
+ clients = plugin_context.clients
+ cluster_config = plugin_context.cluster_config
+ plugin_context.set_variable('start_check_status', check_status)
+
+ for server in cluster_config.servers:
+ check_status[server] = {
+ 'check_operation_task': err.CheckStatus(),
+ 'check_machine_status': err.CheckStatus(),
+ 'metadb_version': err.CheckStatus(),
+ 'java': err.CheckStatus(),
+ }
+
+ if init_check_status:
+ return plugin_context.return_true(start_check_status=check_status)
+
+ stdio.start_loading('Check before upgrade ocp-server')
+ success = True
+
+ for server in cluster_config.servers:
+ client = clients[server]
+ server_config = cluster_config.get_server_conf_with_default(server)
+ try:
+ # java version check
+ java_bin = server_config.get('java_bin', '/usr/bin/java')
+ ret = client.execute_command('{} -version'.format(java_bin))
+ if not ret:
+ critical('java', err.EC_OCP_EXPRESS_JAVA_NOT_FOUND.format(server=str(server)), [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0')])
+ version_pattern = r'version\s+\"(\d+\.\d+\.\d+)(\_\d+)'
+ found = re.search(version_pattern, ret.stdout) or re.search(version_pattern, ret.stderr)
+ if not found:
+ error('java', err.EC_OCP_EXPRESS_JAVA_VERSION_ERROR.format(server=str(server), version='1.8.0'), [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0'),])
+ java_major_version = found.group(1)
+ java_update_version = found.group(2)[1:]
+ if Version(java_major_version) != Version('1.8.0') and int(java_update_version) >= 161:
+ critical('java', err.EC_OCP_SERVER_JAVA_VERSION_ERROR.format(server=str(server), version='1.8.0'), [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0'),])
+ check_pass('java')
+ except Exception as e:
+ stdio.error(e)
+ error('java', err.EC_OCP_EXPRESS_JAVA_VERSION_ERROR.format(server=str(server), version='1.8.0'),
+ [err.SUG_OCP_EXPRESS_INSTALL_JAVA_WITH_VERSION.format(version='1.8.0'), ])
+
+ sql = "select count(*) num from %s.task_instance where state not in ('FAILED', 'SUCCESSFUL');" % database
+ if meta_cursor.fetchone(sql)['num'] > 0:
+ success = False
+ error('check_operation_task', err.EC_OCP_SERVER_RUNNING_TASK)
+ else:
+ check_pass('check_operation_task')
+
+ sql = "select count(*) num from %s.compute_host where status not in ('available', 'online');" % database
+ if meta_cursor.fetchone(sql)['num'] > 0:
+ success = False
+ error('check_machine_status', err.EC_OCP_SERVER_MACHINE_STATUS)
+ else:
+ check_pass('check_machine_status')
+
+ sql = "select ob_version();"
+ v1 = meta_cursor.fetchone(sql)['ob_version()']
+ if Version(v1) > Version('2.2.50'):
+ check_pass('metadb_version')
+ else:
+ success = False
+ error('metadb_version', err.EC_OCP_SERVER_METADB_VERSION)
+
+ if success:
+ stdio.stop_loading('succeed')
+ return plugin_context.return_true()
+ else:
+ stdio.stop_loading('fail')
+ return plugin_context.return_false()
+
+
+
diff --git a/profile/obd.sh b/profile/obd.sh
index 0612086..c208f29 100644
--- a/profile/obd.sh
+++ b/profile/obd.sh
@@ -70,7 +70,7 @@ function _obd_complete_func
prev=${!#}
all_cmds["obd"]="mirror cluster test update repo demo web obdiag display-trace"
- all_cmds["obd cluster"]="autodeploy tenant start deploy redeploy restart reload destroy stop edit-config list display upgrade chst check4ocp reinstall"
+ all_cmds["obd cluster"]="autodeploy tenant start deploy redeploy restart reload destroy stop edit-config export-to-ocp list display upgrade chst check4ocp reinstall"
all_cmds["obd cluster *"]="_obd_reply_deploy_names"
all_cmds["obd cluster tenant"]="create drop show create-standby switchover failover decouple"
all_cmds["obd cluster tenant *"]="_obd_reply_deploy_names"
@@ -87,6 +87,9 @@ function _obd_complete_func
all_cmds["obd repo"]="list"
all_cmds["obd test"]="mysqltest sysbench tpch tpcc"
all_cmds["obd test *"]="_obd_reply_deploy_names"
+ all_cmds["obd web *"]="install upgrade"
+ all_cmds["obd web install *"]="_obd_reply_deploy_names"
+ all_cmds["obd web upgrade *"]="_obd_reply_deploy_names"
# if [ -f "$env_file" ] && [ "$(grep '"OBD_DEV_MODE": "1"' "$env_file")" != "" ]; then
all_cmds["obd"]="${all_cmds[obd]} devmode env tool"
@@ -97,9 +100,11 @@ function _obd_complete_func
all_cmds["obd tool command"]="_obd_reply_deploy_names"
all_cmds["obd tool command *"]="_obd_reply_tool_commands"
all_cmds["obd env"]="set unset show clear"
- all_cmds["obd obdiag"]="gather deploy"
+ all_cmds["obd obdiag"]="check gather deploy analyze"
all_cmds["obd obdiag gather"]="all log clog slog obproxy_log perf plan_monitor stack sysstat"
all_cmds["obd obdiag gather *"]="_obd_reply_deploy_names"
+ all_cmds["obd obdiag analyze"]="log"
+ all_cmds["obd obdiag check *"]="_obd_reply_deploy_names"
# fi
case $prev in
list)
@@ -140,4 +145,4 @@ function _obd_complete_func
esac
-}
\ No newline at end of file
+}
diff --git a/rpm/ob-deploy.spec b/rpm/ob-deploy.spec
index bcee280..2ae5d09 100644
--- a/rpm/ob-deploy.spec
+++ b/rpm/ob-deploy.spec
@@ -95,6 +95,7 @@ mkdir -p ${RPM_BUILD_ROOT}/usr/obd/lib/executer
\cp -rf ${RPM_DIR}/executer27 ${RPM_BUILD_ROOT}/usr/obd/lib/executer/
\cp -rf $BUILD_DIR/SOURCES/example ${RPM_BUILD_ROOT}/usr/obd/
cd ${RPM_BUILD_ROOT}/usr/obd/plugins && ln -s oceanbase oceanbase-ce && \cp -rf obproxy/3.1.0 obproxy-ce/ && \cp -rf $SRC_DIR/plugins/obproxy-ce/* obproxy-ce/
+cd ${RPM_BUILD_ROOT}/usr/obd/plugins && ln -sf ocp-server ocp-server-ce
mv obproxy/3.1.0 obproxy/3.2.1
cd ${RPM_BUILD_ROOT}/usr/obd/config_parser && ln -s oceanbase oceanbase-ce
cd ${RPM_BUILD_ROOT}/usr/obd/optimize && ln -s obproxy obproxy-ce
@@ -131,6 +132,38 @@ echo -e 'Installation of obd finished successfully\nPlease source /etc/profile.d
#/sbin/chkconfig obd on
%changelog
+* Fri Nov 24 2023 obd 2.4.0
+ - new features: support for graphical deployment of OCP-CE V4.2.1
+ - new features: support for graphical deployment of OCP-CE V4.2.1 along with its MetaDB
+ - new features: support for command-line deployment of OCP-CE V4.2.1 along with its MetaDB
+ - new features: support for upgrading previous versions to OCP-CE V4.2.1
+ - new features: compatibility updates for OBDiag V1.4.0 and V1.3.0
+ - new features: compatibility with kylin OS
+ - enhancements: improved pre-launch checks for OceanBase databases
+ - improvements: enhanced error messaging during SQL execution and provide SQL execution Trace
+ - bug fixes: fixed an issue where deploying OceanBase V4.2.0 and above with local_ip would still perform NIC checks
+ - bug fixes: resolved a RuntimeError that could occur when destroying clusters deployed with OBD versions prior to V2.3.0
+ - bug fixes: fixed an issue where edit-config could not exit after enabling IO_DEFAULT_CONFIRM
+* Fri Oct 13 2023 obd 2.3.1
+ - new features: adapt to OCP Express V4.2.1
+ - bug fixes: fix checks during rolling upgrade that did not behave as expected under special circumstances
+ - bug fixes: resolve self-upgrade extraction failure of obd on el8 operating systems
+ - bug fixes: unexpected exceptions in obd cluster chst with ob-configserver component
+ - bug fixes: unexpected exceptions in ob-configserver when connection_url is not configured
+* Fri Sep 15 2023 obd 2.3.0
+ - new features: support for OceanBase 4.2 network-based primary/standby solution
+ - new features: support for ConfigServer
+ - new features: support for selecting units for capacity type parameters during web-based graphical deployment
+* Wed Aug 02 2023 obd 2.2.0
+ - new features: adapt to OceanBase-CE V4.2
+ - new features: introduce 19G storage option for small-scale deployment with OceanBase-CE V4.2
+ - new features: adapt to OCP Express V4.2
+ - new features: web-based graphical deployment now allows for custom component selection
+ - optimization: improved OBProxy startup performance on machines with low specs
+ - change: redeploy now requires confirmation, can be bypassed with --confirm option
+ - change: automatic confirmation for all commands can be enabled with obd env set IO_DEFAULT_CONFIRM 1
+ - fix bug: fixed the issue where OCP Express ocp_meta_tenant setting was not effective
+ - fix bug: fixed incorrect recognition of custom capacity type parameters in obd demo
* Mon Jun 12 2023 obd 2.1.1
- new features: support upgrade keyword 'when_come_from' and 'deprecated'
- fix bug: start server failed when other servers downtime #171
diff --git a/service/api/response_utils.py b/service/api/response_utils.py
index 629bf41..5168f1f 100644
--- a/service/api/response_utils.py
+++ b/service/api/response_utils.py
@@ -51,6 +51,7 @@ def new_not_found_exception(ex):
def new_internal_server_error_exception(ex):
log.get_logger().error("got internal server error exception: {0}".format(traceback.format_exc()))
+ log.get_logger().error("Runing Exception: {}".format(ex))
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, detail="internal server error, exception: {0}".format(ex))
diff --git a/service/api/v1/installer.py b/service/api/v1/installer.py
new file mode 100644
index 0000000..1fa1a47
--- /dev/null
+++ b/service/api/v1/installer.py
@@ -0,0 +1,66 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from fastapi import APIRouter, Path
+from fastapi import BackgroundTasks
+
+from _deploy import UserConfig
+from service.api.response import OBResponse
+from service.api.response_utils import new_internal_server_error_exception, new_ok_response
+from service.model.server import UserInfo
+from service.handler import handler_utils
+
+
+router = APIRouter()
+
+
+# @router.get("/upgrade/info/{cluster_name}",
+# response_model=OBResponse[ServerInfo],
+# description='get upgrade server info',
+# operation_id='get_upgrade_server_info',
+# tags=['Info'])
+# async def get_server_info(cluster_name: str = Path(description="ocp cluster_name")):
+# try:
+# handler = handler_utils.new_server_info_handler()
+# service_info = handler.get_upgrade_cluster_info(cluster_name)
+# if service_info.metadb:
+# service_info.metadb.password = ''
+# except Exception as ex:
+# return new_internal_server_error_exception(ex)
+# return new_ok_response(service_info)
+
+
+@router.post("/suicide",
+ response_model=OBResponse,
+ description='exit after a while',
+ operation_id='suicide',
+ tags=['Process'])
+async def suicide(backgroundtasks: BackgroundTasks):
+ handler = handler_utils.new_common_handler()
+ backgroundtasks.add_task(handler.suicide)
+ return new_ok_response("suicide")
+
+
+@router.get("/get/user",
+ response_model=OBResponse[UserInfo],
+ description='get system user',
+ operation_id='user',
+ tags=['User'])
+async def get_user():
+ username = UserConfig.DEFAULT.get('username')
+ return new_ok_response(UserInfo(username=username))
diff --git a/service/api/v1/metadb.py b/service/api/v1/metadb.py
new file mode 100644
index 0000000..dace5d0
--- /dev/null
+++ b/service/api/v1/metadb.py
@@ -0,0 +1,62 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from fastapi import APIRouter, Path, Query, BackgroundTasks
+
+from service.api.response import OBResponse, DataList
+from service.api import response_utils
+from service.model.metadb import MetadbDeploymentInfo, MetadbDeploymentConfig, DatabaseConnection, RecoverChangeParameter
+from service.model.resource import MetaDBResource, ResourceCheckResult
+from service.model.task import TaskInfo, PrecheckTaskInfo, TaskLog
+from service.handler.handler_utils import new_metadb_handler
+
+router = APIRouter()
+
+
+@router.post("/metadb/connections",
+ response_model=OBResponse[DatabaseConnection],
+ description='create metadb connection',
+ operation_id='create_metadb_connection',
+ tags=['Metadb'])
+async def create_metadb_connection(
+ sys: bool = Query(False, description='whether the incoming tenant is the sys tenant'),
+ metadb_connection: DatabaseConnection = ...
+ ):
+ handler = new_metadb_handler()
+ try:
+ connection_info = handler.create_connection_info(metadb_connection, sys)
+ return response_utils.new_ok_response(connection_info)
+ except Exception as e:
+ return response_utils.new_internal_server_error_exception(e)
+
+
+@router.get("/metadb/connections/{cluster_name}",
+ response_model=OBResponse[DatabaseConnection],
+ description='get metadb connection',
+ operation_id='get_metadb_connection',
+ tags=['Metadb'])
+async def get_metadb_connection(cluster_name: str = Path(description="cluster name")):
+ handler = new_metadb_handler()
+ connection_info = handler.get_connection_info(cluster_name)
+ if connection_info is None:
+ return response_utils.new_internal_server_error_exception(Exception("deployment {0} not found".format(id)))
+ else:
+ return response_utils.new_ok_response(connection_info)
+
+
+
diff --git a/service/api/v1/ocp_deployments.py b/service/api/v1/ocp_deployments.py
new file mode 100644
index 0000000..3d0acbd
--- /dev/null
+++ b/service/api/v1/ocp_deployments.py
@@ -0,0 +1,368 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+
+from fastapi import APIRouter, Path, Query, BackgroundTasks, Body
+
+from service.api.response import OBResponse, DataList
+from service.handler import handler_utils
+from service.model.deployments import OCPDeploymnetConfig, PreCheckResult, RecoverChangeParameter, TaskInfo, \
+ ConnectionInfo, InstallLog, Deployment, DeploymentInfo, DeploymentReport, DeploymentStatus, UserCheck
+from service.model.task import TaskInfo, PrecheckTaskInfo, TaskLog
+from service.model.database import DatabaseConnection
+from service.model.ocp import OcpInfo, OcpDeploymentInfo, OcpDeploymentConfig, OcpResource, OcpInstalledInfo, OcpUpgradeLostAddress
+from service.model.metadb import RecoverChangeParameter
+from service.api import response_utils
+
+router = APIRouter()
+
+
+@router.get("/ocp/info/{id}",
+ response_model=OBResponse[OcpInstalledInfo],
+ description='get_installed_ocp_info',
+ operation_id='get_installed_ocp_info',
+ tags=['OCP'])
+async def get_installed_ocp_info(id: int = Path(description="deployment id")):
+ ocp_handler = handler_utils.new_ocp_handler()
+ try:
+ info = ocp_handler.get_installed_ocp_info(id)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(info)
+
+
+@router.post("/ocp_deployments/{name}",
+ response_model=OBResponse,
+ description='create ocp deployment config',
+ operation_id='createOcpDeploymentConfig',
+ tags=['OCP'])
+async def create_deployment(name: str = Path(description='name'),
+ config: OCPDeploymnetConfig = ...):
+ handler = handler_utils.new_ocp_handler()
+ cluster = None
+ try:
+ ocp_config_path = handler.create_ocp_config_path(config)
+ cluster = handler.create_ocp_deployment(name, ocp_config_path)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ if cluster:
+ return response_utils.new_ok_response(cluster)
+ else:
+ return response_utils.new_bad_request_exception(Exception('deployment {0} already exists'.format(name)))
+
+
+@router.post("/machine/check/user",
+ response_model=OBResponse,
+ description='Check if the user input exists',
+ operation_id='machineUser',
+ tags=['OCP']
+ )
+async def check_user(user: UserCheck = Body(description='server, port, username, password')):
+ handler = handler_utils.new_ocp_handler()
+ exist = None
+ try:
+ exist = handler.check_user(user)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ if exist:
+ return response_utils.new_ok_response(exist)
+ else:
+ return response_utils.new_bad_request_exception(Exception('user {0} user/password error'.format(user)))
+
+
+@router.post("/ocp/deployments/{id}/precheck",
+ response_model=OBResponse[TaskInfo],
+ description='precheck for ocp deployment',
+ operation_id='precheck_ocp_deployment',
+ tags=['OCP'])
+async def precheck_ocp_deployment(background_tasks: BackgroundTasks,
+ id: int = Path(description="deployment id")):
+ try:
+ handler = handler_utils.new_ocp_handler()
+ ret = handler.ocp_precheck(id, background_tasks)
+ if not isinstance(ret, TaskInfo) and ret:
+ return response_utils.new_internal_server_error_exception(str(ret[1].args[0]))
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(ret)
+
+
+@router.get("/ocp/deployments/{id}/precheck/{task_id}",
+ response_model=OBResponse[PrecheckTaskInfo],
+ description='precheck for ocp deployment',
+ operation_id='precheck_ocp',
+ tags=['OCP'])
+async def get_ocp_precheck_task(id: int = Path(description="deployment id"),
+ task_id: int = Path(description="task id")):
+ handler = handler_utils.new_ocp_handler()
+ try:
+ precheck_result = handler.get_precheck_result(id, task_id)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(precheck_result)
+
+
+@router.post("/ocp/deployments/{id}/recover",
+ response_model=OBResponse[DataList[RecoverChangeParameter]],
+ description='recover ocp deployment config',
+ operation_id='recover_ocp_deployment',
+ tags=['OCP'])
+async def recover_ocp_deployment(id: int = Path(description="deployment id")):
+ handler = handler_utils.new_ocp_handler()
+ try:
+ recover_result = handler.recover(id)
+ return response_utils.new_ok_response(recover_result)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+
+
+@router.post("/ocp/deployments/{id}/install",
+ response_model=OBResponse[TaskInfo],
+ description='install ocp',
+ operation_id='install_ocp',
+ tags=['OCP'])
+async def install_ocp(background_tasks: BackgroundTasks, id: int = Path(description="deployment id")):
+ handler = handler_utils.new_ocp_handler()
+ ret = handler.install(id, background_tasks)
+ if not isinstance(ret, TaskInfo) and ret:
+ return response_utils.new_internal_server_error_exception(ret[1])
+ return response_utils.new_ok_response(ret)
+
+
+@router.get("/ocp/deployments/{id}/install/{task_id}",
+ response_model=OBResponse[TaskInfo],
+ description='get ocp install task',
+ operation_id='get_ocp_install_task',
+ tags=['OCP'])
+async def get_ocp_install_task(id: int = Path(description="deployment id"),
+ task_id: int = Path(description="task id")):
+ handler = handler_utils.new_ocp_handler()
+ task_info = handler.get_install_task_info(id, task_id)
+ if not isinstance(task_info, TaskInfo):
+ return response_utils.new_internal_server_error_exception("task {0} not found".format(id))
+ return response_utils.new_ok_response(task_info)
+
+
+@router.get("/ocp/deployments/{id}/install/{task_id}/log",
+ response_model=OBResponse[TaskLog],
+ description='get ocp install task log',
+ operation_id='get_ocp_install_task_log',
+ tags=['OCP'])
+async def get_ocp_install_task_log(id: int = Path(description="deployment id"),
+ task_id: int = Path(description="task id"),
+ offset: int = Query(0, description="offset to read task log")):
+ handler = handler_utils.new_ocp_handler()
+ task_info = handler.get_install_task_info(id, task_id)
+ if task_info is None:
+ return response_utils.new_internal_server_error_exception("task {0} not found".format(id))
+ log_content = handler.buffer.read()
+ # log_info = InstallLog(log=log_content[offset:], offset=len(log_content))
+ return response_utils.new_ok_response(TaskLog(log=log_content, offset=offset))
+
+
+@router.post("/ocp/deployments/{id}/reinstall",
+ response_model=OBResponse[TaskInfo],
+ description='reinstall ocp',
+ operation_id='reinstall_ocp',
+ tags=['OCP'])
+async def reinstall_ocp(background_tasks: BackgroundTasks, id: int = Path(description="deployment id")):
+ handler = handler_utils.new_ocp_handler()
+ ret = handler.reinstall(id, background_tasks)
+ if not isinstance(ret, TaskInfo) and ret:
+ return response_utils.new_internal_server_error_exception(ret[1])
+ return response_utils.new_ok_response(ret)
+
+
+@router.get("/ocp/deployments/{id}/reinstall/{task_id}",
+ response_model=OBResponse[TaskInfo],
+ description='get ocp reinstall task',
+ operation_id='get_ocp_reinstall_task',
+ tags=['OCP'])
+async def get_ocp_reinstall_task(id: int = Path(description="deployment id"),
+ task_id: int = Path(description="task id")):
+ handler = handler_utils.new_ocp_handler()
+ task_info = handler.get_reinstall_task_info(id, task_id)
+ if not isinstance(task_info, TaskInfo):
+ return response_utils.new_internal_server_error_exception("task {0} not found".format(id))
+ return response_utils.new_ok_response(task_info)
+
+
+@router.get("/ocp/deployments/{id}/reinstall/{task_id}/log",
+ response_model=OBResponse[TaskLog],
+ description='get ocp reinstall task log',
+ operation_id='get_ocp_reinstall_task_log',
+ tags=['OCP'])
+async def get_ocp_reinstall_task_log(id: int = Path(description="deployment id"),
+ task_id: int = Path(description="task id"),
+ offset: int = Query(0, description="offset to read task log")):
+ handler = handler_utils.new_ocp_handler()
+ task_info = handler.get_reinstall_task_info(id, task_id)
+ if task_info is None:
+ return response_utils.new_internal_server_error_exception("task {0} not found".format(id))
+ log_content = handler.buffer.read()
+ # log_info = InstallLog(log=log_content[offset:], offset=len(log_content))
+ return response_utils.new_ok_response(TaskLog(log=log_content, offset=offset))
+
+
+@router.delete("/ocp/deployments/{id}",
+ response_model=OBResponse[TaskInfo],
+ description='destroy ocp',
+ operation_id='destroy_ocp',
+ tags=['OCP'])
+async def destroy_ocp(id: int, background_tasks: BackgroundTasks):
+ handler = handler_utils.new_ocp_handler()
+ try:
+ info = handler.destroy(id, background_tasks)
+ except Exception as ex:
+ raise response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(info)
+
+
+@router.get("/ocp/deployments/{id}/destroy/{task_id}",
+ response_model=OBResponse[TaskInfo],
+ description='get ocp destroy task',
+ operation_id='get_ocp_destroy_task',
+ tags=['OCP'])
+async def get_ocp_destroy_task(background_tasks: BackgroundTasks,
+ id: int = Path(description="deployment id"),
+ task_id: int = Path(description="task id")):
+ handler = handler_utils.new_ocp_handler()
+ info = handler.get_destroy_task_info(id, task_id)
+ if not isinstance(info, TaskInfo):
+ return response_utils.new_internal_server_error_exception(info[1])
+ return response_utils.new_ok_response(info)
+
+
+@router.post("/ocp",
+ response_model=OBResponse[OcpInfo],
+ description='create ocp info',
+ operation_id='create_ocp_info',
+ tags=['OCP'])
+async def create_ocp_info(metadb: DatabaseConnection = ...):
+ ocp_handler = handler_utils.new_ocp_handler()
+ try:
+ data = ocp_handler.create_ocp_info(metadb)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(data)
+
+
+@router.get("/ocp/{cluster_name}",
+ response_model=OBResponse[OcpInfo],
+ description='get ocp info',
+ operation_id='get_ocp_info',
+ tags=['OCP'])
+async def get_ocp_info(cluster_name: str = Path(description="ocp cluster_name")):
+ ocp_handler = handler_utils.new_ocp_handler()
+ try:
+ data = ocp_handler.get_ocp_info(cluster_name)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(data)
+
+
+@router.post("/ocp/{cluster_name}/upgrade/precheck",
+ response_model=OBResponse[TaskInfo],
+ description='post precheck for ocp upgrade',
+ operation_id='precheck_ocp_upgrade',
+ tags=['OCP'])
+async def precheck_ocp_upgrade(background_tasks: BackgroundTasks,
+ cluster_name: str = Path(description="deployment cluster_name")):
+ handler = handler_utils.new_ocp_handler()
+ ret = handler.upgrade_precheck(cluster_name, background_tasks)
+ if not isinstance(ret, TaskInfo) and ret:
+ return response_utils.new_internal_server_error_exception(ret[1])
+ return response_utils.new_ok_response(ret)
+
+
+@router.get("/ocp/{cluster_name}/upgrade/precheck/{task_id}",
+ response_model=OBResponse[PrecheckTaskInfo],
+ description='get precheck for ocp upgrade',
+ operation_id='get_ocp_upgrade_precheck_task',
+ tags=['OCP'])
+async def get_ocp_upgrade_precheck_task(cluster_name: str = Path(description="ocp cluster_name"),
+ task_id: int = Path(description="task id")):
+ handler = handler_utils.new_ocp_handler()
+ try:
+ precheck_result = handler.get_upgrade_precheck_result(cluster_name, task_id)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(precheck_result)
+
+
+@router.post("/ocp/{cluster_name}/upgrade",
+ response_model=OBResponse[TaskInfo],
+ description='upgrade ocp',
+ operation_id='upgrade_ocp',
+ tags=['OCP'])
+async def upgrade_ocp(
+ background_tasks: BackgroundTasks,
+ cluster_name: str = Path(description="ocp cluster_name"),
+ version: str = Query(description="ocp upgrade version"),
+ usable: str = Query('', description="ocp upgrade hash")
+):
+ handler = handler_utils.new_ocp_handler()
+ ret = handler.upgrade_ocp(cluster_name, version, usable, background_tasks)
+ if not isinstance(ret, TaskInfo) and ret:
+ return response_utils.new_internal_server_error_exception(ret[1])
+ return response_utils.new_ok_response(ret)
+
+
+@router.get("/ocp/{cluster_name}/upgrade/{task_id}",
+ response_model=OBResponse[TaskInfo],
+ description='get ocp upgrade task',
+ operation_id='get_ocp_upgrade_task',
+ tags=['OCP'])
+async def get_ocp_upgrade_task(cluster_name: str = Path(description="ocp cluster_name"),
+ task_id: int = Path(description="task id")):
+ handler = handler_utils.new_ocp_handler()
+ task_info = handler.get_ocp_upgrade_task(cluster_name, task_id)
+ if not isinstance(task_info, TaskInfo):
+ return response_utils.new_internal_server_error_exception("task {0} not found".format(cluster_name))
+ return response_utils.new_ok_response(task_info)
+
+
+@router.get("/ocp/{cluster_name}/upgrade/{task_id}/log",
+ response_model=OBResponse[TaskLog],
+ description='get ocp upgrade task log',
+ operation_id='get_ocp_upgrade_task_log',
+ tags=['OCP'])
+async def get_ocp_upgrade_task_log(cluster_name: str = Path(description="ocp cluster_name"),
+ task_id: int = Path(description="task id"),
+ offset: int = Query(0, description="offset to read task log")):
+ handler = handler_utils.new_ocp_handler()
+ task_info = handler.get_ocp_upgrade_task(cluster_name, task_id)
+ if task_info is None:
+ return response_utils.new_internal_server_error_exception("task {0} not found".format(cluster_name))
+ log_content = handler.buffer.read()
+ # log_info = InstallLog(log=log_content[offset:], offset=len(log_content))
+ return response_utils.new_ok_response(TaskLog(log=log_content, offset=offset))
+
+
+@router.get("/ocp/upgraade/agent/hosts",
+ response_model=OBResponse[OcpUpgradeLostAddress],
+ description='get ocp not upgrading host',
+ operation_id='get_ocp_not_upgrading_host',
+ tags=['OCP'])
+async def get_ocp_upgrade_task_log():
+ handler = handler_utils.new_ocp_handler()
+ try:
+ ret = handler.get_not_upgrade_host()
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+ return response_utils.new_ok_response(ret)
\ No newline at end of file
diff --git a/service/api/v1/service_info.py b/service/api/v1/service_info.py
index 0ffe200..2468328 100644
--- a/service/api/v1/service_info.py
+++ b/service/api/v1/service_info.py
@@ -17,12 +17,14 @@
# You should have received a copy of the GNU General Public License
# along with OceanBase Deploy. If not, see .
-from fastapi import APIRouter
+from fastapi import APIRouter, Query, Body
from service.api import response_utils
from service.api.response import OBResponse
from service.handler import handler_utils
-from service.model.service_info import ServiceInfo
+from service.model.service_info import ServiceInfo, DeployName
+from service.model.database import DatabaseConnection
+from service.model.server import OcpServerInfo
router = APIRouter()
@@ -36,3 +38,58 @@ async def get_info():
handler = handler_utils.new_service_info_handler()
service_info = handler.get_service_info()
return response_utils.new_ok_response(service_info)
+
+
+@router.get("/deployment/names",
+ response_model=OBResponse[DeployName],
+ description='get deployment names',
+ operation_id='getDeploymentNames',
+ tags=['Info'])
+async def get_deployment_names():
+ try:
+ handler = handler_utils.new_service_info_handler()
+ deploy_names = handler.get_deployments_name()
+ return response_utils.new_ok_response(deploy_names)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+
+
+@router.get("/deployment/metadb/connection",
+ response_model=OBResponse[DatabaseConnection],
+ description='get connection info',
+ operation_id='getConnectionInfo',
+ tags=['Info'])
+async def get_metadb_connection(name: str = Query(..., description='cluster name')):
+ try:
+ handler = handler_utils.new_service_info_handler()
+ metadb = handler.get_metadb_connection(name)
+ return response_utils.new_ok_response(metadb)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+
+@router.post("/deployment/ocp/agent/ip",
+ response_model=OBResponse[OcpServerInfo],
+ description='get ocp server info',
+ operation_id='getOcpServerInfo',
+ tags=['Info'])
+async def post_metadb_connection(metadb: DatabaseConnection = Body(..., description='cluster name')):
+ try:
+ handler = handler_utils.new_service_info_handler()
+ metadb = handler.get_component_agent(metadb)
+ return response_utils.new_ok_response(metadb)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
+
+
+@router.post("/deployment/upgrade/ocp",
+ response_model=OBResponse,
+ description='get obd info',
+ operation_id='create ocp deployment',
+ tags=['Info'])
+async def create_ocp_deployment(name: str = Query(..., description='cluster name')):
+ try:
+ handler = handler_utils.new_service_info_handler()
+ metadb = handler.create_ocp_info(name)
+ return response_utils.new_ok_response(metadb)
+ except Exception as ex:
+ return response_utils.new_internal_server_error_exception(ex)
\ No newline at end of file
diff --git a/service/app.py b/service/app.py
index 1f075de..368f556 100644
--- a/service/app.py
+++ b/service/app.py
@@ -22,6 +22,7 @@
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
+from fastapi.middleware.cors import CORSMiddleware
from starlette.staticfiles import StaticFiles
from starlette_prometheus import metrics, PrometheusMiddleware
@@ -33,12 +34,17 @@
from service.middleware.request_response_log import RequestResponseLogMiddleware
from service.middleware.process_time import ProcessTimeMiddleware
from service.handler import handler_utils
+from service.middleware.ip_white import IPBlockMiddleware
+from service.api.v1 import ocp_deployments
+from service.api.v1 import metadb
+from service.api.v1 import installer
+
app = FastAPI()
class OBDWeb(object):
- def __init__(self, obd, resource_path):
+ def __init__(self, obd, white_ip_list=None, resource_path="./"):
CoreManager.INSTANCE = obd
self.app = app
self.app.add_route("/metrics", metrics)
@@ -47,6 +53,10 @@ def __init__(self, obd, resource_path):
self.app.include_router(common.router, prefix='/api/v1')
self.app.include_router(service_info.router, prefix='/api/v1')
self.app.include_router(mirror.router, prefix='/api/v1')
+ self.app.include_router(ocp_deployments.router, prefix='/api/v1')
+ self.app.include_router(metadb.router, prefix='/api/v1')
+ self.app.include_router(installer.router, prefix='/api/v1')
+ self.app.add_middleware(IPBlockMiddleware, ips=white_ip_list)
self.app.add_middleware(ProcessTimeMiddleware)
self.app.add_middleware(RequestResponseLogMiddleware, logger=log.get_logger())
self.app.add_middleware(PrometheusMiddleware)
diff --git a/service/common/const.py b/service/common/const.py
index 43b5478..7c8d150 100644
--- a/service/common/const.py
+++ b/service/common/const.py
@@ -39,12 +39,15 @@
OBPROXY = 'obproxy'
OCP_EXPRESS = 'ocpexpress'
+OCP_SERVER_CE = 'ocp-server-ce'
+OCP_SERVER = 'ocp-server'
OBAGENT = 'obagent'
DESTROY_PLUGIN = "destroy"
INIT_PLUGINS = ("init",)
START_PLUGINS = ("start_check", "start", "connect", "bootstrap", "display")
+UPGRADE_PLUGINS = ("upgrade")
# filter component of oceanbase and obproxy version above 4.0
VERSION_FILTER = {
OCEANBASE: "4.0.0.0",
diff --git a/service/common/core.py b/service/common/core.py
index e94f8c9..1fb008c 100644
--- a/service/common/core.py
+++ b/service/common/core.py
@@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with OceanBase Deploy. If not, see .
from collections import defaultdict
-
from singleton_decorator import singleton
from _stdio import BufferIO
diff --git a/service/common/task.py b/service/common/task.py
index a22968f..87260f7 100644
--- a/service/common/task.py
+++ b/service/common/task.py
@@ -137,10 +137,14 @@ def __init__(self, task_type=DEFAULT_TASK_TYPE):
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
+ ret = None
self.lock.acquire()
try:
- func(*args, **kwargs)
+ ret = func(*args, **kwargs)
+ except Exception as ex:
+ ret = False, ex
finally:
self.lock.release()
+ return ret
return wrapper
diff --git a/service/handler/base_handler.py b/service/handler/base_handler.py
index 56993a5..671674b 100644
--- a/service/handler/base_handler.py
+++ b/service/handler/base_handler.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with OceanBase Deploy. If not, see .
-from collections import defaultdict
-
-from _plugin import PluginContextNamespace
from service.common import core
SPACENAME = "API"
+
+
class BaseHandler(object):
def __init__(self):
self._obd = core.CoreManager().get_obd()
diff --git a/service/handler/component_handler.py b/service/handler/component_handler.py
index 405e240..b3c834c 100644
--- a/service/handler/component_handler.py
+++ b/service/handler/component_handler.py
@@ -109,6 +109,10 @@ def list_components(self):
componentInfo.version_type = const.CE
for componentInfo in component_dict[const.OBPROXY]:
componentInfo.version_type = const.BUSINESS
+ for componentInfo in component_dict[const.OCP_SERVER_CE]:
+ componentInfo.version_type = const.CE
+ for componentInfo in component_dict[const.OCP_SERVER]:
+ componentInfo.version_type = const.BUSINESS
if const.OCEANBASE in component_dict.keys() and const.OCEANBASE_CE in component_dict.keys():
component_dict[const.OCEANBASE].extend(component_dict[const.OCEANBASE_CE])
@@ -124,6 +128,13 @@ def list_components(self):
elif const.OBPROXY_CE in component_dict.keys():
component_dict[const.OBPROXY] = component_dict[const.OBPROXY_CE]
component_dict.pop(const.OBPROXY_CE)
+ if const.OCP_SERVER in component_dict.keys() and const.OCP_SERVER_CE in component_dict.keys():
+ component_dict[const.OCP_SERVER].extend(component_dict[const.OCP_SERVER_CE])
+ component_dict.pop(const.OCP_SERVER_CE)
+ component_dict[const.OCP_SERVER].sort(key=lambda x: x.version, reverse=True)
+ elif const.OCP_SERVER_CE in component_dict.keys():
+ component_dict[const.OCP_SERVER] = component_dict[const.OCP_SERVER_CE]
+ component_dict.pop(const.OCP_SERVER_CE)
for name, info in component_dict.items():
component_list.append(Component(name=name, info=info))
return component_list
diff --git a/service/handler/handler_utils.py b/service/handler/handler_utils.py
index b3bc5e9..d8f2cc1 100644
--- a/service/handler/handler_utils.py
+++ b/service/handler/handler_utils.py
@@ -22,6 +22,8 @@
from service.handler.service_info_handler import ServiceInfoHandler
from service.handler.comment_handler import CommonHandler
from service.handler.mirror_handler import MirrorHandler
+from service.handler.ocp_handler import OcpHandler
+from service.handler.metadb_handler import MetadbHandler
def new_component_handler():
@@ -42,3 +44,15 @@ def new_service_info_handler():
def new_mirror_handler():
return MirrorHandler()
+
+
+def new_ocp_handler():
+ return OcpHandler()
+
+
+def new_metadb_handler():
+ return MetadbHandler()
+
+
+def new_server_info_handler():
+ return ServiceInfoHandler()
diff --git a/service/handler/metadb_handler.py b/service/handler/metadb_handler.py
new file mode 100644
index 0000000..aa943ec
--- /dev/null
+++ b/service/handler/metadb_handler.py
@@ -0,0 +1,907 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+import copy
+import yaml
+import tempfile
+from collections import defaultdict
+from optparse import Values
+from singleton_decorator import singleton
+
+from service.handler.base_handler import BaseHandler
+from service.common import log, task, util, const
+from service.common.task import Serial as serial
+from service.common.task import AutoRegister as auto_register
+from service.model.ssh import SshAuthMethod
+from service.model.metadb import MetadbDeploymentInfo, RecoverChangeParameter, MetadbDeploymentConfig, Flag
+from service.model.deployments import OCPDeploymentStatus
+from service.model.parameter import Parameter
+from service.model.resource import DiskInfo, Disk, MetaDBResource, ResourceCheckResult
+from service.model.database import DatabaseConnection
+from service.model.task import TaskStatus, TaskResult, TaskInfo, PreCheckResult, PrecheckTaskInfo, PrecheckEventResult, TaskStepInfo
+from _deploy import DeployStatus, DeployConfigStatus
+from _errno import CheckStatus, FixEval
+from tool import Cursor
+
+
+@singleton
+class MetadbHandler(BaseHandler):
+
+ def generate_metadb_config_path(self, config):
+ cluster_config = {}
+
+ if config is not None:
+ self.generate_metadb_config(cluster_config, config)
+ if config.auth is not None:
+ self.generate_auth_config(cluster_config, config.auth)
+
+ with tempfile.NamedTemporaryFile(delete=False, prefix="ocp", suffix=".yaml", mode="w", encoding="utf-8") as f:
+ f.write(yaml.dump(cluster_config, sort_keys=False))
+ cluster_config_yaml_path = f.name
+ log.get_logger().info('dump metadb config from path: %s' % cluster_config_yaml_path)
+ self.context['id'] = self.context['id'] + 1 if self.context['id'] else 1
+ self.context['deployment_info'][self.context['id']] = {'status': OCPDeploymentStatus.INIT.value, 'config': config, 'connection': None}
+ self.context['meta_path'] = cluster_config_yaml_path
+ return cluster_config_yaml_path
+
+ def generate_auth_config(self, cluster_config, auth):
+ if 'user' not in cluster_config.keys():
+ cluster_config['user'] = {}
+ cluster_config['user']['username'] = auth.user
+ cluster_config['user']['password'] = auth.password
+ cluster_config['user']['private_key'] = '' if auth.auth_method == SshAuthMethod.PASSWORD else auth.private_key
+ cluster_config['user']['port'] = auth.port
+
+ def generate_metadb_config(self, cluster_config, config):
+ log.get_logger().debug('generate metadb config')
+ oceanbase_config = dict()
+ config_dict = config.dict()
+
+ if config_dict.get('servers'):
+ oceanbase_config['servers'] = config.servers
+
+ if 'global' not in oceanbase_config.keys():
+ oceanbase_config['global'] = {}
+
+ for key in config_dict:
+ if config_dict[key] and key in {'sql_port', 'rpc_port', 'home_path', 'data_dir', 'log_dir', 'appname',
+ 'root_password', 'devname'}:
+ if key == 'sql_port':
+ oceanbase_config['global']['mysql_port'] = config_dict[key]
+ continue
+ if key == 'data_dir':
+ oceanbase_config['global']['data_dir'] = config_dict[key]
+ continue
+ if key == 'log_dir':
+ oceanbase_config['global']['redo_dir'] = config_dict[key]
+ continue
+ oceanbase_config['global'][key] = config_dict[key]
+
+ if config.home_path == '':
+ oceanbase_config['global']['home_path'] = config.home_path + '/oceanbase'
+
+ if config.parameters:
+ for parameter in config.parameters:
+ oceanbase_config['global'][parameter.name] = parameter.value
+ cluster_config[const.OCEANBASE_CE] = oceanbase_config
+
+ def create_deployment(self, name: str, config_path: str):
+ log.get_logger().info('in deploy metadb stage')
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ if deploy:
+ deploy_info = deploy.deploy_info
+ if deploy_info.status == DeployStatus.STATUS_DEPLOYED:
+ self.destroy_name(name, deploy)
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ deploy_info = deploy.deploy_info
+ if deploy_info.status not in [DeployStatus.STATUS_CONFIGURED, DeployStatus.STATUS_DESTROYED]:
+ log.get_logger().error('Deploy "%s" is %s. You could not deploy an %s cluster.' % (
+ name, deploy_info.status.value, deploy_info.status.value))
+ raise Exception('Deploy "%s" is %s. You could not deploy an %s cluster.' % (
+ name, deploy_info.status.value, deploy_info.status.value))
+ if deploy_info.config_status != DeployConfigStatus.UNCHNAGE:
+ log.get_logger().info('Apply temp deploy configuration')
+ if not deploy.apply_temp_deploy_config():
+ log.get_logger().error('Failed to apply new deploy configuration')
+ raise Exception('Failed to apply new deploy configuration')
+
+ deploy = self.obd.deploy_manager.create_deploy_config(name, config_path)
+ if not deploy:
+ log.get_logger().error('Failed to create deploy: %s. please check you configuration file' % name)
+ raise Exception('Failed to create deploy: %s. please check you configuration file' % name)
+ self.obd.set_deploy(deploy)
+ log.get_logger().info('cluster config path: %s ' % config_path)
+ self.context['deployment_id'][self.context['id']] = name
+ return self.context['id']
+
+ def generate_secure_metadb_deployment(self, metadb_deployment):
+ metadb_deployment_copy = copy.deepcopy(metadb_deployment)
+ metadb_deployment_copy.root_password = ''
+ if metadb_deployment_copy.auth:
+ metadb_deployment_copy.auth.password = ''
+ return metadb_deployment_copy
+
+ def destroy_name(self, name, deploy):
+ self.obd.set_options(Values({'force_kill': True}))
+ log.get_logger().info('start destroy %s' % name)
+ if self.obd.repositories:
+ repositories = [repository for repository in self.obd.repositories if repository.name == 'oceanbase-ce']
+ self.obd.set_repositories(repositories)
+ self.obd._destroy_cluster(self.obd.deploy, repositories)
+ else:
+ self.obd.destroy_cluster(name)
+ log.get_logger().info('destroy %s end' % name)
+ deploy.update_deploy_status(DeployStatus.STATUS_CONFIGURED)
+
+ def list_metadb_deployments(self):
+ data = []
+ for id, deployment_info in self.context['deployment_info'].items():
+ if deployment_info:
+ meta_deployment_info = MetadbDeploymentInfo()
+ meta_deployment_info.id = id
+ meta_deployment_info.status = deployment_info['status']
+ deployment_info['config'].root_password = ''
+ if deployment_info['config'].auth:
+ deployment_info['config'].auth.password = ''
+ meta_deployment_info.config = deployment_info['config']
+ meta_deployment_info.connection = deployment_info['connection']
+ data.append(meta_deployment_info)
+ return data
+
+ def get_server_disk_info(self, client, paths, data):
+ for path in paths:
+ for _ in client.execute_command(
+ "df --block-size=1g %s | awk '{if(NR>1)print}'" % path).stdout.strip().split('\n'):
+ _ = [i for i in _.split(' ') if i != '']
+ dev = _[0]
+ mount_path = _[5]
+ total_size = _[1]
+ free_size = _[3]
+ _disk_info = DiskInfo(dev=dev, mount_path=mount_path, total_size=total_size, free_size=free_size)
+ data.append(Disk(path=path, disk_info=_disk_info))
+ return data
+
+ def get_server_memory_info(self, client, resource_check_results, address):
+ memory_free = client.execute_command(
+ "cat /proc/meminfo|grep MemFree|cut -f2 -d:|uniq | awk '{print $1}'").stdout.strip()
+ memory_higher_limit = int(int(memory_free) / 1024 / 1024)
+ memory_default = max(int(int(int(memory_free) / 1024 / 1024) * 0.7), 6)
+ memory_lower_limit = 6
+
+ if memory_higher_limit < memory_lower_limit:
+ resource_check_results.append(ResourceCheckResult(
+ address=address, name='memory_limit', check_result=False,
+ error_message=[f'{address}: memory is not enough'])
+ )
+ return memory_higher_limit, memory_default, memory_lower_limit
+
+ def check_dir_empty(self, client, paths, address, resource_check_results, user):
+ def check_directory(client, path, path_name):
+ check_result = ResourceCheckResult(address=address, name=path_name)
+ ret = client.execute_command(f'ls {path}')
+ if not ret or ret.stdout.strip():
+ check_result.check_result = False
+ check_result.error_message.append(f'{address}: {path} is not empty')
+ return check_result
+
+ for path in paths:
+ if not client.execute_command('mkdir -p %s' % path):
+ raise Exception('%s@%s: dir Permission denied' % (user, address))
+ resource_check_results.append(check_directory(client, paths[0], 'home_path'))
+ resource_check_results.append(check_directory(client, paths[1], 'data_dir'))
+ resource_check_results.append(check_directory(client, paths[2], 'log_diir'))
+ return resource_check_results
+
+ def cal_cluster_resource(self, resource_check_results, address, paths, data):
+ flag = Flag.not_matched.value
+ data_size_default = 10
+ log_size_default = 20
+ if data[0].disk_info.mount_path == data[1].disk_info.mount_path == data[2].disk_info.mount_path:
+ data_size_default = int((int(data[0].disk_info.free_size) - 20) * 0.6)
+ log_size_default = int((int(data[0].disk_info.free_size) - 20) * 0.4)
+ flag = Flag.same_disk.value
+ elif data[1].disk_info.mount_path == data[2].disk_info.mount_path:
+ data_size_default = int((int(data[1].disk_info.free_size) - 20) * 0.6)
+ log_size_default = int((int(data[2].disk_info.free_size) - 20) * 0.4)
+ flag = Flag.data_and_log_same_disk.value
+ elif data[0].disk_info.mount_path == data[1].disk_info.mount_path or data[0].disk_info.mount_path == data[
+ 2].disk_info.mount_path:
+ data_size_default = int(data[1].disk_info.free_size) - 20
+ log_size_default = int(data[2].disk_info.free_size) - 20
+ flag = Flag.home_data_or_home_log_same_disk.value
+ elif data[1].disk_info.mount_path != data[2].disk_info.mount_path:
+ data_size_default = int(int(data[1].disk_info.free_size) * 0.9)
+ log_size_default = int(int(data[2].disk_info.free_size) * 0.9)
+ flag = Flag.data_log_different_disk.value
+
+ if data_size_default > int(data[1].disk_info.free_size) or data_size_default < 1:
+ resource_check_results[-2].check_result = False
+ resource_check_results[-2].error_message.append(f'{address}:{paths[1]} disk resource is not enough')
+
+ if log_size_default > int(data[2].disk_info.free_size) or log_size_default < 20:
+ resource_check_results[-1].check_result = False
+ resource_check_results[-1].error_message.append(f'{address}:{paths[2]} disk resource is not enough')
+ return data_size_default, log_size_default, flag
+
+ def check_machine_resource(self, id):
+ if id not in self.context['deployment_id']:
+ raise Exception(f'no such deployment for id {id}')
+ deploy = self.obd.deploy
+ if not deploy:
+ raise Exception("no such deploy for name:{0}".format(self.context['deployment_id'][id]))
+ deploy_config = deploy.deploy_config
+ pkgs, repositories, errors = self.obd.search_components_from_mirrors(deploy_config, only_info=True)
+ if errors:
+ raise Exception("{}".format('\n'.join(errors)))
+ self.obd.search_param_plugin_and_apply(repositories, deploy_config)
+ self.obd.set_repositories(repositories)
+
+ install_plugins = self.obd.get_install_plugin_and_install(repositories, pkgs)
+
+ ssh_clients, connect_status = self.obd.get_clients_with_connect_status(deploy_config, repositories)
+
+ config = self.context['deployment_info'][id]['config']
+ paths = [config.home_path, config.data_dir, config.log_dir]
+
+ resource_check_results = []
+ data = []
+ metadb_resource = []
+ for server in ssh_clients:
+ client = ssh_clients[server]
+ address = server.ip
+
+ self.check_dir_empty(client, paths, address, resource_check_results, config.auth.user)
+ self.get_server_disk_info(client, paths, data)
+ data_size_default, log_size_default, flag = self.cal_cluster_resource(paths, address, resource_check_results, data)
+ memory_higher_limit, memory_default, memory_lower_limit = self.get_server_memory_info(client, address, resource_check_results)
+ metadb_resource.append(MetaDBResource(
+ address=address, disk=data, memory_limit_lower_limit=memory_lower_limit,
+ memory_limit_higher_limit=memory_higher_limit, data_size_default=data_size_default,
+ memory_limit_default=memory_default, log_size_default=log_size_default, flag=flag
+ ))
+ self.context[id]['metadb_resource'] = metadb_resource
+ return resource_check_results
+
+ def get_machine_resource(self, id):
+ if self.context[id]['metadb_resource']:
+ return self.context[id]['metadb_resource']
+ self.check_machine_resource(id)
+ return self.context[id]['metadb_resource'] if self.context[id]['metadb_resource'] else []
+
+ @serial("precheck")
+ def precheck(self, id, background_tasks):
+ task_manager = task.get_task_manager()
+ cluster_name = self.context['deployment_id'][id]
+ if not cluster_name:
+ raise Exception(f"no such deploy for id: {id}")
+ task_info = task_manager.get_task_info(cluster_name, task_type="precheck")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception(f"task {cluster_name} exists and not finished")
+ deploy = self.obd.deploy
+ if not deploy:
+ raise Exception(f"no such deploy: {cluster_name}")
+ deploy_config = deploy.deploy_config
+ pkgs, repositories, errors = self.obd.search_components_from_mirrors(deploy_config, only_info=True)
+ if errors:
+ raise Exception("{}".format('\n'.join(errors)))
+ repositories.extend(pkgs)
+ repositories = self.obd.sort_repository_by_depend(repositories, deploy_config)
+ for repository in repositories:
+ real_servers = set()
+ cluster_config = deploy_config.components[repository.name]
+ for server in cluster_config.servers:
+ if server.ip in real_servers:
+ raise Exception(
+ "Deploying multiple {} instances on the same server is not supported.'".format(repository.name))
+ real_servers.add(server.ip)
+ self.obd.search_param_plugin_and_apply(repositories, deploy_config)
+ self.obd.set_repositories(repositories)
+
+ start_check_plugins = self.obd.search_py_script_plugin(repositories, 'start_check', no_found_act='warn')
+
+ self._precheck(cluster_name, repositories, start_check_plugins, init_check_status=True)
+ info = task_manager.get_task_info(cluster_name, task_type="precheck")
+ if info is not None and info.exception is not None:
+ exception = copy.deepcopy(info.exception)
+ info.exception = None
+ raise exception
+ task_manager.del_task_info(cluster_name, task_type="precheck")
+ background_tasks.add_task(self._precheck, cluster_name, repositories, start_check_plugins, init_check_status=False)
+ self.context['deployment']['task_id'] = self.context['deployment']['task_id'] + 1 if self.context['deployment']['task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'precheck'
+ ret = TaskInfo(id=self.context['deployment']['task_id'], status=task_status, result=task_res, message=task_message)
+ self.context['task_info'][self.context['deployment'][ret.id]] = ret
+ return ret
+
+ def _init_check_status(self, check_key, servers, check_result={}):
+ check_status = defaultdict(lambda: defaultdict(lambda: None))
+ for server in servers:
+ if server in check_result:
+ status = check_result[server]
+ else:
+ status = CheckStatus()
+ check_status[server] = {check_key: status}
+ return check_status
+
+ def __build_connection_info(self, id, component, info):
+ if info is None:
+ log.get_logger().warn("component {0} info from display is None".format(component))
+ return None
+ self.context['sys_cursor'] = Cursor(ip=info['ip'], port=info['port'], user=info['user'], password=info['password'],
+ stdio=self.obd.stdio)
+ return DatabaseConnection(id=id, host=info['ip'], port=info['port'], user=info['user'], password=info['password'], database='oceanbase')
+
+ @auto_register('precheck')
+ def _precheck(self, name, repositories, start_check_plugins, init_check_status=False):
+ if init_check_status:
+ self._init_precheck(repositories, start_check_plugins)
+ else:
+ self._do_precheck(repositories, start_check_plugins)
+
+ def _init_precheck(self, repositories, start_check_plugins):
+ param_check_status = {}
+ servers_set = set()
+ for repository in repositories:
+ if repository not in start_check_plugins:
+ continue
+ repository_status = {}
+ res = self.obd.call_plugin(start_check_plugins[repository], repository, init_check_status=True, work_dir_check=True, clients={})
+ if not res and res.get_return("exception"):
+ raise res.get_return("exception")
+ servers = self.obd.deploy.deploy_config.components.get(repository.name).servers
+ for server in servers:
+ repository_status[server] = {'param': CheckStatus()}
+ servers_set.add(server)
+ param_check_status[repository.name] = repository_status
+
+ self.context['deployment']['param_check_status'] = param_check_status
+ server_connect_status = {}
+ for server in servers_set:
+ server_connect_status[server] = {'ssh': CheckStatus()}
+ self.context['deployment']['connect_check_status'] = {'ssh': server_connect_status}
+ self.context['deployment']['servers_set'] = servers_set
+
+ def _do_precheck(self, repositories, start_check_plugins):
+ ssh_clients, connect_status = self.obd.get_clients_with_connect_status(self.obd.deploy.deploy_config, repositories, fail_exit=False)
+ check_status = self._init_check_status('ssh', self.context['deployment']['servers_set'], connect_status)
+ self.context['deployment']['connect_check_status'] = {'ssh': check_status}
+ for k, v in connect_status.items():
+ if v.status == v.FAIL:
+ return
+ gen_config_plugins = self.obd.search_py_script_plugin(repositories, 'generate_config')
+ if len(repositories) != len(gen_config_plugins):
+ raise Exception("param_check: config error, check stop!")
+
+ param_check_status, check_pass = self.obd.deploy_param_check_return_check_status(repositories, self.obd.deploy.deploy_config, gen_config_plugins=gen_config_plugins)
+ param_check_status_result = {}
+ for comp_name in param_check_status:
+ status_res = param_check_status[comp_name]
+ param_check_status_result[comp_name] = self._init_check_status('param', status_res.keys(), status_res)
+ self.context['deployment']['param_check_status'] = param_check_status_result
+
+ if not check_pass:
+ return
+
+ for repository in repositories:
+ ret = self.obd.call_plugin(gen_config_plugins[repository], repository, generate_check=False,
+ generate_consistent_config=True, auto_depend=True)
+ if ret is None:
+ raise Exception("generate config error")
+ elif not ret and ret.get_return("exception"):
+ raise ret.get_return("exception")
+ if not self.obd.deploy.deploy_config.dump():
+ raise Exception('generate config dump error,place check disk space!')
+
+ for repository in repositories:
+ res = self.obd.call_plugin(start_check_plugins[repository], repository, init_check_status=False, work_dir_check=True, precheck=True)
+ if not res and res.get_return("exception"):
+ raise res.get_return("exception")
+
+ def get_precheck_result(self, id, task_id):
+ precheck_result = PrecheckTaskInfo()
+ deploy = self.obd.deploy
+ name = self.context['deployment_id'][id]
+ if not name:
+ raise Exception(f"no such deploy for id: {id}")
+ if not deploy:
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ self.obd.set_deploy(deploy)
+ components = deploy.deploy_config.components
+ param_check_status = None
+ connect_check_status = None
+ task_info = self.context['task_info'][self.context['deployment'][task_id]]
+ if not task_info:
+ raise Exception(f"no such task_info for task_id: {task_id}")
+
+ if 'deployment' in self.context.keys():
+ param_check_status = self.context['deployment']['param_check_status']
+ connect_check_status = self.context['deployment']['connect_check_status']
+ connect_check_status_flag = True
+ for component in components:
+ namespace_union = {}
+ namespace = self.obd.get_namespace(component)
+ if namespace:
+ variables = namespace.variables
+ if 'start_check_status' in variables.keys():
+ namespace_union = util.recursive_update_dict(namespace_union, variables.get('start_check_status'))
+ if param_check_status is not None:
+ namespace_union = util.recursive_update_dict(namespace_union, param_check_status[component])
+ if connect_check_status is not None and connect_check_status_flag and 'ssh' in connect_check_status.keys():
+ namespace_union = util.recursive_update_dict(namespace_union, connect_check_status['ssh'])
+ connect_check_status_flag = False
+
+ if namespace_union:
+ for server, result in namespace_union.items():
+ if result is None:
+ log.get_logger().warn("precheck for server: {} is None".format(server.ip))
+ continue
+ check_result = self.parse_precheck_result(component, task_info, server, result)
+ precheck_result.precheck_result = check_result
+ precheck_result.task_info = task_info
+ return precheck_result
+
+ def parse_precheck_result(self, component, task_info, server, result):
+ check_result = []
+ all_passed = True
+ task_info.info = []
+ task_info.finished = ''
+ for k, v in result.items():
+ check_info = PreCheckResult(name='{}:{}'.format(component, k), server=server.ip)
+ task_info.current = '{}:{}'.format(component, k)
+ info = TaskStepInfo(name='{}:{}'.format(component, k))
+ if v.status == v.PASS:
+ check_info.result = PrecheckEventResult.PASSED
+ info.status = TaskStatus.FINISHED
+ info.result = TaskResult.SUCCESSFUL
+ task_info.finished += k + ' '
+ elif v.status == v.FAIL:
+ check_info.result = PrecheckEventResult.FAILED
+ check_info.code = v.error.code
+ check_info.advisement = v.error.msg
+ check_info.recoverable = len(v.suggests) > 0 and v.suggests[0].auto_fix
+ all_passed = False
+ info.status = TaskStatus.FINISHED
+ info.result = TaskResult.FAILED
+ task_info.finished += k + ' '
+ elif v.status == v.WAIT:
+ check_info.result = PrecheckEventResult.RUNNING
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ info.status = TaskStatus.RUNNING
+ info.result = TaskResult.RUNNING
+ task_info.info.append(info)
+ check_result.append(check_info)
+ status_flag = [i.result for i in task_info.info]
+ if TaskResult.RUNNING not in status_flag:
+ task_info.status = TaskStatus.FINISHED
+ task_info.result = TaskResult.SUCCESSFUL if all_passed else TaskResult.FAILED
+ check_result.sort(key=lambda p: p.result)
+ return check_result
+
+ def recover(self, id):
+ deploy = self.obd.deploy
+ name = self.context['deployment_id'][id]
+ if not name:
+ raise Exception(f"no such deploy for id: {id}")
+ if not deploy:
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ self.obd.set_deploy(deploy)
+
+ components = deploy.deploy_config.components
+ param_check_status = {}
+ if 'deployment' in self.context.keys():
+ param_check_status = self.context['deployment']['param_check_status']
+ recover_change_parameter_list = []
+ for component in components:
+ namespace_union = {}
+ if component in self.obd.namespaces:
+ namespace = self.obd.get_namespace(component)
+ if namespace:
+ util.recursive_update_dict(namespace_union, namespace.variables.get('start_check_status', {}))
+ util.recursive_update_dict(namespace_union, param_check_status.get('component', {}))
+
+ for server, precheck_result in namespace_union.items():
+ if precheck_result is None:
+ log.get_logger().warn('component : {},precheck_result is None'.format(component))
+ continue
+ for k, v in precheck_result.items():
+ if v.status == v.FAIL and v.suggests is not None and v.suggests[0].auto_fix and v.suggests[0].fix_eval:
+ for fix_eval in v.suggests[0].fix_eval:
+ if fix_eval.operation == FixEval.SET:
+ config_json = None
+ old_value = None
+ if fix_eval.is_global:
+ deploy.deploy_config.update_component_global_conf(name, fix_eval.key, fix_eval.value, save=False)
+ else:
+ deploy.deploy_config.update_component_server_conf(name, server, fix_eval.key, fix_eval.value, save=False)
+ else:
+ config_json, old_value = self.modify_config(id, fix_eval)
+
+ if config_json is None:
+ log.get_logger().warn('config json is None')
+ continue
+ recover_change_parameter = RecoverChangeParameter(name=fix_eval.key, old_value=old_value, new_value=fix_eval.value)
+ recover_change_parameter_list.append(recover_change_parameter)
+ deploy.deploy_config.dump()
+ self.recreate_deployment(id)
+
+ return recover_change_parameter_list
+
+ def recreate_deployment(self, id):
+ config = self.context['deployment_info'][id]['config'] if self.context['deployment_info'] is not None else None
+ name = self.context['deployment_id'][id]
+ if config is not None:
+ cluster_config_yaml_path = self.generate_metadb_config_path(config)
+ self.create_deployment(name, cluster_config_yaml_path)
+
+ def modify_config(self, id, fix_eval):
+ if fix_eval.key == "parameters":
+ raise Exception("try to change parameters")
+ config = self.context['deployment_info'][id]['config'] if self.context['deployment_info'] is not None else None
+ if config is None:
+ log.get_logger().warn("config is none, no need to modify")
+ raise Exception('config is none')
+ config_dict = config.dict()
+ old_value = None
+ if fix_eval.key in config_dict:
+ del config_dict[fix_eval.key]
+ elif "parameters" in config_dict.keys() and config_dict["parameters"] is not None:
+ for index, parameter_dict in enumerate(config_dict["parameters"]):
+ parameter = Parameter(**parameter_dict)
+ if parameter.name == fix_eval.key:
+ del config_dict['parameters'][index]
+ self.context['deployment_info'][id]['config'] = MetadbDeploymentConfig(**config_dict)
+ return config_dict, old_value
+
+ @serial("install")
+ def install(self, id, background_tasks):
+ task_manager = task.get_task_manager()
+ task_info = task_manager.get_task_info(id, task_type="install")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception("task {0} exists and not finished".format(id))
+ task_manager.del_task_info(id, task_type="install")
+ background_tasks.add_task(self._do_install, id)
+ self.context['deployment']['task_id'] = self.context['deployment']['task_id'] + 1 if self.context['deployment']['task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'install'
+ ret = TaskInfo(id=self.context['deployment']['task_id'], status=task_status, result=task_res, total='init start_check, start, connect, bootstrap, display', message=task_message)
+ self.context['task_info'][self.context['deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register("install")
+ def _do_install(self, id):
+ name = self.context['deployment_id'][id]
+ log.get_logger().info("clean io buffer before start install")
+ self.buffer.clear()
+ log.get_logger().info("clean namespace for init")
+ for c in self.obd.deploy.deploy_config.components:
+ for plugin in const.INIT_PLUGINS:
+ self.obd.namespaces[c].set_return(plugin, None)
+ log.get_logger().info("clean namespace for start")
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ self.obd.namespaces[component].set_return(plugin, None)
+
+ log.get_logger().info("start do deploy %s", name)
+ opt = Values()
+ setattr(opt, "clean", True)
+ setattr(opt, "force", True)
+ self.obd.set_options(opt)
+ deploy_success = self.obd.deploy_cluster(name)
+ if not deploy_success:
+ log.get_logger().warn("deploy %s failed", name)
+ log.get_logger().info("start %s", name)
+
+ repositories = self.obd.load_local_repositories(self.obd.deploy.deploy_info, False)
+ repositories = [repository for repository in repositories if repository.name == 'oceanbase-ce']
+ start_success = True
+ for repository in repositories:
+ opt = Values()
+ setattr(opt, "components", repository.name)
+ setattr(opt, "strict_check", False)
+ self.obd.set_options(opt)
+ ret = self.obd._start_cluster(self.obd.deploy, repositories)
+ if not ret:
+ log.get_logger().warn("failed to start component: %s", repository.name)
+ start_success = False
+ else:
+ display_ret = self.obd.namespaces[repository.name].get_return("display")
+ connection_info = self.__build_connection_info(id, repository.name, display_ret.get_return("info"))
+ if connection_info is not None:
+ self.context["connection_info"][id] = connection_info
+ if not start_success:
+ raise Exception("task {0} start failed".format(name))
+ self.obd.deploy.update_deploy_status(DeployStatus.STATUS_RUNNING)
+ log.get_logger().info("finish do start %s", name)
+
+ def get_install_task_info(self, id, task_id):
+ name = self.context['deployment_id'][id]
+ task_info = self.context['task_info'][self.context['deployment'][task_id]]
+ if task_info is None:
+ raise Exception("task {0} not found".format(task_id))
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ task_info.info = []
+ failed = 0
+
+ self.context['deployment']['failed'] = 0 if not self.context['deployment']['failed'] else \
+ self.context['deployment']['failed']
+
+ for component in self.obd.deploy.deploy_config.components:
+ if component in self.obd.namespaces:
+ for plugin in const.INIT_PLUGINS:
+ task_info.current = f'{component}-{plugin}'
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ failed += 1
+ step_info.result = TaskResult.FAILED
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ for component in self.obd.deploy.deploy_config.components:
+ if component in self.obd.namespaces:
+ for plugin in const.START_PLUGINS:
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ task_info.current = f'{component}-{plugin}'
+ if component not in self.obd.namespaces:
+ break
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ if self.obd.deploy.deploy_info.status == DeployStatus.STATUS_RUNNING:
+ task_info.result = TaskResult.SUCCESSFUL
+ task_info.status = TaskStatus.FINISHED
+
+ if failed or self.context['deployment']['failed'] >= 300:
+ self.context['deployment']['failed'] = 0
+ task_info.result = TaskResult.FAILED
+ task_info.status = TaskStatus.FINISHED
+ return task_info
+
+ @serial("reinstall")
+ def reinstall(self, id, background_tasks):
+ log.get_logger().info('start reinstall')
+ task_manager = task.get_task_manager()
+ task_info = task_manager.get_task_info(id, task_type="reinstall")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception("task {0} exists and not finished".format(id))
+ task_manager.del_task_info(id, task_type="reinstall")
+ background_tasks.add_task(self._do_reinstall, id)
+ self.context['deployment']['task_id'] = self.context['deployment']['task_id'] + 1 if \
+ self.context['deployment'][
+ 'task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'reinstall'
+ ret = TaskInfo(id=self.context['deployment']['task_id'], status=task_status, result=task_res,
+ total='destroy init start_check, start, connect, bootstrap, display', message=task_message)
+ self.context['task_info'][self.context['deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register("reinstall")
+ def _do_reinstall(self, id):
+ log.get_logger().info("clean io buffer before start reinstall")
+ self.buffer.clear()
+ log.get_logger().info("clean namespace for init")
+ for c in self.obd.deploy.deploy_config.components:
+ for plugin in const.INIT_PLUGINS:
+ if c in self.obd.namespaces:
+ self.obd.namespaces[c].set_return(plugin, None)
+ log.get_logger().info("clean namespace for start")
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ if component in self.obd.namespaces:
+ self.obd.namespaces[component].set_return(plugin, None)
+
+ name = self.context['deployment_id'][id]
+ log.get_logger().info('start destroy %s' % name)
+ opt = Values()
+ setattr(opt, "force_kill", True)
+ setattr(opt, "force", True)
+ setattr(opt, "clean", True)
+ self.obd.set_options(opt)
+ if not self.obd.redeploy_cluster(name):
+ raise Exception('reinstall failed')
+
+ self.obd.deploy.update_deploy_status(DeployStatus.STATUS_RUNNING)
+ log.get_logger().info("finish do start %s", name)
+
+ def get_reinstall_task_info(self, id, task_id):
+ name = self.context['deployment_id'][id]
+ task_info = self.context['task_info'][self.context['deployment'][task_id]]
+ if task_info is None:
+ raise Exception("task {0} not found".format(task_id))
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ task_info.info = []
+ task_info.finished = ''
+ failed = 0
+ self.context['deployment']['failed'] = 0 if not self.context['deployment']['failed'] else \
+ self.context['deployment']['failed']
+
+ for c in self.obd.deploy.deploy_config.components:
+ step_info = TaskStepInfo(name=f'{c}-{const.DESTROY_PLUGIN}', status=TaskStatus.RUNNING,
+ result=TaskResult.RUNNING)
+ if c in self.obd.namespaces:
+ if self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN) is not None:
+ task_info.status = TaskStatus.RUNNING
+ task_info.current = f'{c}-{const.DESTROY_PLUGIN}'
+ step_info.status = TaskStatus.FINISHED
+ if not self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ task_info.info.append(step_info)
+ task_info.finished += f'{c}-{const.DESTROY_PLUGIN} '
+
+ for component in self.obd.deploy.deploy_config.components:
+ if component in self.obd.namespaces:
+ for plugin in const.INIT_PLUGINS:
+ task_info.current = f'{component}-{plugin}'
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING,
+ result=TaskResult.RUNNING)
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ failed += 1
+ step_info.result = TaskResult.FAILED
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ else:
+ self.context['deployment']['failed'] += 1
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING,
+ result=TaskResult.RUNNING)
+ task_info.current = f'{component}-{plugin}'
+ if component not in self.obd.namespaces:
+ break
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ if self.obd.deploy.deploy_info.status == DeployStatus.STATUS_RUNNING:
+ task_info.result = TaskResult.SUCCESSFUL
+ task_info.status = TaskStatus.FINISHED
+
+ if failed or self.context['deployment']['failed'] >= 300:
+ self.context['deployment']['failed'] = 0
+ task_info.result = TaskResult.FAILED
+ task_info.status = TaskStatus.FINISHED
+ return task_info
+
+ @serial("destroy")
+ def destroy(self, id, background_tasks):
+ task_manager = task.get_task_manager()
+ task_info = task_manager.get_task_info(id, task_type="destroy")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception("task {0} exists and not finished".format(id))
+ task_manager.del_task_info(id, task_type="destroy")
+ background_tasks.add_task(self._destroy_cluster, id)
+ self.context['deployment']['task_id'] = self.context['deployment']['task_id'] + 1 if self.context['deployment'][
+ 'task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'destroy'
+ ret = TaskInfo(id=self.context['deployment']['task_id'], status=task_status, result=task_res,
+ total='destroy', message=task_message)
+ self.context['task_info'][self.context['deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register("destroy")
+ def _destroy_cluster(self, id):
+
+ name = self.context['deployment_id'][id]
+ if not name:
+ raise Exception(f"no such deploy for id: {id}")
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ if not deploy:
+ raise Exception("no such deploy for id: {0}".format(id))
+ self.obd.set_deploy(deploy)
+
+ repositories = self.obd.load_local_repositories(deploy.deploy_info)
+ self.obd.set_repositories(repositories)
+ self.obd.set_options(Values({'force_kill': True}))
+ self.obd.search_param_plugin_and_apply(repositories, deploy.deploy_config)
+
+ ret = self.obd._destroy_cluster(deploy, repositories)
+ if not ret:
+ raise Exception("destroy cluster {0} failed".format(name))
+ deploy.update_deploy_status(DeployStatus.STATUS_CONFIGURED)
+ self.obd.set_options(Values())
+
+ def get_destroy_task_info(self, id, task_id):
+ name = self.context['deployment_id'][id]
+ task_info = self.context['task_info'][self.context['deployment'][task_id]]
+ if task_info is None:
+ raise Exception("task {0} not found".format(task_id))
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ task_info.info = []
+ task_info.finished = ''
+
+ failed = 0
+ for c in self.obd.deploy.deploy_config.components:
+ step_info = TaskStepInfo(name=f'{c}-{const.DESTROY_PLUGIN}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ if c in self.obd.namespaces:
+ if self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN) is not None:
+ task_info.status = TaskStatus.RUNNING
+ task_info.current = f'{c}-{const.DESTROY_PLUGIN}'
+ step_info.status = TaskStatus.FINISHED
+ if not self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ task_info.info.append(step_info)
+ task_info.finished += f'{c}-{const.DESTROY_PLUGIN} '
+ if self.obd.deploy.deploy_info.status == DeployStatus.STATUS_CONFIGURED:
+ task_info.result = TaskResult.SUCCESSFUL
+ task_info.status = TaskStatus.FINISHED
+
+ if failed:
+ task_info.result = TaskResult.FAILED
+ task_info.status = TaskStatus.FINISHED
+ return task_info
+
+ def create_connection_info(self, info, sys=False):
+ self.context["connection_info"][info.cluster_name] = info
+ log.get_logger().info(
+ f'connection host: {info.host}, port: {info.port}, user: {info.user}, password: {info.password}'
+ )
+ if sys and '@' in info.user and info.user.split('@')[1] != 'sys':
+ raise Exception('The incoming user must belong to the sys tenant.')
+ self.context['meta_database'] = info.database
+ self.context['metadb_cursor'] = Cursor(ip=info.host, port=info.port, user=info.user, password=info.password, stdio=self.obd.stdio)
+ connection_info = DatabaseConnection(id=info.cluster_name, host=info.host, port=info.port, user=info.user, password=info.password, database=info.database)
+ connection_info_copy = copy.deepcopy(connection_info)
+ connection_info_copy.password = ''
+ return connection_info_copy
+
+ def get_connection_info(self, cluster_name):
+ if not self.context["connection_info"]:
+ return None
+ if not self.context["connection_info"].get(cluster_name):
+ return None
+ connection_info_copy = copy.deepcopy(self.context['connection_info'][cluster_name])
+ connection_info_copy.password = ''
+ return connection_info_copy
\ No newline at end of file
diff --git a/service/handler/ocp_handler.py b/service/handler/ocp_handler.py
new file mode 100644
index 0000000..8f33b8e
--- /dev/null
+++ b/service/handler/ocp_handler.py
@@ -0,0 +1,1633 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+import copy
+import os
+from optparse import Values
+from singleton_decorator import singleton
+import tempfile
+import yaml
+import json
+from collections import defaultdict
+
+from service.handler.base_handler import BaseHandler
+from service.common import log, task, util, const
+from service.common.task import Serial as serial
+from service.common.task import AutoRegister as auto_register
+from service.model.deployments import Parameter, OCPDeploymentStatus, OCPDeploymnetConfig
+from service.model.database import DatabaseConnection
+from service.model.ssh import SshAuthMethod
+from service.model.ocp import ObserverResource, OcpResource, MetadbResource, OcpInfo, OcpInstalledInfo, OcpUpgradeLostAddress
+from service.model.metadb import RecoverChangeParameter
+from service.model.resource import DiskInfo, ServerResource
+from service.model.task import TaskStatus, TaskResult, TaskInfo, PreCheckResult, PrecheckTaskInfo, PrecheckEventResult, TaskStepInfo
+from _deploy import DeployStatus, DeployConfigStatus, UserConfig
+from _errno import CheckStatus, FixEval
+from _repository import Repository
+from ssh import SshClient, SshConfig
+from tool import Cursor
+
+
+@singleton
+class OcpHandler(BaseHandler):
+
+ def create_ocp_config_path(self, config):
+ cluster_config = {}
+ log.get_logger().info('meta connections: %s' % self.context['connection_info'])
+ log.get_logger().info('meta_password: %s' % self.context['meta_password'])
+
+ home_path = config.home_path
+ launch_user = config.launch_user
+ if config.auth is not None:
+ self.generate_auth_config(cluster_config, config.auth)
+ if config.components.oceanbase is not None:
+ self.generate_metadb_config(cluster_config, config.components.oceanbase, home_path)
+ if config.components.obproxy is not None and config.components.oceanbase is not None:
+ self.generate_obproxy_config(cluster_config, config.components.obproxy, home_path, config.components.oceanbase.component)
+ if config.components.ocpserver is not None:
+ ob_component = obp_component = None
+ if config.components.obproxy is not None:
+ ob_component = config.components.oceanbase.component
+ if config.components.obproxy is not None:
+ obp_component = config.components.obproxy.component
+ self.generate_ocp_config(cluster_config, config.components.ocpserver, home_path, launch_user, ob_component, obp_component)
+
+ cluster_config_yaml_path = ''
+ log.get_logger().info('dump ocp-server config from path: %s' % cluster_config_yaml_path)
+ with tempfile.NamedTemporaryFile(delete=False, prefix="ocp-server", suffix="yaml", mode="w", encoding="utf-8") as f:
+ f.write(yaml.dump(cluster_config, sort_keys=False))
+ cluster_config_yaml_path = f.name
+ self.context['id'] = self.context['id'] + 1 if self.context['id'] else 1
+ log.get_logger().info('ocp-server id: %s' % self.context['id'])
+ status = self.context['ocp_deployment_info'][self.context['id']]['status'] \
+ if self.context['ocp_deployment_info'][self.context['id']] and self.context['ocp_deployment_info'][self.context['id']]['status'] \
+ else OCPDeploymentStatus.INIT
+
+ self.context['ocp_path'] = cluster_config_yaml_path
+ self.context['ocp_deployment_info'][self.context['id']] = {'status': status, 'config': config}
+ return cluster_config_yaml_path
+
+ def generate_auth_config(self, cluster_config, auth):
+ if 'user' not in cluster_config.keys():
+ cluster_config['user'] = {}
+ cluster_config['user']['username'] = auth.user
+ cluster_config['user']['password'] = auth.password
+ cluster_config['user']['port'] = auth.port
+
+ def generate_metadb_config(self, cluster_config, oceanbase, home_path):
+ oceanbase_config = dict()
+ config_dict = oceanbase.dict()
+ for key in config_dict:
+ if config_dict[key] and key in ('version', 'release', 'package_hash'):
+ oceanbase_config[key] = config_dict[key]
+ servers = []
+ if oceanbase.topology:
+ for zone in oceanbase.topology:
+ root_service = zone.rootservice
+ servers.append(root_service)
+ for zone in oceanbase.topology:
+ root_service = zone.rootservice
+ if root_service not in oceanbase_config.keys():
+ oceanbase_config[root_service] = {}
+ oceanbase_config[root_service]['zone'] = zone.name
+ for server in zone.servers:
+ ip = server.ip
+ if ip not in oceanbase_config.keys():
+ oceanbase_config[ip] = {}
+ if ip != root_service:
+ servers.append(server.ip)
+ oceanbase_config[ip]['zone'] = zone.name
+ if server.parameters:
+ for parameter in server.parameters:
+ for key, value in parameter:
+ oceanbase_config[ip][key] = value
+ oceanbase_config['servers'] = servers
+ if 'global' not in oceanbase_config.keys():
+ oceanbase_config['global'] = {}
+
+ for key in config_dict:
+ if config_dict[key] and key in ['mysql_port', 'rpc_port', 'home_path', 'data_dir', 'redo_dir', 'appname',
+ 'root_password']:
+ oceanbase_config['global'][key] = config_dict[key]
+
+ if oceanbase.home_path == '':
+ oceanbase_config['global']['home_path'] = home_path + '/oceanbase'
+
+ if oceanbase.parameters:
+ for parameter in oceanbase.parameters:
+ if not parameter.adaptive:
+ oceanbase_config['global'][parameter.key] = parameter.value
+ if oceanbase.component == const.OCEANBASE_CE:
+ cluster_config[const.OCEANBASE_CE] = oceanbase_config
+ elif oceanbase.component == const.OCEANBASE:
+ cluster_config[const.OCEANBASE] = oceanbase_config
+ else:
+ log.get_logger().error('oceanbase component : %s not exist' % oceanbase.component)
+ raise Exception('oceanbase component : %s not exist' % oceanbase.component)
+
+ def generate_obproxy_config(self, cluster_config, obproxy_config, home_path, ob_componet):
+ comp_config = dict()
+ config_dict = obproxy_config.dict()
+ for key in config_dict:
+ if config_dict[key] and key in ('servers', 'version', 'package_hash', 'release'):
+ comp_config[key] = config_dict[key]
+
+ if 'global' not in comp_config.keys():
+ comp_config['global'] = dict()
+
+ for key in config_dict:
+ if config_dict[key] and key in ('cluster_name', 'prometheus_listen_port', 'listen_port', 'home_path'):
+ comp_config['global'][key] = config_dict[key]
+
+ if obproxy_config.home_path == '':
+ comp_config['global']['home_path'] = home_path + '/obproxy'
+
+ for parameter in obproxy_config.parameters:
+ if not parameter.adaptive:
+ comp_config['global'][parameter.key] = parameter.value
+ if 'depends' not in comp_config.keys():
+ comp_config['depends'] = list()
+ comp_config['depends'].append(ob_componet)
+ if obproxy_config.component == const.OBPROXY_CE:
+ cluster_config[const.OBPROXY_CE] = comp_config
+ elif obproxy_config.component == const.OBPROXY:
+ cluster_config[const.OBPROXY] = comp_config
+ else:
+ log.get_logger().error('obproxy component : %s not exist' % obproxy_config.component)
+ raise Exception('obproxy component : %s not exist' % obproxy_config.component)
+
+ def generate_ocp_config(self, cluster_config, config, home_path, launch_user, ob_component=None, obp_component=None):
+ log.get_logger().debug('generate ocp config')
+ ocp_config = dict()
+ config_dict = config.dict()
+ for key in config_dict:
+ if config_dict[key] and key in ('servers', 'version', 'package_hash', 'release'):
+ ocp_config[key] = config_dict[key]
+
+ if 'global' not in ocp_config.keys():
+ ocp_config['global'] = {}
+
+ for key in config_dict:
+ if config_dict[key] and key in ('port', 'admin_password', 'memory_size', 'manage_info', 'home_path', 'soft_dir', 'log_dir'):
+ ocp_config['global'][key] = config_dict[key]
+
+ if config.metadb:
+ ocp_config['global']['jdbc_url'] = 'jdbc:oceanbase://' + config_dict['metadb']['host'] + ':' + str(config_dict['metadb']['port']) + '/' + config_dict['metadb']['database']
+ ocp_config['global']['jdbc_username'] = config_dict['metadb']['user']
+ ocp_config['global']['jdbc_password'] = config_dict['metadb']['password']
+
+ if config.meta_tenant:
+ ocp_config['global']['ocp_meta_tenant'] = {}
+ ocp_config['global']['ocp_meta_tenant']['tenant_name'] = config_dict['meta_tenant']['name']['tenant_name']
+ ocp_config['global']['ocp_meta_tenant']['max_cpu'] = config_dict['meta_tenant']['resource']['cpu']
+ ocp_config['global']['ocp_meta_tenant']['memory_size'] = str(config_dict['meta_tenant']['resource']['memory']) + 'G'
+ ocp_config['global']['ocp_meta_username'] = config_dict['meta_tenant']['name']['user_name']
+ ocp_config['global']['ocp_meta_password'] = config_dict['meta_tenant']['password']
+ ocp_config['global']['ocp_meta_db'] = config_dict['meta_tenant']['name']['user_database'] if config_dict['meta_tenant']['name']['user_database'] != '' else 'meta_database'
+ self.context['meta_tenant'] = config_dict['meta_tenant']['name']['tenant_name']
+
+ if config.monitor_tenant:
+ ocp_config['global']['ocp_monitor_tenant'] = {}
+ ocp_config['global']['ocp_monitor_tenant']['tenant_name'] = config_dict['monitor_tenant']['name']['tenant_name']
+ ocp_config['global']['ocp_monitor_tenant']['max_cpu'] = config_dict['monitor_tenant']['resource']['cpu']
+ ocp_config['global']['ocp_monitor_tenant']['memory_size'] = str(config_dict['monitor_tenant']['resource']['memory']) + 'G'
+ ocp_config['global']['ocp_monitor_username'] = config_dict['monitor_tenant']['name']['user_name']
+ ocp_config['global']['ocp_monitor_password'] = config_dict['monitor_tenant']['password']
+ ocp_config['global']['ocp_monitor_db'] = config_dict['monitor_tenant']['name']['user_database'] if config_dict['monitor_tenant']['name']['user_database'] != '' else 'monitor_database'
+ self.context['monitor_tenant'] = config_dict['monitor_tenant']['name']['tenant_name']
+
+ if config.home_path == '':
+ ocp_config['global']['home_path'] = home_path + '/ocp-server'
+
+ if launch_user:
+ ocp_config['global']['launch_user'] = launch_user
+
+ if config.soft_dir == '':
+ ocp_config['global']['soft_dir'] = ocp_config['global']['home_path'] + '/data/files/'
+
+ if config.log_dir == '':
+ ocp_config['global']['log_dir'] = ocp_config['global']['home_path'] + '/log'
+
+ if config.parameters:
+ for parameter in config.parameters:
+ if not parameter.adaptive:
+ ocp_config['global'][parameter.key] = parameter.value
+ if not ob_component:
+ if config_dict['metadb']:
+ ocp_config['global']['jdbc_url'] = 'jdbc:oceanbase://' + config_dict['metadb']['host'] + ':' + str(
+ config_dict['metadb']['port']) + '/' + config_dict['metadb']['database']
+ ocp_config['global']['jdbc_username'] = config_dict['metadb']['user']
+ ocp_config['global']['jdbc_password'] = config_dict['metadb']['password']
+ if 'depends' not in ocp_config.keys() and ob_component and obp_component:
+ ocp_config['depends'] = list()
+ ocp_config['depends'].append(ob_component)
+ ocp_config['depends'].append(obp_component)
+ if config.component == const.OCP_SERVER_CE:
+ cluster_config[const.OCP_SERVER_CE] = ocp_config
+ elif config.component == const.OCP_SERVER:
+ cluster_config[const.OCP_SERVER] = ocp_config
+ else:
+ log.get_logger().error('ocp-server component : %s not exist' % config.component)
+ raise Exception('ocp-server component : %s not exist' % config.component)
+
+ def create_ocp_deployment(self, name: str, config_path: str):
+ log.get_logger().debug('deploy cluster')
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ if deploy:
+ deploy_info = deploy.deploy_info
+ if deploy_info.status == DeployStatus.STATUS_DEPLOYED:
+ log.get_logger().debug('start destroy(ocp) %s' % name)
+ self.obd.set_options(Values({'force_kill': True}))
+ if self.obd.repositories:
+ self.obd.set_repositories(self.obd.repositories)
+ self.obd._destroy_cluster(self.obd.deploy, self.obd.repositories)
+ else:
+ self.obd.destroy_cluster(name)
+ log.get_logger().info('destroy %s(ocp) end' % name)
+ deploy.update_deploy_status(DeployStatus.STATUS_CONFIGURED)
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ deploy_info = deploy.deploy_info
+ if deploy_info.status not in [DeployStatus.STATUS_CONFIGURED, DeployStatus.STATUS_DESTROYED]:
+ log.get_logger().error('Deploy "%s" is %s. You could not deploy an %s cluster.' % (
+ name, deploy_info.status.value, deploy_info.status.value))
+ raise Exception('Deploy "%s" is %s. You could not deploy an %s cluster.' % (
+ name, deploy_info.status.value, deploy_info.status.value))
+ if deploy_info.config_status != DeployConfigStatus.UNCHNAGE:
+ log.get_logger().debug('Apply temp deploy configuration')
+ if not deploy.apply_temp_deploy_config():
+ log.get_logger().error('Failed to apply new deploy configuration')
+ raise Exception('Failed to apply new deploy configuration')
+
+ deploy = self.obd.deploy_manager.create_deploy_config(name, config_path)
+ if not deploy:
+ log.get_logger().error('Failed to create deploy: %s. please check you configuration file' % name)
+ raise Exception('Failed to create deploy: %s. please check you configuration file' % name)
+ self.obd.set_deploy(deploy)
+ log.get_logger().info('ocp server cluster config path: %s ' % config_path)
+ self.context['ocp_deployment_id'][self.context['id']] = name
+ return self.context['id']
+
+ def check_user(self, user):
+ self.context['upgrade_servers'] = user.servers
+ for ip in user.servers:
+ log.get_logger().info('ip: %s, port: %s, user: %s, password: %s' % (ip, user.port, user.user, user.password))
+ self.context['upgrade_user'] = user.user
+ self.context['upgrade_user_password'] = user.password
+ self.context['upgrade_ssh_port'] = user.port if user.port else 22
+ config = SshConfig(host=ip, port=user.port, username=user.user, password=user.password)
+ client = SshClient(config)
+ if not (client.execute_command('sudo -n true') or client.execute_command('[ `id -u` == "0" ]')):
+ raise Exception('Please execute `bash -c \'echo "{user} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers`\' as root in {ip}.'.format(user=user.user, ip=ip))
+ res = client.connect(self.obd.stdio, exit=False)
+ if res != True:
+ return False
+ return True
+
+ def generate_secure_ocp_deployment(self, ocp_deployment):
+ log.get_logger().info('generate secure ocp_deployment')
+ config = copy.deepcopy(ocp_deployment)
+ config.admin_password = ''
+ if config.meta_tenant:
+ config.meta_tenant.password = ''
+ if config.monitor_tenant:
+ config.monitor_tenant.password = ''
+ if config.components.oceanbase.password:
+ config.components.oceanbase.password = ''
+ if config.auth:
+ config.auth.password = ''
+ return config
+
+ def list_ocp_deployments(self):
+ log.get_logger().info('list secure ocp_deployment')
+ data = []
+ for id, ocp_deployment_info in self.context['ocp_deployment_info'].items():
+ if ocp_deployment_info:
+ copy_ocp_deployment_info = copy.deepcopy(ocp_deployment_info)
+ copy_ocp_deployment_info['config'].admin_password = ''
+ if copy_ocp_deployment_info['config'].meta_tenant:
+ copy_ocp_deployment_info['config'].meta_tenant.password = ''
+ if copy_ocp_deployment_info['config'].monitor_tenant:
+ copy_ocp_deployment_info['config'].monitor_tenant.password = ''
+ if copy_ocp_deployment_info['config'].metadb.password:
+ copy_ocp_deployment_info['config'].metadb.password = ''
+ if copy_ocp_deployment_info['config'].auth:
+ copy_ocp_deployment_info['config'].auth.password = ''
+ data.append(ocp_deployment_info)
+ return data
+
+ def get_ocp_deployment(self, id):
+ log.get_logger().info('get id(%s) secure ocp_deployment' % id)
+ if id not in self.context['ocp_deployment_info']:
+ raise Exception(f'id: {id} not deployment')
+ data = self.context['ocp_deployment_info'][id]
+ copy_data = copy.deepcopy(data)
+ copy_data['config'].admin_password = ''
+ if copy_data['config'].meta_tenant:
+ copy_data['config'].meta_tenant.password = ''
+ if copy_data['config'].monitor_tenant:
+ copy_data['config'].monitor_tenant.password = ''
+ if copy_data['config'].metadb.password:
+ copy_data['config'].metadb.password = ''
+ if copy_data['config'].auth:
+ copy_data['config'].auth.password = ''
+ return copy_data['config']
+
+ @serial("ocp_precheck")
+ def ocp_precheck(self, id, background_tasks):
+ task_manager = task.get_task_manager()
+ app_name = self.context['ocp_deployment_id'][id]
+ log.get_logger().info('precheck start: %s' % app_name)
+ if not app_name:
+ raise Exception(f"no such deploy for id: {id}")
+ task_info = task_manager.get_task_info(app_name, task_type="ocp_precheck")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception(f"task {app_name} exists and not finished")
+ deploy = self.obd.deploy
+ if not deploy:
+ raise Exception("no such deploy for name:{0}".format(app_name))
+ deploy_config = deploy.deploy_config
+ pkgs, repositories, errors = self.obd.search_components_from_mirrors(deploy_config, only_info=True)
+ if errors:
+ raise Exception("{}".format('\n'.join(errors)))
+ repositories.extend(pkgs)
+ repositories = self.obd.sort_repository_by_depend(repositories, deploy_config)
+ for repository in repositories:
+ real_servers = set()
+ cluster_config = deploy_config.components[repository.name]
+ for server in cluster_config.servers:
+ if server.ip in real_servers:
+ raise Exception(
+ "Deploying multiple {} instances on the same server is not supported.'".format(
+ repository.name))
+ return False
+ real_servers.add(server.ip)
+ self.obd.search_param_plugin_and_apply(repositories, deploy_config)
+ self.obd.set_repositories(repositories)
+
+ start_check_plugins = self.obd.search_py_script_plugin(repositories, 'start_check', no_found_act='warn')
+ log.get_logger().info('start_check plugins: %s' % start_check_plugins)
+ self._precheck(app_name, repositories, start_check_plugins, init_check_status=True)
+ info = task_manager.get_task_info(app_name, task_type="ocp_precheck")
+ if info is not None and info.exception is not None:
+ exception = copy.deepcopy(info.exception)
+ info.exception = None
+ raise exception
+ task_manager.del_task_info(app_name, task_type="ocp_precheck")
+ background_tasks.add_task(self._precheck, app_name, repositories, start_check_plugins, init_check_status=False)
+ self.context['ocp_deployment']['task_id'] = self.context['ocp_deployment']['task_id'] + 1 if self.context['ocp_deployment']['task_id'] else 1
+ log.get_logger().info('task id: %d' % self.context['ocp_deployment']['task_id'])
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'ocp_precheck'
+ ret = TaskInfo(id=self.context['ocp_deployment']['task_id'], status=task_status, result=task_res, message=task_message, total='port, java, disk, mem, oceanbase version')
+ log.get_logger().info('task ret: %s' % ret)
+ self.context['task_info'][self.context['ocp_deployment'][ret.id]] = ret
+ return ret
+
+ def _init_check_status(self, check_key, servers, check_result={}):
+ check_status = defaultdict(lambda: defaultdict(lambda: None))
+ for server in servers:
+ if server in check_result:
+ status = check_result[server]
+ else:
+ status = CheckStatus()
+ check_status[server] = {check_key: status}
+ return check_status
+
+ @auto_register('ocp_precheck')
+ def _precheck(self, name, repositories, start_check_plugins, init_check_status=False):
+ if init_check_status:
+ self._init_precheck(repositories, start_check_plugins)
+ else:
+ self._do_precheck(repositories, start_check_plugins)
+
+ def _init_precheck(self, repositories, start_check_plugins):
+ log.get_logger().info('init precheck')
+ param_check_status = {}
+ servers_set = set()
+ for repository in repositories:
+ if repository not in start_check_plugins:
+ continue
+ repository_status = {}
+ res = self.obd.call_plugin(start_check_plugins[repository], repository, init_check_status=True, work_dir_check=True, clients={})
+ if not res and res.get_return("exception"):
+ raise res.get_return("exception")
+ servers = self.obd.deploy.deploy_config.components.get(repository.name).servers
+ for server in servers:
+ repository_status[server] = {'param': CheckStatus()}
+ servers_set.add(server)
+ param_check_status[repository.name] = repository_status
+
+ self.context['ocp_deployment']['param_check_status'] = param_check_status
+ server_connect_status = {}
+ for server in servers_set:
+ server_connect_status[server] = {'ssh': CheckStatus()}
+ self.context['ocp_deployment']['connect_check_status'] = {'ssh': server_connect_status}
+ self.context['ocp_deployment']['servers_set'] = servers_set
+
+ def _do_precheck(self, repositories, start_check_plugins):
+ log.get_logger().info('start precheck')
+ log.get_logger().info('ssh check')
+ ssh_clients, connect_status = self.obd.get_clients_with_connect_status(self.obd.deploy.deploy_config, repositories, fail_exit=False)
+ log.get_logger().info('connect_status: ', connect_status)
+ check_status = self._init_check_status('ssh', self.context['ocp_deployment']['servers_set'], connect_status)
+ self.context['ocp_deployment']['connect_check_status'] = {'ssh': check_status}
+ for k, v in connect_status.items():
+ if v.status == v.FAIL:
+ self.context['ocp_deployment_ssh'][self.context['id']] = 'fail'
+ log.get_logger().info('ssh check failed')
+ return
+ log.get_logger().info('ssh check succeed')
+ gen_config_plugins = self.obd.search_py_script_plugin(repositories, 'generate_config')
+ if len(repositories) != len(gen_config_plugins):
+ raise Exception("param_check: config error, check stop!")
+
+ param_check_status, check_pass = self.obd.deploy_param_check_return_check_status(repositories, self.obd.deploy.deploy_config, gen_config_plugins=gen_config_plugins)
+ param_check_status_result = {}
+ for comp_name in param_check_status:
+ status_res = param_check_status[comp_name]
+ param_check_status_result[comp_name] = self._init_check_status('param', status_res.keys(), status_res)
+ self.context['ocp_deployment']['param_check_status'] = param_check_status_result
+
+ log.get_logger().info('precheck param check status: %s' % param_check_status)
+ log.get_logger().info('precheck param check status res: %s' % check_pass)
+ if not check_pass:
+ return
+
+ for repository in repositories:
+ ret = self.obd.call_plugin(gen_config_plugins[repository], repository, generate_check=False,
+ generate_consistent_config=True, auto_depend=True)
+ if ret is None:
+ raise Exception("generate config error")
+ elif not ret and ret.get_return("exception"):
+ raise ret.get_return("exception")
+ if not self.obd.deploy.deploy_config.dump():
+ raise Exception('generate config dump error,place check disk space!')
+
+ log.get_logger().info('generate config succeed')
+ ssh_clients = self.obd.get_clients(self.obd.deploy.deploy_config, repositories)
+ for repository in repositories:
+ log.get_logger().info('begin start_check: %s' % repository.name)
+ res = self.obd.call_plugin(start_check_plugins[repository], repository, init_check_status=False,
+ work_dir_check=True, precheck=True, clients=ssh_clients, sys_cursor=self.context['metadb_cursor'])
+ if not res and res.get_return("exception"):
+ raise res.get_return("exception")
+ log.get_logger().info('end start_check: %s' % repository.name)
+
+ def get_precheck_result(self, id, task_id):
+ log.get_logger().info('get ocp precheck result')
+ precheck_result = PrecheckTaskInfo()
+ deploy = self.obd.deploy
+ name = self.context['ocp_deployment_id'][id]
+ if not name:
+ raise Exception(f"no such deploy for id: {id}")
+ if not deploy:
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ self.obd.set_deploy(deploy)
+ components = deploy.deploy_config.components
+
+ param_check_status = None
+ connect_check_status = None
+ check_result = []
+ task_info = self.context['task_info'][self.context['ocp_deployment'][task_id]]
+ all_passed = []
+ precheck_result.task_info = task_info
+ task_info.info = []
+
+ if 'ocp_deployment' in self.context.keys():
+ param_check_status = self.context['ocp_deployment']['param_check_status']
+ connect_check_status = self.context['ocp_deployment']['connect_check_status']
+ for component in components:
+ namespace_union = {}
+ namespace = self.obd.get_namespace(component)
+ if namespace:
+ variables = namespace.variables
+ if 'start_check_status' in variables.keys():
+ namespace_union = util.recursive_update_dict(namespace_union, variables.get('start_check_status'))
+ if param_check_status:
+ namespace_union = util.recursive_update_dict(namespace_union, param_check_status[component])
+ if connect_check_status and 'ssh' in connect_check_status.keys():
+ namespace_union = util.recursive_update_dict(namespace_union, connect_check_status['ssh'])
+
+ log.get_logger().info('namespace_union: %s' % namespace_union)
+ if namespace_union:
+ for server, result in namespace_union.items():
+ if result is None:
+ log.get_logger().warn("precheck for server: {} is None".format(server.ip))
+ continue
+ all_passed.append(self.parse_precheck_result(component, check_result, task_info, server, result))
+ check_result.sort(key=lambda p: p.result)
+ precheck_result.precheck_result = check_result
+ status_flag = [i.status for i in task_info.info]
+ if TaskStatus.RUNNING not in status_flag:
+ task_info.status = TaskStatus.FINISHED
+ task_info.result = TaskResult.SUCCESSFUL if all(all_passed) else TaskResult.FAILED
+ precheck_result.task_info = task_info
+ if self.context['ocp_deployment_ssh'][id] == 'fail' and TaskStatus.FINISHED in status_flag:
+ precheck_result.task_info.result = TaskResult.FAILED
+ precheck_result.task_info.status = TaskStatus.FINISHED
+ return precheck_result
+
+ def parse_precheck_result(self, component, check_result, task_info, server, result):
+ all_passed = True
+ task_info.finished = ''
+ for k, v in result.items():
+ check_info = PreCheckResult(name='{}:{}'.format(component, k), server=server.ip)
+ task_info.current = '{}:{}'.format(component, k)
+ log.get_logger().info('precheck result current: %s' % task_info.current)
+ info = TaskStepInfo(name='{}:{}'.format(component, k))
+ if v.status == v.PASS:
+ check_info.result = PrecheckEventResult.PASSED
+ info.status = TaskStatus.FINISHED
+ info.result = TaskResult.SUCCESSFUL
+ task_info.finished += k + ' '
+ elif v.status == v.FAIL:
+ check_info.result = PrecheckEventResult.FAILED
+ check_info.code = v.error.code
+ check_info.advisement = v.error.msg
+ check_info.recoverable = len(v.suggests) > 0 and v.suggests[0].auto_fix
+ all_passed = False
+ info.status = TaskStatus.FINISHED
+ info.result = TaskResult.FAILED
+ task_info.finished += k + ' '
+ elif v.status == v.WAIT:
+ check_info.result = PrecheckEventResult.RUNNING
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ info.status = TaskStatus.RUNNING
+ info.result = TaskResult.RUNNING
+ task_info.info.append(info)
+ check_result.append(check_info)
+ return all_passed
+
+ def recover(self, id):
+ log.get_logger().info('recover config')
+ deploy = self.obd.deploy
+ name = self.context['ocp_deployment_id'][id]
+ if not deploy:
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ self.obd.set_deploy(deploy)
+
+ components = deploy.deploy_config.components
+ param_check_status = {}
+ if 'ocp_deployment' in self.context.keys():
+ param_check_status = self.context['ocp_deployment']['param_check_status']
+ recover_change_parameter_list = []
+ for component in components:
+ namespace_union = {}
+ if component in self.obd.namespaces:
+ namespace = self.obd.get_namespace(component)
+ if namespace:
+ util.recursive_update_dict(namespace_union, namespace.variables.get('start_check_status', {}))
+ util.recursive_update_dict(namespace_union, param_check_status.get('component', {}))
+
+ for server, precheck_result in namespace_union.items():
+ if precheck_result is None:
+ log.get_logger().warn('component : {},precheck_result is None'.format(component))
+ continue
+ for k, v in precheck_result.items():
+ log.get_logger().info('k: %s, v: %s' % (k, v))
+ log.get_logger().info('status: %s' % v.status)
+ if v.status == v.FAIL and v.suggests is not None and v.suggests[0].auto_fix and v.suggests[0].fix_eval:
+ log.get_logger().info('auto_fix : %s' % v.suggests[0].auto_fix)
+ log.get_logger().info('fix_eval: %s' % v.suggests[0].fix_eval)
+ for fix_eval in v.suggests[0].fix_eval:
+ if fix_eval.operation == FixEval.SET:
+ config_json = None
+ old_value = None
+ if fix_eval.is_global:
+ deploy.deploy_config.update_component_global_conf(name, fix_eval.key, fix_eval.value, save=False)
+ else:
+ deploy.deploy_config.update_component_server_conf(name, server, fix_eval.key, fix_eval.value, save=False)
+ else:
+ config_json, old_value = self.modify_config(component, id, fix_eval)
+
+ if config_json is None:
+ log.get_logger().warn('config json is None')
+ continue
+ recover_change_parameter = RecoverChangeParameter(name=fix_eval.key, old_value=old_value, new_value=fix_eval.value)
+ recover_change_parameter_list.append(recover_change_parameter)
+ self.context['ocp_deployment_info'][id]['config'] = OCPDeploymnetConfig(**json.loads(json.dumps(config_json)))
+ deploy.deploy_config.dump()
+ self.recreate_deployment(id)
+
+ return recover_change_parameter_list
+
+ def recreate_deployment(self, id):
+ log.get_logger().info('recreate ocp deployment')
+ config = self.context['ocp_deployment_info'][id]['config'] if self.context['ocp_deployment_info'][id]['config'] is not None else None
+ log.get_logger().info('config: %s' % config)
+ if config is not None:
+ cluster_config_yaml_path = self.create_ocp_config_path(config)
+ self.create_ocp_deployment(self.context['ocp_deployment_id'][id], cluster_config_yaml_path)
+
+ def modify_config(self, component, id, fix_eval):
+ log.get_logger().info('modify ocp config')
+ if fix_eval.key == "parameters":
+ raise Exception("try to change parameters")
+ config = self.context['ocp_deployment_info'][id] if self.context['ocp_deployment_info'] is not None else None
+ if config is None:
+ log.get_logger().warn("config is none, no need to modify")
+ raise Exception('config is none')
+ log.get_logger().info('%s ocp config: %s' % (id, config))
+ config = config['config']
+ config_dict = config.dict()
+ if config_dict['components'] is None:
+ log.get_logger().warn("component is none, no need to modify")
+ raise Exception('component is none')
+ old_value = None
+ for value in config_dict['components'].values():
+ if value is not None and 'component' in value.keys() and value['component'] == component:
+ log.get_logger().info('old value: %s' % value)
+ if fix_eval.key in value.keys():
+ log.get_logger().info('new value: %s' % fix_eval.value)
+ old_value = value[fix_eval.key]
+ value[fix_eval.key] = fix_eval.value
+ elif "parameters" in value.keys() and value["parameters"] is not None:
+ log.get_logger().info('new value: %s' % fix_eval.value)
+ for parameter_dict in value["parameters"]:
+ parameter = Parameter(**parameter_dict)
+ if parameter.key == fix_eval.key:
+ if fix_eval.operation == FixEval.DEL:
+ old_value = parameter.value
+ value["parameters"].remove(parameter_dict)
+ else:
+ parameter_dict[fix_eval.key] = fix_eval.value
+ return config_dict, old_value
+ return None, None
+
+ @serial("install")
+ def install(self, id, background_tasks):
+ task_manager = task.get_task_manager()
+ task_info = task_manager.get_task_info(id, task_type="install")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception("task {0} exists and not finished".format(id))
+ task_manager.del_task_info(id, task_type="install")
+ self.context['ocp_deployment']['task_id'] = self.context['ocp_deployment']['task_id'] + 1 if self.context['ocp_deployment']['task_id'] else 1
+ background_tasks.add_task(self._do_install, id, self.context['ocp_deployment']['task_id'])
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'install'
+ ret = TaskInfo(id=self.context['ocp_deployment']['task_id'], status=task_status, result=task_res, total='init start_check, start, connect, bootstrap, display', message=task_message)
+ self.context['task_info'][self.context['ocp_deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register("install")
+ def _do_install(self, id, task_id):
+ self.context['deploy_status'] = self.context['process_installed'] = ''
+ log.get_logger().info("clean io buffer before start install")
+ self.buffer.clear()
+ log.get_logger().info("clean namespace for init")
+ for c in self.obd.deploy.deploy_config.components:
+ for plugin in const.INIT_PLUGINS:
+ if c in self.obd.namespaces:
+ self.obd.namespaces[c].set_return(plugin, None)
+ log.get_logger().info("clean namespace for start")
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ if component in self.obd.namespaces:
+ self.obd.namespaces[component].set_return(plugin, None)
+
+ name = self.context['ocp_deployment_id'][id]
+ deploy = self.obd.deploy
+ log.get_logger().info("start deploy %s", name)
+ opt = Values()
+ setattr(opt, "clean", True)
+ setattr(opt, "force", True)
+ self.obd.set_options(opt)
+ try:
+ deploy_success = self.obd.deploy_cluster(name)
+ if not deploy_success:
+ log.get_logger().warn("deploy %s failed", name)
+ raise Exception('deploy failed')
+ except:
+ self.context['deploy_status'] = 'failed'
+ raise Exception('deploy failed')
+ log.get_logger().info("deploy %s succeed", name)
+
+ repositories = self.obd.load_local_repositories(self.obd.deploy.deploy_info, False)
+ repositories = self.obd.sort_repository_by_depend(repositories, self.obd.deploy.deploy_config)
+ start_success = True
+ oceanbase_repository = None
+ for repository in repositories:
+ log.get_logger().info("begin start %s", repository.name)
+ opt = Values()
+ setattr(opt, "components", repository.name)
+ setattr(opt, "strict_check", False)
+ setattr(opt, "metadb_cursor", self.context['metadb_cursor'])
+ self.obd.set_options(opt)
+ if repository.name == const.OCEANBASE_CE:
+ oceanbase_repository = repository
+ ret = self.obd._start_cluster(self.obd.deploy, repositories)
+ if not ret:
+ log.get_logger().warn("failed to start component: %s", repository.name)
+ start_success = False
+ log.get_logger().info("end start %s", repository.name)
+ if not start_success:
+ if len(repositories) > 1 and oceanbase_repository:
+ drop_tenant_plugins = self.obd.search_py_script_plugin([repository for repository in repositories if repository.name == const.OCEANBASE_CE], 'drop_tenant', no_found_act='warn')
+ config = self.context['ocp_deployment_info'][id]['config'].components.oceanbase
+ cursor = Cursor(ip=config.topology[0].servers[0].ip, port=config.mysql_port, user='root',
+ password=config.root_password, stdio=self.obd.stdio)
+ opt = Values()
+ setattr(opt, "tenant_name", self.context['meta_tenant'])
+ self.obd.set_options(opt)
+ self.obd.call_plugin(drop_tenant_plugins[oceanbase_repository], oceanbase_repository, cursor=cursor)
+ opt = Values()
+ setattr(opt, "tenant_name", self.context['monitor_tenant'])
+ self.obd.set_options(opt)
+ self.obd.call_plugin(drop_tenant_plugins[oceanbase_repository], oceanbase_repository, cursor=cursor)
+ raise Exception("task {0} start failed".format(name))
+ self.obd.deploy.update_deploy_status(DeployStatus.STATUS_RUNNING)
+ log.get_logger().info("finish do start %s", name)
+ if not self.context['ocp_deployment_info'][id]['config'].components.ocpserver.metadb:
+ log.get_logger().info("begin take_over metadb")
+ ocp_info = self.get_installed_ocp_info(id)
+ self.obd.options._update_loose({"address": ocp_info.url[0], "user": ocp_info.account, "password": ocp_info.password})
+ self.obd.export_to_ocp(name)
+ log.get_logger().info("finish take_over metadb")
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ self.obd.set_deploy(deploy)
+ self.context['process_installed'] = 'done'
+
+ def get_install_task_info(self, id, task_id):
+ log.get_logger().info('get ocp install task info')
+ name = self.context['ocp_deployment_id'][id]
+ task_info = self.context['task_info'][self.context['ocp_deployment'][task_id]]
+ if task_info is None:
+ raise Exception("task {0} not found".format(task_id))
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ task_info.info = []
+ task_info.finished = ''
+ failed = 0
+ self.context['ocp_deployment']['failed'] = 0 if not self.context['ocp_deployment']['failed'] else self.context['ocp_deployment']['failed']
+ if not self.obd.deploy:
+ return task_info
+ for component in self.obd.deploy.deploy_config.components:
+ if component in self.obd.namespaces:
+ for plugin in const.INIT_PLUGINS:
+ task_info.current = f'{component}-{plugin}'
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ failed += 1
+ step_info.result = TaskResult.FAILED
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ else:
+ self.context['ocp_deployment']['failed'] += 1
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ task_info.current = f'{component}-{plugin}'
+ if component not in self.obd.namespaces:
+ break
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ if self.obd.deploy.deploy_info.status == DeployStatus.STATUS_RUNNING and self.context['process_installed'] == 'done':
+ task_info.result = TaskResult.SUCCESSFUL
+ task_info.status = TaskStatus.FINISHED
+
+ if failed or self.context['ocp_deployment']['failed'] >= 1500 or self.context['deploy_status'] == 'failed':
+ self.context['ocp_deployment']['failed'] = 0
+ task_info.result = TaskResult.FAILED
+ task_info.status = TaskStatus.FINISHED
+ return task_info
+
+ @serial("reinstall")
+ def reinstall(self, id, background_tasks):
+ log.get_logger().info('start reinstall')
+ task_manager = task.get_task_manager()
+ task_info = task_manager.get_task_info(id, task_type="reinstall")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception("task {0} exists and not finished".format(id))
+ task_manager.del_task_info(id, task_type="reinstall")
+ background_tasks.add_task(self._do_reinstall, id)
+ self.context['ocp_deployment']['task_id'] = self.context['ocp_deployment']['task_id'] + 1 if self.context['ocp_deployment'][
+ 'task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'reinstall'
+ ret = TaskInfo(id=self.context['ocp_deployment']['task_id'], status=task_status, result=task_res,
+ total='destroy init start_check, start, connect, bootstrap, display', message=task_message)
+ self.context['task_info'][self.context['ocp_deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register("reinstall")
+ def _do_reinstall(self, id):
+ log.get_logger().info("clean io buffer before start reinstall")
+ self.buffer.clear()
+ log.get_logger().info("clean namespace for init")
+ for c in self.obd.deploy.deploy_config.components:
+ for plugin in const.INIT_PLUGINS:
+ if c in self.obd.namespaces:
+ self.obd.namespaces[c].set_return(plugin, None)
+ log.get_logger().info("clean namespace for start")
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ if component in self.obd.namespaces:
+ self.obd.namespaces[component].set_return(plugin, None)
+
+ name = self.context['ocp_deployment_id'][id]
+ repositories = self.obd.repositories
+ log.get_logger().info('start destroy %s' % name)
+ opt = Values()
+ setattr(opt, "force_kill", True)
+ self.obd.set_options(opt)
+ if not self.obd._destroy_cluster(self.obd.deploy, repositories):
+ raise Exception('destroy failed')
+
+ self.obd.set_repositories([])
+ deploy = self.obd.deploy_manager.create_deploy_config(name, self.context['ocp_path'])
+ if not deploy:
+ raise Exception("no such deploy for name:{0}".format(name))
+ deploy_config = deploy.deploy_config
+ pkgs, repositories, errors = self.obd.search_components_from_mirrors(deploy_config, only_info=True)
+ if errors:
+ raise Exception("{}".format('\n'.join(errors)))
+ repositories.extend(pkgs)
+ repositories = self.obd.sort_repository_by_depend(repositories, deploy_config)
+ for repository in repositories:
+ real_servers = set()
+ cluster_config = deploy_config.components[repository.name]
+ for server in cluster_config.servers:
+ if server.ip in real_servers:
+ raise Exception(
+ "Deploying multiple {} instances on the same server is not supported.'".format(
+ repository.name))
+ real_servers.add(server.ip)
+ self.obd.search_param_plugin_and_apply(repositories, deploy_config)
+ self.obd.set_repositories(repositories)
+
+ gen_config_plugins = self.obd.search_py_script_plugin(repositories, 'generate_config')
+ for repository in repositories:
+ ret = self.obd.call_plugin(gen_config_plugins[repository], repository, generate_check=False,
+ generate_consistent_config=True, auto_depend=True)
+ if ret is None:
+ raise Exception("generate config error")
+ elif not ret and ret.get_return("exception"):
+ raise ret.get_return("exception")
+ if not self.obd.deploy.deploy_config.dump():
+ raise Exception('generate config dump error,place check disk space!')
+
+ log.get_logger().info("start deploy %s", name)
+ opt = Values()
+ setattr(opt, "clean", True)
+ setattr(opt, "force", True)
+ self.obd.set_options(opt)
+ deploy_success = self.obd.deploy_cluster(name)
+ if not deploy_success:
+ log.get_logger().warn("deploy %s failed", name)
+ raise Exception('deploy failed')
+ log.get_logger().info("deploy %s succeed", name)
+
+ repositories = self.obd.load_local_repositories(self.obd.deploy.deploy_info, False)
+ repositories = self.obd.sort_repository_by_depend(repositories, self.obd.deploy.deploy_config)
+ start_success = True
+ for repository in repositories:
+ opt = Values()
+ setattr(opt, "components", repository.name)
+ setattr(opt, "strict_check", False)
+ setattr(opt, "metadb_cursor", self.context['metadb_cursor'])
+ self.obd.set_options(opt)
+ ret = self.obd._start_cluster(self.obd.deploy, repositories)
+ if not ret:
+ log.get_logger().warn("failed to start component: %s", repository.name)
+ start_success = False
+ if not start_success:
+ raise Exception("task {0} start failed".format(name))
+
+ self.obd.deploy.update_deploy_status(DeployStatus.STATUS_RUNNING)
+ self.context['process_installed'] = 'done'
+ log.get_logger().info("finish do start %s", name)
+
+ def get_reinstall_task_info(self, id, task_id):
+ name = self.context['ocp_deployment_id'][id]
+ task_info = self.context['task_info'][self.context['ocp_deployment'][task_id]]
+ if task_info is None:
+ raise Exception("task {0} not found".format(task_id))
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ task_info.info = []
+ task_info.finished = ''
+ failed = 0
+ self.context['ocp_deployment']['failed'] = 0 if not self.context['ocp_deployment']['failed'] else self.context['ocp_deployment']['failed']
+
+ for c in self.obd.deploy.deploy_config.components:
+ step_info = TaskStepInfo(name=f'{c}-{const.DESTROY_PLUGIN}', status=TaskStatus.RUNNING,
+ result=TaskResult.RUNNING)
+ if c in self.obd.namespaces:
+ if self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN) is not None:
+ task_info.status = TaskStatus.RUNNING
+ task_info.current = f'{c}-{const.DESTROY_PLUGIN}'
+ step_info.status = TaskStatus.FINISHED
+ if not self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ task_info.info.append(step_info)
+ task_info.finished += f'{c}-{const.DESTROY_PLUGIN} '
+
+ for component in self.obd.deploy.deploy_config.components:
+ if component in self.obd.namespaces:
+ for plugin in const.INIT_PLUGINS:
+ task_info.current = f'{component}-{plugin}'
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ failed += 1
+ step_info.result = TaskResult.FAILED
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ else:
+ self.context['ocp_deployment']['failed'] += 1
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ task_info.current = f'{component}-{plugin}'
+ if component not in self.obd.namespaces:
+ break
+ if self.obd.namespaces[component].get_return(plugin) is not None:
+ if not self.obd.namespaces[component].get_return(plugin):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ if self.obd.deploy.deploy_info.status == DeployStatus.STATUS_RUNNING and self.context['process_installed'] == 'done':
+ task_info.result = TaskResult.SUCCESSFUL
+ task_info.status = TaskStatus.FINISHED
+
+ if failed or self.context['ocp_deployment']['failed'] >= 1500:
+ self.context['ocp_deployment']['failed'] = 0
+ task_info.result = TaskResult.FAILED
+ task_info.status = TaskStatus.FINISHED
+ return task_info
+
+ @serial("destroy")
+ def destroy(self, id, background_tasks):
+ task_manager = task.get_task_manager()
+ task_info = task_manager.get_task_info(id, task_type="destroy")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception("task {0} exists and not finished".format(id))
+ task_manager.del_task_info(id, task_type="destroy")
+ background_tasks.add_task(self._destroy_cluster, id)
+ self.context['ocp_deployment']['task_id'] = self.context['ocp_deployment']['task_id'] + 1 \
+ if self.context['ocp_deployment']['task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'destroy'
+ ret = TaskInfo(id=self.context['ocp_deployment']['task_id'], status=task_status, result=task_res,
+ total='destroy', message=task_message)
+ self.context['task_info'][self.context['ocp_deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register("destroy")
+ def _destroy_cluster(self, id):
+ name = self.context['ocp_deployment_id'][id]
+ if not name:
+ raise Exception(f"no such deploy for id: {id}")
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ if not deploy:
+ raise Exception("no such deploy for id: {0}".format(id))
+ self.obd.set_deploy(deploy)
+
+ repositories = self.obd.load_local_repositories(deploy.deploy_info)
+ self.obd.set_repositories(repositories)
+ self.obd.set_options(Values({'force_kill': True}))
+ self.obd.search_param_plugin_and_apply(repositories, deploy.deploy_config)
+ # set namespace return value to none before do destroy
+ for component in self.obd.deploy.deploy_config.components:
+ if component in self.obd.namespaces:
+ self.obd.namespaces[component].set_return(const.DESTROY_PLUGIN, None)
+
+ ret = self.obd._destroy_cluster(deploy, repositories)
+ if not ret:
+ raise Exception("destroy cluster {0} failed".format(name))
+ deploy.update_deploy_status(DeployStatus.STATUS_CONFIGURED)
+ self.obd.set_options(Values())
+
+ def get_destroy_task_info(self, id, task_id):
+ name = self.context['ocp_deployment_id'][id]
+ task_info = self.context['task_info'][self.context['ocp_deployment'][task_id]]
+ if task_info is None:
+ raise Exception("task {0} not found".format(task_id))
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ task_info.info = []
+ task_info.finished = ''
+
+ failed = 0
+ for c in self.obd.deploy.deploy_config.components:
+ step_info = TaskStepInfo(name=f'{c}-{const.DESTROY_PLUGIN}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ if c in self.obd.namespaces:
+ if self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN) is not None:
+ task_info.status = TaskStatus.RUNNING
+ task_info.current = f'{c}-{const.DESTROY_PLUGIN}'
+ step_info.status = TaskStatus.FINISHED
+ if not self.obd.namespaces[c].get_return(const.DESTROY_PLUGIN):
+ step_info.result = TaskResult.FAILED
+ failed += 1
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ task_info.info.append(step_info)
+ task_info.finished += f'{c}-{const.DESTROY_PLUGIN} '
+ if self.obd.deploy.deploy_info.status == DeployStatus.STATUS_CONFIGURED:
+ task_info.result = TaskResult.SUCCESSFUL
+ task_info.status = TaskStatus.FINISHED
+
+ if failed:
+ task_info.result = TaskResult.FAILED
+ task_info.status = TaskStatus.FINISHED
+ return task_info
+
+ def create_ocp_info(self, metadb):
+ deploy = self.obd.deploy
+ if not deploy:
+ raise Exception("no such deploy")
+ self.obd.set_deploy(deploy)
+
+ deploy = self.obd.deploy
+ if not deploy:
+ raise Exception(f"no such deploy")
+ deploy_config = deploy.deploy_config
+ pkgs, repositories, errors = self.obd.search_components_from_mirrors(deploy_config, only_info=True)
+ if errors:
+ raise Exception("{}".format('\n'.join(errors)))
+ repositories.extend(pkgs)
+ repositories = self.obd.sort_repository_by_depend(repositories, deploy_config)
+
+ ocp_servers = []
+ for repository in repositories:
+ cluster_config = deploy_config.components[repository.name]
+ for server in cluster_config.servers:
+ ocp_servers.append(server.ip)
+
+ current_version = repositories[0].version
+ self.context['ocp_info'][metadb.cluster_name] = OcpInfo(cluster_name=metadb.cluster_name, status=self.context['ocp_deployment_info'][self.context['id']]['status'], current_version=current_version, ocp_servers=ocp_servers)
+ return self.context['ocp_info'][metadb.cluster_name]
+
+ def get_ocp_info(self, cluster_name):
+ if self.context['ocp_info'][cluster_name]:
+ return self.context['ocp_info'][cluster_name]
+
+ @serial("upgrade_precheck")
+ def upgrade_precheck(self, cluster_name, background_tasks):
+ task_manager = task.get_task_manager()
+ if not cluster_name:
+ raise Exception(f"no such deploy for cluster_name: {cluster_name}")
+ task_info = task_manager.get_task_info(cluster_name, task_type="upgrade_precheck")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception(f"task {cluster_name} exists and not finished")
+ deploy = self.obd.deploy
+ if not deploy:
+ raise Exception(f"no such deploy: {cluster_name}")
+ deploy_config = deploy.deploy_config
+ pkgs, repositories, errors = self.obd.search_components_from_mirrors(deploy_config, only_info=True)
+ if errors:
+ raise Exception("{}".format('\n'.join(errors)))
+ repositories.extend(pkgs)
+ repositories = self.obd.sort_repository_by_depend(repositories, deploy_config)
+ try:
+ ssh_clients = self.obd.get_clients(deploy_config, repositories)
+ except:
+ deploy_config.user.username = self.context['upgrade_user']
+ deploy_config.user.password = self.context['upgrade_user_password']
+ ssh_clients = self.obd.get_clients(deploy_config, repositories)
+ deploy_config.dump()
+ for repository in repositories:
+ real_servers = set()
+ cluster_config = deploy_config.components[repository.name]
+ for server in cluster_config.servers:
+ if server.ip in real_servers:
+ raise Exception(
+ "Deploying multiple {} instances on the same server is not supported.'".format(
+ repository.name))
+ return False
+ real_servers.add(server.ip)
+ self.obd.search_param_plugin_and_apply(repositories, deploy_config)
+ repositories = [repository for repository in repositories if repository.name in ['ocp-server', 'ocp-server-ce']]
+ self.obd.set_repositories(repositories)
+
+ start_check_plugins = self.obd.search_py_script_plugin(repositories, 'upgrade_check', no_found_act='warn')
+
+ self._upgrade_precheck(cluster_name, repositories, start_check_plugins, init_check_status=True)
+ info = task_manager.get_task_info(cluster_name, task_type="upgrade_check")
+ if info is not None and info.exception is not None:
+ raise info.exception
+ task_manager.del_task_info(cluster_name, task_type="upgrade_check")
+ background_tasks.add_task(self._upgrade_precheck, cluster_name, repositories, start_check_plugins,
+ init_check_status=False)
+ self.context['ocp_deployment']['task_id'] = self.context['ocp_deployment']['task_id'] + 1 if self.context['ocp_deployment'][
+ 'task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'upgrade_check'
+ ret = TaskInfo(id=self.context['ocp_deployment']['task_id'], status=task_status, result=task_res,
+ message=task_message, total='task, machine, ob_version')
+ self.context['task_info'][self.context['ocp_deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register('upgrade_precheck')
+ def _upgrade_precheck(self, name, repositories, start_check_plugins, init_check_status=False):
+ if init_check_status:
+ self._init_upgrade_precheck(repositories, start_check_plugins)
+ else:
+ self._do_upgrade_precheck(repositories, start_check_plugins)
+
+ def _init_upgrade_precheck(self, repositories, start_check_plugins):
+ param_check_status = {}
+ servers_set = set()
+ for repository in repositories:
+ if repository not in start_check_plugins:
+ continue
+ repository_status = {}
+ res = self.obd.call_plugin(start_check_plugins[repository], repository, init_check_status=True, meta_cursor=self.context['metadb_cursor'])
+ if not res and res.get_return("exception"):
+ raise res.get_return("exception")
+ servers = self.obd.deploy.deploy_config.components.get(repository.name).servers
+ for server in servers:
+ repository_status[server] = {'param': CheckStatus()}
+ servers_set.add(server)
+ param_check_status[repository.name] = repository_status
+
+ def _do_upgrade_precheck(self, repositories, start_check_plugins):
+ gen_config_plugins = self.obd.search_py_script_plugin(repositories, 'generate_config')
+ if len(repositories) != len(gen_config_plugins):
+ raise Exception("param_check: config error, check stop!")
+
+ for repository in repositories:
+ ret = self.obd.call_plugin(gen_config_plugins[repository], repository, generate_check=False, generate_consistent_config=True, auto_depend=True)
+ if ret is None:
+ raise Exception("generate config error")
+ elif not ret and ret.get_return("exception"):
+ raise ret.get_return("exception")
+ if not self.obd.deploy.deploy_config.dump():
+ raise Exception('generate config dump error,place check disk space!')
+
+ for repository in repositories:
+ res = self.obd.call_plugin(start_check_plugins[repository], repository, database=self.context['meta_database'] ,meta_cursor=self.context['metadb_cursor'])
+ if not res and res.get_return("exception"):
+ raise res.get_return("exception")
+
+ def get_upgrade_precheck_result(self, cluster_name, task_id):
+ precheck_result = PrecheckTaskInfo()
+ deploy = self.obd.deploy
+ if not deploy:
+ deploy = self.obd.deploy_manager.get_deploy_config(cluster_name)
+ self.obd.set_deploy(deploy)
+ components = deploy.deploy_config.components
+ task_info = self.context['task_info'][self.context['ocp_deployment'][task_id]]
+ check_result = []
+ task_info.info = []
+ if not task_info:
+ raise Exception(f"no such task_info for task_id: {task_id}")
+
+ all_passed = False
+ for component in components:
+ namespace_union = {}
+ namespace = self.obd.get_namespace(component)
+ if namespace:
+ variables = namespace.variables
+ if 'start_check_status' in variables.keys():
+ namespace_union = util.recursive_update_dict(namespace_union, variables.get('start_check_status'))
+ if namespace_union:
+ for server, result in namespace_union.items():
+ if result is None:
+ log.get_logger().warn("precheck for server: {} is None".format(server.ip))
+ continue
+ all_passed = self.parse_precheck_result(component, check_result, task_info, server, result)
+ precheck_result.precheck_result = check_result
+ precheck_result.task_info = task_info
+ status_flag = [i.status for i in task_info.info]
+ log.get_logger().info('task status: %s' % status_flag)
+ if TaskResult.RUNNING not in status_flag:
+ task_info.status = TaskStatus.FINISHED
+ task_info.result = TaskResult.SUCCESSFUL if all_passed else TaskResult.FAILED
+ return precheck_result
+
+ @serial("upgrade")
+ def upgrade_ocp(self, cluster_name, version, usable, background_tasks):
+ task_manager = task.get_task_manager()
+ task_info = task_manager.get_task_info(cluster_name, task_type="ocp_upgrade")
+ if task_info is not None and task_info.status != TaskStatus.FINISHED:
+ raise Exception(f"task {cluster_name} exists and not finished")
+ task_manager.del_task_info(cluster_name, task_type="upgrade")
+ self.obd.set_options(Values({"component": 'ocp-server', "version": version, "usable": usable}))
+ background_tasks.add_task(self._upgrade, 'id', cluster_name)
+ self.context['ocp_deployment']['task_id'] = self.context['ocp_deployment']['task_id'] + 1 if self.context['ocp_deployment']['task_id'] else 1
+ task_status = TaskStatus.RUNNING.value
+ task_res = TaskResult.RUNNING.value
+ task_message = 'upgrade'
+ ret = TaskInfo(id=self.context['ocp_deployment']['task_id'], status=task_status, result=task_res, total='upgrade', message=task_message)
+ self.context['task_info'][self.context['ocp_deployment'][ret.id]] = ret
+ return ret
+
+ @auto_register('upgrade')
+ def _upgrade(self, id, app_name):
+ self.context['upgrade']['succeed'] = None
+ log.get_logger().info("clean io buffer before start install")
+ self.buffer.clear()
+ log.get_logger().info("clean namespace for init")
+ for c in self.obd.deploy.deploy_config.components:
+ for plugin in const.INIT_PLUGINS:
+ if c in self.obd.namespaces:
+ self.obd.namespaces[c].set_return(plugin, None)
+ log.get_logger().info("clean namespace for start")
+ for component in self.obd.deploy.deploy_config.components:
+ for plugin in const.START_PLUGINS:
+ if component in self.obd.namespaces:
+ self.obd.namespaces[component].set_return(plugin, None)
+
+ deploy = self.obd.deploy
+ if not deploy:
+ raise Exception(f"no such deploy: {app_name}")
+ deploy_config = deploy.deploy_config
+ deploy_info = deploy.deploy_info
+ pkgs, repositories, errors = self.obd.search_components_from_mirrors(deploy_config, only_info=True)
+ if errors:
+ raise Exception("{}".format('\n'.join(errors)))
+ repositories.extend(pkgs)
+ repositories = self.obd.sort_repository_by_depend(repositories, deploy_config)
+ self.obd.search_param_plugin_and_apply(repositories, deploy_config)
+ repositories = [repository for repository in repositories if repository.name in ['ocp-server', 'ocp-server-ce']]
+ self.obd.set_repositories(repositories)
+ setattr(self.obd.options, 'component', repositories[0].name)
+
+ try:
+ if deploy_info.status == DeployStatus.STATUS_RUNNING:
+ if self._ocp_upgrade_use_obd(repositories, deploy):
+ log.get_logger().info("finish do upgrade %s", app_name)
+ else:
+ if not self._ocp_upgrade_from_new_deployment(repositories, deploy, pkgs, app_name):
+ self.context['upgrade']['succeed'] = False
+ return
+ log.get_logger().info("finish do upgrade %s", app_name)
+ self.obd.deploy.update_deploy_status(DeployStatus.STATUS_RUNNING)
+ self.context['upgrade']['succeed'] = True
+ except:
+ log.get_logger().info("upgrade %s failed", app_name)
+ self.context['upgrade']['succeed'] = False
+
+ def _ocp_upgrade_use_obd(self, repositories, deploy):
+ deploy_config = deploy.deploy_config
+ deploy_info = deploy.deploy_info
+ component = getattr(self.obd.options, 'component')
+ version = getattr(self.obd.options, 'version')
+ if component == const.OCP_SERVER and (version == '4.0.3' or version == '4.2.0' or version == '4.2.1'):
+ component = const.OCP_SERVER_CE
+ deploy_config.components[const.OCP_SERVER_CE] = deploy_config.components[const.OCP_SERVER]
+ deploy_config._src_data[const.OCP_SERVER_CE] = deploy_config._src_data[const.OCP_SERVER]
+ usable = getattr(self.obd.options, 'usable', '')
+ disable = getattr(self.obd.options, 'disable', '')
+
+ opt = Values()
+ setattr(opt, "skip_create_tenant", True)
+ self.obd.set_options(opt)
+
+ current_repository = None
+ for current_repository in repositories:
+ if current_repository.version == '4.0.3':
+ setattr(opt, "switch_monitor_tenant_flag", 'True')
+ self.obd.set_options(opt)
+ if current_repository.name == component:
+ break
+
+ if not version:
+ self.obd._call_stdio('error', 'Specify the target version.')
+ raise Exception('Specify the upgrade version.')
+
+ if usable:
+ usable = usable.split(',')
+ if disable:
+ disable = disable.split(',')
+
+ self.obd._call_stdio('verbose', 'search target version')
+ images = self.obd.search_images(component, version=version, disable=disable, usable=usable)
+ if not images:
+ self.obd._call_stdio('error', 'No such package %s-%s' % (component, version))
+ raise Exception('No such package %s-%s' % (component, version))
+ if len(images) > 1:
+ self.obd._call_stdio(
+ 'print_list',
+ images,
+ ['name', 'version', 'release', 'arch', 'md5'],
+ lambda x: [x.name, x.version, x.release, x.arch, x.md5],
+ title='%s %s Candidates' % (component, version)
+ )
+ self.obd._call_stdio('error', 'Too many match')
+ raise Exception('Too many match')
+
+ if isinstance(images[0], Repository):
+ pkg = self.obd.mirror_manager.get_exact_pkg(name=images[0].name, md5=images[0].md5)
+ if pkg:
+ repositories = []
+ pkgs = [pkg]
+ else:
+ repositories = [images[0]]
+ pkgs = []
+ else:
+ repositories = []
+ pkg = self.obd.mirror_manager.get_exact_pkg(name=images[0].name, md5=images[0].md5)
+ pkgs = [pkg]
+
+ install_plugins = self.obd.get_install_plugin_and_install(repositories, pkgs)
+ if not install_plugins:
+ raise Exception('install plugin error')
+
+ dest_repository = repositories[0]
+ if dest_repository is None:
+ self.obd._call_stdio('error', 'Target version not found')
+ raise Exception('Target version not found')
+
+ if dest_repository == current_repository:
+ self.obd._call_stdio('print', 'The current version is already %s.\nNoting to do.' % current_repository)
+ raise Exception('The current version is already %s.\nNoting to do.' % current_repository)
+ ssh_clients = self.obd.get_clients(deploy_config, [current_repository])
+ cluster_config = deploy_config.components[current_repository.name]
+
+ upgrade_repositories = [current_repository]
+ upgrade_repositories.append(dest_repository)
+ self.obd.set_repositories(upgrade_repositories)
+
+ self.obd._call_stdio(
+ 'print_list',
+ upgrade_repositories,
+ ['name', 'version', 'release', 'arch', 'md5', 'mark'],
+ lambda x: [x.name, x.version, x.release, x.arch, x.md5,
+ 'start' if x == current_repository else 'dest' if x == dest_repository else ''],
+ title='Packages Will Be Used'
+ )
+
+ index = 1
+ upgrade_ctx = {
+ 'route': [],
+ 'upgrade_repositories': [
+ {
+ 'version': repository.version,
+ 'hash': repository.md5
+ } for repository in upgrade_repositories
+ ],
+ 'index': 1
+ }
+ deploy.start_upgrade(component, **upgrade_ctx)
+
+ install_plugins = self.obd.get_install_plugin_and_install(upgrade_repositories, [])
+ if not install_plugins:
+ raise Exception('install upgrade plugin error')
+
+ if not self.obd.install_repositories_to_servers(deploy_config, upgrade_repositories[1:], install_plugins,
+ ssh_clients, self.obd.options):
+ raise Exception('install upgrade plugin error to server')
+
+ repository = upgrade_repositories[upgrade_ctx['index']]
+ repositories = [repository]
+ upgrade_plugin = self.obd.search_py_script_plugin(repositories, 'upgrade')[repository]
+ self.obd.set_repositories(repositories)
+ ret = self.obd.call_plugin(
+ upgrade_plugin, repository,
+ search_py_script_plugin=self.obd.search_py_script_plugin,
+ local_home_path=self.obd.home_path,
+ current_repository=current_repository,
+ upgrade_repositories=upgrade_repositories,
+ apply_param_plugin=lambda repository: self.obd.search_param_plugin_and_apply([repository],
+ deploy_config),
+ metadb_cursor=self.context['metadb_cursor'],
+ sys_cursor=self.context['sys_cursor']
+ )
+ deploy.update_upgrade_ctx(**upgrade_ctx)
+ if not ret:
+ self.obd.deploy.update_deploy_status(DeployStatus.STATUS_RUNNING)
+ raise Exception('call upgrade plugin error')
+ deploy.stop_upgrade(dest_repository)
+ if version == '4.2.1':
+ if const.OCP_SERVER in deploy_config._src_data:
+ del deploy_config._src_data[const.OCP_SERVER]
+ if const.OCP_SERVER in deploy_config.components:
+ del deploy_config.components[const.OCP_SERVER]
+ if const.OCP_SERVER in deploy_info.components:
+ del deploy_info.components[const.OCP_SERVER]
+ if const.OCEANBASE_CE in deploy_config.components:
+ del deploy_config.components[const.OCEANBASE_CE]
+ if const.OCEANBASE_CE in deploy_config._src_data and const.OBPROXY_CE not in deploy_info.components:
+ del deploy_config._src_data[const.OCEANBASE_CE]
+ if const.OCEANBASE_CE in deploy_info.components and const.OBPROXY_CE not in deploy_info.components:
+ del deploy_info.components[const.OCEANBASE_CE]
+ deploy_config.dump()
+ return True
+
+ def _ocp_upgrade_from_new_deployment(self, repositories, deploy, pkgs, name):
+ deploy_config = deploy.deploy_config
+ try:
+
+ component = getattr(self.obd.options, 'component')
+ version = getattr(self.obd.options, 'version')
+ usable = getattr(self.obd.options, 'usable', '')
+ disable = getattr(self.obd.options, 'disable', '')
+ if not version:
+ self.obd._call_stdio('error', 'Specify the target version.')
+ raise Exception('Specify the upgrade version.')
+
+ if usable:
+ usable = usable.split(',')
+ if disable:
+ disable = disable.split(',')
+
+ self.obd._call_stdio('verbose', 'search target version')
+ images = self.obd.search_images(component, version=version, disable=disable, usable=usable)
+ if not images:
+ self.obd._call_stdio('error', 'No such package %s-%s' % (component, version))
+ raise Exception('No such package %s-%s' % (component, version))
+ if len(images) > 1:
+ self.obd._call_stdio(
+ 'print_list',
+ images,
+ ['name', 'version', 'release', 'arch', 'md5'],
+ lambda x: [x.name, x.version, x.release, x.arch, x.md5],
+ title='%s %s Candidates' % (component, version)
+ )
+ self.obd._call_stdio('error', 'Too many match')
+ raise Exception('Too many match')
+
+ if isinstance(images[0], Repository):
+ pkg = self.obd.mirror_manager.get_exact_pkg(name=images[0].name, md5=images[0].md5)
+ if pkg:
+ repositories = []
+ pkgs = [pkg]
+ else:
+ repositories = [images[0]]
+ pkgs = []
+ else:
+ repositories = []
+ pkg = self.obd.mirror_manager.get_exact_pkg(name=images[0].name, md5=images[0].md5)
+ pkgs = [pkg]
+
+ self.obd.set_repositories(repositories)
+ ssh_clients = self.obd.get_clients(deploy_config, repositories)
+
+ # kill docker and ocp process on upgrade servers
+ for server in self.context['upgrade_servers']:
+ ssh_config = SshConfig(server, username=self.context['upgrade_user'], password=self.context['upgrade_user_password'], port=self.context['upgrade_ssh_port'])
+ ssh_client = SshClient(ssh_config)
+ log.get_logger().info("kill ocp process on host: {}".format(server))
+ kill_docker_res = ssh_client.execute_command("sudo docker ps | grep ocp-all-in-one | awk '{print $1}' | xargs sudo docker stop")
+ log.get_logger().info("stop container get result {0} {1} {2}".format(kill_docker_res.code, kill_docker_res.stdout, kill_docker_res.stderr))
+ kill_process_res = ssh_client.execute_command("ps -ef | grep java | grep 'ocp-server.jar' | grep -v grep | awk '{print $2}' | xargs kill -9 ")
+ log.get_logger().info("stop ocp process get result {0} {1} {2}".format(kill_process_res.code, kill_process_res.stdout, kill_process_res.stderr))
+
+ install_plugins = self.obd.get_install_plugin_and_install(repositories, pkgs)
+ if not install_plugins:
+ return False
+ if not self.obd.install_repositories_to_servers(deploy_config, repositories[1:], install_plugins,
+ ssh_clients, self.obd.options):
+ return False
+ start_success = True
+ repositories = list(set(repositories))
+ for repository in repositories:
+ opt = Values()
+ setattr(opt, "components", repository.name)
+ setattr(opt, "strict_check", False)
+ setattr(opt, "clean", True)
+ setattr(opt, "force", True)
+ self.obd.set_options(opt)
+ log.get_logger().info('begin deploy')
+ ret = self.obd.deploy_cluster(name)
+ log.get_logger().info('finished deploy')
+ if not ret:
+ log.get_logger().error("failed to deploy component: %s", repository.name)
+ raise Exception("failed to deploy component: %s", repository.name)
+ opt = Values()
+ setattr(opt, "skip_create_tenant", True)
+ setattr(opt, "without_ocp_parameter", True)
+ self.obd.set_options(opt)
+ log.get_logger().info('begin start ocp')
+ ret = self.obd.start_cluster(name)
+ log.get_logger().info('finished start ocp')
+ if not ret:
+ log.get_logger().error("failed to start component: %s", repository.name)
+ raise Exception("failed to deploy component: %s", repository.name)
+ return True
+ except Exception as e:
+ return False
+
+ def get_ocp_upgrade_task(self, cluster_name, task_id):
+ task_info = self.context['task_info'][self.context['ocp_deployment'][task_id]]
+ if task_info is None:
+ raise Exception("task {0} not found".format(task_id))
+ task_info.status = TaskStatus.RUNNING
+ task_info.result = TaskResult.RUNNING
+ task_info.info = []
+ task_info.finished = ''
+
+ self.context['ocp_upgrade']['failed'] = 0 if not self.context['ocp_upgrade']['failed'] else self.context['ocp_upgrade']['failed']
+ for component in self.obd.deploy.deploy_config.components:
+ plugin = const.UPGRADE_PLUGINS
+ step_info = TaskStepInfo(name=f'{component}-{plugin}', status=TaskStatus.RUNNING, result=TaskResult.RUNNING)
+ task_info.current = f'{component}-{plugin}'
+ if component not in self.obd.namespaces:
+ break
+ if self.obd.namespaces[component].get_return('stop') is not None:
+ log.get_logger().info('stop: %s' % self.obd.namespaces[component].get_return('stop'))
+ if not self.obd.namespaces[component].get_return('stop'):
+ step_info.result = TaskResult.FAILED
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ else:
+ self.context['ocp_upgrade']['failed'] += 1
+
+ if self.obd.namespaces[component].get_return('start') is not None:
+ log.get_logger().info('start: %s' % self.obd.namespaces[component].get_return('start'))
+ if not self.obd.namespaces[component].get_return('start'):
+ step_info.result = TaskResult.FAILED
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ else:
+ self.context['ocp_upgrade']['failed'] += 1
+
+ if self.obd.namespaces[component].get_return('display') is not None:
+ log.get_logger().info('display: %s' % self.obd.namespaces[component].get_return('display'))
+ if not self.obd.namespaces[component].get_return('display'):
+ step_info.result = TaskResult.FAILED
+ else:
+ step_info.result = TaskResult.SUCCESSFUL
+ step_info.status = TaskStatus.FINISHED
+ else:
+ self.context['ocp_upgrade']['failed'] += 1
+
+ task_info.info.append(step_info)
+ task_info.finished += f'{component}-{plugin} '
+
+ status_flag = [i.result for i in task_info.info]
+ if TaskResult.FAILED in status_flag or self.context['ocp_upgrade']['failed'] >= 3000 or self.context['upgrade']['succeed'] is False:
+ task_info.result = TaskResult.FAILED
+ task_info.status = TaskStatus.FINISHED
+
+ if self.obd.deploy.deploy_info.status == DeployStatus.STATUS_RUNNING and self.context['upgrade']['succeed']:
+ task_info.result = TaskResult.SUCCESSFUL
+ task_info.status = TaskStatus.FINISHED
+ return task_info
+
+ def get_installed_ocp_info(self, id):
+ config = self.context['ocp_deployment_info'][id]['config']
+ servers = config.components.ocpserver.servers
+ port = config.components.ocpserver.port
+ password = config.components.ocpserver.admin_password
+ address = ['http://' + str(server) + ':' + str(port) for server in servers]
+ return OcpInstalledInfo(url=address, password=password)
+
+ def get_not_upgrade_host(self):
+ sql = "select inner_ip_address, ssh_port, version from {0}.compute_host, " \
+ "{0}.compute_host_agent where compute_host.id = compute_host_agent.host_id".format(self.context['meta_database'])
+ log.get_logger().info('sql: %s' % sql)
+ ret = self.context['metadb_cursor'].fetchall(sql)
+
+ data = OcpUpgradeLostAddress(address=[])
+ if ret is False:
+ raise Exception('error get cursor')
+ if ret:
+ log.get_logger().info('ret: %s' % ret)
+ for _ in ret:
+ data.address.append(_['inner_ip_address'])
+ return data
+
+
+
+
diff --git a/service/handler/service_info_handler.py b/service/handler/service_info_handler.py
index 0dc0687..74c549f 100644
--- a/service/handler/service_info_handler.py
+++ b/service/handler/service_info_handler.py
@@ -17,11 +17,25 @@
# You should have received a copy of the GNU General Public License
# along with OceanBase Deploy. If not, see .
-from _deploy import UserConfig
-from service.handler.base_handler import BaseHandler
+import re
+import os
+import copy
from singleton_decorator import singleton
-from service.model.service_info import ServiceInfo
+from _deploy import UserConfig, DeployStatus
+from tool import Cursor, NetUtil
+from ssh import LocalClient, SshConfig, SshClient
+from service.handler.base_handler import BaseHandler
+from service.common import log, const
+from service.model.service_info import ServiceInfo, DeployName
+from service.model.server import OcpServerInfo, InstallerMode, ComponentInfo, MsgInfo
+from service.model.metadb import DatabaseConnection
+from service.model.ocp import OcpDeploymentConfig
+from service.model.deployments import OCPDeploymnetConfig, OcpServer, OcpComponentConfig, Auth
+from service.model.parameter import Parameter
+from service.model.ssh import SshAuth
+from service.model.tenant import TenantConfig, TenantUser, TenantResource
+from service.handler.ocp_handler import OcpHandler
@singleton
@@ -31,3 +45,265 @@ def get_service_info(self):
info = ServiceInfo(user=UserConfig.DEFAULT.get('username'))
return info
+ def version_convert_to_int(self, version):
+ return int(version.split('.')[0]) * 1000 ** 2 + int(version.split('.')[1]) * 1000 + int(
+ version.split('.')[2])
+
+ def version_compare(self, v1, v2):
+ v1_int = self.version_convert_to_int(v1)
+ v2_int = self.version_convert_to_int(v2)
+ return v1_int - v2_int
+
+ def install_repositories(self):
+ pkgs = self.obd.mirror_manager.get_pkgs_info('ocp-server')
+ versions = [pkg.version for pkg in pkgs]
+ return versions
+
+ def execute_command_in_docker(self, contain_id, shell_command):
+ return LocalClient.execute_command(
+ "sudo docker exec %s bash -c '%s'" % (contain_id, shell_command)).stdout.strip()
+
+ def get_missing_required_parameters(self, parameters):
+ results = []
+ for key in ["jdbc_url", "jdbc_username", "jdbc_password"]:
+ if parameters.get(key) is None:
+ results.append(key)
+ return results
+
+ def get_ocp_depend_config(self, cluster_config, stdio):
+ # depends config
+ env = {}
+ depend_observer = False
+ depend_info = {}
+ ob_servers_conf = {}
+ for comp in ["oceanbase", "oceanbase-ce"]:
+ ob_zones = {}
+ if comp in cluster_config.depends:
+ depend_observer = True
+ ob_servers = cluster_config.get_depend_servers(comp)
+ for ob_server in ob_servers:
+ ob_servers_conf[ob_server] = ob_server_conf = cluster_config.get_depend_config(comp, ob_server)
+ if 'server_ip' not in depend_info:
+ depend_info['server_ip'] = ob_server.ip
+ depend_info['mysql_port'] = ob_server_conf['mysql_port']
+ depend_info['root_password'] = ob_server_conf['root_password']
+ zone = ob_server_conf['zone']
+ if zone not in ob_zones:
+ ob_zones[zone] = ob_server
+ break
+ for comp in ['obproxy', 'obproxy-ce']:
+ if comp in cluster_config.depends:
+ obproxy_servers = cluster_config.get_depend_servers(comp)
+ obproxy_server = obproxy_servers[0]
+ obproxy_server_config = cluster_config.get_depend_config(comp, obproxy_server)
+ depend_info['server_ip'] = obproxy_server.ip
+ depend_info['mysql_port'] = obproxy_server_config['listen_port']
+ break
+
+ for server in cluster_config.servers:
+ server_config = copy.deepcopy(cluster_config.get_server_conf_with_default(server))
+ original_server_config = cluster_config.get_original_server_conf(server)
+ missed_keys = self.get_missing_required_parameters(original_server_config)
+ if missed_keys:
+ if 'jdbc_url' in missed_keys and depend_observer:
+ server_config['jdbc_url'] = 'jdbc:oceanbase://{}:{}/{}'.format(depend_info['server_ip'],
+ depend_info['mysql_port'],
+ server_config['ocp_meta_db'])
+ server_config['jdbc_username'] = '%s@%s' % (
+ server_config['ocp_meta_username'], server_config['ocp_meta_tenant']['tenant_name'])
+ server_config['jdbc_password'] = server_config['ocp_meta_password']
+ server_config['root_password'] = depend_info.get('root_password', '')
+ env[server] = server_config
+ return env
+
+ def generate_config(self, cluster_name):
+ log.get_logger().info('do command upgrade with context: {}'.format(self.context))
+ servers = self.context['upgrade_servers']
+ if len(servers) == 0:
+ raise Exception("no server to upgrade")
+ ssh_port = self.context['upgrade_ssh_port']
+ username = self.context['upgrade_user']
+ password = self.context['upgrade_user_password']
+ log.get_logger().info('use command to get info')
+
+ # get monitordb connection info from metadb
+ monitor_user_config = self.context['metadb_cursor'].fetchone("select default_value, value from {0}.config_properties where `key` = 'ocp.monitordb.username'".format(self.context['connection_info'][cluster_name].database))
+ monitor_user = monitor_user_config['value'] if monitor_user_config['value'] else monitor_user_config['default_value']
+ monitor_password_config = self.context['metadb_cursor'].fetchone("select default_value, value from {0}.config_properties where `key` = 'ocp.monitordb.password'".format(self.context['connection_info'][cluster_name].database))
+ monitor_password = monitor_password_config['value'] if monitor_password_config['value'] else monitor_password_config['default_value']
+ monitor_database_config = self.context['metadb_cursor'].fetchone("select default_value, value from {0}.config_properties where `key` = 'ocp.monitordb.database'".format(self.context['connection_info'][cluster_name].database))
+ monitor_database = monitor_database_config['value'] if monitor_database_config['value'] else monitor_database_config['default_value']
+ log.get_logger().info('successfully get monior config from metadb')
+
+ server_port_config = self.context['metadb_cursor'].fetchone("select default_value, value from {0}.config_properties where `key` = 'server.port'".format(self.context['connection_info'][cluster_name].database))
+ server_port = server_port_config['value'] if server_port_config['value'] else server_port_config['default_value']
+ log.get_logger().info('successfully get ocp-server port: %s' % server_port)
+
+ # get memory info and home_path using ssh client
+ ssh_config = SshConfig(servers[0], username=self.context['upgrade_user'], password=self.context['upgrade_user_password'], port=self.context['upgrade_ssh_port'])
+ ssh_client = SshClient(ssh_config)
+ res = ssh_client.execute_command("ps -ef | grep java | grep 'ocp-server.*.jar' | grep -v grep")
+ if not res:
+ raise Exception("failed to query ocp process info")
+ memory_xmx = res.stdout.split("Xmx")[1].split(" ")[0]
+ home_path = res.stdout.split("/lib/ocp-server")[0].split(' ')[-1]
+
+ # check whether ocp docker exists
+ res = ssh_client.execute_command("sudo docker ps | grep ocp-all-in-one ")
+ if res:
+ log.get_logger().info("found ocp docker")
+ home_path = "/home/{0}/ocp-server".format(self.context['upgrade_user']) if self.context['upgrade_user'] != 'root' else "/root/ocp-server"
+ auth = Auth(user=username, port=ssh_port, password=password)
+ jdbc_username = self.context['connection_info'][cluster_name].user
+ user_name = jdbc_username.split('@')[0]
+ tenant_name = jdbc_username.split('@')[1].split('#')[0] if '#' in jdbc_username else jdbc_username.split('@')[1]
+ tenant_user = TenantUser(tenant_name=tenant_name, user_name=user_name, user_database=self.context['connection_info'][cluster_name].database)
+ meta_tenant = TenantConfig(name=tenant_user, password=self.context['connection_info'][cluster_name].password)
+
+ monitor_tenant_name = monitor_user.split('@')[1].split('#')[0] if '#' in monitor_user else monitor_user.split('@')[1]
+ monitor_tenant_username = monitor_user.split('@')[0]
+ monitor_tenant_user = TenantUser(tenant_name=monitor_tenant_name, user_name=monitor_tenant_username, user_database=monitor_database)
+ monitor_tenant = TenantConfig(name=monitor_tenant_user, password=monitor_password)
+ ocp_server = OcpServer(component='ocp-server-ce', metadb=self.context['connection_info'][cluster_name], meta_tenant=meta_tenant, monitor_tenant=monitor_tenant, admin_password='********',
+ home_path=home_path, servers=servers, port=server_port, memory_size=memory_xmx)
+ components = OcpComponentConfig(ocpserver=ocp_server)
+ data = OCPDeploymnetConfig(auth=auth, components=components)
+ log.get_logger().info('create deployment config: %s' % data)
+
+ ocp_handler = OcpHandler()
+ try:
+ cluster_config_yaml_path = ocp_handler.create_ocp_config_path(data)
+ log.get_logger().info('upgrade path: %s' % cluster_config_yaml_path)
+ deployment_id = ocp_handler.create_ocp_deployment(cluster_name, cluster_config_yaml_path)
+ log.get_logger().info('upgrade id: %s' % deployment_id)
+ except Exception as ex:
+ log.get_logger().error(ex)
+
+ def create_ocp_info(self, cluster_name):
+ deploy = self.obd.deploy_manager.get_deploy_config(cluster_name)
+ log.get_logger().info('deploy: %s' % deploy)
+ if deploy and deploy.deploy_info.status in [DeployStatus.STATUS_RUNNING, DeployStatus.STATUS_UPRADEING]:
+ deploy_config = deploy.deploy_config
+ if const.OCP_SERVER in deploy_config.components:
+ cluster_config = deploy_config.components[const.OCP_SERVER]
+ global_config = cluster_config.get_global_conf()
+ if not global_config.get('ocp_meta_username', ''):
+ jdbc_url = global_config['jdbc_url']
+ matched = re.match(r"^jdbc:\S+://(\S+?)(|:\d+)/(\S+)", jdbc_url)
+ if matched:
+ cluster_config.update_global_conf('ocp_meta_db', matched.group(3))
+ cluster_config.update_global_conf('ocp_meta_username', global_config['jdbc_username'].split('@')[0])
+ cluster_config.update_global_conf('ocp_meta_password', global_config['jdbc_password'])
+ cluster_config.update_global_conf('ocp_meta_tenant',
+ {'tenant_name': global_config['jdbc_username'].split('@')[1]})
+ self.obd.set_deploy(deploy)
+ else:
+ self.generate_config(cluster_name)
+
+ def get_deployments_name(self):
+ deploys = self.obd.deploy_manager.get_deploy_configs()
+ log.get_logger().info('deploys: %s' % deploys)
+ ret = DeployName()
+ for _ in deploys:
+ if _.deploy_info.status == DeployStatus.STATUS_RUNNING and \
+ (const.OCP_SERVER in _.deploy_config.components or const.OCP_SERVER_CE in _.deploy_config.components):
+ ret.name.append(_.name)
+ return ret
+
+ def get_metadb_connection(self, name):
+ metadb = DatabaseConnection(cluster_name=name)
+ deploy = self.obd.deploy_manager.get_deploy_config(name)
+ log.get_logger().info('deploys: %s' % deploy)
+ if deploy is None:
+ res = LocalClient.execute_command("sudo docker ps | grep ocp-all-in-one | awk '{print $1}'").stdout.strip()
+ log.get_logger().info('docker ps: %s' % res)
+ if res:
+ metadb.host = self.execute_command_in_docker(res, 'env | grep OCP_METADB_HOST').split('=')[1]
+ metadb.port = self.execute_command_in_docker(res, 'env | grep OCP_METADB_PORT').split('=')[1]
+ metadb.user = self.execute_command_in_docker(res, 'env | grep OCP_METADB_USER').split('=')[1]
+ metadb.password = self.execute_command_in_docker(res, 'env | grep OCP_METADB_PASSWORD').split('=')[1]
+ metadb.database = self.execute_command_in_docker(res, 'env | grep OCP_METADB_DBNAME').split('=')[1]
+ try:
+ self.context['metadb_cursor'] = Cursor(ip=metadb.host, port=metadb.port, user=metadb.user,
+ password=metadb.password, stdio=self.obd.stdio)
+ except:
+ log.get_logger().error('Automatic database connection failed, please input manually.')
+ metadb_copy = copy.deepcopy(metadb)
+ metadb_copy.password = ''
+ return metadb_copy
+ return metadb
+ deploy_config = deploy.deploy_config
+ if const.OCP_SERVER in deploy_config.components:
+ cluster_config = deploy_config.components[const.OCP_SERVER]
+ elif const.OCP_SERVER_CE in deploy_config.components:
+ cluster_config = deploy_config.components[const.OCP_SERVER_CE]
+ else:
+ return metadb
+ servers = cluster_config.servers
+ start_env = self.get_ocp_depend_config(cluster_config, self.obd.stdio)
+ for server in servers:
+ server_config = start_env[server]
+ jdbc_url = server_config['jdbc_url']
+ metadb.user = server_config['jdbc_username']
+ metadb.password = server_config['jdbc_password']
+ matched = re.match(r"^jdbc:\S+://(\S+?)(|:\d+)/(\S+)", jdbc_url)
+ if matched:
+ metadb.host = matched.group(1)
+ metadb.port = int(matched.group(2)[1:])
+ metadb.database = matched.group(3)
+ if server_config.get('ocp_meta_tenant', ''):
+ if 'sys' in metadb.user:
+ try:
+ self.context['sys_cursor'] = Cursor(ip=metadb.host, port=metadb.port, user=metadb.user, password=metadb.password, stdio=self.obd.stdio)
+ except:
+ log.get_logger().error('Automatic database connection failed, please input manually.')
+ metadb.user = server_config['ocp_meta_username'] + '@' + server_config['ocp_meta_tenant']['tenant_name']
+ metadb.password = server_config['ocp_meta_password']
+ metadb.database = server_config['ocp_meta_db']
+ self.context["connection_info"][metadb.cluster_name] = metadb
+ try:
+ self.context['metadb_cursor'] = Cursor(ip=metadb.host, port=metadb.port, user=metadb.user, password=metadb.password, stdio=self.obd.stdio)
+ except:
+ log.get_logger().error('Automatic database connection failed, please input manually.')
+ break
+ metadb_copy = copy.deepcopy(metadb)
+ metadb_copy.password = ''
+ return metadb_copy
+
+ def get_component_agent(self, metadb):
+ try:
+ user = ''
+ self.context["connection_info"][metadb.cluster_name] = metadb
+ self.context['meta_database'] = metadb.database
+ self.context['metadb_cursor'] = Cursor(ip=metadb.host, port=metadb.port, user=metadb.user, password=metadb.password,
+ stdio=self.obd.stdio)
+ log.get_logger().info('cursor: %s' % self.context['metadb_cursor'])
+ monitor_tenant_sql = "select `value` from %s.config_properties where `key` = 'ocp.monitordb.username'" % metadb.database
+ monitor_tenant = self.context['metadb_cursor'].fetchone(monitor_tenant_sql, raise_exception=True)
+ log.get_logger().info('monitor_tenant: %s' % monitor_tenant)
+ tips = False
+ if monitor_tenant and monitor_tenant.get('value') in metadb.user:
+ tips = True
+ sql = "select id from %s.distributed_server" % metadb.database
+ res = self.context['metadb_cursor'].fetchall(sql, raise_exception=True)
+ log.get_logger().info('ocp server ip: %s' % res)
+ component_servers = []
+ component_info = []
+ for _ in res:
+ component_servers.append(_['id'].split(':')[0])
+ component_info.append(ComponentInfo(name=const.OCP_SERVER_CE, ip=component_servers))
+ ocp_version = self.context['metadb_cursor'].fetchone(
+ "select `value` from %s.config_properties where `key` = 'ocp.version'" % metadb.database, raise_exception=True)['value']
+ log.get_logger().info('ocp version: %s' % ocp_version)
+ log.get_logger().info('get obd user')
+ deploy = self.obd.deploy_manager.get_deploy_config(metadb.cluster_name)
+ log.get_logger().info('deploy: %s' % deploy)
+ if deploy and deploy.deploy_info.status in [DeployStatus.STATUS_RUNNING, DeployStatus.STATUS_UPRADEING]:
+ deploy_config = deploy.deploy_config
+ user = deploy_config.user.username
+ return OcpServerInfo(user=user, ocp_version=ocp_version, component=component_info, tips=tips)
+ except Exception as e:
+ log.get_logger().error('failed to get ocp info: %s' % e)
+ log.get_logger().error('Please ensure the use of the meta tenant.')
+ raise Exception('Failed to get ocp info, Please ensure the use of the meta tenant.')
+
diff --git a/service/middleware/ip_white.py b/service/middleware/ip_white.py
new file mode 100644
index 0000000..2640086
--- /dev/null
+++ b/service/middleware/ip_white.py
@@ -0,0 +1,41 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+import re
+
+from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
+from starlette.requests import Request
+from starlette.exceptions import HTTPException
+
+
+class IPBlockMiddleware(BaseHTTPMiddleware):
+ def __init__(self, app, ips):
+ self.app = app
+ self.ip_whitelist = ips
+ super().__init__(app)
+
+ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
+ remote_ip = request.client.host
+ if self.ip_whitelist:
+ for ip_regx in self.ip_whitelist:
+ if re.match(ip_regx, remote_ip):
+ break
+ else:
+ raise HTTPException(status_code=403, detail="Forbidden IP")
+ response = await call_next(request)
+ return response
\ No newline at end of file
diff --git a/service/model/backup.py b/service/model/backup.py
new file mode 100644
index 0000000..7b93542
--- /dev/null
+++ b/service/model/backup.py
@@ -0,0 +1,28 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List, Optional
+from enum import auto
+from fastapi_utils.enums import StrEnum
+from pydantic import BaseModel
+
+
+class BackupMethod(StrEnum):
+ DUMP = auto()
+ DATA_BACKUP = auto()
+
diff --git a/service/model/database.py b/service/model/database.py
new file mode 100644
index 0000000..2452e70
--- /dev/null
+++ b/service/model/database.py
@@ -0,0 +1,33 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List, Optional
+from enum import auto
+from fastapi import Body
+from fastapi_utils.enums import StrEnum
+from pydantic import BaseModel
+
+
+class DatabaseConnection(BaseModel):
+ cluster_name = Body('', description="cluster name of the connection in installer")
+ host: str = Body('', description="host")
+ port: int = Body(0, description="port")
+ user: str = Body('', description="user")
+ password: str = Body('', description="password")
+ database: str = Body('oceanbase', description="database")
+
diff --git a/service/model/deployments.py b/service/model/deployments.py
index ee73333..ab4c310 100644
--- a/service/model/deployments.py
+++ b/service/model/deployments.py
@@ -25,6 +25,8 @@
from fastapi_utils.enums import StrEnum
from service.common.task import TaskStatus, TaskResult
+from service.model.tenant import TenantConfig
+from service.model.database import DatabaseConnection
class Auth(BaseModel):
@@ -33,6 +35,10 @@ class Auth(BaseModel):
port: int = Body(22, description='ssh port')
+class UserCheck(Auth):
+ servers: List[str] = Body(..., description="server ip, ex:[ '1.1.1.1','2.2.2.2']")
+
+
class PrecheckTaskResult(StrEnum):
PASSED = auto()
FAILED = auto()
@@ -58,6 +64,7 @@ class OceanbaseServers(BaseModel):
ip: str = Body(..., description='server ip')
parameters: dict = None
+
class Zone(BaseModel):
name: str = Body(..., description='zone name')
rootservice: str = Body(..., description='root service')
@@ -232,4 +239,49 @@ class Config:
orm_mode = True
+class OCPDeploymentStatus(StrEnum):
+ INIT = auto()
+ DEPLOYING = auto()
+ FINISHED = auto()
+
+
+class ClusterManageInfo(BaseModel):
+ machine: Optional[int] = Body(None, description='manage machine num')
+
+
+class OcpServer(BaseModel):
+ component: str = Body('ocp-server', description='ocp-server component name')
+ version: str = Body('', description='version')
+ package_hash: str = Body('', description='ocp-server package md5')
+ release: str = Body('', description='ocp-server release no')
+ home_path: str = Body('', description='install ocp-server home path')
+ soft_dir: str = Body('', description='software path')
+ log_dir: str = Body('', description='log dir')
+ ocp_site_url: str = Body('', description='ocp server url')
+ port: int = Body(..., description='server port')
+ admin_password: str = Body(..., description='admin password')
+ parameters: List[Parameter] = Body(None, description='config parameter')
+ memory_size: str = Body('2G', description='ocp server memory size')
+ ocp_cpu: int = Body(0, description='ocp server cpu num')
+ meta_tenant: Optional[TenantConfig] = Body(None, description="meta tenant config")
+ monitor_tenant: Optional[TenantConfig] = Body(None, description="monitor tenant config")
+ manage_info: Optional[ClusterManageInfo] = Body(None, description='manage cluster info')
+ servers: List[str] = Body(..., description="server ip, ex:[ '1.1.1.1','2.2.2.2']")
+ metadb: Optional[DatabaseConnection] = Body(None, description="connection info of metadb")
+
+
+class OcpComponentConfig(BaseModel):
+ oceanbase: Optional[OceanBase]
+ obproxy: Optional[ObProxy]
+ ocpserver: OcpServer
+
+
+class OCPDeploymnetConfig(BaseModel):
+ auth: Auth
+ components: OcpComponentConfig
+ home_path: str = Body('', description='global home path')
+ launch_user: Optional[str] = Body(None, description='process user')
+
+
+
diff --git a/service/model/metadb.py b/service/model/metadb.py
new file mode 100644
index 0000000..3c89ae4
--- /dev/null
+++ b/service/model/metadb.py
@@ -0,0 +1,69 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List, Optional
+from enum import auto, Enum
+from fastapi import Body
+from fastapi_utils.enums import StrEnum
+from pydantic import BaseModel
+
+from service.model.ssh import SshAuth
+from service.model.database import DatabaseConnection
+from service.model.parameter import Parameter
+from service.model.deployments import OCPDeploymentStatus
+
+
+class PrecheckTaskResult(StrEnum):
+ PASSED = auto()
+ FAILED = auto()
+ RUNNING = auto()
+
+
+class Flag(Enum):
+ not_matched = 0
+ same_disk = 1
+ data_and_log_same_disk = 2
+ home_data_or_home_log_same_disk = 3
+ data_log_different_disk = 4
+
+
+class MetadbDeploymentConfig(BaseModel):
+ auth: SshAuth = Body(None, description="ssh auth info")
+ cluster_name: str = Body("obcluster", description="cluster name")
+ servers: List[str] = Body(..., description = "servers to deploy")
+ root_password: str = Body("", description="password of user root@sys")
+ home_path: str = Body("", description="home path to install")
+ data_dir: Optional[str] = Body("", description="data directory")
+ log_dir: Optional[str] = Body("", description="log directory")
+ sql_port: int = Body(2881, description="sql port")
+ rpc_port: int = Body(2882, description="rpc port")
+ devname: str = Body('', description='devname')
+ parameters: Optional[List[Parameter]] = Body(None, description='config parameter')
+
+
+class MetadbDeploymentInfo(BaseModel):
+ id: int = Body(0, description="metadb deployment id")
+ status: OCPDeploymentStatus = Body(OCPDeploymentStatus.INIT, description="metadb deployment status, ex: INIT, FINISHED")
+ config: MetadbDeploymentConfig = Body(None, description="metadb deployment")
+ connection: Optional[DatabaseConnection] = Body(None, description="connection info of metadb")
+
+
+class RecoverChangeParameter(BaseModel):
+ name: str = Body(..., description='repaired item')
+ old_value: str = Body(None, description='old value item')
+ new_value: str = Body(None, description='new value item')
diff --git a/service/model/ocp.py b/service/model/ocp.py
new file mode 100644
index 0000000..de87cd9
--- /dev/null
+++ b/service/model/ocp.py
@@ -0,0 +1,109 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List, Optional, Union
+from enum import auto
+from fastapi import Body
+from fastapi_utils.enums import StrEnum
+from pydantic import BaseModel
+
+from service.model.ssh import SshAuth
+from service.model.parameter import Parameter
+from service.model.deployments import OCPDeploymentStatus
+from service.model.tenant import TenantConfig
+from service.model.resource import ServerResource
+from service.model.task import TaskStatus, TaskResult
+from service.model.database import DatabaseConnection
+from service.model.backup import BackupMethod
+
+
+class OcpDeploymentConfig(BaseModel):
+ auth: SshAuth = Body(None, description="ssh auth info")
+ metadb: Union[int, DatabaseConnection] = Body(..., description="connection info of metadb")
+ meta_tenant: Optional[TenantConfig] = Body(None, description="meta tenant config")
+ monitor_tenant: Optional[TenantConfig] = Body(None, description="monitor tenant config")
+ appname: str = Body("ocp", description="ocp app name")
+ admin_password: str = Body('', description="ocp login password")
+ servers: List[str] = Body(..., description="servers to deploy")
+ home_path: str = Body("", description="home path to install")
+ server_port: int = Body(8080, description="server port")
+ parameters: Optional[List[Parameter]]
+
+
+class OcpDeploymentInfo(BaseModel):
+ id: int = Body(0, description="metadb deployment id")
+ status: OCPDeploymentStatus = Body(OCPDeploymentStatus.INIT, description="ocp deployment status, ex: INIT, DEPLOYING, FINISHED")
+ config: Optional[OcpDeploymentConfig] = Body(..., description="ocp deployment config")
+ monitor_display: bool = Body(True, description="monitor tenant configured")
+
+
+class ObserverResource(BaseModel):
+ address: str = Body(..., description="observer address")
+ cpu_total: float = Body(..., description="total cpu")
+ cpu_free: float = Body(..., description="free cpu")
+ memory_total: int = Body(..., description="total memory size")
+ memory_free: int = Body(..., description="free memory size")
+
+
+class MetadbResource(BaseModel):
+ servers: List[ObserverResource] = Body(..., description="observer resource")
+
+
+class OcpResource(BaseModel):
+ servers: List[ServerResource] = Body(..., description="server resource")
+ metadb: MetadbResource = Body(..., description="metadb resource")
+
+
+class OcpDeploymentReport(BaseModel):
+ status: TaskStatus = Body(..., description="task status")
+ result: TaskResult = Body(..., description="task result")
+ servers: List[str] = Body(..., description="ocp server addresses")
+ user: str = Body(..., description="ocp admin user")
+ password: str = Body(..., description="ocp admin password")
+
+
+class OcpInfo(BaseModel):
+ cluster_name: str = Body('', description="ocp deployment cluster_name")
+ status: OCPDeploymentStatus = Body(OCPDeploymentStatus.INIT, description="ocp deployment status, ex:INIT, FINISHED")
+ current_version: str = Body(..., description="current ocp version")
+ ocp_servers: List[str] = Body(..., description="ocp servers")
+ agent_servers: List[str] = Body(None, description="servers deployed agent")
+
+
+class OcpBackupConfig(BaseModel):
+ method: BackupMethod = Body(BackupMethod.DUMP, description="backup method, ex: DUMP, DATA_BACKUP")
+ destination: str = Body(..., description="backup destination")
+ tenants: List[str] = Body(..., description="backup tenants")
+ root_password: Optional[str] = Body(..., description="root password of metadb")
+
+
+class OcpBackupInfo(BaseModel):
+ id: int = Body(0, description="backup id")
+ status: OCPDeploymentStatus = Body(OCPDeploymentStatus.INIT, description="backup status, ex: INIT, RUNNING, FINISHED")
+ config: OcpBackupConfig = Body(..., description="backup config")
+
+
+class OcpInstalledInfo(BaseModel):
+ url: List[str] = Body(..., description="Access address, eq: ip:port")
+ account: str = Body('admin', description="account")
+ password: str = Body(..., description="account password")
+
+
+class OcpUpgradeLostAddress(BaseModel):
+ address: List[str] = Body([], description="lost ip address")
+
diff --git a/service/model/parameter.py b/service/model/parameter.py
new file mode 100644
index 0000000..9aeb360
--- /dev/null
+++ b/service/model/parameter.py
@@ -0,0 +1,29 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List, Optional
+from enum import auto
+from fastapi import Body
+from fastapi_utils.enums import StrEnum
+from pydantic import BaseModel
+
+
+class Parameter(BaseModel):
+ name: str = Body(..., description="parameter name")
+ value: str = Body(..., description="parameter value")
+
diff --git a/service/model/resource.py b/service/model/resource.py
new file mode 100644
index 0000000..01d42cd
--- /dev/null
+++ b/service/model/resource.py
@@ -0,0 +1,61 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List
+from fastapi import Body
+from pydantic import BaseModel
+
+
+class DiskInfo(BaseModel):
+ dev: str = Body(..., description="dev")
+ mount_path: str = Body(..., description="mount path")
+ total_size: str = Body(..., description="total size")
+ free_size: str = Body(..., description="free size")
+
+
+class ServerResource(BaseModel):
+ address: str = Body(..., description="server address")
+ cpu_total: float = Body(..., description="total cpu")
+ cpu_free: float = Body(..., description="free cpu")
+ memory_total: str = Body(..., description="total memory size")
+ memory_free: str = Body(..., description="free memory size")
+ disk: List[DiskInfo] = Body(..., description="disk info")
+
+
+class Disk(BaseModel):
+ path: str = Body(..., description="path")
+ disk_info: DiskInfo = Body(..., description="disk info")
+
+
+class MetaDBResource(BaseModel):
+ address: str = Body(..., description="server address")
+ disk: List[Disk] = Body(..., description="path: disk_info")
+ memory_limit_lower_limit: int = Body(..., description="memory_limit lower limit")
+ memory_limit_higher_limit: int = Body(..., description="memory_limit higher limit")
+ memory_limit_default: int = Body(..., description="default memory_limit")
+ data_size_default: int = Body(..., description="default data size")
+ log_size_default: int = Body(..., description="default log size")
+ flag: int = Body(..., description="which solution to use")
+
+
+class ResourceCheckResult(BaseModel):
+ address: str = Body(..., description='server ip')
+ name: str = Body(..., description="resource check type name, eq memory_limit, data_dir, home_path, log_dir..")
+ check_result: bool = Body(True, description="check result, true/false")
+ error_message: List[str] = Body([], description='error message, eq path not enough')
+
diff --git a/service/model/server.py b/service/model/server.py
new file mode 100644
index 0000000..0e500d1
--- /dev/null
+++ b/service/model/server.py
@@ -0,0 +1,36 @@
+from fastapi import Body
+from fastapi_utils.enums import StrEnum
+from typing import List, Optional
+from pydantic import BaseModel
+from enum import auto
+
+from service.model.metadb import DatabaseConnection
+
+
+class InstallerMode(StrEnum):
+ STANDARD = auto()
+ COMPACT = auto()
+
+
+class ComponentInfo(BaseModel):
+ name: str = Body("ocp-server", description="ocp component")
+ ip: List[str] = Body([], description="server address")
+
+
+class OcpServerInfo(BaseModel):
+ user: str = Body('', description="deploy user")
+ ocp_version: str = Body('', description="ocp-server current version")
+ component: List[ComponentInfo] = Body([], description="component info")
+ tips: bool = Body(False, description='display tips')
+ msg: str = Body('', description="failed message")
+
+
+class MsgInfo(BaseModel):
+ msg: str = Body(..., description="failed message")
+ status: int = Body(..., description='eq: 0, 1')
+
+
+class UserInfo(BaseModel):
+ username: str = Body(..., description='system user')
+
+
diff --git a/service/model/service_info.py b/service/model/service_info.py
index 327d98f..e1d8fb4 100644
--- a/service/model/service_info.py
+++ b/service/model/service_info.py
@@ -19,7 +19,12 @@
from fastapi import Body
from pydantic import BaseModel
+from typing import List
class ServiceInfo(BaseModel):
user: str = Body(..., description='user name')
+
+
+class DeployName(BaseModel):
+ name: List[str] = Body([], description="deploy name list")
diff --git a/service/model/ssh.py b/service/model/ssh.py
new file mode 100644
index 0000000..8ea9a7c
--- /dev/null
+++ b/service/model/ssh.py
@@ -0,0 +1,38 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List, Optional
+from enum import auto
+from fastapi import Body
+from fastapi_utils.enums import StrEnum
+from pydantic import BaseModel
+
+
+class SshAuthMethod(StrEnum):
+ PUBKEY = auto()
+ PASSWORD = auto()
+
+
+class SshAuth(BaseModel):
+ user: str = Body("", description="username")
+ auth_method: SshAuthMethod = Body(SshAuthMethod.PASSWORD, description="auth method")
+ password: str = Body("", description="password")
+ private_key: str = Body("", description="private key")
+ port: int = Body(0, description="ssh port")
+
+
diff --git a/service/model/task.py b/service/model/task.py
new file mode 100644
index 0000000..1e1c2ce
--- /dev/null
+++ b/service/model/task.py
@@ -0,0 +1,76 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List
+from enum import auto
+from fastapi_utils.enums import StrEnum
+from fastapi import Body
+from pydantic import BaseModel
+
+
+class TaskStatus(StrEnum):
+ RUNNING = auto()
+ FINISHED = auto()
+
+
+class TaskResult(StrEnum):
+ SUCCESSFUL=auto()
+ FAILED=auto()
+ RUNNING=auto()
+
+
+class PrecheckEventResult(StrEnum):
+ PASSED = auto()
+ FAILED = auto()
+ RUNNING = auto()
+
+
+class TaskStepInfo(BaseModel):
+ name: str = Body('', description="task step")
+ status: TaskStatus = Body('', description="task step status")
+ result: TaskResult = Body('', description="task step result")
+
+
+class TaskInfo(BaseModel):
+ id: int = Body(..., description="task id")
+ status: TaskStatus = Body(..., description="task status")
+ result: TaskResult = Body(..., description="task result")
+ total: str = Body('port, mem, disk, ulimit, aio, net, ntp, dir, param, ssh', description="total steps")
+ finished: str = Body('', description="finished steps")
+ current: str = Body('', description="current step")
+ message: str = Body('', description="task message")
+ info: List[TaskStepInfo] = Body([], description="")
+
+
+class PreCheckResult(BaseModel):
+ name: str = Body(..., description="precheck event name")
+ server: str = Body("", description="precheck server")
+ result: PrecheckEventResult = Body('', description="precheck event result")
+ recoverable: bool = Body(False, description="precheck event recoverable")
+ code: str = Body('', description="error code")
+ advisement: str = Body("", description="advisement of precheck event failure")
+
+
+class PrecheckTaskInfo(BaseModel):
+ task_info: TaskInfo = Body('', description="task detailed info")
+ precheck_result: List[PreCheckResult] = Body([], description="precheck result")
+
+
+class TaskLog(BaseModel):
+ log: str = Body("", description="task log content")
+ offset: int = Body(0, description="offset of current log")
diff --git a/service/model/tenant.py b/service/model/tenant.py
new file mode 100644
index 0000000..2491737
--- /dev/null
+++ b/service/model/tenant.py
@@ -0,0 +1,42 @@
+# coding: utf-8
+# OceanBase Deploy.
+# Copyright (C) 2021 OceanBase
+#
+# This file is part of OceanBase Deploy.
+#
+# OceanBase Deploy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# OceanBase Deploy 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 for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with OceanBase Deploy. If not, see .
+from typing import List, Optional
+from enum import auto
+from fastapi import Body
+from fastapi_utils.enums import StrEnum
+from pydantic import BaseModel
+
+
+# only support cpu and memory currently
+class TenantResource(BaseModel):
+ cpu: float = Body(2, description="cpu resource of a tenant")
+ memory: int = Body(4, description="memory resource of a tenant in GB")
+
+
+class TenantUser(BaseModel):
+ tenant_name: str = Body(..., description="tenant name")
+ user_name: str = Body('root', description="user name")
+ user_database: str = Body('', description='user database')
+
+
+class TenantConfig(BaseModel):
+ name: TenantUser = Body(..., description="tenant name")
+ password: Optional[str] = Body('', description="tenant password")
+ resource: Optional[TenantResource] = Body(TenantResource(), description="tenant resource")
+
diff --git a/ssh.py b/ssh.py
index ac9264d..46a0c2d 100644
--- a/ssh.py
+++ b/ssh.py
@@ -40,10 +40,10 @@
from multiprocessing import Queue, Process
from multiprocessing.pool import ThreadPool
-from tool import COMMAND_ENV, DirectoryUtil, FileUtil
+from tool import COMMAND_ENV, DirectoryUtil, FileUtil, NetUtil, Timeout
from _stdio import SafeStdio
from _errno import EC_SSH_CONNECT
-from _environ import ENV_DISABLE_RSYNC
+from _environ import ENV_DISABLE_RSYNC, ENV_DISABLE_RSA_ALGORITHMS, ENV_HOST_IP_MODE
__all__ = ("SshClient", "SshConfig", "LocalClient", "ConcurrentExecutor")
@@ -261,6 +261,44 @@ def get_file(local_path, remote_path, stdio=None):
def get_dir(local_path, remote_path, stdio=None):
return LocalClient.put_dir(remote_path, local_path, stdio=stdio)
+ @staticmethod
+ def run_command(command, env=None, timeout=None, print_stderr=True, elimit=0, olimit=0, stdio=None):
+ stdio.verbose('local execute: %s ' % command)
+ stdout = ""
+ process = None
+ try:
+ with Timeout(timeout):
+ process = Popen(command, env=LocalClient.init_env(env), shell=True, stdout=PIPE, stderr=PIPE)
+ while process.poll() is None:
+ lines = process.stdout.readline()
+ line = lines.strip()
+ if line:
+ stdio.print(line.decode("utf8", 'ignore'))
+ stderr = process.stderr.read().decode("utf8", 'ignore')
+ code = process.returncode
+ verbose_msg = 'exit code {}'.format(code)
+ if code != 0 and stderr:
+ verbose_msg += ', error output:\n'
+ stdio.verbose(verbose_msg)
+ if print_stderr:
+ stdio.print(stderr)
+ if elimit == 0:
+ stderr = ""
+ elif elimit > 0:
+ stderr = stderr[-elimit:]
+ except Exception as e:
+ if process:
+ process.terminate()
+ stdout = ''
+ stderr = str(e)
+ code = 255
+ verbose_msg = 'exited code 255, error output:\n%s' % stderr
+ stdio.verbose(verbose_msg)
+ stdio.exception('')
+ finally:
+ if process:
+ process.terminate()
+ return SshReturn(code, stdout, stderr)
class RemoteTransporter(enum.Enum):
CLIENT = 0
@@ -277,6 +315,7 @@ class SshClient(SafeStdio):
DEFAULT_PATH = '/sbin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:'
LOCAL_HOST = ['127.0.0.1', 'localhost', '127.1', '127.0.1']
+ DISABLED_ALGORITHMS = dict(pubkeys=["rsa-sha2-512", "rsa-sha2-256"])
def __init__(self, config, stdio=None):
self.config = config
@@ -288,13 +327,16 @@ def __init__(self, config, stdio=None):
self._remote_transporter = None
self.task_queue = None
self.result_queue = None
- self._is_local = self.is_localhost() and self.config.username == getpass.getuser()
-
+ self._is_local = self.is_local()
if self._is_local:
self.env = {}
else:
self.env = {'PATH': self.DEFAULT_PATH}
self._update_env()
+
+ self._disabled_rsa_algorithms = None
+ if COMMAND_ENV.get(ENV_DISABLE_RSA_ALGORITHMS) == '1':
+ self._disabled_rsa_algorithms = self.DISABLED_ALGORITHMS
super(SshClient, self).__init__()
def _init_queue(self):
@@ -346,19 +388,21 @@ def __str__(self):
def is_localhost(self, stdio=None):
return self.config.host in self.LOCAL_HOST
- def _login(self, stdio=None):
+ def _login(self, stdio=None, exit=True):
if self.is_connected:
return True
err = None
try:
self.ssh_client.set_missing_host_key_policy(AutoAddPolicy())
+ stdio.verbose('host: %s, port: %s, user: %s, password: %s' % (self.config.host, self.config.port, self.config.username, self.config.password))
self.ssh_client.connect(
self.config.host,
port=self.config.port,
username=self.config.username,
password=self.config.password,
key_filename=self.config.key_filename,
- timeout=self.config.timeout
+ timeout=self.config.timeout,
+ disabled_algorithms=self._disabled_rsa_algorithms
)
self.is_connected = True
except AuthenticationException:
@@ -371,7 +415,10 @@ def _login(self, stdio=None):
stdio.exception('')
err = EC_SSH_CONNECT.format(user=self.config.username, ip=self.config.host, message=e)
if err:
- stdio.critical(err)
+ if exit:
+ stdio.critical(err)
+ return err
+ stdio.error(err)
return err
return self.is_connected
@@ -384,10 +431,14 @@ def _open_sftp(self, stdio=None):
return True
return False
- def connect(self, stdio=None):
+ def is_local(self):
+ return self.is_localhost() and self.config.username == getpass.getuser() or \
+ (COMMAND_ENV.get(ENV_HOST_IP_MODE, '0') == '1' and self.config.host == NetUtil.get_host_ip())
+
+ def connect(self, stdio=None, exit=True):
if self._is_local:
return True
- return self._login(stdio=stdio)
+ return self._login(stdio=stdio, exit=exit)
def reconnect(self, stdio=None):
self.close(stdio=stdio)
@@ -441,12 +492,13 @@ def execute_command(self, command, timeout=None, stdio=None):
timeout = self.config.timeout
elif timeout <= 0:
timeout = None
+
if self._is_local:
return LocalClient.execute_command(command, self.env if self.env else None, timeout, stdio=stdio)
verbose_msg = '%s execute: %s ' % (self.config, command)
stdio.verbose(verbose_msg, end='')
- command = '%s %s;echo -e "\n$?\c"' % (self.env_str, command.strip(';').lstrip('\n'))
+ command = '(%s %s);echo -e "\n$?\c"' % (self.env_str, command.strip(';').lstrip('\n'))
return self._execute_command(command, retry=3, timeout=timeout, stdio=stdio)
@property
@@ -503,7 +555,7 @@ def _put_file(self):
def _client_put_file(self, local_path, remote_path, stdio=None):
if self.execute_command('mkdir -p %s && rm -fr %s' % (os.path.dirname(remote_path), remote_path), stdio=stdio):
stdio.verbose('send %s to %s' % (local_path, remote_path))
- if self.sftp.put(local_path, remote_path):
+ if self.sftp.put(local_path.replace('~', os.getenv('HOME')), remote_path.replace('~', os.getenv('HOME'))):
return self.execute_command('chmod %s %s' % (oct(os.stat(local_path).st_mode)[-3:], remote_path))
return False
diff --git a/tool.py b/tool.py
index bc35de2..a02d814 100644
--- a/tool.py
+++ b/tool.py
@@ -34,14 +34,18 @@
import json
import hashlib
import socket
+import datetime
from io import BytesIO
+from copy import copy
import string
from ruamel.yaml import YAML, YAMLContextManager, representer
+from _errno import EC_SQL_EXECUTE_FAILED
from _stdio import SafeStdio
_open = open
if sys.version_info.major == 2:
+ import MySQLdb as mysql
from collections import OrderedDict
from backports import lzma
from io import open as _open
@@ -60,13 +64,14 @@ def __init__(self, *args, **kwargs):
else:
import lzma
+ import pymysql as mysql
encoding_open = open
class OrderedDict(dict):
pass
-__all__ = ("timeout", "DynamicLoading", "ConfigUtil", "DirectoryUtil", "FileUtil", "YamlLoader", "OrderedDict", "COMMAND_ENV")
+__all__ = ("timeout", "DynamicLoading", "ConfigUtil", "DirectoryUtil", "FileUtil", "YamlLoader", "OrderedDict", "COMMAND_ENV", "TimeUtils")
_WINDOWS = os.name == 'nt'
@@ -677,7 +682,8 @@ def get_host_ip():
class TimeUtils(SafeStdio):
- def parse_time_sec(time_str, stdio=None):
+ @staticmethod
+ def parse_time_sec(time_str):
unit = time_str[-1]
value = int(time_str[:-1])
if unit == "s":
@@ -689,5 +695,140 @@ def parse_time_sec(time_str, stdio=None):
elif unit == "d":
value *= 3600 * 24
else:
- stdio.error('%s parse time to second fialed:' % (time_str))
- return int(value)
\ No newline at end of file
+ raise Exception('%s parse time to second fialed:' % (time_str))
+ return value
+
+ @staticmethod
+ def get_format_time(time_str, stdio=None):
+ try:
+ return datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
+ except Exception as e:
+ stdio.exception('%s parse time fialed, error:\n%s, time format need to be %s' % (time_str, e, '%Y-%m-%d %H:%M:%S'))
+
+
+ @staticmethod
+ def sub_minutes(t, delta, stdio=None):
+ try:
+ return (t - datetime.timedelta(minutes=delta)).strftime('%Y-%m-%d %H:%M:%S')
+ except Exception as e:
+ stdio.exception('%s get time fialed, error:\n%s' % (t, e))
+
+
+ @staticmethod
+ def add_minutes(t, delta, stdio=None):
+ try:
+ return (t + datetime.timedelta(minutes=delta)).strftime('%Y-%m-%d %H:%M:%S')
+ except Exception as e:
+ stdio.exception('%s get time fialed, error:\n%s' % (t, e))
+
+ @staticmethod
+ def parse_time_from_to(from_time=None, to_time=None, stdio=None):
+ format_from_time = None
+ format_to_time = None
+ sucess = False
+ if from_time:
+ format_from_time = TimeUtils.get_format_time(from_time, stdio)
+ format_to_time = TimeUtils.get_format_time(to_time, stdio) if to_time else TimeUtils.add_minutes(format_from_time, 30)
+ else:
+ if to_time:
+ format_to_time = TimeUtils.get_format_time(to_time, stdio)
+ format_from_time = TimeUtils.sub_minutes(format_to_time, 30)
+ if format_from_time and format_to_time:
+ sucess = True
+ return format_from_time, format_to_time, sucess
+
+ @staticmethod
+ def parse_time_since(since=None, stdio=None):
+ now_time = datetime.datetime.now()
+ format_to_time = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S')
+ try:
+ format_from_time = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_sec(since))).strftime('%Y-%m-%d %H:%M:%S')
+ except Exception as e:
+ stdio.exception('%s parse time fialed, error:\n%s' % (since, e))
+ format_from_time = TimeUtils.sub_minutes(format_to_time, 30)
+ return format_from_time, format_to_time
+
+class Cursor(SafeStdio):
+
+ def __init__(self, ip, port, user='root', tenant='sys', password='', stdio=None):
+ self.stdio = stdio
+ self.ip = ip
+ self.port = port
+ self._user = user
+ self.tenant = tenant
+ self.password = password
+ self.cursor = None
+ self.db = None
+ self._connect()
+ self._raise_exception = False
+ self._raise_cursor = None
+
+ @property
+ def user(self):
+ if "@" in self._user:
+ return self._user
+ if self.tenant:
+ return "{}@{}".format(self._user, self.tenant)
+ else:
+ return self._user
+
+ @property
+ def raise_cursor(self):
+ if self._raise_cursor:
+ return self._raise_cursor
+ raise_cursor = copy(self)
+ raise_cursor._raise_exception = True
+ self._raise_cursor = raise_cursor
+ return raise_cursor
+
+ if sys.version_info.major == 2:
+ def _connect(self):
+ self.stdio.verbose('connect %s -P%s -u%s -p%s' % (self.ip, self.port, self.user, self.password))
+ self.db = mysql.connect(host=self.ip, user=self.user, port=int(self.port), passwd=str(self.password))
+ self.cursor = self.db.cursor(cursorclass=mysql.cursors.DictCursor)
+ else:
+ def _connect(self):
+ self.stdio.verbose('connect %s -P%s -u%s -p%s' % (self.ip, self.port, self.user, self.password))
+ self.db = mysql.connect(host=self.ip, user=self.user, port=int(self.port), password=str(self.password),
+ cursorclass=mysql.cursors.DictCursor)
+ self.cursor = self.db.cursor()
+
+ def new_cursor(self, tenant='sys', user='root', password=''):
+ try:
+ self.stdio.verbose('fail to connect %s -P%s -u%s@%s -p%s' % (self.ip, self.port, user, tenant, password))
+ return Cursor(ip=self.ip, port=self.port, user=user, tenant=tenant, password=str(password), stdio=self.stdio)
+ except:
+ self.stdio.exception('')
+ self.stdio.verbose('fail to connect %s -P%s -u%s -p%s' % (self.ip, self.port, user, password))
+ return None
+
+ def execute(self, sql, args=None, execute_func=None, raise_exception=None, exc_level='error', stdio=None):
+
+ try:
+ stdio.verbose('execute sql: %s. args: %s' % (sql, args))
+ self.cursor.execute(sql, args)
+ if not execute_func:
+ return self.cursor
+ return getattr(self.cursor, execute_func)()
+ except Exception as e:
+ getattr(stdio, exc_level)(EC_SQL_EXECUTE_FAILED.format(sql=sql))
+ if raise_exception is None:
+ raise_exception = self._raise_exception
+ if raise_exception:
+ stdio.exception('')
+ raise e
+ return False
+
+ def fetchone(self, sql, args=None, raise_exception=None, exc_level='error', stdio=None):
+ return self.execute(sql, args=args, execute_func='fetchone', raise_exception=raise_exception, exc_level=exc_level, stdio=stdio)
+
+ def fetchall(self, sql, args=None, raise_exception=None, exc_level='error', stdio=None):
+ return self.execute(sql, args=args, execute_func='fetchall', raise_exception=raise_exception, exc_level=exc_level, stdio=stdio)
+
+ def close(self):
+ if self.cursor:
+ self.cursor.close()
+ self.cursor = None
+ if self.db:
+ self.db.close()
+ self.db = None
diff --git a/web/.fast-spm-local.json b/web/.fast-spm-local.json
new file mode 100644
index 0000000..7edb542
--- /dev/null
+++ b/web/.fast-spm-local.json
@@ -0,0 +1,102 @@
+{
+ "src/component/AobException": [],
+ "src/component/Banner": [],
+ "src/component/CheckBadge": [],
+ "src/component/ConnectConfig": [],
+ "src/component/ConnectionInfo": [],
+ "src/component/ContentWithInfo": [],
+ "src/component/ContentWithIcon": [],
+ "src/component/ContentWithQuestion": [],
+ "src/component/DeployConfig": [
+ {
+ "name": "部署配置-上一步",
+ "spm": "ca54435.da43437"
+ },
+ {
+ "name": "部署配置-下一步",
+ "spm": "ca54435.da43438"
+ }
+ ],
+ "src/component/CustomFooter": [],
+ "src/component/Empty": [],
+ "src/component/EnvPreCheck": [],
+ "src/component/FooterToolbar": [],
+ "src/component/Icon": [],
+ "src/component/InsstallResult": [],
+ "src/component/InstallProcess": [],
+ "src/component/InstallProcessNew": [],
+ "src/component/InstallResult": [],
+ "src/component/MetaDBConfig": [],
+ "src/component/ModifyOCPResourcePoolModal": [],
+ "src/component/MyCard": [],
+ "src/component/MyDrawer": [],
+ "src/component/NoAuth": [],
+ "src/component/OCPConfig": [
+ {
+ "name": "ocp配置-上一步",
+ "spm": "ca54436.da43439"
+ },
+ {
+ "name": "ocp配置-下一步",
+ "spm": "ca54436.da43440"
+ }
+ ],
+ "src/component/OCPPreCheck": [
+ {
+ "name": "预检查-上一步",
+ "spm": "ca54437.da43442"
+ },
+ {
+ "name": "预检查-预检查",
+ "spm": "ca54437.da43443"
+ },
+ {
+ "name": "预检查结果-重新检查",
+ "spm": "ca54438.da43444"
+ },
+ {
+ "name": "预检查结果-重新检查",
+ "spm": "ca54438.da43444"
+ },
+ {
+ "name": "预检查结果-自动修复",
+ "spm": "ca54438.da43445"
+ },
+ {
+ "name": "预检查结果-自动修复",
+ "spm": "ca54438.da43445"
+ },
+ {
+ "name": "预检查结果-上一步",
+ "spm": "ca54438.da43446"
+ },
+ {
+ "name": "预检查失败-部署置灰",
+ "spm": "ca54439.da43447"
+ },
+ {
+ "name": "预检查成功-部署",
+ "spm": "ca54440.da43441"
+ }
+ ],
+ "src/component/OCPConfigNew": [
+ {
+ "name": "ocp配置-上一步",
+ "spm": "ca54436.da43439"
+ },
+ {
+ "name": "ocp配置-下一步",
+ "spm": "ca54436.da43440"
+ }
+ ],
+ "src/component/PageCard": [],
+ "src/component/PageLoading": [],
+ "src/component/Result": [],
+ "src/component/SliderAndInputNumber": [],
+ "src/component/Steps": [],
+ "src/pages/Guide": [],
+ "src/pages/Layout": [],
+ "src/pages/Obdeploy": [],
+ "src/pages/OcpInstaller": [],
+ "src/pages/constants": []
+}
\ No newline at end of file
diff --git a/web/config/config.ts b/web/config/config.ts
index 7980f3a..300bd8c 100644
--- a/web/config/config.ts
+++ b/web/config/config.ts
@@ -1,13 +1,19 @@
import { defineConfig } from 'umi';
+import routes from './routes';
+import AntdMomentWebpackPlugin from '@ant-design/moment-webpack-plugin';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
+ exclude: [],
},
- routes: [{ path: '/', component: 'index' }],
+ // routes: [{ path: '/', component: 'index' }],
+ routes,
title: 'OceanBase Deployer',
fastRefresh: {},
favicon: '/assets/logo.png',
+ history: { type: 'hash' },
+ mfsu: {},
locale: {
default: 'zh-CN',
antd: false,
@@ -31,5 +37,25 @@ export default defineConfig({
`!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}([function(module,exports){"use strict";!function(){if(!window.Tracert){for(var Tracert={_isInit:!0,_readyToRun:[],_guid:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=16*Math.random()|0,v="x"===c?r:3&r|8;return v.toString(16)})},get:function(key){if("pageId"===key){if(window._tracert_loader_cfg=window._tracert_loader_cfg||{},window._tracert_loader_cfg.pageId)return window._tracert_loader_cfg.pageId;var metaa=document.querySelectorAll("meta[name=data-aspm]"),spma=metaa&&metaa[0].getAttribute("content"),spmb=document.body&&document.body.getAttribute("data-aspm"),pageId=spma&&spmb?spma+"."+spmb+"_"+Tracert._guid()+"_"+Date.now():"-_"+Tracert._guid()+"_"+Date.now();return window._tracert_loader_cfg.pageId=pageId,pageId}return this[key]},call:function(){var argsList,args=arguments;try{argsList=[].slice.call(args,0)}catch(ex){var argsLen=args.length;argsList=[];for(var i=0;i {
+ if (env === 'production') {
+ config.optimization.delete('noEmitOnErrors');
+ config.plugins.delete('optimize-css');
+
+ // 因为删除原来适配webpack4的css压缩插件,css压缩可以用 mini-css-extract-plugin
+ config.optimization.minimize(true);
+ // config.optimization.minimizer(`css-esbuildMinify`).use(CSSMinimizerWebpackPlugin);
+ }
+ // 添加 AntdMomentWebpackPlugin 插件
+ config.plugin('antd-moment').use(AntdMomentWebpackPlugin, [
+ {
+ // 关闭 dayjs alias,避免 antd 以外的 dayjs 被 alias 成 moment
+ disableDayjsAlias: true,
+ },
+ ]);
+ // 静态资源的文件限制调整为 1GB,避免视频等大文件资源阻塞项目启动
+ config.performance.maxAssetSize(1000000000);
+ return config;
+ },
});
diff --git a/web/config/routes.ts b/web/config/routes.ts
new file mode 100644
index 0000000..734e822
--- /dev/null
+++ b/web/config/routes.ts
@@ -0,0 +1,61 @@
+export default [
+ {
+ path: '/',
+ component: 'Layout',
+ name: '系统布局',
+ routes: [
+ {
+ path: '/',
+ component: 'index',
+ name: '欢迎页',
+ },
+ {
+ path: 'obdeploy',
+ component: 'Obdeploy',
+ name: 'oceanbase部署',
+ },
+ {
+ path: 'updateWelcome',
+ component: 'OcpInstaller/Welcome',
+ name: 'ocp升级欢迎页',
+ },
+ {
+ path: 'update',
+ component: 'OcpInstaller/Update',
+ name: 'ocp升级',
+ spmb:'b71440'
+ },
+ {
+ path: 'ocpInstaller/install',
+ component: 'OcpInstaller/Install',
+ name: '安装无MetaDB',
+ spmb:'b71462',
+ exact: true,
+ },
+ {
+ path: 'ocpInstaller/configuration',
+ component: 'OcpInstaller/Configuration',
+ name: '安装有MetaDB',
+ spmb:'b71463',
+ exact: true,
+ },
+ {
+ path: 'ocpInstaller',
+ component: 'OcpInstaller/Index/index',
+ name: 'oceanbase云平台安装',
+ },
+ {
+ path: 'quit',
+ component: 'OcpInstaller/Quit',
+ name: '退出安装程序',
+ exact: true,
+ },
+ {
+ path: '/guide',
+ component: 'Guide',
+ spmb:'b57206',
+ name: '部署向导',
+ },
+ ],
+ },
+];
diff --git a/web/package.json b/web/package.json
index bd64410..2d45395 100644
--- a/web/package.json
+++ b/web/package.json
@@ -20,28 +20,40 @@
]
},
"dependencies": {
+ "@ant-design/compatible": "^1.0.5",
"@ant-design/icons": "4.8.0",
"@ant-design/pro-components": "2.3.34",
"@ant-design/pro-layout": "6.5.0",
- "@types/video.js": "7.3.50",
+ "@antv/g6": "^4.8.22",
+ "@oceanbase/design": "^0.2.2",
+ "@oceanbase/icons": "^0.2.0",
+ "@oceanbase/ui": "^0.2.2",
+ "@oceanbase/util": "^0.2.1",
"@umijs/plugin-openapi": "1.3.3",
"antd": "5.0.7",
+ "classnames": "^2.3.2",
"copy-to-clipboard": "3.3.3",
"cross-env": "7.0.3",
- "i18next": "^23.2.11",
+ "i18next": "^23.7.6",
+ "lodash": "^4.17.21",
"lottie-web": "5.10.2",
- "moment": "2.29.4",
+ "moment": "^2.29.4",
"number-precision": "1.6.0",
"randexp": "0.5.3",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-intl": "6.4.1",
"umi": "3.5.35",
+ "validator": "^12.1.0",
"video.js": "7.20.3"
},
"devDependencies": {
+ "@ant-design/moment-webpack-plugin": "^0.0.4",
+ "@types/lodash": "^4.14.198",
"@types/react": "17.0.52",
"@types/react-dom": "17.0.2",
+ "@types/validator": "^12.0.1",
+ "@types/video.js": "7.3.50",
"@umijs/preset-react": "1.8.31",
"@umijs/test": "3.5.35",
"lint-staged": "10.5.4",
diff --git a/web/public/assets/icon/client.svg b/web/public/assets/icon/client.svg
new file mode 100644
index 0000000..8da1b09
--- /dev/null
+++ b/web/public/assets/icon/client.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/close.svg b/web/public/assets/icon/close.svg
new file mode 100644
index 0000000..8ddb1c4
--- /dev/null
+++ b/web/public/assets/icon/close.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/ellipsis.svg b/web/public/assets/icon/ellipsis.svg
new file mode 100644
index 0000000..adba3cf
--- /dev/null
+++ b/web/public/assets/icon/ellipsis.svg
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/fit_canvas.svg b/web/public/assets/icon/fit_canvas.svg
new file mode 100644
index 0000000..3b72d3b
--- /dev/null
+++ b/web/public/assets/icon/fit_canvas.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/more.svg b/web/public/assets/icon/more.svg
new file mode 100644
index 0000000..0c06060
--- /dev/null
+++ b/web/public/assets/icon/more.svg
@@ -0,0 +1,24 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/new.svg b/web/public/assets/icon/new.svg
new file mode 100644
index 0000000..7e2d9b2
--- /dev/null
+++ b/web/public/assets/icon/new.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/assets/icon/obproxy.svg b/web/public/assets/icon/obproxy.svg
new file mode 100644
index 0000000..8d7fec4
--- /dev/null
+++ b/web/public/assets/icon/obproxy.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/reset.svg b/web/public/assets/icon/reset.svg
new file mode 100644
index 0000000..2682c0e
--- /dev/null
+++ b/web/public/assets/icon/reset.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/settings.svg b/web/public/assets/icon/settings.svg
new file mode 100644
index 0000000..6981e2c
--- /dev/null
+++ b/web/public/assets/icon/settings.svg
@@ -0,0 +1,29 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/success.svg b/web/public/assets/icon/success.svg
new file mode 100644
index 0000000..218d9ea
--- /dev/null
+++ b/web/public/assets/icon/success.svg
@@ -0,0 +1,43 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/icon/warning.svg b/web/public/assets/icon/warning.svg
new file mode 100644
index 0000000..4e42839
--- /dev/null
+++ b/web/public/assets/icon/warning.svg
@@ -0,0 +1,72 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/install/empty.png b/web/public/assets/install/empty.png
new file mode 100644
index 0000000..b8e6664
Binary files /dev/null and b/web/public/assets/install/empty.png differ
diff --git a/web/public/assets/install/failed.png b/web/public/assets/install/failed.png
new file mode 100644
index 0000000..0ba7db0
Binary files /dev/null and b/web/public/assets/install/failed.png differ
diff --git a/web/public/assets/install/metadbFailed.png b/web/public/assets/install/metadbFailed.png
new file mode 100644
index 0000000..d0ba45a
Binary files /dev/null and b/web/public/assets/install/metadbFailed.png differ
diff --git a/web/public/assets/install/metadbSuccessful.png b/web/public/assets/install/metadbSuccessful.png
new file mode 100644
index 0000000..01bc879
Binary files /dev/null and b/web/public/assets/install/metadbSuccessful.png differ
diff --git a/web/public/assets/install/prechecked.svg b/web/public/assets/install/prechecked.svg
new file mode 100644
index 0000000..b0d9f37
--- /dev/null
+++ b/web/public/assets/install/prechecked.svg
@@ -0,0 +1,242 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/install/successful.png b/web/public/assets/install/successful.png
new file mode 100644
index 0000000..0a94b90
Binary files /dev/null and b/web/public/assets/install/successful.png differ
diff --git a/web/public/assets/update/arrow.svg b/web/public/assets/update/arrow.svg
new file mode 100644
index 0000000..eed127b
--- /dev/null
+++ b/web/public/assets/update/arrow.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/welcome/banner.png b/web/public/assets/welcome/banner.png
new file mode 100644
index 0000000..e7003c1
Binary files /dev/null and b/web/public/assets/welcome/banner.png differ
diff --git a/web/public/assets/welcome/new-db-selected.svg b/web/public/assets/welcome/new-db-selected.svg
new file mode 100644
index 0000000..8406353
--- /dev/null
+++ b/web/public/assets/welcome/new-db-selected.svg
@@ -0,0 +1,23 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/welcome/new-db-unselected.svg b/web/public/assets/welcome/new-db-unselected.svg
new file mode 100644
index 0000000..e870392
--- /dev/null
+++ b/web/public/assets/welcome/new-db-unselected.svg
@@ -0,0 +1,23 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/welcome/ob-selected.png b/web/public/assets/welcome/ob-selected.png
new file mode 100644
index 0000000..b3c262d
Binary files /dev/null and b/web/public/assets/welcome/ob-selected.png differ
diff --git a/web/public/assets/welcome/ob-unselected.png b/web/public/assets/welcome/ob-unselected.png
new file mode 100644
index 0000000..2196d9c
Binary files /dev/null and b/web/public/assets/welcome/ob-unselected.png differ
diff --git a/web/public/assets/welcome/ocp-selected.png b/web/public/assets/welcome/ocp-selected.png
new file mode 100644
index 0000000..de1b4de
Binary files /dev/null and b/web/public/assets/welcome/ocp-selected.png differ
diff --git a/web/public/assets/welcome/ocp-unselected.png b/web/public/assets/welcome/ocp-unselected.png
new file mode 100644
index 0000000..0c52f18
Binary files /dev/null and b/web/public/assets/welcome/ocp-unselected.png differ
diff --git a/web/public/assets/welcome/odc-selected.png b/web/public/assets/welcome/odc-selected.png
new file mode 100644
index 0000000..2a48892
Binary files /dev/null and b/web/public/assets/welcome/odc-selected.png differ
diff --git a/web/public/assets/welcome/odc-unselected.png b/web/public/assets/welcome/odc-unselected.png
new file mode 100644
index 0000000..78bec97
Binary files /dev/null and b/web/public/assets/welcome/odc-unselected.png differ
diff --git a/web/public/assets/welcome/old-db-selected.svg b/web/public/assets/welcome/old-db-selected.svg
new file mode 100644
index 0000000..9f9355a
--- /dev/null
+++ b/web/public/assets/welcome/old-db-selected.svg
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/welcome/old-db-unselected.svg b/web/public/assets/welcome/old-db-unselected.svg
new file mode 100644
index 0000000..d77377c
--- /dev/null
+++ b/web/public/assets/welcome/old-db-unselected.svg
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/web/public/assets/welcome/oms-selected.png b/web/public/assets/welcome/oms-selected.png
new file mode 100644
index 0000000..8f8dd14
Binary files /dev/null and b/web/public/assets/welcome/oms-selected.png differ
diff --git a/web/public/assets/welcome/oms-unselected.png b/web/public/assets/welcome/oms-unselected.png
new file mode 100644
index 0000000..2707533
Binary files /dev/null and b/web/public/assets/welcome/oms-unselected.png differ
diff --git a/web/src/component/Access.tsx b/web/src/component/Access.tsx
new file mode 100644
index 0000000..2c07268
--- /dev/null
+++ b/web/src/component/Access.tsx
@@ -0,0 +1,97 @@
+import { intl } from '@/utils/intl';
+import React from 'react';
+import { Button, Checkbox, Menu, Switch, Tooltip } from '@oceanbase/design';
+import { isObject } from 'lodash';
+import type { TooltipProps } from 'antd/es/tooltip';
+
+export interface AccessProps {
+ /* 是否可访问 */
+ accessible: boolean;
+ /* 所见即所得模式下,无权限时的展示内容,与 tooltip 属性是互斥的 (优先级: tooltip > fallback),不能同时设置 */
+ fallback?: React.ReactNode;
+ /* 默认是所见即所得模式,如果想展示为 disabled + Tooltip 模式,则需要设置 tooltip 属性 */
+ tooltip?:
+ | boolean
+ | (Omit & {
+ // 将 title 改为可选属性
+ title?: React.ReactNode;
+ });
+ children: React.ReactElement;
+}
+
+export default ({
+ accessible = true,
+ fallback,
+ tooltip = false,
+ children,
+ ...restProps
+}: AccessProps) => {
+ const childrenProps = children.props || {};
+ const disabled = !accessible || childrenProps.disabled;
+ const tooltipProps = isObject(tooltip) ? tooltip || {} : {};
+ const { title, ...restTooltipProps } = tooltipProps;
+ const element = React.cloneElement(children, {
+ style: {
+ // 为了保证 Tooltip -> span -> disabled Button 的组件结构下,鼠标移出按钮时 Tooltip 可以正常消失,需要设置 pointerEvents: 'none'
+ // issue: https://github.com/react-component/tooltip/issues/18#issuecomment-650864750
+ // 判断逻辑参考自 antd: https://github.com/ant-design/ant-design/blob/master/components/tooltip/index.tsx#L88
+ ...(disabled &&
+ (children.type === Button ||
+ children.type === Checkbox ||
+ children.type === Switch)
+ ? { pointerEvents: 'none' }
+ : {}),
+ ...childrenProps.style,
+ },
+ // 从 antd 4.16.0 版本开始,Menu.Item & Menu.SubMenu 支持 HOC,只需要设置 eventKey 即可支持让 Menu.Item & Menu.SubMenu 间接嵌套在 Menu 下
+ // https://github.com/ant-design/ant-design/issues/30828#issuecomment-854418007
+ ...(children.type === Menu.Item || children.type === Menu.SubMenu
+ ? { eventKey: children.key }
+ : {}),
+ ...(tooltip
+ ? {
+ // 根据 accessible 设置 disabled
+ disabled,
+ }
+ : {}),
+ });
+ return tooltip ? (
+ // disabled + Tooltip 模式
+
+
+ {element}
+
+
+ ) : accessible ? (
+
+ {element}
+
+ ) : (
+ <>{fallback || null}>
+ );
+};
diff --git a/web/src/component/AobException/index.less b/web/src/component/AobException/index.less
new file mode 100644
index 0000000..ce41b5b
--- /dev/null
+++ b/web/src/component/AobException/index.less
@@ -0,0 +1,61 @@
+.container {
+ display: flex;
+ align-items: center;
+ height: 80%;
+ min-height: 500px;
+
+ .imgWrapper {
+ flex: 0 0 62.5%;
+ width: 62.5%;
+ padding-right: 152px;
+ zoom: 1;
+
+ &::before,
+ &::after {
+ display: table;
+ content: ' ';
+ }
+
+ &::after {
+ clear: both;
+ height: 0;
+ font-size: 0;
+ visibility: hidden;
+ }
+ }
+
+ .img {
+ float: right;
+ width: 100%;
+ max-width: 430px;
+ min-height: 360px;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ background-size: contain;
+ }
+
+ .content {
+ flex: auto;
+
+ h1 {
+ margin-bottom: 24px;
+ color: #434e59;
+ font-weight: 600;
+ font-size: 72px;
+ line-height: 72px;
+ }
+
+ .desc {
+ margin-bottom: 16px;
+ color: rgba(0, 0, 0, 0.45);
+ font-size: 20px;
+ line-height: 28px;
+ }
+
+ .actions {
+ button:not(:last-child) {
+ margin-right: 8px;
+ }
+ }
+ }
+}
diff --git a/web/src/component/AobException/index.tsx b/web/src/component/AobException/index.tsx
new file mode 100644
index 0000000..1b3216e
--- /dev/null
+++ b/web/src/component/AobException/index.tsx
@@ -0,0 +1,76 @@
+import { intl } from '@/utils/intl';
+import React, { createElement } from 'react';
+import { Button } from '@oceanbase/design';
+import styles from './index.less';
+
+interface AobExceptionProps {
+ title?: React.ReactNode;
+ desc?: React.ReactNode;
+ img?: string;
+ actions?: React.ReactNode;
+ style?: React.CSSProperties;
+ className?: string;
+ linkElement?: string;
+ backText?: string;
+ redirect?: string;
+ onBack?: () => void;
+}
+
+class AobException extends React.PureComponent {
+ constructor(props: AobExceptionProps) {
+ super(props);
+ this.state = {};
+ }
+
+ public render() {
+ const {
+ className,
+ backText = intl.formatMessage({
+ id: 'OBD.component.AobException.ReturnToHomePage',
+ defaultMessage: '返回首页',
+ }),
+ title,
+ desc,
+ img,
+ linkElement = 'a',
+ actions,
+ redirect = '/',
+ onBack,
+ ...rest
+ } = this.props;
+
+ return (
+
+
+
+
{title}
+
{desc}
+
+ {actions ||
+ (onBack ? (
+
+ ) : (
+ createElement(
+ linkElement,
+ {
+ to: redirect,
+ href: redirect,
+ },
+ ,
+ )
+ ))}
+
+
+
+ );
+ }
+}
+
+export default AobException;
diff --git a/web/src/component/Banner/index.less b/web/src/component/Banner/index.less
new file mode 100644
index 0000000..43a2c59
--- /dev/null
+++ b/web/src/component/Banner/index.less
@@ -0,0 +1,19 @@
+.directions {
+ position: relative;
+ padding-top: 48px;
+ .banner {
+ width: 100%;
+ height: 123px;
+ }
+ .title {
+ position: absolute;
+ top: 98px;
+ left: 144px;
+ height: 48px;
+ color: #fff;
+ font-weight: 500;
+ font-size: 28px;
+ line-height: 24px;
+ letter-spacing: 0;
+ }
+ }
\ No newline at end of file
diff --git a/web/src/component/Banner/index.tsx b/web/src/component/Banner/index.tsx
new file mode 100644
index 0000000..e697a28
--- /dev/null
+++ b/web/src/component/Banner/index.tsx
@@ -0,0 +1,42 @@
+import { useRef, useEffect } from 'react';
+
+import styles from './index.less';
+import bannerImg from '../../../public/assets/welcome/banner.png';
+
+export default function Banner({title}:{title:string}) {
+ const bannerRef = useRef(null);
+ const welcomeTextRef = useRef(null);
+ const windowResize = () => {
+ welcomeTextRef.current!.style.top =
+ bannerRef.current?.clientHeight! / 2 + 36 + 'px';
+ };
+
+ useEffect(() => {
+ window.addEventListener('resize', windowResize);
+
+ //待图片加载完成获取高度
+ if (bannerRef.current && welcomeTextRef.current) {
+ bannerRef.current.onload = function () {
+ welcomeTextRef.current!.style.top =
+ bannerRef.current?.clientHeight! / 2 + 36 + 'px';
+ };
+ }
+
+ return () => {
+ window.removeEventListener('resize', windowResize);
+ };
+ }, [bannerRef]);
+ return (
+
+
+
+ {title}
+
+
+ );
+}
diff --git a/web/src/component/CheckBadge/index.less b/web/src/component/CheckBadge/index.less
new file mode 100644
index 0000000..5ca8161
--- /dev/null
+++ b/web/src/component/CheckBadge/index.less
@@ -0,0 +1,63 @@
+@import '~@oceanbase/design/es/theme/index.less';
+
+.processing {
+ color: @colorPrimary;
+}
+.success {
+ color: @colorSuccessTextHover;
+}
+.error {
+ color: @colorErrorText;
+}
+.ignored {
+ color: @colorTextTertiary;
+}
+
+.rotate {
+ -webkit-animation: rotate 1s linear infinite;
+ animation: rotate 1s linear infinite;
+}
+
+@keyframes rotate {
+ 0% {
+ transform: rotate(0);
+ }
+
+ 25% {
+ transform: rotate(90deg);
+ }
+
+ 50% {
+ transform: rotate(180deg);
+ }
+
+ 75% {
+ transform: rotate(270deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@-webkit-keyframes rotate {
+ 0% {
+ transform: rotate(0);
+ }
+
+ 25% {
+ transform: rotate(90deg);
+ }
+
+ 50% {
+ transform: rotate(180deg);
+ }
+
+ 75% {
+ transform: rotate(270deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/web/src/component/CheckBadge/index.tsx b/web/src/component/CheckBadge/index.tsx
new file mode 100644
index 0000000..b666218
--- /dev/null
+++ b/web/src/component/CheckBadge/index.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { Space } from '@oceanbase/design';
+import {
+ CloseCircleFilled,
+ CheckCircleFilled,
+ Loading3QuartersOutlined,
+ MinusCircleFilled,
+} from '@ant-design/icons';
+import styles from './index.less';
+
+export interface CheckBadgeProps {
+ className?: string;
+ status?: string; // 设置 Badge 为状态点
+ text?: React.ReactNode;
+}
+
+const CheckBadge = ({ status, text, className, ...restProps }: CheckBadgeProps) => {
+ const statusTextNode = !text ? <>> : {text};
+ let statusIcon: React.ReactNode | undefined = undefined;
+ if (status === 'processing') {
+ statusIcon = ;
+ } else if (status === 'success') {
+ statusIcon = ;
+ } else if (status === 'error') {
+ statusIcon = ;
+ } else if (status === 'ignored') {
+ statusIcon = ;
+ }
+ return (
+
+ {statusIcon}
+ {statusTextNode}
+
+ );
+};
+
+export default CheckBadge;
diff --git a/web/src/component/ConnectConfig/index.less b/web/src/component/ConnectConfig/index.less
new file mode 100644
index 0000000..fc76df0
--- /dev/null
+++ b/web/src/component/ConnectConfig/index.less
@@ -0,0 +1,5 @@
+.titleText{
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+}
\ No newline at end of file
diff --git a/web/src/component/ConnectConfig/index.tsx b/web/src/component/ConnectConfig/index.tsx
new file mode 100644
index 0000000..00cfbe4
--- /dev/null
+++ b/web/src/component/ConnectConfig/index.tsx
@@ -0,0 +1,250 @@
+import { intl } from '@/utils/intl';
+import { ProForm, ProCard } from '@ant-design/pro-components';
+import {
+ Input,
+ Space,
+ Tooltip,
+ Button,
+ InputNumber,
+ message,
+ Modal,
+} from 'antd';
+import { QuestionCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
+import { useRequest } from 'ahooks';
+import { useModel } from 'umi';
+
+import * as Metadb from '@/services/ocp_installer_backend/Metadb';
+import CustomFooter from '../CustomFooter';
+import InputPort from '../InputPort';
+import ExitBtn from '../ExitBtn';
+import styles from './index.less';
+const InputWidthStyle = { width: 328 };
+
+type FormValues = {
+ metadb: {
+ host: string;
+ port: number;
+ user: string;
+ password: string;
+ };
+};
+
+export default function ConnectConfig({ setCurrent, current }: API.StepProp) {
+ const { ocpConfigData, setOcpConfigData, setErrorVisible, setErrorsList } =
+ useModel('global');
+ const { components = {} } = ocpConfigData;
+ const { ocpserver = {}, oceanbase = {} } = components;
+ const { metadb = {} } = ocpserver;
+ const cluster_name = oceanbase?.appname;
+ const { host, port, user, password } = metadb;
+ const [form] = ProForm.useForm();
+ const setData = (dataSource: FormValues) => {
+ let newOcpserver = {
+ ...ocpserver,
+ ...dataSource,
+ };
+ setOcpConfigData({
+ ...ocpConfigData,
+ components: {
+ ...components,
+ ocpserver: newOcpserver,
+ },
+ });
+ };
+ // 通过 connection 方式创建一个 metadb 连接
+ const { run: createMetadbConnection, loading } = useRequest(
+ Metadb.createMetadbConnection,
+ {
+ manual: true,
+ onError: ({ data }: any) => {
+ const errorInfo =
+ data?.detail?.msg || (data?.detail[0] && data?.detail[0]?.msg);
+ Modal.error({
+ title: intl.formatMessage({
+ id: 'OBD.component.ConnectConfig.MetadbConnectionFailedPleaseCheck',
+ defaultMessage: 'MetaDB 连接失败,请检查连接配置',
+ }),
+ icon: ,
+ content: errorInfo,
+ okText: intl.formatMessage({
+ id: 'OBD.component.ConnectConfig.IKnow',
+ defaultMessage: '我知道了',
+ }),
+ });
+ },
+ },
+ );
+
+ const nextStep = () => {
+ form
+ .validateFields()
+ .then((values) => {
+ const { host, port, user, password } = values.metadb;
+ createMetadbConnection(
+ { sys: true },
+ {
+ host,
+ port,
+ user,
+ password,
+ cluster_name,
+ },
+ ).then(() => {
+ setData(values);
+ setCurrent(current + 1);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ });
+ })
+ .catch(({ errorFields }) => {
+ const errorName = errorFields?.[0].name;
+ form.scrollToField(errorName);
+ message.destroy();
+ });
+ };
+ const prevStep = () => {
+ setCurrent(current - 1);
+ };
+ const initialValues: FormValues = {
+ metadb: {
+ host: host || undefined,
+ port: port || undefined,
+ user: user || 'root@sys',
+ password: password || undefined,
+ },
+ };
+ return (
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.ConnectConfig.ConnectionInformation',
+ defaultMessage: '连接信息',
+ })}
+
+
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.ConnectConfig.AccessPassword',
+ defaultMessage: '访问密码',
+ })}
+
+
+
+
+ >
+ }
+ name={['metadb', 'password']}
+ rules={[
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.component.ConnectConfig.EnterAnAccessPassword',
+ defaultMessage: '请输入访问密码',
+ }),
+ },
+ ]}
+ style={InputWidthStyle}
+ >
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/ContentWithIcon/index.less b/web/src/component/ContentWithIcon/index.less
new file mode 100644
index 0000000..f7bbf0b
--- /dev/null
+++ b/web/src/component/ContentWithIcon/index.less
@@ -0,0 +1,26 @@
+.item {
+ display: inline-flex;
+ align-items: center;
+
+ .prefix {
+ margin-right: 8px;
+ :global {
+ .anticon {
+ vertical-align: middle;
+ }
+ }
+ }
+
+ .affix {
+ margin-left: 8px;
+ :global {
+ .anticon {
+ vertical-align: middle;
+ }
+ }
+ }
+
+ .pointable {
+ cursor: pointer;
+ }
+}
diff --git a/web/src/component/ContentWithIcon/index.tsx b/web/src/component/ContentWithIcon/index.tsx
new file mode 100644
index 0000000..fa9551c
--- /dev/null
+++ b/web/src/component/ContentWithIcon/index.tsx
@@ -0,0 +1,81 @@
+import { Badge, Tooltip } from '@oceanbase/design';
+import React, { isValidElement } from 'react';
+import classNames from 'classnames';
+import Icon from '@ant-design/icons';
+import type { IconComponentProps } from '@ant-design/icons/lib/components/Icon';
+import type { BadgeProps } from 'antd/es/badge';
+import type { TooltipProps } from 'antd/es/tooltip';
+import styles from './index.less';
+
+interface IconConfig extends IconComponentProps {
+ badge?: BadgeProps;
+ tooltip?: TooltipProps;
+ pointable?: boolean;
+}
+
+type IconPosition = 'prefix' | 'affix';
+
+export interface ContentWithIconProps {
+ content?: React.ReactNode;
+ prefixIcon?: IconConfig | React.ReactNode;
+ affixIcon?: IconConfig | React.ReactNode;
+ onClick?: (e: React.SyntheticEvent) => void;
+ style?: React.CSSProperties;
+ className?: string;
+}
+
+const ContentWithIcon: React.FC = ({
+ content,
+ prefixIcon,
+ affixIcon,
+ className,
+ ...restProps
+}) => {
+ return (
+
+ {prefixIcon &&
+ (isValidElement(prefixIcon) ? (
+ {prefixIcon}
+ ) : (
+ getIcon('prefix', prefixIcon)
+ ))}
+ {content}
+ {affixIcon &&
+ (isValidElement(affixIcon) ? (
+ {affixIcon}
+ ) : (
+ getIcon('affix', affixIcon)
+ ))}
+
+ );
+};
+
+function getIcon(position: IconPosition, config: IconConfig) {
+ const { component, badge, tooltip, pointable = false, ...restProps } = config;
+ return (
+ config && (
+
+ {badge ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ )
+ );
+}
+
+export default ContentWithIcon;
diff --git a/web/src/component/ContentWithInfo/index.less b/web/src/component/ContentWithInfo/index.less
new file mode 100644
index 0000000..590803b
--- /dev/null
+++ b/web/src/component/ContentWithInfo/index.less
@@ -0,0 +1,16 @@
+@import '~@oceanbase/design/es/theme/index.less';
+
+.container {
+ font-weight: normal;
+ font-size: 12px;
+
+ .icon {
+ color: @colorPrimary !important;
+ }
+
+ .content {
+ color: rgba(0, 0, 0, 0.45);
+ // 可能位于 Card title,避免字体加粗,需要设置字体类型
+ font-family: PingFangSC-Regular;
+ }
+}
diff --git a/web/src/component/ContentWithInfo/index.tsx b/web/src/component/ContentWithInfo/index.tsx
new file mode 100644
index 0000000..0702608
--- /dev/null
+++ b/web/src/component/ContentWithInfo/index.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import type { SpaceProps } from '@oceanbase/design';
+import { Space } from '@oceanbase/design';
+import { InfoCircleFilled } from '@ant-design/icons';
+import styles from './index.less';
+
+export interface ContentWithInfoProps extends SpaceProps {
+ content: React.ReactNode;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const ContentWithInfo: React.FC = ({ content, className, ...restProps }) => (
+
+
+ {content}
+
+);
+
+export default ContentWithInfo;
diff --git a/web/src/component/ContentWithQuestion/index.tsx b/web/src/component/ContentWithQuestion/index.tsx
new file mode 100644
index 0000000..d441411
--- /dev/null
+++ b/web/src/component/ContentWithQuestion/index.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { useToken } from '@oceanbase/design';
+import type { TooltipProps } from 'antd/es/tooltip';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import ContentWithIcon from '@/component/ContentWithIcon';
+
+export interface ContentWithQuestionProps {
+ content?: React.ReactNode;
+ /* tooltip 为空,则不展示 quertion 图标和 Tooltip */
+ tooltip?: TooltipProps;
+ /* 是否作为 label */
+ inLabel?: boolean;
+ onClick?: (e: React.SyntheticEvent) => void;
+ style?: React.CSSProperties;
+ className?: string;
+}
+
+const ContentWithQuestion: React.FC = ({
+ content,
+ tooltip,
+ inLabel,
+ ...restProps
+}) => {
+ const { token } = useToken();
+ return (
+
+ );
+};
+
+export default ContentWithQuestion;
diff --git a/web/src/component/CustomFooter/index.less b/web/src/component/CustomFooter/index.less
new file mode 100644
index 0000000..3ba2e8b
--- /dev/null
+++ b/web/src/component/CustomFooter/index.less
@@ -0,0 +1,18 @@
+.pageFooterContainer {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 99;
+ padding: 16px;
+ background-color: #f5f8ff;
+ border-top: 1px solid #dde4ed;
+ .pageFooter {
+ width: 1040px;
+ margin: 0 auto;
+ overflow: hidden;
+ .foolterAction {
+ float: right;
+ }
+ }
+}
diff --git a/web/src/component/CustomFooter/index.tsx b/web/src/component/CustomFooter/index.tsx
new file mode 100644
index 0000000..af234ba
--- /dev/null
+++ b/web/src/component/CustomFooter/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { Space } from 'antd';
+import styles from './index.less';
+
+export default function CustomFooter(
+ props: React.PropsWithChildren,
+) {
+ return (
+
+ );
+}
diff --git a/web/src/component/CustomPasswordInput/index.tsx b/web/src/component/CustomPasswordInput/index.tsx
new file mode 100644
index 0000000..aa2881a
--- /dev/null
+++ b/web/src/component/CustomPasswordInput/index.tsx
@@ -0,0 +1,177 @@
+import { intl } from '@/utils/intl';
+import { ProForm } from '@ant-design/pro-components';
+import { Input, Button, message } from 'antd';
+import { FormInstance } from 'antd/lib/form';
+import { NamePath } from 'rc-field-form/lib/interface';
+import { generateRandomPassword } from '@/utils';
+import { copyText } from '@/utils/helper';
+
+interface CustomPasswordInputProps {
+ onChange: (value: string) => void;
+ value: string;
+ label: React.ReactNode;
+ name: NamePath | undefined;
+ showCopyBtn?: boolean;
+ form: FormInstance;
+ msgInfo: MsgInfoType;
+ setMsgInfo: React.Dispatch>;
+}
+
+type MsgInfoType = {
+ validateStatus: 'success' | 'error';
+ errorMsg: string | null;
+};
+const passwordReg =
+ /^(?=.*[A-Z].*[A-Z])(?=.*[a-z].*[a-z])(?=.*\d.*\d)(?=.*[~!@#%^&*_\-+=`|(){}[\]:;',.?/].*[~!@#%^&*_\-+=`|(){}[\]:;',.?/])[A-Za-z\d~!@#%^&*_\-+=`|(){}[\]:;',.?/]{8,32}$/;
+export default function CustomPasswordInput({
+ onChange,
+ value,
+ showCopyBtn = false,
+ form,
+ name,
+ msgInfo,
+ setMsgInfo,
+ ...props
+}: CustomPasswordInputProps) {
+ const textStyle = { marginTop: '8px' };
+ const validateInput = (value: string): MsgInfoType => {
+ const regex = /^[A-Za-z\d~!@#%^&*_\-+=`|(){}[\]:;',.?/]*$/;
+ if (value.length < 8 || value.length > 32) {
+ return {
+ validateStatus: 'error',
+ errorMsg: intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.TheLengthShouldBeTo',
+ defaultMessage: '长度应为 8~32 个字符',
+ }),
+ };
+ } else if (!regex.test(value)) {
+ return {
+ validateStatus: 'error',
+ errorMsg: intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.CanOnlyContainLettersNumbers',
+ defaultMessage:
+ "只能包含字母、数字和特殊字符~!@#%^&*_-+=`|(){}[]:;',.?/",
+ }),
+ };
+ } else if (!passwordReg.test(value)) {
+ return {
+ validateStatus: 'error',
+ errorMsg: intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.AtLeastUppercaseAndLowercase',
+ defaultMessage: '大小写字母、数字和特殊字符都至少包含 2 个',
+ }),
+ };
+ }
+ return {
+ validateStatus: 'success',
+ errorMsg: null,
+ };
+ };
+ const handleChange = (value: string) => {
+ setMsgInfo(validateInput(value));
+ onChange(value);
+ };
+
+ const handleRandomGenerate = () => {
+ const password = generateRandomPassword();
+ setMsgInfo(validateInput(password));
+ onChange(password);
+ };
+ const passwordCopy = () => {
+ if (value) {
+ if (copyText(value)) {
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.CopiedSuccessfully',
+ defaultMessage: '复制成功',
+ }),
+ );
+ } else {
+ message.warning(
+ intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.TheCurrentBrowserDoesNot',
+ defaultMessage: '当前浏览器不支持文本复制',
+ }),
+ );
+ }
+ }
+ };
+ const Help = () => {
+ if (showCopyBtn) {
+ return (
+
+ {intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.KeepThePasswordInMind',
+ defaultMessage: '请牢记密码,也可复制密码并妥善保存',
+ })}
+
+ );
+ }
+ return (
+
+ {intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.PleaseRememberThePasswordOr',
+ defaultMessage: '请牢记密码,也可',
+ })}
+
+ passwordCopy()}>
+ {intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.CopyPassword',
+ defaultMessage: '复制密码',
+ })}
+
+ {intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.AndKeepItProperly',
+ defaultMessage: '并妥善保存',
+ })}
+
+ );
+ };
+
+ return (
+ {msgInfo?.errorMsg}
+ ) : (
+
+ )
+ }
+ rules={[
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.component.CustomPasswordInput.EnterAPassword',
+ defaultMessage: '请输入密码',
+ }),
+ },
+ ]}
+ name={name}
+ {...props}
+ >
+
+ handleChange(e.target.value)}
+ value={value}
+ style={{ width: 328 }}
+ />
+
+
+ {showCopyBtn && (
+
+ )}
+
+
+ );
+}
diff --git a/web/src/component/DeployConfig/constants.ts b/web/src/component/DeployConfig/constants.ts
new file mode 100644
index 0000000..dee126d
--- /dev/null
+++ b/web/src/component/DeployConfig/constants.ts
@@ -0,0 +1,82 @@
+import { intl } from '@/utils/intl';
+
+export type VersionInfoType = {
+ version: string;
+ md5: string;
+ release: string;
+ versionType: string;
+ value?: string;
+};
+
+export type TableDataType = {
+ name: string;
+ versionInfo: VersionInfoType[];
+ componentInfo: ComponentMetaType;
+ key:string
+};
+
+export type ClusterNameType = {
+ label: string;
+ value: string;
+};
+
+export type ComponentMetaType = { name: string; desc: string; url: string ,key:string};
+
+export const OCEANBASE = 'oceanbase';
+export const OBPROXY = 'obproxy';
+export const OCP = 'ocp-server';
+export const OCEANBASE_META:ComponentMetaType = {
+ key:OCEANBASE,
+ name:'OceanBase',
+ desc: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.FinancialLevelDistributedDatabasesAre',
+ defaultMessage:
+ '金融级分布式数据库,具备数据强一致,高扩展、高性价比稳定可靠等特征',
+ }),
+ url: 'https://www.oceanbase.com/docs/oceanbase-database-cn',
+}
+
+export const OCP_META:ComponentMetaType = {
+ key:OCP,
+ name:'OCP',
+ desc: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.EnterpriseLevelDataManagementPlatform',
+ defaultMessage:
+ '以 OceanBase 为核心的企业级数据管理平台,实现 OceanBase 全生命周期运维管理',
+ }),
+ url: 'https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001577895',
+}
+
+export const OBPROXY_META:ComponentMetaType = {
+ key:OBPROXY,
+ name:'OBProxy',
+ desc: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.OceanbaseADedicatedDatabaseProxy',
+ defaultMessage:
+ 'OceanBase 数据库专用代理服务器,可将用户的 SQL 请求转发至最佳目标 OBServer',
+ }),
+ url: 'https://www.oceanbase.com/docs/odp-doc-cn',
+}
+export const CompoentsInfo:ComponentMetaType[] = [OCP_META,OCEANBASE_META,OBPROXY_META]
+
+
+export const OCPComponent:TableDataType = {
+ key:OCP,
+ name:'OCP',
+ versionInfo:[],
+ componentInfo:OCP_META,
+}
+
+export const OBComponent:TableDataType = {
+ key:OCEANBASE,
+ name:'OceanBase',
+ versionInfo:[],
+ componentInfo:OCEANBASE_META,
+}
+
+export const OBProxyComponent:TableDataType = {
+ key:OBPROXY,
+ name:'OBProxy',
+ versionInfo:[],
+ componentInfo:OBPROXY_META,
+}
\ No newline at end of file
diff --git a/web/src/component/DeployConfig/index.less b/web/src/component/DeployConfig/index.less
new file mode 100644
index 0000000..e69de29
diff --git a/web/src/component/DeployConfig/index.tsx b/web/src/component/DeployConfig/index.tsx
new file mode 100644
index 0000000..56fa8e9
--- /dev/null
+++ b/web/src/component/DeployConfig/index.tsx
@@ -0,0 +1,1039 @@
+import { commonStyle, STABLE_OCP_VERSION } from '@/pages/constants';
+import { queryAllComponentVersions } from '@/services/ob-deploy-web/Components';
+import {
+ getClusterNames,
+ getConnectInfo,
+} from '@/services/ocp_installer_backend/OCP';
+import {
+ clusterNameReg,
+ errorHandler,
+ getErrorInfo,
+ updateClusterNameReg,
+} from '@/utils';
+import copy from 'copy-to-clipboard';
+import { getTailPath } from '@/utils/helper';
+import { intl } from '@/utils/intl';
+import customRequest from '@/utils/useRequest';
+import { InfoCircleOutlined, CopyOutlined } from '@ant-design/icons';
+import { ProCard, ProForm, ProFormText } from '@ant-design/pro-components';
+import { useRequest, useUpdateEffect } from 'ahooks';
+import {
+ Button,
+ Select,
+ Space,
+ Spin,
+ Table,
+ Tag,
+ Tooltip,
+ Alert,
+ message,
+} from 'antd';
+import type { ColumnsType } from 'antd/es/table';
+import { FormInstance } from 'antd/lib/form';
+import NP from 'number-precision';
+import { useEffect, useRef, useState } from 'react';
+import { getLocale, history, useModel } from 'umi';
+import EnStyles from '../../pages/Obdeploy/indexEn.less';
+import ZhStyles from '../../pages/Obdeploy/indexZh.less';
+import CustomFooter from '../CustomFooter';
+import ErrorCompToolTip from '../ErrorCompToolTip';
+import { listRemoteMirrors } from '@/services/ob-deploy-web/Mirror';
+import ExitBtn from '../ExitBtn';
+import type {
+ ClusterNameType,
+ TableDataType,
+ VersionInfoType,
+} from './constants';
+import {
+ CompoentsInfo,
+ OBComponent,
+ OBPROXY,
+ OBProxyComponent,
+ OCEANBASE,
+ OCP,
+ OCPComponent,
+} from './constants';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+export default function DeployConfig({
+ setCurrent,
+ current,
+ connectForm,
+ clearConnection,
+}: API.StepProp & {
+ connectForm?: FormInstance;
+ clearConnection?: () => void;
+}) {
+ const {
+ getInfoByName,
+ setErrorVisible,
+ errorsList,
+ setErrorsList,
+ setOcpNewFirstTime,
+ ocpConfigData,
+ setOcpConfigData,
+ selectCluster,
+ setSelectCluster,
+ } = useModel('global');
+ const {
+ obVersionInfo,
+ setObVersionInfo,
+ ocpVersionInfo,
+ setOcpVersionInfo,
+ obproxyVersionInfo,
+ setObproxyVersionInfo,
+ deployMemory,
+ setDeployMemory,
+ tableData,
+ setTableData,
+ } = useModel('ocpInstallData');
+ const [componentLoading, setComponentLoading] = useState(false);
+ const taiPath = getTailPath();
+ const isUpdate = taiPath === 'update';
+ const isNewDB = taiPath === 'install';
+ const [form] = ProForm.useForm();
+ const checkRegInfo = {
+ reg: isUpdate ? updateClusterNameReg : clusterNameReg,
+ msg: isUpdate
+ ? intl.formatMessage({
+ id: 'OBD.component.DeployConfig.ItStartsWithALetter.1',
+ defaultMessage:
+ '以英文字母开头、英文或数字结尾,可包含英文、数字、连字符和下划线,且长度为 2 ~ 32',
+ })
+ : intl.formatMessage({
+ id: 'OBD.component.DeployConfig.ItStartsWithALetter',
+ defaultMessage:
+ '以英文字母开头、英文或数字结尾,可包含英文、数字和下划线,且长度为 2 ~ 32',
+ }),
+ };
+ const { components = {} } = ocpConfigData || {};
+ const { oceanbase = {} } = components || {};
+ const [clusterOption, setClusterOption] = useState([]);
+ const selectClusetNameRef = useRef();
+ const searchTextRef = useRef();
+ const [existNoVersion, setExistNoVersion] = useState(false);
+ const wholeComponents = useRef([]);
+ const [unavailableList, setUnavailableList] = useState([]);
+ //查询version的前两位和type相匹配的obproxyInfo
+ const findObproxy = (version: string, type: 'ce' | 'business') => {
+ const obproxyVersionInfo = tableData?.find(
+ (item: TableDataType) => item.key === OBPROXY,
+ )?.versionInfo;
+ const target = obproxyVersionInfo?.find(
+ (item: VersionInfoType) =>
+ // 类型以及版本号前两位相同
+ item.versionType === type &&
+ version[0] === item.version[0] &&
+ version[2] === item.version[2],
+ );
+ return target;
+ };
+
+ const onVersionChange = (versionInfo: any, record: any) => {
+ const _version = versionInfo.value;
+ const [version, release, md5] = _version.split('-');
+ const versionType = record.versionInfo.find(
+ (item: VersionInfoType) =>
+ item.version === version &&
+ item.md5 === md5 &&
+ item.release === release,
+ ).versionType;
+ let target;
+ if (record.key === OCEANBASE) {
+ if (obVersionInfo?.versionType !== versionType) {
+ target = findObproxy(_version, versionType);
+ } else if (
+ obVersionInfo?.version[0] !== _version[0] ||
+ obVersionInfo?.version[2] !== _version[2]
+ ) {
+ target = findObproxy(_version, versionType);
+ }
+ if (target) {
+ target.value = `${target.version}-${target.release}`;
+ setObproxyVersionInfo(target);
+ }
+ setObVersionInfo({
+ version,
+ release,
+ md5,
+ versionType,
+ value: _version,
+ });
+ }
+ if (record.key === OCP) {
+ setOcpVersionInfo({
+ version,
+ release,
+ md5,
+ versionType,
+ value: _version,
+ });
+ }
+ };
+ const getColumns = () => {
+ const columns: ColumnsType = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.ProductName',
+ defaultMessage: '产品名称',
+ }),
+ dataIndex: 'name',
+ width: locale === 'zh-CN' ? 134 : 140,
+ render: (name, record) => {
+ return (
+ <>
+ {name}
+ {!record.versionInfo.length && (
+
+ )}
+ >
+ );
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.Version',
+ defaultMessage: '版本',
+ }),
+ dataIndex: 'versionInfo',
+ width: 220,
+ render: (_, record) => {
+ let selectVersion =
+ record.key === OCEANBASE
+ ? obVersionInfo
+ : record.key === OBPROXY
+ ? obproxyVersionInfo
+ : ocpVersionInfo;
+
+ if (selectVersion) {
+ selectVersion.valueInfo = {
+ value: selectVersion?.value,
+ label: (
+
+
+ V {selectVersion?.version}
+ {selectVersion?.release ? `-${selectVersion?.release}` : ''}
+
+ {selectVersion?.versionType === 'ce' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.CommunityEdition',
+ defaultMessage: '社区版',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.CommercialEdition',
+ defaultMessage: '商业版',
+ })}
+
+ )}
+
+ ),
+ };
+ } else {
+ selectVersion = {};
+ }
+ return (
+ // 版本联动 ocp是社区版,ob也得是社区版,obproxy不支持选择并且版本号与ob前两位一致
+
+ {record.key === OBPROXY ? (
+ // 用div包裹可以使Tooltip生效
+
+
+
+ ) : (
+
+ )}
+
+ );
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.Description',
+ defaultMessage: '描述',
+ }),
+ dataIndex: 'componentInfo',
+ render: (_, record) => (
+ <>
+ {record.componentInfo.desc || '-'}
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.LearnMore',
+ defaultMessage: '了解更多',
+ })}
+
+ >
+ ),
+ },
+ ];
+
+ return columns;
+ };
+ const nameValidator = async (_: any, value: string) => {
+ if (value) {
+ if (!checkRegInfo.reg.test(value)) {
+ return Promise.reject(new Error(checkRegInfo.msg));
+ }
+ if (!isUpdate) {
+ try {
+ const { success, data } = await getInfoByName({ name: value });
+ if (success) {
+ if (['CONFIGURED', 'DESTROYED'].includes(data?.status)) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ intl.formatMessage(
+ {
+ id: 'OBD.component.DeployConfig.ADeploymentNameWithValue',
+ defaultMessage: '已存在为 {value} 的部署名称,请指定新名称',
+ },
+ { value: value },
+ ),
+ ),
+ );
+ }
+ return Promise.resolve();
+ } catch ({ response, data, type }: any) {
+ if (response?.status === 404) {
+ if (isUpdate) {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage(
+ {
+ id: 'OBD.component.DeployConfig.TheDeploymentNameWithValue',
+ defaultMessage: '不存在为 {value} 的部署名称',
+ },
+ { value: value },
+ ),
+ ),
+ );
+ }
+ return Promise.resolve();
+ } else {
+ const errorInfo = getErrorInfo({ response, data, type });
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ }
+ }
+ }
+ };
+
+ const caculateSize = (originSize: number): number => {
+ return Number(NP.divide(NP.divide(originSize, 1024), 1024).toFixed(2));
+ };
+ const getVersion = (name: string, info?: any[]) => {
+ if (!info) return [];
+ return info.map((item) => ({
+ versionType: item.version_type,
+ version: item.version,
+ release: item.release,
+ md5: item.md5,
+ }));
+ };
+
+ const getRecommendInfo = (data: any) => {
+ if (data.key === OCP) {
+ let stableVersionArr = data.info.filter((item: any) => {
+ const versionArr = item.version.split('.');
+ return (
+ versionArr[0] === STABLE_OCP_VERSION[0] &&
+ versionArr[1] === STABLE_OCP_VERSION[1] &&
+ versionArr[2] === STABLE_OCP_VERSION[2]
+ );
+ });
+ const temp = stableVersionArr.map((item: any) =>
+ Number(item?.release?.split('.')[0]),
+ );
+ if (temp && temp.length >= 2) {
+ const maxRelease = temp.sort(
+ (pre: number, next: number) => next - pre,
+ )[0];
+ return stableVersionArr.find(
+ (stableVersion: any) =>
+ Number(stableVersion?.release?.split('.')[0]) === maxRelease,
+ );
+ } else {
+ return data.info[0];
+ }
+ }
+ return data.info[0];
+ };
+
+ const judgVersions = (length: number) => {
+ if (length !== wholeComponents.current.length) {
+ setExistNoVersion(true);
+ } else {
+ setExistNoVersion(false);
+ }
+ };
+
+ //初始值 最新版本
+ const setInitVersion = (data: any) => {
+ let versionInfo = getRecommendInfo(data);
+ let detail = {
+ version: versionInfo.version,
+ release: versionInfo.release,
+ md5: versionInfo.md5,
+ versionType: versionInfo.version_type || 'business',
+ value: `${versionInfo.version}-${versionInfo.release}`,
+ };
+ if (data.name === OCEANBASE) {
+ setObVersionInfo(detail);
+ }
+ if (data.name === OCP) {
+ setOcpVersionInfo(detail);
+ }
+ if (data.name === OBPROXY) {
+ setObproxyVersionInfo(detail);
+ }
+ };
+
+ const { run: getClusterList } = useRequest(getClusterNames, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res.success) {
+ let clusterNames = res.data?.name.map((val: string) => ({
+ label: val,
+ value: val,
+ }));
+ setClusterOption(clusterNames || []);
+ }
+ },
+ });
+
+ //获取连接信息默认值
+ const { run: getConnectInfoReq } = useRequest(getConnectInfo, {
+ manual: true,
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const prevStep = () => {
+ setOcpConfigData({});
+ setErrorVisible(false);
+ setErrorsList([]);
+ setObVersionInfo(undefined);
+ setOcpVersionInfo(undefined);
+ setObproxyVersionInfo(undefined);
+ setTableData(undefined);
+ if (isUpdate) {
+ history.push('/updateWelcome');
+ } else {
+ history.push('/ocpInstaller');
+ }
+ };
+
+ const formateConnectData = (data: API.ConnectInfo) => {
+ return {
+ host: data.host,
+ port: data.port,
+ database: data.database,
+ accessUser: data.user || 'ocp@ocp_meta',
+ accessCode: data.password,
+ };
+ };
+
+ const changeClusterName = (newName: string): boolean => {
+ let oldName = ocpConfigData?.components?.oceanbase?.appname;
+ return oldName !== newName;
+ };
+
+ const nextStep = () => {
+ if (form.getFieldsError(['appname'])[0].errors.length) return;
+ form.validateFields().then(async (values) => {
+ let newComponents: API.Components, newOcpConfigData: any;
+ if (!isNewDB) {
+ newComponents = {
+ oceanbase: {
+ appname: values?.appname,
+ },
+ ocpserver: {
+ ...(components?.ocpserver || {}),
+ component:
+ ocpVersionInfo?.versionType === 'ce'
+ ? 'ocp-server-ce'
+ : 'ocp-server',
+ version: ocpVersionInfo?.version,
+ release: ocpVersionInfo?.release,
+ package_hash: ocpVersionInfo?.md5,
+ },
+ };
+ } else {
+ newComponents = {
+ oceanbase: {
+ ...(components?.oceanbase || {}),
+ component:
+ obVersionInfo?.versionType === 'ce' ? 'oceanbase-ce' : OCEANBASE,
+ appname: values?.appname,
+ version: obVersionInfo?.version,
+ release: obVersionInfo?.release,
+ package_hash: obVersionInfo?.md5,
+ },
+ obproxy: {
+ ...(components?.obproxy || {}),
+ component:
+ obproxyVersionInfo?.versionType === 'ce' ? 'obproxy-ce' : OBPROXY,
+ version: obproxyVersionInfo?.version,
+ release: obproxyVersionInfo?.release,
+ package_hash: obproxyVersionInfo?.md5,
+ },
+ ocpserver: {
+ ...(components?.ocpserver || {}),
+ component:
+ ocpVersionInfo?.versionType === 'ce'
+ ? 'ocp-server-ce'
+ : 'ocp-server',
+ version: ocpVersionInfo?.version,
+ release: ocpVersionInfo?.release,
+ package_hash: ocpVersionInfo?.md5,
+ },
+ };
+ }
+ newOcpConfigData = {
+ ...ocpConfigData,
+ components: newComponents,
+ };
+ if (isUpdate && changeClusterName(values?.appname)) {
+ try {
+ const { success, data } = await getConnectInfoReq({
+ name: values?.appname,
+ });
+ if (success && data) {
+ //首次进入 设置初始值
+ if (!ocpConfigData.updateConnectInfo) {
+ newOcpConfigData.updateConnectInfo =
+ formateConnectData(data) || {};
+ } else {
+ //重置状态
+ clearConnection && clearConnection();
+ connectForm?.setFieldsValue({
+ ...formateConnectData(data),
+ });
+ }
+ }
+ } catch (err) {}
+ }
+
+ setOcpConfigData({ ...newOcpConfigData });
+ setCurrent(current + 1);
+ setOcpNewFirstTime(false);
+ setErrorVisible(false);
+ setErrorsList([]);
+ });
+ };
+
+ const oparete = (item: any, dataSource: any, memory: number) => {
+ const component = CompoentsInfo.find(
+ (compoentInfo) => compoentInfo.key === item.name,
+ );
+ let temp: TableDataType = {
+ name: component?.name!,
+ versionInfo: getVersion(item.name, item.info),
+ componentInfo: component!,
+ key: component?.key!,
+ };
+ setInitVersion(item);
+ memory += caculateSize(getRecommendInfo(item).estimated_size);
+ dataSource.push(temp);
+ return memory;
+ };
+
+ const sortComponent = (dataSource: any[]) => {
+ let OCPComp = dataSource.find((comp) => comp.key === OCP);
+ let OBComp = dataSource.find((comp) => comp.key === OCEANBASE);
+ let ProxyComp = dataSource.find((comp) => comp.key === OBPROXY);
+ dataSource[0] = OCPComp;
+ dataSource[1] = OBComp;
+ dataSource[2] = ProxyComp;
+ };
+
+ const completionComponent = (dataSource: any[]) => {
+ for (let component of wholeComponents.current) {
+ if (!dataSource.find((item) => item.name === component.name)) {
+ dataSource.push(component);
+ }
+ }
+ };
+
+ const { run: fetchListRemoteMirrors } = useRequest(listRemoteMirrors, {
+ onSuccess: () => {
+ setComponentLoading(false);
+ },
+ onError: ({ response, data, type }: any) => {
+ if (response?.status === 503) {
+ setTimeout(() => {
+ fetchListRemoteMirrors();
+ }, 1000);
+ } else {
+ const errorInfo = getErrorInfo({ response, data, type });
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ setComponentLoading(false);
+ }
+ },
+ });
+
+ const { run: fetchAllComponentVersions, loading: versionLoading } =
+ customRequest(queryAllComponentVersions, {
+ onSuccess: async ({
+ success,
+ data,
+ }: API.OBResponseDataListComponent_) => {
+ if (success && data?.items) {
+ let dataSource: any[] = [],
+ memory = 0;
+ for (let item of data?.items) {
+ if (!isNewDB) {
+ wholeComponents.current = [OCPComponent];
+ if (item.name === OCP) {
+ if (item.info?.length) {
+ memory = oparete(item, dataSource, memory);
+ }
+ }
+ } else {
+ wholeComponents.current = [
+ OCPComponent,
+ OBComponent,
+ OBProxyComponent,
+ ];
+
+ if (
+ item.name === OCP ||
+ item.name === OCEANBASE ||
+ item.name === OBPROXY
+ ) {
+ if (item.info?.length) {
+ memory = oparete(item, dataSource, memory);
+ }
+ }
+ }
+ }
+
+ //需判断是否区分部署有无metadb 升级wholeComponents
+ const noVersion =
+ dataSource.length !== wholeComponents.current.length;
+ judgVersions(dataSource.length);
+
+ if (noVersion) {
+ const { success: mirrorSuccess, data: mirrorData } =
+ await fetchListRemoteMirrors();
+ if (mirrorSuccess) {
+ const nameList: string[] = [];
+ if (mirrorData?.total < 2) {
+ const mirrorName = mirrorData?.items?.map(
+ (item: API.Mirror) => item.section_name,
+ );
+
+ const noDataName = [...mirrorName, ...mirrors].filter(
+ (name) =>
+ mirrors.includes(name) && !mirrorName.includes(name),
+ );
+
+ noDataName.forEach((name) => {
+ nameList.push(name);
+ });
+ }
+ if (mirrorData?.total) {
+ mirrorData?.items?.forEach((item: API.Mirror) => {
+ if (!item.available) {
+ nameList.push(item.section_name);
+ }
+ });
+ }
+ setUnavailableList(nameList);
+ }
+ } else {
+ setComponentLoading(false);
+ }
+
+ completionComponent(dataSource);
+ isNewDB && sortComponent(dataSource);
+ setDeployMemory(memory);
+ setTableData(dataSource);
+ setComponentLoading(false);
+ }
+ },
+ onError: ({ response, data, type }: any) => {
+ if (response?.status === 503) {
+ setTimeout(() => {
+ fetchAllComponentVersions();
+ }, 1000);
+ } else {
+ const errorInfo = getErrorInfo({ response, data, type });
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ setComponentLoading(false);
+ }
+ },
+ });
+
+ useEffect(() => {
+ getClusterList();
+ if (!obVersionInfo || !obproxyVersionInfo || !ocpVersionInfo) {
+ setComponentLoading(true);
+ fetchAllComponentVersions();
+ }
+ }, []);
+ const handleChangeCluster = (val: string) => {
+ setSelectCluster(val);
+ form.setFieldValue('appname', val);
+ };
+
+ const handleSearchCluster = (val: string) => {
+ if (val) {
+ searchTextRef.current = val;
+ }
+ };
+
+ const handleChangeSearchText = () => {
+ const select = clusterOption.find((option) => {
+ return option.value === searchTextRef.current;
+ });
+ if (!select && searchTextRef.current) {
+ setClusterOption([
+ { value: searchTextRef.current, label: searchTextRef.current },
+ ...clusterOption,
+ ]);
+ }
+ };
+
+ const handleCopy = (content: string) => {
+ copy(content);
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.CopiedSuccessfully',
+ defaultMessage: '复制成功',
+ }),
+ );
+ };
+
+ const clusterNameRules = [
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.EnterAClusterName',
+ defaultMessage: '请输入集群名称',
+ }),
+ validateTrigger: 'onChange',
+ },
+ {
+ pattern: checkRegInfo.reg,
+ message: checkRegInfo.msg,
+
+ validateTrigger: 'onChange',
+ },
+ { validator: nameValidator, validateTrigger: 'onBlur' },
+ ];
+
+ const cluserNameProps = {
+ name: 'appname',
+ label: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.ClusterName',
+ defaultMessage: '集群名称',
+ }),
+ rules: clusterNameRules,
+ placeholder: intl.formatMessage({
+ id: 'OBD.component.DeployConfig.PleaseEnter',
+ defaultMessage: '请输入',
+ }),
+ validateTrigger: ['onBlur', 'onChange'],
+ };
+
+ useEffect(() => {
+ if (searchTextRef.current) {
+ setSelectCluster(searchTextRef.current);
+ form.setFieldValue('appname', searchTextRef.current);
+ }
+ }, [clusterOption]);
+
+ useUpdateEffect(() => {
+ form.validateFields(['appname']);
+ }, [selectCluster]);
+
+ return (
+ <>
+
+
+
+
+
+ {isUpdate ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.VersionSelection',
+ defaultMessage: '版本选择',
+ })}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.EstimatedInstallationRequirements',
+ defaultMessage: '预计安装需要',
+ })}
+ {deployMemory}
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.MbSpace',
+ defaultMessage: 'MB空间',
+ })}
+
+ >
+ }
+ className="card-header-padding-top-0 card-padding-bottom-24 card-padding-top-0"
+ >
+
+ {existNoVersion ? (
+ unavailableList?.length ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.IfTheCurrentEnvironmentCannot',
+ defaultMessage:
+ '如当前环境无法正常访问外网,建议使用 OceanBase\n 离线安装包进行安装部署。',
+ })}
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.GoToDownloadOfflineInstallation',
+ defaultMessage: '前往下载离线安装',
+ })}
+
+ >
+ }
+ type="error"
+ showIcon
+ style={{ marginTop: '16px' }}
+ />
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.IfTheCurrentEnvironmentHas',
+ defaultMessage:
+ '如当前环境可正常访问外网,可启动 OceanBase 在线镜像仓库,或联系您的镜像仓库管理员。',
+ })}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.RunTheCommandOnThe',
+ defaultMessage:
+ '请在主机上执行一下命令启用在线镜像仓库',
+ })}
+
obd mirror enable
+ oceanbase.community.stable
+ oceanbase.development-kit
+
+
+ handleCopy(
+ 'obd mirror enable oceanbase.community.stable oceanbase.development-kit',
+ )
+ }
+ />
+
+
+ }
+ >
+
+ {intl.formatMessage({
+ id: 'OBD.component.DeployConfig.HowToEnableOnlineImage',
+ defaultMessage: '如何启用在线镜像仓库',
+ })}
+
+
+ >
+ }
+ type="error"
+ showIcon
+ style={{ marginTop: '16px' }}
+ />
+ )
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/component/EditText.tsx b/web/src/component/EditText.tsx
new file mode 100644
index 0000000..d5bbc29
--- /dev/null
+++ b/web/src/component/EditText.tsx
@@ -0,0 +1,72 @@
+import React, { useState, useEffect } from 'react';
+import { Input, Row, Col } from '@oceanbase/design';
+import { EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
+
+export interface EditTextProps {
+ address: string;
+ onOk: (address: string) => void;
+ style?: React.CSSProperties;
+}
+
+const EditText: React.FC = ({ address: initAddress, onOk, ...restProps }) => {
+ const [edit, setEdit] = useState(false);
+ const [address, setAddress] = useState(initAddress as string);
+ const [oldAddress, setOldAddress] = useState(initAddress as string);
+ // 初始化默认值
+ useEffect(() => {
+ if (initAddress) {
+ setAddress(initAddress);
+ setOldAddress(initAddress);
+ }
+ }, [initAddress]);
+
+ return (
+
+ {edit ? (
+ <>
+
+ {
+ setAddress(value);
+ }}
+ />
+
+
+ {
+ setAddress(oldAddress);
+ setEdit(false);
+ }}
+ />
+
+
+ {
+ if (onOk) {
+ onOk(address);
+ }
+ }}
+ />
+
+ >
+ ) : (
+ <>
+ {address}
+
+
+ {
+ setEdit(true);
+ }}
+ />
+
+
+ >
+ )}
+
+ );
+};
+
+export default EditText;
diff --git a/web/src/component/Empty/index.less b/web/src/component/Empty/index.less
new file mode 100644
index 0000000..a426267
--- /dev/null
+++ b/web/src/component/Empty/index.less
@@ -0,0 +1,32 @@
+.page {
+ height: calc(100vh - 72px);
+}
+.empty {
+ .description {
+ margin: 24px 0;
+ color: #8592ad;
+ }
+ :global {
+ .ant-empty-image {
+ height: 102px;
+ }
+ .ant-empty-footer {
+ margin-top: 24px;
+ }
+ }
+}
+.small {
+ .title {
+ margin-bottom: 4px;
+ font-size: 14px;
+ }
+ .description {
+ margin: 20px 0;
+ font-size: 12px;
+ }
+ :global {
+ .ant-empty-image {
+ height: 72px;
+ }
+ }
+}
diff --git a/web/src/component/Empty/index.tsx b/web/src/component/Empty/index.tsx
new file mode 100644
index 0000000..21023e3
--- /dev/null
+++ b/web/src/component/Empty/index.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { Empty } from '@oceanbase/design';
+import type { EmptyProps as AntdEmptyProps } from 'antd/es/empty';
+import { PageContainer } from '@oceanbase/ui';
+
+import PageCard from '@/component/PageCard';
+import styles from './index.less';
+
+export interface EmptyProps extends AntdEmptyProps {
+ image?: React.ReactNode;
+ title?: React.ReactNode;
+ description?: React.ReactNode;
+ children?: React.ReactNode;
+ // 展示模式: 页面模式 | 组件模式
+ mode?: 'page' | 'pageCard' | 'component';
+ size?: 'default' | 'small';
+}
+
+export default ({
+ image = '/assets/common/empty.svg',
+ title,
+ description,
+ children,
+ // 默认为页面模式
+ mode = 'page',
+ size = 'default',
+ className,
+ style,
+ ...restProps
+}: EmptyProps) => {
+ const empty = (
+
+ {title && {title}
}
+ {description}
+
+ }
+ {...restProps}
+ >
+ {children}
+
+ );
+ const pageCard = (
+
+ {empty}
+
+ );
+ if (mode === 'page') {
+ return {pageCard};
+ }
+ if (mode === 'pageCard') {
+ return pageCard;
+ }
+ if (mode === 'component') {
+ return empty;
+ }
+ return {pageCard};
+};
diff --git a/web/src/component/EnvPreCheck/CheckFailuredItem.tsx b/web/src/component/EnvPreCheck/CheckFailuredItem.tsx
new file mode 100644
index 0000000..2340733
--- /dev/null
+++ b/web/src/component/EnvPreCheck/CheckFailuredItem.tsx
@@ -0,0 +1,240 @@
+import { intl } from '@/utils/intl';
+import React, { useState } from 'react';
+import {
+ Button,
+ Space,
+ Checkbox,
+ Typography,
+ Tag,
+ Spin,
+} from '@oceanbase/design';
+import {
+ CloseCircleFilled,
+ QuestionCircleFilled,
+ ReadFilled,
+} from '@ant-design/icons';
+import { findByValue } from '@oceanbase/util';
+import { useRequest } from 'ahooks';
+import { errorHandler } from '@/utils';
+import * as Metadb from '@/services/ocp_installer_backend/Metadb';
+import { ERROR_CODE_LIST } from '@/constant/envPresCheck';
+import Prechecked from '@/component/Icon/Prechecked';
+import styles from './index.less';
+const { Text } = Typography;
+
+export interface CheckFailuredItemProps {
+ id?: number;
+ loading?: boolean;
+ precheckMetadbResult: any;
+ onSuccess?: () => void;
+}
+
+const CheckFailuredItem: React.FC = ({
+ id,
+ loading,
+ onSuccess,
+ precheckMetadbResult,
+}) => {
+ let precheckStatus;
+ // const { precheckStatus } = useSelector((state: DefaultRootState) => state.global);
+ const precheck_failed_list =
+ precheckMetadbResult?.precheck_result?.filter(
+ (item) => item.result === 'FAILED',
+ ) || [];
+
+ const [precheckFailedList, setlrecheckFailedList] =
+ useState<[]>(precheck_failed_list);
+
+ const { runAsync: recoverMetadbDeployment } = useRequest(
+ Metadb.recoverMetadbDeployment,
+ {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success && onSuccess) {
+ onSuccess();
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ // recoverable: false 手动
+ // recoverable: true 自动
+ const onChange = (e: CheckboxChangeEvent) => {
+ setlrecheckFailedList(
+ e?.target?.checked
+ ? precheck_failed_list.filter((item) => item.recoverable === false)
+ : precheck_failed_list,
+ );
+ };
+
+ return (
+
+
+ {`失败项 ${precheck_failed_list.length}/${
+ precheckMetadbResult?.precheck_result?.length || 0
+ }`}
+
+ {precheckStatus === 'RUNNING' ? null : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.EnvPreCheck.CheckFailuredItem.OnlyManualFixes',
+ defaultMessage: '只看手动修复项',
+ })}
+
+ )}
+
+
+
+
+ <>
+ {precheckFailedList.length === 0 || loading ? (
+
+ {precheckStatus === 'RUNNING' && (
+
+ )}
+
+
+ {precheckStatus === 'FINISHED' &&
+ precheckMetadbResult?.task_info?.result === 'SUCCESSFUL' && (
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.EnvPreCheck.CheckFailuredItem.GreatCheckAllSuccessful',
+ defaultMessage: '太棒了!检查全部成功!',
+ })}
+
+
+ )}
+
+
+ ) : (
+ <>
+ {precheckFailedList.map((item: API.PreCheckResult) => (
+
+
+
+ {`目录空间(${item?.name})`}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.EnvPreCheck.CheckFailuredItem.Reason',
+ defaultMessage: '原因:',
+ })}
+
+
+
+ {`ERR-${item.code}`}
+
+ {findByValue(ERROR_CODE_LIST, item.code).label}
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.EnvPreCheck.CheckFailuredItem.Suggestions',
+ defaultMessage: '建议:',
+ })}
+
+
+ {item.recoverable ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.EnvPreCheck.CheckFailuredItem.AutomaticRepair',
+ defaultMessage: '自动修复',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.EnvPreCheck.CheckFailuredItem.ManualRepair',
+ defaultMessage: '手动修复',
+ })}
+
+ )}
+ {item.advisement}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.EnvPreCheck.CheckFailuredItem.LearnMore',
+ defaultMessage: '了解更多方案',
+ })}
+
+
+ ))}
+ {precheckStatus === 'RUNNING' ||
+ (loading &&
)}
+ >
+ )}
+ >
+
+ );
+};
+
+export default CheckFailuredItem;
diff --git a/web/src/component/EnvPreCheck/CheckItem.tsx b/web/src/component/EnvPreCheck/CheckItem.tsx
new file mode 100644
index 0000000..3f9f2d8
--- /dev/null
+++ b/web/src/component/EnvPreCheck/CheckItem.tsx
@@ -0,0 +1,139 @@
+import { intl } from '@/utils/intl';
+import React, { useEffect, useState } from 'react';
+import { Button, Progress } from '@oceanbase/design';
+import { Timeline } from 'antd';
+import {
+ CheckCircleTwoTone,
+ CloseCircleTwoTone,
+ LoadingOutlined,
+} from '@ant-design/icons';
+import NP from 'number-precision';
+import styles from './index.less';
+
+export interface CheckItemProps {
+ precheckMetadbResult?: API.PrecheckTaskInfo;
+ refresh?: () => void;
+}
+
+const statusColorConfig = {
+ PASSED: 'green',
+ PENDING: 'gray',
+ FAILED: 'red',
+};
+
+const CheckItem: React.FC = ({
+ refresh,
+ precheckMetadbResult,
+}) => {
+ // const { precheckStatus } = useSelector((state: DefaultRootState) => state.global);
+ let precheckStatus;
+
+ const [currentPrecheckMetadbResult, setCurrentPrecheckMetadbResult] =
+ useState(precheckMetadbResult || {});
+
+ const total = precheckMetadbResult?.precheck_result?.length || 0;
+ const passedCount =
+ precheckMetadbResult?.precheck_result?.filter(
+ (item) => item.result === 'PASSED',
+ ).length || 0;
+ const currentPassedCount =
+ currentPrecheckMetadbResult?.precheck_result?.filter(
+ (item) => item.result === 'PASSED',
+ ).length || 0;
+
+ useEffect(() => {
+ if (passedCount !== currentPassedCount) {
+ setCurrentPrecheckMetadbResult(precheckMetadbResult);
+ }
+ if (precheckStatus === 'RUNNING') {
+ setTimeout(() => {
+ const timelineContainer = document.getElementById('timeline-container');
+ const runningItemDom = document.getElementById('running-timeline-item');
+ if (timelineContainer) {
+ timelineContainer.scrollTop = NP.minus(
+ NP.strip(runningItemDom?.offsetTop),
+ 150,
+ );
+ }
+ }, 10);
+ }
+ }, [precheckStatus, passedCount]);
+
+ const renderTimelineItemsIcon = (result?: string) => {
+ switch (result) {
+ case 'PASSED':
+ return (
+
+ );
+ case 'FAILED':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+
+
+ {intl.formatMessage(
+ {
+ id: 'OBD.component.EnvPreCheck.CheckItem.CheckItemPassedcountTotal',
+ defaultMessage: '检查项 {{passedCount}}/{{total}}',
+ },
+ { passedCount: passedCount, total: total },
+ )}
+
+
+
+
+
+
+ {currentPrecheckMetadbResult?.precheck_result?.map(
+ (item: any, index: number) => (
+
+ {item?.name} {item?.server}
+
+ ),
+ )}
+
+
+ );
+};
+
+export default CheckItem;
diff --git a/web/src/component/EnvPreCheck/index.less b/web/src/component/EnvPreCheck/index.less
new file mode 100644
index 0000000..1606ae0
--- /dev/null
+++ b/web/src/component/EnvPreCheck/index.less
@@ -0,0 +1,61 @@
+.precheck {
+ :global {
+ .ant-card-head {
+ border-bottom: none;
+ }
+ }
+ .checkItem {
+ min-height: 656px;
+ background-color: #f8fafe;
+ border-radius: 8px;
+
+ .checkItemTitle {
+ display: flex;
+ justify-content: space-between;
+ padding: 16px;
+ border-bottom: 2px solid #e2e8f3;
+ }
+
+ .checnProgress {
+ position: relative;
+ top: -14px;
+ :global {
+ .ant-progress-inner {
+ background-color: #e2e8f3;
+ }
+ .ant-progress-outer {
+ height: 2px !important;
+ }
+ .ant-progress-bg {
+ height: 2px !important;
+ }
+ }
+ }
+
+ .checkSteps {
+ margin-left: 16px;
+ :global {
+ .ant-steps-item:first-child {
+ .ant-steps-item-icon {
+ display: none;
+ }
+ }
+ .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail {
+ padding: 0 !important;
+ }
+ }
+ }
+
+ .checkFailuredContent {
+ display: flex;
+ flex-direction: column;
+ padding: 16px;
+ border-bottom: 1px solid #e2e8f3;
+
+ .checkFailuredItem {
+ display: flex;
+ white-space: nowrap;
+ }
+ }
+ }
+}
diff --git a/web/src/component/EnvPreCheck/index.tsx b/web/src/component/EnvPreCheck/index.tsx
new file mode 100644
index 0000000..533bd3e
--- /dev/null
+++ b/web/src/component/EnvPreCheck/index.tsx
@@ -0,0 +1,246 @@
+import { intl } from '@/utils/intl';
+import React, { useEffect } from 'react';
+import { Card, Row, Col } from '@oceanbase/design';
+import { useRequest } from 'ahooks';
+import { errorHandler } from '@/utils';
+import * as Metadb from '@/services/ocp_installer_backend/Metadb';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import CheckItem from './CheckItem';
+import CheckFailuredItem from './CheckFailuredItem';
+import ContentWithQuestion from '../ContentWithQuestion';
+import styles from './index.less';
+
+export interface EnvPreCheckProps {
+ id?: number;
+ installType?: string;
+}
+
+const EnvPreCheck: React.FC = ({ id, installType }) => {
+ // const dispatch = useDispatch();
+ let precheckStatus, precheckMetadbResult;
+
+ // const { precheckStatus, precheckMetadbResult } = useSelector(
+ // (state: DefaultRootState) => state.global
+ // );
+
+ // 发起MetaDb的预检查
+ const {
+ run: precheckMetadbDeploymentFn,
+ refresh: rePrecheckMetadbDeployment,
+ } = useRequest(Metadb.precheckMetadbDeployment, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res.success) {
+ // dispatch({
+ // type: 'global/update',
+ // payload: {
+ // precheckStatus: res?.data?.status,
+ // precheckResult: res?.data?.result,
+ // },
+ // });
+ getMetadbPrecheckTask({
+ id: id,
+ task_id: res.data?.id,
+ });
+ }
+ },
+ });
+
+ //MetaDb的预检查结果
+ const {
+ data: metadbPrecheckTaskData,
+ run: getMetadbPrecheckTask,
+ refresh: reGetMetadbPrecheckTask,
+ loading: metadbPrecheckTaskLoading,
+ } = useRequest(Metadb.getMetadbPrecheckTask, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success) {
+ // dispatch({
+ // type: 'global/update',
+ // payload: {
+ // precheckResult: res?.data?.task_info?.result || '',
+ // precheckStatus: res?.data?.task_info?.status || '',
+ // precheckMetadbResult: res?.data || {},
+ // },
+ // });
+ if (res?.data?.task_info?.status === 'RUNNING') {
+ setTimeout(() => {
+ reGetMetadbPrecheckTask();
+ }, 2000);
+ }
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const precheckMetadTask = metadbPrecheckTaskData?.data || {};
+
+ // 发起OCP的预检查
+ const { run: precheckOcpDeployment, refresh: rePrecheckOcpDeployment } =
+ useRequest(OCP.precheckOcpDeployment, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res.success) {
+ // dispatch({
+ // type: 'global/update',
+ // payload: {
+ // precheckStatus: res?.data?.status,
+ // precheckResult: res?.data?.result,
+ // },
+ // });
+ precheckOcp({
+ id: id,
+ task_id: res.data?.id,
+ });
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const {
+ data: precheckOcpDatas,
+ run: precheckOcp,
+ refresh,
+ loading,
+ } = useRequest(OCP.precheckOcp, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success) {
+ // dispatch({
+ // type: 'global/update',
+ // payload: {
+ // precheckResult: res?.data?.task_info?.result || '',
+ // precheckStatus: res?.data?.task_info?.status || '',
+ // precheckMetadbResult: res?.data || {},
+ // },
+ // });
+ if (res?.data?.task_info?.status === 'RUNNING') {
+ setTimeout(() => {
+ refresh();
+ }, 2000);
+ }
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const precheckOcpData = precheckOcpDatas?.data || {};
+
+ useEffect(() => {
+ if (id) {
+ if (installType === 'OCP') {
+ precheckOcpDeployment({
+ id: id,
+ });
+ } else {
+ precheckMetadbDeploymentFn({
+ id: id,
+ });
+ }
+ }
+ }, [id]);
+
+ return (
+
+ )
+ }
+ bodyStyle={{ paddingTop: 0, minHeight: 680 }}
+ >
+ {installType === 'OCP' ? (
+
+ ) : (
+
+ )}
+
+
+
+ {
+ if (installType === 'OCP') {
+ rePrecheckOcpDeployment();
+ } else {
+ rePrecheckMetadbDeployment();
+ }
+ }}
+ precheckMetadbResult={
+ id
+ ? installType === 'OCP'
+ ? precheckOcpData
+ : precheckMetadTask
+ : precheckMetadbResult
+ }
+ />
+
+
+ {
+ if (installType === 'OCP') {
+ rePrecheckOcpDeployment();
+ } else {
+ rePrecheckMetadbDeployment();
+ }
+ }}
+ precheckMetadbResult={
+ id
+ ? installType === 'OCP'
+ ? precheckOcpData
+ : precheckMetadTask
+ : precheckMetadbResult
+ }
+ />
+
+
+
+ );
+};
+
+export default EnvPreCheck;
diff --git a/web/src/component/ErrorBoundary.tsx b/web/src/component/ErrorBoundary.tsx
new file mode 100644
index 0000000..233f307
--- /dev/null
+++ b/web/src/component/ErrorBoundary.tsx
@@ -0,0 +1,87 @@
+import { intl } from '@/utils/intl';
+import { history } from 'umi';
+import React from 'react';
+import { Alert, Button } from '@oceanbase/design';
+import Empty from '@/component/Empty';
+
+interface ErrorBoundaryProps {
+ message?: React.ReactNode;
+ description?: React.ReactNode;
+}
+
+interface ErrorBoundaryState {
+ error?: Error | null;
+ info: {
+ componentStack?: string;
+ };
+}
+
+class ErrorBoundary extends React.Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ state = {
+ error: undefined,
+ info: {
+ componentStack: '',
+ },
+ };
+
+ componentDidCatch(error: Error | null, info: object) {
+ this.setState({ error, info });
+ }
+
+ render() {
+ const { message, description, children } = this.props;
+ const { error, info } = this.state;
+ const componentStack =
+ info && info.componentStack ? info.componentStack : null;
+ const errorMessage =
+ typeof message === 'undefined' ? (error || '').toString() : message;
+ const errorDescription =
+ typeof description === 'undefined' ? componentStack : description;
+ if (error) {
+ return (
+
+
+ {/* 展示报错详情,方便定位问题原因 */}
+
+
+ );
+ }
+ return children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/web/src/component/ErrorCompToolTip/index.less b/web/src/component/ErrorCompToolTip/index.less
new file mode 100644
index 0000000..76ad121
--- /dev/null
+++ b/web/src/component/ErrorCompToolTip/index.less
@@ -0,0 +1,17 @@
+.iconContainer {
+ position: relative;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ margin-left: 8px;
+ color: #fff;
+ font-size: 12px;
+ line-height: 22px;
+ vertical-align: middle;
+ border-radius: 50%;
+ .icon {
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ }
+}
diff --git a/web/src/component/ErrorCompToolTip/index.tsx b/web/src/component/ErrorCompToolTip/index.tsx
new file mode 100644
index 0000000..be8a7be
--- /dev/null
+++ b/web/src/component/ErrorCompToolTip/index.tsx
@@ -0,0 +1,19 @@
+import { Tooltip } from "antd";
+import { CloseOutlined } from '@ant-design/icons';
+
+import styles from './index.less'
+
+interface ErrorCompToolTipProps {
+ title:string,
+ status:'warning' | 'error'
+}
+
+export default function ErrorCompToolTip({title,status}:ErrorCompToolTipProps) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/ExitBtn.tsx b/web/src/component/ExitBtn.tsx
new file mode 100644
index 0000000..a065c63
--- /dev/null
+++ b/web/src/component/ExitBtn.tsx
@@ -0,0 +1,88 @@
+import { intl } from '@/utils/intl';
+import { Button } from 'antd';
+import { useRequest, history } from 'umi';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { Modal } from '@oceanbase/design';
+import { errorHandler } from '@/utils';
+import { useModel } from 'umi';
+import * as Process from '@/services/ocp_installer_backend/Process';
+import { PathType } from '@/pages/type';
+import { getTailPath } from '@/utils/helper';
+
+export default function ExitBtn() {
+ const { setInstallStatus, setInstallResult } = useModel('ocpInstallData');
+ const path: PathType = getTailPath() as PathType;
+ // 退出
+ const { run: suicide, loading: suicideLoading } = useRequest(
+ Process.suicide,
+ {
+ manual: true,
+ onSuccess: () => {
+ if (path === 'configuration' || path === 'install') {
+ setInstallStatus('');
+ setInstallResult('');
+ }
+ history.push(`/quit?path=${path}`);
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ return (
+
+ );
+}
diff --git a/web/src/component/ExitPageWrapper/index.tsx b/web/src/component/ExitPageWrapper/index.tsx
new file mode 100644
index 0000000..21aa4c8
--- /dev/null
+++ b/web/src/component/ExitPageWrapper/index.tsx
@@ -0,0 +1,82 @@
+import { useEffect } from 'react';
+import { history } from 'umi';
+import { PathType } from '@/pages/type';
+import { TIME_REFRESH } from '@/pages/constants';
+
+export default function ExitPageWrapper(props: {
+ target?: PathType;
+ children: React.ReactNode;
+}) {
+ const { target } = props;
+ let path: PathType | undefined;
+ path = target
+ ? target
+ : //@ts-ignore
+ (history.location.query.path as PathType | undefined);
+ const PATH_HANDLES = [
+ {
+ paths: ['guide', 'ocpInstaller', 'update'],
+ exec(path: PathType) {
+ history.push(`/${path}`);
+ },
+ },
+ {
+ paths: ['configuration', 'install'],
+ exec(path: PathType) {
+ history.push(`/ocpinstaller/${path}`);
+ },
+ },
+ {
+ paths: ['obdeploy'],
+ exec(path: PathType) {
+ history.push('/');
+ },
+ },
+ ];
+ const setStorage = () => {
+ if (path) {
+ sessionStorage.setItem(
+ 'pathInfo',
+ JSON.stringify({
+ path,
+ timestamp: new Date().getTime(),
+ }),
+ );
+ }
+ };
+ const toTargetPage = (path: PathType) => {
+ sessionStorage.removeItem('pathInfo');
+ const targetHandler = PATH_HANDLES.find((item) =>
+ item.paths.includes(path),
+ );
+ if (targetHandler) {
+ targetHandler.exec(path);
+ }
+ };
+
+ useEffect(() => {
+ let sessionData;
+ try {
+ sessionData = JSON.parse(sessionStorage.getItem('pathInfo') as string);
+ } catch (e) {
+
+ } finally {
+ if (sessionData) {
+ const isRefresh =
+ new Date().getTime() - sessionData.timestamp <= TIME_REFRESH;
+ if (isRefresh) {
+ toTargetPage(sessionData.path);
+ }
+ }
+ }
+
+ window.addEventListener('beforeunload', setStorage);
+ return () => {
+ window.removeEventListener('beforeunload', setStorage);
+ };
+ }, []);
+
+ return
+ {props.children}
+
;
+}
diff --git a/web/src/component/FooterToolbar/index.less b/web/src/component/FooterToolbar/index.less
new file mode 100644
index 0000000..b475494
--- /dev/null
+++ b/web/src/component/FooterToolbar/index.less
@@ -0,0 +1,17 @@
+.container {
+ width: 100% !important;
+ height: 72px;
+ background-color: #F5F8FE;
+ padding: 0 10%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+
+}
+:global {
+ .ant-affix {
+ box-shadow: 0 -1px 0 0 #E8EAF3;
+ }
+}
+
+
diff --git a/web/src/component/FooterToolbar/index.tsx b/web/src/component/FooterToolbar/index.tsx
new file mode 100644
index 0000000..173d59f
--- /dev/null
+++ b/web/src/component/FooterToolbar/index.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { Affix } from '@oceanbase/design';
+import type { AffixProps } from '@oceanbase/design';
+
+import styles from './index.less';
+
+export interface BatchOperationBarProps extends AffixProps {
+ style?: React.CSSProperties;
+}
+
+const FooterToolbar: React.FC = ({ children, style, ...restProps }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default FooterToolbar;
diff --git a/web/src/component/Icon/ArrowIcon.tsx b/web/src/component/Icon/ArrowIcon.tsx
new file mode 100644
index 0000000..2707425
--- /dev/null
+++ b/web/src/component/Icon/ArrowIcon.tsx
@@ -0,0 +1,14 @@
+import type { ImgHTMLAttributes } from 'react';
+import React from 'react';
+
+interface ArrowIconProps extends ImgHTMLAttributes {
+ size?: number;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const ArrowIcon: React.FC = ({ size = 12, ...restProps }) => {
+ return ;
+};
+
+export default ArrowIcon;
diff --git a/web/src/component/Icon/ClusterIcon.tsx b/web/src/component/Icon/ClusterIcon.tsx
new file mode 100644
index 0000000..45c2959
--- /dev/null
+++ b/web/src/component/Icon/ClusterIcon.tsx
@@ -0,0 +1,14 @@
+import type { ImgHTMLAttributes } from 'react';
+import React from 'react';
+
+interface ClusterIconProps extends ImgHTMLAttributes {
+ size?: number;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const ClusterIcon: React.FC = ({ size = 12, ...restProps }) => {
+ return ;
+};
+
+export default ClusterIcon;
diff --git a/web/src/component/Icon/NewIcon.tsx b/web/src/component/Icon/NewIcon.tsx
new file mode 100644
index 0000000..30e7cde
--- /dev/null
+++ b/web/src/component/Icon/NewIcon.tsx
@@ -0,0 +1,14 @@
+import type { ImgHTMLAttributes } from 'react';
+import React from 'react';
+
+interface NewIconProps extends ImgHTMLAttributes {
+ size?: number;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const NewIcon: React.FC = ({ size = 12, ...restProps }) => {
+ return ;
+};
+
+export default NewIcon;
diff --git a/web/src/component/Icon/Prechecked.tsx b/web/src/component/Icon/Prechecked.tsx
new file mode 100644
index 0000000..6f894f9
--- /dev/null
+++ b/web/src/component/Icon/Prechecked.tsx
@@ -0,0 +1,14 @@
+import type { ImgHTMLAttributes } from 'react';
+import React from 'react';
+
+interface PrecheckedProps extends ImgHTMLAttributes {
+ size?: number;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const Prechecked: React.FC = ({ size = 12, ...restProps }) => {
+ return ;
+};
+
+export default Prechecked;
diff --git a/web/src/component/InputPort/index.tsx b/web/src/component/InputPort/index.tsx
new file mode 100644
index 0000000..a7019fe
--- /dev/null
+++ b/web/src/component/InputPort/index.tsx
@@ -0,0 +1,62 @@
+import { commonStyle } from '@/pages/constants';
+import { intl } from '@/utils/intl';
+import { ProFormDigit } from '@ant-design/pro-components';
+import { NamePath } from 'antd/es/form/interface';
+
+interface InputPortProps {
+ name: NamePath;
+ label: React.ReactNode;
+ fieldProps?: any;
+ message?: string;
+ limit?: boolean; //是否需要限制端口号范围
+}
+
+/**
+ * default port range 1024~65535
+ */
+export default function InputPort({
+ name,
+ label,
+ fieldProps,
+ message,
+ limit = true,
+}: InputPortProps) {
+ const rules: any = [
+ {
+ required: true,
+ message:
+ message ||
+ intl.formatMessage({
+ id: 'OBD.component.InputPort.PleaseEnter',
+ defaultMessage: '请输入',
+ }),
+ },
+ ];
+ if (limit) {
+ rules.push(() => ({
+ validator(_: any, value: number) {
+ if (value < 1024 || value > 65535) {
+ return Promise.reject(
+ intl.formatMessage({
+ id: 'OBD.component.InputPort.ThePortNumberCanOnly',
+ defaultMessage: '端口号只支持 1024~65535 范围',
+ }),
+ );
+ }
+ return Promise.resolve();
+ },
+ }));
+ }
+ return (
+
+ );
+}
diff --git a/web/src/component/InsstallResult/index.less b/web/src/component/InsstallResult/index.less
new file mode 100644
index 0000000..68463cb
--- /dev/null
+++ b/web/src/component/InsstallResult/index.less
@@ -0,0 +1,58 @@
+@cardBackgroundColor: #f8fafe;
+
+.versionContainer{
+ margin-bottom: 16px;
+
+ .ocpVersion {
+ float: left;
+ width: calc(50% - 66px);
+ padding: 21px 0;
+ color: #5c6b8a;
+ font-size: 16px;
+ text-align: center;
+ background-color: #fff;
+ border: 1px solid #cdd5e4;
+ border-radius: 8px;
+ span {
+ padding-right: 8px;
+ color: #132039;
+ font-weight: 500;
+ position: relative;
+ .newVersionIcon{
+ position: absolute;
+ right: -34px;
+ top: -20px;
+ }
+ }
+ }
+}
+
+.componentTable {
+ :global {
+ .ant-table-thead > tr > th {
+ background-color: #f5f8fe !important;
+ }
+ }
+}
+.componentCard {
+ margin-bottom: 16px !important;
+ background-color: @cardBackgroundColor !important;
+ :global {
+ .ant-pro-card-body {
+ padding: 0 !important;
+ }
+ }
+ &.disabledCard {
+ opacity: 0.4;
+ }
+ &:last-child {
+ margin-bottom: 0 !important;
+ }
+}
+.upgradeReport {
+ :global {
+ .ant-card-head {
+ border-bottom: none;
+ }
+ }
+}
diff --git a/web/src/component/InsstallResult/index.tsx b/web/src/component/InsstallResult/index.tsx
new file mode 100644
index 0000000..b2fe441
--- /dev/null
+++ b/web/src/component/InsstallResult/index.tsx
@@ -0,0 +1,559 @@
+import { intl } from '@/utils/intl';
+import React, { useEffect } from 'react';
+// import { useSelector } from 'umi';
+import {
+ Result,
+ Descriptions,
+ Card,
+ Space,
+ Table,
+ Typography,
+ Button,
+ Row,
+ Col,
+ Tag,
+} from '@oceanbase/design';
+import { ProCard } from '@ant-design/pro-components';
+import { useRequest } from 'ahooks';
+import { useModel } from 'umi';
+import { Alert } from 'antd';
+import { errorHandler } from '@/utils';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import type { ResultProps } from 'antd/es/result';
+import ArrowIcon from '@/component/Icon/ArrowIcon';
+import NewIcon from '@/component/Icon/NewIcon';
+import { copyText } from '@/utils';
+import styles from './index.less';
+
+const { Text } = Typography;
+
+export interface InsstallResultProps extends ResultProps {
+ upgradeOcpInfo?: API.connectMetaDB;
+ ocpInfo?: any;
+ installStatus?: string; // RUNNING, FINISHED
+ installResult?: string; // SUCCESSFUL, FAILED
+ taskId?: number;
+ installType?: string;
+ type?: string; // install update
+}
+
+const InsstallResult: React.FC = ({
+ ocpInfo,
+ upgradeOcpInfo,
+ installStatus,
+ installResult,
+ type,
+ installType,
+ ...restProps
+}) => {
+ let isHaveMetadb;
+ const { ocpConfigData } = useModel('global');
+ const version: string = ocpConfigData?.components?.ocpserver?.version;
+ // 获取 升级主机列表
+ const { data: upgraadeAgentHosts, run: getOcpNotUpgradingHost } = useRequest(
+ OCP.getOcpNotUpgradingHost,
+ {
+ manual: true,
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ const upgraadeHosts = upgraadeAgentHosts?.data || {};
+
+ useEffect(() => {
+ if (
+ type === 'update' &&
+ installStatus === 'FINISHED' &&
+ installResult === 'SUCCESSFUL'
+ ) {
+ getOcpNotUpgradingHost();
+ }
+ }, [type, installStatus, installResult]);
+
+ const columns = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.InsstallResult.ComponentName',
+ defaultMessage: '组件名称',
+ }),
+ dataIndex: 'name',
+ width: '20%',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.InsstallResult.NodeIp',
+ defaultMessage: '节点 IP',
+ }),
+ dataIndex: 'ip',
+ render: (ip: string[]) => {
+ if (!ip || !ip.length) {
+ return '-';
+ }
+ return ip.map((item: string) => {item});
+ },
+ },
+ ];
+
+ return (
+
+
+ }
+ title={
+ installResult === 'SUCCESSFUL' ? (
+
+ {type === 'update' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.OcpUpgradedSuccessfully',
+ defaultMessage: 'OCP 升级成功',
+ })}
+
+ ) : (
+
+ {installType === 'OCP' ? (
+ <>
+ {isHaveMetadb === 'install' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.OcpDeployedSuccessfully',
+ defaultMessage: 'OCP 部署成功',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.OcpDeployedSuccessfully',
+ defaultMessage: 'OCP 部署成功',
+ })}
+
+ )}
+ >
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.MetadbDeployedSuccessfully',
+ defaultMessage: 'MetaDB 部署成功',
+ })}
+
+ )}
+
+ )}
+
+ ) : (
+ <>
+ {type === 'update' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.OcpUpgradeFailed',
+ defaultMessage: 'OCP 升级失败',
+ })}
+
+ ) : (
+
+ {installType === 'OCP' ? (
+ <>
+ {isHaveMetadb === 'install' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.OcpDeploymentFailed',
+ defaultMessage: 'OCP 部署失败',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.OcpDeploymentFailed',
+ defaultMessage: 'OCP 部署失败',
+ })}
+
+ )}
+ >
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.MetadbDeploymentFailed',
+ defaultMessage: 'MetaDB 部署失败',
+ })}
+
+ )}
+
+ )}
+ >
+ )
+ }
+ subTitle={
+ installResult === 'FAILED' &&
+ installStatus === 'FINISHED' &&
+ intl.formatMessage({
+ id: 'OBD.component.InsstallResult.PleaseCheckTheLogInformation',
+ defaultMessage: '请查看日志信息获取失败原因,联系技术支持同学处理',
+ })
+ }
+ {...restProps}
+ />
+
+ {installStatus === 'FINISHED' && (
+ <>
+ {installStatus === 'FINISHED' && installResult === 'SUCCESSFUL' ? (
+ <>
+ {type === 'update' ? (
+
+ {upgraadeHosts?.address?.length > 0 && (
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.ThereAreHostsThatHave.1',
+ defaultMessage:
+ '·存在未升级 OCP Agent 的主机,建议您在 OCP\n 平台「主机管理」模块安装新版本 OCP Agent。',
+ })}
+
+
+ {' '}
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.HostNotUpgraded',
+ defaultMessage: '未升级主机:',
+ })}
+
+ {upgraadeHosts?.address?.join()}
+
+
+
+
+ }
+ />
+ )}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.PreUpgradeVersion',
+ defaultMessage: '升级前版本:',
+ })}
+ V {upgradeOcpInfo?.ocp_version}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.UpgradedVersion',
+ defaultMessage: '升级后版本:',
+ })}
+
+ V {version}{' '}
+
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.Click',
+ defaultMessage: '点击',
+ })}
+
+
+ {' '}
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.OcpReleaseRecords',
+ defaultMessage: 'OCP 发布记录',
+ })}{' '}
+
+ {intl.formatMessage({
+ id: 'OBD.component.InsstallResult.LearnMoreAboutTheNew',
+ defaultMessage: '了解新版本更多信息',
+ })}
+
+
+ ) : (
+ <>
+ {installType === 'OCP' && (
+
+
+
+
+
+ }
+ />
+
+
+
+ {ocpInfo?.url?.join(',') || '-'}
+
+
+ {ocpInfo?.account || '-'}
+
+
+ {ocpInfo?.password ? (
+
+ {ocpInfo?.password}
+
+ ) : (
+ '-'
+ )}
+
+
+
+ )}
+ >
+ )}
+ >
+ ) : null}
+ >
+ )}
+
+ );
+};
+
+export default InsstallResult;
diff --git a/web/src/component/InstallProcess/index.less b/web/src/component/InstallProcess/index.less
new file mode 100644
index 0000000..d805fb2
--- /dev/null
+++ b/web/src/component/InstallProcess/index.less
@@ -0,0 +1,92 @@
+.progressEffectContainer {
+ position: relative;
+ height: 185px;
+ .computer {
+ position: absolute;
+ top: 55px;
+ left: 16px;
+ .computerAnimate {
+ width: 150px;
+ }
+ }
+ .progress {
+ position: absolute;
+ top: 97px;
+ left: 153px;
+ .progressAnimate {
+ width: 320px;
+ }
+ .progressVedio {
+ width: 285px;
+ height: 11px;
+ background-color: #fff;
+ :global {
+ .vjs-text-track-display {
+ background-color: #fff !important;
+ }
+ .vjs-poster {
+ top: 1px !important;
+ left: 1px !important;
+ background-position: 0 0 !important;
+ background-size: 100% !important;
+ }
+ .vjs-modal-dialog {
+ background: #fff !important;
+ }
+ .vjs-error-display:before {
+ content: '' !important;
+ }
+ }
+ }
+ }
+ .spaceman {
+ position: absolute;
+ top: 57px;
+ left: 133px;
+ .spacemanAnimate {
+ width: 320px;
+ }
+ }
+ .database {
+ position: absolute;
+ top: 55px;
+ right: 36px;
+ .sqlAnimate {
+ width: 150px;
+ }
+ }
+}
+.deploymentTitle {
+ position: absolute;
+ top: 32px;
+ left: 50%;
+ color: #132039;
+ font-weight: 500;
+ font-size: 16px;
+ transform: translateX(-50%);
+}
+
+.deploymentName {
+ position: absolute;
+ bottom: 16px;
+ left: 50%;
+ color: #8592ad;
+ font-size: 12px;
+ transform: translateX(-50%);
+}
+
+.installSubCard {
+ padding: 0 24px;
+ background-color: #fff;
+ :global {
+ .ant-card-head {
+ padding: 0;
+ border-bottom: none;
+ }
+ }
+ .installLog {
+ margin: 0 !important;
+ overflow: auto;
+ color: #8592ad;
+ }
+}
diff --git a/web/src/component/InstallProcess/index.tsx b/web/src/component/InstallProcess/index.tsx
new file mode 100644
index 0000000..3c2caec
--- /dev/null
+++ b/web/src/component/InstallProcess/index.tsx
@@ -0,0 +1,462 @@
+import { intl } from '@/utils/intl';
+import React, { useEffect, useState } from 'react';
+import { Card, Row, Col, Spin, Space } from '@oceanbase/design';
+import { CaretRightOutlined, CaretDownOutlined } from '@ant-design/icons';
+import { useRequest } from 'ahooks';
+import * as Metadb from '@/services/ocp_installer_backend/Metadb';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import { errorHandler } from '@/utils';
+import lottie from 'lottie-web';
+import videojs from 'video.js';
+import NP from 'number-precision';
+import 'video.js/dist/video-js.css';
+import { getLocale } from 'umi';
+import styles from './index.less';
+import InsstallResult from '@/component/InsstallResult';
+
+export interface InstallProcessProps {
+ id?: number;
+ type?: 'install' | 'update';
+ isReinstall?: boolean;
+ installType: string;
+ ocpInfo?: any;
+ installInfo?: any;
+ upgradeOcpInfo?: any;
+ onSuccess?: () => void;
+ cluster_name?: string;
+ installStatus: string;
+ setInstallStatus: React.Dispatch>;
+ setInstallResult: React.Dispatch>;
+}
+
+let timerLogScroll: NodeJS.Timer;
+let timerProgress: NodeJS.Timer;
+// const locale = getLocale();
+// const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+const InstallProcess: React.FC = ({
+ id,
+ type,
+ ocpInfo,
+ isReinstall,
+ installType,
+ installInfo,
+ upgradeOcpInfo,
+ cluster_name,
+ installStatus,
+ setInstallStatus,
+ setInstallResult,
+}) => {
+ const [toBottom, setToBottom] = useState(true);
+ const [progress, setProgress] = useState(0);
+ const [showProgress, setShowProgress] = useState(0);
+ const [lottieProgress, setlottieProgress] = useState();
+ const [openLog, setOpenLog] = useState(false);
+ const progressCoverInitWidth = 282;
+ let Video: any;
+ const [progressCoverStyle, setProgreddCoverStyle] = useState({
+ width: progressCoverInitWidth,
+ background: '#fff',
+ borderRadius: '5px',
+ });
+
+ // 安装与升级
+ const getInstallTaskFn =
+ installType === 'OCP'
+ ? type === 'update'
+ ? OCP.getOcpUpgradeTask
+ : OCP.getOcpInstallTask
+ : Metadb.getMetadbInstallTask;
+ const getInstallTaskLogFn =
+ installType === 'OCP'
+ ? type === 'update'
+ ? OCP.getOcpUpgradeTaskLog
+ : OCP.getOcpInstallTaskLog
+ : Metadb.getMetadbInstallTaskLog;
+ // 重装
+ const getReinstallTaskFn =
+ installType === 'OCP'
+ ? OCP.getOcpReinstallTask
+ : Metadb.getMetadbReinstallTask;
+ const getreInstallTaskLogFn =
+ installType === 'OCP'
+ ? OCP.getOcpReinstallTaskLog
+ : Metadb.getMetadbReinstallTaskLog;
+
+ const getTaskFn = isReinstall ? getReinstallTaskFn : OCP.getOcpUpgradeTask;
+ const getTaskLogFn = isReinstall
+ ? getreInstallTaskLogFn
+ : getInstallTaskLogFn;
+
+ const { run: getInstallTask, data: installResultData } = useRequest(
+ getTaskFn,
+ {
+ manual:true,
+ onSuccess: ({ success, data }) => {
+ if (success) {
+ setOpenLog(data?.result === 'FAILED' || data?.result === 'RUNNING');
+ setInstallStatus(data?.status);
+ setInstallResult(data?.result);
+ clearInterval(timerProgress);
+
+ if (data?.status && data?.status === 'RUNNING') {
+ setTimeout(() => {
+ if (type === 'update') {
+ getInstallTask({
+ cluster_name,
+ task_id: installInfo?.id,
+ });
+ }
+ }, 2000);
+ }
+ // if (data.result === 'SUCCESSFUL' || data.result === 'SUCCESSFUL' || data?.status === 'FINISHED') {
+ // lottie.stop()
+ // }
+ const finished = data?.info?.filter(
+ (item) =>
+ item.status === 'FINISHED' && item.result === 'SUCCESSFUL',
+ ).length;
+
+ const newProgress = Number(
+ NP.divide(finished, data?.info?.length).toFixed(2),
+ );
+
+ setProgress(newProgress);
+ const step = NP.minus(newProgress, progress);
+ let stepNum = 1;
+ timerProgress = setInterval(() => {
+ const currentProgressNumber = NP.plus(
+ progress,
+ NP.times(NP.divide(step, 100), stepNum),
+ );
+ if (currentProgressNumber >= 1) {
+ clearInterval(timerProgress);
+ } else {
+ stepNum += 1;
+ setShowProgress(currentProgressNumber);
+ }
+ }, 10);
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ setInstallStatus('FINISHED');
+ setInstallResult('FAILED');
+ },
+ },
+ );
+
+ const installResult = installResultData?.data || {};
+
+ const { run: getInstallTaskLog, data: installLogData } = useRequest(
+ getTaskLogFn,
+ {
+ manual:true,
+ onSuccess: ({ success }: API.OBResponseInstallLog_) => {
+ if (success && installStatus === 'RUNNING') {
+ setTimeout(() => {
+ getInstallTaskLog({
+ cluster_name,
+ task_id: installInfo?.id,
+ });
+ }, 2000);
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ setInstallStatus('FINISHED');
+ setInstallResult('FAILED');
+ },
+ },
+ );
+
+ const installLog = installLogData?.data || {};
+
+ const toLogBottom = () => {
+ const log = document.getElementById('installLog');
+ if (log) {
+ log.scrollTop = log.scrollHeight;
+ }
+ };
+
+ const handleScroll = (e?: any) => {
+ e = e || window.event;
+ const dom = e.target;
+ if (dom.scrollTop + dom.clientHeight === dom.scrollHeight) {
+ setToBottom(true);
+ } else {
+ setToBottom(false);
+ }
+ };
+ const getAnimate = () => {
+ const computerAnimate = document.querySelector('.computer-animate');
+ const progressAnimate = document.querySelector('.progress-animate');
+ const spacemanAnimate = document.querySelector('.spaceman-animate');
+ const sqlAnimate = document.querySelector('.database-animate');
+
+ if (progressAnimate) {
+ Video = videojs(progressAnimate, {
+ controls: false,
+ autoplay: true,
+ loop: true,
+ preload: 'auto',
+ });
+ }
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: computerAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/computer/data.json',
+ });
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: spacemanAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/spaceman/data.json',
+ });
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: sqlAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/database/data.json',
+ });
+ };
+
+ useEffect(() => {
+ if (installInfo.id) {
+ if (type === 'update') {
+ setInstallResult('RUNNING');
+ setInstallStatus('RUNNING');
+ getInstallTask({
+ cluster_name,
+ task_id: installInfo?.id,
+ });
+ getInstallTaskLog({
+ cluster_name,
+ task_id: installInfo?.id,
+ });
+ }
+ }
+ }, [installInfo, isReinstall]);
+
+ useEffect(() => {
+ // lottie.play();
+ getAnimate();
+ const log = document.querySelector('#installLog');
+ log.addEventListener('scroll', handleScroll);
+
+ return () => {
+ log.removeEventListener('DOMMouseScroll', handleScroll);
+ clearInterval(timerLogScroll);
+ clearInterval(timerProgress);
+ Video.dispose();
+ };
+ }, [id, installInfo?.id]);
+
+ useEffect(() => {
+ if (toBottom) {
+ toLogBottom();
+ timerLogScroll = setInterval(() => toLogBottom());
+ } else {
+ clearInterval(timerLogScroll);
+ }
+ }, [toBottom]);
+
+ useEffect(() => {
+ let newCoverStyle: any = { ...progressCoverStyle };
+ const newCoverWidth = NP.times(
+ NP.minus(1, showProgress),
+ progressCoverInitWidth,
+ );
+
+ if (showProgress > 0) {
+ newCoverStyle = {
+ width: `${newCoverWidth}px`,
+ background:
+ 'linear-gradient( to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 10px, rgba(255, 255, 255, 1) )',
+ };
+ }
+ setProgreddCoverStyle(newCoverStyle);
+ }, [showProgress]);
+
+ useEffect(() => {
+ if (lottieProgress) {
+ lottieProgress.goToAndStop(
+ NP.times(showProgress, lottieProgress.totalFrames - 1),
+ true,
+ );
+ }
+ }, [lottieProgress, showProgress]);
+
+ return (
+
+
+
+ {installStatus === 'RUNNING' ? (
+
+
+
+ {installType}{' '}
+ {type === 'update'
+ ? intl.formatMessage({
+ id: 'OBD.component.InstallProcess.Upgraded',
+ defaultMessage: '升级中',
+ })
+ : intl.formatMessage({
+ id: 'OBD.component.InstallProcess.Deploying',
+ defaultMessage: '部署中',
+ })}
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallProcess.Deploying.1',
+ defaultMessage: '正在部署',
+ })}
+ {installResult?.current}
+
+
+ ) : (
+
+ )}
+
+
+
+ {type === 'update'
+ ? intl.formatMessage({
+ id: 'OBD.component.InstallProcess.UpgradeLogs',
+ defaultMessage: '升级日志',
+ })
+ : intl.formatMessage({
+ id: 'OBD.component.InstallProcess.DeploymentLogs',
+ defaultMessage: '部署日志',
+ })}
+ {
+ setOpenLog(!openLog);
+ }}
+ >
+ {openLog ? : }
+
+
+ }
+ bodyStyle={{
+ padding: 24,
+ }}
+ className={`${styles.installSubCard} resource-card card-background-color`}
+ style={{
+ background: installStatus === 'RUNNING' ? '#F8FAFE' : '#fff',
+ }}
+ >
+
+ {openLog && (
+ <>
+ {installLog?.log}
+ {installStatus === 'RUNNING' ? (
+
+ ) : null}
+
+
+
+
+ >
+ )}
+
+
+
+
+
+ );
+};
+
+export default InstallProcess;
diff --git a/web/src/component/InstallProcessNew/index.tsx b/web/src/component/InstallProcessNew/index.tsx
new file mode 100644
index 0000000..70805b8
--- /dev/null
+++ b/web/src/component/InstallProcessNew/index.tsx
@@ -0,0 +1,452 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import { ProCard } from '@ant-design/pro-components';
+import useCustomRequest from '@/utils/useRequest';
+import { useRequest } from 'ahooks';
+import {
+ queryInstallStatus,
+ queryInstallLog,
+} from '@/services/ob-deploy-web/Deployments';
+import { getErrorInfo } from '@/utils';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import lottie from 'lottie-web';
+import NP from 'number-precision';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import { getLocale } from 'umi';
+import CustomFooter from '../CustomFooter';
+import ExitBtn from '../ExitBtn';
+import EnStyles from '@/pages/Obdeploy/indexEn.less';
+import ZhStyles from '@/pages/Obdeploy/indexZh.less';
+
+interface InstallProcessNewProps {
+ current: number;
+ setCurrentStep: React.Dispatch>;
+ name?: string;
+ id?: number; //connectId
+ task_id?: number;
+ type?: 'install' | 'update';
+ installStatus: string;
+ setInstallStatus: React.Dispatch>;
+ cluster_name?: string;
+}
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+let timerLogScroll: NodeJS.Timer;
+let timerProgress: NodeJS.Timer;
+
+export default function InstallProcessNew({
+ setCurrentStep,
+ name,
+ current,
+ id,
+ task_id,
+ installStatus,
+ setInstallStatus,
+ type,
+ cluster_name,
+}: InstallProcessNewProps) {
+ const { setErrorVisible, setErrorsList, errorsList } = useModel('global');
+ const { setInstallResult, isReinstall, logData, setLogData } =
+ useModel('ocpInstallData');
+ const progressCoverInitWidth = 282;
+ const [toBottom, setToBottom] = useState(true);
+ const [progress, setProgress] = useState(0);
+ const [showProgress, setShowProgress] = useState(0);
+ const [progressCoverStyle, setProgreddCoverStyle] = useState({
+ width: progressCoverInitWidth,
+ background: '#fff',
+ borderRadius: '5px',
+ });
+ const [currentPage, setCurrentPage] = useState(true);
+ const [statusData, setStatusData] = useState({});
+ const [opcStatusData, setOpcStatusData] = useState({});
+ let Video: any;
+
+ const getInstallTaskFn =
+ type === 'update' ? OCP.getOcpUpgradeTask : OCP.getOcpInstallTask;
+ const getInstallTaskLogFn =
+ type === 'update' ? OCP.getOcpUpgradeTaskLog : OCP.getOcpInstallTaskLog;
+ const getReinstallTaskFn = OCP.getOcpReinstallTask;
+ const getreInstallTaskLogFn = OCP.getOcpReinstallTaskLog;
+ const getTaskFn = isReinstall ? getReinstallTaskFn : getInstallTaskFn;
+ const getTaskLogFn = isReinstall
+ ? getreInstallTaskLogFn
+ : getInstallTaskLogFn;
+
+ const { run: fetchInstallStatus } = useCustomRequest(queryInstallStatus, {
+ onSuccess: ({ success, data }: API.OBResponseTaskInfo_) => {
+ if (success) {
+ setStatusData(data || {});
+ clearInterval(timerProgress);
+ if (data?.status !== 'RUNNING') {
+ setInstallStatus(data?.status);
+ setCurrentPage(false);
+ setTimeout(() => {
+ setCurrentStep(current + 1);
+ setErrorVisible(false);
+ setErrorsList([]);
+ }, 2000);
+ } else {
+ setTimeout(() => {
+ fetchInstallStatus({ name });
+ }, 2000);
+ }
+ const newProgress = NP.divide(data?.finished, data?.total).toFixed(2);
+ setProgress(newProgress);
+ let step = NP.minus(newProgress, progress);
+ let stepNum = 1;
+ timerProgress = setInterval(() => {
+ const currentProgressNumber = NP.plus(
+ progress,
+ NP.times(NP.divide(step, 100), stepNum),
+ );
+
+ if (currentProgressNumber >= 1) {
+ clearInterval(timerProgress);
+ } else {
+ stepNum += 1;
+ setShowProgress(currentProgressNumber);
+ }
+ }, 10);
+ }
+ },
+ onError: (e: any) => {
+ if (currentPage) {
+ setTimeout(() => {
+ fetchInstallStatus({ name });
+ }, 2000);
+ }
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const { run: handleInstallLog } = useCustomRequest(queryInstallLog, {
+ onSuccess: ({ success, data }: API.OBResponseInstallLog_) => {
+ if (success && installStatus === 'RUNNING') {
+ setLogData(data || {});
+ setTimeout(() => {
+ handleInstallLog({ name });
+ }, 2000);
+ }
+ },
+ onError: (e: any) => {
+ if (installStatus === 'RUNNING' && currentPage) {
+ setTimeout(() => {
+ handleInstallLog({ name });
+ }, 2000);
+ }
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+ // ocp
+ const { run: getInstallTask } = useRequest(getTaskFn, {
+ manual: true,
+ onSuccess: ({ success, data }) => {
+ if (success) {
+ setOpcStatusData(data || {});
+ clearInterval(timerProgress);
+ setInstallResult(data?.result);
+ if (data?.status && data?.status !== 'RUNNING') {
+ setInstallStatus(data?.status);
+ setCurrentPage(false);
+ setTimeout(() => {
+ setCurrentStep(current + 1);
+ setErrorVisible(false);
+ setErrorsList([]);
+ }, 2000);
+ } else {
+ setTimeout(() => {
+ getInstallTask({ id, task_id });
+ }, 2000);
+ }
+ const finished = data?.info?.filter(
+ (item) => item.status === 'FINISHED' && item.result === 'SUCCESSFUL',
+ ).length;
+ const newProgress = Number(
+ NP.divide(finished, data?.info?.length).toFixed(2),
+ );
+ setProgress(newProgress);
+ let step = NP.minus(newProgress, progress);
+ let stepNum = 1;
+ timerProgress = setInterval(() => {
+ const currentProgressNumber = NP.plus(
+ progress,
+ NP.times(NP.divide(step, 100), stepNum),
+ );
+
+ if (currentProgressNumber >= 1) {
+ clearInterval(timerProgress);
+ } else {
+ stepNum += 1;
+ setShowProgress(currentProgressNumber);
+ }
+ }, 10);
+ }
+ },
+ onError: (e: any) => {
+ if (currentPage) {
+ setTimeout(() => {
+ getInstallTask({ id, task_id });
+ }, 2000);
+ }
+ setInstallResult('FAILED');
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+ const { run: getInstallTaskLog } = useRequest(getTaskLogFn, {
+ manual: true,
+ onSuccess: ({ success, data }: API.OBResponseInstallLog_) => {
+ if (success) setLogData(data || {});
+ if (success && installStatus === 'RUNNING') {
+ setTimeout(() => {
+ if (type === 'update') {
+ getInstallTaskLog({ cluster_name, task_id });
+ } else {
+ getInstallTaskLog({ id, task_id });
+ }
+ }, 2000);
+ }
+ },
+ onError: (e: any) => {
+ if (installStatus === 'RUNNING' && currentPage) {
+ setTimeout(() => {
+ if (type === 'update') {
+ getInstallTaskLog({ cluster_name, task_id });
+ } else {
+ getInstallTaskLog({ id, task_id });
+ }
+ }, 2000);
+ }
+ setInstallResult('FAILED');
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const toLogBottom = () => {
+ const log = document.getElementById('installLog');
+ if (log) {
+ log.scrollTop = log.scrollHeight;
+ }
+ };
+
+ const handleScroll = (e?: any) => {
+ e = e || window.event;
+ const dom = e.target;
+ if (dom.scrollTop + dom.clientHeight === dom.scrollHeight) {
+ setToBottom(true);
+ } else {
+ setToBottom(false);
+ }
+ };
+
+ const getAnimate = () => {
+ const computerAnimate = document.querySelector('.computer-animate');
+ const progressAnimate = document.querySelector('.progress-animate');
+ const spacemanAnimate = document.querySelector('.spaceman-animate');
+ const sqlAnimate = document.querySelector('.database-animate');
+
+ if (progressAnimate) {
+ Video = videojs(progressAnimate, {
+ controls: false,
+ autoplay: true,
+ loop: true,
+ preload: 'auto',
+ });
+ }
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: computerAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/computer/data.json',
+ });
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: spacemanAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/spaceman/data.json',
+ });
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: sqlAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/database/data.json',
+ });
+ };
+
+ useEffect(() => {
+ if (name) {
+ fetchInstallStatus({ name });
+ handleInstallLog({ name });
+ } else if (id && task_id) {
+ setInstallResult('RUNNING');
+ setInstallStatus('RUNNING');
+ getInstallTask({ id, task_id });
+ if (type === 'update') {
+ getInstallTaskLog({ cluster_name, task_id });
+ } else {
+ getInstallTaskLog({ id, task_id });
+ }
+ }
+ }, [name, id, task_id]);
+
+ useEffect(() => {
+ getAnimate();
+ const log = document.querySelector('#installLog');
+ log.addEventListener('scroll', handleScroll);
+ return () => {
+ log.removeEventListener('DOMMouseScroll', handleScroll);
+ clearInterval(timerLogScroll);
+ clearInterval(timerProgress);
+ Video.dispose();
+ };
+ }, []);
+
+ useEffect(() => {
+ if (toBottom) {
+ toLogBottom();
+ timerLogScroll = setInterval(() => toLogBottom());
+ } else {
+ clearInterval(timerLogScroll);
+ }
+ }, [toBottom]);
+
+ useEffect(() => {
+ let newCoverStyle: any = { ...progressCoverStyle };
+ const newCoverWidth = NP.times(
+ NP.minus(1, showProgress),
+ progressCoverInitWidth,
+ );
+
+ if (showProgress > 0) {
+ newCoverStyle = {
+ width: `${newCoverWidth}px`,
+ background:
+ 'linear-gradient( to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 10px, rgba(255, 255, 255, 1) )',
+ };
+ }
+ setProgreddCoverStyle(newCoverStyle);
+ }, [showProgress]);
+
+ const getText = (name?: string) => {
+ return intl.formatMessage(
+ {
+ id: 'OBD.pages.components.InstallProcess.DeployingName',
+ defaultMessage: '正在部署 {name}',
+ },
+ { name: name },
+ );
+ };
+ return (
+
+
+
+
+ {type === 'update' ? (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.component.InstallProcessNew.Upgrading',
+ defaultMessage: '升级中...',
+ })}
+ >
+ ) : (
+ <>
+ {' '}
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallProcess.Deploying',
+ defaultMessage: '部署中...',
+ })}
+ >
+ )}
+
+
+
+
+
+
+
+ {getText(statusData?.current)}
+
+
+
+
+ {logData?.log}
+ {installStatus === 'RUNNING' ? (
+
+ ) : null}
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/InstallResult/index.less b/web/src/component/InstallResult/index.less
new file mode 100644
index 0000000..235a393
--- /dev/null
+++ b/web/src/component/InstallResult/index.less
@@ -0,0 +1,41 @@
+.ocpVersion {
+ float: left;
+ width: calc(50% - 66px);
+ padding: 21px 0;
+ color: #5c6b8a;
+ font-size: 16px;
+ text-align: center;
+ background-color: #fff;
+ border: 1px solid #cdd5e4;
+ border-radius: 8px;
+ span {
+ padding-right: 8px;
+ color: #132039;
+ font-weight: 500;
+ }
+ }
+
+ .upgradeReport {
+ :global {
+ .ant-card-head {
+ border-bottom: none;
+ }
+ }
+ }
+
+
+.installSubCard {
+ padding: 0 24px;
+ background-color: #fff;
+ :global {
+ .ant-card-head {
+ padding: 0;
+ border-bottom: none;
+ }
+ }
+ .installLog {
+ margin: 0 !important;
+ overflow: auto;
+ color: #8592ad;
+ }
+}
diff --git a/web/src/component/InstallResult/index.tsx b/web/src/component/InstallResult/index.tsx
new file mode 100644
index 0000000..ff36769
--- /dev/null
+++ b/web/src/component/InstallResult/index.tsx
@@ -0,0 +1,743 @@
+import { intl } from '@/utils/intl';
+import React, { useEffect, useState } from 'react';
+import {
+ Modal,
+ Result,
+ Descriptions,
+ Card,
+ Space,
+ Table,
+ Typography,
+ Button,
+ Row,
+ Col,
+ Tag,
+} from '@oceanbase/design';
+import { useRequest } from 'ahooks';
+import {
+ ExclamationCircleOutlined,
+ CaretRightOutlined,
+ CaretDownOutlined,
+} from '@ant-design/icons';
+import { Alert, Spin } from 'antd';
+import { history, useModel } from 'umi';
+import { errorHandler } from '@/utils';
+import * as Process from '@/services/ocp_installer_backend/Process';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import type { ResultProps } from 'antd/es/result';
+import ArrowIcon from '@/component/Icon/ArrowIcon';
+import NewIcon from '@/component/Icon/NewIcon';
+import { copyText } from '@/utils';
+import CustomFooter from '../CustomFooter';
+import ExitBtn from '../ExitBtn';
+import styles from './index.less';
+import { getTailPath } from '@/utils/helper';
+
+const { Text } = Typography;
+
+export interface InsstallResultProps extends ResultProps {
+ upgradeOcpInfo?: any;
+ ocpInfo?: any;
+ installStatus?: string; // RUNNING, FINISHED
+ installResult?: string; // SUCCESSFUL, FAILED
+ taskId?: number;
+ type?: string; // install update
+ current: number;
+ setCurrent: React.Dispatch>;
+}
+
+const InstallResult: React.FC = ({
+ ocpInfo,
+ upgradeOcpInfo,
+ installStatus,
+ installResult,
+ type,
+ setCurrent,
+ current,
+ ...restProps
+}) => {
+ let isHaveMetadb = 'install';
+ const isUpdate = getTailPath() === 'update';
+ // 获取 升级主机列表
+ const { data: upgraadeAgentHosts, run: getOcpNotUpgradingHost } = useRequest(
+ OCP.getOcpNotUpgradingHost,
+ {
+ manual: true,
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+ const { logData, setInstallStatus, setInstallResult } =
+ useModel('ocpInstallData');
+ const [openLog, setOpenLog] = useState(
+ installResult === 'FAILED' ? true : false,
+ );
+ const { setIsReinstall, connectId, setInstallTaskId } =
+ useModel('ocpInstallData');
+ const upgraadeHosts = upgraadeAgentHosts?.data || {};
+
+ // 重装ocp
+ const {
+ // data: reInstallOcpData,
+ run: reInstallOcp,
+ loading: reInstallOcpLoading,
+ } = useRequest(OCP.reinstallOcp, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success) {
+ res.data?.id && setInstallTaskId(res.data?.id);
+ setIsReinstall(true);
+ setInstallStatus('RUNNING');
+ setCurrent(current - 1);
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ // 退出
+ const { run: suicide, loading: suicideLoading } = useRequest(
+ Process.suicide,
+ {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success) {
+ setInstallStatus('');
+ setInstallResult('');
+ history.push(`/quit?type=${isUpdate ? 'update' : 'install'}`);
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ // 销毁 OCP 安装残留
+ const { run: destroyOcp, loading: destroyOCPLoading } = useRequest(
+ OCP.destroyOcp,
+ {
+ manual: true,
+ onSuccess: (data) => {
+ if (data?.success) {
+ setCurrent(3);
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ useEffect(() => {
+ if (
+ type === 'update' &&
+ installStatus === 'FINISHED' &&
+ installResult === 'SUCCESSFUL'
+ ) {
+ getOcpNotUpgradingHost();
+ }
+ }, [type, installStatus, installResult]);
+
+ const columns = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.InstallResult.ComponentName',
+ defaultMessage: '组件名称',
+ }),
+ dataIndex: 'name',
+ width: '20%',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.InstallResult.NodeIp',
+ defaultMessage: '节点 IP',
+ }),
+ dataIndex: 'ip',
+ render: (text, record) => {
+ if (!text || text === '') {
+ return '-';
+ }
+ const addressList = text.split(',');
+ return addressList.map((item: string) => {item});
+ },
+ },
+ ];
+
+ return (
+
+
+ }
+ title={
+ installResult === 'SUCCESSFUL' ? (
+
+ {type === 'update' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.OcpUpgradedSuccessfully',
+ defaultMessage: 'OCP 升级成功',
+ })}
+
+ ) : (
+
+ <>
+ {isHaveMetadb === 'install' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.OcpDeployedSuccessfully',
+ defaultMessage: 'OCP 部署成功',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.OcpDeployedSuccessfully',
+ defaultMessage: 'OCP 部署成功',
+ })}
+
+ )}
+ >
+
+ )}
+
+ ) : (
+ <>
+ {type === 'update' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.OcpUpgradeFailed',
+ defaultMessage: 'OCP 升级失败',
+ })}
+
+ ) : (
+
+ <>
+ {isHaveMetadb === 'install' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.OcpDeploymentFailed',
+ defaultMessage: 'OCP 部署失败',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.OcpDeploymentFailed',
+ defaultMessage: 'OCP 部署失败',
+ })}
+
+ )}
+ >
+
+ )}
+ >
+ )
+ }
+ subTitle={
+ installResult === 'FAILED' &&
+ installStatus === 'FINISHED' &&
+ intl.formatMessage({
+ id: 'OBD.component.InstallResult.PleaseCheckTheLogInformation',
+ defaultMessage: '请查看日志信息获取失败原因,联系技术支持同学处理',
+ })
+ }
+ {...restProps}
+ />
+
+
+ {installStatus === 'FINISHED' && installResult === 'SUCCESSFUL' ? (
+ <>
+ {type === 'update' ? (
+
+ {upgraadeHosts?.address?.length > 0 && (
+
+ {/* 备份文件保存地址:/abc/def/hijk,可根据需要对备份文件进行维护管理 */}
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.WeRecommendThatYouInstall',
+ defaultMessage:
+ '存在未升级 OCP Agent 的主机,建议您在 OCP 平台「主机管理」模块安装新版本 OCP Agent。',
+ })}
+
+ {upgraadeHosts?.address?.join()}
+
+ {/*
+ ·备份文件保存地址:/abc/def/hijk, 可根据需要对备份文件进行维护管理
+ */}
+
+ }
+ />
+ )}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.PreUpgradeVersion',
+ defaultMessage: '升级前版本:',
+ })}
+ V {upgradeOcpInfo?.ocp_version}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.UpgradedVersion',
+ defaultMessage: '升级后版本:',
+ })}
+
+ V {upgradeOcpInfo?.upgrade_version}
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.Click',
+ defaultMessage: '点击',
+ })}
+
+
+ {' '}
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.OcpReleaseRecords',
+ defaultMessage: 'OCP 发布记录',
+ })}{' '}
+
+ {intl.formatMessage({
+ id: 'OBD.component.InstallResult.LearnMoreAboutTheNew',
+ defaultMessage: '了解新版本更多信息',
+ })}
+
+
+ ) : (
+
+
+
+
+
+ }
+ />
+
+
+
+ {ocpInfo?.url?.join(',') || '-'}
+
+
+ {ocpInfo?.account || '-'}
+
+
+ {ocpInfo?.password ? (
+
+ {ocpInfo?.password}
+
+ ) : (
+ '-'
+ )}
+
+
+
+ )}
+ >
+ ) : null}
+
+
+
+ {type === 'update'
+ ? intl.formatMessage({
+ id: 'OBD.component.InstallResult.UpgradeLogs',
+ defaultMessage: '升级日志',
+ })
+ : intl.formatMessage({
+ id: 'OBD.component.InstallResult.DeploymentLogs',
+ defaultMessage: '部署日志',
+ })}
+ {
+ setOpenLog(!openLog);
+ }}
+ >
+ {openLog ? : }
+
+
+ }
+ bodyStyle={{
+ padding: 0,
+ }}
+ className={`${styles.installSubCard} resource-card card-background-color`}
+ style={{
+ background: installStatus === 'RUNNING' ? '#F8FAFE' : '#fff',
+ }}
+ >
+
+ {openLog && (
+ <>
+ {logData?.log}
+ {installStatus === 'RUNNING' ? (
+
+ ) : null}
+
+
+
+
+ >
+ )}
+
+
+
+
+ {installResult === 'SUCCESSFUL' ? (
+
+ ) : (
+ <>
+
+
+
+ >
+ )}
+
+
+ );
+};
+
+export default InstallResult;
diff --git a/web/src/component/MetaDBConfig/ClusterConfig.tsx b/web/src/component/MetaDBConfig/ClusterConfig.tsx
new file mode 100644
index 0000000..4cd7881
--- /dev/null
+++ b/web/src/component/MetaDBConfig/ClusterConfig.tsx
@@ -0,0 +1,337 @@
+import { intl } from '@/utils/intl';
+import { Space, Input, Button, Row } from 'antd';
+import { ProFormText, ProForm, ProFormDigit } from '@ant-design/pro-components';
+import { RightOutlined, DownOutlined } from '@ant-design/icons';
+import { useEffect, useState } from 'react';
+import { FormInstance } from 'antd/lib/form';
+import { useModel } from 'umi';
+
+import {
+ commonStyle,
+ componentsConfig,
+ componentVersionTypeToComponent,
+} from '@/pages/constants';
+import useRequest from '@/utils/useRequest';
+import { queryComponentParameters } from '@/services/ob-deploy-web/Components';
+import ConfigTable from '@/pages/Obdeploy/ClusterConfig/ConfigTable';
+import { showConfigKeys } from '@/constant/configuration';
+import Parameter from '@/pages/Obdeploy/ClusterConfig/Parameter';
+import InputPort from '../InputPort';
+import {
+ generateRandomPassword as generatePassword,
+ passwordRules,
+ getErrorInfo,
+} from '@/utils';
+import styles from './indexZh.less';
+import { oceanbaseAddonAfter } from '@/constant/configuration';
+
+export default function ClusterConfig({ form }: { form: FormInstance }) {
+ const [isShowMoreConfig, setIsShowMoreConfig] = useState(false);
+ const [clusterMoreLoading, setClusterMoreLoading] = useState(false);
+ const {
+ ocpClusterMore,
+ setOcpClusterMore,
+ ocpConfigData,
+ setOcpConfigData,
+ ocpClusterMoreConfig,
+ setOcpClusterMoreConfig,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+ const { components = {}, home_path } = ocpConfigData || {};
+ const { oceanbase = {} } = components;
+ const [rootPassword, setRootPassword] = useState(
+ oceanbase.root_password || '',
+ );
+ const { run: getMoreParamsters } = useRequest(queryComponentParameters);
+ const getInitialParameters = (
+ currentComponent: string,
+ dataSource: API.MoreParameter[],
+ data: API.NewParameterMeta[],
+ ) => {
+ const currentComponentNameConfig = data?.filter(
+ (item) => item.component === currentComponent,
+ )?.[0];
+ if (currentComponentNameConfig) {
+ const parameters: any = {};
+ currentComponentNameConfig.configParameter.forEach((item) => {
+ let parameter = {
+ ...item,
+ key: item.name,
+ params: {
+ value: item.default,
+ adaptive: item.auto,
+ auto: item.auto,
+ require: item.require,
+ type: item.type,
+ },
+ };
+ dataSource?.some((dataItem) => {
+ if (item.name === dataItem.key) {
+ parameter = {
+ key: dataItem.key,
+ description: parameter.description,
+ params: {
+ ...parameter.params,
+ ...dataItem,
+ },
+ };
+ return true;
+ }
+ return false;
+ });
+ if (
+ (parameter.params.type === 'CapacityMB' ||
+ parameter.params.type === 'Capacity') &&
+ parameter.params.value == '0'
+ ) {
+ parameter.params.value += 'GB';
+ }
+ parameters[item.name] = parameter;
+ });
+ return parameters;
+ } else {
+ return undefined;
+ }
+ };
+
+ const formatMoreConfig = (dataSource: API.ParameterMeta[]) => {
+ return dataSource.map((item) => {
+ const component = componentVersionTypeToComponent[item.component]
+ ? componentVersionTypeToComponent[item.component]
+ : item.component;
+ const componentConfig = componentsConfig[component];
+ // filter out existing parameters
+ let configParameter = item?.config_parameters.filter((parameter) => {
+ return !showConfigKeys?.[componentConfig.componentKey]?.includes(
+ parameter.name,
+ );
+ });
+ const newConfigParameter: API.NewConfigParameter[] = configParameter.map(
+ (parameterItem) => {
+ if(parameterItem.name === "cluster_id")parameterItem.default = '0'
+ return {
+ ...parameterItem,
+ parameterValue: {
+ value: parameterItem.default,
+ adaptive: parameterItem.auto,
+ auto: parameterItem.auto,
+ require: parameterItem.require,
+ },
+ };
+ },
+ );
+
+ const result: API.NewParameterMeta = {
+ ...item,
+ componentKey: componentConfig.componentKey,
+ label: componentConfig.labelName,
+ configParameter: newConfigParameter,
+ };
+ result.configParameter.forEach((item) => {
+ Object.assign(item.parameterValue, { type: item.type });
+ });
+ return result;
+ });
+ };
+ const getClusterMoreParamsters = async () => {
+ setClusterMoreLoading(true);
+ try {
+ const { success, data } = await getMoreParamsters(
+ {},
+ {
+ filters: [
+ {
+ component: oceanbase?.component,
+ version: oceanbase?.version,
+ is_essential_only: true,
+ },
+ ],
+ },
+ );
+ if (success) {
+ const newClusterMoreConfig = formatMoreConfig(data?.items);
+ setOcpClusterMoreConfig(newClusterMoreConfig);
+ form.setFieldsValue({
+ oceanbase: {
+ parameters: getInitialParameters(
+ oceanbase?.component,
+ oceanbase?.parameters,
+ newClusterMoreConfig,
+ ),
+ },
+ });
+ }
+ } catch (e: any) {
+ setOcpClusterMore(false);
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ setClusterMoreLoading(false);
+ };
+
+ const handleCluserMoreChange = () => {
+ setOcpClusterMore(!ocpClusterMore);
+ if (!ocpClusterMoreConfig?.length) {
+ getClusterMoreParamsters();
+ }
+ };
+
+ const setPassword = (password: string) => {
+ form.setFieldValue(['oceanbase', 'root_password'], password);
+ form.validateFields([['oceanbase', 'root_password']]);
+ setRootPassword(password);
+ };
+
+ const generateRandomPassword = () => {
+ const password = generatePassword();
+ setPassword(password);
+ };
+
+ useEffect(() => {
+ if (isShowMoreConfig) {
+ }
+ }, [isShowMoreConfig]);
+
+ return (
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.ClusterConfig.ClusterConfiguration',
+ defaultMessage: '集群配置',
+ })}
+
+
+
{
+ setPassword(e.target.value);
+ },
+ }}
+ placeholder={intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.ClusterConfig.PleaseEnter',
+ defaultMessage: '请输入',
+ })}
+ validateFirst
+ />
+
+
+
+
+
{oceanbaseAddonAfter},
+ style: { width: 552 },
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ handleCluserMoreChange()}
+ className={styles.moreConfigText}
+ >
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.ClusterConfig.MoreConfigurations',
+ defaultMessage: '更多配置',
+ })}
+
+ {!ocpClusterMore ? : }
+
+
+ }
+ />
+
+ );
+}
diff --git a/web/src/component/MetaDBConfig/ConfigTable.tsx b/web/src/component/MetaDBConfig/ConfigTable.tsx
new file mode 100644
index 0000000..eed2bf7
--- /dev/null
+++ b/web/src/component/MetaDBConfig/ConfigTable.tsx
@@ -0,0 +1,72 @@
+import { intl } from '@/utils/intl';
+import { Space } from 'antd';
+import { getLocale } from 'umi';
+
+import styles from './indexZh.less';
+interface ConfigTableProps {
+ dataSource: API.NewParameterMeta[];
+ loading: boolean;
+}
+const locale = getLocale();
+const getMoreColumns = () => {
+ const columns = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.ConfigTable.ClusterParameterName',
+ defaultMessage: '集群参数名称',
+ }),
+ dataIndex: 'name',
+ width: 250,
+ render: (text: string) => text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.ConfigTable.ParameterValue',
+ defaultMessage: '参数值',
+ }),
+ width: locale === 'zh-CN' ? 280 : 360,
+ dataIndex: 'parameterValue',
+ render: () => {
+ return (
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.ConfigTable.ParameterValue',
+ defaultMessage: '参数值',
+ })}
+
+ //
+ //
+ //
+ );
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.ConfigTable.Introduction',
+ defaultMessage: '介绍',
+ }),
+ dataIndex: 'description',
+ render: (text:string) =>
+ text ? {text}
: '-',
+ },
+ ];
+
+ return columns;
+};
+
+export default function ConfigTable() {
+ return (
+
+ aa
+
+ );
+}
diff --git a/web/src/component/MetaDBConfig/DataBaseNodeConfig.tsx b/web/src/component/MetaDBConfig/DataBaseNodeConfig.tsx
new file mode 100644
index 0000000..4d2427d
--- /dev/null
+++ b/web/src/component/MetaDBConfig/DataBaseNodeConfig.tsx
@@ -0,0 +1,436 @@
+import { intl } from '@/utils/intl';
+import { EditableProTable, ProForm, ProCard } from '@ant-design/pro-components';
+import { useRef, useState } from 'react';
+import type {
+ ProColumns,
+ EditableFormInstance,
+} from '@ant-design/pro-components';
+import { useModel } from 'umi';
+import { Tooltip, Popconfirm, message, Select } from 'antd';
+import { QuestionCircleOutlined, DeleteOutlined } from '@ant-design/icons';
+
+import ServerTags from '@/pages/Obdeploy/ServerTags';
+import styles from './indexZh.less';
+
+interface DataBaseNodeConfigProps {
+ tableFormRef: React.MutableRefObject<
+ EditableFormInstance | undefined
+ >;
+ dbConfigData: API.DBConfig[];
+ setDBConfigData: React.Dispatch>;
+}
+
+export default function DataBaseNodeConfig({
+ tableFormRef,
+ dbConfigData,
+ setDBConfigData,
+}: DataBaseNodeConfigProps) {
+ const { ocpConfigData, ocpNameIndex, setOcpNameIndex } = useModel('global');
+ const { components = {} } = ocpConfigData || {};
+ const { oceanbase = {} } = components;
+ const [editableForm] = ProForm.useForm();
+ const [allZoneOBServer, setAllZoneOBServer] = useState({});
+ const [allOBServer, setAllOBServer] = useState([]);
+ const [lastDeleteServer, setLastDeleteServer] = useState('');
+ const serverReg =
+ /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])?$/;
+
+ const [editableKeys, setEditableRowKeys] = useState(() =>
+ dbConfigData.map((item) => item.id),
+ );
+ const formatOptions = (data: string[]) =>
+ data?.map((item) => ({ label: item, value: item }));
+ const nameValidator = ({ field }: any, value: string) => {
+ const currentId = field.split('.')[0];
+ let validtor = true;
+ const reg = /^[a-zA-Z]([a-zA-Z0-9_]{0,30})[a-zA-Z0-9]$/;
+ if (value) {
+ if (reg.test(value)) {
+ dbConfigData.some((item) => {
+ if (currentId !== item.id && item.name === value) {
+ validtor = false;
+ return true;
+ }
+ return false;
+ });
+ } else {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.ItStartsWithALetter',
+ defaultMessage:
+ '以英文字母开头,英文或数字结尾,可包含英文数字和下划线且长度在 2-32 个字符之间',
+ }),
+ ),
+ );
+ }
+ }
+ if (validtor) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.ZoneNameAlreadyOccupied',
+ defaultMessage: 'Zone 名称已被占用',
+ }),
+ ),
+ );
+ };
+ const serversValidator = (_: any, value: string[], type: string) => {
+ let validtor = true;
+ if (value && value.length) {
+ value.some((item) => {
+ validtor = serverReg.test(item.trim());
+ return !serverReg.test(item.trim());
+ });
+ }
+ if (validtor) {
+ return Promise.resolve();
+ }
+ if (type === 'OBServer') {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.EnterTheCorrectIpAddress',
+ defaultMessage: '请输入正确的 IP 地址',
+ }),
+ ),
+ );
+ } else {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.SelectTheCorrectObproxyNode',
+ defaultMessage: '请选择正确的 OBProxy 节点',
+ }),
+ ),
+ );
+ }
+ };
+
+ const columns: ProColumns[] = [
+ {
+ title: (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.ZoneName',
+ defaultMessage: 'Zone 名称',
+ })}
+
+
+
+
+ >
+ ),
+
+ dataIndex: 'name',
+ width: 224,
+ formItemProps: {
+ rules: [
+ {
+ required: true,
+ whitespace: false,
+ message: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.ThisItemIsRequired',
+ defaultMessage: '此项是必填项',
+ }),
+ },
+ { validator: nameValidator },
+ ],
+ },
+ },
+ {
+ title: (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.ObserverNodes',
+ defaultMessage: 'OBServer 节点',
+ })}
+
+
+
+
+ >
+ ),
+
+ dataIndex: 'servers',
+ formItemProps: {
+ rules: [
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.ThisItemIsRequired',
+ defaultMessage: '此项是必填项',
+ }),
+ },
+ {
+ validator: (_: any, value: string[]) =>
+ serversValidator(_, value, 'OBServer'),
+ },
+ ],
+ },
+ renderFormItem: (_: any, { isEditable, record }: any) => {
+ return isEditable ? (
+
+ ) : null;
+ },
+ },
+ {
+ title: (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.RootserverNodes',
+ defaultMessage: 'RootServer 节点',
+ })}
+
+
+
+
+ >
+ ),
+
+ dataIndex: 'rootservice',
+ formItemProps: {
+ rules: [
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.ThisOptionIsRequired',
+ defaultMessage: '此项是必选项',
+ }),
+ },
+ {
+ pattern: serverReg,
+ message: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.SelectTheCorrectRootserverNode',
+ defaultMessage: '请选择正确的 RootServer 节点',
+ }),
+ },
+ ],
+ },
+ width: 224,
+ renderFormItem: (_: any, { isEditable, record }: any) => {
+ // rootservice options are items entered by the OBServer
+ const options = record?.servers ? formatOptions(record?.servers) : [];
+ return isEditable ? (
+
+ ) : null;
+ },
+ },
+ {
+ title: '',
+ valueType: 'option',
+ width: 20,
+ },
+ ];
+
+ const handleDelete = (id: string) => {
+ setDBConfigData(dbConfigData.filter((item) => item.id !== id));
+ };
+ return (
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.DatabaseNodeConfiguration',
+ defaultMessage: '数据库节点配置',
+ })}
+
+
+ bordered={false}
+ className={styles.nodeEditabletable}
+ columns={columns}
+ rowKey="id"
+ value={dbConfigData}
+ editableFormRef={tableFormRef}
+ onChange={setDBConfigData}
+ recordCreatorProps={{
+ newRecordType: 'dataSource',
+ record: () => ({
+ id: Date.now().toString(),
+ name: `zone${ocpNameIndex}`,
+ }),
+ onClick: () => setOcpNameIndex(ocpNameIndex + 1),
+ creatorButtonText: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.AddZone',
+ defaultMessage: '新增 Zone',
+ }),
+ }}
+ editable={{
+ type: 'multiple',
+ form: editableForm,
+ editableKeys,
+ actionRender: (row) => {
+ if (dbConfigData?.length === 1) {
+ return (
+
+
+
+
+
+ );
+ }
+ if (!row?.servers?.length && !row?.rootservice) {
+ return (
+ handleDelete(row.id)}
+ style={{ color: '#8592ad' }}
+ />
+ );
+ }
+ return (
+ handleDelete(row.id)}
+ >
+
+
+ );
+ },
+ onValuesChange: (editableItem, recordList) => {
+ if (!editableItem?.id) {
+ return;
+ }
+ const editorServers =
+ editableItem?.servers?.map((item) => item.trim()) || [];
+ const rootService = editableItem?.rootservice;
+ let newRootService = rootService;
+ const serversErrors = editableForm.getFieldError([
+ editableItem?.id,
+ 'servers',
+ ]);
+
+ if (editorServers.length) {
+ if (!rootService || !editorServers.includes(rootService)) {
+ newRootService = editorServers[0];
+ }
+ } else {
+ newRootService = undefined;
+ }
+ editableForm.setFieldsValue({
+ [editableItem?.id]: {
+ rootservice: newRootService,
+ },
+ });
+ if (!newRootService) {
+ tableFormRef?.current?.setFields([
+ {
+ name: [editableItem.id, 'rootservice'],
+ touched: false,
+ },
+ ]);
+ } else if (editorServers?.length === 1 && serversErrors.length) {
+ tableFormRef?.current?.setFields([
+ {
+ name: [editableItem.id, 'rootservice'],
+ errors: [
+ intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.SelectTheCorrectRootserverNode',
+ defaultMessage: '请选择正确的 RootServer 节点',
+ }),
+ ],
+ },
+ ]);
+ }
+
+ const beforeChangeServersLength =
+ allZoneOBServer[`${editableItem?.id}`]?.length || 0;
+ if (
+ editorServers &&
+ editorServers.length &&
+ editorServers.length > beforeChangeServersLength
+ ) {
+ if (
+ allOBServer.includes(editorServers[editorServers.length - 1])
+ ) {
+ message.warning(
+ intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.DataBaseNodeConfig.DoNotEnterDuplicateNodes',
+ defaultMessage: '禁止输入重复节点',
+ }),
+ );
+ const rawData = editorServers.slice(
+ 0,
+ editorServers.length - 1,
+ );
+
+ editableForm.setFieldsValue({
+ [editableItem?.id]: {
+ servers: rawData?.length ? rawData : undefined,
+ },
+ });
+ return;
+ }
+ const errors = editableForm.getFieldError([
+ editableItem?.id,
+ 'servers',
+ ]);
+
+ if (errors?.length) {
+ tableFormRef?.current?.setFields([
+ {
+ name: [editableItem.id, 'servers'],
+ errors: errors,
+ },
+ ]);
+ } else {
+ editableForm.setFieldsValue({
+ [editableItem?.id]: {
+ servers: editorServers,
+ },
+ });
+ }
+ }
+ const newRecordList = recordList.map((item) => {
+ if (item.id === editableItem.id) {
+ return {
+ ...editableItem,
+ rootservice: newRootService,
+ servers: editorServers,
+ };
+ }
+ return item;
+ });
+ setDBConfigData(newRecordList);
+ },
+ onChange: setEditableRowKeys,
+ }}
+ />
+
+ );
+}
diff --git a/web/src/component/MetaDBConfig/NodeConfig.tsx b/web/src/component/MetaDBConfig/NodeConfig.tsx
new file mode 100644
index 0000000..d275933
--- /dev/null
+++ b/web/src/component/MetaDBConfig/NodeConfig.tsx
@@ -0,0 +1,61 @@
+import { intl } from '@/utils/intl';
+import { ProForm, ProFormDigit } from '@ant-design/pro-components';
+import styles from './indexZh.less';
+import { Select, Row } from 'antd';
+import { ocpServersValidator } from '@/utils';
+import { useModel } from 'umi';
+import { FormInstance } from 'antd/lib/form';
+
+export default function NodeConfig({ form }: { form: FormInstance }) {
+ const { isSingleOcpNode, setIsSingleOcpNode } = useModel('ocpInstallData');
+
+ const selectChange = (value: string[]) => {
+ if (isSingleOcpNode === true && value.length > 1) {
+ setIsSingleOcpNode(false);
+ } else if (value.length === 1) {
+ setIsSingleOcpNode(true);
+ } else if (value.length === 0) {
+ setIsSingleOcpNode(undefined);
+ }
+ };
+
+ return (
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.NodeConfig.OcpNodeConfiguration',
+ defaultMessage: 'OCP 节点配置',
+ })}
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/MetaDBConfig/OBProxyConfig.tsx b/web/src/component/MetaDBConfig/OBProxyConfig.tsx
new file mode 100644
index 0000000..46c1d9c
--- /dev/null
+++ b/web/src/component/MetaDBConfig/OBProxyConfig.tsx
@@ -0,0 +1,261 @@
+import { intl } from '@/utils/intl';
+import { ProCard, ProForm, ProFormSelect } from '@ant-design/pro-components';
+import { useState } from 'react';
+import { RightOutlined, DownOutlined } from '@ant-design/icons';
+import { FormInstance } from 'antd/lib/form';
+import { Row, Input, Space } from 'antd';
+import { useModel } from 'umi';
+
+import useRequest from '@/utils/useRequest';
+import styles from './indexZh.less';
+import { getErrorInfo } from '@/utils';
+import Parameter from '@/pages/Obdeploy/ClusterConfig/Parameter';
+import { queryComponentParameters } from '@/services/ob-deploy-web/Components';
+import ConfigTable from '@/pages/Obdeploy/ClusterConfig/ConfigTable';
+import InputPort from '../InputPort';
+import { componentsConfig } from '@/pages/constants';
+import { ocpServersValidator } from '@/utils';
+import { showConfigKeys } from '@/constant/configuration';
+import { componentVersionTypeToComponent } from '@/pages/constants';
+import { obproxyAddonAfter } from '@/constant/configuration';
+export default function OBProxyConfig({ form }: { form: FormInstance }) {
+ const {
+ ocpConfigData,
+ proxyMoreConfig,
+ setProxyMoreConfig,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+ const { isShowMoreConfig, setIsShowMoreConfig } = useModel('ocpInstallData');
+ const { components = {} } = ocpConfigData || {};
+ const { obproxy = {} } = components;
+ const [proxyMoreLoading, setProxyMoreLoading] = useState(false);
+ const { run: getMoreParamsters } = useRequest(queryComponentParameters);
+ const getInitialParameters = (
+ currentComponent: string,
+ dataSource: API.MoreParameter[],
+ data: API.NewParameterMeta[],
+ ) => {
+ const currentComponentNameConfig = data?.filter(
+ (item) => item.component === currentComponent,
+ )?.[0];
+ if (currentComponentNameConfig) {
+ const parameters: any = {};
+ currentComponentNameConfig.configParameter.forEach((item) => {
+ let parameter = {
+ ...item,
+ key: item.name,
+ params: {
+ value: item.default,
+ adaptive: item.auto,
+ auto: item.auto,
+ require: item.require,
+ type: item.type,
+ },
+ };
+ dataSource?.some((dataItem) => {
+ if (item.name === dataItem.key) {
+ parameter = {
+ key: dataItem.key,
+ description: parameter.description,
+ params: {
+ ...parameter.params,
+ ...dataItem,
+ },
+ };
+ return true;
+ }
+ return false;
+ });
+ if (
+ (parameter.params.type === 'CapacityMB' ||
+ parameter.params.type === 'Capacity') &&
+ parameter.params.value == '0'
+ ) {
+ parameter.params.value += 'GB';
+ }
+ parameters[item.name] = parameter;
+ });
+ return parameters;
+ } else {
+ return undefined;
+ }
+ };
+
+ const formatMoreConfig = (dataSource: API.ParameterMeta[]) => {
+ return dataSource.map((item) => {
+ const component = componentVersionTypeToComponent[item.component]
+ ? componentVersionTypeToComponent[item.component]
+ : item.component;
+ const componentConfig = componentsConfig[component];
+ // filter out existing parameters
+ let configParameter = item?.config_parameters.filter((parameter) => {
+ return !showConfigKeys?.[componentConfig.componentKey]?.includes(
+ parameter.name,
+ );
+ });
+ const newConfigParameter: API.NewConfigParameter[] = configParameter.map(
+ (parameterItem) => {
+ return {
+ ...parameterItem,
+ parameterValue: {
+ value: parameterItem.default,
+ adaptive: parameterItem.auto,
+ auto: parameterItem.auto,
+ require: parameterItem.require,
+ },
+ };
+ },
+ );
+
+ const result: API.NewParameterMeta = {
+ ...item,
+ componentKey: componentConfig.componentKey,
+ label: componentConfig.labelName,
+ configParameter: newConfigParameter,
+ };
+ result.configParameter.forEach((item) => {
+ Object.assign(item.parameterValue, { type: item.type });
+ });
+ return result;
+ });
+ };
+ const getProxyMoreParamsters = async () => {
+ setProxyMoreLoading(true);
+ try {
+ const { success, data } = await getMoreParamsters(
+ {},
+ {
+ filters: [
+ {
+ component: obproxy?.component,
+ version: obproxy?.version,
+ is_essential_only: true,
+ },
+ ],
+ },
+ );
+ if (success) {
+ const newClusterMoreConfig = formatMoreConfig(data?.items);
+ setProxyMoreConfig(newClusterMoreConfig);
+ form.setFieldsValue({
+ obproxy: {
+ parameters: getInitialParameters(
+ obproxy?.component,
+ obproxy?.parameters,
+ newClusterMoreConfig,
+ ),
+ },
+ });
+ }
+ } catch (e: any) {
+ // aaa TypeError: Cannot read properties of undefined (reading 'setFieldsValue')
+ setIsShowMoreConfig(false);
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ setProxyMoreLoading(false);
+ };
+
+ const handleCluserMoreChange = () => {
+ setIsShowMoreConfig(!isShowMoreConfig);
+ if (!proxyMoreConfig?.length) {
+ getProxyMoreParamsters();
+ }
+ };
+ return (
+
+
+
+
+
+
+
+
+ {/*
+
+ */}
+
+
+
+ {obproxyAddonAfter}} />
+
+
+ handleCluserMoreChange()}
+ className={styles.moreConfigText}
+ >
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.OBProxyConfig.MoreConfigurations',
+ defaultMessage: '更多配置',
+ })}
+
+ {!isShowMoreConfig ? : }
+
+
+ }
+ showVisible={isShowMoreConfig}
+ loading={proxyMoreLoading}
+ />
+
+ );
+}
diff --git a/web/src/component/MetaDBConfig/UserConfig.tsx b/web/src/component/MetaDBConfig/UserConfig.tsx
new file mode 100644
index 0000000..9e95351
--- /dev/null
+++ b/web/src/component/MetaDBConfig/UserConfig.tsx
@@ -0,0 +1,278 @@
+import { intl } from '@/utils/intl';
+import { ProForm, ProFormDigit } from '@ant-design/pro-components';
+import { Input, Checkbox, Tooltip } from 'antd';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import type { FormInstance } from 'antd/lib/form';
+import { useModel } from 'umi';
+import type { CheckboxChangeEvent } from 'antd/es/checkbox';
+
+import { commonStyle } from '@/pages/constants';
+import { nameReg } from '@/utils';
+import styles from './indexZh.less';
+import { useState } from 'react';
+import { getTailPath } from '@/utils/helper';
+import { DOCS_USER } from '@/constant/docs';
+
+type UserInfoType = {
+ user?: string;
+ password?: string;
+ launch_user?: string;
+};
+
+export default function UserConfig({ form }: { form: FormInstance }) {
+ const { useRunningUser, setUseRunningUser, setUsername } =
+ useModel('ocpInstallData');
+ const { ocpConfigData = {} } = useModel('global');
+ const { auth = {}, launch_user = '' } = ocpConfigData;
+ const [userInfo, setUserInfo] = useState({
+ ...auth,
+ launch_user,
+ });
+ const isNewDB = getTailPath() === 'install';
+ const onChange = (e: CheckboxChangeEvent) => {
+ setUseRunningUser(e.target.checked);
+ if (e.target.checked) {
+ setUsername(form.getFieldValue('launch_user'));
+ } else {
+ if (form.getFieldValue('launch_user')) {
+ form.setFieldValue('launch_user', undefined);
+ setUserInfo({ ...userInfo, launch_user: undefined });
+ }
+ if (userInfo.user) {
+ if (isNewDB) {
+ form.setFieldValue(
+ ['obproxy', 'home_path'],
+ `/home/${userInfo.user}`,
+ );
+ form.setFieldValue(
+ ['oceanbase', 'home_path'],
+ `/home/${userInfo.user}`,
+ );
+ }
+ form.setFieldValue(
+ ['ocpserver', 'home_path'],
+ `/home/${userInfo.user}`,
+ );
+ form.setFieldValue(
+ ['ocpserver', 'log_dir'],
+ `/home/${userInfo.user}/logs`,
+ );
+ form.setFieldValue(
+ ['ocpserver', 'soft_dir'],
+ `/home/${userInfo.user}/software`,
+ );
+ }
+ setUsername(form.getFieldValue(['auth', 'user']));
+ }
+ };
+
+ const passwordChange = (e: any) => {
+ userInfo
+ ? setUserInfo({ ...userInfo, password: e.target.value })
+ : setUserInfo({ password: e.target.value });
+ form.setFieldValue(['auth', 'password'], e.target.value);
+ };
+ const userChange = (e: any) => {
+ userInfo
+ ? setUserInfo({ ...userInfo, user: e.target.value })
+ : setUserInfo({ user: e.target.value });
+ form.setFieldValue(['auth', 'user'], e.target.value);
+ if (!form.getFieldValue('launch_user')) {
+ let value = '';
+ if (e.target.value !== 'root') {
+ value = `/home/${e.target.value}`;
+ } else {
+ value = `/${e.target.value}`;
+ }
+ if (isNewDB) {
+ form.setFieldValue(['obproxy', 'home_path'], value);
+ form.setFieldValue(['oceanbase', 'home_path'], value);
+ }
+ form.setFieldValue(['ocpserver', 'home_path'], value);
+ form.setFieldValue(
+ ['ocpserver', 'log_dir'],
+ `/home/${e.target.value}/logs`,
+ );
+ form.setFieldValue(
+ ['ocpserver', 'soft_dir'],
+ `/home/${e.target.value}/software`,
+ );
+ }
+ setUsername(e.target.value);
+ };
+
+ const launchUserChange = (e: any) => {
+ userInfo
+ ? setUserInfo({ ...userInfo, launch_user: e.target.value })
+ : setUserInfo({ launch_user: e.target.value });
+ form.setFieldValue('launch_user', e.target.value);
+ if (isNewDB) {
+ form.setFieldValue(['obproxy', 'home_path'], `/home/${e.target.value}`);
+ form.setFieldValue(['oceanbase', 'home_path'], `/home/${e.target.value}`);
+ }
+ form.setFieldValue(['ocpserver', 'home_path'], `/home/${e.target.value}`);
+ form.setFieldValue(
+ ['ocpserver', 'log_dir'],
+ `/home/${e.target.value}/logs`,
+ );
+ form.setFieldValue(
+ ['ocpserver', 'soft_dir'],
+ `/home/${e.target.value}/software`,
+ );
+ setUsername(e.target.value);
+ };
+ return (
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.UserConfig.DeployUserConfiguration',
+ defaultMessage: '部署用户配置',
+ })}
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.UserConfig.PleaseProvideTheHostUser',
+ defaultMessage: '请提供主机用户名用以自动化配置平台专用操作系统用户',
+ })}
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.UserConfig.ViewHelpDocuments',
+ defaultMessage: '查看帮助文档',
+ })}
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.UserConfig.UseTheRunningUser',
+ defaultMessage: '使用运行用户',
+ })}
+
+ {useRunningUser && (
+
+ {intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.UserConfig.RunningUsername',
+ defaultMessage: '运行用户名',
+ })}
+
+
+
+ >
+ }
+ rules={
+ useRunningUser
+ ? [
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.UserConfig.EnterARunningUsername',
+ defaultMessage: '请输入运行用户名',
+ }),
+ },
+ {
+ pattern: nameReg,
+ message: intl.formatMessage({
+ id: 'OBD.component.MetaDBConfig.UserConfig.ItStartsWithALetter',
+ defaultMessage:
+ '以英文字母开头,可包含英文、数字、下划线和连字符,且不超过32位',
+ }),
+ },
+ ]
+ : []
+ }
+ >
+
+
+ )}
+
+ );
+}
diff --git a/web/src/component/MetaDBConfig/helper.ts b/web/src/component/MetaDBConfig/helper.ts
new file mode 100644
index 0000000..95edda5
--- /dev/null
+++ b/web/src/component/MetaDBConfig/helper.ts
@@ -0,0 +1,81 @@
+import { FormInstance } from 'antd';
+/**
+ *
+ * @param errorFields 待排序数组
+ * @param sortArr 字段顺序
+ * @returns
+ */
+const sortErrorFields = (errorFields: any, sortArr: string[]) => {
+ let res: any[] = [];
+ for (let name of sortArr) {
+ let target;
+ if (name === 'dbNode') {
+ target = errorFields.find(
+ (errorField: any) => !isNaN(parseFloat(errorField.name[0])),
+ );
+ } else {
+ target = errorFields.find(
+ (errorField: any) => errorField.name[0] === name,
+ );
+ }
+ if (target) res.push(target);
+ }
+ return res;
+};
+/**
+ * 滚动聚焦于失败项
+ */
+const formValidScorllHelper = (
+ result: [
+ PromiseSettledResult,
+ PromiseSettledResult,
+ ],
+ form: FormInstance,
+) => {
+ let errorFields: any;
+ const sortErrArr = [
+ 'auth',
+ 'launch_user',
+ 'ocpserver',
+ 'dbNode',
+ 'oceanbase',
+ 'obproxy',
+ ];
+ if (result[0].status === 'rejected' && result[1].status === 'rejected') {
+ errorFields = result[0].reason.errorFields.toSpliced(
+ 2,
+ 0,
+ ...result[1].reason.errorFields,
+ );
+ } else {
+ errorFields =
+ result[0].status === 'rejected'
+ ? result[0].reason.errorFields
+ : result[1].reason.errorFields;
+ }
+ //errorFields[0]应该是页面最上方的错误项 因此需要排序
+ const sortFields = sortErrorFields(errorFields, sortErrArr);
+ //数据库节点sortFields[0].name[0])为数字
+ if (!isNaN(parseFloat(sortFields[0].name[0]))) {
+ window.scrollTo(0, 500);
+ return;
+ }
+ form.scrollToField(sortFields[0].name, {
+ behavior: (actions) => {
+ actions.forEach(({ el, top, left }) => {
+ const META_SCROLLTOPS = {
+ auth: 0,
+ launch_user: 200,
+ ocpserver: 400,
+ oceanbase: 800,
+ obproxy: 2200,
+ };
+ const scrollTop = META_SCROLLTOPS[sortFields[0].name[0]];
+ el.scrollTop = scrollTop !== undefined ? scrollTop : top;
+ el.scrollLeft = left;
+ });
+ },
+ });
+};
+
+export { formValidScorllHelper, sortErrorFields };
diff --git a/web/src/component/MetaDBConfig/index.tsx b/web/src/component/MetaDBConfig/index.tsx
new file mode 100644
index 0000000..ac7be7b
--- /dev/null
+++ b/web/src/component/MetaDBConfig/index.tsx
@@ -0,0 +1,304 @@
+import { intl } from '@/utils/intl';
+import { Space, Button } from 'antd';
+import { ProCard, ProForm } from '@ant-design/pro-components';
+import { useModel } from 'umi';
+import { useRef, useState } from 'react';
+import type { EditableFormInstance } from '@ant-design/pro-components';
+
+import CustomFooter from '../CustomFooter';
+import OBProxyConfig from './OBProxyConfig';
+import ClusterConfig from './ClusterConfig';
+import UserConfig from './UserConfig';
+import NodeConfig from './NodeConfig';
+import DataBaseNodeConfig from './DataBaseNodeConfig';
+import { formValidScorllHelper } from './helper';
+import ExitBtn from '../ExitBtn';
+
+interface MetaDBConfig {
+ setCurrent: React.Dispatch>;
+ current: number;
+}
+
+interface FormValues extends API.Components {
+ auth?: {
+ user?: string;
+ password?: string;
+ port?: number;
+ };
+ ocpserver?: {
+ servers?: string[];
+ };
+ launch_user?: string;
+}
+
+export const addonAfter = '/oceanbase';
+
+export default function MetaDBConfig({ setCurrent, current }: MetaDBConfig) {
+ const {
+ ocpConfigData,
+ setOcpConfigData,
+ setErrorVisible,
+ setErrorsList,
+ ocpClusterMoreConfig,
+ proxyMoreConfig,
+ } = useModel('global');
+ const { components = {}, auth, launch_user } = ocpConfigData;
+ const { oceanbase = {}, ocpserver = {}, obproxy = {} } = components;
+ const initDBConfigData = oceanbase?.topology?.length
+ ? oceanbase?.topology?.map((item: API.Zone, index: number) => ({
+ id: (Date.now() + index).toString(),
+ ...item,
+ servers: item?.servers?.map((server) => server?.ip),
+ }))
+ : [
+ {
+ id: (Date.now() + 1).toString(),
+ name: 'zone1',
+ servers: [],
+ rootservice: undefined,
+ },
+ {
+ id: (Date.now() + 2).toString(),
+ name: 'zone2',
+ servers: [],
+ rootservice: undefined,
+ },
+ {
+ id: (Date.now() + 3).toString(),
+ name: 'zone3',
+ servers: [],
+ rootservice: undefined,
+ },
+ ];
+
+ const [dbConfigData, setDBConfigData] =
+ useState(initDBConfigData);
+ const tableFormRef = useRef>();
+ const formatParameters = (dataSource: any) => {
+ if (dataSource) {
+ const parameterKeys = Object.keys(dataSource);
+ return parameterKeys.map((key) => {
+ const { params, ...rest } = dataSource[key];
+ return {
+ key,
+ ...rest,
+ ...params,
+ };
+ });
+ } else {
+ return [];
+ }
+ };
+ const setData = (dataSource: FormValues) => {
+ let newAuth = { ...auth, ...dataSource.auth };
+ let newComponents: API.Components = { ...components };
+ dataSource.oceanbase.home_path;
+ newComponents.obproxy = {
+ ...(components.obproxy || {}),
+ ...dataSource.obproxy,
+ parameters: formatParameters(dataSource.obproxy?.parameters),
+ };
+ newComponents.ocpserver = {
+ ...(components.ocpserver || {}),
+ ...dataSource.ocpserver,
+ };
+ newComponents.oceanbase = {
+ ...(components.oceanbase || {}),
+ ...dataSource.oceanbase,
+ topology: dbConfigData?.map((item) => ({
+ ...item,
+ servers: item?.servers?.map((server) => ({ ip: server })),
+ })),
+ parameters: formatParameters(dataSource.oceanbase?.parameters),
+ };
+ let newConfigData = {
+ ...ocpConfigData,
+ components: newComponents,
+ auth: newAuth,
+ };
+ if (dataSource.launch_user) {
+ newConfigData.launch_user = dataSource.launch_user;
+ }
+ setOcpConfigData(newConfigData);
+ };
+ const prevStep = () => {
+ const formValues = form.getFieldsValue(true);
+ setData(formValues);
+ setErrorVisible(false);
+ setErrorsList([]);
+ setCurrent(current - 1);
+ };
+
+ const nextStep = () => {
+ const tableFormRefValidate = () => {
+ return tableFormRef?.current?.validateFields().then((values) => {
+ return values;
+ });
+ };
+
+ const formValidate = () => {
+ return form.validateFields().then((values) => {
+ return values;
+ });
+ };
+
+ Promise.allSettled([formValidate(), tableFormRefValidate()])
+ .then((result) => {
+ if (
+ result[0].status === 'rejected' ||
+ result[1].status === 'rejected'
+ ) {
+ formValidScorllHelper(result,form);
+ return;
+ }
+ const formValues = result[0].value;
+ setData(formValues);
+ setCurrent(current + 1);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ })
+ };
+
+ const [form] = ProForm.useForm();
+ const getInitialParameters = (
+ currentComponent: string,
+ dataSource: API.MoreParameter[],
+ data: API.NewParameterMeta[],
+ ) => {
+ const currentComponentNameConfig = data?.filter(
+ (item) => item.component === currentComponent,
+ )?.[0];
+ if (currentComponentNameConfig) {
+ const parameters: any = {};
+ currentComponentNameConfig.configParameter.forEach((item) => {
+ let parameter = {
+ ...item,
+ key: item.name,
+ params: {
+ value: item.default,
+ adaptive: item.auto,
+ auto: item.auto,
+ require: item.require,
+ type: item.type,
+ },
+ };
+ dataSource?.some((dataItem) => {
+ if (item.name === dataItem.key) {
+ parameter = {
+ key: dataItem.key,
+ description: parameter.description,
+ params: {
+ ...parameter.params,
+ ...dataItem,
+ },
+ };
+ return true;
+ }
+ return false;
+ });
+ if (
+ (parameter.params.type === 'CapacityMB' ||
+ parameter.params.type === 'Capacity') &&
+ parameter.params.value == '0'
+ ) {
+ parameter.params.value += 'GB';
+ }
+ parameters[item.name] = parameter;
+ });
+ return parameters;
+ } else {
+ return [];
+ }
+ };
+ const initialValues: FormValues = {
+ auth: {
+ user: auth?.user || undefined,
+ password: auth?.password || undefined,
+ port: auth?.port || 22,
+ },
+ ocpserver: {
+ servers: ocpserver?.servers?.length ? ocpserver?.servers : undefined,
+ },
+ oceanbase: {
+ root_password: oceanbase?.root_password || undefined,
+ data_dir: oceanbase?.data_dir || '/data/1',
+ redo_dir: oceanbase?.redo_dir || '/data/log1',
+ mysql_port: oceanbase?.mysql_port || 2881,
+ rpc_port: oceanbase?.rpc_port || 2882,
+ home_path: oceanbase?.home_path || '/home/admin',
+ parameters: getInitialParameters(
+ oceanbase?.component,
+ oceanbase?.parameters,
+ ocpClusterMoreConfig,
+ ),
+ },
+ obproxy: {
+ servers: obproxy?.servers?.length ? obproxy?.servers : undefined,
+ listen_port: obproxy?.listen_port || 2883,
+ prometheus_listen_port: obproxy?.prometheus_listen_port || 2884,
+ home_path: obproxy?.home_path || '/home',
+ parameters: getInitialParameters(
+ obproxy?.component,
+ obproxy?.parameters,
+ proxyMoreConfig,
+ ),
+ },
+ launch_user: launch_user || undefined,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/MetaDBConfig/indexEn.less b/web/src/component/MetaDBConfig/indexEn.less
new file mode 100644
index 0000000..e69de29
diff --git a/web/src/component/MetaDBConfig/indexZh.less b/web/src/component/MetaDBConfig/indexZh.less
new file mode 100644
index 0000000..690c8f3
--- /dev/null
+++ b/web/src/component/MetaDBConfig/indexZh.less
@@ -0,0 +1,85 @@
+@subTitleColor: #5c6b8a;
+.userConfigContainer {
+ :global {
+ .ant-form-item {
+ margin-bottom: 0px;
+ }
+ .ant-col {
+ padding: 0 !important;
+ }
+ }
+ .descText {
+ margin-top: 8px;
+ margin-bottom: 16px;
+ margin-left: 4px;
+ color: #8592ad;
+ font-weight: 400;
+ font-size: 14px;
+ line-height: 22px;
+ letter-spacing: 0;
+ }
+}
+
+.nodeEditabletable {
+ :global {
+ .ant-table,
+ .ant-table-wrapper .ant-table-thead > tr > th,
+ .ant-table-thead > tr > th {
+ background-color: rgba(0, 0, 0, 0) !important;
+ }
+ .ant-table {
+ color: #132039 !important;
+ }
+ .ant-table-thead > tr > th {
+ color: @subTitleColor !important;
+ font-weight: normal !important;
+ border: none;
+ }
+ .ant-table-thead > tr > th::before {
+ display: none;
+ }
+ .ant-table-tbody > tr > td,
+ .ant-table-thead > tr > th {
+ padding-left: 0 !important;
+ }
+ .ant-pro-card-body {
+ padding: 0;
+ }
+ }
+}
+
+.titleText {
+ color: #132039;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+ letter-spacing: 0;
+ text-align: left;
+}
+
+.passwordInput {
+ display: flex;
+ align-items: flex-end;
+ :global {
+ .ant-col-xs-24 {
+ flex: 0 0 0;
+ }
+ }
+}
+
+.moreConfigText {
+ color: #5c6b8a;
+ cursor: pointer;
+}
+.moreConfigText > span {
+ margin-right: 8px;
+}
+
+.clusterContainer {
+ margin-top: 24px;
+ :global {
+ .ant-col {
+ padding: 0 !important;
+ }
+ }
+}
diff --git a/web/src/component/ModifyOCPResourcePoolModal/index.tsx b/web/src/component/ModifyOCPResourcePoolModal/index.tsx
new file mode 100644
index 0000000..6b9251b
--- /dev/null
+++ b/web/src/component/ModifyOCPResourcePoolModal/index.tsx
@@ -0,0 +1,435 @@
+import { intl } from '@/utils/intl';
+import { Row, Col, Form, Modal, Card, Spin } from '@oceanbase/design';
+import React, { useEffect, useState } from 'react';
+// import { Pie } from '@alipay/ob-charts';
+import { useRequest } from 'ahooks';
+import { Alert } from 'antd';
+import { errorHandler } from '@/utils';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import SliderAndInputNumber from '@/component/SliderAndInputNumber';
+import { find } from 'lodash';
+
+export interface ModifyResourcePoolModalProps {
+ currentOcpDeploymentConfig?: any;
+ createOCPDeployment: (params) => void;
+ monitorDisplay?: boolean;
+ visible?: boolean;
+ loading?: boolean;
+ id: number;
+}
+
+const ModifyResourcePoolModal: React.FC = ({
+ currentOcpDeploymentConfig,
+ createOCPDeployment,
+ monitorDisplay,
+ loading,
+ visible,
+ id,
+ ...restProps
+}) => {
+ const [form] = Form.useForm();
+ const { validateFields, setFieldsValue } = form;
+
+ // OCP-SERVER
+ const [ocpCpuFree, setOcpCpuFree] = useState(1);
+ const [ocpMemoryFree, setOcpMemoryFree] = useState(1);
+
+ // 剩余cpu
+ const [cpu_Free, setCpu_Free] = useState(10);
+ // // 剩余内存
+ const [memory_Free, setMemory_Free] = useState(10);
+
+ // 其他已使用内存
+ const [otherUsedMemory, setOtherUsedMemory] = useState(1);
+
+ // 剩余cpu
+ const [cpuFree, setCpuFree] = useState(1);
+ // 剩余内存
+ const [memoryFree, setMemoryFree] = useState(1);
+
+ // OCP 租户
+ const [ocpTenantCpuFree, setOcpTenantCpuFree] = useState(2);
+ const [ocpTenantMemoryFree, setOcpTenantMemoryFree] = useState(4);
+
+ // 原信息租户
+ const [tenantCpuFree, setTenantCpuFree] = useState(2);
+ const [tenantMemoryFree, setTenantMemoryFree] = useState(4);
+
+ // 监控数据租户
+ const [monitorCpuFree, setMonitorCpuFree] = useState(4);
+ const [monitorMemory, setMonitorMemory] = useState(8);
+
+ // 查询主机的资源
+ const {
+ data: resourceData,
+ run: getOcpDeploymentResource,
+ loading: getOcpDeploymentResourceLoading,
+ } = useRequest(OCP.getOcpDeploymentResource, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res.success) {
+ const ocpDeploymentResource = res?.data || {};
+ const { cpu_free, memory_free, memory_total } =
+ ocpDeploymentResource?.metadb?.servers?.length > 0
+ ? ocpDeploymentResource?.metadb?.servers[0]
+ : {};
+
+ setCpu_Free(cpu_free);
+ setMemory_Free(memory_free);
+ setOcpCpuFree(cpu_free);
+ setOcpMemoryFree(memory_free > 5 ? 5 : 1);
+ setOtherUsedMemory(memory_total - memory_free);
+ setCpuFree(cpu_free > 7 ? cpu_free - 7 : cpu_free);
+ setMemoryFree(memory_free - (memory_free > 5 ? 13 : 14));
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+ const ocpResource = resourceData?.data;
+
+ useEffect(() => {
+ // 如果用户如有上次的手动输入,需要采用用户输入
+ if (currentOcpDeploymentConfig?.id) {
+ const { meta_tenant, monitor_tenant, parameters } =
+ currentOcpDeploymentConfig?.config;
+ setFieldsValue({
+ ocpMemory:
+ find(parameters, (item) => item?.name === 'memory_size')?.value || 1,
+ ocpCPU: find(parameters, (item) => item?.name === 'ocpCPU')?.value || 1,
+ tenantCPU: meta_tenant?.resource?.cpu || 2,
+ tenantMemory: meta_tenant?.resource?.memory || 3,
+ monitorCPU: monitor_tenant?.resource?.cpu || 4,
+ monitorMemory: monitor_tenant?.resource?.memory || 8,
+ });
+ }
+ }, [currentOcpDeploymentConfig?.id]);
+
+ useEffect(() => {
+ if (id && visible) {
+ getOcpDeploymentResource({ id });
+ }
+ }, [id, visible]);
+
+ const handleSubmit = () => {
+ validateFields().then((values) => {
+ if (createOCPDeployment) {
+ createOCPDeployment(values);
+ }
+ });
+ };
+
+ const resourceList = [
+ {
+ type: intl.formatMessage({
+ id: 'OBD.component.ModifyOCPResourcePoolModal.OcpServerMemory',
+ defaultMessage: 'OCP-Server 内存',
+ }),
+ value: ocpMemoryFree,
+ },
+ ...(!monitorDisplay
+ ? [
+ {
+ type: intl.formatMessage({
+ id: 'OBD.component.ModifyOCPResourcePoolModal.OcpTenantMemory',
+ defaultMessage: 'OCP 租户内存',
+ }),
+ value: ocpTenantMemoryFree,
+ },
+ ]
+ : [
+ {
+ type: intl.formatMessage({
+ id: 'OBD.component.ModifyOCPResourcePoolModal.MetadataTenantMemory',
+ defaultMessage: '元信息租户 内存',
+ }),
+ value: tenantMemoryFree,
+ },
+ {
+ type: intl.formatMessage({
+ id: 'OBD.component.ModifyOCPResourcePoolModal.MonitorDataTenantMemory',
+ defaultMessage: '监控数据租户内存',
+ }),
+ value: monitorMemory,
+ },
+ ]),
+
+ {
+ type: intl.formatMessage({
+ id: 'OBD.component.ModifyOCPResourcePoolModal.OtherUsedMemory',
+ defaultMessage: '其他已使用内存',
+ }),
+ value: otherUsedMemory,
+ },
+ {
+ type: intl.formatMessage({
+ id: 'OBD.component.ModifyOCPResourcePoolModal.RemainingMemory',
+ defaultMessage: '剩余内存',
+ }),
+ value: memoryFree,
+ },
+ ];
+
+ const config1 = {
+ data: resourceList,
+ angleField: 'value',
+ colorField: 'type',
+ innerRadius: 0.8,
+ isDonut: true,
+ lineWidth: 20,
+ label: false,
+ style: {
+ textAlign: 'center',
+ fontSize: 14,
+ },
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default ModifyResourcePoolModal;
diff --git a/web/src/component/MyCard/index.less b/web/src/component/MyCard/index.less
new file mode 100644
index 0000000..efaf3e6
--- /dev/null
+++ b/web/src/component/MyCard/index.less
@@ -0,0 +1,17 @@
+@import '~@oceanbase/design/es/theme/index.less';
+
+.card {
+ .header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ margin-bottom: 20px;
+
+ .title {
+ color: @colorText;
+ font-size: 16px;
+ font-family: PingFangSC-Medium;
+ }
+ }
+}
diff --git a/web/src/component/MyCard/index.tsx b/web/src/component/MyCard/index.tsx
new file mode 100644
index 0000000..afd731d
--- /dev/null
+++ b/web/src/component/MyCard/index.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { Card, Spin } from '@oceanbase/design';
+import type { CardProps } from '@oceanbase/design/es/card';
+import styles from './index.less';
+
+export interface MyCardProps extends CardProps {
+ children: React.ReactNode;
+ title?: React.ReactNode;
+ extra?: React.ReactNode;
+ loading?: boolean;
+ className?: string;
+ headStyle?: React.CSSProperties;
+}
+
+const MyCard = ({
+ children,
+ title,
+ extra,
+ loading = false,
+ className,
+ headStyle,
+ bodyStyle,
+ ...restProps
+}: MyCardProps) => (
+
+ {(title || extra) && (
+
+ {title && {title}}
+ {extra && {extra}}
+
+ )}
+
+ {children}
+
+
+);
+
+export default MyCard;
diff --git a/web/src/component/MyDrawer/index.tsx b/web/src/component/MyDrawer/index.tsx
new file mode 100644
index 0000000..a39fd31
--- /dev/null
+++ b/web/src/component/MyDrawer/index.tsx
@@ -0,0 +1,97 @@
+import { intl } from '@/utils/intl';
+import type { MouseEvent } from 'react';
+import React from 'react';
+import { Button, Drawer, Space } from '@oceanbase/design';
+import type { ButtonProps } from 'antd/es/button';
+import type { DrawerProps } from 'antd/es/drawer';
+import { isBoolean } from 'lodash';
+
+export type EventType = MouseEvent<
+ HTMLElement,
+ MouseEvent
+>;
+
+export interface MyDrawerProps extends DrawerProps {
+ onOk?: (e: EventType) => void;
+ onCancel?: (e: EventType) => void;
+ confirmLoading?: boolean;
+ footer?: React.ReactNode;
+ extra?: React.ReactNode;
+ cancelText?: string;
+ okText?: string;
+ okButtonProps?: ButtonProps;
+}
+
+const MyDrawer: React.FC = ({
+ children,
+ onClose,
+ onOk,
+ onCancel,
+ cancelText = intl.formatMessage({
+ id: 'OBD.component.MyDrawer.Cancel',
+ defaultMessage: '取消',
+ }),
+ okText = intl.formatMessage({
+ id: 'OBD.component.MyDrawer.Ok',
+ defaultMessage: '确定',
+ }),
+ okButtonProps,
+ confirmLoading = false,
+ footer = true,
+ extra,
+ bodyStyle = {},
+ ...restProps
+}) => {
+ return (
+
+ {children}
+ {(footer || extra) && (
+
+ {extra}
+
+ {isBoolean(footer) ? (
+
+
+
+
+ ) : (
+ footer
+ )}
+
+
+ )}
+
+ );
+};
+
+export default MyDrawer;
diff --git a/web/src/component/MyDropdown.tsx b/web/src/component/MyDropdown.tsx
new file mode 100644
index 0000000..f973c76
--- /dev/null
+++ b/web/src/component/MyDropdown.tsx
@@ -0,0 +1,115 @@
+import { intl } from '@/utils/intl';
+import React, { useState, useEffect } from 'react';
+import { Dropdown, Menu } from '@oceanbase/design';
+import { DownOutlined, CaretDownOutlined } from '@ant-design/icons';
+import type { DropDownProps } from 'antd/es/dropdown';
+import { toString } from 'lodash';
+import { ALL } from '@/constant';
+import ContentWithIcon from '@/component/ContentWithIcon';
+
+type MenuItem = {
+ value?: string | number;
+ label?: string;
+ [key: string]: any;
+};
+
+export interface MyDropdownProps extends Omit {
+ value?: string | number;
+ // 需要设置默认值优先级使用 defaultMenuKey,使用 value 可能与 menuKey 的 useEffect 的逻辑产生意外的情况
+ defaultMenuKey?: string | number;
+ menuList: MenuItem[];
+ showAll?: boolean;
+ allLabel?: string;
+ onChange?: (value: number | string) => void;
+ valueProp?: string;
+ style?: React.CSSProperties;
+ className?: string;
+ // 是否使用实心的图标
+ isSolidIcon?: boolean;
+}
+
+const MyDropdown: React.FC = ({
+ value,
+ defaultMenuKey,
+ menuList = [],
+ showAll = false,
+ allLabel,
+ onChange,
+ valueProp = 'value',
+ style = {},
+ className,
+ isSolidIcon = false,
+ ...restProps
+}) => {
+ const newMenuList = showAll
+ ? [
+ {
+ value: ALL,
+ label:
+ allLabel ||
+ intl.formatMessage({
+ id: 'OBD.src.component.MyDropdown.All',
+ defaultMessage: '全部',
+ }),
+ },
+
+ ...menuList,
+ ]
+ : menuList;
+
+ const firstMenuKey = newMenuList && newMenuList[0] && newMenuList[0].value;
+
+ /**
+ * 需要加上 value,不然设置了 value 未设置 defaultMenuKey 会导致值一直在 firstMenuKey 和 value 死循环
+ * 因为 useEffect 里面同时对 menuKey 和 value 进行了设置,首次设置会冲突
+ * */
+ const realDefaultMenuKey = defaultMenuKey || value;
+ const [menuKey, setMenuKey] = useState(realDefaultMenuKey || firstMenuKey);
+ // Dropdown 组件的 menuKey 是 string 类型,value 可能是非 string 类型的值,并且 menuKey 的初始化值可能不是 string 类型,统一转成 string 再做判断
+ const menuItem =
+ newMenuList.find((item) => toString(item.value) === toString(menuKey)) ||
+ {};
+
+ useEffect(() => {
+ if (onChange) {
+ onChange(menuItem[valueProp]);
+ }
+ }, [menuKey]);
+
+ useEffect(() => {
+ if (value) {
+ // 为了保证 Dropdown 组件的 menuKey 是 string 类型,设置时需要转成 string
+ setMenuKey(toString(value));
+ if (onChange) {
+ onChange(value);
+ }
+ }
+ }, [value]);
+
+ const menu = (
+
+ );
+
+ return (
+
+
+
+ );
+};
+
+export default MyDropdown;
diff --git a/web/src/component/MyInput.tsx b/web/src/component/MyInput.tsx
new file mode 100644
index 0000000..2b294df
--- /dev/null
+++ b/web/src/component/MyInput.tsx
@@ -0,0 +1,28 @@
+import { intl } from '@/utils/intl';
+import React from 'react';
+import { Input } from '@oceanbase/design';
+import type { InputProps } from 'antd/es/input';
+
+interface MyInputProps extends React.FC {
+ Search: typeof Input.Search;
+ Password: typeof Input.Password;
+ TextArea: typeof Input.TextArea;
+}
+
+const MyInput: MyInputProps = (props) => {
+ return (
+
+ );
+};
+
+MyInput.Search = Input.Search;
+MyInput.Password = Input.Password;
+MyInput.TextArea = Input.TextArea;
+
+export default MyInput;
diff --git a/web/src/component/MySelect.tsx b/web/src/component/MySelect.tsx
new file mode 100644
index 0000000..f3da4fc
--- /dev/null
+++ b/web/src/component/MySelect.tsx
@@ -0,0 +1,23 @@
+import { intl } from '@/utils/intl';
+import React from 'react';
+import { Select } from '@oceanbase/design';
+import type { SelectProps } from 'antd/es/select';
+
+const { Option, OptGroup } = Select;
+
+const MySelect: React.FC> = ({ children, ...restProps }) => (
+
+);
+
+MySelect.Option = Option;
+MySelect.OptGroup = OptGroup;
+
+export default MySelect;
diff --git a/web/src/component/NoAuth/index.tsx b/web/src/component/NoAuth/index.tsx
new file mode 100644
index 0000000..eaf59d6
--- /dev/null
+++ b/web/src/component/NoAuth/index.tsx
@@ -0,0 +1,21 @@
+import { intl } from '@/utils/intl';
+import React from 'react';
+import type { EmptyProps } from '@/component/Empty';
+import Empty from '@/component/Empty';
+
+type NoAuthProps = EmptyProps;
+
+export default ({
+ image = '/assets/common/no_auth.svg',
+ title = intl.formatMessage({
+ id: 'OBD.component.NoAuth.NoPermissionToView',
+ defaultMessage: '暂无权限查看',
+ }),
+ description = intl.formatMessage({
+ id: 'OBD.component.NoAuth.ContactTheAdministratorToActivate',
+ defaultMessage: '请联系管理员开通权限',
+ }),
+ ...restProps
+}: NoAuthProps) => (
+
+);
diff --git a/web/src/component/OCPConfig/index.tsx b/web/src/component/OCPConfig/index.tsx
new file mode 100644
index 0000000..976ae39
--- /dev/null
+++ b/web/src/component/OCPConfig/index.tsx
@@ -0,0 +1,681 @@
+import { intl } from '@/utils/intl';
+import React, { useState, useEffect } from 'react';
+import { Card, Form, Input, Row, Col, Descriptions } from '@oceanbase/design';
+import { some } from 'lodash';
+import { FORM_ITEM_SMALL_LAYOUT, SELECT_TOKEN_SPEARATORS } from '@/constant';
+import MyInput from '@/component/MyInput';
+import Password from '@/component/Password';
+import MySelect from '@/component/MySelect';
+import ContentWithQuestion from '@/component/ContentWithQuestion';
+// import { validatePassword } from '@/utils';
+// import tracert from '@/util/tracert';
+import validator from 'validator';
+
+export interface OCPConfigProps {
+ form: any;
+ hosts?: string[];
+ userInfo?: any;
+ createOcp?: any;
+ metadbType?: string;
+ clusterName?: string;
+ currentOcpDeploymentConfig: any;
+}
+
+const OCPConfig: React.FC = ({
+ form,
+ hosts,
+ userInfo,
+ clusterName,
+ createOcp,
+ metadbType = 'new',
+ currentOcpDeploymentConfig,
+}) => {
+ const { getFieldsValue, setFieldsValue } = form;
+ const [adminPasswordPassed, setAdminPasswordPassed] = useState(true);
+ const [tenantPasswordPassed, setTenantPasswordPassed] = useState(true);
+ const [passed, setPassed] = useState(true);
+
+ const { user } = getFieldsValue();
+
+ useEffect(() => {
+ if (userInfo?.username) {
+ const { home_path } = getFieldsValue();
+
+ setFieldsValue({
+ user: userInfo?.username,
+ home_path:
+ !home_path || home_path === `/home/${userInfo?.username || 'root'}`
+ ? `/home/${userInfo?.username || 'root'}`
+ : home_path.split('/ocp')[0],
+ });
+ }
+
+ if (currentOcpDeploymentConfig && !createOcp && !user && !clusterName) {
+ const {
+ auth,
+ appname,
+ // admin_password,
+ meta_tenant,
+ monitor_tenant,
+ servers,
+ home_path,
+ server_port,
+ } = currentOcpDeploymentConfig;
+
+ setFieldsValue({
+ hosts: servers || hosts,
+ user: auth?.user,
+ // password: auth?.password,
+ appname: clusterName ? `${clusterName}-OCP` : appname || 'OCP',
+ // admin_password,
+ meta_tenant_name: meta_tenant?.name?.tenant_name,
+ // tenantPassword: meta_tenant?.password,
+ // monitorPassword: monitor_tenant?.password,
+ monitor_tenant_name: monitor_tenant?.name?.tenant_name,
+ home_path:
+ !home_path || home_path === `/home/${userInfo?.username || 'root'}`
+ ? `/home/${userInfo?.username || 'root'}`
+ : home_path.split('/ocp')[0],
+ server_port: server_port || 8080,
+ confirmTenantPassword: meta_tenant?.password,
+ confirmMonitorPassword: monitor_tenant?.password,
+ });
+ } else if (hosts) {
+ setFieldsValue({
+ hosts,
+ });
+ }
+ }, [
+ currentOcpDeploymentConfig,
+ createOcp,
+ hosts,
+ clusterName,
+ userInfo?.username,
+ ]);
+
+ const validateConfirmTenantPassword = (rule, value, callback) => {
+ const { tenantPassword } = getFieldsValue();
+ if (value && value !== tenantPassword) {
+ callback(
+ intl.formatMessage({
+ id: 'OBD.component.OCPConfig.ThePasswordsEnteredTwiceAre',
+ defaultMessage: '两次输入的密码不一致,请重新输入',
+ }),
+ );
+ } else {
+ callback();
+ }
+ };
+
+ const validateConfirmMonitorPassword = (rule, value, callback) => {
+ const { monitorPassword } = getFieldsValue();
+ if (value && value !== monitorPassword) {
+ callback(
+ intl.formatMessage({
+ id: 'OBD.component.OCPConfig.ThePasswordsEnteredTwiceAre',
+ defaultMessage: '两次输入的密码不一致,请重新输入',
+ }),
+ );
+ } else {
+ callback();
+ }
+ };
+
+ const validate = (rule, values: any[], callback) => {
+ if (
+ values &&
+ some(
+ values,
+ (item) =>
+ // ipv4 地址
+ !validator.isIP(item, '4'),
+ )
+ ) {
+ callback(
+ intl.formatMessage({
+ id: 'OBD.component.OCPConfig.InvalidIpAddress',
+ defaultMessage: 'IP 地址不合法',
+ }),
+ );
+
+ return;
+ }
+ callback();
+ };
+
+ const onFinish = (values) => {
+ };
+
+ return (
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default OCPConfig;
diff --git a/web/src/component/OCPConfigNew/ResourcePlan.tsx b/web/src/component/OCPConfigNew/ResourcePlan.tsx
new file mode 100644
index 0000000..09ce5be
--- /dev/null
+++ b/web/src/component/OCPConfigNew/ResourcePlan.tsx
@@ -0,0 +1,353 @@
+import { intl } from '@/utils/intl';
+import { ProCard, ProFormText, ProFormDigit } from '@ant-design/pro-components';
+import { FormInstance } from 'antd/lib/form';
+import { Alert, Row, Col, message } from 'antd';
+import { useState } from 'react';
+import { useModel } from 'umi';
+
+import { generateRandomPassword as generatePassword } from '@/utils';
+import { copyText } from '@/utils/helper';
+import CustomPasswordInput from '../CustomPasswordInput';
+import { resourceMap } from '@/pages/constants';
+import styles from './index.less';
+import { MsgInfoType } from './index';
+interface ResourcePlanProps {
+ form: FormInstance;
+ metaMsgInfo: MsgInfoType;
+ tenantMsgInfo: MsgInfoType;
+ setTenantMsgInfo: React.Dispatch<
+ React.SetStateAction
+ >;
+
+ setMetaMsgInfo: React.Dispatch>;
+}
+
+type ResourceType = {
+ hosts: number;
+ cpu: number;
+ memory: number;
+};
+
+export default function ResourcePlan({
+ form,
+ metaMsgInfo,
+ tenantMsgInfo,
+ setTenantMsgInfo,
+ setMetaMsgInfo,
+}: ResourcePlanProps) {
+ const { ocpConfigData = {} } = useModel('global');
+ const { components = {} } = ocpConfigData;
+ const { ocpserver = {} } = components;
+ const { meta_tenant = {}, monitor_tenant = {} } = ocpserver;
+ const [tenantPassword, setTenantPassword] = useState(
+ meta_tenant.password || '',
+ );
+ const [monitorPassword, setMonitorPassword] = useState(
+ monitor_tenant.password || '',
+ );
+
+ const handleSetTenantPassword = (password: string) => {
+ form.setFieldValue(['ocpserver', 'meta_tenant', 'password'], password);
+ form.validateFields([['ocpserver', 'meta_tenant', 'password']]);
+ setTenantPassword(password);
+ };
+
+ const handleSetMonitorPassword = (password: string) => {
+ form.setFieldValue(['ocpserver', 'monitor_tenant', 'password'], password);
+ form.validateFields([['ocpserver', 'monitor_tenant', 'password']]);
+ setMonitorPassword(password);
+ };
+
+ const hostsMapValue = (inputHosts: number, resourceMap: ResourceType[]) => {
+ let cpuRes, memoryRes;
+ const { cpu: maxCpu, memory: maxMemory } =
+ resourceMap[resourceMap.length - 1];
+ for (let item of resourceMap) {
+ if (inputHosts <= item.hosts) {
+ cpuRes = item.cpu;
+ memoryRes = item.memory;
+ break;
+ }
+ }
+ if (!cpuRes) cpuRes = maxCpu;
+ if (!memoryRes) memoryRes = maxMemory;
+ return [cpuRes, memoryRes];
+ };
+
+ const handleChangeHosts = (inputHosts: number) => {
+ const metaDBMap = resourceMap['metaDB'],
+ monitorDBMap = resourceMap['monitorDB'],
+ OCPMap = resourceMap['OCP'];
+ const [metaDBCpu, metaDBMemory] = hostsMapValue(inputHosts, metaDBMap);
+ const [monitorDBCpu, monitorDBMemory] = hostsMapValue(
+ inputHosts,
+ monitorDBMap,
+ );
+ const [_, ocpMemory] = hostsMapValue(inputHosts, OCPMap);
+ form.setFieldsValue({
+ ocpserver: {
+ memory_size: ocpMemory,
+ monitor_tenant: {
+ resource: {
+ cpu: monitorDBCpu,
+ memory: monitorDBMemory,
+ },
+ },
+ meta_tenant: {
+ resource: {
+ cpu: metaDBCpu,
+ memory: metaDBMemory,
+ },
+ },
+ },
+ });
+ };
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ResourcePlan.YouAreExpectedToNeed',
+ defaultMessage: '您预计需要管理:',
+ })}
+
+
+ handleChangeHosts(inputHosts),
+ }}
+ />
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ResourcePlan.Table',
+ defaultMessage: '台',
+ })}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ResourcePlan.ResourceConfiguration',
+ defaultMessage: '资源配置',
+ })}
+
+
+ {' '}
+
+ GiB
+
+ {/*
+
+ GIB
+ */}
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ResourcePlan.MetadataTenantConfiguration',
+ defaultMessage: '元信息租户配置',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ VCPUS
+
+
+
+ GiB
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ResourcePlan.MonitorDataTenantConfiguration',
+ defaultMessage: '监控数据租户配置',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ VCPUS
+
+
+
+ GiB
+
+
+ );
+}
diff --git a/web/src/component/OCPConfigNew/ServiceConfig.tsx b/web/src/component/OCPConfigNew/ServiceConfig.tsx
new file mode 100644
index 0000000..136aa81
--- /dev/null
+++ b/web/src/component/OCPConfigNew/ServiceConfig.tsx
@@ -0,0 +1,297 @@
+import { intl } from '@/utils/intl';
+import { ProCard, ProForm, ProFormDigit } from '@ant-design/pro-components';
+import { Input, Tooltip, Button, Row, message, Form } from 'antd';
+import { FormInstance } from 'antd/lib/form';
+import {
+ QuestionCircleOutlined,
+ CheckCircleFilled,
+ CloseCircleFilled,
+} from '@ant-design/icons';
+import { useEffect, useState } from 'react';
+import { useModel, getLocale } from 'umi';
+import { copyText } from '@/utils/helper';
+import {
+ generateRandomPassword as generatePassword,
+ passwordRules,
+ siteReg,
+} from '@/utils';
+import styles from './index.less';
+import CustomPasswordInput from '../CustomPasswordInput';
+import { ocpAddonAfter } from '@/constant/configuration';
+import { MsgInfoType } from './index';
+
+interface ServiceConfigProps {
+ form: FormInstance;
+ adminMsgInfo: MsgInfoType;
+ setAdminMsgInfo: React.Dispatch<
+ React.SetStateAction
+ >;
+}
+
+const multipleNodesDesc = intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ServiceConfig.WeRecommendThatYouUse',
+ defaultMessage:
+ '建议使用负载均衡的地址作为外部访问 OCP 网站的入口, 实现 OCP 服务高可用。如无,您可选择使用 OCP 的节点 IP+端口进行设置,请后续登录 OCP 后进入系统管理->系统参数变更 ocp.site.url(重启生效)',
+});
+const locale = getLocale();
+
+export default function ServiceConfig({
+ form,
+ adminMsgInfo,
+ setAdminMsgInfo,
+}: ServiceConfigProps) {
+ const [checkStatus, setCheckStatus] = useState<
+ 'unchecked' | 'fail' | 'success'
+ >('unchecked');
+ const { ocpConfigData } = useModel('global');
+ const { isSingleOcpNode } = useModel('ocpInstallData');
+ const { components = {} } = ocpConfigData;
+ const { ocpserver = {} } = components;
+ const [adminPassword, setAdminPassword] = useState(
+ ocpserver.admin_password || '',
+ );
+
+ const ip =
+ form.getFieldValue(['ocpserver', 'servers']) || ocpserver.servers || [];
+ const defaultSiteUrl =
+ (ocpserver.ocp_site_url || isSingleOcpNode === true) && ip.length
+ ? `http://${ip[0]}:8080`
+ : '';
+ const [siteUrl, setSiteUrl] = useState(defaultSiteUrl);
+ const setPassword = (password: string) => {
+ form.setFieldValue(['ocpserver', 'admin_password'], password);
+ form.validateFields([['ocpserver', 'admin_password']]);
+ setAdminPassword(password);
+ };
+
+ const handleCheckSystemUser = () => {
+ let site = form.getFieldValue(['ocpserver', 'ocp_site_url']);
+ if (siteReg.test(site)) {
+ setCheckStatus('success');
+ } else {
+ setCheckStatus('fail');
+ }
+ };
+
+ const siteUrlChange = (e: any) => {
+ let { value } = e.target;
+ setCheckStatus('unchecked');
+ setSiteUrl(value);
+ form.setFieldValue(['ocpserver', 'ocp_site_url'], value);
+ };
+
+ useEffect(() => {
+ if (typeof isSingleOcpNode !== 'undefined' && !ocpserver.ocp_site_url) {
+ if (isSingleOcpNode && ip.length) {
+ let url = `http://${ip[0]}:8080`;
+ setSiteUrl(url);
+ form.setFieldValue(['ocpserver', 'ocp_site_url'], url);
+ } else {
+ setSiteUrl('');
+ form.setFieldValue(['ocpserver', 'ocp_site_url'], '');
+ }
+ }
+ }, [isSingleOcpNode]);
+
+ return (
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ServiceConfig.AdminPassword',
+ defaultMessage: 'Admin 密码',
+ })}
+
+
+
+
+ >
+ }
+ />
+
+
+ {ocpAddonAfter}} />
+
+
+
+
+
+
+
+
+
+
+
+ ocp.site.url
+
+
+
+ >
+ }
+ rules={[
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ServiceConfig.EnterOcpSiteUrl',
+ defaultMessage: '请输入ocp.site.url',
+ }),
+ },
+ ]}
+ >
+
+
+
+
{
+ form.validateFields([['ocpserver', 'ocp_site_url']]);
+ }}
+ // placeholder="例如:http://localhost:8080"
+ style={{ width: 328 }}
+ />
+
+ {checkStatus === 'success' && (
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ServiceConfig.TheCurrentVerificationIsSuccessful',
+ defaultMessage: '当前校验成功,请进行下一步',
+ })}
+
+
+ )}
+
+ {checkStatus === 'fail' && (
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.component.OCPConfigNew.ServiceConfig.TheCurrentVerificationFailedPlease',
+ defaultMessage: '当前校验失败,请重新输入',
+ })}
+
+
+ )}
+
+
+
+ {isSingleOcpNode === false && (
+
{multipleNodesDesc}
+ )}
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/OCPConfigNew/UserConfig.tsx b/web/src/component/OCPConfigNew/UserConfig.tsx
new file mode 100644
index 0000000..d27a378
--- /dev/null
+++ b/web/src/component/OCPConfigNew/UserConfig.tsx
@@ -0,0 +1,14 @@
+import { ProCard } from '@ant-design/pro-components';
+import type { FormInstance } from 'antd/lib/form';
+
+import MateDBUserConfig from '../MetaDBConfig/UserConfig';
+import NodeConfig from '../MetaDBConfig/NodeConfig';
+
+export default function UserConfig({ form }: { form: FormInstance }) {
+ return (
+
+
+
+
+ );
+}
diff --git a/web/src/component/OCPConfigNew/index.less b/web/src/component/OCPConfigNew/index.less
new file mode 100644
index 0000000..a212d1b
--- /dev/null
+++ b/web/src/component/OCPConfigNew/index.less
@@ -0,0 +1,8 @@
+.titleText {
+ color: #132039;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+ letter-spacing: 0;
+ text-align: left;
+}
diff --git a/web/src/component/OCPConfigNew/index.tsx b/web/src/component/OCPConfigNew/index.tsx
new file mode 100644
index 0000000..537742b
--- /dev/null
+++ b/web/src/component/OCPConfigNew/index.tsx
@@ -0,0 +1,407 @@
+import { intl } from '@/utils/intl';
+import { useRef } from 'react';
+import { ProForm } from '@ant-design/pro-components';
+import { Space, Button, message } from 'antd';
+import { useModel } from 'umi';
+import { useState } from 'react';
+
+import ExitBtn from '../ExitBtn';
+import CustomFooter from '../CustomFooter';
+import ResourcePlan from './ResourcePlan';
+import ServiceConfig from './ServiceConfig';
+import UserConfig from './UserConfig';
+import { getTailPath } from '@/utils/helper';
+
+type TenantType = {
+ name: {
+ tenant_name: string;
+ };
+ password: string;
+ resource: {
+ cpu: number;
+ memory: number;
+ };
+};
+
+interface FormValues extends API.Components {
+ auth?: {
+ user?: string;
+ password?: string;
+ port: number;
+ };
+ ocpserver?: {
+ home_path?: string;
+ log_dir?: string;
+ soft_dir?: string;
+ ocp_site_url?: string;
+ admin_password?: string;
+ meta_tenant?: TenantType;
+ monitor_tenant?: TenantType;
+ memory_size?: number;
+ port?: number;
+ servers?: string[];
+ manage_info?: {
+ cluster: number;
+ tenant: number;
+ machine: number;
+ };
+ };
+ launch_user?: string;
+}
+
+// 无site_url
+const rulePath: any[] = [
+ ['ocpserver', 'manage_info', 'cluster'],
+ ['ocpserver', 'manage_info', 'tenant'],
+ ['ocpserver', 'manage_info', 'machine'],
+ ['ocpserver', 'memory_size'],
+ ['ocpserver', 'meta_tenant', 'name', 'tenant_name'],
+ ['ocpserver', 'meta_tenant', 'password'],
+ ['ocpserver', 'meta_tenant', 'resource', 'cpu'],
+ ['ocpserver', 'meta_tenant', 'resource', 'memory'],
+ ['ocpserver', 'monitor_tenant', 'name', 'tenant_name'],
+ ['ocpserver', 'monitor_tenant', 'password'],
+ ['ocpserver', 'monitor_tenant', 'resource', 'cpu'],
+ ['ocpserver', 'monitor_tenant', 'resource', 'memory'],
+ ['ocpserver', 'admin_password'],
+ ['ocpserver', 'home_path'],
+ ['ocpserver', 'log_dir'],
+ ['ocpserver', 'soft_dir'],
+ ['ocpserver', 'port'],
+];
+export type MsgInfoType = {
+ validateStatus: 'success' | 'error';
+ errorMsg: string | null;
+};
+
+// 使用旧的数据库多 OCP部署选择
+export default function OCPConfigNew({ setCurrent, current }: API.StepProp) {
+ const isUseNewDBRef = useRef(getTailPath() === 'install');
+ const isOldDB = getTailPath() === 'configuration'
+ const { ocpConfigData, setOcpConfigData, setErrorVisible, setErrorsList } =
+ useModel('global');
+ const { useRunningUser, isSingleOcpNode } = useModel('ocpInstallData');
+ const { components = {}, auth = {}, launch_user } = ocpConfigData;
+ const { ocpserver = {} } = components;
+ const [form] = ProForm.useForm();
+ const [adminMsgInfo, setAdminMsgInfo] = useState();
+ const [metaMsgInfo, setMetaMsgInfo] = useState();
+ const [tenantMsgInfo, setTenantMsgInfo] = useState();
+ const user = useRunningUser ? launch_user : auth.user;
+ const prevStep = () => {
+ const formValues = form.getFieldsValue(true);
+ setData(formValues);
+ setErrorVisible(false);
+ setErrorsList([]);
+ setCurrent(current - 1);
+ };
+ const setData = (dataSource: FormValues) => {
+ let newOcpserver: any = {
+ ...(ocpserver || {}),
+ ...dataSource.ocpserver,
+ };
+ let result = {
+ ...ocpConfigData,
+ components: {
+ ...components,
+ ocpserver: newOcpserver,
+ },
+ };
+ if (isOldDB) {
+ if (dataSource.auth) result.auth = dataSource.auth;
+ if (dataSource.launch_user) result.launch_user = dataSource.launch_user;
+ }
+ setOcpConfigData({ ...result });
+ };
+
+ const validateFields = async (rulePath?: any[]) => {
+ if (
+ adminMsgInfo?.validateStatus === 'error' ||
+ metaMsgInfo?.validateStatus === 'error' ||
+ tenantMsgInfo?.validateStatus === 'error'
+ ) {
+ //两种情况会是 error 不符合要求 或者 没有填写 没有填写form.validateFields是可以检测到的
+ let errorPromises: Promise[] = [];
+ if (adminMsgInfo?.validateStatus === 'error') {
+ errorPromises.push(
+ Promise.reject({
+ errorFields: [
+ {
+ errors: [adminMsgInfo.errorMsg],
+ name: ['ocpserver', 'admin_password'],
+ },
+ ],
+ }),
+ );
+ }
+ if(metaMsgInfo?.validateStatus === 'error'){
+ errorPromises.push(
+ Promise.reject({
+ errorFields: [
+ {
+ errors: [metaMsgInfo.errorMsg],
+ name: ['ocpserver', 'meta_tenant', 'password'],
+ },
+ ],
+ }),
+ );
+ }
+ if(tenantMsgInfo?.validateStatus === 'error'){
+ errorPromises.push(
+ Promise.reject({
+ errorFields: [
+ {
+ errors: [tenantMsgInfo.errorMsg],
+ name: ['ocpserver', 'monitor_tenant', 'password'],
+ },
+ ],
+ }),
+ );
+ }
+ return Promise.allSettled([...errorPromises,form.validateFields(rulePath)])
+ }
+ return Promise.allSettled([form.validateFields(rulePath)]);
+ };
+
+ const sortErrorFields = (errorFields: any, sortArr: string[]) => {
+ let res: any[] = [];
+ for (let name of sortArr) {
+ let target = errorFields.find((errorField: any) => {
+ if (errorField.name[0] === 'ocpserver') {
+ return name === errorField.name[1];
+ } else {
+ return name === errorField.name[0];
+ }
+ });
+ if (target) res.push(target);
+ }
+ return res;
+ };
+
+ const formValidScorllHelper = (result:PromiseSettledResult[])=>{
+ let errorFields = [];
+ for(let item of result){
+ if(item.status === 'rejected'){
+ errorFields.push(...item.reason.errorFields)
+ }
+ }
+ for (let errField of errorFields) {
+ if (errField.name[1] && errField.name[1] === 'admin_password') {
+ setAdminMsgInfo({
+ validateStatus: 'error',
+ errorMsg: errField.errors[0],
+ });
+ }
+ if (
+ errField.name[1] === 'meta_tenant' &&
+ errField.name[2] === 'password'
+ ) {
+ setMetaMsgInfo({
+ validateStatus: 'error',
+ errorMsg: errField.errors[0],
+ });
+ }
+ if (
+ errField.name[1] === 'monitor_tenant' &&
+ errField.name[2] === 'password'
+ ) {
+ setTenantMsgInfo({
+ validateStatus: 'error',
+ errorMsg: errField.errors[0],
+ });
+ }
+ }
+ const userBlock = ['auth', 'launch_user', 'servers'];
+ const serviceBlock = [
+ 'admin_password',
+ 'soft_dir',
+ 'log_dir',
+ 'home_path',
+ 'ocp_site_url',
+ 'port',
+ ];
+ const resourceBlock = ['manage_info', 'memory_size'];
+ const tenantBlock = ['meta_tenant', 'monitor_tenant'];
+ let pathArr = isUseNewDBRef.current
+ ? [...serviceBlock, ...resourceBlock, ...tenantBlock]
+ : [...userBlock, ...serviceBlock, ...resourceBlock, ...tenantBlock];
+ const sortFields = sortErrorFields(errorFields, pathArr);
+ form.scrollToField(sortFields[0].name, {
+ behavior: (actions) => {
+ actions.forEach(({ el, top, left }) => {
+ if (
+ sortFields[0].name[0] === 'auth' ||
+ sortFields[0].name[1] === 'servers' ||
+ sortFields[0].name[0] === 'launch_user'
+ ) {
+ el.scrollTop = 0;
+ } else if (serviceBlock.includes(sortFields[0].name[1])) {
+ el.scrollTop = isUseNewDBRef.current ? 0 : 400;
+ } else if (resourceBlock.includes(sortFields[0].name[1])) {
+ el.scrollTop = isUseNewDBRef.current ? 400 : 900;
+ } else if (tenantBlock.includes(sortFields[0].name[1])) {
+ el.scrollTop = isUseNewDBRef.current ? 800 : 1300;
+ }
+ el.scrollLeft = left;
+ });
+ },
+ });
+ message.destroy();
+ }
+
+ const nextStep = async () => {
+ let validatePromise: Promise;
+ if (isSingleOcpNode === false) {
+ validatePromise = validateFields(rulePath);
+ } else {
+ validatePromise = validateFields();
+ }
+ validatePromise
+ .then((result) => {
+ if(result.find((item:any)=>item.status === 'rejected'))
+ {
+ formValidScorllHelper(result)
+ return
+ }
+ setData(result[0].value);
+ setCurrent(current + 1);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ })
+ };
+ let home_path = undefined,
+ log_dir = undefined,
+ soft_dir = undefined;
+
+ if (isUseNewDBRef.current) {
+ let val = launch_user ? launch_user : auth.user;
+ home_path = launch_user
+ ? `/home/${launch_user}`
+ : auth.user === 'root'
+ ? '/root'
+ : `/home/${auth.user}`;
+ log_dir = `/home/${val}/logs`;
+ soft_dir = `/home/${val}/software`;
+ }
+
+ let initialValues: FormValues = {
+ ocpserver: {
+ home_path: ocpserver?.home_path || home_path,
+ log_dir: ocpserver?.log_dir || log_dir,
+ soft_dir: ocpserver?.soft_dir || soft_dir,
+ port: ocpserver?.port || 8080,
+ ocp_site_url: ocpserver?.ocp_site_url || undefined,
+ admin_password: ocpserver?.admin_password || undefined,
+ memory_size: ocpserver?.memory_size || 4,
+ meta_tenant: ocpserver?.meta_tenant || {
+ resource: {
+ cpu: 2,
+ memory: 4,
+ },
+ name: {
+ tenant_name: 'ocp_meta',
+ },
+ },
+ monitor_tenant: ocpserver?.monitor_tenant || {
+ resource: {
+ cpu: 2,
+ memory: 8,
+ },
+ name: {
+ tenant_name: 'ocp_monitor',
+ },
+ },
+ manage_info: ocpserver?.manage_info || {
+ // cluster: 2,
+ // tenant: 4,
+ machine: 10,
+ },
+ },
+ launch_user: launch_user || undefined,
+ };
+
+ if (!isUseNewDBRef.current) {
+ initialValues = {
+ ...initialValues,
+ auth: {
+ user: auth?.user || undefined,
+ password: auth?.password || undefined,
+ port: auth?.port || 22,
+ },
+ ocpserver: {
+ ...initialValues.ocpserver,
+ servers: ocpserver?.servers?.length ? ocpserver?.servers : undefined,
+ },
+ launch_user: launch_user || undefined,
+ };
+ rulePath.push(
+ ...[
+ ['auth', 'user'],
+ ['auth', 'password'],
+ 'launch_user',
+ ['ocpserver', 'servers'],
+ ],
+ );
+ }
+
+ return (
+
+
+ {!isUseNewDBRef.current && }
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/OCPLogo.tsx b/web/src/component/OCPLogo.tsx
new file mode 100644
index 0000000..bd644d1
--- /dev/null
+++ b/web/src/component/OCPLogo.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { isEnglish } from '@/utils';
+
+export interface OCPLogoProps {
+ onClick?: (e: React.SyntheticEvent) => void;
+ height?: number;
+ mode?: 'default' | 'simple';
+ style?: React.CSSProperties;
+ className?: string;
+}
+
+const OCPLogo: React.FC = ({
+ mode = 'default',
+ height = mode === 'default' ? 80 : 24,
+ style,
+ ...restProps
+}) => {
+ const logoUrl = isEnglish()
+ ? '/assets/logo/ocp_express_logo_en.svg'
+ : '/assets/logo/ocp_express_logo_zh.svg';
+ const simpleLogoUrl = isEnglish()
+ ? '/assets/logo/ocp_express_simple_logo_en.svg'
+ : '/assets/logo/ocp_express_simple_logo_zh.svg';
+ return (
+
+ );
+};
+
+export default OCPLogo;
diff --git a/web/src/component/OCPPreCheck/CheckInfo/BasicInfo.tsx b/web/src/component/OCPPreCheck/CheckInfo/BasicInfo.tsx
new file mode 100644
index 0000000..1033113
--- /dev/null
+++ b/web/src/component/OCPPreCheck/CheckInfo/BasicInfo.tsx
@@ -0,0 +1,104 @@
+import { intl } from '@/utils/intl';
+import { Row, Col, Tag } from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import { getLocale } from 'umi';
+
+import type { BasicInfoProp } from './type';
+import styles from './index.less';
+import { leftCardStyle } from '.';
+interface BasicInfoProps {
+ basicInfoProp: BasicInfoProp;
+}
+
+const locale = getLocale();
+export default function BasicInfo({ basicInfoProp }: BasicInfoProps) {
+ return (
+
+
+
+
+
+
+ {basicInfoProp.appname}
+
+
+ {basicInfoProp.type}
+
+
+
+
+
+
+ {basicInfoProp.productsInfo.map((versionInfo, idx) => (
+
+
+
+ {versionInfo.productName}
+ {typeof versionInfo.isCommunity !== 'undefined' && (
+
+ {versionInfo.isCommunity
+ ? intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.BasicInfo.CommunityEdition',
+ defaultMessage: '社区版',
+ })
+ : intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.BasicInfo.CommercialEdition',
+ defaultMessage: '商业版',
+ })}
+
+ )}
+
+
+ V {versionInfo.version}
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/web/src/component/OCPPreCheck/CheckInfo/ConfigInfo.tsx b/web/src/component/OCPPreCheck/CheckInfo/ConfigInfo.tsx
new file mode 100644
index 0000000..ea9a0a4
--- /dev/null
+++ b/web/src/component/OCPPreCheck/CheckInfo/ConfigInfo.tsx
@@ -0,0 +1,412 @@
+import { intl } from '@/utils/intl';
+import { Row, Col, Tooltip, Space, Table } from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import type { ConnectInfoPropType } from './type';
+import { componentsConfig } from '@/pages/constants';
+import type { ColumnsType } from 'antd/es/table';
+import styles from './index.less';
+import type { DBNodeType } from './type';
+import PasswordCard from '@/component/PasswordCard';
+import { leftCardStyle } from '.';
+import {
+ oceanbaseAddonAfter,
+ obproxyAddonAfter,
+} from '@/constant/configuration';
+interface BasicInfo {
+ isNewDB: boolean;
+ configInfoProp: ConnectInfoPropType;
+ oceanbase: any;
+ obproxy: any;
+}
+export default function ConfigInfo({
+ isNewDB,
+ configInfoProp,
+ oceanbase,
+ obproxy,
+}: BasicInfo) {
+ const { userConfig, ocpNodeConfig, clusterConfig, obproxyConfig, dbNode } =
+ configInfoProp;
+ const obConfigInfo = clusterConfig.info;
+ const obproxyConfigInfo = obproxyConfig.info;
+ const ObproxyServer = () => (
+ <>
+ {obproxyConfigInfo.servers &&
+ obproxyConfigInfo.servers.map((text: string, idx: number) => (
+
+ {text}
+ {idx !== obproxyConfigInfo.servers.length - 1 && <>,>}{' '}
+
+ ))}
+ >
+ );
+
+ const configInfo = [
+ {
+ key: 'cluster',
+ group: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.ClusterConfiguration',
+ defaultMessage: '集群配置',
+ }),
+ content: [
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.RootSysPassword',
+ defaultMessage: 'root@sys 密码',
+ }),
+ colSpan: 5,
+ value: (
+
+ {obConfigInfo?.root_password}
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.SoftwarePath',
+ defaultMessage: '软件路径',
+ }),
+ value: (
+
+
+ {obConfigInfo?.home_path}
+ {oceanbaseAddonAfter}
+
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.DataPath',
+ defaultMessage: '数据路径',
+ }),
+ value: (
+
+ {obConfigInfo?.data_dir}
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.LogPath',
+ defaultMessage: '日志路径',
+ }),
+ value: (
+
+ {obConfigInfo?.redo_dir}
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.MysqlPort',
+ defaultMessage: 'mysql 端口',
+ }),
+ colSpan: 3,
+ value: obConfigInfo?.mysql_port,
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.RpcPort',
+ defaultMessage: 'rpc 端口',
+ }),
+ colSpan: 3,
+ value: obConfigInfo?.rpc_port,
+ },
+ ],
+
+ more: oceanbase?.parameters?.length
+ ? [
+ {
+ label: componentsConfig['oceanbase'].labelName,
+ parameters: oceanbase?.parameters,
+ },
+ ]
+ : [],
+ },
+ {
+ key: 'obproxy',
+ group: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObproxyConfiguration',
+ defaultMessage: 'OBProxy 配置',
+ }),
+ content: [
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObproxyNodes',
+ defaultMessage: 'OBProxy 节点',
+ }),
+ colSpan: 6,
+ value: (
+ } placement="topLeft">
+
+
+
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.SoftwarePath',
+ defaultMessage: '软件路径',
+ }),
+ colSpan: 6,
+ value: (
+
+
+ {obproxyConfigInfo?.home_path}
+ {obproxyAddonAfter}
+
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.SqlPort',
+ defaultMessage: 'SQL 端口',
+ }),
+ colSpan: 6,
+ value: (
+
+ {obproxyConfigInfo?.listen_port}
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.PortExporter',
+ defaultMessage: 'Exporter 端口',
+ }),
+ colSpan: 6,
+ value: (
+
+
+ {obproxyConfigInfo?.prometheus_listen_port}
+
+
+ ),
+ },
+ ],
+
+ more: obproxy?.parameters?.length
+ ? [
+ {
+ label: componentsConfig['obproxy'].labelName,
+ parameters: obproxy?.parameters,
+ },
+ ]
+ : [],
+ },
+ ];
+
+ const getMoreColumns = (label: string) => {
+ const columns: ColumnsType = [
+ {
+ title: label,
+ dataIndex: 'key',
+ render: (text) => text,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.ParameterValue',
+ defaultMessage: '参数值',
+ }),
+ dataIndex: 'value',
+ render: (text, record) =>
+ record.adaptive
+ ? intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.AutomaticAllocation',
+ defaultMessage: '自动分配',
+ })
+ : text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.Introduction',
+ defaultMessage: '介绍',
+ }),
+ dataIndex: 'description',
+ render: (text) => (
+
+ {text}
+
+ ),
+ },
+ ];
+
+ return columns;
+ };
+
+ const dbNodeColums: ColumnsType = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.ZoneName',
+ defaultMessage: 'Zone 名称',
+ }),
+ dataIndex: 'name',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObServerNodes',
+ defaultMessage: 'OB Server 节点',
+ }),
+ dataIndex: 'servers',
+ render: (_, record) => (
+ <>
+ {_.map((item: { ip: string }, idx: number) => (
+
+ {item.ip}
+ {idx !== _.length - 1 && ,}
+
+ ))}
+ >
+ ),
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ConfigInfo.RootServerNodes',
+ defaultMessage: 'Root Server 节点',
+ }),
+ dataIndex: 'rootservice',
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+ {userConfig.user}
+
+
+
+ {userConfig.port || '-'}
+
+
+
+
+
+
+
+
+ {ocpNodeConfig &&
+ ocpNodeConfig.map((server, idx) => (
+
+ {server}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {configInfo?.map((item, index) => {
+ return (
+
+
+
+ {item.content.map((subItem) => (
+
+ {subItem.value}
+
+ ))}
+
+
+ {item?.more?.length ? (
+
+ {item?.more.map((moreItem) => (
+
+
+
+ ))}
+
+ ) : null}
+
+ );
+ })}
+
+
+
+
+ );
+}
diff --git a/web/src/component/OCPPreCheck/CheckInfo/ConnectInfo.tsx b/web/src/component/OCPPreCheck/CheckInfo/ConnectInfo.tsx
new file mode 100644
index 0000000..a91c659
--- /dev/null
+++ b/web/src/component/OCPPreCheck/CheckInfo/ConnectInfo.tsx
@@ -0,0 +1,57 @@
+import { intl } from '@/utils/intl';
+import { Row, Col } from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import styles from './index.less';
+import type { ConnectInfoType } from './type';
+
+interface ConnectInfoProps {
+ connectInfoProp: ConnectInfoType[];
+}
+export default function ConnectInfo({ connectInfoProp }: ConnectInfoProps) {
+ return (
+
+
+
+ {connectInfoProp.map((connectInfo, idx) => (
+
+
+ {connectInfo.map((item, _idx) => (
+
+ {item.value}
+
+ ))}
+
+
+ ))}
+ {/*
+
+
+ 111.222.3333
+
+
+ 2882
+
+
+
+
+
+
+ root@sys
+
+
+ 12345
+
+
+ */}
+
+
+
+ );
+}
diff --git a/web/src/component/OCPPreCheck/CheckInfo/ResourceInfo.tsx b/web/src/component/OCPPreCheck/CheckInfo/ResourceInfo.tsx
new file mode 100644
index 0000000..0c1ef26
--- /dev/null
+++ b/web/src/component/OCPPreCheck/CheckInfo/ResourceInfo.tsx
@@ -0,0 +1,271 @@
+import { intl } from '@/utils/intl';
+import { Row, Col } from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import type { ResourceInfoPropType } from './type';
+import styles from './index.less';
+import { leftCardStyle } from '.';
+import { ocpAddonAfter } from '@/constant/configuration';
+import PasswordCard from '@/component/PasswordCard';
+interface BasicInfoProps {
+ isNewDB: boolean;
+ resourceInfoProp: ResourceInfoPropType;
+}
+
+export default function ResourceInfo({
+ isNewDB,
+ resourceInfoProp,
+}: BasicInfoProps) {
+ const {
+ serviceConfig,
+ resourcePlan,
+ memory_size,
+ tenantConfig,
+ monitorConfig,
+ ocpServer,
+ userConfig,
+ } = resourceInfoProp;
+ const serviceMap: any = {
+ admin_password: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ResourceInfo.AdminPassword',
+ defaultMessage: 'Admin密码',
+ }),
+ home_path: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ResourceInfo.SoftwarePath',
+ defaultMessage: '软件路径',
+ }),
+ log_dir: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ResourceInfo.LogPath',
+ defaultMessage: '日志路径',
+ }),
+ soft_dir: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.ResourceInfo.PackagePath',
+ defaultMessage: '软件包路径',
+ }),
+ ocp_site_url: 'ocp.site.url',
+ };
+ return (
+
+
+ {!isNewDB && userConfig && (
+
+
+
+
+ {userConfig.user}
+
+
+
+ {userConfig.port || '-'}
+
+
+
+
+ )}
+
+ {!isNewDB && ocpServer && (
+
+
+
+ {ocpServer.map((server, idx) => (
+
+ {server}
+
+ ))}
+
+
+
+ )}
+
+
+
+
+
+ {serviceConfig &&
+ Object.keys(serviceConfig).map((key, idx) => (
+
+ {key === 'home_path'
+ ? `${serviceConfig[key]}${ocpAddonAfter}`
+ : `${serviceConfig[key]}`}
+
+ ))}
+
+
+
+
+
+
+
+ {/*
+ {resourcePlan.cluster}
+
+
+ {resourcePlan.tenant}
+ */}
+
+ {resourcePlan.machine}
+
+
+
+
+
+
+
+
+ {memory_size} GiB
+
+
+
+
+
+
+
+
+
+ {tenantConfig.info.tenant_name}
+
+
+ {tenantConfig.info.password}
+
+
+
+
+
+
+ {tenantConfig.resource.cpu} VCPUS
+
+
+ {tenantConfig.resource.memory} GiB
+
+
+
+
+
+
+
+
+
+
+ {monitorConfig.info.tenant_name}
+
+
+ {monitorConfig.info.password}
+
+
+
+
+
+
+ {monitorConfig.resource.cpu} VCPUS
+
+
+ {monitorConfig.resource.memory} GiB
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/OCPPreCheck/CheckInfo/index.less b/web/src/component/OCPPreCheck/CheckInfo/index.less
new file mode 100644
index 0000000..4242628
--- /dev/null
+++ b/web/src/component/OCPPreCheck/CheckInfo/index.less
@@ -0,0 +1,82 @@
+@cardBackgroundColor: #f8fafe;
+@subTitleColor: #5c6b8a;
+
+.pageCard {
+ box-shadow: 0 2px 4px 0 rgba(19, 32, 57, 0.02),
+ 0 1px 6px -1px rgba(19, 32, 57, 0.02), 0 1px 2px 0 rgba(19, 32, 57, 0.03);
+ :global {
+ .ant-space-item {
+ .ant-col {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+ }
+ }
+ .dbTable {
+ :global {
+ .ant-table-tbody > tr > td {
+ border-bottom: 1px solid #e2e8f3 !important;
+ }
+ .ant-table-tbody > tr > td,
+ .ant-table-thead > tr > th {
+ background-color: #f5f8fe !important;
+ }
+ .ant-table-tbody > tr:last-child > td {
+ border: none !important;
+ }
+ .ant-table-tbody > tr:last-child > td:first-child {
+ border-bottom-left-radius: 8px !important;
+ }
+ .ant-table-tbody > tr:last-child > td:last-child {
+ border-bottom-right-radius: 8px !important;
+ }
+ }
+ }
+}
+.infoSubCard {
+ overflow: hidden;
+ border-radius: 8px;
+ .pwdIcon {
+ position: absolute;
+ top: 2px;
+ right: 0;
+ color: #8592ad;
+ font-size: 17px;
+ }
+ .versionTag{
+ position: absolute;
+ right: 45px;
+ }
+ :global {
+ .ant-pro-card .ant-pro-card-body {
+ padding-top: 4px !important;
+ }
+
+ .ant-pro-card-body,
+ .ant-pro-card-header {
+ background-color: @cardBackgroundColor !important;
+ }
+ .ant-pro-card-title {
+ color: @subTitleColor !important ;
+ font-weight: normal !important;
+ font-size: 14px !important;
+ }
+ .ant-pro-card-body {
+ color: #132039 !important;
+ }
+ .ant-pro-card-col.ant-pro-card-split-vertical {
+ border-inline-end: none !important;
+ .ant-pro-card::before {
+ position: absolute;
+ top: 50%;
+ width: 1px;
+ height: 48px;
+ background-color: #e8eaf3;
+ transform: translateY(-50%);
+ transition: #e8eaf3 0.3s;
+ content: '';
+ inset-inline-end: 0;
+ }
+ }
+ }
+}
diff --git a/web/src/component/OCPPreCheck/CheckInfo/index.tsx b/web/src/component/OCPPreCheck/CheckInfo/index.tsx
new file mode 100644
index 0000000..17ecee7
--- /dev/null
+++ b/web/src/component/OCPPreCheck/CheckInfo/index.tsx
@@ -0,0 +1,258 @@
+import { intl } from '@/utils/intl';
+import { Button, Space, Alert, Row, Col } from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import { useModel } from 'umi';
+import useRequest from '@/utils/useRequest';
+import { getErrorInfo } from '@/utils';
+import { createOcpDeploymentConfig } from '@/services/ocp_installer_backend/OCP';
+import CustomFooter from '../../CustomFooter';
+import BasicInfo from './BasicInfo';
+import ConfigInfo from './ConfigInfo';
+import ConnectInfo from './ConnectInfo';
+import ResourceInfo from './ResourceInfo';
+import ExitBtn from '@/component/ExitBtn';
+import { formatPreCheckData } from '../helper';
+import styles from '../index.less';
+import type {
+ BasicInfoProp,
+ ProductInfoType,
+ ConnectInfoType,
+ ConnectInfoPropType,
+ ResourceInfoPropType,
+} from './type';
+
+interface CheckInfoProps {
+ showNext: React.Dispatch>;
+ isNewDB: boolean;
+}
+
+export const leftCardStyle = { width: 211 };
+
+export default function CheckInfo({
+ showNext,
+ current,
+ setCurrent,
+ isNewDB,
+}: CheckInfoProps & API.StepProp) {
+ const { ocpConfigData, setErrorVisible, setErrorsList, errorsList } =
+ useModel('global');
+ const { setConnectId, obVersionInfo, ocpVersionInfo, obproxyVersionInfo } =
+ useModel('ocpInstallData');
+ const { components = {}, auth = {} } = ocpConfigData;
+ const { oceanbase = {}, obproxy = {}, ocpserver = {} } = components;
+ const basicInfoProp: BasicInfoProp = {
+ appname: oceanbase.appname,
+ type: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.InstallAll',
+ defaultMessage: '全部安装',
+ }),
+ productsInfo: [
+ {
+ productName: 'OCP',
+ version: ocpserver.version,
+ isCommunity: ocpVersionInfo?.versionType === 'ce',
+ },
+ ],
+ };
+ const configInfoProp: ConnectInfoPropType = {
+ userConfig: { ...auth },
+ ocpNodeConfig: ocpserver.servers,
+ clusterConfig: {
+ info: {
+ root_password: oceanbase.root_password,
+ home_path: oceanbase.home_path,
+ data_dir: oceanbase.data_dir,
+ redo_dir: oceanbase.redo_dir,
+ mysql_port: oceanbase.mysql_port,
+ rpc_port: oceanbase.rpc_port,
+ },
+ },
+ dbNode: oceanbase.topology,
+ obproxyConfig: {
+ info: {
+ servers: obproxy.servers,
+ home_path: obproxy.home_path,
+ listen_port: obproxy.listen_port,
+ prometheus_listen_port: obproxy.prometheus_listen_port,
+ },
+ },
+ };
+ const connectInfoProp: ConnectInfoType[] = [
+ [
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.HostIp',
+ defaultMessage: '主机IP',
+ }),
+ value: ocpserver?.metadb?.host,
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.AccessPort',
+ defaultMessage: '访问端口',
+ }),
+ value: ocpserver?.metadb?.port,
+ },
+ ],
+
+ [
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.AccessAccount',
+ defaultMessage: '访问账号',
+ }),
+ value: ocpserver?.metadb?.user,
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.OCPPreCheck.CheckInfo.Password',
+ defaultMessage: '密码',
+ }),
+ value: ocpserver?.metadb?.password,
+ },
+ ],
+ ];
+
+ const resourceInfoProp: ResourceInfoPropType = {
+ serviceConfig: {
+ admin_password: ocpserver.admin_password,
+ home_path: ocpserver.home_path,
+ log_dir: ocpserver.log_dir,
+ soft_dir: ocpserver.soft_dir,
+ ocp_site_url: ocpserver.ocp_site_url,
+ },
+ resourcePlan: { ...ocpserver.manage_info },
+ memory_size: ocpserver.memory_size,
+ tenantConfig: {
+ info: {
+ tenant_name: ocpserver?.meta_tenant?.name?.tenant_name,
+ password: ocpserver?.meta_tenant?.password,
+ },
+ resource: { ...ocpserver?.meta_tenant?.resource },
+ },
+ monitorConfig: {
+ info: {
+ tenant_name: ocpserver?.monitor_tenant?.name?.tenant_name,
+ password: ocpserver?.monitor_tenant?.password,
+ },
+ resource: { ...ocpserver?.monitor_tenant?.resource },
+ },
+ };
+ if (isNewDB) {
+ let extraProducts: ProductInfoType[] = [
+ {
+ productName: 'OceanBase',
+ version: oceanbase.version,
+ isCommunity: obVersionInfo?.versionType === 'ce',
+ },
+ {
+ productName: 'OBProxy',
+ version: obproxy.version,
+ },
+ ];
+
+ basicInfoProp.productsInfo.push(...extraProducts);
+ }
+ if (!isNewDB) {
+ resourceInfoProp.userConfig = { ...auth };
+ resourceInfoProp.ocpServer = ocpserver.servers;
+ }
+ const { run: handleCreateConfig, loading } = useRequest(
+ createOcpDeploymentConfig,
+ {
+ onSuccess: ({ success, data }: API.OBResponse) => {
+ if (success) {
+ setConnectId(data);
+ showNext(true);
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const nextStep = () => {
+ handleCreateConfig(
+ { name: oceanbase?.appname },
+ formatPreCheckData(ocpConfigData),
+ );
+ };
+ const prevStep = () => {
+ setCurrent(current - 1);
+ };
+ return (
+
+
+
+
+
+
+
+
+ {!isNewDB ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/component/OCPPreCheck/CheckInfo/type.ts b/web/src/component/OCPPreCheck/CheckInfo/type.ts
new file mode 100644
index 0000000..643bd61
--- /dev/null
+++ b/web/src/component/OCPPreCheck/CheckInfo/type.ts
@@ -0,0 +1,78 @@
+export type BasicInfoProp = {
+ appname: string;
+ type: string;
+ productsInfo: ProductInfoType[];
+};
+
+export type ProductInfoType = {
+ productName: string;
+ version: string;
+ isCommunity?: boolean;
+};
+
+export type DBNodeType = {
+ id?: string;
+ name: string;
+ rootservice: string;
+ servers: string[];
+};
+
+export type ConnectInfoType = { label: string; value: string }[];
+
+export type ConnectInfoPropType = {
+ userConfig: { user: string; password: string; port: number };
+ ocpNodeConfig: string[];
+ dbNode: DBNodeType[];
+ clusterConfig: {
+ info: {
+ root_password: string;
+ home_path: string;
+ data_dir: string;
+ redo_dir: string;
+ mysql_port: number;
+ rpc_port: number;
+ };
+ more?: any;
+ };
+ obproxyConfig: {
+ info: {
+ servers: string[];
+ home_path: string;
+ listen_port: number;
+ prometheus_listen_port: number;
+ };
+ more?: any;
+ };
+};
+
+export type ResourceInfoPropType = {
+ userConfig?: { user: string; password: string; port: number };
+ serviceConfig: {
+ admin_password: string;
+ home_path: string;
+ log_dir: string;
+ soft_dir: string;
+ ocp_site_url: string;
+ };
+ resourcePlan: {
+ cluster: number;
+ tenant: number;
+ machine: number;
+ };
+ memory_size: string;
+ tenantConfig: {
+ info: {
+ tenant_name: string;
+ password: string;
+ };
+ resource: { cpu: number; memory: number };
+ };
+ monitorConfig: {
+ info: {
+ tenant_name: string;
+ password: string;
+ };
+ resource: { cpu: number; memory: number };
+ };
+ ocpServer?: string[];
+};
diff --git a/web/src/component/OCPPreCheck/PreCheck/index.tsx b/web/src/component/OCPPreCheck/PreCheck/index.tsx
new file mode 100644
index 0000000..c468fe8
--- /dev/null
+++ b/web/src/component/OCPPreCheck/PreCheck/index.tsx
@@ -0,0 +1,826 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import {
+ Space,
+ Button,
+ Progress,
+ Timeline,
+ Checkbox,
+ Typography,
+ Tooltip,
+ Tag,
+ Spin,
+ message,
+ Empty,
+} from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import {
+ CloseOutlined,
+ QuestionCircleFilled,
+ ReadFilled,
+ CheckCircleFilled,
+ CloseCircleFilled,
+} from '@ant-design/icons';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import customRequest from '@/utils/useRequest';
+import { useRequest } from 'ahooks';
+import {
+ deployAndStartADeployment,
+ createDeploymentConfig,
+} from '@/services/ob-deploy-web/Deployments';
+import { handleQuit, getErrorInfo, errorHandler } from '@/utils';
+import CustomFooter from '../../CustomFooter';
+import ExitBtn from '@/component/ExitBtn';
+import NP from 'number-precision';
+import { getLocale } from 'umi';
+import ZhStyles from '@/pages/Obdeploy/indexZh.less';
+import EnStyles from '@/pages/Obdeploy/indexEn.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+const { Text } = Typography;
+
+const statusColorConfig = {
+ PASSED: 'green',
+ PENDING: 'gray',
+ FAILED: 'red',
+};
+
+let timerScroll: NodeJS.Timer;
+let timerFailed: NodeJS.Timer;
+const initDuration = 3;
+let durationScroll = initDuration;
+let durationFailed = initDuration;
+
+const errCodeUrl = 'https://www.oceanbase.com/product/ob-deployer/error-codes';
+interface PreCheckProps {
+ isNewDB: boolean;
+}
+
+// result: RUNNING | FALIED | SUCCESSFUL
+// status: RUNNING | FINISHED
+export default function PreCheck({
+ current,
+ setCurrent,
+ isNewDB,
+}: PreCheckProps & API.StepProp) {
+ const {
+ setCheckOK,
+ handleQuitProgress,
+ getInfoByName,
+ setConfigData,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ ocpConfigData,
+ } = useModel('global');
+ const { setInstallTaskId } = useModel('ocpInstallData');
+
+ const { connectId } = useModel('ocpInstallData');
+ const oceanbase = ocpConfigData?.components?.oceanbase;
+ const name = ocpConfigData?.components?.oceanbase?.appname;
+ const [statusData, setStatusData] = useState({});
+ const [failedList, setFailedList] = useState([]);
+ const [showFailedList, setShowFailedList] = useState([]);
+ const [hasAuto, setHasAuto] = useState(false);
+ const [hasManual, setHasManual] = useState(false);
+ const [onlyManual, setOnlyManual] = useState(false);
+ const [checkFinished, setCheckFinished] = useState(false);
+ const [isScroll, setIsScroll] = useState(false);
+ const [isScrollFailed, setIsScrollFailed] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [checkStatus, setCheckStatus] = useState(true);
+ const [currentPage, setCurrentPage] = useState(true);
+ const [firstErrorTimestamp, setFirstErrorTimestamp] = useState();
+ const {
+ data: precheckOcpDatas,
+ run: precheckOcp,
+ refresh,
+ loading: ocpPrecheckLoading,
+ } = useRequest(OCP.precheckOcp, {
+ manual: true,
+ onSuccess: (res: any) => {
+ if (res?.success) {
+ const { data } = res;
+ let timer: NodeJS.Timer;
+ data.finished =
+ data?.task_info?.info.filter(
+ (item) => item.result === 'SUCCESSFUL' || item.result === 'FAILED',
+ ).length || 0;
+ data.total = data?.precheck_result?.length || 0;
+ data.all_passed = data.task_info.result === 'SUCCESSFUL';
+ setStatusData(data || {});
+ if (data?.task_info?.status === 'RUNNING') {
+ timer = setTimeout(() => {
+ refresh();
+ }, 2000);
+ }
+ if (
+ data?.task_info?.result === 'FAILED' &&
+ data?.precheck_result.find((item: any) => item.result === 'RUNNING')
+ ) {
+ const newFailedList =
+ data?.precheck_result?.filter(
+ (item: any) => item.result === 'FAILED',
+ ) || [];
+ setShowFailedList(newFailedList);
+ setFailedList(newFailedList);
+ let errorInfo: API.ErrorInfo = {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.RequestError',
+ defaultMessage: '请求错误',
+ }),
+ desc: data?.message,
+ };
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ setCheckStatus(false);
+ setCheckFinished(true);
+ } else {
+ if (data.all_passed) {
+ setFailedList([]);
+ setShowFailedList([]);
+ } else {
+ const newFailedList =
+ data?.precheck_result?.filter(
+ (item: any) => item.result === 'FAILED',
+ ) || [];
+ newFailedList.forEach((item: any) => {
+ if (item.recoverable) {
+ setHasAuto(true);
+ } else {
+ setHasManual(true);
+ }
+ });
+ setFailedList(newFailedList);
+ setShowFailedList(newFailedList);
+ }
+ // const isFinished = !!data?.total && data?.finished === data?.total;
+ const isFinished = data.task_info.status === 'FINISHED';
+ setCheckFinished(isFinished);
+ if (isFinished) {
+ clearTimeout(timer);
+ }
+ if (!isScroll && !isFinished) {
+ setTimeout(() => {
+ const timelineContainer =
+ document.getElementById('timeline-container');
+ const runningItemDom = document.getElementById(
+ 'running-timeline-item',
+ );
+ if (timelineContainer) {
+ timelineContainer.scrollTop = NP.minus(
+ NP.strip(runningItemDom?.offsetTop),
+ 150,
+ );
+ }
+ }, 10);
+ }
+
+ if (!isScrollFailed && !isFinished && failedList) {
+ setTimeout(() => {
+ const failedContainer =
+ document.getElementById('failed-container');
+ if (failedContainer) {
+ failedContainer.scrollTop = NP.strip(
+ failedContainer?.scrollHeight,
+ );
+ }
+ }, 10);
+ }
+ setCheckStatus(true);
+ }
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ // 发起OCP的预检查
+ const {
+ run: precheckOcpDeployment,
+ refresh: rePrecheckOcpDeployment,
+ loading: preCheckLoading,
+ } = useRequest(OCP.precheckOcpDeployment, {
+ manual: true,
+ onSuccess: (res: any) => {
+ if (res.success) {
+ precheckOcp({
+ id: connectId,
+ task_id: res.data?.id,
+ });
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const { run: handleInstallConfirm } = customRequest(
+ deployAndStartADeployment,
+ {
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const handelCheck = async () => {
+ setLoading(true);
+ try {
+ if (connectId) {
+ await precheckOcpDeployment({ id: connectId });
+ }
+ } catch {
+ setLoading(false);
+ }
+ };
+
+ const { run: handleCreateConfig, loading: createLoading } = useRequest(
+ createDeploymentConfig,
+ {
+ manual: true,
+ onSuccess: ({ success }: API.OBResponse) => {
+ if (success) {
+ handelCheck();
+ }
+ setLoading(false);
+ },
+ onError: (e: any) => {
+ setCheckStatus(false);
+ if (loading) {
+ setLoading(false);
+ }
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const handleRetryCheck = (newConfigData?: any) => {
+ setStatusData({});
+ setFailedList([]);
+ setShowFailedList([]);
+ setCheckFinished(false);
+ let params = { ...ocpConfigData };
+ if (newConfigData) {
+ params = { ...newConfigData };
+ }
+ precheckOcpDeployment({ id: connectId });
+ };
+
+ const { run: handleRecover, loading: recoverLoading } = useRequest(
+ OCP.recoverOcpDeployment,
+ {
+ manual: true,
+ onSuccess: async ({ success }) => {
+ if (success) {
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.OCPPreCheck.PreCheck.AutomaticRepairSucceeded',
+ defaultMessage: '自动修复成功',
+ }),
+ );
+ try {
+ rePrecheckOcpDeployment();
+ } catch (e: any) {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const prevStep = () => {
+ setCheckOK(false);
+ setCurrent(current - 1);
+ setCurrentPage(false);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ };
+
+ const { run: installOcp, loading: precheckLoading } = useRequest(
+ OCP.installOcp,
+ {
+ manual: true,
+ onSuccess: ({ data, success }) => {
+ if (success) {
+ setInstallTaskId(data?.id);
+ setCurrent(5);
+ setCurrent(current + 1);
+ setCurrentPage(false);
+ setErrorVisible(false);
+ setErrorsList([]);
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const handleInstall = async () => {
+ installOcp({
+ id: connectId,
+ });
+ };
+
+ const handleScrollTimeline = () => {
+ if (!checkFinished) {
+ setIsScroll(true);
+ clearInterval(timerScroll);
+ durationScroll = initDuration;
+ timerScroll = setInterval(() => {
+ if (durationScroll === 0) {
+ clearInterval(timerScroll);
+ setIsScroll(false);
+ durationScroll = initDuration;
+ } else {
+ durationScroll -= 1;
+ }
+ }, 1000);
+ }
+ };
+
+ const handleScrollFailed = () => {
+ if (!checkFinished) {
+ setIsScrollFailed(true);
+ clearInterval(timerFailed);
+ durationFailed = initDuration;
+ timerFailed = setInterval(() => {
+ if (durationFailed === 0) {
+ clearInterval(timerFailed);
+ setIsScrollFailed(false);
+ durationFailed = initDuration;
+ } else {
+ durationFailed -= 1;
+ }
+ }, 1000);
+ }
+ };
+
+ const handleAutoRepair = () => {
+ setHasAuto(false);
+ handleRecover({ id: connectId });
+ };
+
+ useEffect(() => {
+ if (onlyManual) {
+ const newShowFailedList = failedList.filter((item) => !item.recoverable);
+ setShowFailedList(newShowFailedList);
+ } else {
+ setShowFailedList(failedList);
+ }
+ }, [onlyManual]);
+
+ useEffect(() => {
+ precheckOcpDeployment({ id: connectId });
+ const timelineContainer = document.getElementById('timeline-container');
+ timelineContainer.onmousewheel = handleScrollTimeline; // ie , chrome
+ timelineContainer?.addEventListener('DOMMouseScroll', handleScrollTimeline); // firefox
+ return () => {
+ timelineContainer.onmousewheel = () => {};
+ timelineContainer?.removeEventListener(
+ 'DOMMouseScroll',
+ handleScrollTimeline,
+ );
+ };
+ }, []);
+
+ useEffect(() => {
+ const addEventFailedContainer = () => {
+ const failedContainer = document.getElementById('failed-container');
+ if (failedList?.length && failedContainer) {
+ if (!failedContainer.onmousewheel) {
+ failedContainer.onmousewheel = handleScrollFailed; // ie , chrome
+ failedContainer?.addEventListener(
+ 'DOMMouseScroll',
+ handleScrollFailed,
+ );
+ // firefox
+ }
+ } else {
+ setTimeout(() => {
+ addEventFailedContainer();
+ }, 3000);
+ }
+ };
+
+ addEventFailedContainer();
+ return () => {
+ const failedContainer = document.getElementById('failed-container');
+ if (failedContainer) {
+ failedContainer.onmousewheel = () => {};
+ failedContainer?.removeEventListener(
+ 'DOMMouseScroll',
+ handleScrollFailed,
+ );
+ }
+ };
+ }, [failedList]);
+
+ let progressStatus = 'active';
+ if (statusData?.task_info?.status === 'FAILED') {
+ progressStatus = 'exception';
+ } else if (checkFinished) {
+ if (statusData.all_passed) {
+ progressStatus = 'success';
+ } else {
+ progressStatus = 'exception';
+ }
+ }
+
+ const shape = (
+
+ );
+
+ const checkItemLength = `${statusData?.finished || 0}/${
+ statusData?.total || 0
+ }`;
+ const failedItemLength = failedList?.length;
+
+ return (
+
+
+ handleRetryCheck()}
+ data-aspm-click="ca54438.da43444"
+ data-aspm-desc={intl.formatMessage({
+ id: 'OBD.OCPPreCheck.PreCheck.PreCheckResultReCheck',
+ defaultMessage: '预检查结果-重新检查',
+ })}
+ data-aspm-param={``}
+ data-aspm-expo
+ >
+ {intl.formatMessage({
+ id: 'OBD.OCPPreCheck.PreCheck.ReCheck',
+ defaultMessage: '重新检查',
+ })}
+
+ }
+ headStyle={{ paddingLeft: '16px', paddingRight: '16px' }}
+ >
+
+
+ {loading ? null : (
+ <>
+
+
+
+ {statusData?.precheck_result?.map(
+ (item: API.PreCheckInfo, index: number) => {
+ //根据索引找到对应项
+ const task_info_item =
+ statusData?.task_info?.info[index - 1];
+ return (
+
+ ) : (
+
+ )
+ ) : null
+ }
+ >
+ {item?.name} {item?.server}
+
+ );
+ },
+ )}
+
+ >
+ )}
+
+
+ {hasManual ? (
+ setOnlyManual(e.target.checked)}
+ disabled={!checkFinished || statusData?.all_passed}
+ >
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.OnlyManualFixes',
+ defaultMessage: '只看手动修复项',
+ })}
+
+ ) : null}
+
+
+ }
+ >
+ {showFailedList?.length ? (
+
+ {showFailedList?.map((item, index) => {
+ let reason = '';
+ if (item?.advisement) {
+ const index = item?.advisement.indexOf(':');
+ reason = item?.advisement.substring(
+ index,
+ item?.advisement.length,
+ );
+ }
+ return (
+
+
+
+
+
+ {item.name}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.Reason',
+ defaultMessage: '原因:',
+ })}
+
+ ERR-{item.code}
+ {' '}
+ {reason}
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.Suggestions',
+ defaultMessage: '建议:',
+ })}
+ {item.recoverable ? (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.AutomaticRepair',
+ defaultMessage: '自动修复',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.ManualRepair',
+ defaultMessage: '手动修复',
+ })}
+
+ )}{' '}
+ {item.advisement}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.LearnMore',
+ defaultMessage: '了解更多方案',
+ })}
+
+
+
+ );
+ })}
+ {!checkFinished ? (
+
{shape}
+ ) : null}
+
+ ) : checkFinished ? (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.GreatNoFailedItems',
+ defaultMessage: '太棒了!无失败项',
+ })}
+
+ }
+ />
+ ) : (
+
+ {shape}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.NoFailedItemsFoundYet',
+ defaultMessage: '暂未发现失败项',
+ })}
+
+
+ )}
+
+
+
+ {' '}
+
+
+ {!statusData?.all_passed ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/web/src/component/OCPPreCheck/helper.ts b/web/src/component/OCPPreCheck/helper.ts
new file mode 100644
index 0000000..2b6582f
--- /dev/null
+++ b/web/src/component/OCPPreCheck/helper.ts
@@ -0,0 +1,60 @@
+import _ from 'lodash';
+
+export const formatPreCheckData = (configData: any) => {
+ let _configData = _.cloneDeep(configData);
+ let { memory_size } = _configData.components.ocpserver;
+ _configData.components.oceanbase.mode = 'PRODUCTION';
+ if (typeof memory_size === 'number') {
+ _configData.components.ocpserver.memory_size = memory_size + 'G';
+ }
+ if (
+ _configData.components.oceanbase &&
+ _configData.components.oceanbase.topology
+ ) {
+ let {parameters,topology} = _configData.components.oceanbase
+ for (let item of topology) {
+ delete item.id;
+ }
+ for(let idx = 0;idx {
+ return {
+ key: parameter.key,
+ value: parameter.value,
+ adaptive: parameter.adaptive,
+ };
+ });
+ }
+ if (
+ (key === 'oceanbase' || key === 'obproxy') &&
+ item.home_path &&
+ !item.home_path.split('/').includes(key)
+ ) {
+ item.home_path += `/${key}`;
+ }
+ if (
+ key === 'ocpserver' &&
+ !item.home_path.split('/').includes('ocpserver')
+ ) {
+ item.home_path += '/ocp';
+ }
+ }
+
+ if (location.hash.split('/').pop() !== 'install') {
+ return {
+ ..._configData,
+ auth: _configData.auth,
+ components: {
+ ocpserver: _configData.components.ocpserver,
+ },
+ };
+ }
+ return _configData;
+};
diff --git a/web/src/component/OCPPreCheck/index.less b/web/src/component/OCPPreCheck/index.less
new file mode 100644
index 0000000..a31f13b
--- /dev/null
+++ b/web/src/component/OCPPreCheck/index.less
@@ -0,0 +1,16 @@
+@smallSpace: 8px;
+.checkInfoSpace {
+ width: 100%;
+ :global {
+ .ant-col {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+ .ant-col.ant-col-12:nth-child(2n + 1) {
+ padding-right: @smallSpace !important;
+ }
+ .ant-col.ant-col-12:nth-child(2n) {
+ padding-left: @smallSpace !important;
+ }
+ }
+}
diff --git a/web/src/component/OCPPreCheck/index.tsx b/web/src/component/OCPPreCheck/index.tsx
new file mode 100644
index 0000000..dff9a07
--- /dev/null
+++ b/web/src/component/OCPPreCheck/index.tsx
@@ -0,0 +1,19 @@
+import { useState } from 'react';
+
+import CheckInfo from './CheckInfo';
+import PreCheck from './PreCheck';
+import { getTailPath } from '@/utils/helper';
+
+export default function OCPPreCheck(prop: API.StepProp) {
+ const [preCheckVisible, setPreCheckVisible] = useState(false);
+ const isNewDB = getTailPath() === 'install'
+ return (
+ <>
+ {!preCheckVisible ? (
+
+ ) : (
+
+ )}
+ >
+ );
+}
diff --git a/web/src/component/PageCard/index.less b/web/src/component/PageCard/index.less
new file mode 100644
index 0000000..40fb3ce
--- /dev/null
+++ b/web/src/component/PageCard/index.less
@@ -0,0 +1,7 @@
+.card {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 100%;
+ padding: 24px 64px;
+}
diff --git a/web/src/component/PageCard/index.tsx b/web/src/component/PageCard/index.tsx
new file mode 100644
index 0000000..db3ba9a
--- /dev/null
+++ b/web/src/component/PageCard/index.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Card, Spin } from '@oceanbase/design';
+import styles from './index.less';
+
+export interface TaskSuccessProps {
+ children: React.ReactNode;
+ loading?: boolean;
+ style?: React.CSSProperties;
+ className?: string;
+}
+
+const PageCard: React.FC = ({
+ children,
+ loading = false,
+ className,
+ ...restProps
+}) => (
+
+ {children}
+
+);
+
+export default PageCard;
diff --git a/web/src/component/PageLoading/index.tsx b/web/src/component/PageLoading/index.tsx
new file mode 100644
index 0000000..3187020
--- /dev/null
+++ b/web/src/component/PageLoading/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { Spin } from '@oceanbase/design';
+import type { SpinProps } from 'antd/es/spin';
+
+const PageLoading: React.FC = props => (
+
+
+
+);
+
+export default PageLoading;
diff --git a/web/src/component/Password.tsx b/web/src/component/Password.tsx
new file mode 100644
index 0000000..bd1d24b
--- /dev/null
+++ b/web/src/component/Password.tsx
@@ -0,0 +1,43 @@
+import { intl } from '@/utils/intl';
+import React from 'react';
+import { Password } from '@oceanbase/ui';
+import type { PasswordProps } from '@oceanbase/ui/es/Password';
+
+const OCPPassword: React.FC = (props) => {
+ // 特殊字符支持 ~!@#%^&*_\-+=`|(){}[]:;',.?/
+ const ocpPasswordRules = [
+ {
+ validate: (val?: string) => val?.length >= 8 && val?.length <= 32,
+ message: intl.formatMessage({
+ id: 'OBD.src.component.Password.ToCharactersInLength',
+ defaultMessage: '长度为 8~32 个字符',
+ }),
+ },
+
+ {
+ validate: (val?: string) =>
+ /^[0-9a-zA-Z~!@#%^&*_\-+=|(){}\[\]:;,.?/]+$/.test(val),
+ message: intl.formatMessage({
+ id: 'OBD.src.component.Password.CanOnlyContainLettersNumbers',
+ defaultMessage:
+ '只能包含字母、数字和特殊字符(~!@#%^&*_-+=|(){}[]:;,.?/)',
+ }),
+ },
+
+ {
+ validate: (val?: string) =>
+ /^(?=(.*[a-z]){2,})(?=(.*[A-Z]){2,})(?=(.*\d){2,})(?=(.*[~!@#%^&*_\-+=|(){}\[\]:;,.?/]){2,})[A-Za-z\d~!@#%^&*_\-+=|(){}\[\]:;,.?/]{2,}$/.test(
+ val,
+ ),
+
+ message: intl.formatMessage({
+ id: 'OBD.src.component.Password.AtLeastUppercaseAndLowercase',
+ defaultMessage: '大小写字母、数字和特殊字符都至少包含 2 个',
+ }),
+ },
+ ];
+
+ return ;
+};
+
+export default OCPPassword;
diff --git a/web/src/component/PasswordCard/index.less b/web/src/component/PasswordCard/index.less
new file mode 100644
index 0000000..c144c1a
--- /dev/null
+++ b/web/src/component/PasswordCard/index.less
@@ -0,0 +1,9 @@
+.passwordCardContainer {
+ .pwdIcon {
+ position: absolute;
+ top: 2px;
+ right: 0;
+ color: #8592ad;
+ font-size: 17px;
+ }
+}
diff --git a/web/src/component/PasswordCard/index.tsx b/web/src/component/PasswordCard/index.tsx
new file mode 100644
index 0000000..b15f234
--- /dev/null
+++ b/web/src/component/PasswordCard/index.tsx
@@ -0,0 +1,51 @@
+import { intl } from '@/utils/intl';
+import { useState } from 'react';
+import { ProCard } from '@ant-design/pro-components';
+import { Tooltip } from 'antd';
+import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
+
+import styles from './index.less';
+
+export default function PasswordCard({ password }: { password: string }) {
+ const [showPwd, setShowPwd] = useState(false);
+ return (
+
+ {password ? (
+
+ {showPwd ? (
+
+
+
+ {password}
+
+
+
setShowPwd(false)}
+ />
+
+ ) : (
+
+ {password.replace(/./g, '*')}
+ setShowPwd(true)}
+ />
+
+ )}
+
+ ) : (
+ '-'
+ )}
+
+ );
+}
diff --git a/web/src/component/Result/index.less b/web/src/component/Result/index.less
new file mode 100644
index 0000000..ee900be
--- /dev/null
+++ b/web/src/component/Result/index.less
@@ -0,0 +1,5 @@
+.container {
+ .detail {
+ background-color: #fafafa;
+ }
+}
diff --git a/web/src/component/Result/index.tsx b/web/src/component/Result/index.tsx
new file mode 100644
index 0000000..c4cac07
--- /dev/null
+++ b/web/src/component/Result/index.tsx
@@ -0,0 +1,72 @@
+import { intl } from '@/utils/intl';
+import { history } from 'umi';
+import React from 'react';
+import { Button, Result, Space } from '@oceanbase/design';
+import type { ResultProps } from 'antd/es/result';
+import { PageContainer } from '@oceanbase/ui';
+
+import PageCard from '@/component/PageCard';
+import styles from './index.less';
+
+export interface SuccessProps extends ResultProps {
+ iconUrl?: string;
+ taskId?: number;
+ children?: React.ReactNode;
+ style?: React.CSSProperties;
+ className?: string;
+}
+
+const Success: React.FC = ({
+ iconUrl = '/assets/icon/success.svg',
+ taskId,
+ children,
+ className,
+ style,
+ ...restProps
+}) => {
+ return (
+
+
+ }
+ extra={
+
+
+ {taskId && (
+
+ )}
+
+ }
+ {...restProps}
+ >
+ {children}
+
+
+
+ );
+};
+
+export default Success;
diff --git a/web/src/component/SliderAndInputNumber/index.less b/web/src/component/SliderAndInputNumber/index.less
new file mode 100644
index 0000000..f410ec3
--- /dev/null
+++ b/web/src/component/SliderAndInputNumber/index.less
@@ -0,0 +1,18 @@
+.SliderAndInputNumber {
+ :global {
+ .ant-slider {
+ margin: 0 5px;
+ .ant-slider-rail {
+ background-color: #E2E8F3;
+ }
+ }
+ }
+ .marks {
+ color: #5C6B8A;
+ display: flex;
+ margin: 0 5px;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
\ No newline at end of file
diff --git a/web/src/component/SliderAndInputNumber/index.tsx b/web/src/component/SliderAndInputNumber/index.tsx
new file mode 100644
index 0000000..10691a0
--- /dev/null
+++ b/web/src/component/SliderAndInputNumber/index.tsx
@@ -0,0 +1,67 @@
+import React, { useState, useEffect } from 'react';
+import { InputNumber, Slider, Row, Col } from '@oceanbase/design';
+
+import styles from './index.less';
+
+export interface SliderAndInputNumberProps {
+ max: number;
+ min: number;
+ value?: number;
+ addonAfter?: string;
+ onChange?: (value: number) => void;
+}
+
+const SliderAndInputNumber: React.FC = ({
+ max,
+ min,
+ value,
+ addonAfter = 'GiB',
+ onChange,
+}) => {
+ const [currentValue, setCurrentValue] = useState(value || 0);
+
+ useEffect(() => {
+ if (value) {
+ setCurrentValue(value);
+ }
+ }, [value]);
+
+ const onHandleChange = (val: number) => {
+ setCurrentValue(val);
+ if (onChange) {
+ onChange(val);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SliderAndInputNumber;
diff --git a/web/src/component/Steps/index.tsx b/web/src/component/Steps/index.tsx
new file mode 100644
index 0000000..efcb445
--- /dev/null
+++ b/web/src/component/Steps/index.tsx
@@ -0,0 +1,101 @@
+import { intl } from '@/utils/intl';
+import { Space } from 'antd';
+import { ClockCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import { getLocale } from 'umi';
+import EnStyles from '../../pages/Obdeploy/indexEn.less';
+import ZhStyles from '../../pages/Obdeploy/indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+import { useEffect, useState } from 'react';
+
+interface StepsProps {
+ stepsItems: {
+ title: string;
+ key: number;
+ }[];
+ currentStep: number;
+ showStepsKeys: number[];
+}
+
+export default function Steps({
+ currentStep,
+ showStepsKeys,
+ stepsItems,
+}: StepsProps) {
+ const [showBorder, setShowBorder] = useState(false);
+ const getIcon = (key: number) => {
+ return currentStep > key ? (
+
+ ) : (
+
+ );
+ };
+
+ const handleScroll = () => {
+ if (document.documentElement.scrollTop > 0) {
+ setShowBorder(true);
+ } else {
+ setShowBorder(false);
+ }
+ };
+
+ const getZhGap = () => {
+ if (showStepsKeys.length === 4) {
+ return 150;
+ }
+ return 100;
+ };
+ const getEnGap = () => {
+ if (showStepsKeys.length === 4) {
+ return 60;
+ }
+ return 0;
+ };
+
+ useEffect(() => {
+ document.addEventListener('scroll', handleScroll);
+ }, []);
+
+ return (
+
+ {showStepsKeys.includes(currentStep) ? (
+
+
+
+
+ {stepsItems.map((item) => (
+
+ {getIcon(item.key)}
+ item.key ? styles.stepAlreadyTitle : ''}`}
+ >
+ {item.title}
+
+
+ ))}
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/web/src/constant/configuration.ts b/web/src/constant/configuration.ts
new file mode 100644
index 0000000..1e12c76
--- /dev/null
+++ b/web/src/constant/configuration.ts
@@ -0,0 +1,136 @@
+import { intl } from '@/utils/intl';
+export const NEW_METADB_OCP_INSTALL = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.DeploymentConfiguration',
+ defaultMessage: '部署配置',
+ }),
+ key: 1,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.MetadbConfiguration',
+ defaultMessage: 'MetaDB 配置',
+ }),
+ key: 2,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.OcpConfiguration',
+ defaultMessage: 'OCP 配置',
+ }),
+ key: 3,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.PreCheck',
+ defaultMessage: '预检查',
+ }),
+ key: 4,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.Deployment',
+ defaultMessage: '部署',
+ }),
+ key: 5,
+ },
+];
+
+export const METADB_OCP_INSTALL = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.DeploymentConfiguration',
+ defaultMessage: '部署配置',
+ }),
+ key: 1,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.ObClusterConnectionConfiguration',
+ defaultMessage: 'OB集群 连接配置',
+ }),
+ key: 2,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.OcpConfiguration',
+ defaultMessage: 'OCP 配置',
+ }),
+ key: 3,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.PreCheck',
+ defaultMessage: '预检查',
+ }),
+ key: 4,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.Deployment',
+ defaultMessage: '部署',
+ }),
+ key: 5,
+ },
+];
+
+export const STEPS_KEYS_INSTALL = [1, 2, 3, 4, 5];
+export const STEPS_KEYS_UPDATE = [1, 2, 3, 4];
+export const METADB_OCP_UPDATE = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.DeploymentConfiguration',
+ defaultMessage: '部署配置',
+ }),
+ key: 1,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.ConnectivityTest',
+ defaultMessage: '联通性测试',
+ }),
+ key: 2,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.EnvironmentPreCheck',
+ defaultMessage: '环境预检查',
+ }),
+ key: 3,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.src.constant.configuration.OcpUpgrade',
+ defaultMessage: 'OCP升级',
+ }),
+ key: 4,
+ },
+];
+
+// ocp install
+export const CONFIG_KEYS = {
+ oceanbase: ['cpu_count', 'memory_limit', 'data_file', 'log_file'],
+ obproxy: ['cpu_count', 'memory_limit', 'data_file', 'log_file'],
+ // obagent: ['home_path', 'monagent_http_port', 'mgragent_http_port'],
+ // ocpexpress: ['home_path', 'port'],
+};
+export const showConfigKeys = {
+ oceanbase: [
+ 'home_path',
+ 'mode',
+ 'root_password',
+ 'data_dir',
+ 'redo_dir',
+ 'mysql_port',
+ 'rpc_port',
+ ],
+
+ obproxy: ['home_path', 'listen_port', 'prometheus_listen_port'],
+ obagent: ['home_path', 'monagent_http_port', 'mgragent_http_port'],
+ ocpexpress: ['home_path', 'port'],
+};
+
+export const ocpAddonAfter = '/ocp';
+export const obproxyAddonAfter = '/obproxy';
+export const oceanbaseAddonAfter = '/oceanbase';
diff --git a/web/src/constant/datetime.ts b/web/src/constant/datetime.ts
new file mode 100644
index 0000000..0a409fb
--- /dev/null
+++ b/web/src/constant/datetime.ts
@@ -0,0 +1,58 @@
+import moment from 'moment';
+
+const localeData = moment.localeData();
+
+export const FOREVER_TIME = '2099-12-31T00:00:00.000Z';
+
+/* 年 */
+export const YEAR_FORMAT = 'YYYY';
+
+export const YEAR_FORMAT_DISPLAY = localeData.longDateFormat('year');
+
+/* 月 */
+export const MONTH_FORMAT = 'YYYY-MM';
+
+export const MONTH_FORMAT_DISPLAY = localeData.longDateFormat('month');
+
+/* 日期 */
+export const DATE_FORMAT = 'YYYY-MM-DD';
+
+export const DATE_FORMAT_DISPLAY = localeData.longDateFormat('date');
+
+export const DATE_FORMAT_WITHOUT_YEAR_DISPLAY = localeData.longDateFormat('dateWithoutYear');
+
+/* 日期 + 时间 */
+
+// RFC3339 的日期时间格式
+export const RFC3339_DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
+
+// 日期时间格式
+export const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
+
+// 没有秒数据的日期时间格式
+export const DATE_TIME_FORMAT_WITHOUT_SECOND = 'YYYY-MM-DD HH:mm';
+
+export const DATE_TIME_FORMAT_DISPLAY = localeData.longDateFormat('datetime');
+
+export const DATE_TIME_FORMAT_WITH_SSS_DISPLAY = localeData.longDateFormat('datetimeWithSSS');
+
+export const DATE_TIME_FORMAT_WITHOUT_SECOND_DISPLAY =
+ localeData.longDateFormat('datetimeWithoutSecond');
+
+export const DATE_TIME_FORMAT_WITHOUT_YEAR_AND_SECOND_DISPLAY = localeData.longDateFormat(
+ 'datetimeWithoutYearAndSecond'
+);
+
+/* 时间 */
+
+// RFC3339 的时间格式
+export const RFC3339_TIME_FORMAT = 'HH:mm:ssZ';
+
+// 时间格式
+export const TIME_FORMAT = 'HH:mm:ss';
+
+// 带毫秒的时间格式
+export const TIME_FORMAT_WITH_SSS = 'HH:mm:ss.SSS';
+
+// 不带秒信息的时间格式
+export const TIME_FORMAT_WITHOUT_SECOND = 'HH:mm';
diff --git a/web/src/constant/docs.ts b/web/src/constant/docs.ts
new file mode 100644
index 0000000..7440516
--- /dev/null
+++ b/web/src/constant/docs.ts
@@ -0,0 +1,19 @@
+import { getLocale } from 'umi';
+
+const docsMap = {
+ 'zh-CN': 'https://www.oceanbase.com/docs',
+ 'en-US': 'https://en.oceanbase.com/docs',
+};
+// 文档首页路径
+export const DOCS_LINK = `/communityDocs/${docsMap[getLocale()]}/index.html`;
+// 部署向导帮助文档
+export const DOCS_PRODUCTION =
+ 'https://www.oceanbase.com/docs/community-obd-cn-1000000000314362';
+// SOP文档
+export const DOCS_SOP = 'https://ask.oceanbase.com/t/topic/35605473';
+// 用户帮助文档
+const DOCS_USER_CN =
+ 'https://www.oceanbase.com/docs/common-ocp-1000000000368844';
+const DOCS_USER_EN =
+ 'https://en.oceanbase.com/docs/common-ocp-10000000001064778';
+export const DOCS_USER = getLocale() === 'zh-CN' ? DOCS_USER_CN : DOCS_USER_EN;
diff --git a/web/src/constant/envPresCheck.ts b/web/src/constant/envPresCheck.ts
new file mode 100644
index 0000000..31db43d
--- /dev/null
+++ b/web/src/constant/envPresCheck.ts
@@ -0,0 +1,174 @@
+export const ERROR_CODE_LIST = [
+ {
+ value: '1000',
+ label: 'Configuration conflict x.x.x.x: xxx port is used for x.x.x.x',
+ },
+ {
+ value: '1001',
+ label: 'x.x.x.x:xxx port is already used',
+ },
+ {
+ value: '1002',
+ label: 'Fail to init x.x.x.x path',
+ },
+ {
+ value: '1003',
+ label: 'fail to clean x.x.x.x:xxx',
+ },
+ {
+ value: '1004',
+ label: 'Configuration conflict x.x.x.x: xxx is used for x.x.x.x',
+ },
+ {
+ value: '1005',
+ label: 'Some of the servers in the cluster have been stopped',
+ },
+ {
+ value: '1006',
+ label: 'Failed to connect to xxx',
+ },
+ {
+ value: '1007',
+ label: '(x.x.x.x) xxx must not be less than xxx (Current value: xxx)',
+ },
+ {
+ value: '1008',
+ label: '(x.x.x.x) failed to get fs.aio-max-nr and fs.aio-nr',
+ },
+ {
+ value: '1009',
+ label: 'x.x.x.x xxx need config: xxx',
+ },
+ {
+ value: '1010',
+ label: 'x.x.x.x No such net interface: xxx',
+ },
+ {
+ value: '1011',
+ label:
+ '(x.x.x.x) Insufficient AIO remaining (Avail: xxx, Need: xxx), The recommended value of fs.aio-max-nr is 1048576',
+ },
+ {
+ value: '1012',
+ label: 'xxx',
+ },
+ {
+ value: '1013',
+ label: 'xxx@x.x.x.x connect failed: xxx',
+ },
+ {
+ value: '2000',
+ label: 'x.x.x.x not enough memory',
+ },
+ {
+ value: '2001',
+ label: 'server can not migrate in',
+ },
+ {
+ value: '2002',
+ label: 'failed to start x.x.x.x observer',
+ },
+ {
+ value: '2003',
+ label:
+ 'not enough disk space for clog. Use redo_dir to set other disk for clog, or reduce the value of datafile_size',
+ },
+ {
+ value: '2004',
+ label: 'Invalid: xxx is not a single server configuration item',
+ },
+ {
+ value: '2005',
+ label: 'Failed to register cluster. xxx may have been registered in xxx',
+ },
+ {
+ value: '2006',
+ label: 'x.x.x.x has more than one network interface. Please set devname for x.x.x.x',
+ },
+ {
+ value: '2007',
+ label: 'x.x.x.x xxx fail to ping x.x.x.x. Please check configuration devname',
+ },
+ {
+ value: '2008',
+ label: 'Cluster clocks are out of sync',
+ },
+ {
+ value: '2009',
+ label: 'x.x.x.x: when production_mode is True, xxx can not be less then xxx',
+ },
+ {
+ value: '2010',
+ label:
+ 'x.x.x.x: system_memory too large. system_memory must be less than memory_limit/memory_limit_percentage',
+ },
+ {
+ value: '2011',
+ label:
+ "x.x.x.x: fail to get memory info.\nPlease configure 'memory_limit' manually in configuration file",
+ },
+ {
+ value: '3000',
+ label: 'parse cmd failed',
+ },
+ {
+ value: '3001',
+ label: 'xxx.sql not found',
+ },
+ {
+ value: '3002',
+ label: 'Failed to load data',
+ },
+ {
+ value: '3003',
+ label: 'Failed to run TPC-C benchmark',
+ },
+ {
+ value: '4000',
+ label: 'Fail to reload x.x.x.x',
+ },
+ {
+ value: '4001',
+ label: 'Fail to send config file to x.x.x.x',
+ },
+ {
+ value: '4100',
+ label: 'x.x.x.x need config "rs_list" or "obproxy_config_server_url"',
+ },
+ {
+ value: '4101',
+ label: 'failed to start x.x.x.x obproxy: xxx',
+ },
+ {
+ value: '4200',
+ label: "x.x.x.x grafana admin password should not be 'admin'",
+ },
+ {
+ value: '4201',
+ label: 'x.x.x.x grafana admin password length should not be less than 5',
+ },
+ {
+ value: '4300',
+ label: 'x.x.x.x: failed to query java version, you may not have java installed',
+ },
+ {
+ value: '4301',
+ label: 'x.x.x.x: ocp-express need java with version xxx',
+ },
+ {
+ value: '4302',
+ label: 'x.x.x.x not enough memory. (Free: xxx, Need: xxx)',
+ },
+ {
+ value: '4303',
+ label: 'x.x.x.x xxx not enough disk space. (Avail: xxx, Need: xxx)',
+ },
+ {
+ value: '4304',
+ label: 'OCP express xxx needs to use xxx with version xxx or above',
+ },
+ {
+ value: '4305',
+ label: 'There is not enough xxx for ocp meta tenant',
+ },
+];
diff --git a/web/src/constant/index.ts b/web/src/constant/index.ts
new file mode 100644
index 0000000..2bed93e
--- /dev/null
+++ b/web/src/constant/index.ts
@@ -0,0 +1,398 @@
+import moment from 'moment';
+// import {
+// getNameValidateMessage,
+// getChineseNameValidateMessage,
+// getUsernameValidateMessage,
+// getDatabaseNameValidateMessage,
+// getMySQLDbUserNameValidateMessage,
+// getOracleDbUserNameValidateMessage,
+// getSpaceValidateMessage,
+// } from '@/constant/component';
+import { MICROSECOND } from '@/constant/must-ignore';
+import { intl } from '@/utils/intl';
+// import { showTotal } from '@/utils';
+
+export const ALL = '__OCP_ALL_CONSTANT_VALUE__';
+
+// 通配符
+export const WILDCARD = '*';
+
+// OB 官网链接
+export const OB_SITE_LINK = 'https://www.oceanbase.com';
+
+/* 正则表达式 */
+
+// // 通用名称正则校验: 以英文字母开头、英文或数字结尾,可包含英文、数字和下划线,且长度为 2 ~ 32
+// export const NAME_REGEXP = /^[a-zA-Z]{1,1}[a-zA-Z0-9_]{0,30}[a-zA-Z0-9]{1,1}$/;
+// export const NAME_RULE = {
+// pattern: NAME_REGEXP,
+// message: getNameValidateMessage(),
+// };
+
+// // 支持中文名称正则校验: 可包含中文、英文、数字、下划线、中横线,且长度为 2 ~ 32
+// export const CHINESE_NAME_REGEXP = /^[a-zA-Z0-9\-_\u4e00-\u9fa5]{2,32}$/;
+// export const CHINESE_NAME_RULE = {
+// pattern: CHINESE_NAME_REGEXP,
+// message: getChineseNameValidateMessage(),
+// };
+
+// // 合法名称正则校验,不限制长度,可结合 getTextLength() 搭配使用
+// export const VALID_NAME_REGEXP = /^[a-zA-Z][a-zA-Z0-9_]*$/;
+// export const VALID_NAME_RULE = {
+// pattern: VALID_NAME_REGEXP,
+// message: intl.formatMessage({
+// id: 'ocp-express.src.constant.ItMustStartWithA',
+// defaultMessage: '以英文字母开头,可包含英文、数字和下划线',
+// }),
+// };
+
+// // database 命名规则
+// export const DATABASE_NAME_RULE = {
+// pattern: /^[a-z]{1,1}[a-z0-9_]{1,127}$/,
+// message: getDatabaseNameValidateMessage(),
+// };
+
+// // MySQLdbuser 命名规则
+// export const DATABASE_USER_NAME_RULE = {
+// pattern: /^[a-z]{1,1}[a-z0-9_]{1,63}$/,
+// message: getMySQLDbUserNameValidateMessage(),
+// };
+
+// // Oracle dbuUser 命名规则
+// export const ORACLE_DATABASE_USER_NAME_RULE = {
+// pattern: /^[a-zA-Z]{1,1}[a-zA-Z0-9_]{1,29}$/,
+// message: getOracleDbUserNameValidateMessage(30),
+// };
+
+// // Oracle dbRole 命名规则
+// export const ORACLE_DATABASE_ROLE_NAME_RULE = {
+// pattern: /^[a-zA-Z]{1,1}[a-zA-Z0-9_]{1,29}$/,
+// message: getOracleDbUserNameValidateMessage(30),
+// };
+
+// // 用户名称正则校验: 以英文字母开头、英文或数字结尾,可包含英文、数字、点号、中划线和下划线,且长度为 4 ~ 48
+// export const USERNAME_REGEXP = /^[a-zA-Z]{1,1}[a-zA-Z0-9.\-_]{2,46}[a-zA-Z0-9]{1,1}$/;
+// export const USERNAME_RULE = {
+// pattern: USERNAME_REGEXP,
+// message: getUsernameValidateMessage(),
+// };
+
+// // 特殊字符支持 ~!@#%^&*_\-+=|(){}[]:;,.?/
+// export const PASSWORD_REGEX =
+// /^(?=(.*[a-z]){2,})(?=(.*[A-Z]){2,})(?=(.*\d){2,})(?=(.*[~!@#%^&*_\-+=|(){}\[\]:;,.?/]){2,})[A-Za-z\d~!@#%^&*_\-+=|(){}\[\]:;,.?/]{8,32}$/;
+
+// // 校验空格
+// export const SPACE_REGEX = /^[^\s]*$/;
+// // export const SPACE_RULE = {
+// // pattern: SPACE_REGEX,
+// // message: getSpaceValidateMessage(),
+// // };
+
+// export const BOOLEAN_LIST = [
+// {
+// label: intl.formatMessage({ id: 'ocp-express.src.constant.Is', defaultMessage: '是' }),
+// value: true,
+// },
+
+// {
+// label: intl.formatMessage({ id: 'ocp-express.src.constant.No', defaultMessage: '否' }),
+// value: false,
+// },
+// ];
+
+export const SMLL_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 12,
+ },
+
+ wrapperCol: {
+ span: 12,
+ },
+};
+
+export const FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 4,
+ },
+
+ wrapperCol: {
+ span: 20,
+ },
+};
+
+export const MODAL_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 24,
+ },
+
+ wrapperCol: {
+ span: 18,
+ },
+};
+
+export const DRAWER_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 24,
+ },
+
+ wrapperCol: {
+ span: 12,
+ },
+};
+
+export const MODAL_HORIZONTAL_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 6,
+ },
+
+ wrapperCol: {
+ span: 18,
+ },
+};
+
+export const NEST_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 3,
+ },
+
+ wrapperCol: {
+ span: 21,
+ },
+};
+
+export const FORM_ITEM_SMALL_LAYOUT = {
+ labelCol: {
+ span: 4,
+ },
+
+ wrapperCol: {
+ span: 10,
+ },
+};
+
+export const TAIL_FORM_ITEM_LAYOUT = {
+ wrapperCol: {
+ span: 20,
+ offset: 4,
+ },
+};
+
+export const MIDDLE_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 6,
+ },
+
+ wrapperCol: {
+ span: 18,
+ },
+};
+
+export const MIDDLE_TAIL_FORM_ITEM_LAYOUT = {
+ wrapperCol: {
+ span: 18,
+ offset: 6,
+ },
+};
+
+export const LARGE_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 8,
+ },
+
+ wrapperCol: {
+ span: 16,
+ },
+};
+
+export const MAX_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 24,
+ },
+
+ wrapperCol: {
+ span: 24,
+ },
+};
+
+export const LARGE_TAIL_FORM_ITEM_LAYOUT = {
+ wrapperCol: {
+ span: 16,
+ offset: 8,
+ },
+};
+
+export const SUPERSIZE_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 10,
+ },
+
+ wrapperCol: {
+ span: 14,
+ },
+};
+
+export const PAGE_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 4,
+ },
+
+ wrapperCol: {
+ span: 12,
+ },
+};
+
+export const PAGR_TAIL_FORM_ITEM_LAYOUT = {
+ labelCol: {
+ span: 0,
+ },
+
+ wrapperCol: {
+ offset: 4,
+ span: 12,
+ },
+};
+
+export const DEFAULT_LIST_DATA = {
+ page: {
+ totalElements: 0,
+ totalPages: 0,
+ number: 0,
+ size: 0,
+ },
+
+ contents: [],
+};
+
+export const PAGINATION_OPTION_10 = {
+ defaultPageSize: 10,
+ showSizeChanger: true,
+ pageSizeOptions: ['10', '20', '50', '100'],
+ // showTotal,
+};
+
+export const PAGINATION_OPTION_5 = {
+ defaultPageSize: 5,
+ showSizeChanger: true,
+ pageSizeOptions: ['5', '10', '20', '50'],
+ // showTotal,
+};
+
+export const EMAIL_DOMAIN_LIST = [
+ 'aliyun.com',
+ '163.com',
+ '126.com',
+ 'foxmail.com',
+ 'gmail.com',
+ 'outlook.com',
+ 'msn.com',
+ 'sohu.com',
+ 'sina.com',
+ 'hotmail.com',
+ 'qq.com',
+];
+
+export function getRanges() {
+ const rangeList = [
+ {
+ label: intl.formatMessage({
+ id: 'ocp-express.src.constant.Minutes',
+ defaultMessage: '1 分钟',
+ }),
+ value: () => [moment().subtract(1, 'minute'), moment()],
+ },
+
+ {
+ label: intl.formatMessage({
+ id: 'ocp-express.src.constant.Minutes.1',
+ defaultMessage: '5 分钟',
+ }),
+ value: () => [moment().subtract(5, 'minute'), moment()],
+ },
+
+ {
+ label: intl.formatMessage({
+ id: 'ocp-express.src.constant.Minutes.2',
+ defaultMessage: '10 分钟',
+ }),
+ value: () => [moment().subtract(10, 'minute'), moment()],
+ },
+
+ {
+ label: intl.formatMessage({
+ id: 'ocp-express.src.constant.Minutes.3',
+ defaultMessage: '20 分钟',
+ }),
+ value: () => [moment().subtract(20, 'minute'), moment()],
+ },
+
+ {
+ label: intl.formatMessage({
+ id: 'ocp-express.src.constant.HalfAnHour',
+ defaultMessage: '半小时',
+ }),
+ value: () => [moment().subtract(30, 'minute'), moment()],
+ },
+
+ {
+ label: intl.formatMessage({
+ id: 'ocp-express.src.constant.AnHour',
+ defaultMessage: '一小时',
+ }),
+ value: () => [moment().subtract(60, 'minute'), moment()],
+ },
+ ];
+
+ const ranges = {};
+ rangeList.forEach((item) => {
+ ranges[item.label] = (item.value && item.value()) || [];
+ });
+ return ranges;
+}
+
+// OCP 实时监控的刷新频率,单位为 s
+export const FREQUENCY = 5;
+
+export const TIME_UNIT_LIST = [MICROSECOND, 'ms', 's', 'min'];
+export const SIZE_UNIT_LIST = ['byte', 'KB', 'MB', 'GB', 'TB', 'PB'];
+
+// 提供给 Select 组件做分词使用,支持(逗号、空格、逗号 + 空格、换行符、逗号 + 换行符)等 5 种场景,因为 Select 组件默认支持换行符分隔,所以不显示定义 (换行符、逗号 + 换行符)
+export const SELECT_TOKEN_SPEARATORS = [',', ', ', ' '];
+
+export const OCP_UPGRADE_STATUS_LIST = [
+ {
+ label: intl.formatMessage({
+ id: 'OBD.src.constant.Checking',
+ defaultMessage: '检查中',
+ }),
+
+ value: 'RUNNING',
+ badgeStatus: 'processing',
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.src.constant.Pass',
+ defaultMessage: '通过',
+ }),
+
+ value: 'PASSED',
+ badgeStatus: 'success',
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.src.constant.Failed',
+ defaultMessage: '未通过',
+ }),
+
+ value: 'FAILED',
+ badgeStatus: 'error',
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.src.constant.Ignored',
+ defaultMessage: '已忽略',
+ }),
+
+ value: 'IGNORED',
+ badgeStatus: 'ignored',
+ },
+];
diff --git a/web/src/constant/must-ignore.ts b/web/src/constant/must-ignore.ts
new file mode 100644
index 0000000..8a86492
--- /dev/null
+++ b/web/src/constant/must-ignore.ts
@@ -0,0 +1,18 @@
+import { intl } from '@/utils/intl';
+export const LOCALE_LIST = [
+ {
+ label: 'English',
+ value: 'en-US',
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.src.constant.must-ignore.SimplifiedChinese',
+ defaultMessage: '简体中文',
+ }),
+ value: 'zh-CN',
+ },
+];
+
+// μs 会被 must 视别为中文字符
+export const MICROSECOND = 'μs';
+export type MICROSECOND_TYPE = 'μs';
diff --git a/web/src/i18n/strings/en-US.json b/web/src/i18n/strings/en-US.json
index ec0d287..adae0c5 100644
--- a/web/src/i18n/strings/en-US.json
+++ b/web/src/i18n/strings/en-US.json
@@ -286,5 +286,605 @@
"OBD.pages.components.NodeConfig.NodeConfigurationPreviousStep": "Node Configuration-Previous Step",
"OBD.pages.components.NodeConfig.AreYouSureYouWant": "Are you sure to delete the configuration of this Zone?",
"OBD.pages.components.ClusterConfig.PortObproxyOfExporterIs": "Used for Prometheus to pull OBProxy monitoring data.",
- "OBD.pages.components.InstallConfig.OptionalComponents": "Optional components"
+ "OBD.pages.components.InstallConfig.OptionalComponents": "Optional components",
+ "OBD.pages.Guide.OceanbaseAndSupportingTools": "OceanBase and supporting tools",
+ "OBD.pages.Guide.DistributedDatabasesAndVariousTools": "Distributed databases and various tools to facilitate customer management, operation and maintenance",
+ "OBD.pages.Guide.OceanbaseCloudPlatformFullLifecycle": "OceanBase cloud platform: full lifecycle management of OB clusters",
+ "OBD.pages.Guide.OceanbaseDeveloperCenterManageDatabases": "OceanBase Developer Center: manage databases and tables",
+ "OBD.pages.Guide.OceanbaseDataMigrationFastData": "OceanBase data migration: fast data migration",
+ "OBD.pages.Guide.TheProductIsUnderConstruction": "The product is under construction.",
+ "OBD.pages.Guide.SelectAnInstallationProduct": "Select an installation product",
+ "OBD.pages.Guide.HelpDocument": "Help document",
+ "OBD.pages.Guide.WelcomeToTheOceanbaseDeployment": "Welcome to the OceanBase Deployment Wizard",
+ "OBD.pages.Guide.WizardSelectDeploymentTool": "Wizard-select deployment tool",
+ "OBD.pages.Guide.Ok": "OK",
+ "OBD.Obdeploy.ClusterConfig.ConfigTable.ParameterValue": "Parameter value",
+ "OBD.pages.Obdeploy.InstallConfig.ItStartsWithALetter": "It starts with a letter, ends with a number, and can contain English, numbers, and underscores. The length is 2 to 32.",
+ "OBD.pages.Obdeploy.InstallConfig.PreviousStep": "Previous step",
+ "OBD.OcpInstaller.Configuration.MetadbConnectionFailedPleaseCheck": "MetaDB connection failed. Please check the connection configuration.",
+ "OBD.OcpInstaller.Configuration.IKnow": "I know",
+ "OBD.OcpInstaller.Error.403.SorryYouAreNotAuthorized": "Sorry, you are not authorized to access this page.",
+ "OBD.OcpInstaller.Error.403.ReturnToHomePage": "Return to Home Page",
+ "OBD.OcpInstaller.Error.404.SorryThePageYouVisited": "Sorry, the page you visited does not exist.",
+ "OBD.OcpInstaller.Error.404.ReturnToHomePage": "Return to Home Page",
+ "OBD.OcpInstaller.Index.LifecycleManagementOM": "Lifecycle management (O & M)",
+ "OBD.OcpInstaller.Index.OcpImplementsUnifiedManagementOf": "OCP implements unified management of OceanBase resources, and implements full lifecycle management of resource creation, backup and recovery, monitoring and alerting, inspection, autonomy, upgrade, and deletion.",
+ "OBD.OcpInstaller.Index.MonitoringAlarm": "Monitoring Alarm",
+ "OBD.OcpInstaller.Index.OcpMonitorsOceanbaseFromDifferent": "OCP monitors OceanBase from different dimensions such as hosts, clusters, and tenants, and provides various alert methods such as DingTalk, WeChat, and email to ensure cluster security.",
+ "OBD.OcpInstaller.Index.BackupAndRecovery": "Backup and Recovery",
+ "OBD.OcpInstaller.Index.OcpProvidesBackupAndRecovery": "OCP provides backup and recovery capabilities for OceanBase clusters and tenants. It supports automatic backup of full, incremental, and log files to storage types such as NAS and OSS, and supports one-click recovery operations.",
+ "OBD.OcpInstaller.Index.DiagnosticOptimization": "Diagnostic optimization",
+ "OBD.OcpInstaller.Index.OcpProvidesClosedLoopDiagnostics": "OCP provides closed-loop diagnostics for SQL from perception, root cause analysis, and execution recommendations. OCP also provides diagnostic capabilities from the dimensions of Cluster Replication, session, deadlock, and capacity.",
+ "OBD.OcpInstaller.Index.UpgradeWelcomePage": "Upgrade welcome page",
+ "OBD.OcpInstaller.Index.WelcomeToTheOcpUpgrade": "Welcome to the OCP Upgrade Wizard",
+ "OBD.OcpInstaller.Index.StartUpgradingToV": "Start upgrading to V 2.3.2",
+ "OBD.OcpInstaller.Index.InstallTheWelcomePage": "Install the welcome page",
+ "OBD.OcpInstaller.Index.WelcomeToTheOcpDeployment": "Welcome to the OCP Deployment Wizard",
+ "OBD.OcpInstaller.Index.SelectAMetadbConfigurationMethod": "Select a MetaDB configuration method for OCP",
+ "OBD.OcpInstaller.Index.MetadbIsAnImportantPart": "MetaDB is an important part of OCP. MetaDB provides the underlying storage capability for OCP's metadata management and monitoring data. OCP-Server provides you with OceanBase database lifecycle management services by calling MetaDB data.",
+ "OBD.OcpInstaller.Index.CreateANewOceanbaseDatabase": "Create a new OceanBase database",
+ "OBD.OcpInstaller.Index.MetadbAsOcp": "MetaDB as OCP",
+ "OBD.OcpInstaller.Index.Recommend": "Recommend",
+ "OBD.OcpInstaller.Index.UseAnExistingOceanbaseDatabase": "Use an existing OceanBase database",
+ "OBD.OcpInstaller.Index.PreviousStep": "Previous step",
+ "OBD.OcpInstaller.Index.Ok": "OK",
+ "OBD.Component.ModifyResourcePoolModal.NoResourcesFound": "No resources found",
+ "OBD.Component.ModifyResourcePoolModal.SystemPreOccupation": "System pre-occupation",
+ "OBD.Component.ModifyResourcePoolModal.OcpServiceReservation": "OCP service Reservation",
+ "OBD.Component.ModifyResourcePoolModal.CommonTenantReservation": "Common tenant Reservation",
+ "OBD.Component.ModifyResourcePoolModal.ResourceAllocation": "Resource allocation",
+ "OBD.Component.ModifyResourcePoolModal.BasedOnTheMinimumAvailable": "Based on the minimum available resources in the current environment, we recommend that you allocate the following resources for MetaDB:",
+ "OBD.Component.ModifyResourcePoolModal.DataFile": "Data File",
+ "OBD.Component.ModifyResourcePoolModal.TheDataFileSpaceIs": "The data file space is insufficient. By default, the data file size is three times that of memory_limit.",
+ "OBD.Component.ModifyResourcePoolModal.LogFile": "Log File",
+ "OBD.Component.ModifyResourcePoolModal.TheLogFileHasInsufficient": "The log file has insufficient space. By default, the log file size is three times that of memory_limit.",
+ "OBD.Install.Component.SystemConfig.InvalidIpAddress": "Invalid IP address",
+ "OBD.Install.Component.SystemConfig.InstallAndDeployMetadbConfiguration": "Install and deploy MetaDB configuration page",
+ "OBD.Install.Component.SystemConfig.SystemConfiguration": "System Configuration",
+ "OBD.Install.Component.SystemConfig.ToAvoidOperatingSystemUser": "To avoid operating system user conflicts, configure independent operating system users for MetaDB and OCP",
+ "OBD.Install.Component.SystemConfig.SelectHost": "Select host",
+ "OBD.Install.Component.SystemConfig.EnterAnIpAddress": "Enter an IP address",
+ "OBD.Install.Component.SystemConfig.PleaseEnter": "Please enter",
+ "OBD.Install.Component.SystemConfig.HostUser": "Host user",
+ "OBD.Install.Component.SystemConfig.EnterAHostUser": "Enter a host User",
+ "OBD.Install.Component.SystemConfig.UserPassword": "User password",
+ "OBD.Install.Component.SystemConfig.IfYouHaveSetPassword": "If you have set password-free, ignore this option.",
+ "OBD.Install.Component.SystemConfig.MetadbConfiguration": "MetaDB configuration",
+ "OBD.Install.Component.SystemConfig.ClusterName": "Cluster name",
+ "OBD.Install.Component.SystemConfig.EnterAClusterName": "Enter a cluster name",
+ "OBD.Install.Component.SystemConfig.RootSysPassword": "root @ sys password",
+ "OBD.Install.Component.SystemConfig.EnterOrRandomlyGenerateThe": "Enter or randomly generate the root @ sys password.",
+ "OBD.Install.Component.SystemConfig.SoftwarePath": "Software path",
+ "OBD.Install.Component.SystemConfig.EnterTheSoftwarePath": "Enter the software path",
+ "OBD.Install.Component.SystemConfig.DataPath": "Data path",
+ "OBD.Install.Component.SystemConfig.EnterADataPath": "Enter a data path",
+ "OBD.Install.Component.SystemConfig.LogPath": "Log path",
+ "OBD.Install.Component.SystemConfig.EnterALogPath": "Enter a log path",
+ "OBD.Install.Component.SystemConfig.SqlPort": "SQL Port",
+ "OBD.Install.Component.SystemConfig.TheSqlPortCannotBe": "The SQL Port cannot be the same as the RPC port.",
+ "OBD.Install.Component.SystemConfig.RpcPort": "rpc Port",
+ "OBD.Install.Component.SystemConfig.TheRpcPortCannotBe": "The RPC Port cannot be the same as the SQL port.",
+ "OBD.Install.Component.SystemConfig.NicName": "Nic name",
+ "OBD.Install.Component.SystemConfig.AutomaticConfiguration": "Automatic configuration",
+ "OBD.Install.Component.SystemConfig.ManualConfiguration": "Manual configuration",
+ "OBD.Install.Component.SystemConfig.EnterANicName": "Enter a nic name",
+ "OBD.Install.Component.SystemConfig.AMaximumOfCharactersCan": "A maximum of 13 characters can be entered.",
+ "OBD.Install.Component.SystemConfig.EnterTheNameOfThe": "Enter the name of the NIC device bound to OBServer.",
+ "OBD.OcpInstaller.Install.CreateANewMetadbMetadb": "Create a new MetaDB. MetaDB and OCP-Server use the same host deployment service. OCP-Server will access local MetaDB for better service reliability.",
+ "OBD.Layout.BasicLayout.InstallationDeploymentAndUpgradeSystem": "Installation, deployment, and upgrade system information",
+ "OBD.Layout.BasicLayout.OcpUpgradeWizardVersionNumber": "OCP Upgrade Wizard (version number: 4.0)",
+ "OBD.Layout.BasicLayout.OcpDeploymentWizardVersionNumber": "OCP Deployment Wizard (version number: 4.0)",
+ "OBD.Layout.BasicLayout.VisitTheOfficialWebsite": "Visit the official website",
+ "OBD.Layout.BasicLayout.HelpCenter": "Help Center",
+ "OBD.OcpInstaller.Layout.OceanbaseCloudPlatform": "OceanBase cloud platform",
+ "OBD.OcpInstaller.Quit.TheUpgradeProgramHasExited": "The upgrade program has exited.",
+ "OBD.OcpInstaller.Quit.TheDeploymentInstallerHasExited": "The deployment installer has exited!",
+ "OBD.OcpInstaller.Quit.TheUpgradeProgramHasQuit": "The upgrade program has quit. To enable the upgrade program again, run it in the system.",
+ "OBD.OcpInstaller.Quit.ToEnableTheDeploymentProgram": "To enable the deployment program again, run",
+ "OBD.OcpInstaller.Quit.ExecuteOcpN": "Execute ocp_N",
+ "OBD.Component.ConnectionInfo.ComponentName": "Component name",
+ "OBD.Component.ConnectionInfo.NodeIp": "Node IP",
+ "OBD.Component.ConnectionInfo.TheInstallerAutomaticallyObtainsMetadb": "The installer automatically obtains MetaDB configuration information based on the current host OCP environment. Check whether the MetaDB configuration information is correct. OCP will perform the upgrade program based on the following information.",
+ "OBD.Component.ConnectionInfo.ConnectionInformation": "Connection information",
+ "OBD.Component.ConnectionInfo.AccessAddress": "Access address",
+ "OBD.Component.ConnectionInfo.EnterAnAccessAddress": "Enter an access address",
+ "OBD.Component.ConnectionInfo.EnterADatabaseAccessIp": "Enter a database access IP address",
+ "OBD.Component.ConnectionInfo.AccessPort": "Access Port",
+ "OBD.Component.ConnectionInfo.EnterAnAccessPort": "Enter an access port",
+ "OBD.Component.ConnectionInfo.EnterTheDatabaseConnectionPort": "Enter the database connection port",
+ "OBD.Component.ConnectionInfo.DatabaseName": "Database name",
+ "OBD.Component.ConnectionInfo.EnterADatabaseName": "Enter a database name",
+ "OBD.Component.ConnectionInfo.AccessAccount": "Access account",
+ "OBD.Component.ConnectionInfo.EnterAnAccessAccount": "Enter an access account",
+ "OBD.Component.ConnectionInfo.AccessPassword": "Access password",
+ "OBD.Component.ConnectionInfo.EnterAnAccessPassword": "Enter an access password",
+ "OBD.Component.ConnectionInfo.PleaseEnter": "Please enter",
+ "OBD.Component.ConnectionInfo.Verification": "Verification",
+ "OBD.Component.ConnectionInfo.TheCurrentVerificationFailedPlease": "The current verification failed. Please fill in the error parameter again.",
+ "OBD.Component.ConnectionInfo.TheVerificationIsSuccessfulPlease": "The verification is successful. Please enter the following parameters.",
+ "OBD.Component.ConnectionInfo.OperatingSystemUsers": "Operating system users",
+ "OBD.Component.ConnectionInfo.Username": "Username",
+ "OBD.Component.ConnectionInfo.EnterAUsername": "Enter a username",
+ "OBD.Component.ConnectionInfo.PleaseProvideTheUserName": "Please provide the user name to automatically configure the platform-specific operating system user.",
+ "OBD.Component.ConnectionInfo.Password": "Password",
+ "OBD.Component.ConnectionInfo.EnterAPassword": "Enter a password",
+ "OBD.Component.ConnectionInfo.TheVerificationIsSuccessfulProceed": "The verification is successful. Proceed to the next step.",
+ "OBD.Component.ConnectionInfo.TheCurrentVerificationFailedPlease.1": "The current verification failed. Please enter it again.",
+ "OBD.Component.UpdatePreCheck.CheckItems": "Check items",
+ "OBD.Component.UpdatePreCheck.CheckStatus": "Check status",
+ "OBD.Component.UpdatePreCheck.Impact": "Impact",
+ "OBD.Component.UpdatePreCheck.UpgradeEnvironmentPreCheckPage": "Upgrade environment Pre-Check page",
+ "OBD.Component.UpdatePreCheck.BasedOnTheMetadbConfiguration": "Based on the MetaDB configuration information, OCP configuration information is successfully obtained. To ensure the consistency of management functions, the upgrade program upgrades the platform management service (OCP Server) and all host proxy services (OCP Agent) it manages, check and confirm the following configuration information, and then start the pre-check",
+ "OBD.Component.UpdatePreCheck.InstallationConfiguration": "Installation configuration",
+ "OBD.Component.UpdatePreCheck.ClusterName": "Cluster name",
+ "OBD.Component.UpdatePreCheck.UpgradeType": "Upgrade Type",
+ "OBD.Component.UpdatePreCheck.UpgradeAll": "Upgrade all",
+ "OBD.Component.UpdatePreCheck.UpgradeConfigurationInformation": "Upgrade configuration information",
+ "OBD.Component.UpdatePreCheck.PreUpgradeVersion": "Pre-upgrade version:",
+ "OBD.Component.UpdatePreCheck.UpgradedVersion": "Upgraded version:",
+ "OBD.Component.UpdatePreCheck.ComponentName": "Component name",
+ "OBD.Component.UpdatePreCheck.NodeIp": "Node IP",
+ "OBD.Component.UpdatePreCheck.AreYouSureYouWant": "Are you sure you want to ignore all failed checks?",
+ "OBD.Component.UpdatePreCheck.UpgradeIgnoresAllFailedItems": "Upgrade ignores all failed items",
+ "OBD.Component.UpdatePreCheck.IgnoreAllFailedItems": "Ignore all failed items",
+ "OBD.Component.UpdatePreCheck.PreCheckIsInProgress": "Pre-check is in progress, and re-check is not supported.",
+ "OBD.Component.UpdatePreCheck.UpgradeAndReCheck": "Upgrade and re-check",
+ "OBD.Component.UpdatePreCheck.ReCheck": "Re-check",
+ "OBD.OcpInstaller.Update.UnableToObtainOcpConfiguration": "Unable to obtain OCP configuration information. Check whether the connection is OCP MetaBase configuration.",
+ "OBD.OcpInstaller.Update.IKnow": "I know",
+ "OBD.OcpInstaller.Update.TheCurrentVersionCannotBe": "The current version cannot be upgraded to the Target version.",
+ "OBD.OcpInstaller.Update.TheCurrentVersionOfOcp": "The current version of OCP is V 3.0.0. Currently, you cannot directly upgrade to V 4.1.0. You can use OCP.\nAccess address",
+ "OBD.OcpInstaller.Update.ViewVersionDetailsForMore": "View version details\nFor more information about the upgrade path of the OCP version, see",
+ "OBD.OcpInstaller.Update.VersionReleaseRecord": "Version release record",
+ "OBD.OcpInstaller.Update.InThePreCheckProcess": "In the pre-check process, the previous step is not supported.",
+ "OBD.OcpInstaller.Update.PreviousStep": "Previous step",
+ "OBD.OcpInstaller.Update.InThePreCheckProcess.1": "In the pre-check process, the next step is not supported.",
+ "OBD.OcpInstaller.Update.PreCheck": "Pre-Check",
+ "OBD.OcpInstaller.Update.NextStep": "Next Step",
+ "OBD.OcpInstaller.Update.UpgradeAgain": "Upgrade Again",
+ "OBD.OcpInstaller.Welcome.WelcomeToTheOcpUpgrade": "Welcome to the OCP Upgrade Wizard",
+ "OBD.OcpInstaller.Welcome.StartUpgrade": "Start upgrade",
+ "OBD.pages.constants.OcpParameterName": "OCP parameter name",
+ "OBD.src.utils.SelectTheCorrectOcpNode": "Select the correct OCP node",
+ "OBD.src.utils.EnterAPassword": "Enter a password",
+ "OBD.src.utils.ToCharactersInLength": "8 to 32 characters in length",
+ "OBD.src.utils.CanOnlyContainLettersNumbers": "Can only contain letters, numbers, and special characters (~! @ **** & *_-+ = |(){}[]:;,.? /'$\"<>)",
+ "OBD.src.utils.AtLeastUppercaseAndLowercase": "At least 2 uppercase and lowercase letters, numbers, and special characters",
+ "OBD.src.component.Access.NoOperationPermissionIsAvailable": "No operation permission is available. Please contact the administrator to activate the permission.",
+ "OBD.component.AobException.ReturnToHomePage": "Return to Home Page",
+ "OBD.component.ConnectConfig.MetadbConnectionFailedPleaseCheck": "MetaDB connection failed. Please check the connection configuration.",
+ "OBD.component.ConnectConfig.IKnow": "I know",
+ "OBD.component.ConnectConfig.ConnectionInformation": "Connection information",
+ "OBD.component.ConnectConfig.HostIp": "Host IP",
+ "OBD.component.ConnectConfig.EnterTheHostIpAddress": "Enter the host IP address.",
+ "OBD.component.ConnectConfig.TheHostIpAddressFormat": "The host IP address format is incorrect.",
+ "OBD.component.ConnectConfig.EnterADatabaseAccessIp": "Enter a database access IP address",
+ "OBD.component.ConnectConfig.AccessPort": "Access Port",
+ "OBD.component.ConnectConfig.EnterAnAccessPort": "Enter an access port",
+ "OBD.component.ConnectConfig.EnterTheDatabaseConnectionPort": "Enter the database connection port",
+ "OBD.component.ConnectConfig.AccessAccount": "Access account",
+ "OBD.component.ConnectConfig.EnterAnAccessAccount": "Enter an access account",
+ "OBD.component.ConnectConfig.AccessPassword": "Access password",
+ "OBD.component.ConnectConfig.OcpPlatformAdministratorAccountPassword": "OCP platform administrator account password",
+ "OBD.component.ConnectConfig.EnterAnAccessPassword": "Enter an access password",
+ "OBD.component.ConnectConfig.PleaseEnter": "Please enter",
+ "OBD.component.ConnectConfig.PreviousStep": "Previous step",
+ "OBD.component.ConnectConfig.NextStep": "Next Step",
+ "OBD.component.DeployConfig.EnterpriseLevelDataManagementPlatform": "Enterprise-level data management platform with OceanBase as the core to realize OceanBase lifecycle O & M Management",
+ "OBD.component.DeployConfig.FinancialLevelDistributedDatabasesAre": "Financial-level distributed databases are characterized by strong data consistency, high scalability, high cost performance, stability and reliability.",
+ "OBD.component.DeployConfig.OceanbaseADedicatedDatabaseProxy": "OceanBase a dedicated database proxy server to forward user SQL requests to the best target OBServer",
+ "OBD.component.DeployConfig.ProductName": "Product Name",
+ "OBD.component.DeployConfig.Version": "Version",
+ "OBD.component.DeployConfig.CommunityEdition": "Community Edition",
+ "OBD.component.DeployConfig.CommercialEdition": "Commercial Edition",
+ "OBD.component.DeployConfig.Description": "Description",
+ "OBD.component.DeployConfig.LearnMore": "Learn More",
+ "OBD.component.DeployConfig.ItStartsWithALetter": "It starts with a letter, ends with a number, and can contain English, numbers, and underscores. The length is 2 to 32.",
+ "OBD.component.DeployConfig.TheDeploymentNameWithValue": "The deployment name with {value} does not exist.",
+ "OBD.component.DeployConfig.ADeploymentNameWithValue": "A deployment name with {value} already exists. Please specify a new name.",
+ "OBD.component.DeployConfig.BasicConfiguration": "Basic configuration",
+ "OBD.component.DeployConfig.DeploymentConfiguration": "Deployment configuration",
+ "OBD.component.DeployConfig.ClusterName": "Cluster name",
+ "OBD.component.DeployConfig.PleaseEnter": "Please enter",
+ "OBD.component.DeployConfig.EnterAClusterName": "Enter a cluster name",
+ "OBD.component.DeployConfig.VersionSelection": "Version selection",
+ "OBD.component.DeployConfig.EstimatedInstallationRequirements": "Estimated installation requirements",
+ "OBD.component.DeployConfig.MbSpace": "MB space",
+ "OBD.component.DeployConfig.DeploymentConfigurationPreviousStep": "Deployment configuration-previous step",
+ "OBD.component.DeployConfig.PreviousStep": "Previous step",
+ "OBD.component.DeployConfig.DeploymentConfigurationNextStep": "Deployment configuration-Next Step",
+ "OBD.component.DeployConfig.NextStep": "Next Step",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.OnlyManualFixes": "Only manual fixes",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.AutomaticRepair": "Automatic Repair",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.NoFailedItemsFoundYet": "No failed items found yet",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.GreatCheckAllSuccessful": "Great! Check all successful!",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.Reason": "Reason:",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.Suggestions": "Suggestions:",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.ManualRepair": "Manual repair",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.LearnMore": "Learn More",
+ "OBD.component.EnvPreCheck.CheckItem.CheckItemPassedcountTotal": "Check item {passedCount}/{total}",
+ "OBD.component.EnvPreCheck.CheckItem.ReCheck": "Re-check",
+ "OBD.component.EnvPreCheck.CheckCompleted": "Check completed",
+ "OBD.component.EnvPreCheck.Checking": "Checking",
+ "OBD.component.EnvPreCheck.VerifyingThatYourEnvironmentMeets": "Verifying that your environment meets all the minimum requirements for installing and configuring MetaDB and OCP.",
+ "OBD.component.EnvPreCheck.InstallAndDeployOcpEnvironment": "Install and deploy OCP environment Pre-Check page",
+ "OBD.component.EnvPreCheck.InstallAndDeployMetadbEnvironment": "Install and deploy MetaDB environment Pre-Check page",
+ "OBD.src.component.ErrorBoundary.PageExecutionError": "Page execution error",
+ "OBD.src.component.ErrorBoundary.ReturnToHomePage": "Return to Home Page",
+ "OBD.src.component.ExitBtn.DeploymentConfigurationExit": "Deployment configuration-exit",
+ "OBD.src.component.ExitBtn.ExitTheOcpUpgradeProgram": "Exit the OCP upgrade program",
+ "OBD.src.component.ExitBtn.ExitTheOcpDeploymentInstaller": "Exit the OCP deployment installer",
+ "OBD.src.component.ExitBtn.AfterExitingTheUpgradeWill": "After exiting, the upgrade will be terminated. Proceed with caution.",
+ "OBD.src.component.ExitBtn.AfterExitingTheDeploymentAnd": "After exiting, the deployment and installation will be terminated. Proceed with caution.",
+ "OBD.src.component.ExitBtn.Exit": "Exit",
+ "OBD.component.InsstallResult.ComponentName": "Component name",
+ "OBD.component.InsstallResult.NodeIp": "Node IP",
+ "OBD.component.InsstallResult.UpgradeSuccessful": "Upgrade successful",
+ "OBD.component.InsstallResult.OcpUpgradedSuccessfully": "OCP upgraded successfully",
+ "OBD.component.InsstallResult.InstallationAndDeploymentNoMetadb": "Installation and deployment no MetaDB deployment succeeded",
+ "OBD.component.InsstallResult.OcpDeployedSuccessfully": "OCP deployed successfully",
+ "OBD.component.InsstallResult.MetadbIsDeployedSuccessfully": "MetaDB is deployed successfully.",
+ "OBD.component.InsstallResult.InstallationAndDeploymentMetadbDeployment": "Installation and deployment MetaDB deployment succeeded",
+ "OBD.component.InsstallResult.MetadbDeployedSuccessfully": "MetaDB deployed successfully",
+ "OBD.component.InsstallResult.UpgradeFailed": "Upgrade failed",
+ "OBD.component.InsstallResult.OcpUpgradeFailed": "OCP upgrade failed",
+ "OBD.component.InsstallResult.InstallationAndDeploymentNoMetadb.1": "Installation and deployment no MetaDB deployment failed",
+ "OBD.component.InsstallResult.OcpDeploymentFailed": "OCP deployment failed",
+ "OBD.component.InsstallResult.FailedToInstallAndDeploy": "Failed to install and deploy MetaDB deployment",
+ "OBD.component.InsstallResult.FailedToInstallAndDeploy.1": "Failed to install and deploy MetaDB deployment",
+ "OBD.component.InsstallResult.MetadbDeploymentFailed": "MetaDB deployment failed",
+ "OBD.component.InsstallResult.PleaseCheckTheLogInformation": "Please check the log information to obtain the cause of the failure and contact the technical support staff to handle it.",
+ "OBD.component.InsstallResult.UpgradeReport": "Upgrade report",
+ "OBD.component.InsstallResult.ThereAreHostsThatHave": "· There are hosts that have not been upgraded to OCP Agent. We recommend that you use OCP\nThe host management module of the platform installs the new version OCP Agent.\nHost not upgraded:",
+ "OBD.component.InsstallResult.PreUpgradeVersion": "Pre-upgrade version:",
+ "OBD.component.InsstallResult.UpgradedVersion": "Upgraded version:",
+ "OBD.component.InsstallResult.Click": "Click",
+ "OBD.component.InsstallResult.OcpReleaseRecords": "OCP release records",
+ "OBD.component.InsstallResult.LearnMoreAboutTheNew": "Learn more about the new version",
+ "OBD.component.InsstallResult.AccessAddressAndAccountSecret": "Access address and account secret information",
+ "OBD.component.InsstallResult.PleaseKeepTheFollowingAccess": "Please keep the following access address and account secret information properly and update OCP in time.\nInitial password. For more information, visit",
+ "OBD.component.InsstallResult.OceanbaseDocumentCenter": "OceanBase Document Center",
+ "OBD.component.InsstallResult.CopyInformation": "Copy Information",
+ "OBD.component.InsstallResult.AccessAddress": "Access address",
+ "OBD.component.InsstallResult.AdministratorAccount": "Administrator account",
+ "OBD.component.InsstallResult.InitialPassword": "Initial password",
+ "OBD.component.InstallProcess.Upgraded": "Upgraded",
+ "OBD.component.InstallProcess.Deploying": "Deploying",
+ "OBD.component.InstallProcess.Deploying.1": "Deploying",
+ "OBD.component.InstallProcess.UpgradeLogs": "Upgrade logs",
+ "OBD.component.InstallProcess.DeploymentLogs": "Deployment logs",
+ "OBD.component.InstallProcessNew.Upgrading": "Upgrading...",
+ "OBD.component.InstallResult.ComponentName": "Component name",
+ "OBD.component.InstallResult.NodeIp": "Node IP",
+ "OBD.component.InstallResult.UpgradeSuccessful": "Upgrade successful",
+ "OBD.component.InstallResult.OcpUpgradedSuccessfully": "OCP upgraded successfully",
+ "OBD.component.InstallResult.InstallationAndDeploymentNoMetadb": "Installation and deployment no MetaDB deployment succeeded",
+ "OBD.component.InstallResult.OcpDeployedSuccessfully": "OCP deployed successfully",
+ "OBD.component.InstallResult.MetadbIsDeployedSuccessfully": "MetaDB is deployed successfully.",
+ "OBD.component.InstallResult.UpgradeFailed": "Upgrade failed",
+ "OBD.component.InstallResult.OcpUpgradeFailed": "OCP upgrade failed",
+ "OBD.component.InstallResult.InstallationAndDeploymentNoMetadb.1": "Installation and deployment no MetaDB deployment failed",
+ "OBD.component.InstallResult.OcpDeploymentFailed": "OCP deployment failed",
+ "OBD.component.InstallResult.FailedToInstallAndDeploy": "Failed to install and deploy MetaDB deployment",
+ "OBD.component.InstallResult.PleaseCheckTheLogInformation": "Please check the log information to obtain the cause of the failure and contact the technical support staff to handle it.",
+ "OBD.component.InstallResult.UpgradeReport": "Upgrade report",
+ "OBD.component.InstallResult.ThereAreHostsThatHave": "· There are hosts that have not been upgraded to OCP Agent. We recommend that you use OCP\nThe host management module of the platform installs the new version OCP Agent.\nHost not upgraded:",
+ "OBD.component.InstallResult.PreUpgradeVersion": "Pre-upgrade version:",
+ "OBD.component.InstallResult.UpgradedVersion": "Upgraded version:",
+ "OBD.component.InstallResult.Click": "Click",
+ "OBD.component.InstallResult.OcpReleaseRecords": "OCP release records",
+ "OBD.component.InstallResult.LearnMoreAboutTheNew": "Learn more about the new version",
+ "OBD.component.InstallResult.AccessAddressAndAccountSecret": "Access address and account secret information",
+ "OBD.component.InstallResult.PleaseKeepTheFollowingAccess": "Please keep the following access address and account secret information properly and update OCP in time.\nInitial password. For more information, visit",
+ "OBD.component.InstallResult.OceanbaseDocumentCenter": "OceanBase Document Center",
+ "OBD.component.InstallResult.CopyInformation": "Copy Information",
+ "OBD.component.InstallResult.AccessAddress": "Access address",
+ "OBD.component.InstallResult.AdministratorAccount": "Administrator account",
+ "OBD.component.InstallResult.InitialPassword": "Initial password",
+ "OBD.component.InstallResult.UpgradeLogs": "Upgrade logs",
+ "OBD.component.InstallResult.DeploymentLogs": "Deployment logs",
+ "OBD.component.InstallResult.ExitTheInstaller": "Exit the installer",
+ "OBD.component.InstallResult.DoYouWantToExit": "Do you want to exit the page?",
+ "OBD.component.InstallResult.BeforeExitingMakeSureThat": "Before exiting, make sure that the access address and account secret information have been copied.",
+ "OBD.component.InstallResult.Exit": "Exit",
+ "OBD.component.InstallResult.Complete": "Complete",
+ "OBD.component.InstallResult.PreviousStep": "Previous step",
+ "OBD.component.InstallResult.PleaseConfirmWhetherTheInstallation": "Please confirm whether the installation failure reason has been located and fix the problem?",
+ "OBD.component.InstallResult.ReinstallationWillFirstCleanUp": "Reinstallation will first clean up the failed OCP installation environment.",
+ "OBD.component.InstallResult.Ok": "OK",
+ "OBD.component.InstallResult.Cancel": "Cancel",
+ "OBD.component.InstallResult.Redeploy": "Redeploy",
+ "OBD.component.MetaDBConfig.ClusterConfig.ThePortNumberCanOnly": "The port number can only range from 1024 to 65535.",
+ "OBD.component.MetaDBConfig.ClusterConfig.ClusterConfiguration": "Cluster configuration",
+ "OBD.component.MetaDBConfig.ClusterConfig.RootSysPassword": "root @ sys password",
+ "OBD.component.MetaDBConfig.ClusterConfig.PleaseEnter": "Please enter",
+ "OBD.component.MetaDBConfig.ClusterConfig.RandomlyGenerated": "Randomly generated",
+ "OBD.component.MetaDBConfig.ClusterConfig.SoftwareInstallationPath": "Software Installation Path",
+ "OBD.component.MetaDBConfig.ClusterConfig.DataPath": "Data path",
+ "OBD.component.MetaDBConfig.ClusterConfig.LogPath": "Log path",
+ "OBD.component.MetaDBConfig.ClusterConfig.SqlPort": "SQL Port",
+ "OBD.component.MetaDBConfig.ClusterConfig.RpcPort": "rpc Port",
+ "OBD.component.MetaDBConfig.ClusterConfig.MoreConfigurations": "More configurations",
+ "OBD.component.MetaDBConfig.ConfigTable.ClusterParameterName": "Cluster parameter name",
+ "OBD.component.MetaDBConfig.ConfigTable.ParameterValue": "Parameter value",
+ "OBD.component.MetaDBConfig.ConfigTable.Introduction": "Introduction",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ItStartsWithALetter": "It starts with a letter and ends with a number. It can contain numbers and underscores and can be 2 to 32 characters in length.",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ZoneNameAlreadyOccupied": "Zone name already occupied",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.EnterTheCorrectIpAddress": "Enter the correct IP address.",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.SelectTheCorrectObproxyNode": "Select the correct OBProxy node",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ZoneName": "Zone name",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.AZoneThatRepresentsA": "A zone that represents a group of nodes with similar hardware availability in a cluster, usually in the same rack, data center, or region.",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ThisItemIsRequired": "This item is required",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ObserverNodes": "OBServer nodes",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.TheNodeWhereDatabaseService": "The node where Database Service (OBServer) resides, including SQL engine, transaction engine, and storage engine, and serves multiple data partitions.",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.RootserverNodes": "RootServer nodes",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.TheNodeWhereTheMaster": "The node where the Master Control Service (RootService) is located is used to perform cluster management, server management, and automatic load balancing.",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ThisOptionIsRequired": "This option is required.",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.SelectTheCorrectRootserverNode": "Select the correct RootServer node",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.PleaseSelect": "Please select",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.DatabaseNodeConfiguration": "Database node configuration",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.AddZone": "Add Zone",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.KeepAtLeastOneZone": "Keep at least one zone",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.AreYouSureYouWant": "Are you sure you want to delete the configuration of this Zone?",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.DoNotEnterDuplicateNodes": "Do not enter duplicate nodes",
+ "OBD.component.MetaDBConfig.NodeConfig.OcpNodeConfiguration": "OCP node configuration",
+ "OBD.component.MetaDBConfig.NodeConfig.PleaseEnter": "Please enter",
+ "OBD.component.MetaDBConfig.NodeConfig.SelectHost": "Select host",
+ "OBD.component.MetaDBConfig.OBProxyConfig.ThePortNumberCanOnly": "The port number can only range from 1024 to 65535.",
+ "OBD.component.MetaDBConfig.OBProxyConfig.ObproxyConfiguration": "OBProxy configuration",
+ "OBD.component.MetaDBConfig.OBProxyConfig.PleaseEnter": "Please enter",
+ "OBD.component.MetaDBConfig.OBProxyConfig.ObproxyNodes": "OBProxy nodes",
+ "OBD.component.MetaDBConfig.OBProxyConfig.SqlPort": "SQL Port",
+ "OBD.component.MetaDBConfig.OBProxyConfig.PortExporter": "Port Exporter",
+ "OBD.component.MetaDBConfig.OBProxyConfig.SoftwarePath": "Software path",
+ "OBD.component.MetaDBConfig.OBProxyConfig.MoreConfigurations": "More configurations",
+ "OBD.component.MetaDBConfig.UserConfig.DeployUserConfiguration": "Deploy user configuration",
+ "OBD.component.MetaDBConfig.UserConfig.Username": "Username",
+ "OBD.component.MetaDBConfig.UserConfig.EnterAUsername": "Enter a username",
+ "OBD.component.MetaDBConfig.UserConfig.ItStartsWithALetter": "It starts with a letter and can contain English, numbers, underscores, and hyphens, and cannot exceed 32 digits.",
+ "OBD.component.MetaDBConfig.UserConfig.PleaseProvideTheHostUser": "Please provide the host user name to automatically configure the platform-specific operating system user.",
+ "OBD.component.MetaDBConfig.UserConfig.ViewHelpDocuments": "View help documents",
+ "OBD.component.MetaDBConfig.UserConfig.PasswordOptional": "Password (optional)",
+ "OBD.component.MetaDBConfig.UserConfig.IfYouHaveConfiguredPassword": "If you have configured password-free logon, you do not need to enter the password again.",
+ "OBD.component.MetaDBConfig.UserConfig.SshPort": "SSH Port",
+ "OBD.component.MetaDBConfig.UserConfig.PleaseEnter": "Please enter",
+ "OBD.component.MetaDBConfig.UserConfig.UseTheRunningUser": "Use the running user",
+ "OBD.component.MetaDBConfig.UserConfig.RunningUsername": "Running username",
+ "OBD.component.MetaDBConfig.UserConfig.EnterARunningUsername": "Enter a running username",
+ "OBD.component.MetaDBConfig.MetadbConfigurationPreviousStep": "MetaDB configuration-previous step",
+ "OBD.component.MetaDBConfig.PreviousStep": "Previous step",
+ "OBD.component.MetaDBConfig.MetadbConfigurationNext": "MetaDB configuration-Next",
+ "OBD.component.MetaDBConfig.NextStep": "Next Step",
+ "OBD.component.ModifyOCPResourcePoolModal.OcpServerMemory": "OCP-Server memory",
+ "OBD.component.ModifyOCPResourcePoolModal.OcpTenantMemory": "OCP tenant memory",
+ "OBD.component.ModifyOCPResourcePoolModal.MetadataTenantMemory": "Metadata tenant memory",
+ "OBD.component.ModifyOCPResourcePoolModal.MonitorDataTenantMemory": "Monitor data tenant memory",
+ "OBD.component.ModifyOCPResourcePoolModal.OtherUsedMemory": "Other used memory",
+ "OBD.component.ModifyOCPResourcePoolModal.RemainingMemory": "Remaining memory",
+ "OBD.component.ModifyOCPResourcePoolModal.ResourceAllocation": "Resource allocation",
+ "OBD.component.ModifyOCPResourcePoolModal.AccordingToTheResourcesOf": "According to the resources of the current host environment, allocate resources for OCP-related services as follows",
+ "OBD.component.ModifyOCPResourcePoolModal.MemoryAllocationMapGib": "Memory allocation map (GiB)",
+ "OBD.component.ModifyOCPResourcePoolModal.Memory": "Memory",
+ "OBD.component.ModifyOCPResourcePoolModal.OcpTenant": "OCP tenant",
+ "OBD.component.ModifyOCPResourcePoolModal.MetadataTenant": "Metadata tenant",
+ "OBD.component.ModifyOCPResourcePoolModal.MonitorDataTenants": "Monitor data tenants",
+ "OBD.component.MyDrawer.Cancel": "Cancel",
+ "OBD.component.MyDrawer.Ok": "OK",
+ "OBD.src.component.MyDropdown.All": "All",
+ "OBD.src.component.MyInput.PleaseEnter": "Please enter",
+ "OBD.src.component.MySelect.PleaseSelect": "Please select",
+ "OBD.component.NoAuth.NoPermissionToView": "No permission to view",
+ "OBD.component.NoAuth.ContactTheAdministratorToActivate": "Contact the administrator to activate permissions",
+ "OBD.component.OCPConfig.ThePasswordsEnteredTwiceAre": "The passwords entered twice are inconsistent. Please enter them again.",
+ "OBD.component.OCPConfig.InvalidIpAddress": "Invalid IP address",
+ "OBD.component.OCPConfig.SystemConfiguration": "System Configuration",
+ "OBD.component.OCPConfig.SelectHost": "Select host",
+ "OBD.component.OCPConfig.EnterAnIpAddress": "Enter an IP address",
+ "OBD.component.OCPConfig.PleaseEnter": "Please enter",
+ "OBD.component.OCPConfig.HostUser": "Host user",
+ "OBD.component.OCPConfig.PleaseProvideUsersOnThe": "Please provide users on the operating system for automatic configuration of the installer.",
+ "OBD.component.OCPConfig.EnterAnAdministratorAccount": "Enter an administrator account",
+ "OBD.component.OCPConfig.UserPassword": "User password",
+ "OBD.component.OCPConfig.IfYouHaveSetPassword": "If you have set password-free, ignore this option.",
+ "OBD.component.OCPConfig.OcpServiceConfiguration": "OCP service configuration",
+ "OBD.component.OCPConfig.ServiceConfiguration": "Service configuration",
+ "OBD.component.OCPConfig.ClusterName": "Cluster name",
+ "OBD.component.OCPConfig.EnterAClusterName": "Enter a cluster name",
+ "OBD.component.OCPConfig.AdminPassword": "Admin password",
+ "OBD.component.OCPConfig.OcpPlatformAdministratorAccountPassword": "OCP platform administrator account password",
+ "OBD.component.OCPConfig.EnterAPassword": "Enter a password",
+ "OBD.component.OCPConfig.SoftwarePath": "Software path",
+ "OBD.component.OCPConfig.EnterTheSoftwarePath": "Enter the software path",
+ "OBD.component.OCPConfig.ServicePort": "Service Port",
+ "OBD.component.OCPConfig.EnterAServicePort": "Enter a service port",
+ "OBD.component.OCPConfig.PortOcpagent": "Port ocpagent",
+ "OBD.component.OCPConfig.EnterPortOcpagent": "Enter Port ocpagent",
+ "OBD.component.OCPConfig.OcpagentMonitoringPort": "ocpagent monitoring port",
+ "OBD.component.OCPConfig.EnterOcpagentMonitoringPort": "Enter ocpagent monitoring port",
+ "OBD.component.OCPConfig.MetadataTenantConfiguration": "Metadata tenant configuration",
+ "OBD.component.OCPConfig.TenantName": "Tenant name",
+ "OBD.component.OCPConfig.EnterATenantName": "Enter a tenant name",
+ "OBD.component.OCPConfig.Password": "Password",
+ "OBD.component.OCPConfig.ConfirmPassword": "Confirm password",
+ "OBD.component.OCPConfig.PleaseEnterANewPassword": "Please enter a new password again",
+ "OBD.component.OCPConfig.MonitorDataTenantConfiguration": "Monitor data tenant configuration",
+ "OBD.component.OCPConfigNew.ResourcePlan.CopiedSuccessfully": "Copied successfully",
+ "OBD.component.OCPConfigNew.ResourcePlan.TheCurrentBrowserDoesNot": "The current browser does not support copying text.",
+ "OBD.component.OCPConfigNew.ResourcePlan.ResourcePlanning": "Resource Planning",
+ "OBD.component.OCPConfigNew.ResourcePlan.TheOcpServiceRunsWith": "The OCP service runs with computing and storage resources. You need to plan resources based on the size of objects to be managed, including OCP service, MetaDB, and MonitorDB.",
+ "OBD.component.OCPConfigNew.ResourcePlan.YouAreExpectedToNeed": "You are expected to need to manage:",
+ "OBD.component.OCPConfigNew.ResourcePlan.Host": "Host",
+ "OBD.component.OCPConfigNew.ResourcePlan.PleaseEnter": "Please enter",
+ "OBD.component.OCPConfigNew.ResourcePlan.Less": "Less",
+ "OBD.component.OCPConfigNew.ResourcePlan.Table": "Table",
+ "OBD.component.OCPConfigNew.ResourcePlan.ResourceConfiguration": "Resource configuration",
+ "OBD.component.OCPConfigNew.ResourcePlan.Memory": "Memory",
+ "OBD.component.OCPConfigNew.ResourcePlan.MetadataTenantConfiguration": "Metadata tenant configuration",
+ "OBD.component.OCPConfigNew.ResourcePlan.TenantName": "Tenant name",
+ "OBD.component.OCPConfigNew.ResourcePlan.EnterATenantName": "Enter a tenant name",
+ "OBD.component.OCPConfigNew.ResourcePlan.Password": "Password",
+ "OBD.component.OCPConfigNew.ResourcePlan.EnterAPassword": "Enter a password",
+ "OBD.component.OCPConfigNew.ResourcePlan.RandomlyGenerated": "Randomly generated",
+ "OBD.component.OCPConfigNew.ResourcePlan.PleaseRememberThePasswordOr": "Please remember the password, or",
+ "OBD.component.OCPConfigNew.ResourcePlan.CopyPassword": "Copy password",
+ "OBD.component.OCPConfigNew.ResourcePlan.AndKeepItProperly": "And keep it properly",
+ "OBD.component.OCPConfigNew.ResourcePlan.MonitorDataTenantConfiguration": "Monitor data tenant configuration",
+ "OBD.component.OCPConfigNew.ServiceConfig.WeRecommendThatYouUse": "We recommend that you use the server load balancer address as a portal for external access to OCP websites to achieve high availability of OCP services. If not, you can choose to use the node IP address + Port of OCP to set it. Please log on to OCP later and go to System Management-> system parameter change ocp.site.url (restart takes effect)",
+ "OBD.component.OCPConfigNew.ServiceConfig.CopiedSuccessfully": "Copied successfully",
+ "OBD.component.OCPConfigNew.ServiceConfig.TheCurrentBrowserDoesNot": "The current browser does not support text copying.",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePortNumberCanOnly": "The port number can only range from 1024 to 65535.",
+ "OBD.component.OCPConfigNew.ServiceConfig.ServiceConfiguration": "Service configuration",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterAPassword": "Enter a password",
+ "OBD.component.OCPConfigNew.ServiceConfig.AdminPassword": "Admin password",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePasswordMustBeTo": "The password must be 8 to 32 characters in length and can contain at least 2 large, lowercase letters, numbers, and special characters, the supported special characters are ~! @ >>>& *_\\-+ = |(){}[] :>:;,.?/",
+ "OBD.component.OCPConfigNew.ServiceConfig.RandomlyGenerated": "Randomly generated",
+ "OBD.component.OCPConfigNew.ServiceConfig.CopyPassword": "Copy password",
+ "OBD.component.OCPConfigNew.ServiceConfig.KeepThePasswordInMind": "Keep the password in mind, or copy the password and keep it properly.",
+ "OBD.component.OCPConfigNew.ServiceConfig.SoftwarePath": "Software path",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterTheSoftwarePath": "Enter the software path",
+ "OBD.component.OCPConfigNew.ServiceConfig.LogPath": "Log path",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterALogPath": "Enter a log path",
+ "OBD.component.OCPConfigNew.ServiceConfig.PackagePath": "Package path",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterThePackagePath": "Enter the package path",
+ "OBD.component.OCPConfigNew.ServiceConfig.AddressForExternalAccessTo": "Address for external access to OCP website: it must start with http/https, include VIP address/domain name/Port URL, and end without slash/",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterOcpSiteUrl": "Enter ocp.site.url",
+ "OBD.component.OCPConfigNew.ServiceConfig.TheCurrentVerificationIsSuccessful": "The current verification is successful. Proceed to the next step.",
+ "OBD.component.OCPConfigNew.ServiceConfig.TheCurrentVerificationFailedPlease": "The current verification failed. Please enter it again.",
+ "OBD.component.OCPConfigNew.ServiceConfig.Verification": "Verification",
+ "OBD.component.OCPConfigNew.ServiceConfig.ServicePort": "Service Port",
+ "OBD.component.OCPConfigNew.ServiceConfig.PleaseEnter": "Please enter",
+ "OBD.component.OCPConfigNew.OcpConfigurationPreviousStep": "ocp configuration-previous step",
+ "OBD.component.OCPConfigNew.PreviousStep": "Previous step",
+ "OBD.component.OCPConfigNew.OcpConfigurationNextStep": "ocp configuration-Next Step",
+ "OBD.component.OCPConfigNew.NextStep": "Next Step",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.InstallationConfiguration": "Installation configuration",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.ClusterName": "Cluster name",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.InstallationType": "Installation type",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.ProductVersion": "Product Version",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.Product": "Product",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.CommunityEdition": "Community Edition",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.CommercialEdition": "Commercial Edition",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.Version": "Version",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ClusterConfiguration": "Cluster configuration",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.RootSysPassword": "root @ sys password",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.SoftwarePath": "Software path",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.DataPath": "Data path",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.LogPath": "Log path",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.MysqlPort": "mysql Port",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.RpcPort": "rpc Port",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObproxyConfiguration": "OBProxy configuration",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObproxyNodes": "OBProxy nodes",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.SqlPort": "SQL Port",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.PortExporter": "Port Exporter",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ParameterValue": "Parameter value",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.AutomaticAllocation": "Automatic allocation",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.Introduction": "Introduction",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ZoneName": "Zone name",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObServerNodes": "OB Server nodes",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.RootServerNodes": "Root Server nodes",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.DeployUserConfiguration": "Deploy user configuration",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.Username": "Username",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.Password": "Password",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.SshPort": "SSH Port",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.OcpNodeConfiguration": "OCP node configuration",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.DatabaseNodeConfiguration": "Database node configuration",
+ "OBD.OCPPreCheck.CheckInfo.ConnectInfo.ConnectionInformation": "Connection information",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.AdminPassword": "Admin password",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.SoftwarePath": "Software path",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.LogPath": "Log path",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.PackagePath": "Package path",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.DeployUserConfiguration": "Deploy user configuration",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.Username": "Username",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.Password": "Password",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.SshPort": "SSH Port",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.OcpDeploymentSelection": "OCP deployment selection",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ServiceConfiguration": "Service configuration",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ResourcePlanning": "Resource Planning",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ManageTheNumberOfHosts": "Manage the number of hosts",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ResourceConfiguration": "Resource configuration",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.Memory": "Memory",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.MetadataTenantConfiguration": "Metadata tenant configuration",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.TenantName": "Tenant name",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.MonitorDataTenantConfiguration": "Monitor data tenant configuration",
+ "OBD.OCPPreCheck.CheckInfo.InstallAll": "Install all",
+ "OBD.OCPPreCheck.CheckInfo.HostIp": "Host IP",
+ "OBD.OCPPreCheck.CheckInfo.AccessPort": "Access Port",
+ "OBD.OCPPreCheck.CheckInfo.AccessAccount": "Access account",
+ "OBD.OCPPreCheck.CheckInfo.Password": "Password",
+ "OBD.OCPPreCheck.CheckInfo.TheOcpInstallationInformationConfiguration": "The OCP installation information configuration has been completed. Check and confirm the following configuration information, and then start the pre-check.",
+ "OBD.OCPPreCheck.CheckInfo.PreCheckPreviousStep": "Pre-Check-previous step",
+ "OBD.OCPPreCheck.CheckInfo.PreviousStep": "Previous step",
+ "OBD.OCPPreCheck.CheckInfo.PreCheck": "Pre-Check",
+ "OBD.OCPPreCheck.CheckInfo.PreCheck.1": "Pre-Check",
+ "OBD.OCPPreCheck.PreCheck.AutomaticRepairSucceeded": "Automatic repair succeeded",
+ "OBD.OCPPreCheck.PreCheck.PreCheckResultReCheck": "Pre-check result-re-check",
+ "OBD.OCPPreCheck.PreCheck.ReCheck": "Re-check",
+ "OBD.OCPPreCheck.PreCheck.PreCheckResultAutomaticRepair": "Pre-check result-automatic repair",
+ "OBD.OCPPreCheck.PreCheck.AutomaticRepair": "Automatic Repair",
+ "OBD.OCPPreCheck.PreCheck.PreCheckResultsPreviousStep": "Pre-check results-previous step",
+ "OBD.OCPPreCheck.PreCheck.PreviousStep": "Previous step",
+ "OBD.OCPPreCheck.PreCheck.PreCheckFailedDeploymentGreyed": "Pre-Check failed-deployment greyed",
+ "OBD.OCPPreCheck.PreCheck.NextStep": "Next Step",
+ "OBD.OCPPreCheck.PreCheck.PreCheckSuccessfulDeployment": "Pre-Check successful-deployment",
+ "OBD.src.component.Password.ToCharactersInLength": "8 to 32 characters in length",
+ "OBD.src.component.Password.CanOnlyContainLettersNumbers": "Can only contain letters, numbers, and special characters ._+@#$%",
+ "OBD.src.component.Password.AtLeastUppercaseAndLowercase": "At least 2 uppercase and lowercase letters, numbers, and special characters",
+ "OBD.component.Result.Return": "Return",
+ "OBD.component.Result.ViewTaskDetails": "View task details",
+ "OBD.src.constant.configuration.DeploymentConfiguration": "Deployment configuration",
+ "OBD.src.constant.configuration.MetadbConfiguration": "MetaDB configuration",
+ "OBD.src.constant.configuration.OcpConfiguration": "OCP configuration",
+ "OBD.src.constant.configuration.PreCheck": "Pre-Check",
+ "OBD.src.constant.configuration.Deployment": "Deployment",
+ "OBD.src.constant.configuration.ObClusterConnectionConfiguration": "OB cluster connection configuration",
+ "OBD.src.constant.configuration.ConnectivityTest": "Connectivity Test",
+ "OBD.src.constant.configuration.EnvironmentPreCheck": "Environment pre-check",
+ "OBD.src.constant.configuration.OcpUpgrade": "OCP upgrade",
+ "OBD.src.constant.Checking": "Checking",
+ "OBD.src.constant.Pass": "Pass",
+ "OBD.src.constant.Failed": "Failed",
+ "OBD.src.constant.Ignored": "Ignored",
+ "OBD.src.constant.must-ignore.SimplifiedChinese": "Simplified Chinese",
+ "OBD.Component.ConnectionInfo.EnterAnAccount": "Enter an account",
+ "OBD.component.CustomPasswordInput.PasswordSettingsDoNotMeet": "Password settings do not meet the requirements.",
+ "OBD.component.CustomPasswordInput.CopiedSuccessfully": "Copied successfully",
+ "OBD.component.CustomPasswordInput.TheCurrentBrowserDoesNot": "The current browser does not support text copying.",
+ "OBD.component.CustomPasswordInput.KeepThePasswordInMind": "Keep the password in mind, or copy the password and keep it properly.",
+ "OBD.component.CustomPasswordInput.PleaseRememberThePasswordOr": "Please remember the password, or",
+ "OBD.component.CustomPasswordInput.CopyPassword": "Copy password",
+ "OBD.component.CustomPasswordInput.AndKeepItProperly": "And keep it properly",
+ "OBD.component.CustomPasswordInput.EnterAPassword": "Enter a password",
+ "OBD.component.CustomPasswordInput.RandomlyGenerated": "Randomly generated",
+ "OBD.component.DeployConfig.ItStartsWithALetter.1": "It starts with a letter, ends with a number, and can contain English, numbers, hyphens, and underscores. The length is 2 to 32.",
+ "OBD.src.component.ExitBtn.ExitTheOceanbaseDeploymentWizard": "Exit the OceanBase Deployment Wizard",
+ "OBD.component.MetaDBConfig.UserConfig.TheUserWhoRunsThe": "The user who runs the service.",
+ "OBD.src.utils.CanOnlyContainLettersNumbers.1": "Can only contain letters, numbers, and special characters._+ @$$%",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePasswordMustBeTo.1": "The password must be 8 to 32 characters in length and can contain at least 2 large, lowercase letters, numbers, and special characters, the supported special characters are._+ @ ##%",
+ "OBD.component.DeployConfig.UnableToObtainTheInstallation": "Unable to obtain the installation package. Please check the installation program configuration.",
+ "OBD.component.InstallResult.WeRecommendThatYouInstall": "We recommend that you install the new version OCP Agent in the host management module of the OCP platform for hosts that have not been upgraded to OCP Agent.",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePasswordMustBeTo.2": "The password must be 8 to 32 characters in length and can contain at least 2 large, lowercase letters, numbers, and special characters, the supported special characters are ~!@#%^&*_-+=`|(){}[]:;',.?/",
+ "OBD.src.utils.CanOnlyContainLettersNumbers.2": "Can only contain letters, numbers, and special characters ~!@#%^&*_-+=`|(){}[]:;',.?/",
+ "OBD.component.MetaDBConfig.UserConfig.OperatingSystemUsersRunningOcp": "Operating system users running OCP services",
+ "OBD.pages.Obdeploy.InstallConfig.OcpExpressOnlySupportsAnd": "OCP Express only supports 4.0 and later versions OceanBase Database.",
+ "OBD.pages.Obdeploy.InstallConfig.UnableToObtainTheInstallation": "Unable to obtain the installation package. Please check the installation program configuration.",
+ "OBD.component.MetaDBConfig.ClusterConfig.SqlPort.1": "SQL Port",
+ "OBD.component.MetaDBConfig.ClusterConfig.RpcPort.1": "RPC Port",
+ "OBD.component.PasswordCard.Password": "Password",
+ "OBD.component.InputPort.PleaseEnter": "Please enter",
+ "OBD.component.InputPort.ThePortNumberCanOnly": "The port number can only range from 1024 to 65535.",
+ "OBD.Component.ConnectionInfo.PasswordOptional": "Password (optional)",
+ "OBD.Component.ConnectionInfo.IfYouHaveConfiguredPassword": "If you have configured password-free logon, you do not need to enter the password again.",
+ "OBD.component.CustomPasswordInput.TheLengthShouldBeTo": "The length should be 8 to 32 characters.",
+ "OBD.component.CustomPasswordInput.CanOnlyContainLettersNumbers": "Can only contain letters, numbers, and special characters ~! @ **** & *_-+ = '|(){}[]:;',.?/",
+ "OBD.component.CustomPasswordInput.AtLeastUppercaseAndLowercase": "At least 2 uppercase and lowercase letters, numbers, and special characters",
+ "OBD.src.utils.TheLengthShouldBeTo": "The length should be 8 to 32 characters.",
+ "OBD.component.InsstallResult.ThereAreHostsThatHave.1": "· There are hosts that have not been upgraded to OCP Agent. We recommend that you use OCP\nThe host management module of the platform installs the new version OCP Agent.",
+ "OBD.component.InsstallResult.HostNotUpgraded": "Host not upgraded:",
+ "OBD.Component.ConnectionInfo.SshPort": "SSH Port",
+ "OBD.Component.UpdatePreCheck.MetadbSharesMetaTenantResources": "MetaDB shares Meta tenant resources with MonitorDB, which may cause OCP running exceptions. We strongly recommend that you create Monitor tenants, clean up MetaDB data, and migrate MonitorDB data. For more information, see 《",
+ "OBD.component.DeployConfig.IfTheCurrentEnvironmentCannot": "If the current environment cannot access the Internet properly, we recommend that you use OceanBase.\nInstall and deploy offline installer.",
+ "OBD.component.DeployConfig.GoToDownloadOfflineInstallation": "Go to download offline installation",
+ "OBD.component.DeployConfig.HowToEnableOnlineImage": "How to enable online image repository",
+ "OBD.component.OCPConfigNew.ResourcePlan.OcpApplicationMemory": "OCP application memory",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.OcpApplicationMemory": "OCP application memory"
}
diff --git a/web/src/i18n/strings/zh-CN.json b/web/src/i18n/strings/zh-CN.json
index 1e58025..4a2716c 100644
--- a/web/src/i18n/strings/zh-CN.json
+++ b/web/src/i18n/strings/zh-CN.json
@@ -286,5 +286,605 @@
"OBD.pages.components.NodeConfig.AreYouSureYouWant": "确定删除该条 Zone 的相关配置吗?",
"OBD.pages.components.ClusterConfig.PortObproxyOfExporterIs": "OBProxy 的 Exporter 端口,用于 Prometheus 拉取 OBProxy 监控数据。",
"OBD.pages.components.InstallProcess.Deploying": "部署中...",
- "OBD.pages.components.InstallConfig.OptionalComponents": "可选组件"
+ "OBD.pages.components.InstallConfig.OptionalComponents": "可选组件",
+ "OBD.pages.Guide.OceanbaseAndSupportingTools": "OceanBase 及配套工具",
+ "OBD.pages.Guide.DistributedDatabasesAndVariousTools": "分布式数据库以及各类工具,方便客户管理、运维和使用",
+ "OBD.pages.Guide.OceanbaseCloudPlatformFullLifecycle": "OceanBase 云平台:对 OB 集群进行全生命周期管理",
+ "OBD.pages.Guide.OceanbaseDeveloperCenterManageDatabases": "OceanBase 开发者中心:对数据库&表进行管理",
+ "OBD.pages.Guide.OceanbaseDataMigrationFastData": "OceanBase 数据迁移:对数据进行快速迁移",
+ "OBD.pages.Guide.TheProductIsUnderConstruction": "产品正在建设中",
+ "OBD.pages.Guide.SelectAnInstallationProduct": "请选择安装产品",
+ "OBD.pages.Guide.HelpDocument": "帮助文档",
+ "OBD.pages.Guide.WelcomeToTheOceanbaseDeployment": "欢迎使用 OceanBase 部署向导",
+ "OBD.pages.Guide.WizardSelectDeploymentTool": "向导-选择部署工具",
+ "OBD.pages.Guide.Ok": "确定",
+ "OBD.Obdeploy.ClusterConfig.ConfigTable.ParameterValue": "参数值",
+ "OBD.pages.Obdeploy.InstallConfig.ItStartsWithALetter": "以英文字母开头、英文或数字结尾,可包含英文、数字和下划线,且长度为 2 ~ 32",
+ "OBD.pages.Obdeploy.InstallConfig.PreviousStep": "上一步",
+ "OBD.OcpInstaller.Configuration.MetadbConnectionFailedPleaseCheck": "MetaDB 连接失败,请检查连接配置",
+ "OBD.OcpInstaller.Configuration.IKnow": "我知道了",
+ "OBD.OcpInstaller.Error.403.SorryYouAreNotAuthorized": "抱歉,你无权访问此页面",
+ "OBD.OcpInstaller.Error.403.ReturnToHomePage": "返回首页",
+ "OBD.OcpInstaller.Error.404.SorryThePageYouVisited": "抱歉,你访问的页面不存在",
+ "OBD.OcpInstaller.Error.404.ReturnToHomePage": "返回首页",
+ "OBD.OcpInstaller.Index.LifecycleManagementOM": "全生命周期管理(运维管控)",
+ "OBD.OcpInstaller.Index.OcpImplementsUnifiedManagementOf": "OCP 实现对 OceanBase 资源的统一管理,实现了对资源的创建、备份恢复、监控告警、巡检、自治、升级、删除等全生命周期管理。",
+ "OBD.OcpInstaller.Index.MonitoringAlarm": "监控告警",
+ "OBD.OcpInstaller.Index.OcpMonitorsOceanbaseFromDifferent": "OCP 支持从主机、集群、租户等不同维度对 OceanBase 进行监控,并且提供包括钉钉、微信、邮件等多种不同的告警方式,保障集群安全 。",
+ "OBD.OcpInstaller.Index.BackupAndRecovery": "备份恢复",
+ "OBD.OcpInstaller.Index.OcpProvidesBackupAndRecovery": "OCP 提供对 OceanBase 集群、租户的备份恢复能力,支持自动将全量、增量、日志备份到NAS、OSS等存储类型,支持一键恢复操作。",
+ "OBD.OcpInstaller.Index.DiagnosticOptimization": "诊断优化",
+ "OBD.OcpInstaller.Index.OcpProvidesClosedLoopDiagnostics": "OCP 针对 SQL 提供从感知、根因分析、执行建议的闭环诊断能力。OCP 同时还实现了从集群复制、会话、死锁、容量等维度的诊断能力。",
+ "OBD.OcpInstaller.Index.UpgradeWelcomePage": "升级欢迎页",
+ "OBD.OcpInstaller.Index.WelcomeToTheOcpUpgrade": "欢迎使用 OCP 升级向导",
+ "OBD.OcpInstaller.Index.StartUpgradingToV": "开始升级至 V 4.0.3",
+ "OBD.OcpInstaller.Index.InstallTheWelcomePage": "安装欢迎页",
+ "OBD.OcpInstaller.Index.WelcomeToTheOcpDeployment": "欢迎使用 OCP 部署向导",
+ "OBD.OcpInstaller.Index.SelectAMetadbConfigurationMethod": "请为 OCP 选择一个 MetaDB 的配置方式",
+ "OBD.OcpInstaller.Index.MetadbIsAnImportantPart": "MetaDB 是 OCP 重要组成部分,MetaDB 为 OCP 的管理元信息及监控数据提供底层存储能力,OCP-Server 通过调用 MetaDB 数据为您提供 OceanBase 数据库全生命周期管理服务。",
+ "OBD.OcpInstaller.Index.CreateANewOceanbaseDatabase": "创建全新的 OceanBase 数据库",
+ "OBD.OcpInstaller.Index.MetadbAsOcp": "作为 OCP 的 MetaDB",
+ "OBD.OcpInstaller.Index.Recommend": "推荐",
+ "OBD.OcpInstaller.Index.UseAnExistingOceanbaseDatabase": "使用已有的 OceanBase 数据库",
+ "OBD.OcpInstaller.Index.PreviousStep": "上一步",
+ "OBD.OcpInstaller.Index.Ok": "确定",
+ "OBD.Component.ModifyResourcePoolModal.NoResourcesFound": "未查询到资源",
+ "OBD.Component.ModifyResourcePoolModal.SystemPreOccupation": "系统预占用",
+ "OBD.Component.ModifyResourcePoolModal.OcpServiceReservation": "OCP 服务预留",
+ "OBD.Component.ModifyResourcePoolModal.CommonTenantReservation": "普通租户预留",
+ "OBD.Component.ModifyResourcePoolModal.ResourceAllocation": "资源分配",
+ "OBD.Component.ModifyResourcePoolModal.BasedOnTheMinimumAvailable": "根据当前环境最小可用资源计算,建议为 MetaDB 分配的资源如下:",
+ "OBD.Component.ModifyResourcePoolModal.DataFile": "数据文件",
+ "OBD.Component.ModifyResourcePoolModal.TheDataFileSpaceIs": "数据文件空间不足,数据文件大小默认为 memory_limit 的 3 倍",
+ "OBD.Component.ModifyResourcePoolModal.LogFile": "日志文件",
+ "OBD.Component.ModifyResourcePoolModal.TheLogFileHasInsufficient": "日志文件空间不足,日志文件大小默认为 memory_limit 的 3 倍",
+ "OBD.Install.Component.SystemConfig.InvalidIpAddress": "IP 地址不合法",
+ "OBD.Install.Component.SystemConfig.InstallAndDeployMetadbConfiguration": "安装部署MetaDB配置页",
+ "OBD.Install.Component.SystemConfig.SystemConfiguration": "系统配置",
+ "OBD.Install.Component.SystemConfig.ToAvoidOperatingSystemUser": "为了避免操作系统用户冲突,请为 MetaDB 及 OCP 配置独立的操作系统用户",
+ "OBD.Install.Component.SystemConfig.SelectHost": "选择主机",
+ "OBD.Install.Component.SystemConfig.EnterAnIpAddress": "请输入 IP 地址",
+ "OBD.Install.Component.SystemConfig.PleaseEnter": "请输入",
+ "OBD.Install.Component.SystemConfig.HostUser": "主机用户",
+ "OBD.Install.Component.SystemConfig.EnterAHostUser": "请输入主机用户",
+ "OBD.Install.Component.SystemConfig.UserPassword": "用户密码",
+ "OBD.Install.Component.SystemConfig.IfYouHaveSetPassword": "如果您已设置免密,请忽略本选项",
+ "OBD.Install.Component.SystemConfig.MetadbConfiguration": "MetaDB 配置",
+ "OBD.Install.Component.SystemConfig.ClusterName": "集群名称",
+ "OBD.Install.Component.SystemConfig.EnterAClusterName": "请输入集群名称",
+ "OBD.Install.Component.SystemConfig.RootSysPassword": "root@sys 密码",
+ "OBD.Install.Component.SystemConfig.EnterOrRandomlyGenerateThe": "请输入或随机生成 root@sys 密码",
+ "OBD.Install.Component.SystemConfig.SoftwarePath": "软件路径",
+ "OBD.Install.Component.SystemConfig.EnterTheSoftwarePath": "请输入软件路径",
+ "OBD.Install.Component.SystemConfig.DataPath": "数据路径",
+ "OBD.Install.Component.SystemConfig.EnterADataPath": "请输入数据路径",
+ "OBD.Install.Component.SystemConfig.LogPath": "日志路径",
+ "OBD.Install.Component.SystemConfig.EnterALogPath": "请输入日志路径",
+ "OBD.Install.Component.SystemConfig.SqlPort": "sql 端口",
+ "OBD.Install.Component.SystemConfig.TheSqlPortCannotBe": "SQL 端口不可与 RPC 端口相同",
+ "OBD.Install.Component.SystemConfig.RpcPort": "rpc 端口",
+ "OBD.Install.Component.SystemConfig.TheRpcPortCannotBe": "RPC 端口不可与 SQL 端口相同",
+ "OBD.Install.Component.SystemConfig.NicName": "网卡名称",
+ "OBD.Install.Component.SystemConfig.AutomaticConfiguration": "自动配置",
+ "OBD.Install.Component.SystemConfig.ManualConfiguration": "手动配置",
+ "OBD.Install.Component.SystemConfig.EnterANicName": "请输入网卡名称",
+ "OBD.Install.Component.SystemConfig.AMaximumOfCharactersCan": "最长可输入13个字符",
+ "OBD.Install.Component.SystemConfig.EnterTheNameOfThe": "请输入OBServer 绑定的网卡设备名",
+ "OBD.OcpInstaller.Install.CreateANewMetadbMetadb": "创建全新的 MetaDB,MetaDB 与 OCP-Server 将使用相同的主机部署服务。OCP-Server 将访问本地 MetaDB 以获得更好的服务可靠性。",
+ "OBD.Layout.BasicLayout.InstallationDeploymentAndUpgradeSystem": "安装部署升级系统信息",
+ "OBD.Layout.BasicLayout.OcpUpgradeWizardVersionNumber": "OCP 升级向导(版本号: 4.0.3)",
+ "OBD.Layout.BasicLayout.OcpDeploymentWizardVersionNumber": "OCP 部署向导(版本号: 4.0.3)",
+ "OBD.Layout.BasicLayout.VisitTheOfficialWebsite": "访问官网",
+ "OBD.Layout.BasicLayout.HelpCenter": "帮助中心",
+ "OBD.OcpInstaller.Layout.OceanbaseCloudPlatform": "OceanBase 云平台",
+ "OBD.OcpInstaller.Quit.TheUpgradeProgramHasExited": "升级程序已退出",
+ "OBD.OcpInstaller.Quit.TheDeploymentInstallerHasExited": "部署安装程序已经退出!",
+ "OBD.OcpInstaller.Quit.TheUpgradeProgramHasQuit": "升级程序已退出 如需再次启用升级程序,请在系统中执行",
+ "OBD.OcpInstaller.Quit.ToEnableTheDeploymentProgram": "如需再次启用部署程序,请在系统中执行",
+ "OBD.OcpInstaller.Quit.ExecuteOcpN": "执行ocp_N",
+ "OBD.Component.ConnectionInfo.ComponentName": "组件名称",
+ "OBD.Component.ConnectionInfo.NodeIp": "节点 IP",
+ "OBD.Component.ConnectionInfo.TheInstallerAutomaticallyObtainsMetadb": "安装程序根据当前主机 OCP 环境自动获取 MetaDB 配置信息,请检查 MetaDB 配置信息是否正确,OCP 将根据以下信息执行升级程序",
+ "OBD.Component.ConnectionInfo.ConnectionInformation": "连接信息",
+ "OBD.Component.ConnectionInfo.AccessAddress": "访问地址",
+ "OBD.Component.ConnectionInfo.EnterAnAccessAddress": "请输入访问地址",
+ "OBD.Component.ConnectionInfo.EnterADatabaseAccessIp": "请输入数据库访问 IP 地址",
+ "OBD.Component.ConnectionInfo.AccessPort": "访问端口",
+ "OBD.Component.ConnectionInfo.EnterAnAccessPort": "请输入访问端口",
+ "OBD.Component.ConnectionInfo.EnterTheDatabaseConnectionPort": "请输入数据库连接访问端口",
+ "OBD.Component.ConnectionInfo.DatabaseName": "数据库名",
+ "OBD.Component.ConnectionInfo.EnterADatabaseName": "请输入数据库名",
+ "OBD.Component.ConnectionInfo.AccessAccount": "访问账号",
+ "OBD.Component.ConnectionInfo.EnterAnAccessAccount": "请输入访问账号",
+ "OBD.Component.ConnectionInfo.AccessPassword": "访问密码",
+ "OBD.Component.ConnectionInfo.EnterAnAccessPassword": "请输入访问密码",
+ "OBD.Component.ConnectionInfo.PleaseEnter": "请输入",
+ "OBD.Component.ConnectionInfo.Verification": "验 证",
+ "OBD.Component.ConnectionInfo.TheCurrentVerificationFailedPlease": "当前验证失败,请重新填写错误参数",
+ "OBD.Component.ConnectionInfo.TheVerificationIsSuccessfulPlease": "当前验证成功,请填写下方参数",
+ "OBD.Component.ConnectionInfo.OperatingSystemUsers": "操作系统用户",
+ "OBD.Component.ConnectionInfo.Username": "用户名",
+ "OBD.Component.ConnectionInfo.EnterAUsername": "请输入用户名",
+ "OBD.Component.ConnectionInfo.PleaseProvideTheUserName": "请提供用户名用以自动化配置平台专用操作系统用户",
+ "OBD.Component.ConnectionInfo.Password": "密码",
+ "OBD.Component.ConnectionInfo.EnterAPassword": "请输入密码",
+ "OBD.Component.ConnectionInfo.TheVerificationIsSuccessfulProceed": "当前验证成功,请进行下一步",
+ "OBD.Component.ConnectionInfo.TheCurrentVerificationFailedPlease.1": "当前验证失败,请重新输入",
+ "OBD.Component.UpdatePreCheck.CheckItems": "检查项",
+ "OBD.Component.UpdatePreCheck.CheckStatus": "检查状态",
+ "OBD.Component.UpdatePreCheck.Impact": "影响",
+ "OBD.Component.UpdatePreCheck.UpgradeEnvironmentPreCheckPage": "升级环境预检查页",
+ "OBD.Component.UpdatePreCheck.BasedOnTheMetadbConfiguration": "根据 MetaDB 配置信息,成功获取 OCP 相关配置信息,为保证管理功能一致性,升级程序将升级平台管理服务(OCP Server) 及其管理的所有主机代理服务(OCP Agent),请检查并确认以下配置信息,确定后开始预检查",
+ "OBD.Component.UpdatePreCheck.InstallationConfiguration": "安装配置",
+ "OBD.Component.UpdatePreCheck.ClusterName": "集群名称",
+ "OBD.Component.UpdatePreCheck.UpgradeType": "升级类型",
+ "OBD.Component.UpdatePreCheck.UpgradeAll": "全部升级",
+ "OBD.Component.UpdatePreCheck.UpgradeConfigurationInformation": "升级配置信息",
+ "OBD.Component.UpdatePreCheck.PreUpgradeVersion": "升级前版本:",
+ "OBD.Component.UpdatePreCheck.UpgradedVersion": "升级后版本:",
+ "OBD.Component.UpdatePreCheck.ComponentName": "组件名称",
+ "OBD.Component.UpdatePreCheck.NodeIp": "节点 IP",
+ "OBD.Component.UpdatePreCheck.AreYouSureYouWant": "确认要忽略所有未通过的检查项吗?",
+ "OBD.Component.UpdatePreCheck.UpgradeIgnoresAllFailedItems": "升级忽略全部未通过项",
+ "OBD.Component.UpdatePreCheck.IgnoreAllFailedItems": "忽略全部未通过项",
+ "OBD.Component.UpdatePreCheck.PreCheckIsInProgress": "预检查进行中,暂不支持重新检查",
+ "OBD.Component.UpdatePreCheck.UpgradeAndReCheck": "升级重新检查",
+ "OBD.Component.UpdatePreCheck.ReCheck": "重新检查",
+ "OBD.OcpInstaller.Update.UnableToObtainOcpConfiguration": "无法获取 OCP 配置信息,请检查连接是否为 OCP 元信息数据库配置",
+ "OBD.OcpInstaller.Update.IKnow": "我知道了",
+ "OBD.OcpInstaller.Update.TheCurrentVersionCannotBe": "当前版本不可升级到目标版本",
+ "OBD.OcpInstaller.Update.TheCurrentVersionOfOcp": "当前 OCP 版本为 V 3.0.0,暂不直接支持升级到 V 4.1.0 您可通过 OCP\n 访问地址",
+ "OBD.OcpInstaller.Update.ViewVersionDetailsForMore": "查看版本详细信息\n 了解更多 OCP 版本升级路径信息,请查看",
+ "OBD.OcpInstaller.Update.VersionReleaseRecord": "版本发布记录",
+ "OBD.OcpInstaller.Update.InThePreCheckProcess": "预检查中,暂不支持进入上一步",
+ "OBD.OcpInstaller.Update.PreviousStep": "上一步",
+ "OBD.OcpInstaller.Update.InThePreCheckProcess.1": "预检查中,暂不支持进入下一步",
+ "OBD.OcpInstaller.Update.PreCheck": "预检查",
+ "OBD.OcpInstaller.Update.NextStep": "下一步",
+ "OBD.OcpInstaller.Update.UpgradeAgain": "重新升级",
+ "OBD.OcpInstaller.Welcome.WelcomeToTheOcpUpgrade": "欢迎您使用 OCP 升级向导",
+ "OBD.OcpInstaller.Welcome.StartUpgrade": "开始升级",
+ "OBD.pages.constants.OcpParameterName": "OCP 参数名称",
+ "OBD.src.utils.SelectTheCorrectOcpNode": "请选择正确的 OCP 节点",
+ "OBD.src.utils.EnterAPassword": "请输入密码",
+ "OBD.src.utils.ToCharactersInLength": "长度为 8~32 个字符",
+ "OBD.src.utils.CanOnlyContainLettersNumbers": "只能包含字母、数字和特殊字符(~!@#%^&*_-+=|(){}[]:;,.?/`$\"<>)",
+ "OBD.src.utils.AtLeastUppercaseAndLowercase": "大小写字母、数字和特殊字符都至少包含 2 个",
+ "OBD.src.component.Access.NoOperationPermissionIsAvailable": "暂无操作权限,请联系管理员开通权限",
+ "OBD.component.AobException.ReturnToHomePage": "返回首页",
+ "OBD.component.ConnectConfig.MetadbConnectionFailedPleaseCheck": "MetaDB 连接失败,请检查连接配置",
+ "OBD.component.ConnectConfig.IKnow": "我知道了",
+ "OBD.component.ConnectConfig.ConnectionInformation": "连接信息",
+ "OBD.component.ConnectConfig.HostIp": "主机 IP",
+ "OBD.component.ConnectConfig.EnterTheHostIpAddress": "请输入主机 IP",
+ "OBD.component.ConnectConfig.TheHostIpAddressFormat": "主机IP格式不正确",
+ "OBD.component.ConnectConfig.EnterADatabaseAccessIp": "请输入数据库访问 IP 地址",
+ "OBD.component.ConnectConfig.AccessPort": "访问端口",
+ "OBD.component.ConnectConfig.EnterAnAccessPort": "请输入访问端口",
+ "OBD.component.ConnectConfig.EnterTheDatabaseConnectionPort": "请输入数据库连接访问端口",
+ "OBD.component.ConnectConfig.AccessAccount": "访问账号",
+ "OBD.component.ConnectConfig.EnterAnAccessAccount": "请输入访问账号",
+ "OBD.component.ConnectConfig.AccessPassword": "访问密码",
+ "OBD.component.ConnectConfig.OcpPlatformAdministratorAccountPassword": "OCP 平台管理员账号密码",
+ "OBD.component.ConnectConfig.EnterAnAccessPassword": "请输入访问密码",
+ "OBD.component.ConnectConfig.PleaseEnter": "请输入",
+ "OBD.component.ConnectConfig.PreviousStep": "上一步",
+ "OBD.component.ConnectConfig.NextStep": "下一步",
+ "OBD.component.DeployConfig.EnterpriseLevelDataManagementPlatform": "以 OceanBase 为核心的企业级数据管理平台,实现 OceanBase 全生命周期运维管理",
+ "OBD.component.DeployConfig.FinancialLevelDistributedDatabasesAre": "金融级分布式数据库,具备数据强一致,高扩展、高性价比稳定可靠等特征",
+ "OBD.component.DeployConfig.OceanbaseADedicatedDatabaseProxy": "OceanBase 数据库专用代理服务器,可将用户的 SQL 请求转发至最佳目标 OBServer",
+ "OBD.component.DeployConfig.ProductName": "产品名称",
+ "OBD.component.DeployConfig.Version": "版本",
+ "OBD.component.DeployConfig.CommunityEdition": "社区版",
+ "OBD.component.DeployConfig.CommercialEdition": "商业版",
+ "OBD.component.DeployConfig.Description": "描述",
+ "OBD.component.DeployConfig.LearnMore": "了解更多",
+ "OBD.component.DeployConfig.ItStartsWithALetter": "以英文字母开头、英文或数字结尾,可包含英文、数字和下划线,且长度为 2 ~ 32",
+ "OBD.component.DeployConfig.TheDeploymentNameWithValue": "不存在为 {{value}} 的部署名称",
+ "OBD.component.DeployConfig.ADeploymentNameWithValue": "已存在为 {{value}} 的部署名称,请指定新名称",
+ "OBD.component.DeployConfig.BasicConfiguration": "基础配置",
+ "OBD.component.DeployConfig.DeploymentConfiguration": "部署配置",
+ "OBD.component.DeployConfig.ClusterName": "集群名称",
+ "OBD.component.DeployConfig.PleaseEnter": "请输入",
+ "OBD.component.DeployConfig.EnterAClusterName": "请输入集群名称",
+ "OBD.component.DeployConfig.VersionSelection": "版本选择",
+ "OBD.component.DeployConfig.EstimatedInstallationRequirements": "预计安装需要",
+ "OBD.component.DeployConfig.MbSpace": "MB空间",
+ "OBD.component.DeployConfig.DeploymentConfigurationPreviousStep": "部署配置-上一步",
+ "OBD.component.DeployConfig.PreviousStep": "上一步",
+ "OBD.component.DeployConfig.DeploymentConfigurationNextStep": "部署配置-下一步",
+ "OBD.component.DeployConfig.NextStep": "下一步",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.OnlyManualFixes": "只看手动修复项",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.AutomaticRepair": "自动修复",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.NoFailedItemsFoundYet": "暂未发现失败项",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.GreatCheckAllSuccessful": "太棒了!检查全部成功!",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.Reason": "原因:",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.Suggestions": "建议:",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.ManualRepair": "手动修复",
+ "OBD.component.EnvPreCheck.CheckFailuredItem.LearnMore": "了解更多方案",
+ "OBD.component.EnvPreCheck.CheckItem.CheckItemPassedcountTotal": "检查项 {{passedCount}}/{{total}}",
+ "OBD.component.EnvPreCheck.CheckItem.ReCheck": "重新检查",
+ "OBD.component.EnvPreCheck.CheckCompleted": "检查完成",
+ "OBD.component.EnvPreCheck.Checking": "检查中",
+ "OBD.component.EnvPreCheck.VerifyingThatYourEnvironmentMeets": "正在验证您的环境是否满足安装和配置 MetaDB 及 OCP 的所有最低要求。",
+ "OBD.component.EnvPreCheck.InstallAndDeployOcpEnvironment": "安装部署OCP环境预检查页",
+ "OBD.component.EnvPreCheck.InstallAndDeployMetadbEnvironment": "安装部署MetaDB环境预检查页",
+ "OBD.src.component.ErrorBoundary.PageExecutionError": "页面执行出错",
+ "OBD.src.component.ErrorBoundary.ReturnToHomePage": "返回首页",
+ "OBD.src.component.ExitBtn.DeploymentConfigurationExit": "部署配置-退出",
+ "OBD.src.component.ExitBtn.ExitTheOcpUpgradeProgram": "退出 OCP 升级程序",
+ "OBD.src.component.ExitBtn.ExitTheOcpDeploymentInstaller": "退出 OCP 部署安装程序",
+ "OBD.src.component.ExitBtn.AfterExitingTheUpgradeWill": "退出后,升级工作将被终止,请谨慎操作",
+ "OBD.src.component.ExitBtn.AfterExitingTheDeploymentAnd": "退出后,部署安装工作将被终止,请谨慎操作。",
+ "OBD.src.component.ExitBtn.Exit": "退出",
+ "OBD.component.InsstallResult.ComponentName": "组件名称",
+ "OBD.component.InsstallResult.NodeIp": "节点 IP",
+ "OBD.component.InsstallResult.UpgradeSuccessful": "升级成功",
+ "OBD.component.InsstallResult.OcpUpgradedSuccessfully": "OCP 升级成功",
+ "OBD.component.InsstallResult.InstallationAndDeploymentNoMetadb": "安装部署无MetaDB部署成功",
+ "OBD.component.InsstallResult.OcpDeployedSuccessfully": "OCP 部署成功",
+ "OBD.component.InsstallResult.MetadbIsDeployedSuccessfully": "安装部署有MetaDB部署成功",
+ "OBD.component.InsstallResult.InstallationAndDeploymentMetadbDeployment": "安装部署MetaDB部署成功",
+ "OBD.component.InsstallResult.MetadbDeployedSuccessfully": "MetaDB 部署成功",
+ "OBD.component.InsstallResult.UpgradeFailed": "升级失败",
+ "OBD.component.InsstallResult.OcpUpgradeFailed": "OCP 升级失败",
+ "OBD.component.InsstallResult.InstallationAndDeploymentNoMetadb.1": "安装部署无MetaDB部署失败",
+ "OBD.component.InsstallResult.OcpDeploymentFailed": "OCP 部署失败",
+ "OBD.component.InsstallResult.FailedToInstallAndDeploy": "安装部署有MetaDB部署失败",
+ "OBD.component.InsstallResult.FailedToInstallAndDeploy.1": "安装部署MetaDB部署失败",
+ "OBD.component.InsstallResult.MetadbDeploymentFailed": "MetaDB 部署失败",
+ "OBD.component.InsstallResult.PleaseCheckTheLogInformation": "请查看日志信息获取失败原因,联系技术支持同学处理",
+ "OBD.component.InsstallResult.UpgradeReport": "升级报告",
+ "OBD.component.InsstallResult.ThereAreHostsThatHave": "·存在未升级 OCP Agent 的主机,建议您在 OCP\n 平台「主机管理」模块安装新版本 OCP Agent。\n 未升级主机:",
+ "OBD.component.InsstallResult.PreUpgradeVersion": "升级前版本:",
+ "OBD.component.InsstallResult.UpgradedVersion": "升级后版本:",
+ "OBD.component.InsstallResult.Click": "点击",
+ "OBD.component.InsstallResult.OcpReleaseRecords": "OCP 发布记录",
+ "OBD.component.InsstallResult.LearnMoreAboutTheNew": "了解新版本更多信息",
+ "OBD.component.InsstallResult.AccessAddressAndAccountSecret": "访问地址及账密信息",
+ "OBD.component.InsstallResult.PleaseKeepTheFollowingAccess": "请妥善保存以下访问地址及账密信息,及时更新 OCP\n 初始密码,如需了解更多,请访问",
+ "OBD.component.InsstallResult.OceanbaseDocumentCenter": "OceanBase 文档中心",
+ "OBD.component.InsstallResult.CopyInformation": "复制信息",
+ "OBD.component.InsstallResult.AccessAddress": "访问地址",
+ "OBD.component.InsstallResult.AdministratorAccount": "管理员账号",
+ "OBD.component.InsstallResult.InitialPassword": "初始密码",
+ "OBD.component.InstallProcess.Upgraded": "升级中",
+ "OBD.component.InstallProcess.Deploying": "部署中",
+ "OBD.component.InstallProcess.Deploying.1": "正在部署",
+ "OBD.component.InstallProcess.UpgradeLogs": "升级日志",
+ "OBD.component.InstallProcess.DeploymentLogs": "部署日志",
+ "OBD.component.InstallProcessNew.Upgrading": "升级中...",
+ "OBD.component.InstallResult.ComponentName": "组件名称",
+ "OBD.component.InstallResult.NodeIp": "节点 IP",
+ "OBD.component.InstallResult.UpgradeSuccessful": "升级成功",
+ "OBD.component.InstallResult.OcpUpgradedSuccessfully": "OCP 升级成功",
+ "OBD.component.InstallResult.InstallationAndDeploymentNoMetadb": "安装部署无MetaDB部署成功",
+ "OBD.component.InstallResult.OcpDeployedSuccessfully": "OCP 部署成功",
+ "OBD.component.InstallResult.MetadbIsDeployedSuccessfully": "安装部署有MetaDB部署成功",
+ "OBD.component.InstallResult.UpgradeFailed": "升级失败",
+ "OBD.component.InstallResult.OcpUpgradeFailed": "OCP 升级失败",
+ "OBD.component.InstallResult.InstallationAndDeploymentNoMetadb.1": "安装部署无MetaDB部署失败",
+ "OBD.component.InstallResult.OcpDeploymentFailed": "OCP 部署失败",
+ "OBD.component.InstallResult.FailedToInstallAndDeploy": "安装部署有MetaDB部署失败",
+ "OBD.component.InstallResult.PleaseCheckTheLogInformation": "请查看日志信息获取失败原因,联系技术支持同学处理",
+ "OBD.component.InstallResult.UpgradeReport": "升级报告",
+ "OBD.component.InstallResult.ThereAreHostsThatHave": "·存在未升级 OCP Agent 的主机,建议您在 OCP\n 平台「主机管理」模块安装新版本 OCP Agent。\n 未升级主机:",
+ "OBD.component.InstallResult.PreUpgradeVersion": "升级前版本:",
+ "OBD.component.InstallResult.UpgradedVersion": "升级后版本:",
+ "OBD.component.InstallResult.Click": "点击",
+ "OBD.component.InstallResult.OcpReleaseRecords": "OCP 发布记录",
+ "OBD.component.InstallResult.LearnMoreAboutTheNew": "了解新版本更多信息",
+ "OBD.component.InstallResult.AccessAddressAndAccountSecret": "访问地址及账密信息",
+ "OBD.component.InstallResult.PleaseKeepTheFollowingAccess": "请妥善保存以下访问地址及账密信息,及时更新 OCP\n 初始密码,如需了解更多,请访问",
+ "OBD.component.InstallResult.OceanbaseDocumentCenter": "OceanBase 文档中心",
+ "OBD.component.InstallResult.CopyInformation": "复制信息",
+ "OBD.component.InstallResult.AccessAddress": "访问地址",
+ "OBD.component.InstallResult.AdministratorAccount": "管理员账号",
+ "OBD.component.InstallResult.InitialPassword": "初始密码",
+ "OBD.component.InstallResult.UpgradeLogs": "升级日志",
+ "OBD.component.InstallResult.DeploymentLogs": "部署日志",
+ "OBD.component.InstallResult.ExitTheInstaller": "退出安装程序",
+ "OBD.component.InstallResult.DoYouWantToExit": "是否要退出页面?",
+ "OBD.component.InstallResult.BeforeExitingMakeSureThat": "退出前,请确保已复制访问地址及账密信息",
+ "OBD.component.InstallResult.Exit": "退出",
+ "OBD.component.InstallResult.Complete": "完成",
+ "OBD.component.InstallResult.PreviousStep": "上一步",
+ "OBD.component.InstallResult.PleaseConfirmWhetherTheInstallation": "请确认是否已定位安装失败原因并修复问题?",
+ "OBD.component.InstallResult.ReinstallationWillFirstCleanUp": "重新安装将先清理失败的 OCP 安装环境",
+ "OBD.component.InstallResult.Ok": "确定",
+ "OBD.component.InstallResult.Cancel": "取消",
+ "OBD.component.InstallResult.Redeploy": "重新部署",
+ "OBD.component.MetaDBConfig.ClusterConfig.ThePortNumberCanOnly": "端口号只支持 1024~65535 范围",
+ "OBD.component.MetaDBConfig.ClusterConfig.ClusterConfiguration": "集群配置",
+ "OBD.component.MetaDBConfig.ClusterConfig.RootSysPassword": "root@sys 密码",
+ "OBD.component.MetaDBConfig.ClusterConfig.PleaseEnter": "请输入",
+ "OBD.component.MetaDBConfig.ClusterConfig.RandomlyGenerated": "随机生成",
+ "OBD.component.MetaDBConfig.ClusterConfig.SoftwareInstallationPath": "软件安装路径",
+ "OBD.component.MetaDBConfig.ClusterConfig.DataPath": "数据路径",
+ "OBD.component.MetaDBConfig.ClusterConfig.LogPath": "日志路径",
+ "OBD.component.MetaDBConfig.ClusterConfig.SqlPort": "sql 端口",
+ "OBD.component.MetaDBConfig.ClusterConfig.RpcPort": "rpc 端口",
+ "OBD.component.MetaDBConfig.ClusterConfig.MoreConfigurations": "更多配置",
+ "OBD.component.MetaDBConfig.ConfigTable.ClusterParameterName": "集群参数名称",
+ "OBD.component.MetaDBConfig.ConfigTable.ParameterValue": "参数值",
+ "OBD.component.MetaDBConfig.ConfigTable.Introduction": "介绍",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ItStartsWithALetter": "以英文字母开头,英文或数字结尾,可包含英文数字和下划线且长度在 2-32 个字符之间",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ZoneNameAlreadyOccupied": "Zone 名称已被占用",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.EnterTheCorrectIpAddress": "请输入正确的 IP 地址",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.SelectTheCorrectObproxyNode": "请选择正确的 OBProxy 节点",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ZoneName": "Zone 名称",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.AZoneThatRepresentsA": "可用区,表示集群内具有相似硬件可用性的一组节点,通常为同一个机架、机房或地域。",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ThisItemIsRequired": "此项是必填项",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ObserverNodes": "OBServer 节点",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.TheNodeWhereDatabaseService": "数据库服务(OBServer)所在节点,包含 SQL 引擎、事务引擎和存储引擎,并服务多个数据分区。",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.RootserverNodes": "RootServer 节点",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.TheNodeWhereTheMaster": "总控服务(RootService)所在节点,用于执行集群管理、服务器管理、自动负载均衡等操作。",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.ThisOptionIsRequired": "此项是必选项",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.SelectTheCorrectRootserverNode": "请选择正确的 RootServer 节点",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.PleaseSelect": "请选择",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.DatabaseNodeConfiguration": "数据库节点配置",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.AddZone": "新增 Zone",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.KeepAtLeastOneZone": "至少保留一个 zone",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.AreYouSureYouWant": "确定删除该条 Zone 的相关配置吗?",
+ "OBD.component.MetaDBConfig.DataBaseNodeConfig.DoNotEnterDuplicateNodes": "禁止输入重复节点",
+ "OBD.component.MetaDBConfig.NodeConfig.OcpNodeConfiguration": "OCP 节点配置",
+ "OBD.component.MetaDBConfig.NodeConfig.PleaseEnter": "请输入",
+ "OBD.component.MetaDBConfig.NodeConfig.SelectHost": "选择主机",
+ "OBD.component.MetaDBConfig.OBProxyConfig.ThePortNumberCanOnly": "端口号只支持 1024~65535 范围",
+ "OBD.component.MetaDBConfig.OBProxyConfig.ObproxyConfiguration": "OBProxy 配置",
+ "OBD.component.MetaDBConfig.OBProxyConfig.PleaseEnter": "请输入",
+ "OBD.component.MetaDBConfig.OBProxyConfig.ObproxyNodes": "OBProxy 节点",
+ "OBD.component.MetaDBConfig.OBProxyConfig.SqlPort": "SQL 端口",
+ "OBD.component.MetaDBConfig.OBProxyConfig.PortExporter": "Exporter 端口",
+ "OBD.component.MetaDBConfig.OBProxyConfig.SoftwarePath": "软件路径",
+ "OBD.component.MetaDBConfig.OBProxyConfig.MoreConfigurations": "更多配置",
+ "OBD.component.MetaDBConfig.UserConfig.DeployUserConfiguration": "部署用户配置",
+ "OBD.component.MetaDBConfig.UserConfig.Username": "用户名",
+ "OBD.component.MetaDBConfig.UserConfig.EnterAUsername": "请输入用户名",
+ "OBD.component.MetaDBConfig.UserConfig.ItStartsWithALetter": "以英文字母开头,可包含英文、数字、下划线和连字符,且不超过32位",
+ "OBD.component.MetaDBConfig.UserConfig.PleaseProvideTheHostUser": "请提供主机用户名用以自动化配置平台专用操作系统用户",
+ "OBD.component.MetaDBConfig.UserConfig.ViewHelpDocuments": "查看帮助文档",
+ "OBD.component.MetaDBConfig.UserConfig.PasswordOptional": "密码(可选)",
+ "OBD.component.MetaDBConfig.UserConfig.IfYouHaveConfiguredPassword": "如已配置免密登录,则无需再次输入密码",
+ "OBD.component.MetaDBConfig.UserConfig.SshPort": "SSH端口",
+ "OBD.component.MetaDBConfig.UserConfig.PleaseEnter": "请输入",
+ "OBD.component.MetaDBConfig.UserConfig.UseTheRunningUser": "使用运行用户",
+ "OBD.component.MetaDBConfig.UserConfig.RunningUsername": "运行用户名",
+ "OBD.component.MetaDBConfig.UserConfig.EnterARunningUsername": "请输入运行用户名",
+ "OBD.component.MetaDBConfig.MetadbConfigurationPreviousStep": "MetaDB配置-上一步",
+ "OBD.component.MetaDBConfig.PreviousStep": "上一步",
+ "OBD.component.MetaDBConfig.MetadbConfigurationNext": "MetaDB配置-下一步",
+ "OBD.component.MetaDBConfig.NextStep": "下一步",
+ "OBD.component.ModifyOCPResourcePoolModal.OcpServerMemory": "OCP-Server 内存",
+ "OBD.component.ModifyOCPResourcePoolModal.OcpTenantMemory": "OCP 租户内存",
+ "OBD.component.ModifyOCPResourcePoolModal.MetadataTenantMemory": "元信息租户 内存",
+ "OBD.component.ModifyOCPResourcePoolModal.MonitorDataTenantMemory": "监控数据租户内存",
+ "OBD.component.ModifyOCPResourcePoolModal.OtherUsedMemory": "其他已使用内存",
+ "OBD.component.ModifyOCPResourcePoolModal.RemainingMemory": "剩余内存",
+ "OBD.component.ModifyOCPResourcePoolModal.ResourceAllocation": "资源分配",
+ "OBD.component.ModifyOCPResourcePoolModal.AccordingToTheResourcesOf": "根据当前主机环境的资源情况,为 OCP 相关服务分配资源如下",
+ "OBD.component.ModifyOCPResourcePoolModal.MemoryAllocationMapGib": "内存分配图(GiB)",
+ "OBD.component.ModifyOCPResourcePoolModal.Memory": "内存",
+ "OBD.component.ModifyOCPResourcePoolModal.OcpTenant": "OCP 租户",
+ "OBD.component.ModifyOCPResourcePoolModal.MetadataTenant": "元信息租户",
+ "OBD.component.ModifyOCPResourcePoolModal.MonitorDataTenants": "监控数据租户",
+ "OBD.component.MyDrawer.Cancel": "取消",
+ "OBD.component.MyDrawer.Ok": "确定",
+ "OBD.src.component.MyDropdown.All": "全部",
+ "OBD.src.component.MyInput.PleaseEnter": "请输入",
+ "OBD.src.component.MySelect.PleaseSelect": "请选择",
+ "OBD.component.NoAuth.NoPermissionToView": "暂无权限查看",
+ "OBD.component.NoAuth.ContactTheAdministratorToActivate": "请联系管理员开通权限",
+ "OBD.component.OCPConfig.ThePasswordsEnteredTwiceAre": "两次输入的密码不一致,请重新输入",
+ "OBD.component.OCPConfig.InvalidIpAddress": "IP 地址不合法",
+ "OBD.component.OCPConfig.SystemConfiguration": "系统配置",
+ "OBD.component.OCPConfig.SelectHost": "选择主机",
+ "OBD.component.OCPConfig.EnterAnIpAddress": "请输入 IP 地址",
+ "OBD.component.OCPConfig.PleaseEnter": "请输入",
+ "OBD.component.OCPConfig.HostUser": "主机用户",
+ "OBD.component.OCPConfig.PleaseProvideUsersOnThe": "请提供操作系统上用户以便安装程序进行自动化配置",
+ "OBD.component.OCPConfig.EnterAnAdministratorAccount": "请输入管理员账号",
+ "OBD.component.OCPConfig.UserPassword": "用户密码",
+ "OBD.component.OCPConfig.IfYouHaveSetPassword": "如果您已设置免密,请忽略本选项",
+ "OBD.component.OCPConfig.OcpServiceConfiguration": "OCP 服务配置",
+ "OBD.component.OCPConfig.ServiceConfiguration": "服务配置",
+ "OBD.component.OCPConfig.ClusterName": "集群名称",
+ "OBD.component.OCPConfig.EnterAClusterName": "请输入集群名称",
+ "OBD.component.OCPConfig.AdminPassword": "Admin 密码",
+ "OBD.component.OCPConfig.OcpPlatformAdministratorAccountPassword": "OCP 平台管理员账号密码",
+ "OBD.component.OCPConfig.EnterAPassword": "请输入密码",
+ "OBD.component.OCPConfig.SoftwarePath": "软件路径",
+ "OBD.component.OCPConfig.EnterTheSoftwarePath": "请输入软件路径",
+ "OBD.component.OCPConfig.ServicePort": "服务端口",
+ "OBD.component.OCPConfig.EnterAServicePort": "请输入服务端口",
+ "OBD.component.OCPConfig.PortOcpagent": "ocpagent 端口",
+ "OBD.component.OCPConfig.EnterPortOcpagent": "请输入 ocpagent 端口",
+ "OBD.component.OCPConfig.OcpagentMonitoringPort": "ocpagent 监控端口",
+ "OBD.component.OCPConfig.EnterOcpagentMonitoringPort": "请输入 ocpagent 监控端口",
+ "OBD.component.OCPConfig.MetadataTenantConfiguration": "元信息租户配置",
+ "OBD.component.OCPConfig.TenantName": "租户名称",
+ "OBD.component.OCPConfig.EnterATenantName": "请输入租户名称",
+ "OBD.component.OCPConfig.Password": "密码",
+ "OBD.component.OCPConfig.ConfirmPassword": "确认密码",
+ "OBD.component.OCPConfig.PleaseEnterANewPassword": "请再次输入新密码",
+ "OBD.component.OCPConfig.MonitorDataTenantConfiguration": "监控数据租户配置",
+ "OBD.component.OCPConfigNew.ResourcePlan.CopiedSuccessfully": "复制成功",
+ "OBD.component.OCPConfigNew.ResourcePlan.TheCurrentBrowserDoesNot": "当前浏览器不支持复制文本",
+ "OBD.component.OCPConfigNew.ResourcePlan.ResourcePlanning": "资源规划",
+ "OBD.component.OCPConfigNew.ResourcePlan.TheOcpServiceRunsWith": "OCP 服务在运行过程中会有计算和存储资源开销,您需要根据待管理的对象规模进行资源规划,包括 OCP 服务、MetaDB 和 MonitorDB。",
+ "OBD.component.OCPConfigNew.ResourcePlan.YouAreExpectedToNeed": "您预计需要管理:",
+ "OBD.component.OCPConfigNew.ResourcePlan.Host": "主机",
+ "OBD.component.OCPConfigNew.ResourcePlan.PleaseEnter": "请输入",
+ "OBD.component.OCPConfigNew.ResourcePlan.Less": "小于",
+ "OBD.component.OCPConfigNew.ResourcePlan.Table": "台",
+ "OBD.component.OCPConfigNew.ResourcePlan.ResourceConfiguration": "资源配置",
+ "OBD.component.OCPConfigNew.ResourcePlan.Memory": "内存",
+ "OBD.component.OCPConfigNew.ResourcePlan.MetadataTenantConfiguration": "元信息租户配置",
+ "OBD.component.OCPConfigNew.ResourcePlan.TenantName": "租户名称",
+ "OBD.component.OCPConfigNew.ResourcePlan.EnterATenantName": "请输入租户名称",
+ "OBD.component.OCPConfigNew.ResourcePlan.Password": "密码",
+ "OBD.component.OCPConfigNew.ResourcePlan.EnterAPassword": "请输入密码",
+ "OBD.component.OCPConfigNew.ResourcePlan.RandomlyGenerated": "随机生成",
+ "OBD.component.OCPConfigNew.ResourcePlan.PleaseRememberThePasswordOr": "请牢记密码,也可",
+ "OBD.component.OCPConfigNew.ResourcePlan.CopyPassword": "复制密码",
+ "OBD.component.OCPConfigNew.ResourcePlan.AndKeepItProperly": "并妥善保存",
+ "OBD.component.OCPConfigNew.ResourcePlan.MonitorDataTenantConfiguration": "监控数据租户配置",
+ "OBD.component.OCPConfigNew.ServiceConfig.WeRecommendThatYouUse": "建议使用负载均衡的地址作为外部访问 OCP 网站的入口, 实现 OCP 服务高可用。如无,您可选择使用 OCP 的节点 IP+端口进行设置,请后续登录 OCP 后进入系统管理->系统参数变更 ocp.site.url(重启生效)",
+ "OBD.component.OCPConfigNew.ServiceConfig.CopiedSuccessfully": "复制成功",
+ "OBD.component.OCPConfigNew.ServiceConfig.TheCurrentBrowserDoesNot": "当前浏览器不支持文本复制",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePortNumberCanOnly": "端口号只支持 1024~65535 范围",
+ "OBD.component.OCPConfigNew.ServiceConfig.ServiceConfiguration": "服务配置",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterAPassword": "请输入密码",
+ "OBD.component.OCPConfigNew.ServiceConfig.AdminPassword": "Admin 密码",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePasswordMustBeTo": "密码需满足:长度为 8~32 个字符,支持字母、数字和特殊字符,且至少包含大、小写字母、数字和特殊字符各 2 个,支持的特殊字符为 ~!@#%^&*_\\-+=|(){}[]:;,.?/",
+ "OBD.component.OCPConfigNew.ServiceConfig.RandomlyGenerated": "随机生成",
+ "OBD.component.OCPConfigNew.ServiceConfig.CopyPassword": "复制密码",
+ "OBD.component.OCPConfigNew.ServiceConfig.KeepThePasswordInMind": "请牢记密码,也可复制密码并妥善保存",
+ "OBD.component.OCPConfigNew.ServiceConfig.SoftwarePath": "软件路径",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterTheSoftwarePath": "请输入软件路径",
+ "OBD.component.OCPConfigNew.ServiceConfig.LogPath": "日志路径",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterALogPath": "请输入日志路径",
+ "OBD.component.OCPConfigNew.ServiceConfig.PackagePath": "软件包路径",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterThePackagePath": "请输入软件包路径",
+ "OBD.component.OCPConfigNew.ServiceConfig.AddressForExternalAccessTo": "外部访问OCP网站的地址:要求以http/https开始,包含VIP地址/域名/端口的网址,且结尾不含斜杠 /",
+ "OBD.component.OCPConfigNew.ServiceConfig.EnterOcpSiteUrl": "请输入ocp.site.url",
+ "OBD.component.OCPConfigNew.ServiceConfig.TheCurrentVerificationIsSuccessful": "当前校验成功,请进行下一步",
+ "OBD.component.OCPConfigNew.ServiceConfig.TheCurrentVerificationFailedPlease": "当前校验失败,请重新输入",
+ "OBD.component.OCPConfigNew.ServiceConfig.Verification": "校 验",
+ "OBD.component.OCPConfigNew.ServiceConfig.ServicePort": "服务端口",
+ "OBD.component.OCPConfigNew.ServiceConfig.PleaseEnter": "请输入",
+ "OBD.component.OCPConfigNew.OcpConfigurationPreviousStep": "ocp配置-上一步",
+ "OBD.component.OCPConfigNew.PreviousStep": "上一步",
+ "OBD.component.OCPConfigNew.OcpConfigurationNextStep": "ocp配置-下一步",
+ "OBD.component.OCPConfigNew.NextStep": "下一步",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.InstallationConfiguration": "安装配置",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.ClusterName": "集群名称",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.InstallationType": "安装类型",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.ProductVersion": "产品版本",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.Product": "产品",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.CommunityEdition": "社区版",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.CommercialEdition": "商业版",
+ "OBD.OCPPreCheck.CheckInfo.BasicInfo.Version": "版本",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ClusterConfiguration": "集群配置",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.RootSysPassword": "root@sys 密码",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.SoftwarePath": "软件路径",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.DataPath": "数据路径",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.LogPath": "日志路径",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.MysqlPort": "mysql 端口",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.RpcPort": "rpc 端口",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObproxyConfiguration": "OBProxy 配置",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObproxyNodes": "OBProxy 节点",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.SqlPort": "SQL 端口",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.PortExporter": "Exporter 端口",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ParameterValue": "参数值",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.AutomaticAllocation": "自动分配",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.Introduction": "介绍",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ZoneName": "Zone 名称",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.ObServerNodes": "OB Server 节点",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.RootServerNodes": "Root Server 节点",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.DeployUserConfiguration": "部署用户配置",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.Username": "用户名",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.Password": "密码",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.SshPort": "SSH端口",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.OcpNodeConfiguration": "OCP 节点配置",
+ "OBD.OCPPreCheck.CheckInfo.ConfigInfo.DatabaseNodeConfiguration": "数据库节点配置",
+ "OBD.OCPPreCheck.CheckInfo.ConnectInfo.ConnectionInformation": "连接信息",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.AdminPassword": "Admin密码",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.SoftwarePath": "软件路径",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.LogPath": "日志路径",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.PackagePath": "软件包路径",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.DeployUserConfiguration": "部署用户配置",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.Username": "用户名",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.Password": "密码",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.SshPort": "SSH端口",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.OcpDeploymentSelection": "OCP 部署选择",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ServiceConfiguration": "服务配置",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ResourcePlanning": "资源规划",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ManageTheNumberOfHosts": "管理主机数量",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.ResourceConfiguration": "资源配置",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.Memory": "内存",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.MetadataTenantConfiguration": "元信息租户配置",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.TenantName": "租户名称",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.MonitorDataTenantConfiguration": "监控数据租户配置",
+ "OBD.OCPPreCheck.CheckInfo.InstallAll": "全部安装",
+ "OBD.OCPPreCheck.CheckInfo.HostIp": "主机IP",
+ "OBD.OCPPreCheck.CheckInfo.AccessPort": "访问端口",
+ "OBD.OCPPreCheck.CheckInfo.AccessAccount": "访问账号",
+ "OBD.OCPPreCheck.CheckInfo.Password": "密码",
+ "OBD.OCPPreCheck.CheckInfo.TheOcpInstallationInformationConfiguration": "OCP 安装信息配置已完成,请检查并确认以下配置信息,确定后开始预检查。",
+ "OBD.OCPPreCheck.CheckInfo.PreCheckPreviousStep": "预检查-上一步",
+ "OBD.OCPPreCheck.CheckInfo.PreviousStep": "上一步",
+ "OBD.OCPPreCheck.CheckInfo.PreCheck": "预检查-预检查",
+ "OBD.OCPPreCheck.CheckInfo.PreCheck.1": "预检查",
+ "OBD.OCPPreCheck.PreCheck.AutomaticRepairSucceeded": "自动修复成功",
+ "OBD.OCPPreCheck.PreCheck.PreCheckResultReCheck": "预检查结果-重新检查",
+ "OBD.OCPPreCheck.PreCheck.ReCheck": "重新检查",
+ "OBD.OCPPreCheck.PreCheck.PreCheckResultAutomaticRepair": "预检查结果-自动修复",
+ "OBD.OCPPreCheck.PreCheck.AutomaticRepair": "自动修复",
+ "OBD.OCPPreCheck.PreCheck.PreCheckResultsPreviousStep": "预检查结果-上一步",
+ "OBD.OCPPreCheck.PreCheck.PreviousStep": "上一步",
+ "OBD.OCPPreCheck.PreCheck.PreCheckFailedDeploymentGreyed": "预检查失败-部署置灰",
+ "OBD.OCPPreCheck.PreCheck.NextStep": "下一步",
+ "OBD.OCPPreCheck.PreCheck.PreCheckSuccessfulDeployment": "预检查成功-部署",
+ "OBD.src.component.Password.ToCharactersInLength": "长度为 8~32 个字符",
+ "OBD.src.component.Password.CanOnlyContainLettersNumbers": "只能包含字母、数字和特殊字符._+@#$%",
+ "OBD.src.component.Password.AtLeastUppercaseAndLowercase": "大小写字母、数字和特殊字符都至少包含 2 个",
+ "OBD.component.Result.Return": "返回",
+ "OBD.component.Result.ViewTaskDetails": "查看任务详情",
+ "OBD.src.constant.configuration.DeploymentConfiguration": "部署配置",
+ "OBD.src.constant.configuration.MetadbConfiguration": "MetaDB 配置",
+ "OBD.src.constant.configuration.OcpConfiguration": "OCP 配置",
+ "OBD.src.constant.configuration.PreCheck": "预检查",
+ "OBD.src.constant.configuration.Deployment": "部署",
+ "OBD.src.constant.configuration.ObClusterConnectionConfiguration": "OB集群 连接配置",
+ "OBD.src.constant.configuration.ConnectivityTest": "联通性测试",
+ "OBD.src.constant.configuration.EnvironmentPreCheck": "环境预检查",
+ "OBD.src.constant.configuration.OcpUpgrade": "OCP升级",
+ "OBD.src.constant.Checking": "检查中",
+ "OBD.src.constant.Pass": "通过",
+ "OBD.src.constant.Failed": "未通过",
+ "OBD.src.constant.Ignored": "已忽略",
+ "OBD.src.constant.must-ignore.SimplifiedChinese": "简体中文",
+ "OBD.Component.ConnectionInfo.EnterAnAccount": "请输入账号",
+ "OBD.component.CustomPasswordInput.PasswordSettingsDoNotMeet": "密码设置不符合要求",
+ "OBD.component.CustomPasswordInput.CopiedSuccessfully": "复制成功",
+ "OBD.component.CustomPasswordInput.TheCurrentBrowserDoesNot": "当前浏览器不支持文本复制",
+ "OBD.component.CustomPasswordInput.KeepThePasswordInMind": "请牢记密码,也可复制密码并妥善保存",
+ "OBD.component.CustomPasswordInput.PleaseRememberThePasswordOr": "请牢记密码,也可",
+ "OBD.component.CustomPasswordInput.CopyPassword": "复制密码",
+ "OBD.component.CustomPasswordInput.AndKeepItProperly": "并妥善保存",
+ "OBD.component.CustomPasswordInput.EnterAPassword": "请输入密码",
+ "OBD.component.CustomPasswordInput.RandomlyGenerated": "随机生成",
+ "OBD.component.DeployConfig.ItStartsWithALetter.1": "以英文字母开头、英文或数字结尾,可包含英文、数字、连字符和下划线,且长度为 2 ~ 32",
+ "OBD.src.component.ExitBtn.ExitTheOceanbaseDeploymentWizard": "退出 OceanBase 部署向导",
+ "OBD.component.MetaDBConfig.UserConfig.TheUserWhoRunsThe": "运行服务的用户",
+ "OBD.src.utils.CanOnlyContainLettersNumbers.1": "只能包含字母、数字和特殊字符._+@#$%",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePasswordMustBeTo.1": "密码需满足:长度为 8~32 个字符,支持字母、数字和特殊字符,且至少包含大、小写字母、数字和特殊字符各 2 个,支持的特殊字符为._+@#$%",
+ "OBD.component.DeployConfig.UnableToObtainTheInstallation": "无法获取安装包,请检查安装程序配置。",
+ "OBD.component.InstallResult.WeRecommendThatYouInstall": "存在未升级 OCP Agent 的主机,建议您在 OCP 平台「主机管理」模块安装新版本 OCP Agent。",
+ "OBD.component.OCPConfigNew.ServiceConfig.ThePasswordMustBeTo.2": "密码需满足:长度为 8~32 个字符,支持字母、数字和特殊字符,且至少包含大、小写字母、数字和特殊字符各 2 个,支持的特殊字符为~!@#%^&*_-+=`|(){}[]:;',.?/",
+ "OBD.src.utils.CanOnlyContainLettersNumbers.2": "只能包含字母、数字和特殊字符~!@#%^&*_-+=`|(){}[]:;',.?/",
+ "OBD.component.MetaDBConfig.UserConfig.OperatingSystemUsersRunningOcp": "运行 OCP 服务的操作系统用户",
+ "OBD.pages.Obdeploy.InstallConfig.OcpExpressOnlySupportsAnd": "OCP Express 仅支持 4.0 及以上版本 OceanBase Database。",
+ "OBD.pages.Obdeploy.InstallConfig.UnableToObtainTheInstallation": "无法获取安装包,请检查安装程序配置。",
+ "OBD.component.MetaDBConfig.ClusterConfig.SqlPort.1": "SQL 端口",
+ "OBD.component.MetaDBConfig.ClusterConfig.RpcPort.1": "RPC 端口",
+ "OBD.component.PasswordCard.Password": "密码",
+ "OBD.component.InputPort.PleaseEnter": "请输入",
+ "OBD.component.InputPort.ThePortNumberCanOnly": "端口号只支持 1024~65535 范围",
+ "OBD.Component.ConnectionInfo.PasswordOptional": "密码(可选)",
+ "OBD.Component.ConnectionInfo.IfYouHaveConfiguredPassword": "如已配置免密登录,则无需再次输入密码",
+ "OBD.component.CustomPasswordInput.TheLengthShouldBeTo": "长度应为 8~32 个字符",
+ "OBD.component.CustomPasswordInput.CanOnlyContainLettersNumbers": "只能包含字母、数字和特殊字符~!@#%^&*_-+=`|(){}[]:;',.?/",
+ "OBD.component.CustomPasswordInput.AtLeastUppercaseAndLowercase": "大小写字母、数字和特殊字符都至少包含 2 个",
+ "OBD.src.utils.TheLengthShouldBeTo": "长度应为 8~32 个字符",
+ "OBD.component.InsstallResult.ThereAreHostsThatHave.1": "·存在未升级 OCP Agent 的主机,建议您在 OCP\n 平台「主机管理」模块安装新版本 OCP Agent。",
+ "OBD.component.InsstallResult.HostNotUpgraded": "未升级主机:",
+ "OBD.Component.ConnectionInfo.SshPort": "SSH端口",
+ "OBD.Component.UpdatePreCheck.MetadbSharesMetaTenantResources": "MetaDB与MonitorDB共享Meta租户资源,容易造成OCP运行异常,强烈建议您新建Monitor租户,并进行MetaDB数据清理和MonitorDB数据迁移,详情请参考《",
+ "OBD.component.DeployConfig.IfTheCurrentEnvironmentCannot": "如当前环境无法正常访问外网,建议使用 OceanBase\n 离线安装包进行安装部署。",
+ "OBD.component.DeployConfig.GoToDownloadOfflineInstallation": "前往下载离线安装",
+ "OBD.component.DeployConfig.HowToEnableOnlineImage": "如何启用在线镜像仓库",
+ "OBD.component.OCPConfigNew.ResourcePlan.OcpApplicationMemory": "OCP 应用内存",
+ "OBD.OCPPreCheck.CheckInfo.ResourceInfo.OcpApplicationMemory": "OCP 应用内存"
}
diff --git a/web/src/models/global.ts b/web/src/models/global.ts
index 652b4cb..65f5722 100755
--- a/web/src/models/global.ts
+++ b/web/src/models/global.ts
@@ -1,32 +1,176 @@
-import { useState } from 'react';
+import { useRef, useState } from 'react';
import useRequest from '@/utils/useRequest';
import { exitProcess } from '@/services/ob-deploy-web/Common';
import { queryDeploymentConfig } from '@/services/ob-deploy-web/Deployments';
import { getErrorInfo } from '@/utils';
+//ocp reqestBody
+// {
+// "auth": {
+// "user": "",
+// "password": "string",
+// "port": 22
+// },
+// "components": {
+// "oceanbase": {
+// "component": "string",
+// "appname": "string",
+// "version": "string",
+// "release": "string",
+// "package_hash": "",
+// "mode": "DEMO",
+// "root_password": "string",
+// "mysql_port": 0,
+// "rpc_port": 0,
+// "home_path": "",
+// "data_dir": "",
+// "redo_dir": "",
+// "parameters": [
+// {
+// "key": "string",
+// "value": "string",
+// "adaptive": true
+// }
+// ],
+// "topology": [
+// {
+// "name": "string",
+// "rootservice": "string",
+// "servers": [
+// {
+// "ip": "string",
+// "parameters": {}
+// }
+// ]
+// }
+// ]
+// },
+// "obproxy": {
+// "component": "string",
+// "version": "string",
+// "package_hash": "",
+// "release": "string",
+// "cluster_name": "string",
+// "home_path": "",
+// "prometheus_listen_port": 0,
+// "listen_port": 0,
+// "parameters": [
+// {
+// "key": "string",
+// "value": "string",
+// "adaptive": true
+// }
+// ],
+// "servers": [
+// "string"
+// ]
+// },
+// "ocpserver": {
+// "component": "ocp-server",
+// "version": "string",
+// "package_hash": "",
+// "release": "string",
+// "home_path": "",
+// "port": 0,
+// "admin_password": "string",
+// "parameters": [
+// {
+// "key": "string",
+// "value": "string",
+// "adaptive": true
+// }
+// ],
+// "memory_size": "2G",
+// "ocp_cpu": 0,
+// "meta_tenant": {
+// "name": {
+// "tenant_name": "string",
+// "user_name": "meta_user",
+// "user_database": "meta_database"
+// },
+// "password": "",
+// "resource": {
+// "cpu": 2,
+// "memory": 4
+// }
+// },
+// "monitor_tenant": {
+// "name": {
+// "tenant_name": "string",
+// "user_name": "meta_user",
+// "user_database": "meta_database"
+// },
+// "password": "",
+// "resource": {
+// "cpu": 2,
+// "memory": 4
+// }
+// },
+// "manage_info": {
+// "cluster": 0,
+// "tenant": 0,
+// "machine": 0
+// },
+// "servers": [
+// "string"
+// ],
+// "metadb": {
+// "id": 1,
+// "host": "string",
+// "port": 0,
+// "user": "string",
+// "password": "string",
+// "database": "oceanbase"
+// }
+// }
+// },
+// "home_path": "",
+// "launch_user": "string"
+// }
+
export default () => {
const initAppName = 'myoceanbase';
- const [selectedConfig,setSelectedConfig] = useState(['obproxy','ocp-express','obagent']); // 有ocpexpress必定有obagent
- const [currentStep, setCurrentStep] = useState(0);
+ const [selectedConfig, setSelectedConfig] = useState([
+ 'obproxy',
+ 'ocp-express',
+ 'obagent',
+ ]); // 有ocpexpress必定有obagent
+ const [currentStep, setCurrentStep] = useState(1);
const [configData, setConfigData] = useState({});
+ const [ocpConfigData, setOcpConfigData] = useState({});
const [currentType, setCurrentType] = useState('all');
const [checkOK, setCheckOK] = useState(false);
const [installStatus, setInstallStatus] = useState('RUNNING');
const [lowVersion, setLowVersion] = useState(false);
const [isFirstTime, setIsFirstTime] = useState(true);
+ const [ocpNewFirstTime, setOcpNewFirstTime] = useState(true);
const [isDraft, setIsDraft] = useState(false);
const [clusterMore, setClusterMore] = useState(false);
+ const [ocpClusterMore, setOcpClusterMore] = useState(false);
const [nameIndex, setNameIndex] = useState(4);
+ const [ocpNameIndex, setOcpNameIndex] = useState(4);
const [errorVisible, setErrorVisible] = useState(false);
const [errorsList, setErrorsList] = useState([]);
-
+ const [first, setFirst] = useState(true);
+ const [token, setToken] = useState('');
+ const [selectCluster, setSelectCluster] = useState();
const [clusterMoreConfig, setClusterMoreConfig] = useState<
API.NewParameterMeta[]
>([]);
+ const [ocpClusterMoreConfig, setOcpClusterMoreConfig] = useState<
+ API.NewParameterMeta[]
+ >([]);
+ const [proxyMoreConfig, setProxyMoreConfig] = useState<
+ API.NewParameterMeta[]
+ >([]);
const [componentsMore, setComponentsMore] = useState(false);
const [componentsMoreConfig, setComponentsMoreConfig] = useState<
API.NewParameterMeta[]
>([]);
+ const [ocpCompMoreConfig, setOcpCompMoreConfig] = useState<
+
+ API.NewParameterMeta[]
+ >([]);
const [componentsVersionInfo, setComponentsVersionInfo] =
useState({});
@@ -40,6 +184,7 @@ export default () => {
const { run: getInfoByName } = useRequest(queryDeploymentConfig, {
throwOnError: true,
});
+ const aliveTokenTimer = useRef(null)
return {
selectedConfig,
@@ -49,6 +194,8 @@ export default () => {
setCurrentStep,
configData,
setConfigData,
+ ocpConfigData,
+ setOcpConfigData,
checkOK,
setCheckOK,
installStatus,
@@ -57,25 +204,44 @@ export default () => {
setLowVersion,
isFirstTime,
setIsFirstTime,
+ ocpNewFirstTime,
+ setOcpNewFirstTime,
isDraft,
setIsDraft,
clusterMore,
setClusterMore,
+ ocpClusterMore,
+ setOcpClusterMore,
componentsMore,
setComponentsMore,
clusterMoreConfig,
setClusterMoreConfig,
+ ocpClusterMoreConfig,
+ setOcpClusterMoreConfig,
+ proxyMoreConfig,
+ setProxyMoreConfig,
componentsMoreConfig,
setComponentsMoreConfig,
+ ocpCompMoreConfig,
+ setOcpCompMoreConfig,
componentsVersionInfo,
setComponentsVersionInfo,
handleQuitProgress,
getInfoByName,
nameIndex,
setNameIndex,
+ ocpNameIndex,
+ setOcpNameIndex,
errorVisible,
setErrorVisible,
errorsList,
setErrorsList,
+ first,
+ setFirst,
+ token,
+ setToken,
+ aliveTokenTimer,
+ selectCluster,
+ setSelectCluster
};
};
diff --git a/web/src/models/ocpInstallData.ts b/web/src/models/ocpInstallData.ts
new file mode 100644
index 0000000..8eb08c8
--- /dev/null
+++ b/web/src/models/ocpInstallData.ts
@@ -0,0 +1,91 @@
+import { useState } from 'react';
+import type { TableDataType } from '@/component/DeployConfig/constants';
+import {
+ OCPComponent,
+ OBComponent,
+ OBProxyComponent,
+} from '@/component/DeployConfig/constants';
+import { getTailPath } from '@/utils/helper';
+
+type VersionInfoType = {
+ version: string;
+ md5: string;
+ release: string;
+ versionType: 'ce' | 'business'; // Community Edition | Business version
+ value?: string;
+};
+
+export type ConnectInfoType = {
+ host?: string;
+ port?: number;
+ database?: string;
+ accessUser?: string;
+ accessCode?: string;
+ user?: string;
+ password?: string;
+};
+export default () => {
+ const taiPath = getTailPath();
+ const isNewDB = taiPath === 'install';
+ const defaultTableData = isNewDB
+ ? [OCPComponent, OBComponent, OBProxyComponent]
+ : [OCPComponent];
+ const [obVersionInfo, setObVersionInfo] = useState();
+ const [ocpVersionInfo, setOcpVersionInfo] = useState();
+ const [obproxyVersionInfo, setObproxyVersionInfo] =
+ useState();
+ const [deployMemory, setDeployMemory] = useState(0);
+ const [useRunningUser, setUseRunningUser] = useState(false);
+ const [checkConnectInfo, setCheckConnectInfo] = useState<
+ 'unchecked' | 'fail' | 'success'
+ >('unchecked');
+ const [installTaskId, setInstallTaskId] = useState();
+ const [installStatus, setInstallStatus] = useState('RUNNING');
+ const [installResult, setInstallResult] = useState<
+ 'SUCCESSFUL' | 'FAILED' | 'RUNNING'
+ >();
+ const [connectId, setConnectId] = useState();
+ const [isReinstall, setIsReinstall] = useState(false);
+ const [isSingleOcpNode, setIsSingleOcpNode] = useState(); //undefined表示没有输入
+ const [username, setUsername] = useState('');
+ const [logData, setLogData] = useState({});
+ const [isShowMoreConfig, setIsShowMoreConfig] = useState(false);
+ const [connectInfo, setConnectInfo] = useState();
+ const [tableData, setTableData] = useState(defaultTableData);
+ return {
+ obVersionInfo,
+ setObVersionInfo,
+ ocpVersionInfo,
+ setOcpVersionInfo,
+ obproxyVersionInfo,
+ setObproxyVersionInfo,
+ deployMemory,
+ setDeployMemory,
+ useRunningUser,
+ setUseRunningUser,
+ checkConnectInfo,
+ setCheckConnectInfo,
+ connectId,
+ setConnectId,
+ installTaskId,
+ setInstallTaskId,
+ installStatus,
+ setInstallStatus,
+ installResult,
+ setInstallResult,
+ isReinstall,
+ setIsReinstall,
+ isSingleOcpNode,
+ setIsSingleOcpNode,
+ username,
+ setUsername,
+ logData,
+ setLogData,
+ isShowMoreConfig,
+ setIsShowMoreConfig,
+ connectInfo,
+ setConnectInfo,
+ tableData,
+ setTableData,
+ };
+};
diff --git a/web/src/pages/Guide/index.less b/web/src/pages/Guide/index.less
new file mode 100644
index 0000000..b955e31
--- /dev/null
+++ b/web/src/pages/Guide/index.less
@@ -0,0 +1,89 @@
+.content {
+ position: relative;
+ z-index: 10;
+ width: 100vw;
+ height: 79vh;
+ min-height: 628px;
+ padding: 24px calc(120 / 1280 * 100%);
+ padding-bottom: 92px;
+ background-color: rgba(245, 248, 254, 1);
+ box-sizing: border-box;
+}
+.disableCustomCardContainer {
+ background-color: #f5f8fe;
+ // pointer-events: none;
+}
+.disableCustomCardContainer,
+.customCardContainer {
+ box-sizing: border-box;
+ min-width: 484px;
+ height: 100%;
+ min-height: 200px;
+ display: flex;
+ align-items: center;
+ // width: 40%;
+ // height: 40%;
+ padding: 35px 18px;
+ .cardHeader {
+ display: flex;
+ justify-content: center;
+ height: 60px;
+ margin-bottom: 16px;
+ .cardImg {
+ margin-right: 8px;
+ }
+ .cardTitle,
+ .disableCardTitle {
+ margin-left: 8px;
+ font-weight: 500;
+ font-size: 18px;
+ line-height: 60px;
+ }
+ .disableCardTitle {
+ color: #cdd5e4;
+ }
+ }
+ .cardDetail,
+ .disableCardDetail {
+ font-weight: 400;
+ font-size: 16px;
+ line-height: 22px;
+ text-align: center;
+ }
+ .disableCardDetail {
+ color: #cdd5e4;
+ }
+
+ &.customCardSelect {
+ color: #006aff !important;
+ border: 2px solid #006aff;
+ :global {
+ .ant-result-title,
+ .ant-result-subtitle {
+ color: #006aff !important;
+ }
+ }
+ }
+
+ &.customCardContainer:hover {
+ cursor: pointer;
+ }
+}
+.pageFooterContainer {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 99;
+ padding: 16px;
+ background-color: #f5f8ff;
+ border-top: 1px solid #dde4ed;
+ .pageFooter {
+ width: 1040px;
+ margin: 0 auto;
+ overflow: hidden;
+ .foolterAction {
+ float: right;
+ }
+ }
+}
diff --git a/web/src/pages/Guide/index.tsx b/web/src/pages/Guide/index.tsx
new file mode 100644
index 0000000..745082a
--- /dev/null
+++ b/web/src/pages/Guide/index.tsx
@@ -0,0 +1,270 @@
+import { intl } from '@/utils/intl';
+import { history } from 'umi';
+import { useRef, useState } from 'react';
+import { Tooltip, Space, Row, Col, Card, Button, Modal } from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { ProCard } from '@ant-design/pro-components';
+import { useModel } from 'umi';
+
+import obSelectIcon from '../../../public/assets/welcome/ob-selected.png';
+import obUnselectIcon from '../../../public/assets/welcome/ob-unselected.png';
+import ocpSelectIcon from '../../../public/assets/welcome/ocp-selected.png';
+import ocpUnselectIcon from '../../../public/assets/welcome/ocp-unselected.png';
+import odcSelectIcon from '../../../public/assets/welcome/odc-selected.png';
+import odcUnselectIcon from '../../../public/assets/welcome/odc-unselected.png';
+import omsSelectIcon from '../../../public/assets/welcome/oms-selected.png';
+import omsUnselectIcon from '../../../public/assets/welcome/oms-unselected.png';
+import Banner from '@/component/Banner';
+import ExitBtn from '@/component/ExitBtn';
+import CustomFooter from '@/component/CustomFooter';
+import { DOCS_PRODUCTION } from '@/constant/docs';
+
+import styles from './index.less';
+
+type ChooseResultType = 'obdeploy' | 'ocpInstaller';
+interface CustomCardProps {
+ disable: boolean;
+ unselectIconPath: string;
+ selectIconPath: string;
+ title: string;
+ detail: string;
+ onClick?: (prop: string) => void;
+ type?: string;
+ tooltipText?: string;
+ select?: boolean;
+}
+
+const getGuideConfigList = (onClick: any) => {
+ const guideConfigList: CustomCardProps[] = [
+ {
+ disable: false,
+ unselectIconPath: obUnselectIcon,
+ selectIconPath: obSelectIcon,
+ title: intl.formatMessage({
+ id: 'OBD.pages.Guide.OceanbaseAndSupportingTools',
+ defaultMessage: 'OceanBase 及配套工具',
+ }),
+ detail: intl.formatMessage({
+ id: 'OBD.pages.Guide.DistributedDatabasesAndVariousTools',
+ defaultMessage: '分布式数据库以及各类工具,方便客户管理、运维和使用',
+ }),
+ onClick: () => onClick('obdeploy'),
+ type: 'obdeploy',
+ },
+ {
+ disable: false,
+ unselectIconPath: ocpUnselectIcon,
+ selectIconPath: ocpSelectIcon,
+ title: 'OCP',
+ detail: intl.formatMessage({
+ id: 'OBD.pages.Guide.OceanbaseCloudPlatformFullLifecycle',
+ defaultMessage: 'OceanBase 云平台:对 OB 集群进行全生命周期管理',
+ }),
+ onClick: () => onClick('ocpInstaller'),
+ type: 'ocpInstaller',
+ },
+ {
+ disable: true,
+ unselectIconPath: odcUnselectIcon,
+ selectIconPath: odcSelectIcon,
+ title: 'ODC',
+ detail: intl.formatMessage({
+ id: 'OBD.pages.Guide.OceanbaseDeveloperCenterManageDatabases',
+ defaultMessage: 'OceanBase 开发者中心:对数据库&表进行管理',
+ }),
+ },
+ {
+ disable: true,
+ unselectIconPath: omsUnselectIcon,
+ selectIconPath: omsSelectIcon,
+ title: 'OMS',
+ detail: intl.formatMessage({
+ id: 'OBD.pages.Guide.OceanbaseDataMigrationFastData',
+ defaultMessage: 'OceanBase 数据迁移:对数据进行快速迁移',
+ }),
+ },
+ ];
+
+ return guideConfigList;
+};
+
+export default function Guide() {
+ const [chooseResult, setChooseResult] =
+ useState('obdeploy');
+ const guideConfigListRef = useRef(
+ getGuideConfigList(setChooseResult),
+ );
+ const { setCurrentStep: setOBCurrentStep } = useModel('global');
+ const CustomCard = ({
+ disable = false,
+ unselectIconPath,
+ selectIconPath,
+ title,
+ detail,
+ onClick,
+ type,
+ tooltipText,
+ select,
+ }: CustomCardProps) => {
+ const CardWrap = (prop: React.PropsWithChildren) => {
+ if (disable) {
+ return (
+
+ {prop.children}
+
+ );
+ } else {
+ return prop.children;
+ }
+ };
+
+ return (
+
+ onClick(type) : () => {}}
+ >
+
+
+
+
+ {title}
+
+
+
+ {detail}
+
+
+
+ );
+ };
+
+ const Title = () => {
+ const textStyle = {
+ fontWeight: '400',
+ fontSize: '14px',
+ lineHeight: '22px',
+ };
+ return (
+
+ );
+ };
+
+ const nextStep = (path: ChooseResultType) => {
+ if (path === 'obdeploy') {
+ setOBCurrentStep(1);
+ }
+ history.push(path);
+ };
+
+ return (
+
+
+
+
}
+ style={{
+ minWidth: '1040px',
+ height: '100%',
+ boxShadow:
+ '0 2px 4px 0 rgba(19,32,57,0.02), 0 1px 6px -1px rgba(19,32,57,0.02), 0 1px 2px 0 rgba(19,32,57,0.03)',
+ }}
+ bodyStyle={{ height: 'calc(100% - 64px)' }}
+ // divided={false}
+ >
+
+ {guideConfigListRef.current.map((guideConfig, idx) => {
+ let select = Boolean(
+ guideConfig.type && chooseResult === guideConfig.type,
+ );
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Layout/index.tsx b/web/src/pages/Layout/index.tsx
new file mode 100644
index 0000000..e0a7d2e
--- /dev/null
+++ b/web/src/pages/Layout/index.tsx
@@ -0,0 +1,132 @@
+import { ConfigProvider, Space, Dropdown } from 'antd';
+import { ReactElement, useState } from 'react';
+import type { Locale } from 'antd/es/locale-provider';
+import enUS from 'antd/es/locale/en_US';
+import zhCN from 'antd/es/locale/zh_CN';
+import { getLocale, setLocale } from 'umi';
+import {
+ HomeOutlined,
+ ReadOutlined,
+ ProfileOutlined,
+ GlobalOutlined,
+} from '@ant-design/icons';
+
+import { intl } from '@/utils/intl';
+import { localeList, localeText } from '@/constants';
+import theme from '../theme';
+import styles from '../Obdeploy/index.less';
+
+export default function Layout(props: React.PropsWithChildren) {
+ const locale = getLocale();
+ const [token, setToken] = useState('');
+ const [localeConfig, setLocalConfig] = useState(
+ locale === 'zh-CN' ? zhCN : enUS,
+ );
+
+ const setCurrentLocale = (key: string) => {
+ if (key !== locale) {
+ setLocale(key);
+ window.localStorage.setItem('uuid', token);
+ }
+ setLocalConfig(key === 'zh-CN' ? zhCN : enUS);
+ };
+
+ const getLocaleItems = () => {
+ return localeList.map((item) => ({
+ ...item,
+ label: setCurrentLocale(item.key)}>{item.label},
+ }));
+ };
+ return (
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.DeploymentWizard',
+ defaultMessage: '部署向导',
+ })}
+
+
+
+ e.preventDefault()}
+ data-aspm-click="c307509.d326700"
+ data-aspm-desc={intl.formatMessage({
+ id: 'OBD.src.pages.TopNavigationSwitchBetweenChinese',
+ defaultMessage: '顶部导航-中英文切换',
+ })}
+ data-aspm-param={``}
+ data-aspm-expo
+ >
+
+ {localeText[locale]}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.VisitTheOfficialWebsite',
+ defaultMessage: '访问官网',
+ })}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.VisitTheForum',
+ defaultMessage: '访问论坛',
+ })}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.HelpCenter',
+ defaultMessage: '帮助中心',
+ })}
+
+
+
+ {props.children}
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/CheckInfo.tsx b/web/src/pages/Obdeploy/CheckInfo.tsx
new file mode 100644
index 0000000..c558eb2
--- /dev/null
+++ b/web/src/pages/Obdeploy/CheckInfo.tsx
@@ -0,0 +1,658 @@
+import { intl } from '@/utils/intl';
+import { useState } from 'react';
+import { useModel } from 'umi';
+import { Space, Button, Table, Row, Col, Alert, Tooltip } from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import type { ColumnsType } from 'antd/es/table';
+import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
+import useRequest from '@/utils/useRequest';
+import { createDeploymentConfig } from '@/services/ob-deploy-web/Deployments';
+import { handleQuit, getErrorInfo } from '@/utils';
+import PasswordCard from '@/component/PasswordCard';
+import {
+ componentsConfig,
+ allComponentsKeys,
+ onlyComponentsKeys,
+ modeConfig,
+ obproxyComponent,
+} from '../constants';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+interface ComponentsNodeConfig {
+ name: string;
+ servers: string[];
+ key: string;
+ isTooltip: boolean;
+}
+
+export default function CheckInfo() {
+ const {
+ configData,
+ setCheckOK,
+ lowVersion,
+ setCurrentStep,
+ handleQuitProgress,
+ setErrorVisible,
+ setErrorsList,
+ selectedConfig,
+ errorsList,
+ } = useModel('global');
+ const { components = {}, auth, home_path } = configData || {};
+ const {
+ oceanbase = {},
+ obproxy = {},
+ ocpexpress = {},
+ obagent = {},
+ } = components;
+ const [showPwd, setShowPwd] = useState(false);
+
+ const { run: handleCreateConfig, loading } = useRequest(
+ createDeploymentConfig,
+ {
+ onSuccess: ({ success }: API.OBResponse) => {
+ if (success) {
+ setCheckOK(true);
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const prevStep = () => {
+ setCurrentStep(3);
+ window.scrollTo(0, 0);
+ };
+
+ const handlePreCheck = () => {
+ handleCreateConfig({ name: oceanbase?.appname }, { ...configData });
+ };
+
+ const getComponentsList = () => {
+ const componentsList: API.TableComponentInfo[] = [];
+ allComponentsKeys.forEach((key) => {
+ if (components?.[key]) {
+ const componentConfig = componentsConfig?.[key] || {};
+ componentsList.push({
+ ...componentConfig,
+ version: components?.[key].version,
+ key,
+ });
+ }
+ });
+ return componentsList;
+ };
+
+ const getComponentsNodeConfigList = () => {
+ const componentsNodeConfigList: ComponentsNodeConfig[] = [];
+ //todo:待优化
+ let _selectedConfig = [...selectedConfig];
+ _selectedConfig.forEach((item, idx) => {
+ if (item === 'ocp-express') {
+ _selectedConfig[idx] = 'ocpexpress';
+ }
+ });
+ let currentOnlyComponentsKeys = onlyComponentsKeys.filter(
+ (key) => key !== 'obagent' && _selectedConfig.includes(key),
+ );
+
+ if (lowVersion) {
+ currentOnlyComponentsKeys = currentOnlyComponentsKeys.filter(
+ (key) => key !== 'ocpexpress',
+ );
+ }
+
+ currentOnlyComponentsKeys.forEach((key) => {
+ if (componentsConfig?.[key]) {
+ componentsNodeConfigList.push({
+ key,
+ name: componentsConfig?.[key]?.name,
+ servers: components?.[key]?.servers?.join(','),
+ isTooltip: key === obproxyComponent,
+ });
+ }
+ });
+ return componentsNodeConfigList;
+ };
+
+ const dbConfigColumns: ColumnsType = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ZoneName',
+ defaultMessage: 'Zone 名称',
+ }),
+ dataIndex: 'name',
+ width: 200,
+ render: (text) => text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ObServerNodes',
+ defaultMessage: 'OB Server 节点',
+ }),
+ dataIndex: 'servers',
+ render: (text) => {
+ const serversIps = text.map((item: API.OceanbaseServers) => item.ip);
+ const str = serversIps.join(',');
+ return (
+
+ {str}
+
+ );
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.RootServerNodes',
+ defaultMessage: 'Root Server 节点',
+ }),
+ dataIndex: 'rootservice',
+ width: 200,
+ render: (text) => text || '-',
+ },
+ ];
+
+ const getMoreColumns = (label: string) => {
+ const columns: ColumnsType = [
+ {
+ title: label,
+ dataIndex: 'key',
+ render: (text) => text,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ParameterValue',
+ defaultMessage: '参数值',
+ }),
+ dataIndex: 'value',
+ render: (text, record) =>
+ record.adaptive
+ ? intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.Adaptive',
+ defaultMessage: '自动分配',
+ })
+ : text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.Introduction',
+ defaultMessage: '介绍',
+ }),
+ dataIndex: 'description',
+ render: (text) => (
+
+ {text}
+
+ ),
+ },
+ ];
+
+ return columns;
+ };
+
+ const componentsList = getComponentsList();
+ const componentsNodeConfigList = getComponentsNodeConfigList();
+ const initDir = `${home_path}/oceanbase/store`;
+ const clusterConfigInfo = [
+ {
+ key: 'cluster',
+ group: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ClusterConfiguration',
+ defaultMessage: '集群配置',
+ }),
+ content: [
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ConfigurationMode',
+ defaultMessage: '配置模式',
+ }),
+ colSpan: 5,
+ value: modeConfig[oceanbase?.mode],
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.RootSysPassword',
+ defaultMessage: 'root@sys 密码',
+ }),
+ colSpan: 5,
+ value: (
+
+ {oceanbase?.root_password}
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.DataDirectory',
+ defaultMessage: '数据目录',
+ }),
+ value: (
+
+ {oceanbase?.data_dir || initDir}
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.LogDirectory',
+ defaultMessage: '日志目录',
+ }),
+ value: (
+
+ {oceanbase?.redo_dir || initDir}
+
+ ),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.SqlPort',
+ defaultMessage: 'SQL 端口',
+ }),
+ colSpan: 3,
+ value: oceanbase?.mysql_port,
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.RpcPort',
+ defaultMessage: 'RPC 端口',
+ }),
+ colSpan: 3,
+ value: oceanbase?.rpc_port,
+ },
+ ],
+
+ more: oceanbase?.parameters?.length
+ ? [
+ {
+ label: componentsConfig['oceanbase'].labelName,
+ parameters: oceanbase?.parameters,
+ },
+ ]
+ : [],
+ },
+ ];
+
+ if (selectedConfig.length) {
+ let content: any[] = [],
+ more: any = [];
+ if (selectedConfig.includes('obproxy')) {
+ content = content.concat(
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ObproxyServicePort',
+ defaultMessage: 'OBProxy 服务端口',
+ }),
+ value: obproxy?.listen_port,
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.PortObproxyExporter',
+ defaultMessage: 'OBProxy Exporter 端口',
+ }),
+ value: obproxy?.prometheus_listen_port,
+ },
+ );
+ obproxy?.parameters?.length &&
+ more.push({
+ label: componentsConfig['obproxy'].labelName,
+ parameters: obproxy?.parameters,
+ });
+ }
+
+ if (selectedConfig.includes('obagent')) {
+ content = content.concat(
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ObagentMonitoringServicePort',
+ defaultMessage: 'OBAgent 监控服务端口',
+ }),
+ value: obagent?.monagent_http_port,
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ObagentManageServicePorts',
+ defaultMessage: 'OBAgent 管理服务端口',
+ }),
+ value: obagent?.mgragent_http_port,
+ },
+ );
+ obagent?.parameters?.length &&
+ more.push({
+ label: componentsConfig['obagent'].labelName,
+ parameters: obagent?.parameters,
+ });
+ }
+ // more是否有数据跟前面是否打开更多配置有关
+ if (!lowVersion && selectedConfig.includes('ocp-express')) {
+ content.push({
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.PortOcpExpress',
+ defaultMessage: 'OCP Express 端口',
+ }),
+ value: ocpexpress?.port,
+ });
+ ocpexpress?.parameters?.length &&
+ more.push({
+ label: componentsConfig['ocpexpress'].labelName,
+ parameters: ocpexpress?.parameters,
+ });
+ }
+
+ clusterConfigInfo.push({
+ key: 'components',
+ group: intl.formatMessage({
+ id: 'OBD.pages.components.CheckInfo.ComponentConfiguration',
+ defaultMessage: '组件配置',
+ }),
+ content,
+ more,
+ });
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {oceanbase?.appname}
+
+
+
+
+
+
+ {componentsList.map(
+ (item: API.TableComponentInfo, index: number) => (
+ 1 ? { marginTop: 16 } : {}}
+ key={item.key}
+ >
+
+
+ {item?.showComponentName}
+
+
+ {componentsConfig[item.key]?.type}
+
+
+ {item?.version}
+
+
+
+ ),
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {selectedConfig.length ? (
+
+
+
+ {componentsNodeConfigList.map(
+ (item: ComponentsNodeConfig) => (
+
+ {item.isTooltip ? (
+
+ {item?.servers}
+
+ ) : (
+ item?.servers
+ )}
+
+ ),
+ )}
+
+
+
+ ) : null}
+
+
+
+
+ {auth?.user}
+
+
+
+
+
+
+
+
+
+
+ {home_path}
+
+
+
+
+
+
+
+
+
+ {clusterConfigInfo?.map((item, index) => (
+
+
+
+ {item.content.map((subItem) => (
+
+ {subItem.value}
+
+ ))}
+
+
+ {item?.more?.length ? (
+
+ {item?.more.map((moreItem) => (
+
+
+
+ ))}
+
+ ) : null}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/ClusterConfig/ConfigTable.tsx b/web/src/pages/Obdeploy/ClusterConfig/ConfigTable.tsx
new file mode 100644
index 0000000..a420b2d
--- /dev/null
+++ b/web/src/pages/Obdeploy/ClusterConfig/ConfigTable.tsx
@@ -0,0 +1,142 @@
+import { Space, Table, Spin, Form, Tooltip, InputNumber } from 'antd';
+import { ProCard, ProForm } from '@ant-design/pro-components';
+import { getLocale } from 'umi';
+import type { ColumnsType } from 'antd/es/table';
+
+import { intl } from '@/utils/intl';
+import Parameter from './Parameter';
+import EnStyles from '../indexEn.less';
+import ZhStyles from '../indexZh.less';
+import { ReactElement } from 'react';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+interface ConfigTableProps {
+ showVisible: boolean;
+ dataSource: API.NewParameterMeta[];
+ loading: boolean;
+ customParameter?: JSX.Element;
+}
+
+const parameterValidator = (_: any, value?: API.ParameterValue) => {
+ if (value?.adaptive) {
+ return Promise.resolve();
+ } else if (value?.require && !value?.value) {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.RequiredForCustomParameters',
+ defaultMessage: '自定义参数时必填',
+ }),
+ ),
+ );
+ }
+ return Promise.resolve();
+};
+
+const getMoreColumns = (
+ label: string,
+ componentKey: string,
+ customParameter?: JSX.Element,
+) => {
+ const columns: ColumnsType = [
+ {
+ title: label,
+ dataIndex: 'name',
+ width: 250,
+ render: (text) => text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.Obdeploy.ClusterConfig.ConfigTable.ParameterValue',
+ defaultMessage: '参数值',
+ }),
+ width: locale === 'zh-CN' ? 280 : 360,
+ dataIndex: 'parameterValue',
+ render: (parameterValue, record) => {
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.Introduction',
+ defaultMessage: '介绍',
+ }),
+ dataIndex: 'description',
+ render: (text, record) =>
+ text ? (
+
+
+ {text}
+
+
+ ) : (
+ '-'
+ ),
+ },
+ ];
+
+ return columns;
+};
+
+export default function ConfigTable({
+ showVisible,
+ dataSource,
+ loading,
+ customParameter,
+}: ConfigTableProps) {
+ return (
+ <>
+ {showVisible ? (
+
+
+ {dataSource.map((moreItem) => {
+ return (
+
+
+
+ );
+ })}
+
+
+ ) : null}
+ >
+ );
+}
diff --git a/web/src/pages/Obdeploy/ClusterConfig/Footer.tsx b/web/src/pages/Obdeploy/ClusterConfig/Footer.tsx
new file mode 100644
index 0000000..0887f86
--- /dev/null
+++ b/web/src/pages/Obdeploy/ClusterConfig/Footer.tsx
@@ -0,0 +1,81 @@
+import { Space, Button, Tooltip } from 'antd';
+import { getLocale, useModel } from 'umi';
+
+import { intl } from '@/utils/intl';
+import { handleQuit } from '@/utils';
+import EnStyles from '../indexEn.less';
+import ZhStyles from '../indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+interface FooterProps {
+ prevStep: () => void;
+ nextStep: () => void;
+}
+
+export default function Footer({ prevStep, nextStep }: FooterProps) {
+ const { handleQuitProgress, setCurrentStep } = useModel('global');
+
+ return (
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/ClusterConfig/Parameter.tsx b/web/src/pages/Obdeploy/ClusterConfig/Parameter.tsx
new file mode 100644
index 0000000..4e08382
--- /dev/null
+++ b/web/src/pages/Obdeploy/ClusterConfig/Parameter.tsx
@@ -0,0 +1,244 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useRef, useState } from 'react';
+import { Space, InputNumber, Input, Select } from 'antd';
+import { getLocale } from 'umi';
+import { getUnit, isTakeUnit, takeNewUnit } from './helper';
+import EnStyles from '../indexEn.less';
+import ZhStyles from '../indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+interface ParameterProps {
+ value?: API.ParameterValue;
+ onChange?: (value?: API.ParameterValue) => void;
+ onBlur?: () => void;
+}
+
+interface AdaptiveInputProps {
+ parameterValue: API.ParameterValue;
+ onBlur?: () => void;
+ setParameterValue: (prop: API.ParameterValue) => void;
+}
+
+type OptionsType = {
+ label: string;
+ value: string | Boolean;
+}[];
+
+const optionConfig: OptionsType = [
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.Parameter.AutomaticAllocation',
+ defaultMessage: '自动分配',
+ }),
+ value: true,
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.Parameter.Custom',
+ defaultMessage: '自定义',
+ }),
+ value: false,
+ },
+];
+
+const unitOption: OptionsType = [
+ {
+ label: 'KB',
+ value: 'KB',
+ },
+ {
+ label: 'MB',
+ value: 'MB',
+ },
+ {
+ label: 'GB',
+ value: 'GB',
+ },
+];
+
+const booleanOption: OptionsType = [
+ {
+ label: 'True',
+ value: 'True',
+ },
+ {
+ label: 'False',
+ value: 'False',
+ },
+];
+
+const AdaptiveInput = ({
+ parameterValue,
+ onBlur,
+ setParameterValue,
+}: AdaptiveInputProps) => {
+ const { type } = parameterValue;
+ const defaultUnit =
+ type === 'CapacityMB' || type === 'Capacity'
+ ? getUnit(parameterValue.value)
+ : null;
+ const unit = useRef(defaultUnit);
+ if (type === 'int' || type === 'Integer') {
+ return (
+
+ value !== null && setParameterValue({ ...parameterValue, value })
+ }
+ />
+ );
+ }
+ if (type === 'CapacityMB' || type === 'Capacity') {
+ return (
+
+ {
+ if (value !== null) {
+ setParameterValue({
+ ...parameterValue,
+ value: String(value) + unit.current,
+ });
+ }
+ }}
+ addonAfter={
+
+ }
+ />
+
+ );
+ }
+
+ if (type === 'Boolean') {
+ return (
+
+ );
+ }
+
+ return (
+
+ setParameterValue({ ...parameterValue, value: e.target.value })
+ }
+ />
+ );
+};
+//参数来由:当Parameter在form.item下时自带有onchange,value
+export default function Parameter({
+ value: itemValue,
+ onChange,
+ onBlur,
+}: ParameterProps) {
+ const [parameterValue, setParameterValue] = useState(
+ itemValue || {},
+ );
+ useEffect(() => {
+ if (onChange) {
+ if (
+ itemValue?.adaptive !== parameterValue?.adaptive ||
+ itemValue?.value !== parameterValue?.value ||
+ itemValue?.type !== parameterValue?.type
+ ) {
+ onChange(parameterValue);
+ }
+ }
+ }, [parameterValue]);
+ return (
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/ClusterConfig/helper.ts b/web/src/pages/Obdeploy/ClusterConfig/helper.ts
new file mode 100644
index 0000000..34417ef
--- /dev/null
+++ b/web/src/pages/Obdeploy/ClusterConfig/helper.ts
@@ -0,0 +1,50 @@
+//与UI无关的逻辑处理
+
+/**
+ * 将容量单位转换为约定格式 KB、MB、GB,统一输出为大写
+ */
+const getUnit = (val: string = 'GB'): string => {
+ const res = val.match(/\D+/g)?.[0].toUpperCase() || 'GB';
+ if (res.includes('K')) return 'KB';
+ if (res.includes('M')) return 'MB';
+ if (res.includes('G')) return 'GB';
+ return res;
+};
+
+//是否携带单位
+const isTakeUnit = (val: string | undefined): boolean => {
+ if (!val) return false;
+ const upperVal = val.toUpperCase();
+ if (
+ upperVal.includes('M') ||
+ upperVal.includes('MB') ||
+ upperVal.includes('K') ||
+ upperVal.includes('KB') ||
+ upperVal.includes('G') ||
+ upperVal.includes('GB')
+ ) {
+ return true;
+ }
+ return false;
+};
+
+//换新单位
+const takeNewUnit = (target: string, unit: string): string => {
+ if (!isTakeUnit(target)) {
+ //无单位
+ return target + unit;
+ }
+ let targetArr = target.split('');
+ let tailStr = targetArr[targetArr.length - 1].toUpperCase();
+ if (tailStr === 'K' || tailStr === 'M' || tailStr === 'G') {
+ //以K|M|G结尾
+ targetArr[targetArr.length - 1] = unit;
+ return targetArr.join('');
+ } else {
+ //以KB|MB|GB结尾
+ targetArr.splice(-2, 2, unit);
+ return targetArr.join('');
+ }
+};
+
+export { getUnit, isTakeUnit, takeNewUnit };
diff --git a/web/src/pages/Obdeploy/ClusterConfig/index.tsx b/web/src/pages/Obdeploy/ClusterConfig/index.tsx
new file mode 100644
index 0000000..75e0972
--- /dev/null
+++ b/web/src/pages/Obdeploy/ClusterConfig/index.tsx
@@ -0,0 +1,815 @@
+import { intl } from '@/utils/intl';
+import { useState, useEffect } from 'react';
+import { useModel, getLocale } from 'umi';
+import { Space, Tooltip, Row, Switch, Table, Spin, Form, message } from 'antd';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import {
+ ProCard,
+ ProForm,
+ ProFormText,
+ ProFormRadio,
+ ProFormDigit,
+} from '@ant-design/pro-components';
+import { getErrorInfo, getRandomPassword } from '@/utils';
+import useRequest from '@/utils/useRequest';
+import { queryComponentParameters } from '@/services/ob-deploy-web/Components';
+import { showConfigKeys } from '@/constant/configuration';
+import TooltipInput from '../TooltipInput';
+import ConfigTable from './ConfigTable';
+import Parameter from './Parameter';
+import Footer from './Footer';
+import {
+ commonStyle,
+ pathRule,
+ onlyComponentsKeys,
+ componentsConfig,
+ componentVersionTypeToComponent,
+} from '../../constants';
+import EnStyles from '../indexEn.less';
+import ZhStyles from '../indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+interface FormValues extends API.Components {
+ oceanbase?: {
+ mode?: string;
+ parameters?: any;
+ };
+}
+
+export default function ClusterConfig() {
+ const {
+ selectedConfig,
+ setCurrentStep,
+ configData,
+ setConfigData,
+ lowVersion,
+ clusterMore,
+ setClusterMore,
+ componentsMore,
+ setComponentsMore,
+ clusterMoreConfig,
+ setClusterMoreConfig,
+ componentsMoreConfig,
+ setComponentsMoreConfig,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+ const { components = {}, home_path } = configData || {};
+ const {
+ oceanbase = {},
+ ocpexpress = {},
+ obproxy = {},
+ obagent = {},
+ } = components;
+ const [form] = ProForm.useForm();
+ const [currentMode, setCurrentMode] = useState(
+ oceanbase?.mode || 'PRODUCTION',
+ );
+
+ const [passwordVisible, setPasswordVisible] = useState(true);
+ const [clusterMoreLoading, setClusterMoreLoading] = useState(false);
+ const [componentsMoreLoading, setComponentsMoreLoading] = useState(false);
+ const { run: getMoreParamsters } = useRequest(queryComponentParameters);
+
+ const formatParameters = (dataSource: any) => {
+ if (dataSource) {
+ const parameterKeys = Object.keys(dataSource);
+ return parameterKeys.map((key) => {
+ const { params, ...rest } = dataSource[key];
+ return {
+ key,
+ ...rest,
+ ...params,
+ };
+ });
+ } else {
+ return [];
+ }
+ };
+
+ const setData = (dataSource: FormValues) => {
+ let newComponents: API.Components = { ...components };
+ if (selectedConfig.includes('obproxy')) {
+ newComponents.obproxy = {
+ ...(components.obproxy || {}),
+ ...dataSource.obproxy,
+ parameters: formatParameters(dataSource.obproxy?.parameters),
+ };
+ }
+ if (selectedConfig.includes('ocp-express') && !lowVersion) {
+ newComponents.ocpexpress = {
+ ...(components.ocpexpress || {}),
+ ...dataSource.ocpexpress,
+ parameters: formatParameters(dataSource.ocpexpress?.parameters),
+ };
+ }
+ if (selectedConfig.includes('obagent')) {
+ newComponents.obagent = {
+ ...(components.obagent || {}),
+ ...dataSource.obagent,
+ parameters: formatParameters(dataSource.obagent?.parameters),
+ };
+ }
+ newComponents.oceanbase = {
+ ...(components.oceanbase || {}),
+ ...dataSource.oceanbase,
+ parameters: formatParameters(dataSource.oceanbase?.parameters),
+ };
+ setConfigData({ ...configData, components: newComponents });
+ };
+
+ const prevStep = () => {
+ const formValues = form.getFieldsValue(true);
+ setData(formValues);
+ setCurrentStep(2);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ };
+
+ const nextStep = () => {
+ form
+ .validateFields()
+ .then((values) => {
+ setData(values);
+ setCurrentStep(4);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ })
+ .catch(({ errorFields }) => {
+ const errorName = errorFields?.[0].name;
+ form.scrollToField(errorName);
+ message.destroy();
+ if (errorName.includes('parameters')) {
+ message.warning(
+ intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.RequiredParametersForMoreConfigurations',
+ defaultMessage: '更多配置有必填参数未填入',
+ }),
+ );
+ }
+ });
+ };
+
+ const onValuesChange = (values: FormValues) => {
+ if (values?.oceanbase?.mode) {
+ setCurrentMode(values?.oceanbase?.mode);
+ }
+ };
+
+ const portValidator = (_: any, value: number) => {
+ if (value) {
+ if (value >= 1024 && value <= 65535) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.ThePortNumberCanOnly',
+ defaultMessage: '端口号只支持 1024~65535 范围',
+ }),
+ ),
+ );
+ }
+ };
+
+ const formatMoreConfig = (dataSource: API.ParameterMeta[]) => {
+ return dataSource.map((item) => {
+ const component = componentVersionTypeToComponent[item.component]
+ ? componentVersionTypeToComponent[item.component]
+ : item.component;
+ const componentConfig = componentsConfig[component];
+ // filter out existing parameters
+ let configParameter = item?.config_parameters.filter((parameter) => {
+ return !showConfigKeys?.[componentConfig.componentKey]?.includes(
+ parameter.name,
+ );
+ });
+ const newConfigParameter: API.NewConfigParameter[] = configParameter.map(
+ (parameterItem) => {
+ return {
+ ...parameterItem,
+ parameterValue: {
+ value: parameterItem.default,
+ adaptive: parameterItem.auto,
+ auto: parameterItem.auto,
+ require: parameterItem.require,
+ },
+ };
+ },
+ );
+
+ const result: API.NewParameterMeta = {
+ ...item,
+ componentKey: componentConfig.componentKey,
+ label: componentConfig.labelName,
+ configParameter: newConfigParameter,
+ };
+ result.configParameter.forEach((item) => {
+ Object.assign(item.parameterValue, { type: item.type });
+ });
+ return result;
+ });
+ };
+
+ const getInitialParameters = (
+ currentComponent: string,
+ dataSource: API.MoreParameter[],
+ data: API.NewParameterMeta[],
+ ) => {
+ const currentComponentNameConfig = data?.filter(
+ (item) => item.component === currentComponent,
+ )?.[0];
+ if (currentComponentNameConfig) {
+ const parameters: any = {};
+ currentComponentNameConfig.configParameter.forEach((item) => {
+ let parameter = {
+ ...item,
+ key: item.name,
+ params: {
+ value: item.default,
+ adaptive: item.auto,
+ auto: item.auto,
+ require: item.require,
+ type: item.type,
+ },
+ };
+ dataSource?.some((dataItem) => {
+ if (item.name === dataItem.key) {
+ parameter = {
+ key: dataItem.key,
+ description: parameter.description,
+ params: {
+ ...parameter.params,
+ ...dataItem,
+ },
+ };
+ return true;
+ }
+ return false;
+ });
+ if (
+ (parameter.params.type === 'CapacityMB' || parameter.params.type === 'Capacity') &&
+ parameter.params.value == '0'
+ ) {
+ parameter.params.value += 'GB';
+ }
+ parameters[item.name] = parameter;
+ });
+ return parameters;
+ } else {
+ return undefined;
+ }
+ };
+
+ const getClusterMoreParamsters = async () => {
+ setClusterMoreLoading(true);
+ try {
+ const { success, data } = await getMoreParamsters(
+ {},
+ {
+ filters: [
+ {
+ component: oceanbase?.component,
+ version: oceanbase?.version,
+ is_essential_only: true,
+ },
+ ],
+ },
+ );
+ if (success) {
+ const newClusterMoreConfig = formatMoreConfig(data?.items);
+ setClusterMoreConfig(newClusterMoreConfig);
+ form.setFieldsValue({
+ oceanbase: {
+ parameters: getInitialParameters(
+ oceanbase?.component,
+ oceanbase?.parameters,
+ newClusterMoreConfig,
+ ),
+ },
+ });
+ }
+ } catch (e: any) {
+ setClusterMore(false);
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ setClusterMoreLoading(false);
+ };
+
+ const getComponentsMoreParamsters = async () => {
+ const filters: API.ParameterFilter[] = [];
+ let currentOnlyComponentsKeys: string[] = onlyComponentsKeys;
+ if (lowVersion) {
+ currentOnlyComponentsKeys = onlyComponentsKeys.filter(
+ (key) => key !== 'ocpexpress',
+ );
+ }
+ currentOnlyComponentsKeys.forEach((item) => {
+ if (components[item]) {
+ filters.push({
+ component: components[item]?.component,
+ version: components[item]?.version,
+ is_essential_only: true,
+ });
+ }
+ });
+ setComponentsMoreLoading(true);
+ try {
+ const { success, data } = await getMoreParamsters({}, { filters });
+ if (success) {
+ const newComponentsMoreConfig = formatMoreConfig(data?.items);
+ setComponentsMoreConfig(newComponentsMoreConfig);
+ const setValues = {
+ obproxy: {
+ parameters: getInitialParameters(
+ obproxy?.component,
+ obproxy?.parameters,
+ newComponentsMoreConfig,
+ ),
+ },
+ obagent: {
+ parameters: getInitialParameters(
+ obagent?.component,
+ obagent?.parameters,
+ newComponentsMoreConfig,
+ ),
+ },
+ };
+ if (!lowVersion) {
+ setValues.ocpexpress = {
+ parameters: getInitialParameters(
+ ocpexpress?.component,
+ ocpexpress?.parameters,
+ newComponentsMoreConfig,
+ ),
+ };
+ }
+ form.setFieldsValue(setValues);
+ }
+ } catch (e) {
+ setComponentsMore(false);
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+
+ setComponentsMoreLoading(false);
+ };
+
+ const handleCluserMoreChange = (checked: boolean) => {
+ setClusterMore(checked);
+ if (!clusterMoreConfig?.length) {
+ getClusterMoreParamsters();
+ }
+ };
+
+ const handleComponentsMoreChange = (checked: boolean) => {
+ setComponentsMore(checked);
+ if (!componentsMoreConfig?.length) {
+ getComponentsMoreParamsters();
+ }
+ };
+
+ useEffect(() => {
+ if (clusterMore) {
+ getClusterMoreParamsters();
+ }
+ if (componentsMore) {
+ getComponentsMoreParamsters();
+ }
+ }, [selectedConfig]);
+
+ const initPassword = getRandomPassword();
+
+ const initialValues = {
+ oceanbase: {
+ mode: oceanbase?.mode || 'PRODUCTION',
+ root_password: oceanbase?.root_password || initPassword,
+ data_dir: oceanbase?.data_dir || undefined,
+ redo_dir: oceanbase?.redo_dir || undefined,
+ mysql_port: oceanbase?.mysql_port || 2881,
+ rpc_port: oceanbase?.rpc_port || 2882,
+ parameters: getInitialParameters(
+ oceanbase?.component,
+ oceanbase?.parameters,
+ clusterMoreConfig,
+ ),
+ },
+ obproxy: {
+ listen_port: obproxy?.listen_port || 2883,
+ prometheus_listen_port: obproxy?.prometheus_listen_port || 2884,
+ parameters: getInitialParameters(
+ obproxy?.component,
+ obproxy?.parameters,
+ componentsMoreConfig,
+ ),
+ },
+ obagent: {
+ monagent_http_port: obagent?.monagent_http_port || 8088,
+ mgragent_http_port: obagent?.mgragent_http_port || 8089,
+ parameters: getInitialParameters(
+ obagent?.component,
+ obagent?.parameters,
+ componentsMoreConfig,
+ ),
+ },
+ };
+
+ if (!lowVersion) {
+ initialValues.ocpexpress = {
+ port: ocpexpress?.port || 8180,
+ parameters: getInitialParameters(
+ ocpexpress?.component,
+ ocpexpress?.parameters,
+ componentsMoreConfig,
+ ),
+ };
+ }
+
+ const singleItemStyle = { width: 448 };
+ const initDir = `${home_path}/oceanbase/store`;
+ return (
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.ViewModeConfigurationRules',
+ defaultMessage: '查看模式配置规则',
+ })}
+
+
+
+ {currentMode === 'PRODUCTION'
+ ? intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.ThisModeWillMaximizeThe',
+ defaultMessage:
+ '此模式将最大化利用环境资源,保证集群的性能与稳定性,推荐使用此模式。',
+ })
+ : intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.ConfigureResourceParametersThatMeet',
+ defaultMessage: '配置满足集群正常运行的资源参数',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.MoreConfigurations',
+ defaultMessage: '更多配置',
+ })}
+
+
+
+ }
+ />
+
+
+ {selectedConfig.length ? (
+
+
+ {selectedConfig.includes('obproxy') && (
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.PortObproxyExporter',
+ defaultMessage: 'OBProxy Exporter 端口',
+ })}
+
+
+
+
+ >
+ }
+ fieldProps={{ style: commonStyle }}
+ placeholder={intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.PleaseEnter',
+ defaultMessage: '请输入',
+ })}
+ rules={[
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.PleaseEnter',
+ defaultMessage: '请输入',
+ }),
+ },
+ { validator: portValidator },
+ ]}
+ />
+
+
+ )}
+ {selectedConfig.includes('obagent') && (
+
+
+
+
+
+
+
+ )}
+ {selectedConfig.includes('ocp-express') && !lowVersion && (
+
+
+
+ )}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.ClusterConfig.MoreConfigurations',
+ defaultMessage: '更多配置',
+ })}
+
+
+
+ }
+ />
+
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/DeleteDeployModal.tsx b/web/src/pages/Obdeploy/DeleteDeployModal.tsx
new file mode 100644
index 0000000..d7107d7
--- /dev/null
+++ b/web/src/pages/Obdeploy/DeleteDeployModal.tsx
@@ -0,0 +1,259 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import { Modal, Progress, message } from 'antd';
+import { getDestroyTaskInfo } from '@/services/ob-deploy-web/Deployments';
+import useRequest from '@/utils/useRequest';
+import { checkLowVersion, getErrorInfo } from '@/utils';
+import NP from 'number-precision';
+import { oceanbaseComponent, obproxyComponent } from '../constants';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+interface Props {
+ visible: boolean;
+ name: string;
+ onCancel: () => void;
+ setOBVersionValue: (value: string) => void;
+}
+
+let timerProgress: NodeJS.Timer;
+let timerFetch: NodeJS.Timer;
+
+const statusConfig = {
+ RUNNING: 'normal',
+ SUCCESSFUL: 'success',
+ FAILED: 'exception',
+};
+
+export default function DeleteDeployModal({
+ visible,
+ name,
+ onCancel,
+ setOBVersionValue,
+}: Props) {
+ const {
+ selectedConfig,
+ setSelectedConfig,
+ setConfigData,
+ setIsDraft,
+ setClusterMore,
+ setComponentsMore,
+ componentsVersionInfo,
+ setComponentsVersionInfo,
+ getInfoByName,
+ setLowVersion,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+
+ const [status, setStatus] = useState('RUNNING');
+ const [progress, setProgress] = useState(0);
+ const [showProgress, setShowProgress] = useState(0);
+ const [isFinished, setIsFinished] = useState(false);
+
+ const { run: fetchDestroyTaskInfo } = useRequest(getDestroyTaskInfo, {
+ onSuccess: async ({ success, data }: API.OBResponseTaskInfo_) => {
+ if (success) {
+ if (data?.status !== 'RUNNING') {
+ clearInterval(timerFetch);
+ }
+ clearInterval(timerProgress);
+ if (data?.status === 'RUNNING') {
+ const newProgress = NP.times(
+ NP.divide(data?.finished, data?.total),
+ 100,
+ );
+
+ setProgress(newProgress);
+ let step = NP.minus(newProgress, progress);
+ let stepNum = 1;
+ timerProgress = setInterval(() => {
+ setShowProgress(
+ NP.plus(progress, NP.times(NP.divide(step, 100), stepNum)),
+ );
+
+ stepNum += 1;
+ }, 10);
+ } else if (data?.status === 'SUCCESSFUL') {
+ let step = NP.minus(100, progress);
+ let stepNum = 1;
+ timerProgress = setInterval(() => {
+ setShowProgress(
+ NP.plus(progress, NP.times(NP.divide(step, 100), stepNum)),
+ );
+
+ stepNum += 1;
+ }, 10);
+ try {
+ const { success: nameSuccess, data: nameData } =
+ await getInfoByName({ name });
+ if (nameSuccess) {
+ const { config } = nameData;
+ const { components = {} } = config;
+ const newSelectedConfig:string[] = []
+ Object.keys(components).forEach((key)=>{
+ if(selectedConfig.includes(key) && components[key]){
+ newSelectedConfig.push(key)
+ }else if(key === 'ocpexpress' && components[key]){
+ // todo:同步为ocpexpress
+ newSelectedConfig.push('ocp-express')
+ }
+ })
+ setSelectedConfig(newSelectedConfig)
+ setConfigData(config || {});
+ setLowVersion(checkLowVersion(components?.oceanbase?.version));
+ setClusterMore(!!components?.oceanbase?.parameters?.length);
+ setComponentsMore(!!components?.obproxy?.parameters?.length);
+ setIsDraft(true);
+
+ const newSelectedVersionInfo = componentsVersionInfo?.[
+ oceanbaseComponent
+ ]?.dataSource?.filter(
+ (item: any) => item.md5 === components?.oceanbase?.package_hash,
+ )[0];
+ if (newSelectedVersionInfo) {
+ setOBVersionValue(
+ `${components?.oceanbase?.version}-${components?.oceanbase?.release}-${components?.oceanbase?.package_hash}`,
+ );
+
+ let currentObproxyVersionInfo = {};
+ componentsVersionInfo?.[
+ obproxyComponent
+ ]?.dataSource?.some((item: API.service_model_components_ComponentInfo) => {
+ if (item?.version_type === newSelectedVersionInfo?.version_type) {
+ currentObproxyVersionInfo = item;
+ return true;
+ }
+ return false
+ });
+
+ setComponentsVersionInfo({
+ ...componentsVersionInfo,
+ [oceanbaseComponent]: {
+ ...componentsVersionInfo[oceanbaseComponent],
+ ...newSelectedVersionInfo,
+ },
+ [obproxyComponent]: {
+ ...componentsVersionInfo[obproxyComponent],
+ ...currentObproxyVersionInfo
+ }
+ });
+ }
+ setTimeout(() => {
+ onCancel();
+ }, 2000);
+ } else {
+ setIsDraft(false);
+ message.error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.DeleteDeployModal.FailedToObtainConfigurationInformation',
+ defaultMessage: '获取配置信息失败',
+ }),
+ );
+ onCancel();
+ }
+ } catch (e: any) {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ } else {
+ message.error(data?.msg);
+ onCancel();
+ }
+ setStatus(data?.status);
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ useEffect(() => {
+ fetchDestroyTaskInfo({ name });
+ timerFetch = setInterval(() => {
+ fetchDestroyTaskInfo({ name });
+ }, 1000);
+ return () => {
+ clearInterval(timerProgress);
+ clearInterval(timerFetch);
+ };
+ }, []);
+
+ useEffect(() => {
+ if (status !== 'RUNNING') {
+ setTimeout(() => {
+ clearInterval(timerProgress);
+ setIsFinished(true);
+ }, 1000);
+ }
+ }, [status]);
+
+ return (
+
+
+ {isFinished ? (
+ <>
+
+ {status === 'SUCCESSFUL'
+ ? intl.formatMessage({
+ id: 'OBD.pages.components.DeleteDeployModal.FailedHistoryDeploymentEnvironmentCleared',
+ defaultMessage: '清理失败历史部署环境成功',
+ })
+ : intl.formatMessage({
+ id: 'OBD.pages.components.DeleteDeployModal.FailedToCleanUpThe',
+ defaultMessage: '清理失败历史部署环境失败',
+ })}
+
+
+ >
+ ) : (
+ <>
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.DeleteDeployModal.CleaningFailedHistoricalDeploymentEnvironments',
+ defaultMessage: '正在清理失败的历史部署环境',
+ })}
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.DeleteDeployModal.PleaseWaitPatiently',
+ defaultMessage: '请耐心等待',
+ })}
+
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/DeployType.tsx b/web/src/pages/Obdeploy/DeployType.tsx
new file mode 100644
index 0000000..48f6736
--- /dev/null
+++ b/web/src/pages/Obdeploy/DeployType.tsx
@@ -0,0 +1,90 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { Space, Card, Tag } from 'antd';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+interface Props {
+ value?: string;
+ onChange?: (value: string) => void;
+}
+
+const optionConfig = [
+ {
+ label: (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.pages.components.DeployType.FullyDeployed',
+ defaultMessage: '完全部署',
+ })}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.DeployType.Recommended',
+ defaultMessage: '推荐',
+ })}
+
+ >
+ ),
+
+ value: 'all',
+ desc: intl.formatMessage({
+ id: 'OBD.pages.components.DeployType.ConfigureDatabaseClustersAndRelated',
+ defaultMessage:
+ '配置数据库集群及相关生态工具,提供全套数据库运维管理服务',
+ }),
+ },
+ {
+ label: intl.formatMessage({
+ id: 'OBD.pages.components.DeployType.ThinDeployment',
+ defaultMessage: '精简部署',
+ }),
+ value: 'ob',
+ desc: intl.formatMessage({
+ id: 'OBD.pages.components.DeployType.OnlyDatabaseClustersAreConfigured',
+ defaultMessage: '只配置数据库集群,以最精简的数据库内核能力提供服务',
+ }),
+ },
+];
+
+export default function DeployType({ value, onChange }: Props) {
+ const [selectValue, setSelectValue] = useState(value || 'all');
+
+ useEffect(() => {
+ if (value && value !== selectValue) {
+ setSelectValue(value);
+ }
+ }, [value]);
+
+ useEffect(() => {
+ if (onChange) {
+ onChange(selectValue);
+ }
+ }, [selectValue]);
+ return (
+
+ {optionConfig.map((item) => (
+
+ setSelectValue(item.value)}
+ >
+ {item.label}
+
+
+ {item.desc}
+
+
+ ))}
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/ExitPage.tsx b/web/src/pages/Obdeploy/ExitPage.tsx
new file mode 100644
index 0000000..bb35474
--- /dev/null
+++ b/web/src/pages/Obdeploy/ExitPage.tsx
@@ -0,0 +1,71 @@
+import { intl } from '@/utils/intl';
+import { message, Card, Empty } from 'antd';
+import { CopyOutlined } from '@ant-design/icons';
+import copy from 'copy-to-clipboard';
+import { getLocale } from 'umi';
+import ExitPageWrapper from '@/component/ExitPageWrapper';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+export default function ExitPage() {
+ const command = 'obd web';
+
+ const handleCopy = () => {
+ copy(command);
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.pages.components.ExitPage.CopiedSuccessfully',
+ defaultMessage: '复制成功',
+ }),
+ );
+ };
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.ExitPage.TheDeploymentProgramHasExited',
+ defaultMessage: '部署程序已经退出!',
+ })}
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.ExitPage.ToStartAgainGoTo',
+ defaultMessage: '如需再次启动,请前往中控服务器执行',
+ })}
+
+
+ {command}
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/InstallConfig.tsx b/web/src/pages/Obdeploy/InstallConfig.tsx
new file mode 100644
index 0000000..92cb8d3
--- /dev/null
+++ b/web/src/pages/Obdeploy/InstallConfig.tsx
@@ -0,0 +1,1090 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState, useRef } from 'react';
+import { useModel, history } from 'umi';
+import {
+ Space,
+ Button,
+ Form,
+ Tag,
+ Table,
+ Alert,
+ Tooltip,
+ Select,
+ Modal,
+ Spin,
+ message,
+} from 'antd';
+import { ProCard, ProForm, ProFormText } from '@ant-design/pro-components';
+import {
+ CloseOutlined,
+ SafetyCertificateFilled,
+ InfoOutlined,
+ InfoCircleOutlined,
+ CopyOutlined,
+} from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+import useRequest from '@/utils/useRequest';
+import { queryAllComponentVersions } from '@/services/ob-deploy-web/Components';
+import {
+ getDeployment,
+ destroyDeployment,
+} from '@/services/ob-deploy-web/Deployments';
+import { listRemoteMirrors } from '@/services/ob-deploy-web/Mirror';
+import { handleQuit, checkLowVersion, getErrorInfo } from '@/utils';
+import NP from 'number-precision';
+import copy from 'copy-to-clipboard';
+import DeployType from './DeployType';
+import DeleteDeployModal from './DeleteDeployModal';
+import ErrorCompToolTip from '@/component/ErrorCompToolTip';
+import {
+ commonStyle,
+ allComponentsName,
+ oceanbaseComponent,
+ obproxyComponent,
+ ocpexpressComponent,
+ obagentComponent,
+} from '../constants';
+import { getLocale } from 'umi';
+import { clusterNameReg } from '@/utils';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+type rowDataType = {
+ key: string;
+ name: string;
+ onlyAll: boolean;
+ desc: string;
+ doc: string;
+};
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+const appnameReg = /^[a-zA-Z]([a-zA-Z0-9]{0,19})$/;
+
+const oceanBaseInfo = {
+ group: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Database',
+ defaultMessage: '数据库',
+ }),
+ key: 'database',
+ content: [
+ {
+ key: oceanbaseComponent,
+ name: 'OceanBase Database',
+ onlyAll: false,
+ desc: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.ItIsAFinancialLevel',
+ defaultMessage:
+ '是金融级分布式数据库,具备数据强一致、高扩展、高可用、高性价比、稳定可靠等特征。',
+ }),
+ doc: 'https://www.oceanbase.com/docs/oceanbase-database-cn',
+ },
+ ],
+};
+const componentsGroupInfo = [
+ {
+ group: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Proxy',
+ defaultMessage: '代理',
+ }),
+ key: 'agency',
+ onlyAll: true,
+ content: [
+ {
+ key: obproxyComponent,
+ name: 'OBProxy',
+ onlyAll: true,
+ desc: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.ItIsAProxyServer',
+ defaultMessage:
+ '是 OceanBase 数据库专用的代理服务器,可以将用户 SQL 请求转发至最佳目标 OBServer 。',
+ }),
+ doc: 'https://www.oceanbase.com/docs/odp-enterprise-cn',
+ },
+ ],
+ },
+ {
+ group: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Tools',
+ defaultMessage: '工具',
+ }),
+ key: 'ocpexpressTool',
+ onlyAll: true,
+ content: [
+ {
+ key: ocpexpressComponent,
+ name: 'OCP Express',
+ onlyAll: true,
+ desc: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.ItIsAManagementAnd',
+ defaultMessage:
+ '是专为 OceanBase 设计的管控平台,可实现对集群、租户的监控管理、诊断等核心能力。',
+ }),
+ doc: 'https://www.oceanbase.com/docs/common-oceanbase-database-cn-0000000001626262',
+ },
+ ],
+ },
+ {
+ group: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Tools',
+ defaultMessage: '工具',
+ }),
+ key: 'obagentTool',
+ onlyAll: true,
+ content: [
+ {
+ key: obagentComponent,
+ name: 'OBAgent',
+ onlyAll: true,
+ desc: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.IsAMonitoringAndCollection',
+ defaultMessage:
+ '是一个监控采集框架。OBAgent 支持推、拉两种数据采集模式,可以满足不同的应用场景。',
+ }),
+ doc: 'https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001576872',
+ },
+ ],
+ },
+];
+
+const mirrors = ['oceanbase.community.stable', 'oceanbase.development-kit'];
+
+export default function InstallConfig() {
+ const {
+ initAppName,
+ setCurrentStep,
+ configData,
+ setConfigData,
+ lowVersion,
+ isFirstTime,
+ setIsFirstTime,
+ isDraft,
+ setIsDraft,
+ componentsVersionInfo,
+ setComponentsVersionInfo,
+ handleQuitProgress,
+ getInfoByName,
+ setLowVersion,
+ setErrorVisible,
+ errorsList,
+ setErrorsList,
+ selectedConfig,
+ setSelectedConfig,
+ aliveTokenTimer,
+ } = useModel('global');
+ const { components, home_path } = configData || {};
+ const { oceanbase } = components || {};
+ const [existNoVersion, setExistNoVersion] = useState(false);
+ const [obVersionValue, setOBVersionValue] = useState(
+ undefined,
+ );
+
+ const [hasDraft, setHasDraft] = useState(false);
+ const [deleteLoadingVisible, setDeleteLoadingVisible] = useState(false);
+ const [deleteName, setDeleteName] = useState('');
+ const [deployMemory, setDeployMemory] = useState(0);
+ const [componentsMemory, setComponentsMemory] = useState(0);
+ const [form] = ProForm.useForm();
+ const [unavailableList, setUnavailableList] = useState([]);
+ const [componentLoading, setComponentLoading] = useState(false);
+ const draftNameRef = useRef();
+
+ const { run: fetchDeploymentInfo, loading } = useRequest(getDeployment, {
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+ const { run: handleDeleteDeployment } = useRequest(destroyDeployment, {
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+ const { run: fetchListRemoteMirrors } = useRequest(listRemoteMirrors, {
+ onSuccess: () => {
+ setComponentLoading(false);
+ },
+ onError: ({ response, data, type }: any) => {
+ if (response?.status === 503) {
+ setTimeout(() => {
+ fetchListRemoteMirrors();
+ }, 1000);
+ } else {
+ const errorInfo = getErrorInfo({ response, data, type });
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ setComponentLoading(false);
+ }
+ },
+ });
+
+ const judgVersions = (source: API.ComponentsVersionInfo) => {
+ if (Object.keys(source).length !== allComponentsName.length) {
+ setExistNoVersion(true);
+ } else {
+ setExistNoVersion(false);
+ }
+ };
+
+ const { run: fetchAllComponentVersions, loading: versionLoading } =
+ useRequest(queryAllComponentVersions, {
+ onSuccess: async ({
+ success,
+ data,
+ }: API.OBResponseDataListComponent_) => {
+ if (success) {
+ const newComponentsVersionInfo = {};
+ const oceanbaseVersionsData = data?.items?.filter(
+ (item) => item.name === oceanbaseComponent,
+ );
+
+ const initOceanbaseVersionInfo =
+ oceanbaseVersionsData[0]?.info[0] || {};
+ const newSelectedOceanbaseVersionInfo =
+ oceanbaseVersionsData[0]?.info?.filter(
+ (item) => item.md5 === oceanbase?.package_hash,
+ )?.[0];
+
+ const currentOceanbaseVersionInfo =
+ newSelectedOceanbaseVersionInfo || initOceanbaseVersionInfo;
+
+ data?.items?.forEach((item) => {
+ if (allComponentsName.includes(item.name)) {
+ if (item?.info?.length) {
+ const initVersionInfo = item?.info[0] || {};
+ if (item.name === oceanbaseComponent) {
+ setOBVersionValue(
+ `${currentOceanbaseVersionInfo?.version}-${currentOceanbaseVersionInfo?.release}-${currentOceanbaseVersionInfo?.md5}`,
+ );
+ newComponentsVersionInfo[item.name] = {
+ ...currentOceanbaseVersionInfo,
+ dataSource: item.info || [],
+ };
+ } else if (item.name === obproxyComponent) {
+ let currentObproxyVersionInfo = {};
+ item?.info?.some((subItem) => {
+ if (
+ subItem?.version_type ===
+ currentOceanbaseVersionInfo?.version_type
+ ) {
+ currentObproxyVersionInfo = subItem;
+ return true;
+ }
+ return false;
+ });
+ newComponentsVersionInfo[item.name] = {
+ ...currentObproxyVersionInfo,
+ dataSource: item.info || [],
+ };
+ } else {
+ newComponentsVersionInfo[item.name] = {
+ ...initVersionInfo,
+ dataSource: item.info || [],
+ };
+ }
+ }
+ }
+ });
+
+ const noVersion =
+ Object.keys(newComponentsVersionInfo).length !==
+ allComponentsName.length;
+ judgVersions(newComponentsVersionInfo);
+ setComponentsVersionInfo(newComponentsVersionInfo);
+
+ if (noVersion) {
+ const { success: mirrorSuccess, data: mirrorData } =
+ await fetchListRemoteMirrors();
+ if (mirrorSuccess) {
+ const nameList: string[] = [];
+ if (mirrorData?.total < 2) {
+ const mirrorName = mirrorData?.items?.map(
+ (item: API.Mirror) => item.section_name,
+ );
+
+ const noDataName = [...mirrorName, ...mirrors].filter(
+ (name) =>
+ mirrors.includes(name) && !mirrorName.includes(name),
+ );
+
+ noDataName.forEach((name) => {
+ nameList.push(name);
+ });
+ }
+ if (mirrorData?.total) {
+ mirrorData?.items?.forEach((item: API.Mirror) => {
+ if (!item.available) {
+ nameList.push(item.section_name);
+ }
+ });
+ }
+ setUnavailableList(nameList);
+ }
+ } else {
+ setComponentLoading(false);
+ }
+ }
+ },
+ onError: ({ response, data, type }: any) => {
+ if (response?.status === 503) {
+ setTimeout(() => {
+ fetchAllComponentVersions();
+ }, 1000);
+ } else {
+ const errorInfo = getErrorInfo({ response, data, type });
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ setComponentLoading(false);
+ }
+ },
+ });
+
+ const nameValidator = async (_: any, value: string) => {
+ if (value) {
+ if (hasDraft || isDraft) {
+ return Promise.resolve();
+ }
+ if (!clusterNameReg.test(value)) {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.Obdeploy.InstallConfig.ItStartsWithALetter',
+ defaultMessage:
+ '以英文字母开头、英文或数字结尾,可包含英文、数字和下划线,且长度为 2 ~ 32',
+ }),
+ ),
+ );
+ }
+ try {
+ const { success, data } = await getInfoByName({ name: value });
+ if (success) {
+ if (['CONFIGURED', 'DESTROYED'].includes(data?.status)) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ intl.formatMessage(
+ {
+ id: 'OBD.pages.components.InstallConfig.ADeploymentNameWithValue',
+ defaultMessage: '已存在为 {value} 的部署名称,请指定新名称',
+ },
+ { value: value },
+ ),
+ ),
+ );
+ }
+ return Promise.resolve();
+ } catch ({ response, data, type }: any) {
+ if (response?.status === 404) {
+ return Promise.resolve();
+ } else {
+ const errorInfo = getErrorInfo({ response, data, type });
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ }
+ }
+ };
+
+ const preStep = () => {
+ if (aliveTokenTimer.current) {
+ clearTimeout(aliveTokenTimer.current);
+ aliveTokenTimer.current = null;
+ }
+ history.push('guide');
+ };
+
+ const nextStep = () => {
+ if (form.getFieldsError(['appname'])[0].errors.length) return;
+ form.validateFields().then((values) => {
+ const lastAppName = oceanbase?.appname || initAppName;
+ let newHomePath = home_path;
+ if (values?.appname !== lastAppName && home_path) {
+ const firstHalfHomePath = home_path.split(`/${lastAppName}`)[0];
+ newHomePath = `${firstHalfHomePath}/${values?.appname}`;
+ }
+ let newComponents: API.Components = {
+ oceanbase: {
+ ...(components?.oceanbase || {}),
+ component:
+ componentsVersionInfo?.[oceanbaseComponent]?.version_type === 'ce'
+ ? 'oceanbase-ce'
+ : 'oceanbase',
+ appname: values?.appname,
+ version: componentsVersionInfo?.[oceanbaseComponent]?.version,
+ release: componentsVersionInfo?.[oceanbaseComponent]?.release,
+ package_hash: componentsVersionInfo?.[oceanbaseComponent]?.md5,
+ },
+ };
+ if (selectedConfig.includes('obproxy')) {
+ newComponents.obproxy = {
+ ...(components?.obproxy || {}),
+ component:
+ componentsVersionInfo?.[obproxyComponent]?.version_type === 'ce'
+ ? 'obproxy-ce'
+ : 'obproxy',
+ version: componentsVersionInfo?.[obproxyComponent]?.version,
+ release: componentsVersionInfo?.[obproxyComponent]?.release,
+ package_hash: componentsVersionInfo?.[obproxyComponent]?.md5,
+ };
+ }
+ if (selectedConfig.includes('obagent')) {
+ newComponents.obagent = {
+ ...(components?.obagent || {}),
+ component: obagentComponent,
+ version: componentsVersionInfo?.[obagentComponent]?.version,
+ release: componentsVersionInfo?.[obagentComponent]?.release,
+ package_hash: componentsVersionInfo?.[obagentComponent]?.md5,
+ };
+ }
+ if (!lowVersion && selectedConfig.includes('ocp-express')) {
+ newComponents.ocpexpress = {
+ ...(components?.ocpexpress || {}),
+ component: ocpexpressComponent,
+ version: componentsVersionInfo?.[ocpexpressComponent]?.version,
+ release: componentsVersionInfo?.[ocpexpressComponent]?.release,
+ package_hash: componentsVersionInfo?.[ocpexpressComponent]?.md5,
+ };
+ }
+
+ setConfigData({
+ ...configData,
+ components: newComponents,
+ home_path: newHomePath,
+ });
+ setCurrentStep(2);
+ setIsFirstTime(false);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ });
+ };
+
+ const onVersionChange = (
+ value: string,
+ dataSource: API.service_model_components_ComponentInfo[],
+ ) => {
+ const md5 = value.split('-')[2];
+ setOBVersionValue(value);
+ const newSelectedVersionInfo = dataSource.filter(
+ (item) => item.md5 === md5,
+ )[0];
+
+ let currentObproxyVersionInfo = {};
+ componentsVersionInfo?.[obproxyComponent]?.dataSource?.some(
+ (item: API.service_model_components_ComponentInfo) => {
+ if (item?.version_type === newSelectedVersionInfo?.version_type) {
+ currentObproxyVersionInfo = item;
+ return true;
+ }
+ return false;
+ },
+ );
+ setComponentsVersionInfo({
+ ...componentsVersionInfo,
+ [oceanbaseComponent]: {
+ ...componentsVersionInfo[oceanbaseComponent],
+ ...newSelectedVersionInfo,
+ },
+ [obproxyComponent]: {
+ ...componentsVersionInfo[obproxyComponent],
+ ...currentObproxyVersionInfo,
+ },
+ });
+ setLowVersion(
+ !!(
+ newSelectedVersionInfo.version &&
+ checkLowVersion(newSelectedVersionInfo.version.split('')[0])
+ ),
+ );
+ };
+
+ const directTo = (url: string) => {
+ const blankWindow = window.open('about:blank');
+ if (blankWindow) {
+ blankWindow.location.href = url;
+ } else {
+ window.location.href = url;
+ }
+ };
+
+ const getColumns = (group: string, supportCheckbox: boolean) => {
+ const columns: ColumnsType = [
+ {
+ title: group,
+ dataIndex: 'name',
+ width: supportCheckbox ? 147 : 195,
+ render: (text, record) => {
+ return (
+ <>
+ {text}
+ {record.key === ocpexpressComponent && lowVersion ? (
+
+ ) : !componentsVersionInfo[record.key]?.version ? (
+
+ ) : null}
+ >
+ );
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Version',
+ defaultMessage: '版本',
+ }),
+ dataIndex: 'version',
+ width: locale === 'zh-CN' ? 130 : 154,
+ render: (_, record) => {
+ const versionInfo = componentsVersionInfo[record.key] || {};
+ if (record?.key === oceanbaseComponent) {
+ return (
+
+ );
+ } else {
+ return versionInfo?.version ? (
+ <>
+ {versionInfo?.version}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Latest',
+ defaultMessage: '最新',
+ })}
+
+ >
+ ) : (
+ '-'
+ );
+ }
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Description',
+ defaultMessage: '描述',
+ }),
+ dataIndex: 'desc',
+ render: (text, record) => {
+ let disabled = false;
+ if (record.key === ocpexpressComponent && lowVersion) {
+ disabled = true;
+ }
+ return (
+ <>
+ {text || '-'}
+ {
+ if (!disabled) directTo(record.doc);
+ }}
+ target="_blank"
+ >
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.LearnMore',
+ defaultMessage: '了解更多',
+ })}
+
+ >
+ );
+ },
+ },
+ ];
+
+ return columns;
+ };
+
+ const handleCopy = (content: string) => {
+ copy(content);
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.CopiedSuccessfully',
+ defaultMessage: '复制成功',
+ }),
+ );
+ };
+
+ /**
+ * tip:如果选择OCP Express,则OBAgent则自动选择,无需提示
+ * 如果不选择 OBAgent, 则 OCP Express 则自动不选择,无需提示
+ */
+ const handleSelect = (record: rowDataType, selected: boolean) => {
+ if (!selected) {
+ let newConfig = [],
+ target = false;
+ target =
+ record.key === 'obagent' && selectedConfig.includes('ocp-express');
+ for (let val of selectedConfig) {
+ if (target && val === 'ocp-express') continue;
+ if (val !== record.key) {
+ newConfig.push(val);
+ }
+ }
+ setSelectedConfig(newConfig);
+ } else {
+ if (record.key === 'ocp-express' && !selectedConfig.includes('obagent')) {
+ setSelectedConfig([...selectedConfig, record.key, 'obagent']);
+ } else {
+ setSelectedConfig([...selectedConfig, record.key]);
+ }
+ }
+ };
+
+ const caculateSize = (originSize: number): string => {
+ return NP.divide(NP.divide(originSize, 1024), 1024).toFixed(2);
+ };
+
+ useEffect(() => {
+ setComponentLoading(true);
+ if (isFirstTime) {
+ fetchAllComponentVersions();
+ fetchDeploymentInfo({ task_status: 'DRAFT' }).then(
+ ({ success: draftSuccess, data: draftData }: API.OBResponse) => {
+ if (draftSuccess && draftData?.items?.length) {
+ const defaultValue = draftData?.items[0]?.name;
+ draftNameRef.current = defaultValue;
+ setHasDraft(true);
+ Modal.confirm({
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.TheFollowingHistoricalConfigurationsOf',
+ defaultMessage: '检测到系统中存在以下部署失败的历史配置',
+ }),
+ okText: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.ContinueDeployment',
+ defaultMessage: '继续部署',
+ }),
+ cancelText: intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.Ignore',
+ defaultMessage: '忽略',
+ }),
+ closable: true,
+ width: 424,
+ content: (
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.ContinuingDeploymentWillCleanUp',
+ defaultMessage:
+ '继续部署将先清理失败的历史部署环境,是否继续历史部署流程?',
+ })}
+
+
+
+ ),
+
+ onOk: () => {
+ return new Promise(async (resolve) => {
+ try {
+ const { success: deleteSuccess } =
+ await handleDeleteDeployment({
+ name: draftNameRef.current,
+ });
+ if (deleteSuccess) {
+ resolve();
+ setDeleteName(draftNameRef.current);
+ setDeleteLoadingVisible(true);
+ }
+ } catch {
+ setIsDraft(false);
+ resolve();
+ }
+ });
+ },
+ onCancel: () => {
+ setIsDraft(false);
+ setHasDraft(false);
+ },
+ });
+ } else {
+ setIsDraft(false);
+ }
+ },
+ );
+ } else {
+ fetchAllComponentVersions();
+ }
+ }, []);
+
+ useEffect(() => {
+ let deployMemory: number =
+ componentsVersionInfo?.[oceanbaseComponent]?.estimated_size || 0;
+ let componentsMemory: number = 0;
+ const keys = Object.keys(componentsVersionInfo);
+ keys.forEach((key) => {
+ if (key !== 'oceanbaseComponent' && selectedConfig.includes(key)) {
+ componentsMemory += componentsVersionInfo[key]?.estimated_size;
+ }
+ });
+ setDeployMemory(deployMemory);
+ setComponentsMemory(componentsMemory);
+ }, [componentsVersionInfo, selectedConfig]);
+
+ useEffect(() => {
+ form.setFieldsValue({
+ appname: configData?.components?.oceanbase?.appname || initAppName,
+ });
+ }, [configData]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.DeployComponents',
+ defaultMessage: '部署组件',
+ })}
+
+
+ {' '}
+ {intl.formatMessage(
+ {
+ id: 'OBD.pages.components.InstallConfig.EstimatedInstallationRequiresSizeMb',
+ defaultMessage: '预计安装需要 {size}MB 空间',
+ },
+ { size: caculateSize(deployMemory) },
+ )}
+
+ >
+ }
+ className="card-header-padding-top-0 card-padding-bottom-24 card-padding-top-0"
+ >
+
+ {existNoVersion ? (
+ unavailableList?.length ? (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.IfTheCurrentEnvironmentCannot',
+ defaultMessage:
+ '如当前环境无法正常访问外网,建议使用 OceanBase 离线安装包进行安装部署。',
+ })}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.GoToDownloadOfflineInstallation',
+ defaultMessage: '前往下载离线安装',
+ })}
+
+ >
+ }
+ type="error"
+ showIcon
+ style={{ marginTop: '16px' }}
+ />
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.IfTheCurrentEnvironmentHas',
+ defaultMessage:
+ '如当前环境可正常访问外网,可启动 OceanBase 在线镜像仓库,或联系您的镜像仓库管理员。',
+ })}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.RunTheCommandOnThe',
+ defaultMessage:
+ '请在主机上执行一下命令启用在线镜像仓库',
+ })}
+
obd mirror enable
+ oceanbase.community.stable
+ oceanbase.development-kit
+
+
+ handleCopy(
+ 'obd mirror enable oceanbase.community.stable oceanbase.development-kit',
+ )
+ }
+ />
+
+
+ }
+ >
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.HowToEnableOnlineImage',
+ defaultMessage: '如何启用在线镜像仓库',
+ })}
+
+
+ >
+ }
+ type="error"
+ showIcon
+ style={{ marginTop: '16px' }}
+ />
+ )
+ ) : null}
+
+ {
+ if (record.key === ocpexpressComponent && lowVersion) {
+ return styles.disabledRow;
+ }
+ }}
+ />
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallConfig.OptionalComponents',
+ defaultMessage: '可选组件',
+ })}
+
+
+ {' '}
+ {intl.formatMessage(
+ {
+ id: 'OBD.pages.components.InstallConfig.EstimatedInstallationRequiresSizeMb',
+ defaultMessage: '预计部署需要 {size}MB 空间',
+ },
+ { size: caculateSize(componentsMemory) },
+ )}
+
+ >
+ }
+ className="card-header-padding-top-0 card-padding-bottom-24 card-padding-top-0"
+ >
+ {componentsGroupInfo.map((componentInfo) => (
+
+
+ {
+ if (record.key === ocpexpressComponent && lowVersion) {
+ return styles.disabledRow;
+ }
+ }}
+ />
+
+
+ ))}
+
+
+
+ {deleteLoadingVisible && (
+ setDeleteLoadingVisible(false)}
+ setOBVersionValue={setOBVersionValue}
+ />
+ )}
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/InstallFinished.tsx b/web/src/pages/Obdeploy/InstallFinished.tsx
new file mode 100644
index 0000000..97e6cd0
--- /dev/null
+++ b/web/src/pages/Obdeploy/InstallFinished.tsx
@@ -0,0 +1,584 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import {
+ Space,
+ Button,
+ Table,
+ Alert,
+ Result,
+ Tooltip,
+ message,
+ Tag,
+ Modal,
+ Typography,
+ Spin,
+} from 'antd';
+import {
+ CloseCircleFilled,
+ CheckCircleFilled,
+ CaretRightFilled,
+ CaretDownFilled,
+ CopyOutlined,
+ ExclamationCircleOutlined,
+ CheckOutlined,
+} from '@ant-design/icons';
+import { ProCard } from '@ant-design/pro-components';
+import useRequest from '@/utils/useRequest';
+import type { ColumnsType } from 'antd/es/table';
+import copy from 'copy-to-clipboard';
+import {
+ queryDeploymentReport,
+ queryConnectionInfo,
+ queryInstallLog,
+} from '@/services/ob-deploy-web/Deployments';
+import {
+ componentsConfig,
+ componentVersionTypeToComponent,
+} from '../constants';
+import { handleQuit, getErrorInfo } from '@/utils';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+const { Paragraph } = Typography;
+
+export default function InstallProcess() {
+ const {
+ configData,
+ installStatus,
+ setCurrentStep,
+ handleQuitProgress,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+ const [logs, setLogs] = useState({});
+ const [currentExpeandedName, setCurrentExpeandedName] = useState('');
+
+ const name = configData?.components?.oceanbase?.appname;
+ const { run: fetchReportInfo, data: reportInfo } = useRequest(
+ queryDeploymentReport,
+ {
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+ const { run: fetchConnectInfo, data: connectInfo } = useRequest(
+ queryConnectionInfo,
+ {
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const { run: handleInstallLog, loading } = useRequest(queryInstallLog, {
+ onSuccess: (
+ { success, data }: API.OBResponseInstallLog_,
+ [{ component_name }]: [API.queryInstallLogParams],
+ ) => {
+ if (success) {
+ setLogs({ ...logs, [component_name]: data?.log });
+ setTimeout(() => {
+ const log = document.getElementById(`report-log-${component_name}`);
+ if (log) {
+ log.scrollTop = log.scrollHeight;
+ }
+ });
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const handleCopy = (content: string) => {
+ copy(content);
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.CopiedSuccessfully',
+ defaultMessage: '复制成功',
+ }),
+ );
+ };
+
+ const handleCopyCommand = (command: string) => {
+ copy(command);
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.CopiedSuccessfully',
+ defaultMessage: '复制成功',
+ }),
+ );
+ };
+
+ useEffect(() => {
+ fetchReportInfo({ name });
+ fetchConnectInfo({ name });
+ }, []);
+
+ const connectColumns: ColumnsType = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Component',
+ defaultMessage: '组件',
+ }),
+ dataIndex: 'component',
+ width: 200,
+ render: (text) => {
+ const component = componentVersionTypeToComponent[text]
+ ? componentVersionTypeToComponent[text]
+ : text;
+ return componentsConfig[component]?.showComponentName || '-';
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.AccessAddress',
+ defaultMessage: '访问地址',
+ }),
+ dataIndex: 'access_url',
+ width: 160,
+ render: (text) => text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Account',
+ defaultMessage: '账号',
+ }),
+ dataIndex: 'user',
+ render: (text) => text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Password',
+ defaultMessage: '密码',
+ }),
+ dataIndex: 'password',
+ width: 160,
+ render: (text) =>
+ text ? (
+
+ {text}
+
+ ) : (
+ '-'
+ ),
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.ConnectionString',
+ defaultMessage: '连接串',
+ }),
+ dataIndex: 'connect_url',
+ width: 300,
+ render: (text, record) => {
+ let content;
+ if (/^http/g.test(text)) {
+ content = (
+
+ {text}
+
+ );
+ } else {
+ content = (
+
+ {text}
+
+ );
+ }
+ return (
+
+ );
+ },
+ },
+ ];
+
+ const reportColumns: ColumnsType = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.ComponentName',
+ defaultMessage: '组件名称',
+ }),
+ dataIndex: 'name',
+ render: (text) => {
+ const component = componentVersionTypeToComponent[text]
+ ? componentVersionTypeToComponent[text]
+ : text;
+ return componentsConfig[component]?.showComponentName || '-';
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.ComponentType',
+ defaultMessage: '组件类型',
+ }),
+ dataIndex: 'type',
+ render: (_, record) => {
+ const component = componentVersionTypeToComponent[record.name]
+ ? componentVersionTypeToComponent[record.name]
+ : record.name;
+ return componentsConfig[component]?.type || '-';
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Version',
+ defaultMessage: '版本',
+ }),
+ dataIndex: 'version',
+ render: (text) => text || '-',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.InstallationResults',
+ defaultMessage: '安装结果',
+ }),
+ dataIndex: 'status',
+ width: locale === 'zh-CN' ? 200 : 260,
+ render: (text, record) => {
+ const statusIcon =
+ text === 'SUCCESSFUL' ? (
+
+ ) : (
+
+ );
+
+ const status =
+ text === 'SUCCESSFUL'
+ ? intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Success',
+ defaultMessage: '成功',
+ })
+ : intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Failed',
+ defaultMessage: '失败',
+ });
+
+ const getCommand = (component: string, ip: string) => {
+ return `obd tool command ${name} log -c ${component} -s ${ip}`;
+ };
+
+ const serversInfo = record.servers?.map((server) => ({
+ server,
+ command: getCommand(record.name, server),
+ }));
+
+ return (
+ <>
+ {statusIcon}
+ {status}
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.GoToTheObdConsole',
+ defaultMessage: '请前往 OBD 中控机执行以下命令查看日志',
+ })}
+
+ {serversInfo.map((item) => (
+ <>
+
+ {statusIcon}
+ {item.server}
+
+
+ >
+ ))}
+ >
+ }
+ placement="topRight"
+ overlayClassName={styles.reportTooltip}
+ >
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.ViewDetails',
+ defaultMessage: '查看详情',
+ })}
+
+
+ >
+ );
+ },
+ },
+ ];
+
+ const onExpand = (expeanded: boolean, record: API.DeploymentReport) => {
+ if (expeanded && !logs?.[record.name]) {
+ setCurrentExpeandedName(record.name);
+ handleInstallLog({ name, component_name: record.name });
+ }
+ };
+
+ const expandedRowRender = (record: API.DeploymentReport) => {
+ return (
+
+
+ {logs?.[record.name]}
+
+
+ );
+ };
+
+ const handleFinished = () => {
+ Modal.confirm({
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.DoYouWantToExit',
+ defaultMessage: '是否要退出页面?',
+ }),
+ okText: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Exit',
+ defaultMessage: '退出',
+ }),
+ cancelText: intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.Cancel',
+ defaultMessage: '取消',
+ }),
+ okButtonProps: { type: 'primary', danger: true },
+ content: (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.BeforeExitingMakeSureThat',
+ defaultMessage: '退出前,请确保已复制访问地址及账密信息',
+ })}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.CopyInformation',
+ defaultMessage: '复制信息',
+ })}
+ >,
+ <>
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.CopyInformation',
+ defaultMessage: '复制信息',
+ })}
+ >,
+ ],
+ onCopy: () =>
+ handleCopy(JSON.stringify(connectInfo?.items, null, '\n')),
+ }}
+ />
+
+ ),
+
+ icon: ,
+ onOk: () => {
+ handleQuit(handleQuitProgress, setCurrentStep, true);
+ },
+ });
+ };
+
+ return (
+
+
+ }
+ title={
+ installStatus === 'SUCCESSFUL' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.OceanbaseSuccessfullyDeployed',
+ defaultMessage: 'OceanBase 部署成功',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.OceanbaseDeploymentFailed',
+ defaultMessage: 'OceanBase 部署失败',
+ })}
+
+ )
+ }
+ />
+
+ {connectInfo?.items?.length ? (
+
+
+ handleCopy(JSON.stringify(connectInfo?.items, null, '\n'))
+ }
+ data-aspm-click="c307514.d317299"
+ data-aspm-desc={intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.DeploymentResultCopyInformation',
+ defaultMessage: '部署结果-复制信息',
+ })}
+ data-aspm-param={``}
+ data-aspm-expo
+ >
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallFinished.OneClickCopy',
+ defaultMessage: '一键复制',
+ })}
+
+ }
+ />
+
+
+
+ ) : null}
+
+ collapsed ? :
+ }
+ bodyStyle={{ paddingLeft: '0px', paddingRight: '0px' }}
+ >
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/InstallProcess.tsx b/web/src/pages/Obdeploy/InstallProcess.tsx
new file mode 100644
index 0000000..910340c
--- /dev/null
+++ b/web/src/pages/Obdeploy/InstallProcess.tsx
@@ -0,0 +1,312 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import { ProCard } from '@ant-design/pro-components';
+import useRequest from '@/utils/useRequest';
+import {
+ queryInstallStatus,
+ queryInstallLog,
+} from '@/services/ob-deploy-web/Deployments';
+import { getErrorInfo } from '@/utils';
+import lottie from 'lottie-web';
+import NP from 'number-precision';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+let timerLogScroll: NodeJS.Timer;
+let timerProgress: NodeJS.Timer;
+
+export default function InstallProcess() {
+ const {
+ setCurrentStep,
+ configData,
+ installStatus,
+ setInstallStatus,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+ const name = configData?.components?.oceanbase?.appname;
+ const progressCoverInitWidth = 282;
+ const [toBottom, setToBottom] = useState(true);
+ const [progress, setProgress] = useState(0);
+ const [showProgress, setShowProgress] = useState(0);
+ const [progressCoverStyle, setProgreddCoverStyle] = useState({
+ width: progressCoverInitWidth,
+ background: '#fff',
+ borderRadius: '5px',
+ });
+ const [currentPage, setCurrentPage] = useState(true);
+ const [statusData, setStatusData] = useState({});
+ const [logData, setLogData] = useState({});
+ let Video: any;
+
+ const { run: fetchInstallStatus } = useRequest(queryInstallStatus, {
+ onSuccess: ({ success, data }: API.OBResponseTaskInfo_) => {
+ if (success) {
+ setStatusData(data || {});
+ clearInterval(timerProgress);
+ if (data?.status !== 'RUNNING') {
+ setInstallStatus(data?.status);
+ setCurrentPage(false);
+ setTimeout(() => {
+ setCurrentStep(6);
+ setErrorVisible(false);
+ setErrorsList([]);
+ }, 2000);
+ } else {
+ setTimeout(() => {
+ fetchInstallStatus({ name });
+ }, 1000);
+ }
+ const newProgress = NP.divide(data?.finished, data?.total).toFixed(2);
+ setProgress(newProgress);
+ let step = NP.minus(newProgress, progress);
+ let stepNum = 1;
+ timerProgress = setInterval(() => {
+ const currentProgressNumber = NP.plus(
+ progress,
+ NP.times(NP.divide(step, 100), stepNum),
+ );
+
+ if (currentProgressNumber >= 1) {
+ clearInterval(timerProgress);
+ } else {
+ stepNum += 1;
+ setShowProgress(currentProgressNumber);
+ }
+ }, 10);
+ }
+ },
+ onError: (e: any) => {
+ if (currentPage) {
+ setTimeout(() => {
+ fetchInstallStatus({ name });
+ }, 1000);
+ }
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const { run: handleInstallLog } = useRequest(queryInstallLog, {
+ onSuccess: ({ success, data }: API.OBResponseInstallLog_) => {
+ if (success && installStatus === 'RUNNING') {
+ setLogData(data || {});
+ setTimeout(() => {
+ handleInstallLog({ name });
+ }, 1000);
+ }
+ },
+ onError: (e: any) => {
+ if (installStatus === 'RUNNING' && currentPage) {
+ setTimeout(() => {
+ handleInstallLog({ name });
+ }, 1000);
+ }
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const toLogBottom = () => {
+ const log = document.getElementById('installLog');
+ if (log) {
+ log.scrollTop = log.scrollHeight;
+ }
+ };
+
+ const handleScroll = (e?: any) => {
+ e = e || window.event;
+ const dom = e.target;
+ if (dom.scrollTop + dom.clientHeight === dom.scrollHeight) {
+ setToBottom(true);
+ } else {
+ setToBottom(false);
+ }
+ };
+
+ const getAnimate = () => {
+ const computerAnimate = document.querySelector('.computer-animate');
+ const progressAnimate = document.querySelector('.progress-animate');
+ const spacemanAnimate = document.querySelector('.spaceman-animate');
+ const sqlAnimate = document.querySelector('.database-animate');
+
+ if (progressAnimate) {
+ Video = videojs(progressAnimate, {
+ controls: false,
+ autoplay: true,
+ loop: true,
+ preload: 'auto',
+ });
+ }
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: computerAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/computer/data.json',
+ });
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: spacemanAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/spaceman/data.json',
+ });
+
+ lottie.loadAnimation({
+ prefetch: true,
+ container: sqlAnimate,
+ renderer: 'svg',
+ loop: true,
+ autoplay: true,
+ path: '/assets/database/data.json',
+ });
+ };
+
+ useEffect(() => {
+ if (name) {
+ fetchInstallStatus({ name });
+ handleInstallLog({ name });
+ }
+ }, [name]);
+
+ useEffect(() => {
+ getAnimate();
+ const log = document.querySelector('#installLog');
+ log.addEventListener('scroll', handleScroll);
+ return () => {
+ log.removeEventListener('DOMMouseScroll', handleScroll);
+ clearInterval(timerLogScroll);
+ clearInterval(timerProgress);
+ Video.dispose();
+ };
+ }, []);
+
+ useEffect(() => {
+ if (toBottom) {
+ toLogBottom();
+ timerLogScroll = setInterval(() => toLogBottom());
+ } else {
+ clearInterval(timerLogScroll);
+ }
+ }, [toBottom]);
+
+ useEffect(() => {
+ let newCoverStyle: any = { ...progressCoverStyle };
+ const newCoverWidth = NP.times(
+ NP.minus(1, showProgress),
+ progressCoverInitWidth,
+ );
+
+ if (showProgress > 0) {
+ newCoverStyle = {
+ width: `${newCoverWidth}px`,
+ background:
+ 'linear-gradient( to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 10px, rgba(255, 255, 255, 1) )',
+ };
+ }
+ setProgreddCoverStyle(newCoverStyle);
+ }, [showProgress]);
+
+ const getText = (name?: string) => {
+ return intl.formatMessage(
+ {
+ id: 'OBD.pages.components.InstallProcess.DeployingName',
+ defaultMessage: '正在部署 {name}',
+ },
+ { name: name },
+ );
+ };
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.InstallProcess.Deploying',
+ defaultMessage: '部署中...',
+ })}
+
+
+
+
+
+
+
+ {getText(statusData?.current)}
+
+
+
+
+ {logData?.log}
+ {installStatus === 'RUNNING' ? (
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/NodeConfig.tsx b/web/src/pages/Obdeploy/NodeConfig.tsx
new file mode 100644
index 0000000..29e3ecc
--- /dev/null
+++ b/web/src/pages/Obdeploy/NodeConfig.tsx
@@ -0,0 +1,1109 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState, useRef } from 'react';
+import { useModel } from 'umi';
+import {
+ Space,
+ Button,
+ Tooltip,
+ Select,
+ Popconfirm,
+ message,
+ Form,
+} from 'antd';
+import { QuestionCircleOutlined, DeleteOutlined } from '@ant-design/icons';
+import {
+ ProCard,
+ ProForm,
+ ProFormText,
+ ProFormSelect,
+ ProFormDigit,
+ EditableProTable,
+} from '@ant-design/pro-components';
+import type {
+ ProColumns,
+ EditableFormInstance,
+} from '@ant-design/pro-components';
+import { getObdInfo } from '@/services/ob-deploy-web/Info';
+import useRequest from '@/utils/useRequest';
+import { handleQuit, getErrorInfo } from '@/utils';
+import { commonStyle, pathRule } from '../constants';
+import ServerTags from './ServerTags';
+import TooltipInput from './TooltipInput';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+interface FormValues extends API.Components {
+ auth?: {
+ user?: string;
+ password?: string;
+ port?: number;
+ };
+ home_path?: string;
+}
+
+export default function NodeConfig() {
+ const {
+ selectedConfig,
+ setCurrentStep,
+ configData,
+ setConfigData,
+ lowVersion,
+ handleQuitProgress,
+ nameIndex,
+ setNameIndex,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+ const { components = {}, auth, home_path } = configData || {};
+ const { oceanbase = {}, ocpexpress = {}, obproxy = {} } = components;
+ const [form] = ProForm.useForm();
+ const [editableForm] = ProForm.useForm();
+ const tableFormRef = useRef>();
+
+ const initDBConfigData = oceanbase?.topology?.length
+ ? oceanbase?.topology?.map((item: API.Zone, index: number) => ({
+ id: (Date.now() + index).toString(),
+ ...item,
+ servers: item?.servers?.map((server) => server?.ip),
+ }))
+ : [
+ {
+ id: (Date.now() + 1).toString(),
+ name: 'zone1',
+ servers: [],
+ rootservice: undefined,
+ },
+ {
+ id: (Date.now() + 2).toString(),
+ name: 'zone2',
+ servers: [],
+ rootservice: undefined,
+ },
+ {
+ id: (Date.now() + 3).toString(),
+ name: 'zone3',
+ servers: [],
+ rootservice: undefined,
+ },
+ ];
+
+ const homePathSuffix = `/${oceanbase.appname}`;
+
+ const initHomePath = home_path
+ ? home_path.substring(0, home_path.length - homePathSuffix.length)
+ : undefined;
+
+ const [dbConfigData, setDBConfigData] =
+ useState(initDBConfigData);
+ const [editableKeys, setEditableRowKeys] = useState(() =>
+ dbConfigData.map((item) => item.id),
+ );
+
+ // all servers
+ const [allOBServer, setAllOBServer] = useState([]);
+ // all zone servers
+ const [allZoneOBServer, setAllZoneOBServer] = useState({});
+ const [lastDeleteServer, setLastDeleteServer] = useState('');
+ const [ocpServerDropdownVisible, setOcpServerDropdownVisible] =
+ useState(false);
+
+ const serverReg =
+ /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])?$/;
+
+ const { run: getUserInfo } = useRequest(getObdInfo, {
+ onSuccess: ({ success, data }: API.OBResponseServiceInfo_) => {
+ if (success) {
+ form.setFieldsValue({
+ auth: {
+ user: data?.user || undefined,
+ },
+ home_path: data?.user === 'root' ? '/root' : `/home/${data?.user}`,
+ });
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const handleDelete = (id: string) => {
+ setDBConfigData(dbConfigData.filter((item) => item.id !== id));
+ };
+
+ const setData = (dataSource: FormValues) => {
+ let newComponents: API.Components = {};
+ if (selectedConfig.includes('obproxy')) {
+ newComponents.obproxy = {
+ ...(components.obproxy || {}),
+ ...dataSource.obproxy,
+ };
+ }
+ if (selectedConfig.includes('ocp-express') && !lowVersion) {
+ newComponents.ocpexpress = {
+ ...(components.ocpexpress || {}),
+ ...dataSource?.ocpexpress,
+ };
+ }
+ if (selectedConfig.includes('obagent')) {
+ newComponents.obagent = {
+ ...(components.obagent || {}),
+ servers: allOBServer,
+ };
+ }
+ newComponents.oceanbase = {
+ ...(components.oceanbase || {}),
+ topology: dbConfigData?.map((item) => ({
+ ...item,
+ servers: item?.servers?.map((server) => ({ ip: server })),
+ })),
+ };
+ setConfigData({
+ ...configData,
+ components: newComponents,
+ auth: dataSource.auth,
+ home_path: `${
+ dataSource.home_path
+ ? `${dataSource.home_path}${homePathSuffix}`
+ : undefined
+ }`,
+ });
+ };
+
+ const prevStep = () => {
+ const formValues = form.getFieldsValue(true);
+ setData(formValues);
+ setCurrentStep(1);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ };
+
+ const nextStep = () => {
+ const tableFormRefValidate = () => {
+ return tableFormRef?.current?.validateFields().then((values) => {
+ return values;
+ });
+ };
+
+ const formValidate = () => {
+ return form.validateFields().then((values) => {
+ return values;
+ });
+ };
+
+ Promise.all([tableFormRefValidate(), formValidate()]).then((result) => {
+ const formValues = result?.[1];
+ setData(formValues);
+ setCurrentStep(3);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ });
+ };
+
+ const formatOptions = (data: string[]) =>
+ data?.map((item) => ({ label: item, value: item }));
+
+ const getAllServers = (dataSource: API.DBConfig[]) => {
+ const allServersList = dataSource.map((item) => item.servers);
+ let newAllOBServer: string[] = [];
+ allServersList.forEach((item) => {
+ if (item && item.length) {
+ newAllOBServer = [...newAllOBServer, ...item];
+ }
+ });
+ return newAllOBServer;
+ };
+
+ const onValuesChange = (values: FormValues) => {
+ if (values?.auth?.user) {
+ form.setFieldsValue({
+ home_path:
+ values?.auth?.user === 'root'
+ ? '/root'
+ : `/home/${values?.auth?.user}`,
+ });
+ }
+ };
+
+ useEffect(() => {
+ const allServers = getAllServers(dbConfigData);
+ const allZoneServers: any = {};
+ dbConfigData.forEach((item) => {
+ allZoneServers[`${item.id}`] = item.servers;
+ });
+ const obproxyServers = form.getFieldValue(['obproxy', 'servers']);
+ const ocpexpressServers = form.getFieldValue(['ocpexpress', 'servers']);
+ const customOBproxyServers = obproxyServers?.filter(
+ (item: string) =>
+ !(allServers?.includes(item) || item === lastDeleteServer),
+ );
+
+ const customOcpexpressServers = ocpexpressServers?.filter(
+ (item: string) =>
+ !(allServers?.includes(item) || item === lastDeleteServer),
+ );
+
+ let obproxyServersValue;
+ let ocpexpressServersValue;
+ if (allServers?.length) {
+ const checkPass = serverReg.test(allServers[0]);
+ if (!obproxyServers?.length) {
+ obproxyServersValue = [allServers[0]];
+ } else {
+ const newOBproxyServers: string[] = [];
+ obproxyServers?.forEach((item: string) => {
+ if (allServers?.includes(item)) {
+ newOBproxyServers.push(item);
+ }
+ });
+ if (newOBproxyServers?.length) {
+ obproxyServersValue = [...customOBproxyServers, ...newOBproxyServers];
+ } else if (customOBproxyServers?.length) {
+ obproxyServersValue = customOBproxyServers;
+ } else {
+ obproxyServersValue = [allServers[0]];
+ if (!checkPass) {
+ form.setFields([
+ {
+ name: ['obproxy', 'servers'],
+ errors: [
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.SelectTheCorrectObproxyNode',
+ defaultMessage: '请选择正确的 OBProxy 节点',
+ }),
+ ],
+ },
+ ]);
+ }
+ }
+ }
+
+ if (!ocpexpressServers?.length) {
+ ocpexpressServersValue = [allServers[0]];
+ } else {
+ const newOcpexpressServers: string[] = [];
+ ocpexpressServers?.forEach((item: string) => {
+ if (allServers?.includes(item)) {
+ newOcpexpressServers.push(item);
+ }
+ });
+ if (newOcpexpressServers?.length) {
+ ocpexpressServersValue = [
+ ...customOcpexpressServers,
+ ...newOcpexpressServers,
+ ];
+ } else if (customOcpexpressServers?.length) {
+ ocpexpressServersValue = customOcpexpressServers;
+ } else {
+ ocpexpressServersValue = [allServers[0]];
+ if (!checkPass) {
+ form.setFields([
+ {
+ name: ['ocpexpress', 'servers'],
+ errors: [
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.SelectTheCorrectOcpExpress',
+ defaultMessage: '请选择正确的 OCP Express 节点',
+ }),
+ ],
+ },
+ ]);
+ }
+ }
+ }
+ } else {
+ if (!customOBproxyServers?.length) {
+ obproxyServersValue = undefined;
+ } else {
+ obproxyServersValue = customOBproxyServers;
+ }
+ if (!customOcpexpressServers?.length) {
+ ocpexpressServersValue = undefined;
+ } else {
+ ocpexpressServersValue = customOcpexpressServers;
+ }
+ }
+
+ form.setFieldsValue({
+ obproxy: {
+ servers: obproxyServersValue,
+ },
+ ocpexpress: {
+ servers: ocpexpressServersValue,
+ },
+ });
+
+ setAllOBServer(allServers);
+ setAllZoneOBServer(allZoneServers);
+ }, [dbConfigData, lastDeleteServer]);
+
+ useEffect(() => {
+ if (!auth?.user) {
+ getUserInfo();
+ }
+ }, []);
+
+ const nameValidator = ({ field }: any, value: string) => {
+ const currentId = field.split('.')[0];
+ let validtor = true;
+ const reg = /^[a-zA-Z]([a-zA-Z0-9_]{0,30})[a-zA-Z0-9]$/;
+ if (value) {
+ if (reg.test(value)) {
+ dbConfigData.some((item) => {
+ if (currentId !== item.id && item.name === value) {
+ validtor = false;
+ return true;
+ }
+ return false;
+ });
+ } else {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.ItStartsWithALetter',
+ defaultMessage:
+ '以英文字母开头,英文或数字结尾,可包含英文数字和下划线且长度在 2-32 个字符之间',
+ }),
+ ),
+ );
+ }
+ }
+ if (validtor) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.ZoneNameAlreadyOccupied',
+ defaultMessage: 'Zone 名称已被占用',
+ }),
+ ),
+ );
+ };
+
+ const ocpServersValidator = (_: any, value: string[]) => {
+ let validtor = true;
+ if (value?.length > 1) {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.OnlyOneNodeCanBe',
+ defaultMessage: '仅可选择或输入一个节点',
+ }),
+ ),
+ );
+ }
+ if (value && value.length) {
+ value.some((item) => {
+ validtor = serverReg.test(item.trim());
+ return !serverReg.test(item.trim());
+ });
+ }
+ if (validtor) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.SelectTheCorrectOcpExpress',
+ defaultMessage: '请选择正确的 OCP Express 节点',
+ }),
+ ),
+ );
+ };
+
+ const serversValidator = (_: any, value: string[], type: string) => {
+ let validtor = true;
+ if (value && value.length) {
+ value.some((item) => {
+ validtor = serverReg.test(item.trim());
+ return !serverReg.test(item.trim());
+ });
+ }
+ if (validtor) {
+ return Promise.resolve();
+ }
+ if (type === 'OBServer') {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.EnterTheCorrectIpAddress',
+ defaultMessage: '请输入正确的 IP 地址',
+ }),
+ ),
+ );
+ } else {
+ return Promise.reject(
+ new Error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.SelectTheCorrectObproxyNode',
+ defaultMessage: '请选择正确的 OBProxy 节点',
+ }),
+ ),
+ );
+ }
+ };
+
+ const columns: ProColumns[] = [
+ {
+ title: (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.ZoneName',
+ defaultMessage: 'Zone 名称',
+ })}
+
+
+
+
+ >
+ ),
+
+ dataIndex: 'name',
+ width: 224,
+ formItemProps: {
+ rules: [
+ {
+ required: true,
+ whitespace: false,
+ message: intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.ThisItemIsRequired',
+ defaultMessage: '此项是必填项',
+ }),
+ },
+ { validator: nameValidator },
+ ],
+ },
+ },
+ {
+ title: (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.ObserverNodes',
+ defaultMessage: 'OBServer 节点',
+ })}
+
+
+
+
+ >
+ ),
+
+ dataIndex: 'servers',
+ formItemProps: {
+ rules: [
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.ThisItemIsRequired',
+ defaultMessage: '此项是必填项',
+ }),
+ },
+ {
+ validator: (_: any, value: string[]) =>
+ serversValidator(_, value, 'OBServer'),
+ },
+ ],
+ },
+ renderFormItem: (_: any, { isEditable, record }: any) => {
+ return isEditable ? (
+
+ ) : null;
+ },
+ },
+ {
+ title: (
+ <>
+ {intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.RootserverNodes',
+ defaultMessage: 'RootServer 节点',
+ })}
+
+
+
+
+ >
+ ),
+
+ dataIndex: 'rootservice',
+ formItemProps: {
+ rules: [
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.ThisOptionIsRequired',
+ defaultMessage: '此项是必选项',
+ }),
+ },
+ {
+ pattern: serverReg,
+ message: intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.SelectTheCorrectRootserverNode',
+ defaultMessage: '请选择正确的 RootServer 节点',
+ }),
+ },
+ ],
+ },
+ width: 224,
+ renderFormItem: (_: any, { isEditable, record }: any) => {
+ // rootservice options are items entered by the OBServer
+ const options = record?.servers ? formatOptions(record?.servers) : [];
+ return isEditable ? (
+
+ ) : null;
+ },
+ },
+ {
+ title: '',
+ valueType: 'option',
+ width: 20,
+ },
+ ];
+
+ const initialValues: FormValues = {
+ obproxy: {
+ servers: obproxy?.servers?.length ? obproxy?.servers : undefined,
+ },
+ auth: {
+ user: auth?.user || undefined,
+ password: auth?.password || undefined,
+ port: auth?.port || 22,
+ },
+ home_path: initHomePath,
+ };
+ if (!lowVersion) {
+ initialValues.ocpexpress = {
+ servers: ocpexpress?.servers?.length
+ ? [ocpexpress?.servers[0]]
+ : undefined,
+ };
+ }
+
+ return (
+
+
+
+
+ className={styles.nodeEditabletable}
+ columns={columns}
+ rowKey="id"
+ value={dbConfigData}
+ editableFormRef={tableFormRef}
+ onChange={setDBConfigData}
+ recordCreatorProps={{
+ newRecordType: 'dataSource',
+ record: () => ({
+ id: Date.now().toString(),
+ name: `zone${nameIndex}`,
+ }),
+ onClick: () => setNameIndex(nameIndex + 1),
+ creatorButtonText: intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.AddZone',
+ defaultMessage: '新增 Zone',
+ }),
+ }}
+ editable={{
+ type: 'multiple',
+ form: editableForm,
+ editableKeys,
+ actionRender: (row) => {
+ if (dbConfigData?.length === 1) {
+ return (
+
+
+
+
+
+ );
+ }
+ if (!row?.servers?.length && !row?.rootservice) {
+ return (
+ handleDelete(row.id)}
+ style={{ color: '#8592ad' }}
+ />
+ );
+ }
+ return (
+ handleDelete(row.id)}
+ >
+
+
+ );
+ },
+ onValuesChange: (editableItem, recordList) => {
+ if (!editableItem?.id) {
+ return;
+ }
+ const editorServers =
+ editableItem?.servers?.map((item) => item.trim()) || [];
+ const rootService = editableItem?.rootservice;
+ let newRootService = rootService;
+ const serversErrors = editableForm.getFieldError([
+ editableItem?.id,
+ 'servers',
+ ]);
+
+ if (editorServers.length) {
+ if (!rootService || !editorServers.includes(rootService)) {
+ newRootService = editorServers[0];
+ }
+ } else {
+ newRootService = undefined;
+ }
+ editableForm.setFieldsValue({
+ [editableItem?.id]: {
+ rootservice: newRootService,
+ },
+ });
+ if (!newRootService) {
+ tableFormRef?.current?.setFields([
+ {
+ name: [editableItem.id, 'rootservice'],
+ touched: false,
+ },
+ ]);
+ } else if (
+ editorServers?.length === 1 &&
+ serversErrors.length
+ ) {
+ tableFormRef?.current?.setFields([
+ {
+ name: [editableItem.id, 'rootservice'],
+ errors: [
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.SelectTheCorrectRootserverNode',
+ defaultMessage: '请选择正确的 RootServer 节点',
+ }),
+ ],
+ },
+ ]);
+ }
+
+ const beforeChangeServersLength =
+ allZoneOBServer[`${editableItem?.id}`]?.length || 0;
+ if (
+ editorServers &&
+ editorServers.length &&
+ editorServers.length > beforeChangeServersLength
+ ) {
+ if (
+ allOBServer.includes(
+ editorServers[editorServers.length - 1],
+ )
+ ) {
+ message.warning(
+ intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.DoNotEnterDuplicateNodes',
+ defaultMessage: '禁止输入重复节点',
+ }),
+ );
+ const rawData = editorServers.slice(
+ 0,
+ editorServers.length - 1,
+ );
+
+ editableForm.setFieldsValue({
+ [editableItem?.id]: {
+ servers: rawData?.length ? rawData : undefined,
+ },
+ });
+ return;
+ }
+ const errors = editableForm.getFieldError([
+ editableItem?.id,
+ 'servers',
+ ]);
+
+ if (errors?.length) {
+ tableFormRef?.current?.setFields([
+ {
+ name: [editableItem.id, 'servers'],
+ errors: errors,
+ },
+ ]);
+ } else {
+ editableForm.setFieldsValue({
+ [editableItem?.id]: {
+ servers: editorServers,
+ },
+ });
+ }
+ }
+ const newRecordList = recordList.map((item) => {
+ if (item.id === editableItem.id) {
+ return {
+ ...editableItem,
+ rootservice: newRootService,
+ servers: editorServers,
+ };
+ }
+ return item;
+ });
+ setDBConfigData(newRecordList);
+ },
+ onChange: setEditableRowKeys,
+ }}
+ />
+
+ {selectedConfig.includes('ocp-express') ||
+ selectedConfig.includes('obproxy') ? (
+
+
+ {selectedConfig.includes('ocp-express') && !lowVersion ? (
+ {
+ if (value?.length) {
+ form.setFieldsValue({
+ ocpexpress: {
+ servers: [value[value.length - 1]],
+ },
+ });
+ }
+ setOcpServerDropdownVisible(false);
+ },
+ onFocus: () => setOcpServerDropdownVisible(true),
+ onClick: () =>
+ setOcpServerDropdownVisible(!ocpServerDropdownVisible),
+ onBlur: () => setOcpServerDropdownVisible(false),
+ }}
+ validateTrigger={['onBlur']}
+ placeholder={intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.PleaseSelect',
+ defaultMessage: '请选择',
+ })}
+ rules={[
+ {
+ required: true,
+ message: intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.SelectOrEnterOcpExpress',
+ defaultMessage: '请选择或输入 OCP Express 节点',
+ }),
+ },
+ {
+ validator: ocpServersValidator,
+ validateTrigger: 'onBlur',
+ },
+ ]}
+ options={formatOptions(allOBServer)}
+ />
+ ) : null}
+ {selectedConfig.includes('obproxy') && (
+
+ serversValidator(_, value, 'OBProxy'),
+ },
+ ]}
+ options={formatOptions(allOBServer)}
+ />
+ )}
+
+
+ ) : null}
+
+
+
+
+ {locale === 'zh-CN' ? (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.Password',
+ defaultMessage: '密码',
+ })}
+
+
+
+
+ >
+ }
+ fieldProps={{
+ style: { width: 328 },
+ autoComplete: 'new-password',
+ }}
+ placeholder={intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.IfThePasswordFreeConfiguration',
+ defaultMessage: '若各节点间已完成免密配置,则密码可置空',
+ })}
+ />
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.NodeConfig.Password',
+ defaultMessage: '密码',
+ })}
+
+
+
+
+ >
+ }
+ >
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/PreCheck.tsx b/web/src/pages/Obdeploy/PreCheck.tsx
new file mode 100644
index 0000000..61c1acf
--- /dev/null
+++ b/web/src/pages/Obdeploy/PreCheck.tsx
@@ -0,0 +1,9 @@
+import { useModel } from 'umi';
+import CheckInfo from './CheckInfo';
+import PreCheckStatus from './PreCheckStatus';
+
+export default function PreCheck() {
+ const { checkOK } = useModel('global');
+
+ return checkOK ? : ;
+}
diff --git a/web/src/pages/Obdeploy/PreCheckStatus.tsx b/web/src/pages/Obdeploy/PreCheckStatus.tsx
new file mode 100644
index 0000000..00e3d30
--- /dev/null
+++ b/web/src/pages/Obdeploy/PreCheckStatus.tsx
@@ -0,0 +1,813 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import {
+ Space,
+ Button,
+ Progress,
+ Timeline,
+ Checkbox,
+ Typography,
+ Tooltip,
+ Tag,
+ Spin,
+ message,
+ Empty,
+} from 'antd';
+import { ProCard } from '@ant-design/pro-components';
+import {
+ CloseOutlined,
+ QuestionCircleFilled,
+ ReadFilled,
+ CheckCircleFilled,
+ CloseCircleFilled,
+} from '@ant-design/icons';
+import useRequest from '@/utils/useRequest';
+import {
+ preCheck,
+ preCheckStatus,
+ deployAndStartADeployment,
+ createDeploymentConfig,
+ recover,
+} from '@/services/ob-deploy-web/Deployments';
+import { handleQuit, getErrorInfo } from '@/utils';
+import NP from 'number-precision';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+const { Text } = Typography;
+
+const statusColorConfig = {
+ PASSED: 'green',
+ PENDING: 'gray',
+ FAILED: 'red',
+};
+
+let timerScroll: NodeJS.Timer;
+let timerFailed: NodeJS.Timer;
+const initDuration = 3;
+let durationScroll = initDuration;
+let durationFailed = initDuration;
+
+const errCodeUrl = 'https://www.oceanbase.com/product/ob-deployer/error-codes';
+
+export default function PreCheckStatus() {
+ const {
+ setCurrentStep,
+ configData,
+ setCheckOK,
+ handleQuitProgress,
+ getInfoByName,
+ setConfigData,
+ setErrorVisible,
+ setErrorsList,
+ errorsList,
+ } = useModel('global');
+ const oceanbase = configData?.components?.oceanbase;
+ const name = configData?.components?.oceanbase?.appname;
+ const [statusData, setStatusData] = useState({});
+ const [failedList, setFailedList] = useState([]);
+ const [showFailedList, setShowFailedList] = useState([]);
+ const [hasAuto, setHasAuto] = useState(false);
+ const [hasManual, setHasManual] = useState(false);
+ const [onlyManual, setOnlyManual] = useState(false);
+ const [checkFinished, setCheckFinished] = useState(false);
+ const [isScroll, setIsScroll] = useState(false);
+ const [isScrollFailed, setIsScrollFailed] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [checkStatus, setCheckStatus] = useState(true);
+ const [currentPage, setCurrentPage] = useState(true);
+ const [firstErrorTimestamp, setFirstErrorTimestamp] = useState();
+
+ const { run: fetchPreCheckStatus } = useRequest(preCheckStatus, {
+ onSuccess: ({ success, data }: API.OBResponsePreCheckResult_) => {
+ if (success) {
+ let timer: NodeJS.Timer;
+ setStatusData(data || {});
+ if (data?.status === 'RUNNING') {
+ timer = setTimeout(() => {
+ fetchPreCheckStatus({ name });
+ }, 1000);
+ }
+ if (data?.status === 'FAILED') {
+ let errorInfo: API.ErrorInfo = {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.RequestError',
+ defaultMessage: '请求错误',
+ }),
+ desc: data?.message,
+ };
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ setCheckStatus(false);
+ } else {
+ if (data?.all_passed) {
+ setFailedList([]);
+ setShowFailedList([]);
+ } else {
+ const newFailedList =
+ data?.info?.filter((item) => item.result === 'FAILED') || [];
+ newFailedList.forEach((item) => {
+ if (item.recoverable) {
+ setHasAuto(true);
+ } else {
+ setHasManual(true);
+ }
+ });
+ setFailedList(newFailedList);
+ setShowFailedList(newFailedList);
+ }
+ const isFinished = !!data?.total && data?.finished === data?.total;
+ setCheckFinished(isFinished);
+ if (isFinished) {
+ clearTimeout(timer);
+ }
+ if (!isScroll && !isFinished) {
+ setTimeout(() => {
+ const timelineContainer =
+ document.getElementById('timeline-container');
+ const runningItemDom = document.getElementById(
+ 'running-timeline-item',
+ );
+
+ timelineContainer.scrollTop = NP.minus(
+ NP.strip(runningItemDom?.offsetTop),
+ 150,
+ );
+ }, 10);
+ }
+
+ if (!isScrollFailed && !isFinished && failedList) {
+ setTimeout(() => {
+ const failedContainer =
+ document.getElementById('failed-container');
+ if (failedContainer) {
+ failedContainer.scrollTop = NP.strip(
+ failedContainer?.scrollHeight,
+ );
+ }
+ }, 10);
+ }
+ setCheckStatus(true);
+ }
+ if (loading) {
+ setLoading(false);
+ }
+ }
+ },
+ onError: ({ response, data, type }: any) => {
+ const handleError = () => {
+ const errorInfo = getErrorInfo({ response, data, type });
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ };
+ if (response?.status === 504 || (!response && type === 'TypeError')) {
+ const nowTime = Date.now();
+ if (!firstErrorTimestamp) {
+ setFirstErrorTimestamp(nowTime);
+ }
+ if (NP.divide(nowTime - firstErrorTimestamp) > 60000) {
+ handleError();
+ setCheckStatus(false);
+ if (loading) {
+ setLoading(false);
+ }
+ } else {
+ if (currentPage) {
+ setTimeout(() => {
+ fetchPreCheckStatus({ name });
+ }, 1000);
+ }
+ }
+ } else {
+ handleError();
+ }
+ },
+ });
+
+ const { run: handlePreCheck, loading: preCheckLoading } = useRequest(
+ preCheck,
+ {
+ onSuccess: ({ success }: API.OBResponse) => {
+ if (success) {
+ handleStartCheck();
+ }
+ },
+ onError: (e: any) => {
+ setCheckStatus(false);
+ if (loading) {
+ setLoading(false);
+ }
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const { run: handleInstallConfirm } = useRequest(deployAndStartADeployment, {
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const handelCheck = async () => {
+ setLoading(true);
+ try {
+ await handlePreCheck({ name });
+ } catch {
+ setLoading(false);
+ }
+ };
+
+ const { run: handleCreateConfig, loading: createLoading } = useRequest(
+ createDeploymentConfig,
+ {
+ onSuccess: ({ success }: API.OBResponse) => {
+ if (success) {
+ handelCheck();
+ }
+ setLoading(false);
+ },
+ onError: (e: any) => {
+ setCheckStatus(false);
+ if (loading) {
+ setLoading(false);
+ }
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ },
+ );
+
+ const handleRetryCheck = (newConfigData?: any) => {
+ setStatusData({});
+ setFailedList([]);
+ setShowFailedList([]);
+ setCheckFinished(false);
+ let params = { ...configData };
+ if (newConfigData) {
+ params = { ...newConfigData };
+ }
+ setLoading(true);
+ handleCreateConfig({ name: oceanbase?.appname }, { ...params });
+ };
+
+ const { run: handleRecover, loading: recoverLoading } = useRequest(recover, {
+ onSuccess: async ({
+ success,
+ }: API.OBResponseDataListRecoverChangeParameter_) => {
+ if (success) {
+ message.success(
+ intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.AutomaticRepairSucceeded',
+ defaultMessage: '自动修复成功',
+ }),
+ );
+ try {
+ const { success: nameSuccess, data: nameData } = await getInfoByName({
+ name,
+ });
+ if (nameSuccess) {
+ const { config } = nameData;
+ setConfigData(config || {});
+ handleRetryCheck(config);
+ } else {
+ message.error(
+ intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.FailedToObtainConfigurationInformation',
+ defaultMessage: '获取配置信息失败',
+ }),
+ );
+ }
+ } catch (e: any) {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ }
+ }
+ },
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const handleStartCheck = () => {
+ fetchPreCheckStatus({ name });
+ };
+
+ const prevStep = () => {
+ setCheckOK(false);
+ setCurrentStep(3);
+ setCurrentPage(false);
+ setErrorVisible(false);
+ setErrorsList([]);
+ window.scrollTo(0, 0);
+ };
+
+ const handleInstall = async () => {
+ const { success } = await handleInstallConfirm({ name });
+ if (success) {
+ setCurrentStep(5);
+ setCurrentPage(false);
+ setErrorVisible(false);
+ setErrorsList([]);
+ }
+ };
+
+ const handleScrollTimeline = () => {
+ if (!checkFinished) {
+ setIsScroll(true);
+ clearInterval(timerScroll);
+ durationScroll = initDuration;
+ timerScroll = setInterval(() => {
+ if (durationScroll === 0) {
+ clearInterval(timerScroll);
+ setIsScroll(false);
+ durationScroll = initDuration;
+ } else {
+ durationScroll -= 1;
+ }
+ }, 1000);
+ }
+ };
+
+ const handleScrollFailed = () => {
+ if (!checkFinished) {
+ setIsScrollFailed(true);
+ clearInterval(timerFailed);
+ durationFailed = initDuration;
+ timerFailed = setInterval(() => {
+ if (durationFailed === 0) {
+ clearInterval(timerFailed);
+ setIsScrollFailed(false);
+ durationFailed = initDuration;
+ } else {
+ durationFailed -= 1;
+ }
+ }, 1000);
+ }
+ };
+
+ const handleAutoRepair = () => {
+ setHasAuto(false);
+ handleRecover({ name });
+ };
+
+ useEffect(() => {
+ if (onlyManual) {
+ const newShowFailedList = failedList.filter((item) => !item.recoverable);
+ setShowFailedList(newShowFailedList);
+ } else {
+ setShowFailedList(failedList);
+ }
+ }, [onlyManual]);
+
+ useEffect(() => {
+ handelCheck();
+ const timelineContainer = document.getElementById('timeline-container');
+ timelineContainer.onmousewheel = handleScrollTimeline; // ie , chrome
+ timelineContainer?.addEventListener('DOMMouseScroll', handleScrollTimeline); // firefox
+ return () => {
+ timelineContainer.onmousewheel = () => {};
+ timelineContainer?.removeEventListener(
+ 'DOMMouseScroll',
+ handleScrollTimeline,
+ );
+ };
+ }, []);
+
+ useEffect(() => {
+ const addEventFailedContainer = () => {
+ const failedContainer = document.getElementById('failed-container');
+ if (failedList?.length && failedContainer) {
+ if (!failedContainer.onmousewheel) {
+ failedContainer.onmousewheel = handleScrollFailed; // ie , chrome
+ failedContainer?.addEventListener(
+ 'DOMMouseScroll',
+ handleScrollFailed,
+ );
+ // firefox
+ }
+ } else {
+ setTimeout(() => {
+ addEventFailedContainer();
+ }, 3000);
+ }
+ };
+
+ addEventFailedContainer();
+ return () => {
+ const failedContainer = document.getElementById('failed-container');
+ if (failedContainer) {
+ failedContainer.onmousewheel = () => {};
+ failedContainer?.removeEventListener(
+ 'DOMMouseScroll',
+ handleScrollFailed,
+ );
+ }
+ };
+ }, [failedList]);
+
+ let progressStatus = 'active';
+ if (statusData?.status === 'FAILED') {
+ progressStatus = 'exception';
+ } else if (checkFinished) {
+ if (statusData?.all_passed) {
+ progressStatus = 'success';
+ } else {
+ progressStatus = 'exception';
+ }
+ }
+
+ const shape = (
+
+ );
+
+ const checkItemLength = `${statusData?.finished || 0}/${
+ statusData?.total || 0
+ }`;
+ const failedItemLength = failedList?.length;
+
+ return (
+
+
+ handleRetryCheck()}
+ data-aspm-click="c307513.d317293"
+ data-aspm-desc={intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.PreCheckResultReCheck',
+ defaultMessage: '预检查结果-重新检查',
+ })}
+ data-aspm-param={``}
+ data-aspm-expo
+ >
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.ReCheck',
+ defaultMessage: '重新检查',
+ })}
+
+ }
+ headStyle={{ paddingLeft: '16px', paddingRight: '16px' }}
+ >
+
+
+ {loading ? null : (
+ <>
+
+
+ {statusData?.info?.map(
+ (item: API.PreCheckInfo, index: number) => (
+
+ ) : (
+
+ )
+ ) : null
+ }
+ >
+ {item?.name} {item?.server}
+
+ ),
+ )}
+
+ >
+ )}
+
+
+ {hasManual ? (
+ setOnlyManual(e.target.checked)}
+ disabled={!checkFinished || statusData?.all_passed}
+ >
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.OnlyManualFixes',
+ defaultMessage: '只看手动修复项',
+ })}
+
+ ) : null}
+
+
+ }
+ >
+ {showFailedList?.length ? (
+
+ {showFailedList?.map((item, index) => {
+ let reason = '';
+ if (item?.description) {
+ const index = item?.description.indexOf(':');
+ reason = item?.description.substring(
+ index,
+ item?.description.length,
+ );
+ }
+ return (
+
+
+
+
+
+ {item.name}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.Reason',
+ defaultMessage: '原因:',
+ })}
+
+ OBD-{item.code}
+ {' '}
+ {reason}
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.Suggestions',
+ defaultMessage: '建议:',
+ })}
+ {item.recoverable ? (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.AutomaticRepair',
+ defaultMessage: '自动修复',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.ManualRepair',
+ defaultMessage: '手动修复',
+ })}
+
+ )}{' '}
+ {item.advisement?.description}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.LearnMore',
+ defaultMessage: '了解更多方案',
+ })}
+
+
+
+ );
+ })}
+ {!checkFinished ? (
+
{shape}
+ ) : null}
+
+ ) : checkFinished ? (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.GreatNoFailedItems',
+ defaultMessage: '太棒了!无失败项',
+ })}
+
+ }
+ />
+ ) : (
+
+ {shape}
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.PreCheckStatus.NoFailedItemsFoundYet',
+ defaultMessage: '暂未发现失败项',
+ })}
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/ProgressQuit.tsx b/web/src/pages/Obdeploy/ProgressQuit.tsx
new file mode 100644
index 0000000..4d4219b
--- /dev/null
+++ b/web/src/pages/Obdeploy/ProgressQuit.tsx
@@ -0,0 +1,36 @@
+import { intl } from '@/utils/intl';
+import { Card, Empty } from 'antd';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+export default function ProgressQuit() {
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.ProgressQuit.YouHaveSelectedToDeploy',
+ defaultMessage: '您已选择在其他页面进行部署工作,当前页面已停止服务',
+ })}
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/ServerTags.tsx b/web/src/pages/Obdeploy/ServerTags.tsx
new file mode 100644
index 0000000..7370495
--- /dev/null
+++ b/web/src/pages/Obdeploy/ServerTags.tsx
@@ -0,0 +1,157 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState, useRef } from 'react';
+import { Select, Tooltip, Tag } from 'antd';
+
+interface Props {
+ value?: string[];
+ onChange?: (values?: string[]) => void;
+ name?: string;
+ setLastDeleteServer: (value: string) => void;
+}
+
+export default ({
+ value: values,
+ onChange,
+ name,
+ setLastDeleteServer,
+}: Props) => {
+ const [visible, setVisible] = useState(false);
+ const [currentValues, setCurrentValues] = useState(values);
+ const getOverValues = (dataSource?: string[]) => {
+ return dataSource?.length > 3 ? dataSource.splice(3) : [];
+ };
+
+ const [overValuess, setOverValues] = useState(
+ getOverValues([...(values || [])]),
+ );
+
+ const open = useRef();
+ open.current = {
+ input: false,
+ tooltip: false,
+ };
+
+ const onMouseEnterInput = () => {
+ open.current = {
+ ...(open?.current || {}),
+ input: true,
+ };
+ setVisible(true);
+ };
+
+ const onMouseEnterTooltip = () => {
+ open.current = {
+ ...(open?.current || {}),
+ tooltip: true,
+ };
+ setVisible(true);
+ };
+
+ const onMouseLeaveInput = () => {
+ setTimeout(() => {
+ if (!open?.current?.tooltip) {
+ setVisible(false);
+ }
+ }, 300);
+ };
+
+ const onMouseLeaveTooltip = () => {
+ setVisible(false);
+ };
+
+ const addEventTooltipOverlay = () => {
+ const tooltipOverlay = document.querySelector(
+ `.server-tooltip-overlay-${name}`,
+ );
+
+ if (tooltipOverlay) {
+ tooltipOverlay?.addEventListener('mouseenter', onMouseEnterTooltip);
+ tooltipOverlay?.addEventListener('mouseleave', onMouseLeaveTooltip);
+ } else {
+ setTimeout(() => {
+ addEventTooltipOverlay();
+ }, 500);
+ }
+ };
+
+ const addEventInputConatiner = () => {
+ const inputConatiner = document.querySelector(`.server-${name}`);
+ if (inputConatiner) {
+ inputConatiner?.addEventListener('mouseenter', onMouseEnterInput);
+ inputConatiner?.addEventListener('mouseleave', onMouseLeaveInput);
+ } else {
+ setTimeout(() => {
+ addEventInputConatiner();
+ }, 500);
+ }
+ };
+
+ useEffect(() => {
+ const tooltipOverlay = document.querySelector(
+ `.server-tooltip-overlay-${name}`,
+ );
+
+ const inputConatiner = document.querySelector(`.server-${name}`);
+ addEventTooltipOverlay();
+ addEventInputConatiner();
+ return () => {
+ tooltipOverlay?.removeEventListener('mouseenter', onMouseEnterTooltip);
+ tooltipOverlay?.removeEventListener('mouseleave', onMouseLeaveTooltip);
+ inputConatiner?.removeEventListener('mouseenter', onMouseEnterInput);
+ inputConatiner?.removeEventListener('mouseleave', onMouseLeaveInput);
+ };
+ }, []);
+
+ useEffect(() => {
+ setOverValues(getOverValues([...(currentValues || [])]));
+ if (onChange && currentValues?.length !== values?.length) {
+ onChange(currentValues);
+ }
+ }, [currentValues]);
+
+ const onSelectChange = (changeValues?: string[]) => {
+ setCurrentValues(changeValues);
+ setLastDeleteServer('');
+ };
+
+ const onClose = (value: string) => {
+ const newCurrentValues = currentValues?.filter((item) => item !== value);
+ setCurrentValues(newCurrentValues);
+ setLastDeleteServer(value);
+ };
+
+ const getOverContents = () => {
+ return overValuess?.map((item, index) => (
+ onClose(item)}>
+ {item}
+
+ ));
+ };
+
+ return (
+
+
+ );
+};
diff --git a/web/src/pages/Obdeploy/Steps.tsx b/web/src/pages/Obdeploy/Steps.tsx
new file mode 100644
index 0000000..32f302a
--- /dev/null
+++ b/web/src/pages/Obdeploy/Steps.tsx
@@ -0,0 +1,121 @@
+import { intl } from '@/utils/intl';
+import { useModel } from 'umi';
+import { Space } from 'antd';
+import { ClockCircleOutlined, CheckCircleOutlined } from '@ant-design/icons';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+import { useEffect, useState } from 'react';
+
+export default function Steps() {
+ const { currentStep } = useModel('global');
+ const [showBorder, setShowBorder] = useState(false);
+
+ const getIcon = (key: number) => {
+ return currentStep > key ? (
+
+ ) : (
+
+ );
+ };
+
+ const getStepsItems = () => {
+ return [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.Steps.DeploymentConfiguration',
+ defaultMessage: '部署配置',
+ }),
+ key: 1,
+ icon: getIcon(1),
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.Steps.NodeConfiguration',
+ defaultMessage: '节点配置',
+ }),
+ key: 2,
+ icon: getIcon(2),
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.Steps.ClusterConfiguration',
+ defaultMessage: '集群配置',
+ }),
+ key: 3,
+ icon: getIcon(3),
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.Steps.PreCheck',
+ defaultMessage: '预检查',
+ }),
+ key: 4,
+ icon: getIcon(4),
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.pages.components.Steps.Deployment',
+ defaultMessage: '部署',
+ }),
+ key: 5,
+ icon: getIcon(5),
+ },
+ ];
+ };
+
+ const showStepsKeys = [1, 2, 3, 4, 5];
+
+ const handleScroll = () => {
+ if (document.documentElement.scrollTop > 0) {
+ setShowBorder(true);
+ } else {
+ setShowBorder(false);
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener('scroll', handleScroll);
+ }, []);
+
+ return (
+
+ {showStepsKeys.includes(currentStep) ? (
+
+
+
+
+ {getStepsItems().map((item) => (
+
+ {item.icon}
+ item.key ? styles.stepAlreadyTitle : ''}`}
+ >
+ {item.title}
+
+
+ ))}
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/TooltipInput.tsx b/web/src/pages/Obdeploy/TooltipInput.tsx
new file mode 100644
index 0000000..5db16b2
--- /dev/null
+++ b/web/src/pages/Obdeploy/TooltipInput.tsx
@@ -0,0 +1,142 @@
+import { useEffect, useState, useRef } from 'react';
+import { Input, Tooltip } from 'antd';
+
+interface Props {
+ value?: string;
+ onChange?: (value?: string) => void;
+ placeholder: string;
+ name: string;
+ isPassword: boolean;
+ fieldProps: any;
+}
+
+export default ({
+ value,
+ onChange,
+ placeholder,
+ name,
+ isPassword,
+ fieldProps,
+}: Props) => {
+ const [visible, setVisible] = useState(false);
+ const [currentValue, setCurrentValue] = useState(value);
+ const open = useRef();
+ open.current = {
+ input: false,
+ tooltip: false,
+ };
+
+ const onMouseEnterInput = () => {
+ open.current = {
+ ...(open?.current || {}),
+ input: true,
+ };
+ setVisible(true);
+ };
+
+ const onMouseEnterTooltip = () => {
+ open.current = {
+ ...(open?.current || {}),
+ tooltip: true,
+ };
+ setVisible(true);
+ };
+
+ const onMouseLeaveInput = () => {
+ setTimeout(() => {
+ if (!open?.current?.tooltip) {
+ setVisible(false);
+ }
+ }, 300);
+ };
+
+ const onMouseLeaveTooltip = () => {
+ setVisible(false);
+ };
+
+ const addEventTooltipOverlay = () => {
+ const tooltipOverlay = document.querySelector(
+ `.tooltip-input-tooltip-overlay-${name}`,
+ );
+ if (tooltipOverlay) {
+ tooltipOverlay?.addEventListener('mouseenter', onMouseEnterTooltip);
+ tooltipOverlay?.addEventListener('mouseleave', onMouseLeaveTooltip);
+ } else {
+ setTimeout(() => {
+ addEventTooltipOverlay();
+ }, 500);
+ }
+ };
+
+ const addEventInputConatiner = () => {
+ const inputConatiner = document.querySelector(`.tooltip-input-${name}`);
+ if (inputConatiner) {
+ inputConatiner?.addEventListener('mouseenter', onMouseEnterInput);
+ inputConatiner?.addEventListener('mouseleave', onMouseLeaveInput);
+ } else {
+ setTimeout(() => {
+ addEventInputConatiner();
+ }, 500);
+ }
+ };
+
+ useEffect(() => {
+ const tooltipOverlay = document.querySelector(
+ `.tooltip-input-tooltip-overlay-${name}`,
+ );
+ const inputConatiner = document.querySelector(`.tooltip-input-${name}`);
+ addEventTooltipOverlay();
+ addEventInputConatiner();
+ return () => {
+ tooltipOverlay?.removeEventListener('mouseenter', onMouseEnterTooltip);
+ tooltipOverlay?.removeEventListener('mouseleave', onMouseLeaveTooltip);
+ inputConatiner?.removeEventListener('mouseenter', onMouseEnterInput);
+ inputConatiner?.removeEventListener('mouseleave', onMouseLeaveInput);
+ };
+ }, []);
+
+ useEffect(() => {
+ if (onChange) {
+ onChange(currentValue);
+ }
+ }, [currentValue]);
+
+ return (
+ 48 && visible}
+ title={placeholder}
+ overlayClassName={`tooltip-input-tooltip-overlay-${name}`}
+ >
+ {isPassword ? (
+ {
+ setCurrentValue(e?.target?.value);
+ setVisible(false);
+ }}
+ style={{ width: 448 }}
+ onFocus={() => setVisible(false)}
+ {...fieldProps}
+ />
+ ) : (
+ {
+ setCurrentValue(e?.target?.value);
+ setVisible(false);
+ }}
+ autoComplete="off"
+ style={{ width: 448 }}
+ onFocus={() => setVisible(false)}
+ {...fieldProps}
+ />
+ )}
+
+ );
+};
diff --git a/web/src/pages/Obdeploy/Welcome.tsx b/web/src/pages/Obdeploy/Welcome.tsx
new file mode 100644
index 0000000..a7b9ef4
--- /dev/null
+++ b/web/src/pages/Obdeploy/Welcome.tsx
@@ -0,0 +1,111 @@
+import { intl } from '@/utils/intl';
+import { useEffect } from 'react';
+import { useModel } from 'umi';
+import { Button } from 'antd';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import NP from 'number-precision';
+import { getLocale } from 'umi';
+import EnStyles from './indexEn.less';
+import ZhStyles from './indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+export default function Welcome() {
+ const { setCurrentStep, setErrorVisible, setErrorsList } = useModel('global');
+ let Video: any;
+
+ const aspectRatio = NP.divide(2498, 3940).toFixed(10);
+
+ const screenWidth = window.innerWidth * 1.3;
+ let videoWidth = 0;
+ let videoHeight = 0;
+
+ if (screenWidth < 1040) {
+ videoWidth = 1040;
+ } else {
+ videoWidth = screenWidth;
+ }
+
+ videoHeight = Math.ceil(NP.times(videoWidth, aspectRatio));
+
+ useEffect(() => {
+ const welcomeVideo = document.querySelector('.welcome-video');
+ if (welcomeVideo) {
+ Video = videojs(welcomeVideo, {
+ controls: false,
+ autoplay: true,
+ loop: true,
+ preload: 'auto',
+ });
+ }
+ return () => {
+ Video.dispose();
+ };
+ }, []);
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.Welcome.WelcomeToDeploy',
+ defaultMessage: '欢迎您部署',
+ })}
+
+ {locale === 'zh-CN' ? (
+
+ OceanBase
+ {intl.formatMessage({
+ id: 'OBD.pages.components.Welcome.DistributedDatabase',
+ defaultMessage: '分布式数据库',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.Welcome.DistributedDatabase',
+ defaultMessage: '分布式数据库',
+ })}
+
+ )}
+
OceanBase comprehensive database
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/index.less b/web/src/pages/Obdeploy/index.less
new file mode 100644
index 0000000..5b6006b
--- /dev/null
+++ b/web/src/pages/Obdeploy/index.less
@@ -0,0 +1,66 @@
+@backgroundColor: #f5f8ff;
+
+.container {
+ height: 100%;
+}
+
+.englishContainer {
+ font-family: SourceSansPro-Semibold, SourceSansPro-Regular;
+}
+
+.pageHeader {
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 99;
+ height: 48px;
+ padding: 0 20px;
+ overflow: hidden;
+ line-height: 48px;
+ background-color: @backgroundColor;
+ border-bottom: 1px solid #dde4ed;
+ .logo {
+ position: relative;
+ top: -2px;
+ width: 125px;
+ vertical-align: middle;
+ }
+ .logoText {
+ margin-left: 8px;
+ font-size: 14px;
+ }
+ .actionContent {
+ float: right;
+ }
+ .action {
+ color: #5c6b8a;
+ }
+ .actionIcon {
+ margin-right: 10px;
+ }
+}
+
+.pageContainer {
+ min-height: calc(100% - 240px);
+ padding-top: 170px;
+ padding-bottom: 70px;
+ background-color: @backgroundColor;
+ .pageMain {
+ .pageContent {
+ width: 1040px;
+ margin: 0 auto;
+ overflow: auto;
+ }
+ }
+}
+
+.mask {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 100;
+ background: rgba(0, 0, 0, 0.45);
+}
diff --git a/web/src/pages/Obdeploy/index.tsx b/web/src/pages/Obdeploy/index.tsx
new file mode 100644
index 0000000..099e826
--- /dev/null
+++ b/web/src/pages/Obdeploy/index.tsx
@@ -0,0 +1,310 @@
+import { intl } from '@/utils/intl';
+import { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import { Space, notification, ConfigProvider, Dropdown, Modal } from 'antd';
+import {
+ HomeOutlined,
+ ReadOutlined,
+ ProfileOutlined,
+ GlobalOutlined,
+ InfoCircleOutlined,
+} from '@ant-design/icons';
+import useRequest from '@/utils/useRequest';
+import { getErrorInfo, getRandomPassword } from '@/utils';
+import { getDeployment } from '@/services/ob-deploy-web/Deployments';
+import { validateOrSetKeepAliveToken } from '@/services/ob-deploy-web/Common';
+import Welcome from './Welcome';
+import InstallConfig from './InstallConfig';
+import NodeConfig from './NodeConfig';
+import ClusterConfig from './ClusterConfig';
+import PreCheck from './PreCheck';
+import InstallProcess from './InstallProcess';
+import InstallFinished from './InstallFinished';
+import ExitPage from './ExitPage';
+import ProgressQuit from './ProgressQuit';
+import Steps from './Steps';
+import { localeList, localeText } from '@/constants';
+import type { Locale } from 'antd/es/locale-provider';
+import { setLocale, getLocale } from 'umi';
+import enUS from 'antd/es/locale/en_US';
+import zhCN from 'antd/es/locale/zh_CN';
+import theme from '../theme';
+import styles from './index.less';
+
+export default function IndexPage() {
+ const uuid = window.localStorage.getItem('uuid');
+ const locale = getLocale();
+ const {
+ setCurrentStep,
+ setConfigData,
+ currentStep,
+ errorVisible,
+ errorsList,
+ setErrorVisible,
+ setErrorsList,
+ first,
+ setFirst,
+ token,
+ setToken,
+ aliveTokenTimer
+ } = useModel('global');
+ const [lastError, setLastError] = useState({});
+ const [isInstall, setIsInstall] = useState(false);
+ const [localeConfig, setLocalConfig] = useState(
+ locale === 'zh-CN' ? zhCN : enUS,
+ );
+
+ const { run: fetchDeploymentInfo } = useRequest(getDeployment, {
+ onError: (e: any) => {
+ const errorInfo = getErrorInfo(e);
+ setErrorVisible(true);
+ setErrorsList([...errorsList, errorInfo]);
+ },
+ });
+
+ const { run: handleValidateOrSetKeepAliveToken } = useRequest(
+ validateOrSetKeepAliveToken,
+ {
+ onSuccess: ({ success, data }: API.OBResponse) => {
+ if (success) {
+ if (!data) {
+ if (first) {
+ Modal.confirm({
+ className: 'new-page-confirm',
+ title: intl.formatMessage({
+ id: 'OBD.src.pages.ItIsDetectedThatYou',
+ defaultMessage:
+ '检测到您打开了一个新的部署流程页面,请确认是否使用新页面继续部署工作?',
+ }),
+ width: 424,
+ icon: ,
+ content: intl.formatMessage({
+ id: 'OBD.src.pages.UseTheNewPageTo',
+ defaultMessage:
+ '使用新的页面部署,原部署页面将无法再提交任何部署请求',
+ }),
+ onOk: () => {
+ handleValidateOrSetKeepAliveToken({ token, overwrite: true });
+ },
+ onCancel: () => {
+ setCurrentStep(8);
+ },
+ });
+ setTimeout(() => {
+ document.activeElement.blur();
+ }, 100);
+ } else {
+ setCurrentStep(8);
+ }
+ } else if (currentStep > 4) {
+ if (!isInstall) {
+ handleValidateOrSetKeepAliveToken({
+ token: token,
+ is_clear: true,
+ });
+ setIsInstall(true);
+ }
+ } else {
+ aliveTokenTimer.current = setTimeout(() => {
+ handleValidateOrSetKeepAliveToken({ token });
+ }, 1000);
+ }
+ setFirst(false);
+ }
+ },
+ onError: () => {
+ if (currentStep > 4) {
+ handleValidateOrSetKeepAliveToken({ token: token, is_clear: true });
+ } else {
+ aliveTokenTimer.current = setTimeout(() => {
+ handleValidateOrSetKeepAliveToken({ token });
+ }, 1000);
+ }
+ },
+ },
+ );
+
+ const setCurrentLocale = (key: string) => {
+ if (key !== locale) {
+ setLocale(key);
+ window.localStorage.setItem('uuid', token);
+ }
+ setLocalConfig(key === 'zh-CN' ? zhCN : enUS);
+ };
+
+ const getLocaleItems = () => {
+ return localeList.map((item) => ({
+ ...item,
+ label: setCurrentLocale(item.key)}>{item.label},
+ }));
+ };
+
+ const contentConfig = {
+ 1: ,
+ 2: ,
+ 3: ,
+ 4: ,
+ 5: ,
+ 6: ,
+ 7: ,
+ 8: ,
+ };
+
+ useEffect(() => {
+ let newToken = '';
+
+ fetchDeploymentInfo({ task_status: 'INSTALLING' }).then(
+ ({ success, data }: API.OBResponse) => {
+ if (success && data?.items?.length) {
+ setCurrentStep(5);
+ setConfigData({
+ components: { oceanbase: { appname: data?.items[0]?.name } },
+ });
+ } else {
+ if (!token) {
+ if (uuid) {
+ newToken = uuid;
+ } else {
+ newToken = `${Date.now()}${getRandomPassword(true)}`;
+ }
+ setToken(newToken);
+ handleValidateOrSetKeepAliveToken({ token: newToken });
+ } else {
+ handleValidateOrSetKeepAliveToken({ token });
+ }
+ window.localStorage.setItem('uuid', '');
+ }
+ },
+ );
+ const sendBeacon = () => {
+ const url =
+ window.location.origin +
+ '/api/v1/connect/keep_alive?token=' +
+ token +
+ '&is_clear=true';
+ navigator.sendBeacon(url);
+ };
+ window.addEventListener('beforeunload', function (e) {
+ sendBeacon();
+ });
+ }, []);
+
+ useEffect(() => {
+ const newLastError = errorsList?.[errorsList?.length - 1] || null;
+ if (errorVisible) {
+ if (newLastError?.desc !== lastError?.desc) {
+ notification.error({
+ description: newLastError?.desc,
+ message: newLastError?.title,
+ duration: null,
+ });
+ }
+ } else {
+ notification.destroy();
+ }
+ setLastError(newLastError);
+ }, [errorVisible, errorsList, lastError]);
+
+ const containerStyle = {
+ minHeight: `${
+ currentStep < 6 ? 'calc(100% - 240px)' : 'calc(100% - 140px)'
+ }`,
+ paddingTop: `${currentStep < 6 ? '170px' : '70px'}`,
+ };
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.DeploymentWizard',
+ defaultMessage: '部署向导',
+ })}
+
+
+
+ e.preventDefault()}
+ data-aspm-click="c307509.d326700"
+ data-aspm-desc={intl.formatMessage({
+ id: 'OBD.src.pages.TopNavigationSwitchBetweenChinese',
+ defaultMessage: '顶部导航-中英文切换',
+ })}
+ data-aspm-param={``}
+ data-aspm-expo
+ >
+
+ {localeText[locale]}
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.VisitTheOfficialWebsite',
+ defaultMessage: '访问官网',
+ })}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.VisitTheForum',
+ defaultMessage: '访问论坛',
+ })}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.src.pages.HelpCenter',
+ defaultMessage: '帮助中心',
+ })}
+
+
+
+
+
+
+ {contentConfig[currentStep]}
+
+
+
+ );
+}
diff --git a/web/src/pages/Obdeploy/indexEn.less b/web/src/pages/Obdeploy/indexEn.less
new file mode 100644
index 0000000..49831fa
--- /dev/null
+++ b/web/src/pages/Obdeploy/indexEn.less
@@ -0,0 +1,888 @@
+@cardBackgroundColor: #f8fafe;
+@smallSpace: 8px;
+@subTitleColor: #5c6b8a;
+@greyTextColor: #8592ad;
+@backgroundColor: #f5f8ff;
+@welcomeBackgroundColor: #e6ecf1;
+
+.videoContainer {
+ position: relative;
+ width: 100%;
+ height:100%;
+ padding-top: 50px;
+ overflow: hidden;
+ text-align: center;
+ background-color: @welcomeBackgroundColor;
+ :global {
+ .vjs-tech {
+ z-index: 1;
+ }
+ .vjs-text-track-display {
+ background-color: @welcomeBackgroundColor !important;
+ }
+ }
+ .videoContent {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-47%, -43%);
+ .videoActions {
+ position: absolute;
+ top: 35%;
+ left: 16%;
+ z-index: 10;
+ .h1 {
+ margin: 0 !important;
+ font-size: 2.4vw !important;
+ line-height: 4vw;
+ letter-spacing: 0vw;
+ // font-weight: 500;
+ // font-size: 40px;
+ // color: #000000;
+ // letter-spacing: 0;
+ // text-align: left;
+ // line-height: 48px;
+ }
+ .h2 {
+ position: relative;
+ left: -0.2vw;
+ margin: 0 !important;
+ font-weight: 500 !important;
+ font-size: 1vw !important;
+ letter-spacing: 0vw;
+ // font-weight: 500;
+ // font-size: 40px;
+ // color: #000000;
+ // letter-spacing: 0;
+ // text-align: left;
+ // line-height: 48px;
+ .letter {
+ font-family: SourceSansPro-Semibold, SourceSansPro-Regular;
+ }
+ }
+
+ .desc {
+ color: #000000;
+ font-weight: 400;
+ font-size: 18px;
+ line-height: 24px;
+ letter-spacing: 0;
+ text-align: left;
+ opacity: 0.65;
+ }
+ .startButtonContainer {
+ text-align: center;
+ .startButton {
+ height: 2.8vw !important;
+ margin-top: 1.5vw !important;
+ padding: 0.4vw 1vw !important;
+ font-size: 1.3vw !important;
+ border-radius: 0.6vw;
+ }
+ }
+ }
+ .video {
+ width: 100%;
+ background-color: @welcomeBackgroundColor;
+ }
+ :global {
+ .vjs-poster {
+ top: 1px !important;
+ left: 1px !important;
+ background-position: 0 0 !important;
+ background-size: 100% !important;
+ }
+ }
+ }
+}
+
+.spaceWidth {
+ width: 100%;
+}
+
+.stepsContainer {
+ position: fixed;
+ top: 49px;
+ right: 0;
+ left: 0;
+ z-index: 99;
+ background-color: #f5f8ff;
+ .stepsContent {
+ position: relative;
+ width: 900px;
+ margin: 0 auto;
+ padding-top: 30px;
+ .stepsBackground {
+ position: absolute;
+ top: 40px;
+ left: 83px;
+ z-index: 0;
+ width: 725px;
+ height: 12px;
+ background-color: #e2e8f3;
+ }
+ .stepsBackgroundProgress {
+ height: 12px;
+ background-image: linear-gradient(-45deg, #006aff 0%, #5189fb 100%);
+ }
+ .stepItem {
+ position: relative;
+ z-index: 10;
+ display: inline-block;
+ width: 180px;
+ height: 60px;
+ color: #5c6b8a;
+ text-align: center;
+ .stepTitle {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 184px;
+ white-space: nowrap;
+ &.stepAlreadyTitle {
+ color: #132039;
+ }
+ &.stepCurrentTitle {
+ color: #0055ff;
+ font-weight: 500;
+ }
+ }
+
+ .stepIcon {
+ display: inline-block;
+ width: 18px;
+ margin-top: 5px;
+ color: #417cf6;
+ font-size: 20px;
+ text-align: center;
+ background-color: #fff;
+ border-radius: 50%;
+ & > svg {
+ margin-left: -1px !important;
+ }
+ &.stepWaitIcon {
+ color: #cdd5e4;
+ }
+ &.stepCurrentIcon {
+ width: 28px;
+ margin-top: 0;
+ color: #417cf6 !important;
+ font-size: 30px;
+ }
+ }
+ }
+ }
+}
+
+.draftModal {
+ .modalTitleIcon {
+ margin-right: 16px;
+ font-size: 24px;
+ }
+ .modalTitle {
+ vertical-align: top;
+ }
+ :global {
+ .ant-modal-content {
+ padding: 32px !important;
+ }
+ .ant-modal-title {
+ color: #132039 !important;
+ font-weight: 500 !important;
+ line-height: 24px !important;
+ }
+ }
+}
+
+.deleteDeployContent {
+ height: 170px;
+ padding-top: 30px;
+ text-align: center;
+ .deleteDeployText {
+ font-weight: 500;
+ font-size: 16px;
+ }
+ .deleteDeployProgress {
+ margin-top: 32px;
+ }
+}
+
+.checkInfoSpace {
+ :global {
+ .ant-col {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+ .ant-col.ant-col-12:nth-child(2n + 1) {
+ padding-right: @smallSpace !important;
+ }
+ .ant-col.ant-col-12:nth-child(2n) {
+ padding-left: @smallSpace !important;
+ }
+ }
+}
+
+.pageCard {
+ box-shadow: 0 2px 4px 0 rgba(19, 32, 57, 0.02),
+ 0 1px 6px -1px rgba(19, 32, 57, 0.02), 0 1px 2px 0 rgba(19, 32, 57, 0.03);
+ :global {
+ .ant-space-item {
+ .ant-col {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+ }
+ }
+}
+
+.titleExtra {
+ margin-left: 12px;
+ color: @greyTextColor;
+ font-weight: 400 !important;
+ font-size: 14px !important;
+}
+
+.deployTypeCardContailer {
+ display: inline-block;
+ width: 488px;
+ :global {
+ .ant-card-body {
+ line-height: 22px !important;
+ }
+ }
+ .deployTypeCard {
+ font-size: 18px;
+ text-align: center;
+ cursor: pointer;
+ &.selectedDeployTypeCard {
+ font-weight: 500;
+ border: 2px solid #006aff;
+ }
+ }
+
+ .typeTag {
+ vertical-align: top;
+ }
+
+ .typeDesc {
+ display: inline-block;
+ height: 44px;
+ margin-top: @smallSpace;
+ color: @greyTextColor;
+ visibility: hidden;
+ &.selectedTypeDesc {
+ visibility: visible;
+ }
+ }
+}
+
+.iconContainer {
+ position: relative;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ margin-left: @smallSpace;
+ color: #fff;
+ font-size: 12px;
+ line-height: 22px;
+ vertical-align: middle;
+ border-radius: 50%;
+ .icon {
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ }
+}
+
+.popupClassName {
+ width: 500px !important;
+ .localTag {
+ float: right;
+ color: #006aff;
+ font-weight: normal !important;
+ }
+}
+
+.learnMore {
+ float: right;
+ color: #006aff !important;
+}
+
+.disabledDel {
+ color: #cdd5e4;
+ cursor: not-allowed;
+}
+
+.inputText {
+ width: 216px;
+}
+
+.componentCard {
+ margin-bottom: 16px !important;
+ background-color: @cardBackgroundColor !important;
+ :global {
+ .ant-pro-card-body {
+ padding: 0 !important;
+ }
+ }
+ &.disabledCard {
+ opacity: 0.4;
+ }
+ &:last-child {
+ margin-bottom: 0 !important;
+ }
+}
+
+.disabledRow {
+ opacity: 0.4;
+}
+
+.viewRule {
+ position: absolute;
+ top: 92px;
+ left: 360px;
+}
+
+.modeExtra {
+ position: absolute;
+ top: 143px;
+ width: 100%;
+ height: 22px;
+ .modeExtraContent {
+ position: absolute;
+ top: -22px;
+ color: @greyTextColor;
+ line-height: 22px;
+ }
+}
+
+.moreSwitch {
+ color: #132039;
+ font-weight: 500;
+ font-size: 16px;
+}
+
+.infoSubCard {
+ overflow: hidden;
+ border-radius: 8px;
+ .pwdIcon {
+ position: absolute;
+ top: 2px;
+ right: 0;
+ color: #8592ad;
+ font-size: 17px;
+ }
+ :global {
+ .ant-pro-card .ant-pro-card-body {
+ padding-top: 4px !important;
+ }
+
+ .ant-pro-card-body,
+ .ant-pro-card-header {
+ background-color: @cardBackgroundColor !important;
+ }
+ .ant-pro-card-title {
+ color: @subTitleColor !important ;
+ font-weight: normal !important;
+ font-size: 14px !important;
+ }
+ .ant-pro-card-body {
+ color: #132039 !important;
+ }
+ .ant-pro-card-col.ant-pro-card-split-vertical {
+ border-inline-end: none !important;
+ .ant-pro-card::before {
+ position: absolute;
+ top: 50%;
+ width: 1px;
+ height: 48px;
+ background-color: #e8eaf3;
+ transform: translateY(-50%);
+ transition: #e8eaf3 0.3s;
+ content: '';
+ inset-inline-end: 0;
+ }
+ }
+ }
+}
+
+.inlineFormItem {
+ margin-block: -5px;
+ margin-inline: 0px;
+}
+
+.preCheckSubCard {
+ height: 400px;
+ background-color: @cardBackgroundColor;
+ :global {
+ .ant-pro-card-body {
+ padding-top: 0 !important;
+ overflow: auto;
+ }
+ .ant-pro-card-header {
+ padding-top: 12px !important;
+ padding-bottom: 14px !important;
+ border-bottom: 1px solid #e2e8f3 !important;
+ }
+ .ant-pro-card-header .ant-pro-card-title {
+ font-weight: normal !important;
+ }
+ }
+
+ .preCheckProgress {
+ position: absolute;
+ top: 42px;
+ left: 0px;
+ z-index: 9;
+ :global {
+ .ant-progress-inner {
+ height: 1px;
+ overflow: visible !important;
+ background-color: #e2e8f3 !important;
+ .ant-progress-bg {
+ height: 2px !important;
+ border-radius: 0 !important;
+ }
+ }
+ }
+ }
+
+ .preCheckBtn {
+ height: 28px !important;
+ padding: 2px 8px !important;
+ }
+
+ :global {
+ .ant-progress-status-active {
+ .ant-progress-bg::after {
+ position: absolute;
+ top: -2px;
+ right: -4px;
+ display: block;
+ width: 6px;
+ height: 6px;
+ background-color: #006aff;
+ border-radius: 50%;
+ content: '';
+ }
+ }
+ }
+
+ .timelineContainer {
+ position: relative;
+ height: 322px;
+ padding-top: 15px;
+ overflow: auto;
+
+ :global {
+ .ant-timeline-item-last {
+ height: 10px !important;
+ padding-bottom: 0 !important;
+ .ant-timeline-item-content {
+ min-height: 22px !important;
+ }
+ }
+ }
+ }
+
+ .failedContainer {
+ position: relative;
+ height: 322px;
+ overflow: auto;
+ }
+
+ .failedItem {
+ padding: 12px 24px;
+ border-top: 1px solid #e2e8f3;
+ &:first-child {
+ border-top: none;
+ }
+ .preCheckLearnMore {
+ display: inline-block;
+ margin-top: 6px;
+ margin-left: 102px;
+ }
+ }
+ .failedItemIcon {
+ position: relative;
+ width: 10px;
+ height: 10px;
+ margin-left: 0px;
+ font-size: 6px;
+ .icon {
+ top: 2px;
+ left: 2px;
+ }
+ }
+}
+
+@keyframes animate1 {
+ 0% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+@keyframes animate2 {
+ 25% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+@keyframes animate3 {
+ 50% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+@keyframes animate4 {
+ 75% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+.installSubCard,
+.reportLog {
+ background-color: @cardBackgroundColor;
+}
+.installLog,
+.reportLog {
+ margin: 0 !important;
+ overflow: auto;
+ color: #8592ad;
+}
+.installLog {
+ height: 360px;
+}
+.reportLog {
+ max-width: 1008px;
+ min-height: 100px;
+ max-height: 360px;
+ padding: 16px;
+ border-radius: 8px;
+}
+
+.preLoading {
+ padding-top: 130px;
+ text-align: center;
+ .shapeContainer {
+ position: relative;
+ left: -6px;
+ display: inline-block !important;
+ }
+ .desc {
+ margin-top: 16px;
+ color: #8592ad;
+ }
+}
+
+.shapeContainer {
+ display: flex;
+ height: 16px;
+ margin: 10px 0 auto;
+ .shape {
+ position: relative;
+ display: inline-block;
+ width: 5px;
+ height: 5px;
+ margin-left: 10px;
+ background-color: #b3ccff;
+ border-radius: 100%;
+ &:before {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 5px;
+ height: 5px;
+ background-color: #b3ccff;
+ border-radius: 100%;
+ animation-fill-mode: both;
+ content: '';
+ }
+ }
+ .shape:nth-child(1) {
+ animation-delay: -0.16s;
+ &::before {
+ animation: animate1 1.4s infinite linear;
+ }
+ }
+ .shape:nth-child(2) {
+ animation-delay: -0.16s;
+ &::before {
+ animation: animate2 1.4s infinite linear;
+ }
+ }
+ .shape:nth-child(3) {
+ animation-delay: -0.32s;
+ &::before {
+ animation: animate3 1.4s infinite linear;
+ }
+ }
+ .shape:nth-child(4) {
+ animation-delay: -0.16s;
+ &::before {
+ animation: animate4 1.4s infinite linear;
+ }
+ }
+}
+
+.reportTooltip {
+ max-width: 520px;
+ :global {
+ .ant-tooltip-arrow {
+ color: #fff;
+ --antd-arrow-background-color: #fff;
+ }
+ .ant-tooltip-inner {
+ width: 520px;
+ max-height: 230px;
+ padding: 16px;
+ overflow: auto;
+ color: #132039;
+ background-color: #fff;
+ }
+ }
+}
+
+.pageFooterContainer {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 99;
+ padding: 16px;
+ background-color: #f5f8ff;
+ border-top: 1px solid #dde4ed;
+ .pageFooter {
+ width: 1040px;
+ margin: 0 auto;
+ overflow: hidden;
+ .foolterAction {
+ float: right;
+ }
+ }
+}
+
+@media screen and (max-width: 1040px) {
+ .pageFooterContainer {
+ .pageFooter {
+ width: 100% !important;
+ }
+ }
+}
+
+.componentTable,
+.nodeEditabletable {
+ :global {
+ .ant-table,
+ .ant-table-wrapper .ant-table-thead > tr > th,
+ .ant-table-thead > tr > th {
+ background-color: rgba(0, 0, 0, 0) !important;
+ }
+ .ant-table {
+ color: #132039 !important;
+ }
+ .ant-table-thead > tr > th {
+ color: @subTitleColor !important;
+ font-weight: normal !important;
+ }
+ }
+}
+
+.componentTable {
+ :global {
+ .ant-table
+ .ant-table-tbody
+ tr:nth-child(2n + 1):not(.ant-table-placeholder)
+ td {
+ background-color: rgba(0, 0, 0, 0) !important ;
+ }
+ }
+}
+
+.componentTable,
+.nodeEditabletable,
+.connectTable {
+ :global {
+ .ant-table-cell::before {
+ width: 0 !important;
+ }
+ .ant-table-thead > tr > th {
+ padding-bottom: 0 !important;
+ border-bottom: none !important;
+ }
+ }
+}
+
+.nodeEditabletable {
+ :global {
+ .ant-pro-card-body {
+ padding-inline: 0 !important;
+ padding-block: 0 !important;
+ }
+ .ant-table-cell {
+ padding-top: 0 !important;
+ padding-left: 0 !important;
+ }
+ .ant-btn-dashed {
+ width: calc(100% - 36px) !important;
+ margin-top: 4px !important;
+ color: @greyTextColor !important;
+ }
+ .ant-table
+ .ant-table-tbody
+ tr:nth-child(2n):not(.ant-table-placeholder)
+ td {
+ background-color: #fff !important;
+ }
+ .ant-table .ant-table-tbody tr td {
+ padding-top: 11px !important;
+ padding-bottom: 11px !important;
+ }
+ }
+}
+
+.connectTable {
+ :global {
+ .ant-table-thead > tr > th {
+ padding-left: 0 !important;
+ }
+ }
+}
+
+.collapsibleCard {
+ :global {
+ .ant-pro-card-title .anticon {
+ position: absolute;
+ top: 21px;
+ left: 180px;
+ }
+ }
+}
+
+.paramterSelect {
+ :global {
+ .ant-select-selector {
+ height: 28px !important;
+ }
+ .ant-select-selection-item {
+ line-height: 26px !important;
+ }
+ }
+}
+
+.paramterInput {
+ width: 126px !important;
+ :global {
+ .ant-input {
+ height: 28px;
+ }
+ }
+}
+
+.progressEffectContainer {
+ position: relative;
+ height: 150px;
+ .deployTitle {
+ position: absolute;
+ left: calc(50% - 70px);
+ font-weight: 500;
+ font-size: 16px;
+ }
+ .computer {
+ position: absolute;
+ top: 20px;
+ left: 195px;
+ .computerAnimate {
+ width: 150px;
+ }
+ }
+ .progress {
+ position: absolute;
+ top: 65px;
+ left: 327px;
+ .progressVedio {
+ width: 285px;
+ height: 11px;
+ background-color: #fff;
+ :global {
+ .vjs-text-track-display {
+ background-color: #fff !important;
+ }
+ .vjs-poster {
+ top: 1px !important;
+ left: 1px !important;
+ background-position: 0 0 !important;
+ background-size: 100% !important;
+ }
+ .vjs-modal-dialog {
+ background: #fff !important;
+ }
+ .vjs-error-display:before {
+ content: '' !important;
+ }
+ }
+ }
+ .progressCover {
+ position: absolute;
+ top: 2.5px;
+ right: 2px;
+ height: 5.5px;
+ background: #fff;
+ border-radius: 0 5px 5px 0;
+ }
+ }
+
+ .spaceman {
+ position: absolute;
+ top: 25px;
+ left: 310px;
+ .spacemanAnimate {
+ width: 320px;
+ }
+ }
+ .database {
+ position: absolute;
+ top: 20px;
+ right: 190px;
+ .sqlAnimate {
+ width: 150px;
+ }
+ }
+}
+
+.deploymentName {
+ position: absolute;
+ bottom: 40px;
+ left: 50%;
+ color: @greyTextColor;
+ font-size: 12px;
+ transform: translateX(-50%);
+}
+
+.exitPage {
+ height: 680px;
+ margin-top: 32px;
+ padding-top: 160px;
+ text-align: center;
+ border: none;
+ .exitPageText {
+ font-size: 18px !important;
+ }
+}
+
+.commandTooltip {
+ :global {
+ .ant-tooltip-inner {
+ width: 400px;
+ }
+ }
+}
+
+:global {
+ .english-container {
+ }
+}
diff --git a/web/src/pages/Obdeploy/indexZh.less b/web/src/pages/Obdeploy/indexZh.less
new file mode 100644
index 0000000..f4048f8
--- /dev/null
+++ b/web/src/pages/Obdeploy/indexZh.less
@@ -0,0 +1,890 @@
+@cardBackgroundColor: #f8fafe;
+@smallSpace: 8px;
+@subTitleColor: #5c6b8a;
+@greyTextColor: #8592ad;
+@backgroundColor: #f5f8ff;
+@welcomeBackgroundColor: #e6ecf1;
+
+.videoContainer {
+ position: relative;
+ width: 100%;
+ height: calc(100% - 50px);
+ box-sizing: content-box;
+ padding-top: 50px;
+ overflow: hidden;
+ text-align: center;
+ background-color: @welcomeBackgroundColor;
+ :global {
+ .vjs-tech {
+ z-index: 1;
+ }
+ .vjs-text-track-display {
+ background-color: @welcomeBackgroundColor !important;
+ }
+ }
+ .videoContent {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-47%, -43%);
+ .videoActions {
+ position: absolute;
+ top: 35%;
+ left: 18%;
+ z-index: 10;
+ .h1 {
+ margin: 0 !important;
+ // font-size: 6vw !important;
+ // line-height: 6vw;
+ // letter-spacing: 0.7vw;
+ font-weight: 500;
+ font-size: 40px;
+ color: #000000;
+ letter-spacing: 0;
+ text-align: left;
+ line-height: 48px;
+ }
+ .h2 {
+ position: relative;
+ left: -0.2vw;
+ margin: 0 !important;
+ // font-weight: 400 !important;
+ // font-size: 2.8vw !important;
+ // letter-spacing: 0.1vw;
+ font-weight: 500;
+ font-size: 40px;
+ color: #000000;
+ letter-spacing: 0;
+ text-align: left;
+ line-height: 48px;
+ .letter {
+ font-family: SourceSansPro-Semibold, SourceSansPro-Regular;
+ }
+ }
+ .desc {
+ color: #000000;
+ font-weight: 400;
+ font-size: 18px;
+ line-height: 24px;
+ letter-spacing: 0;
+ text-align: left;
+ opacity: 0.65;
+ }
+ .startButtonContainer {
+ display: flex;
+ text-align: center;
+ .startButton {
+ height: 2.8vw !important;
+ margin-top: 2.5vw !important;
+ padding: 0.4vw 1vw !important;
+ font-size: 1.3vw !important;
+ border-radius: 0.6vw;
+ }
+ }
+ }
+ .video {
+ width: 100%;
+ background-color: @welcomeBackgroundColor;
+ }
+ :global {
+ .vjs-poster {
+ top: 1px !important;
+ left: 1px !important;
+ background-position: 0 0 !important;
+ background-size: 100% !important;
+ }
+ }
+ }
+}
+
+.spaceWidth {
+ width: 100%;
+}
+
+.stepsContainer {
+ position: fixed;
+ top: 49px;
+ right: 0;
+ left: 0;
+ z-index: 99;
+ background-color: #f5f8ff;
+ .stepsContent {
+ position: relative;
+ width: 700px;
+ margin: 0 auto;
+ padding-top: 30px;
+ .stepsBackground {
+ position: absolute;
+ top: 40px;
+ left: 30px;
+ z-index: 0;
+ width: 635px;
+ height: 12px;
+ background-color: #e2e8f3;
+ }
+ .stepsBackgroundProgress {
+ height: 12px;
+ background-image: linear-gradient(-45deg, #006aff 0%, #5189fb 100%);
+ }
+ .stepItem {
+ position: relative;
+ z-index: 10;
+ display: inline-block;
+ width: 60px;
+ height: 60px;
+ color: #5c6b8a;
+ text-align: center;
+ .stepTitle {
+ position: absolute;
+ bottom: 0;
+ left: -25px;
+ width: 110px;
+ &.stepAlreadyTitle {
+ color: #132039;
+ }
+ &.stepCurrentTitle {
+ color: #0055ff;
+ font-weight: 500;
+ }
+ }
+
+ .stepIcon {
+ display: inline-block;
+ width: 18px;
+ margin-top: 5px;
+ color: #417cf6;
+ font-size: 20px;
+ text-align: center;
+ background-color: #fff;
+ border-radius: 50%;
+ & > svg {
+ margin-left: -1px !important;
+ }
+ &.stepWaitIcon {
+ color: #cdd5e4;
+ }
+ &.stepCurrentIcon {
+ width: 28px;
+ margin-top: 0;
+ color: #417cf6 !important;
+ font-size: 30px;
+ }
+ }
+ }
+ }
+}
+
+.draftModal {
+ .modalTitleIcon {
+ margin-right: 16px;
+ font-size: 24px;
+ }
+ .modalTitle {
+ vertical-align: top;
+ }
+ :global {
+ .ant-modal-content {
+ padding: 32px !important;
+ }
+ .ant-modal-title {
+ color: #132039 !important;
+ font-weight: 500 !important;
+ line-height: 24px !important;
+ }
+ }
+}
+
+.deleteDeployContent {
+ height: 170px;
+ padding-top: 30px;
+ text-align: center;
+ .deleteDeployText {
+ font-weight: 500;
+ font-size: 16px;
+ }
+ .deleteDeployProgress {
+ margin-top: 32px;
+ }
+}
+
+.checkInfoSpace {
+ :global {
+ .ant-col {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+ .ant-col.ant-col-12:nth-child(2n + 1) {
+ padding-right: @smallSpace !important;
+ }
+ .ant-col.ant-col-12:nth-child(2n) {
+ padding-left: @smallSpace !important;
+ }
+ }
+}
+
+.pageCard {
+ box-shadow: 0 2px 4px 0 rgba(19, 32, 57, 0.02),
+ 0 1px 6px -1px rgba(19, 32, 57, 0.02), 0 1px 2px 0 rgba(19, 32, 57, 0.03);
+ :global {
+ .ant-space-item {
+ .ant-col {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+ }
+ }
+}
+
+.titleExtra {
+ margin-left: 12px;
+ color: @greyTextColor;
+ font-weight: 400 !important;
+ font-size: 14px !important;
+}
+
+.deployTypeCardContailer {
+ display: inline-block;
+ width: 216px;
+ :global {
+ .ant-card-body {
+ line-height: 22px !important;
+ }
+ }
+ .deployTypeCard {
+ font-size: 18px;
+ text-align: center;
+ cursor: pointer;
+ &.selectedDeployTypeCard {
+ font-weight: 500;
+ border: 2px solid #006aff;
+ }
+ }
+
+ .typeTag {
+ vertical-align: top;
+ }
+
+ .typeDesc {
+ display: inline-block;
+ margin-top: @smallSpace;
+ color: @greyTextColor;
+ visibility: hidden;
+ &.selectedTypeDesc {
+ visibility: visible;
+ }
+ }
+}
+
+.iconContainer {
+ position: relative;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ margin-left: @smallSpace;
+ color: #fff;
+ font-size: 12px;
+ line-height: 22px;
+ vertical-align: middle;
+ border-radius: 50%;
+ .icon {
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ }
+}
+
+.popupClassName {
+ width: 400px !important;
+
+ .localTag {
+ float: right;
+ color: #006aff;
+ font-weight: normal !important;
+ }
+}
+
+.learnMore {
+ float: right;
+ color: #006aff !important;
+}
+
+.disabledDel {
+ color: #cdd5e4;
+ cursor: not-allowed;
+}
+
+.inputText {
+ width: 216px;
+}
+
+.componentCard {
+ margin-bottom: 16px !important;
+ background-color: @cardBackgroundColor !important;
+ :global {
+ .ant-pro-card-body {
+ padding: 0 !important;
+ }
+ }
+ &.disabledCard {
+ opacity: 0.4;
+ }
+ &:last-child {
+ margin-bottom: 0 !important;
+ }
+}
+
+.disabledRow {
+ opacity: 0.4;
+}
+
+.viewRule {
+ position: absolute;
+ top: 92px;
+ left: 209px;
+}
+
+.modeExtra {
+ position: absolute;
+ top: 143px;
+ width: 100%;
+ height: 22px;
+ .modeExtraContent {
+ position: absolute;
+ top: -22px;
+ color: @greyTextColor;
+ line-height: 22px;
+ }
+}
+
+.moreSwitch {
+ color: #132039;
+ font-weight: 500;
+ font-size: 16px;
+}
+
+.infoSubCard {
+ overflow: hidden;
+ border-radius: 8px;
+ .pwdIcon {
+ position: absolute;
+ top: 2px;
+ right: 0;
+ color: #8592ad;
+ font-size: 17px;
+ }
+ :global {
+ .ant-pro-card .ant-pro-card-body {
+ padding-top: 4px !important;
+ }
+
+ .ant-pro-card-body,
+ .ant-pro-card-header {
+ background-color: @cardBackgroundColor !important;
+ }
+ .ant-pro-card-title {
+ color: @subTitleColor !important ;
+ font-weight: normal !important;
+ font-size: 14px !important;
+ }
+ .ant-pro-card-body {
+ color: #132039 !important;
+ }
+ .ant-pro-card-col.ant-pro-card-split-vertical {
+ border-inline-end: none !important;
+ .ant-pro-card::before {
+ position: absolute;
+ top: 50%;
+ width: 1px;
+ height: 48px;
+ background-color: #e8eaf3;
+ transform: translateY(-50%);
+ transition: #e8eaf3 0.3s;
+ content: '';
+ inset-inline-end: 0;
+ }
+ }
+ }
+}
+
+.inlineFormItem {
+ margin-block: -5px;
+ margin-inline: 0px;
+}
+
+.preCheckSubCard {
+ height: 400px;
+ background-color: @cardBackgroundColor;
+ :global {
+ .ant-pro-card-body {
+ padding-top: 0 !important;
+ overflow: auto;
+ }
+ .ant-pro-card-header {
+ padding-top: 12px !important;
+ padding-bottom: 14px !important;
+ border-bottom: 1px solid #e2e8f3 !important;
+ }
+ .ant-pro-card-header .ant-pro-card-title {
+ font-weight: normal !important;
+ }
+ }
+
+ .preCheckProgress {
+ position: absolute;
+ top: 42px;
+ left: 0px;
+ z-index: 9;
+ :global {
+ .ant-progress-inner {
+ height: 1px;
+ overflow: visible !important;
+ background-color: #e2e8f3 !important;
+ .ant-progress-bg {
+ height: 2px !important;
+ border-radius: 0 !important;
+ }
+ }
+ }
+ }
+
+ .preCheckBtn {
+ height: 28px !important;
+ padding: 2px 8px !important;
+ }
+
+ :global {
+ .ant-progress-status-active {
+ .ant-progress-bg::after {
+ position: absolute;
+ top: -2px;
+ right: -4px;
+ display: block;
+ width: 6px;
+ height: 6px;
+ background-color: #006aff;
+ border-radius: 50%;
+ content: '';
+ }
+ }
+ }
+
+ .timelineContainer {
+ position: relative;
+ height: 322px;
+ padding-top: 15px;
+ overflow: auto;
+
+ :global {
+ .ant-timeline-item-last {
+ height: 10px !important;
+ padding-bottom: 0 !important;
+ .ant-timeline-item-content {
+ min-height: 22px !important;
+ }
+ }
+ }
+ }
+
+ .failedContainer {
+ position: relative;
+ height: 322px;
+ overflow: auto;
+ }
+
+ .failedItem {
+ padding: 12px 24px;
+ border-top: 1px solid #e2e8f3;
+ &:first-child {
+ border-top: none;
+ }
+ .preCheckLearnMore {
+ display: inline-block;
+ margin-top: 6px;
+ margin-left: 60px;
+ }
+ }
+ .failedItemIcon {
+ position: relative;
+ top: -1px;
+ width: 10px;
+ height: 10px;
+ margin-left: 0px;
+ font-size: 6px;
+ .icon {
+ top: 2px;
+ left: 2px;
+ }
+ }
+}
+
+@keyframes animate1 {
+ 0% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+@keyframes animate2 {
+ 25% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+@keyframes animate3 {
+ 50% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+@keyframes animate4 {
+ 75% {
+ background-color: #1677ff;
+ transform: scale(1.5);
+ }
+}
+
+.installSubCard,
+.reportLog {
+ background-color: @cardBackgroundColor;
+}
+.installLog,
+.reportLog {
+ margin: 0 !important;
+ overflow: auto;
+ color: #8592ad;
+}
+.installLog {
+ height: 360px;
+}
+.reportLog {
+ max-width: 1008px;
+ min-height: 100px;
+ max-height: 360px;
+ padding: 16px;
+ border-radius: 8px;
+}
+
+.preLoading {
+ padding-top: 130px;
+ text-align: center;
+ .shapeContainer {
+ position: relative;
+ left: -6px;
+ display: inline-block !important;
+ }
+ .desc {
+ margin-top: 16px;
+ color: #8592ad;
+ }
+}
+
+.shapeContainer {
+ display: flex;
+ height: 16px;
+ margin: 10px 0 auto;
+ .shape {
+ position: relative;
+ display: inline-block;
+ width: 4px;
+ height: 4px;
+ margin-left: 10px;
+ background-color: #b3ccff;
+ border-radius: 100%;
+ &:before {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 4px;
+ height: 4px;
+ background-color: #b3ccff;
+ border-radius: 100%;
+ animation-fill-mode: both;
+ content: '';
+ }
+ }
+ .shape:nth-child(1) {
+ animation-delay: -0.16s;
+ &::before {
+ animation: animate1 1.4s infinite linear;
+ }
+ }
+ .shape:nth-child(2) {
+ animation-delay: -0.16s;
+ &::before {
+ animation: animate2 1.4s infinite linear;
+ }
+ }
+ .shape:nth-child(3) {
+ animation-delay: -0.32s;
+ &::before {
+ animation: animate3 1.4s infinite linear;
+ }
+ }
+ .shape:nth-child(4) {
+ animation-delay: -0.16s;
+ &::before {
+ animation: animate4 1.4s infinite linear;
+ }
+ }
+}
+
+.reportTooltip {
+ max-width: 520px;
+ :global {
+ .ant-tooltip-arrow {
+ color: #fff;
+ --antd-arrow-background-color: #fff;
+ }
+ .ant-tooltip-inner {
+ width: 520px;
+ max-height: 230px;
+ padding: 16px;
+ overflow: auto;
+ color: #132039;
+ background-color: #fff;
+ }
+ }
+}
+
+.pageFooterContainer {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 99;
+ padding: 16px;
+ background-color: #f5f8ff;
+ border-top: 1px solid #dde4ed;
+ .pageFooter {
+ width: 1040px;
+ margin: 0 auto;
+ overflow: hidden;
+ .foolterAction {
+ float: right;
+ }
+ }
+}
+
+@media screen and (max-width: 1040px) {
+ .pageFooterContainer {
+ .pageFooter {
+ width: 100% !important;
+ }
+ }
+}
+
+.componentTable,
+.nodeEditabletable {
+ :global {
+ .ant-table,
+ .ant-table-wrapper .ant-table-thead > tr > th,
+ .ant-table-thead > tr > th {
+ background-color: rgba(0, 0, 0, 0) !important;
+ }
+ .ant-table {
+ color: #132039 !important;
+ }
+ .ant-table-thead > tr > th {
+ color: @subTitleColor !important;
+ font-weight: normal !important;
+ }
+ }
+}
+
+.componentTable {
+ :global {
+ .ant-table
+ .ant-table-tbody
+ tr:nth-child(2n + 1):not(.ant-table-placeholder)
+ td {
+ background-color: rgba(0, 0, 0, 0) !important ;
+ }
+ }
+}
+
+.componentTable,
+.nodeEditabletable,
+.connectTable {
+ :global {
+ .ant-table-cell::before {
+ width: 0 !important;
+ }
+ .ant-table-thead > tr > th {
+ padding-bottom: 0 !important;
+ border-bottom: none !important;
+ }
+ }
+}
+
+.nodeEditabletable {
+ :global {
+ .ant-pro-card-body {
+ padding-inline: 0 !important;
+ padding-block: 0 !important;
+ }
+ .ant-table-cell {
+ padding-left: 0 !important;
+ padding-left: 0 !important;
+ }
+ .ant-btn-dashed {
+ width: calc(100% - 36px) !important;
+ margin-top: 4px !important;
+ color: @greyTextColor !important;
+ }
+ .ant-table
+ .ant-table-tbody
+ tr:nth-child(2n):not(.ant-table-placeholder)
+ td {
+ background-color: #fff !important;
+ }
+ .ant-table .ant-table-tbody tr td {
+ padding-top: 11px !important;
+ padding-bottom: 11px !important;
+ }
+ }
+}
+
+.connectTable {
+ :global {
+ .ant-table-thead > tr > th {
+ padding-left: 0 !important;
+ }
+ }
+}
+
+.collapsibleCard {
+ :global {
+ .ant-pro-card-title .anticon {
+ position: absolute;
+ top: 21px;
+ left: 100px;
+ }
+ }
+}
+
+.paramterSelect {
+ :global {
+ .ant-select-selector {
+ height: 28px !important;
+ }
+ .ant-select-selection-item {
+ line-height: 26px !important;
+ }
+ }
+}
+
+.paramterInput {
+ width: 126px !important;
+ :global {
+ .ant-input {
+ height: 28px;
+ }
+ }
+}
+
+.progressEffectContainer {
+ position: relative;
+ height: 150px;
+ .deployTitle {
+ position: absolute;
+ left: 50%;
+ font-weight: 500;
+ font-size: 16px;
+ transform: translateX(-50%);
+ }
+ .computer {
+ position: absolute;
+ top: 20px;
+ left: 195px;
+ .computerAnimate {
+ width: 150px;
+ }
+ }
+ .progress {
+ position: absolute;
+ top: 65px;
+ left: 327px;
+ .progressVedio {
+ width: 285px;
+ height: 11px;
+ background-color: #fff;
+ :global {
+ .vjs-text-track-display {
+ background-color: #fff !important;
+ }
+ .vjs-poster {
+ top: 1px !important;
+ left: 1px !important;
+ background-position: 0 0 !important;
+ background-size: 100% !important;
+ }
+ .vjs-modal-dialog {
+ background: #fff !important;
+ }
+ .vjs-error-display:before {
+ content: '' !important;
+ }
+ }
+ }
+ .progressCover {
+ position: absolute;
+ top: 2.5px;
+ right: 2px;
+ height: 5.5px;
+ background: #fff;
+ border-radius: 0 5px 5px 0;
+ }
+ }
+
+ .spaceman {
+ position: absolute;
+ top: 25px;
+ left: 310px;
+ .spacemanAnimate {
+ width: 320px;
+ }
+ }
+ .database {
+ position: absolute;
+ top: 20px;
+ right: 190px;
+ .sqlAnimate {
+ width: 150px;
+ }
+ }
+}
+
+.deploymentName {
+ position: absolute;
+ bottom: 40px;
+ left: 50%;
+ color: @greyTextColor;
+ font-size: 12px;
+ transform: translateX(-50%);
+}
+
+.exitPage {
+ height: 680px;
+ margin-top: 32px;
+ padding-top: 160px;
+ text-align: center;
+ border: none;
+ .exitPageText {
+ font-size: 18px !important;
+ }
+}
+
+.commandTooltip {
+ :global {
+ .ant-tooltip-inner {
+ width: 400px;
+ }
+ }
+}
+
+:global {
+ .english-container {
+ }
+}
diff --git a/web/src/pages/OcpInstaller/Configuration/index.tsx b/web/src/pages/OcpInstaller/Configuration/index.tsx
new file mode 100644
index 0000000..ad90824
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Configuration/index.tsx
@@ -0,0 +1,114 @@
+import React, { useState, useEffect } from 'react';
+import { useModel } from 'umi';
+import { PageContainer } from '@oceanbase/ui';
+import { useRequest } from 'ahooks';
+import { errorHandler } from '@/utils';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import {
+ METADB_OCP_INSTALL,
+ STEPS_KEYS_INSTALL,
+} from '@/constant/configuration';
+import Steps from '@/component/Steps';
+import DeployConfig from '@/component/DeployConfig';
+import ConnectConfig from '@/component/ConnectConfig';
+import OCPConfigNew from '@/component/OCPConfigNew';
+import OCPPreCheck from '@/component/OCPPreCheck';
+import InstallProcessNew from '@/component/InstallProcessNew';
+import InstallResult from '@/component/InstallResult';
+
+const Configuration: React.FC = () => {
+ const [current, setCurrent] = useState(1);
+ const {
+ connectId,
+ installTaskId,
+ installStatus,
+ setInstallStatus,
+ installResult,
+ } = useModel('ocpInstallData');
+
+ // 获取ocp 信息
+ const { data: ocpInfoData, run: getInstalledOcpInfo } = useRequest(
+ OCP.getInstalledOcpInfo,
+ {
+ manual: true,
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ const ocpInfo = ocpInfoData?.data || {};
+
+ useEffect(() => {
+ if (
+ current == 6 &&
+ installStatus === 'FINISHED' &&
+ installResult === 'SUCCESSFUL'
+ ) {
+ getInstalledOcpInfo({
+ id: connectId,
+ });
+ }
+ }, [current, installStatus, installResult]);
+
+ return (
+
+
+
+
+ {current === 1 && (
+
+ )}
+
+ {current === 2 && (
+
+ )}
+
+ {current === 3 && (
+
+ )}
+
+ {current === 4 && (
+
+ )}
+
+ {current === 5 && (
+
+ )}
+
+ {current === 6 && (
+
+ )}
+
+
+ );
+};
+
+export default Configuration;
diff --git a/web/src/pages/OcpInstaller/Error/403.tsx b/web/src/pages/OcpInstaller/Error/403.tsx
new file mode 100644
index 0000000..8ce8ade
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Error/403.tsx
@@ -0,0 +1,28 @@
+import { history } from 'umi';
+import { intl } from '@/utils/intl';
+import React from 'react';
+import AobException from '@/component/AobException';
+
+export default () => {
+ return (
+ {
+ history.push(`/`);
+ }}
+ style={{
+ paddingTop: 50,
+ height: '100%',
+ }}
+ />
+ );
+};
diff --git a/web/src/pages/OcpInstaller/Error/404.tsx b/web/src/pages/OcpInstaller/Error/404.tsx
new file mode 100644
index 0000000..af1fb0e
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Error/404.tsx
@@ -0,0 +1,27 @@
+import { intl, history } from 'umi';
+import React from 'react';
+import AobException from '@/component/AobException';
+
+export default () => {
+ return (
+ {
+ history.push(`/`);
+ }}
+ style={{
+ paddingTop: 50,
+ height: '100%',
+ }}
+ />
+ );
+};
diff --git a/web/src/pages/OcpInstaller/Index/index.less b/web/src/pages/OcpInstaller/Index/index.less
new file mode 100644
index 0000000..352664a
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Index/index.less
@@ -0,0 +1,142 @@
+@import '~@oceanbase/design/es/theme/index.less';
+
+.container {
+ padding-bottom: 24px;
+}
+
+@media (max-width: 1441px) and (max-height: 676px) {
+ .introduce {
+ position: absolute;
+ bottom: 24px;
+ }
+}
+
+@media (min-width: 1441px) and (min-height: 676px) {
+ .introduce {
+ position: absolute;
+ bottom: 25%;
+ }
+}
+
+.container,
+.updateContainer {
+ width: 100vw;
+ height: calc(100vh - 48px);
+ min-height: 700px;
+ font-family: PingFangSC;
+
+ .upgrade {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ height: 100%;
+ padding-left: 151px;
+ background-image: url('/assets/welcome/banner.png');
+ background-repeat: no-repeat;
+ background-position-x: right;
+ background-position-y: bottom;
+ background-size: contain;
+ }
+ .title {
+ position: absolute;
+ top: 98px;
+ left: 144px;
+ height: 48px;
+ color: #fff;
+ font-weight: 500;
+ font-size: 28px;
+ line-height: 24px;
+ letter-spacing: 0;
+ }
+ .descriptions {
+ height: 24px;
+ margin: 12px 0;
+ color: #fff;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 24px;
+ letter-spacing: 0;
+ text-align: left;
+ opacity: 0.65;
+ }
+ .version {
+ // width: 120px;
+ height: 26px;
+ color: #fff;
+ font-weight: 400;
+ font-size: 16px;
+ font-family: PingFangSC;
+ line-height: 26px;
+ }
+ .startBtn {
+ width: 240px;
+ height: 50px;
+ margin-top: 24px;
+ // background-color: #0000ff;
+ border-radius: @borderRadius;
+ }
+ .introduce {
+ margin-top: 100px;
+ color: #fff;
+ .featureName {
+ display: flex;
+ font-weight: 600;
+ font-size: 16px;
+ opacity: 0.85;
+ .icon {
+ display: inline-block;
+ width: 20px;
+ height: 36px;
+ margin-right: 12px;
+ background: url('/assets/welcome/checkbox.svg') no-repeat;
+ }
+ .description {
+ width: 440px;
+ margin-top: 5px;
+ font-size: 12px;
+ line-height: 22px;
+ opacity: 0.65;
+ }
+ }
+ }
+
+ .content {
+ position: relative;
+ z-index: 10;
+ width: 100vw;
+ height: calc(100% - 148px);
+ min-height: 650px;
+ padding: 24px 151px;
+ background-color: rgba(245, 248, 254, 1);
+ .intallType {
+ border: 2px solid #e2e8f3;
+ border-radius: 8px;
+ :global {
+ .ant-result-title {
+ font-weight: 500;
+ font-size: 18px;
+ line-height: 22px;
+ }
+ }
+ :global {
+ .ant-result-subtitle {
+ font-size: 14px;
+ line-height: 22px;
+ font-weight: 400;
+ }
+ }
+
+ &:hover,
+ &.selected {
+ color: #006aff !important;
+ border: 2px solid #006aff;
+ :global {
+ .ant-result-title,
+ .ant-result-subtitle {
+ color: #006aff !important;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/web/src/pages/OcpInstaller/Index/index.tsx b/web/src/pages/OcpInstaller/Index/index.tsx
new file mode 100644
index 0000000..b7e550b
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Index/index.tsx
@@ -0,0 +1,332 @@
+import { intl } from '@/utils/intl';
+import React, { useState } from 'react';
+import { history } from 'umi';
+import { Button, Card, Result, Tag, Col, Row } from '@oceanbase/design';
+import { Alert } from 'antd';
+import { useRequest, useUpdateEffect } from 'ahooks';
+import { errorHandler } from '@/utils';
+import * as Process from '@/services/ocp_installer_backend/Process';
+import Banner from '@/component/Banner';
+import CustomFooter from '@/component/CustomFooter';
+import ExitBtn from '@/component/ExitBtn';
+import styles from './index.less';
+
+export interface IndexProps {
+ location: {
+ query: { type: string };
+ };
+}
+
+type ConfigMethodType = 'ocpInstaller/configuration' | 'ocpInstaller/install';
+type InstallIconType =
+ | '/assets/welcome/new-db-selected.svg'
+ | '/assets/welcome/new-db-unselected.svg';
+type ConfigurationIconType =
+ | '/assets/welcome/old-db-selected.svg'
+ | '/assets/welcome/old-db-unselected.svg';
+//创建新的数据库——》install
+//使用已有的 ——》configuration
+const Index: React.FC = ({
+ location: {
+ query: { type },
+ },
+}) => {
+ let isUpdate, isHaveMetadb;
+ const [configMethod, setConfigMethod] = useState(
+ 'ocpInstaller/install',
+ );
+ const [installIcon, setInstallIcon] = useState(
+ '/assets/welcome/new-db-selected.svg',
+ );
+ const [configurationIcon, setConfigurationIcon] =
+ useState('/assets/welcome/old-db-unselected.svg');
+ // useEffect(() => {
+ // dispatch({
+ // type: 'global/update',
+ // payload: {
+ // isUpdate: type === 'upgrade',
+ // },
+ // });
+ // }, [type]);
+ // 退出
+ const { run: suicide, loading: suicideLoading } = useRequest(
+ Process.suicide,
+ {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success) {
+ // dispatch({
+ // type: 'global/update',
+ // payload: {
+ // installStatus: '',
+ // installResult: '',
+ // },
+ // });
+ history.push(`/quit`);
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ const features = [
+ {
+ name: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.LifecycleManagementOM',
+ defaultMessage: '全生命周期管理(运维管控)',
+ }),
+ description: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.OcpImplementsUnifiedManagementOf',
+ defaultMessage:
+ 'OCP 实现对 OceanBase 资源的统一管理,实现了对资源的创建、备份恢复、监控告警、巡检、自治、升级、删除等全生命周期管理。',
+ }),
+ },
+ {
+ name: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.MonitoringAlarm',
+ defaultMessage: '监控告警',
+ }),
+ description: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.OcpMonitorsOceanbaseFromDifferent',
+ defaultMessage:
+ 'OCP 支持从主机、集群、租户等不同维度对 OceanBase 进行监控,并且提供包括钉钉、微信、邮件等多种不同的告警方式,保障集群安全 。',
+ }),
+ },
+ {
+ name: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.BackupAndRecovery',
+ defaultMessage: '备份恢复',
+ }),
+ description: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.OcpProvidesBackupAndRecovery',
+ defaultMessage:
+ 'OCP 提供对 OceanBase 集群、租户的备份恢复能力,支持自动将全量、增量、日志备份到NAS、OSS等存储类型,支持一键恢复操作。',
+ }),
+ },
+ // 自动的判断商业版还是社区版改起来比较多。我们这次先直接下掉
+ // 商业版特有
+ // {
+ // name: '容灾管理',
+ // description:
+ // 'OCP 支持自动化部署主备集群,实现对业务的容灾保护,支持主备集群解藕、主备切换演练,容灾应急切换等运维能力对集群进行管理。',
+ // },
+ {
+ name: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.DiagnosticOptimization',
+ defaultMessage: '诊断优化',
+ }),
+ description: intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.OcpProvidesClosedLoopDiagnostics',
+ defaultMessage:
+ 'OCP 针对 SQL 提供从感知、根因分析、执行建议的闭环诊断能力。OCP 同时还实现了从集群复制、会话、死锁、容量等维度的诊断能力。',
+ }),
+ },
+ // 商业版特有
+ // {
+ // name: '自治服务',
+ // description:
+ // 'OCP 将多年的 OceanBase 集群的专家经验沉淀为产品功能,提供从事件感知、根因分析、自治自愈到告警通知、应急处理的全链路自治能力。',
+ // },
+ ];
+
+ const mouseLeaveInstall = () => {
+ if (configMethod !== 'ocpInstaller/install') {
+ setInstallIcon('/assets/welcome/new-db-unselected.svg');
+ }
+ };
+
+ const mouseLeaveConfiguration = () => {
+ if (configMethod !== 'ocpInstaller/configuration') {
+ setConfigurationIcon('/assets/welcome/old-db-unselected.svg');
+ }
+ };
+
+ useUpdateEffect(() => {
+ if (configMethod === 'ocpInstaller/install') {
+ setInstallIcon('/assets/welcome/new-db-selected.svg');
+ setConfigurationIcon('/assets/welcome/old-db-unselected.svg');
+ } else {
+ setConfigurationIcon('/assets/welcome/old-db-selected.svg');
+ setInstallIcon('/assets/welcome/new-db-unselected.svg');
+ }
+ }, [configMethod]);
+
+ return (
+
+ {isUpdate ? (
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.WelcomeToTheOcpUpgrade',
+ defaultMessage: '欢迎使用 OCP 升级向导',
+ })}
+
+
+ OceanBase Cloud Platfrom upgrade wizard
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+ setConfigMethod('ocpInstaller/install')}
+ onMouseEnter={() =>
+ setInstallIcon('/assets/welcome/new-db-selected.svg')
+ }
+ onMouseLeave={mouseLeaveInstall}
+ >
+ }
+ status="success"
+ title={intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.CreateANewOceanbaseDatabase',
+ defaultMessage: '创建全新的 OceanBase 数据库',
+ })}
+ subTitle={intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.MetadbAsOcp',
+ defaultMessage: '作为 OCP 的 MetaDB',
+ })}
+ extra={[
+
+ {intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.Recommend',
+ defaultMessage: '推荐',
+ })}
+ ,
+ ]}
+ />
+
+
+
+
+ setConfigMethod('ocpInstaller/configuration')
+ }
+ onMouseEnter={() =>
+ setConfigurationIcon(
+ '/assets/welcome/old-db-selected.svg',
+ )
+ }
+ onMouseLeave={mouseLeaveConfiguration}
+ >
+
}
+ status="success"
+ title={intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.UseAnExistingOceanbaseDatabase',
+ defaultMessage: '使用已有的 OceanBase 数据库',
+ })}
+ subTitle={intl.formatMessage({
+ id: 'OBD.OcpInstaller.Index.MetadbAsOcp',
+ defaultMessage: '作为 OCP 的 MetaDB',
+ })}
+ extra={[
+
,
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default Index;
diff --git a/web/src/pages/OcpInstaller/Install/Component/MetadbDeployResult.tsx b/web/src/pages/OcpInstaller/Install/Component/MetadbDeployResult.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/web/src/pages/OcpInstaller/Install/Component/ModifyResourcePoolModal/index.less b/web/src/pages/OcpInstaller/Install/Component/ModifyResourcePoolModal/index.less
new file mode 100644
index 0000000..97ac794
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Install/Component/ModifyResourcePoolModal/index.less
@@ -0,0 +1,11 @@
+.resorcePollModal {
+ height: 600px;
+ overflow: hidden;
+ :global {
+ .ant-form-item-label > label {
+ display: inline-block;
+ justify-content: space-between !important;
+ width: 100%;
+ }
+ }
+}
diff --git a/web/src/pages/OcpInstaller/Install/Component/ModifyResourcePoolModal/index.tsx b/web/src/pages/OcpInstaller/Install/Component/ModifyResourcePoolModal/index.tsx
new file mode 100644
index 0000000..109a461
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Install/Component/ModifyResourcePoolModal/index.tsx
@@ -0,0 +1,476 @@
+import { intl } from '@/utils/intl';
+import {
+ Col,
+ Descriptions,
+ Form,
+ Modal,
+ Row,
+ Card,
+ message,
+} from '@oceanbase/design';
+import React, { useEffect, useState } from 'react';
+import { ExclamationCircleFilled } from '@ant-design/icons';
+// import { Pie } from '@alipay/ob-charts';
+import { minBy, find } from 'lodash';
+import { useRequest } from 'ahooks';
+import { Alert } from 'antd';
+import { errorHandler } from '@/utils';
+import * as Metadb from '@/services/ocp_installer_backend/Metadb';
+import SliderAndInputNumber from '@/component/SliderAndInputNumber';
+import { findBy } from '@oceanbase/util';
+import styles from './index.less';
+
+export interface ModifyResourcePoolModalProps {
+ createMetadbDeployment: (params) => void;
+ currentMetadbDeploymentConfig?: any;
+ createMetadbData?: any;
+ loading?: boolean;
+ visible?: boolean;
+ id: number;
+}
+
+const ModifyResourcePoolModal: React.FC = ({
+ currentMetadbDeploymentConfig,
+ createMetadbDeployment,
+ createMetadbData,
+ visible,
+ loading,
+ id,
+ ...restProps
+}) => {
+ const [form] = Form.useForm();
+ const { validateFields, setFieldsValue, getFieldValue } = form;
+
+ // 剩余空间
+ const [dataDirFreeSize, setDataDirFreeSize] = useState(0);
+ const [logDirFreeSize, setLogDirFreeSize] = useState(0);
+
+ const [maxDataDirDisk, setMaxDataDirDisk] = useState(0);
+ const [maxLogSize, setMaxLogSize] = useState(0);
+
+ const [memoryLimitSize, setMemoryLimitSize] = useState(1);
+ const [dataFileSize, setDataFileSize] = useState(1);
+ const [logDiskSize, setLogDiskSize] = useState(1);
+
+ const [minMetadbResource, setMinMetadbResource] =
+ useState();
+
+ const [commonTenantReserve, setCommonTenantReserve] = useState(0);
+
+ // 查询主机的资源
+ const {
+ data: metadbDeploymentResourceData,
+ run: getMetadbDeploymentResource,
+ } = useRequest(Metadb.getMetadbDeploymentResource, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res.success) {
+ const metadbDeploymentResource = res?.data || {};
+ if (metadbDeploymentResource?.items?.length === 0) {
+ return message.warning(
+ intl.formatMessage({
+ id: 'OBD.Component.ModifyResourcePoolModal.NoResourcesFound',
+ defaultMessage: '未查询到资源',
+ }),
+ );
+ }
+
+ const currentMinMetadbResource: API.ObserverResource = minBy(
+ res?.data?.items,
+ (item) => item?.memory_limit_higher_limit,
+ );
+ // 如果用户如有上次的手动输入,需要采用用户输入
+ if (currentMetadbDeploymentConfig?.id) {
+ const { parameters } = currentMetadbDeploymentConfig?.config;
+ setFieldsValue({
+ memory_limit: find(
+ parameters,
+ (item) => item?.name === 'memory_limit',
+ )?.value?.split('G')[0],
+ datafile_size: find(
+ parameters,
+ (item) => item?.name === 'datafile_size',
+ )?.value?.split('G')[0],
+ log_disk_size: find(
+ parameters,
+ (item) => item?.name === 'log_disk_size',
+ )?.value?.split('G')[0],
+ });
+ } else {
+ setFieldsValue({
+ memory_limit: currentMinMetadbResource?.memory_limit_default,
+ datafile_size: currentMinMetadbResource?.memory_limit_default * 3,
+ log_disk_size: currentMinMetadbResource?.memory_limit_default * 3,
+ });
+ }
+
+ setMemoryLimitSize(currentMinMetadbResource?.memory_limit_default);
+ setMinMetadbResource(currentMinMetadbResource);
+
+ const memoryLimitSizeMax =
+ currentMinMetadbResource?.memory_limit_higher_limit -
+ Math.ceil(currentMinMetadbResource?.memory_limit_higher_limit * 0.1) -
+ Math.floor(currentMinMetadbResource?.memory_limit_higher_limit * 0.1);
+
+ setCommonTenantReserve(
+ memoryLimitSizeMax - currentMinMetadbResource?.memory_limit_default,
+ );
+
+ const { config } = createMetadbData;
+ const data_dir = config?.data_dir || 'data/1';
+ const log_dir = config?.log_dir || 'data/log';
+
+ // 根据对应路径找到对应盘 取出相应盘的剩余空间
+ const data_dir_disk = findBy(
+ currentMinMetadbResource?.disk || [],
+ 'path',
+ data_dir,
+ );
+ const log_dir_disk = findBy(
+ currentMinMetadbResource?.disk || [],
+ 'path',
+ log_dir,
+ );
+
+ const dataDirDiskInfoFreeSize = Number(
+ data_dir_disk?.disk_info?.free_size,
+ );
+ const logDirDiskInfoFreeSize = Number(
+ log_dir_disk?.disk_info?.free_size,
+ );
+
+ setDataDirFreeSize(dataDirDiskInfoFreeSize);
+ setLogDirFreeSize(logDirDiskInfoFreeSize);
+
+ setLimit(
+ dataDirDiskInfoFreeSize,
+ logDirDiskInfoFreeSize,
+ currentMinMetadbResource?.data_size_default,
+ currentMinMetadbResource?.log_size_default,
+ );
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const metadbResource = metadbDeploymentResourceData?.data || {};
+
+ const setLimit = (
+ dataDirDiskInfoFreeSize: number,
+ logDirDiskInfoFreeSize: number,
+ currentDataSize: number,
+ currentLogSize: number,
+ ) => {
+ let data_size_limit = 0;
+ let log_size_limit = 0;
+
+ switch (
+ minBy(metadbResource?.items, (item) => item?.memory_limit_higher_limit)
+ ?.flag
+ ) {
+ case 1:
+ data_size_limit = dataDirDiskInfoFreeSize - 2 - currentLogSize;
+ log_size_limit = dataDirDiskInfoFreeSize - 2 - currentDataSize;
+ break;
+ case 2:
+ data_size_limit = dataDirDiskInfoFreeSize - currentLogSize;
+ log_size_limit = dataDirDiskInfoFreeSize - currentDataSize;
+ break;
+ case 3:
+ data_size_limit = dataDirDiskInfoFreeSize - 2;
+ log_size_limit = dataDirDiskInfoFreeSize - 2;
+ break;
+ case 4:
+ data_size_limit = dataDirDiskInfoFreeSize * 0.9;
+ log_size_limit = logDirDiskInfoFreeSize * 0.9;
+ break;
+ default:
+ break;
+ }
+
+ setMaxDataDirDisk(data_size_limit);
+ setMaxLogSize(log_size_limit);
+ };
+
+ useEffect(() => {
+ if (id && visible) {
+ getMetadbDeploymentResource({ id });
+ }
+ }, [id, visible]);
+
+ const handleSubmit = () => {
+ validateFields().then((values) => {
+ const { datafile_size, log_disk_size } = values;
+
+ if (createMetadbDeployment) {
+ createMetadbDeployment({
+ parameters: [
+ {
+ name: 'memory_limit',
+ value: `${values?.memory_limit || memoryLimitSize}G`,
+ },
+ {
+ name: 'datafile_size',
+ value: `${datafile_size}G`,
+ },
+ {
+ name: 'log_disk_size',
+ value: `${log_disk_size}G`,
+ },
+ ],
+ });
+ }
+ });
+ };
+
+ const memoryFreeGB = minMetadbResource?.memory_limit_higher_limit;
+
+ const resourceList = [
+ {
+ type: intl.formatMessage({
+ id: 'OBD.Component.ModifyResourcePoolModal.SystemPreOccupation',
+ defaultMessage: '系统预占用',
+ }),
+ value: Math.ceil(memoryFreeGB * 0.1),
+ },
+ {
+ type: 'memory_limit',
+ value: memoryLimitSize,
+ },
+ {
+ type: intl.formatMessage({
+ id: 'OBD.Component.ModifyResourcePoolModal.OcpServiceReservation',
+ defaultMessage: 'OCP 服务预留',
+ }),
+ value: Math.floor(memoryFreeGB * 0.1),
+ },
+ {
+ type: intl.formatMessage({
+ id: 'OBD.Component.ModifyResourcePoolModal.CommonTenantReservation',
+ defaultMessage: '普通租户预留',
+ }),
+ value: commonTenantReserve,
+ },
+ ];
+
+ const memoryLimitSizeMax =
+ minMetadbResource?.memory_limit_higher_limit -
+ Math.ceil(memoryFreeGB * 0.1) -
+ Math.floor(memoryFreeGB * 0.1);
+ const config1 = {
+ data: resourceList,
+ angleField: 'value',
+ colorField: 'type',
+ innerRadius: 0.8,
+ isDonut: true,
+ lineWidth: 20,
+ label: false,
+ style: {
+ textAlign: 'center',
+ fontSize: 14,
+ },
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default ModifyResourcePoolModal;
diff --git a/web/src/pages/OcpInstaller/Install/Component/SystemConfig.tsx b/web/src/pages/OcpInstaller/Install/Component/SystemConfig.tsx
new file mode 100644
index 0000000..71e1ba5
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Install/Component/SystemConfig.tsx
@@ -0,0 +1,625 @@
+import { intl } from '@/utils/intl';
+import React, { useEffect, useState } from 'react';
+import {
+ Card,
+ Form,
+ Input,
+ Row,
+ Col,
+ Space,
+ Radio,
+ InputNumber,
+} from '@oceanbase/design';
+import { InfoCircleOutlined } from '@ant-design/icons';
+import { some } from 'lodash';
+import Password from '@/component/Password';
+import MyInput from '@/component/MyInput';
+import MySelect from '@/component/MySelect';
+import {
+ SMLL_FORM_ITEM_LAYOUT,
+ PASSWORD_REGEX,
+ SELECT_TOKEN_SPEARATORS,
+} from '@/constant';
+import { validatePassword } from '@/utils';
+// import tracert from '@/util/tracert';
+import validator from 'validator';
+
+export interface SystemConfigProps {
+ form: any;
+ step?: number;
+ userInfo?: any;
+ currentMetadbDeploymentConfig?: any;
+ checkHomePathcheckStatus?: boolean;
+ checkDataDircheckStatus?: boolean;
+ checkLogDircheckStatus?: boolean;
+ checkMachineResult?: API.ResourceCheckResult[];
+ onChangeHomePath: () => void;
+ onChangeDataDir: () => void;
+ onChangeLogDir: () => void;
+}
+
+const SystemConfig: React.FC = ({
+ form,
+ step,
+ userInfo,
+ onChangeHomePath,
+ onChangeDataDir,
+ onChangeLogDir,
+ checkMachineResult,
+ checkHomePathcheckStatus,
+ checkDataDircheckStatus,
+ checkLogDircheckStatus,
+ currentMetadbDeploymentConfig,
+}) => {
+ const { getFieldValue, setFieldsValue } = form;
+
+ // 密码校验是否通过
+ const [passed, setPassed] = useState(true);
+ const [devnameType, setDevnameType] = useState('AUTO');
+
+ const [homePathcheckResult, setHomePathcheckResult] = useState(true);
+ const [dataDircheckResult, setDataDircheckResult] = useState(true);
+ const [logDircheckResult, setLogDircheckResult] = useState(true);
+
+ useEffect(() => {
+ if (checkMachineResult && checkMachineResult?.length > 0) {
+ setHomePathcheckResult(checkMachineResult[0] || true);
+ setDataDircheckResult(checkMachineResult[1]);
+ setLogDircheckResult(checkMachineResult[2]);
+ }
+ }, [checkMachineResult, checkMachineResult?.length]);
+
+ useEffect(() => {
+ if (userInfo?.username) {
+ setFieldsValue({
+ user: userInfo?.username,
+ home_path: `/home/${userInfo?.username || 'root'}`,
+ });
+ }
+ if (currentMetadbDeploymentConfig?.id) {
+ const {
+ auth,
+ cluster_name,
+ servers,
+ // root_password,
+ home_path,
+ data_dir,
+ log_dir,
+ sql_port,
+ rpc_port,
+ devname,
+ } = currentMetadbDeploymentConfig?.config;
+ setFieldsValue({
+ servers,
+ user: auth?.user,
+ private_key: auth?.private_key,
+ username: auth?.username,
+ // password: auth?.password,
+ cluster_name,
+ // root_password,
+ home_path: home_path
+ ? home_path.split('/oceanbase')[0]
+ : `/home/${userInfo?.username || 'root'}`,
+ data_dir: data_dir ? data_dir : '/data/1',
+ log_dir: log_dir ? log_dir : '/data/log',
+ sql_port,
+ rpc_port,
+ devname,
+ });
+ setDevnameType(devname || devname !== '' ? 'MANUAL' : 'AUTO');
+ }
+ }, [currentMetadbDeploymentConfig?.id, step, userInfo?.username]);
+
+ const validate = (rule, values: any[], callback) => {
+ if (
+ values &&
+ some(
+ values,
+ (item) =>
+ // ipv4 地址
+ !validator.isIP(item, '4'),
+ )
+ ) {
+ callback(
+ intl.formatMessage({
+ id: 'OBD.Install.Component.SystemConfig.InvalidIpAddress',
+ defaultMessage: 'IP 地址不合法',
+ }),
+ );
+
+ return;
+ }
+ callback();
+ };
+
+ return (
+
+ );
+};
+
+export default SystemConfig;
diff --git a/web/src/pages/OcpInstaller/Install/index.less b/web/src/pages/OcpInstaller/Install/index.less
new file mode 100644
index 0000000..0d20647
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Install/index.less
@@ -0,0 +1,9 @@
+
+.sideTip {
+ padding: 16px 24px;
+ font-size: 14px;
+ color: #5C6B8A;
+ line-height: 24px;
+ background-color: #F8FAFE;
+ border-radius: 8px;
+}
\ No newline at end of file
diff --git a/web/src/pages/OcpInstaller/Install/index.tsx b/web/src/pages/OcpInstaller/Install/index.tsx
new file mode 100644
index 0000000..000cbb8
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Install/index.tsx
@@ -0,0 +1,141 @@
+import { intl } from '@/utils/intl';
+import { useModel } from 'umi';
+import React, { useState, useEffect } from 'react';
+import { Alert } from 'antd';
+import { PageContainer } from '@oceanbase/ui';
+
+import { useRequest } from 'ahooks';
+import { errorHandler } from '@/utils';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import { NEW_METADB_OCP_INSTALL } from '@/constant/configuration';
+import DeployConfig from '@/component/DeployConfig';
+import OCPPreCheck from '@/component/OCPPreCheck';
+import InstallProcessNew from '@/component/InstallProcessNew';
+import InstallResult from '@/component/InstallResult';
+import Steps from '@/component/Steps';
+import { STEPS_KEYS_INSTALL } from '@/constant/configuration';
+import MetaDBConfig from '@/component/MetaDBConfig';
+import OCPConfigNew from '@/component/OCPConfigNew';
+
+export interface InstallProps {
+ location: {
+ query: { step: number; metadbId: number; ocpId: number };
+ };
+}
+
+const Install: React.FC = ({
+ location: {
+ query: { step, metadbId, ocpId },
+ },
+}) => {
+ const [current, setCurrent] = useState(step ? Number(step) : 1);
+
+ const {
+ connectId,
+ installTaskId,
+ installStatus,
+ setInstallStatus,
+ installResult,
+ } = useModel('ocpInstallData');
+
+ // 获取ocp 信息
+ const { data: ocpInfoData, run: getInstalledOcpInfo } = useRequest(
+ OCP.getInstalledOcpInfo,
+ {
+ manual: true,
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ },
+ );
+
+ const ocpInfo = ocpInfoData?.data || {};
+
+ useEffect(() => {
+ if (
+ current == 6 &&
+ installStatus === 'FINISHED' &&
+ installResult === 'SUCCESSFUL'
+ ) {
+ getInstalledOcpInfo({
+ id: connectId,
+ });
+ }
+ }, [current, installStatus, installResult]);
+ return (
+
+
+
+
+ {current === 1 ||
+ (current === 2 && (
+
+ ))}
+
+ {current === 1 && (
+
+ )}
+
+ {current === 2 && (
+
+ )}
+
+ {current === 3 && (
+
+ )}
+
+ {current === 4 && (
+
+ )}
+
+ {current === 5 && (
+
+ )}
+
+ {current === 6 && (
+
+ )}
+
+
+ );
+};
+
+export default Install;
diff --git a/web/src/pages/OcpInstaller/Layout/BasicLayout/index.less b/web/src/pages/OcpInstaller/Layout/BasicLayout/index.less
new file mode 100644
index 0000000..f95bdf3
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Layout/BasicLayout/index.less
@@ -0,0 +1,10 @@
+.container {
+ .action {
+ color: #5c6b8a;
+ }
+ :global {
+ .ob-layout-header .ob-layout-header-extra > .ob-layout-header-extra-item {
+ margin-right: 24px;
+ }
+ }
+}
diff --git a/web/src/pages/OcpInstaller/Layout/BasicLayout/index.tsx b/web/src/pages/OcpInstaller/Layout/BasicLayout/index.tsx
new file mode 100644
index 0000000..1bf5ad5
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Layout/BasicLayout/index.tsx
@@ -0,0 +1,88 @@
+import { intl } from '@/utils/intl';
+import React from 'react';
+import { HomeOutlined, ReadOutlined } from '@ant-design/icons';
+import { Space } from '@oceanbase/design';
+import { BasicLayout as OBUIBasicLayout } from '@oceanbase/ui';
+import type { BasicLayoutProps as OBUIBasicLayoutProps } from '@oceanbase/ui/es/BasicLayout';
+import styles from './index.less';
+
+interface BasicLayoutProps extends OBUIBasicLayoutProps {
+ children: React.ReactNode;
+ location: {
+ pathname: string;
+ };
+}
+
+const BasicLayout: React.FC = (props) => {
+ // const { isUpdate } = useSelector((state: DefaultRootState) => state.global);
+ const isUpdate = false;
+ // 全局菜单
+ const { location, children, ...restProps } = props;
+
+ const simpleLogoUrl = '/assets/logo/logo.png';
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.Layout.BasicLayout.VisitTheOfficialWebsite',
+ defaultMessage: '访问官网',
+ })}
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.Layout.BasicLayout.HelpCenter',
+ defaultMessage: '帮助中心',
+ })}
+
+
+
+ ),
+
+ showLocale: false,
+ showHelp: false,
+ }}
+ >
+ {children}
+
+ );
+};
+
+export default BasicLayout;
diff --git a/web/src/pages/OcpInstaller/Layout/BlankLayout/index.less b/web/src/pages/OcpInstaller/Layout/BlankLayout/index.less
new file mode 100644
index 0000000..f390d14
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Layout/BlankLayout/index.less
@@ -0,0 +1,15 @@
+.main {
+ min-width: 1280px;
+ height: 100%;
+ .layout {
+ min-height: 100%;
+ :global {
+ .ant-pro-page-container {
+ margin: 0 120px;
+ // width: 1040px;
+ margin: 0 auto;
+ overflow: auto;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/pages/OcpInstaller/Layout/BlankLayout/index.tsx b/web/src/pages/OcpInstaller/Layout/BlankLayout/index.tsx
new file mode 100644
index 0000000..dad1814
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Layout/BlankLayout/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { Layout } from '@oceanbase/design';
+import styles from './index.less';
+
+const BlankLayout: React.FC = ({ children, ...restProps }) => (
+
+ {children}
+
+);
+
+export default BlankLayout;
diff --git a/web/src/pages/OcpInstaller/Layout/index.tsx b/web/src/pages/OcpInstaller/Layout/index.tsx
new file mode 100644
index 0000000..652d7a3
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Layout/index.tsx
@@ -0,0 +1,42 @@
+import { getLocale, history } from 'umi';
+import { intl } from '@/utils/intl';
+import React, { useEffect } from 'react';
+import { theme, ConfigProvider } from '@oceanbase/design';
+import en_US from 'antd/es/locale/en_US';
+import zh_CN from 'antd/es/locale/zh_CN';
+import BlankLayout from './BlankLayout';
+import ErrorBoundary from '@/component/ErrorBoundary';
+
+interface LayoutProps {
+ children: React.ReactNode;
+}
+
+const Layout: React.FC = ({ children }) => {
+ const locale = getLocale();
+ const antdLocaleMap = {
+ 'en-US': en_US,
+ 'zh-CN': zh_CN,
+ };
+
+ useEffect(() => {
+ // 设置标签页的 title
+ document.title = intl.formatMessage({
+ id: 'OBD.OcpInstaller.Layout.OceanbaseCloudPlatform',
+ defaultMessage: 'OceanBase 云平台',
+ });
+ }, []);
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default Layout;
diff --git a/web/src/pages/OcpInstaller/Quit/index.less b/web/src/pages/OcpInstaller/Quit/index.less
new file mode 100644
index 0000000..e45c7c4
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Quit/index.less
@@ -0,0 +1,17 @@
+.container {
+ .quitDesc {
+ font-size: 18px;
+ line-height: 22px;
+ font-weight: 400;
+ color: #132039;
+ margin-top: 24px;
+ :global {
+ .ant-typography {
+ font-size: 18px;
+ margin-bottom: 0;
+ color: #006AFF;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/web/src/pages/OcpInstaller/Quit/index.tsx b/web/src/pages/OcpInstaller/Quit/index.tsx
new file mode 100644
index 0000000..0c5959e
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Quit/index.tsx
@@ -0,0 +1,72 @@
+import { intl } from '@/utils/intl';
+import { history } from 'umi';
+import React, { useEffect } from 'react';
+import { Typography, Result, Space } from '@oceanbase/design';
+import { PageContainer } from '@oceanbase/ui';
+
+import PageCard from '@/component/PageCard';
+import styles from './index.less';
+import { PathType } from '@/pages/type';
+import ExitPageWrapper from '@/component/ExitPageWrapper';
+
+const { Paragraph } = Typography;
+
+export default function Quit() {
+ //@ts-ignore
+ const path = history.location.query.path as PathType | undefined;
+
+ return (
+
+
+
+ }
+ title={
+ path === 'update'
+ ? intl.formatMessage({
+ id: 'OBD.OcpInstaller.Quit.TheUpgradeProgramHasExited',
+ defaultMessage: '升级程序已退出',
+ })
+ : intl.formatMessage({
+ id: 'OBD.OcpInstaller.Quit.TheDeploymentInstallerHasExited',
+ defaultMessage: '部署安装程序已经退出!',
+ })
+ }
+ subTitle={
+
+ {path === 'update'
+ ? intl.formatMessage({
+ id: 'OBD.OcpInstaller.Quit.TheUpgradeProgramHasQuit',
+ defaultMessage:
+ '升级程序已退出 如需再次启用升级程序,请在系统中执行',
+ })
+ : intl.formatMessage({
+ id: 'OBD.OcpInstaller.Quit.ToEnableTheDeploymentProgram',
+ defaultMessage: '如需再次启用部署程序,请在系统中执行',
+ })}
+
+
+
+ {path === 'update' ? 'obd web upgrade' : 'obd web install'}
+
+
+
+ }
+ />
+
+
+
+ );
+}
diff --git a/web/src/pages/OcpInstaller/Update/Component/ConnectionInfo/index.less b/web/src/pages/OcpInstaller/Update/Component/ConnectionInfo/index.less
new file mode 100644
index 0000000..48edd6c
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Update/Component/ConnectionInfo/index.less
@@ -0,0 +1,33 @@
+@cardBackgroundColor: #f8fafe;
+
+.componentTable {
+ :global {
+ .ant-table-thead > tr > th {
+ background-color: #f5f8fe !important;
+ }
+ }
+}
+
+.componentCard {
+ margin-bottom: 16px !important;
+ background-color: @cardBackgroundColor !important;
+ :global {
+ .ant-pro-card-body {
+ padding: 0 !important;
+ }
+ }
+ &.disabledCard {
+ opacity: 0.4;
+ }
+ &:last-child {
+ margin-bottom: 0 !important;
+ }
+}
+
+.cardContainer {
+ :global {
+ .ant-pro-card-header,.ant-pro-card-body{
+ padding: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/pages/OcpInstaller/Update/Component/ConnectionInfo/index.tsx b/web/src/pages/OcpInstaller/Update/Component/ConnectionInfo/index.tsx
new file mode 100644
index 0000000..ba4f931
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Update/Component/ConnectionInfo/index.tsx
@@ -0,0 +1,449 @@
+import InputPort from '@/component/InputPort';
+import MyInput from '@/component/MyInput';
+import { FORM_ITEM_SMALL_LAYOUT } from '@/constant';
+import type { ConnectInfoType } from '@/models/ocpInstallData';
+import * as Metadb from '@/services/ocp_installer_backend/Metadb';
+import { intl } from '@/utils/intl';
+import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
+import { ProCard } from '@ant-design/pro-components';
+import { Card, Form, Spin } from '@oceanbase/design';
+import { useRequest } from 'ahooks';
+import { Alert, Button, Table, Tag } from 'antd';
+import type { ColumnsType } from 'antd/es/table';
+import type { FormInstance } from 'antd/lib/form';
+import React from 'react';
+import { useModel } from 'umi';
+import styles from './index.less';
+export interface ConnectionInfoProps {
+ form: FormInstance;
+ loading?: boolean;
+ onSuccess?: () => void;
+ handleCheck: () => void;
+ systemUserForm: any;
+ checkConnectInfo?: 'unchecked' | 'fail' | 'success';
+ checkStatus: 'unchecked' | 'fail' | 'success';
+ setCheckStatus: React.Dispatch<
+ React.SetStateAction<'unchecked' | 'fail' | 'success'>
+ >;
+
+ setCheckConnectInfo: React.Dispatch<
+ React.SetStateAction<'unchecked' | 'fail' | 'success'>
+ >;
+
+ updateInfo: API.connectMetaDB | undefined;
+ upgraadeHosts?: Array;
+ allowInputUser: boolean;
+}
+
+type DataType = {
+ name: string;
+ servers: string[];
+};
+
+const commonWidthStyle = { width: 328 };
+
+const ConnectionInfo: React.FC = ({
+ form,
+ loading = false,
+ handleCheck,
+ checkConnectInfo,
+ systemUserForm,
+ checkStatus,
+ setCheckStatus,
+ setCheckConnectInfo,
+ updateInfo,
+ allowInputUser,
+}) => {
+ const { setFieldsValue, getFieldsValue } = form;
+ const { ocpConfigData = {}, setOcpConfigData } = useModel('global');
+ const { updateConnectInfo = {} } = ocpConfigData;
+ const columns: ColumnsType = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.Component.ConnectionInfo.ComponentName',
+ defaultMessage: '组件名称',
+ }),
+ dataIndex: 'name',
+ key: 'componentName',
+ width: 135,
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.Component.ConnectionInfo.NodeIp',
+ defaultMessage: '节点 IP',
+ }),
+ dataIndex: 'ip',
+ key: 'ip',
+ render: (_, record) => (
+ <>
+ {_.length &&
+ _.map((server: string, idx: number) => (
+
+ {server}
+
+ ))}
+ >
+ ),
+ },
+ ];
+
+ const { run: checkOperatingUser, loading: checkUserLoading } = useRequest(
+ Metadb.checkOperatingUser,
+ {
+ manual: true,
+ onError: (e) => {
+ setCheckStatus('fail');
+ },
+ },
+ );
+
+ const handleCheckSystemUser = () => {
+ systemUserForm.validateFields().then(async (values: any) => {
+ const { user, password, systemPort: port } = values;
+ const res = await checkOperatingUser({
+ user,
+ password,
+ port,
+ servers:
+ updateInfo?.component.find(
+ (item: any) =>
+ item.name === 'ocp-server' || item.name === 'ocp-server-ce',
+ ).ip || [],
+ });
+ if (res.success) {
+ setCheckStatus('success');
+ setOcpConfigData({
+ ...ocpConfigData,
+ updateConnectInfo: {
+ ...ocpConfigData.updateConnectInfo,
+ ...values,
+ },
+ });
+ } else {
+ setCheckStatus('fail');
+ }
+ });
+ };
+ const initialValues: ConnectInfoType = {
+ ...updateConnectInfo,
+ accessUser: updateConnectInfo.accessUser || 'root@sys',
+ };
+
+ const systemUserInitialValues = {
+ user: updateConnectInfo.user || undefined,
+ systemPort: updateConnectInfo.systemPort || 22,
+ password: updateConnectInfo.password || undefined,
+ };
+
+ return (
+
+
+
+
+
+ setCheckConnectInfo('unchecked')}
+ />
+
+
+
+
+ {
+ setCheckConnectInfo('unchecked');
+ }}
+ style={commonWidthStyle}
+ placeholder={intl.formatMessage({
+ id: 'OBD.Component.ConnectionInfo.EnterADatabaseName',
+ defaultMessage: '请输入数据库名',
+ })}
+ />
+
+
+
+ setCheckConnectInfo('unchecked')}
+ style={commonWidthStyle}
+ placeholder={intl.formatMessage({
+ id: 'OBD.Component.ConnectionInfo.EnterAnAccount',
+ defaultMessage: '请输入账号',
+ })}
+ />
+
+
+ {
+ setCheckConnectInfo('unchecked');
+ }}
+ style={commonWidthStyle}
+ placeholder={intl.formatMessage({
+ id: 'OBD.Component.ConnectionInfo.PleaseEnter',
+ defaultMessage: '请输入密码',
+ })}
+ />
+
+
+ {checkConnectInfo === 'fail' && (
+
+
+ {intl.formatMessage({
+ id: 'OBD.Component.ConnectionInfo.TheCurrentVerificationFailedPlease',
+ defaultMessage: '当前验证失败,请重新填写错误参数',
+ })}
+
+ )}
+
+ {checkConnectInfo === 'success' && (
+
+
+ {intl.formatMessage({
+ id: 'OBD.Component.ConnectionInfo.TheVerificationIsSuccessfulPlease',
+ defaultMessage: '当前验证成功,请填写下方参数',
+ })}
+
+ )}
+
+ {checkConnectInfo === 'success' && updateInfo && (
+
+ )}
+
+
+ );
+};
+
+export default ConnectionInfo;
diff --git a/web/src/pages/OcpInstaller/Update/Component/UpdatePreCheck/index.less b/web/src/pages/OcpInstaller/Update/Component/UpdatePreCheck/index.less
new file mode 100644
index 0000000..e22f94a
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Update/Component/UpdatePreCheck/index.less
@@ -0,0 +1,62 @@
+@cardBackgroundColor: #f8fafe;
+@subTitleColor: #5c6b8a;
+
+.ocpVersion {
+ float: left;
+ width: calc(50% - 66px);
+ padding: 21px 0;
+ color: #5c6b8a;
+ font-size: 16px;
+ text-align: center;
+ background-color: #ffffff;
+ border: 1px solid #cdd5e4;
+ border-radius: 8px;
+ span {
+ padding-right: 8px;
+ color: #132039;
+ font-weight: 500;
+ }
+}
+.infoSubCard {
+ overflow: hidden;
+ border-radius: 8px;
+ .pwdIcon {
+ position: absolute;
+ top: 2px;
+ right: 0;
+ color: #8592ad;
+ font-size: 17px;
+ }
+ :global {
+ .ant-pro-card .ant-pro-card-body {
+ padding-top: 4px !important;
+ }
+
+ .ant-pro-card-body,
+ .ant-pro-card-header {
+ background-color: @cardBackgroundColor !important;
+ }
+ .ant-pro-card-title {
+ color: @subTitleColor !important ;
+ font-weight: normal !important;
+ font-size: 14px !important;
+ }
+ .ant-pro-card-body {
+ color: #132039 !important;
+ }
+ .ant-pro-card-col.ant-pro-card-split-vertical {
+ border-inline-end: none !important;
+ .ant-pro-card::before {
+ position: absolute;
+ top: 50%;
+ width: 1px;
+ height: 48px;
+ background-color: #e8eaf3;
+ transform: translateY(-50%);
+ transition: #e8eaf3 0.3s;
+ content: '';
+ inset-inline-end: 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/pages/OcpInstaller/Update/Component/UpdatePreCheck/index.tsx b/web/src/pages/OcpInstaller/Update/Component/UpdatePreCheck/index.tsx
new file mode 100644
index 0000000..e4bfdb0
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Update/Component/UpdatePreCheck/index.tsx
@@ -0,0 +1,373 @@
+import CheckBadge from '@/component/CheckBadge';
+import ArrowIcon from '@/component/Icon/ArrowIcon';
+import NewIcon from '@/component/Icon/NewIcon';
+import { DOCS_SOP } from '@/constant/docs';
+import { OCP_UPGRADE_STATUS_LIST } from '@/constant/index';
+import { intl } from '@/utils/intl';
+import { ProCard } from '@ant-design/pro-components';
+import {
+ Button,
+ Card,
+ Col,
+ Popconfirm,
+ Row,
+ Space,
+ Spin,
+ Table,
+ Tooltip,
+} from '@oceanbase/design';
+import { Alert } from 'antd';
+import { find } from 'lodash';
+import React, { useEffect, useState } from 'react';
+import { useModel } from 'umi';
+import styles from './index.less';
+
+export interface UpdatePreCheckProps {
+ refresh?: () => void;
+ updateInfo?: API.connectMetaDB;
+ ocpUpgradePrecheckTask?: any;
+ getOcpInfoLoading?: boolean;
+ precheckOcpUpgradeLoading?: boolean;
+ changePrecheckNoPassed?: (val: boolean) => void;
+ cluster_name: string;
+}
+
+const UpdatePreCheck: React.FC = ({
+ refresh,
+ updateInfo,
+ getOcpInfoLoading,
+ ocpUpgradePrecheckTask,
+ changePrecheckNoPassed,
+ precheckOcpUpgradeLoading,
+ cluster_name,
+}) => {
+ const [ocpUpgradePrecheckResult, setOcpUpgradePrecheckResult] = useState(
+ ocpUpgradePrecheckTask?.precheck_result,
+ );
+ const { ocpConfigData } = useModel('global');
+ const version: string = ocpConfigData?.components?.ocpserver?.version;
+ const precheckOcpUpgradeStatus = ocpUpgradePrecheckTask?.task_info?.status;
+
+ const precheckOcpUpgradeResultFaild =
+ ocpUpgradePrecheckTask?.precheck_result?.filter(
+ (item) => item.result === 'FAILED',
+ );
+
+ useEffect(() => {
+ setOcpUpgradePrecheckResult(ocpUpgradePrecheckTask?.precheck_result);
+ }, [ocpUpgradePrecheckTask]);
+
+ const columns = [
+ {
+ title: intl.formatMessage({
+ id: 'OBD.Component.UpdatePreCheck.CheckItems',
+ defaultMessage: '检查项',
+ }),
+ dataIndex: 'name',
+ width: '30%',
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.Component.UpdatePreCheck.CheckStatus',
+ defaultMessage: '检查状态',
+ }),
+ dataIndex: 'result',
+ width: 120,
+ filters: OCP_UPGRADE_STATUS_LIST.map((item) => ({
+ text: item.label,
+ value: item.value,
+ })),
+ onFilter: (value: string, record: API.PrecheckResult) =>
+ record.result === value,
+ render: (text: string, record: API.PrecheckResult) => {
+ const statusItem = find(
+ OCP_UPGRADE_STATUS_LIST,
+ (item) => item.value === text,
+ );
+
+ return (
+
+ );
+ },
+ },
+ {
+ title: intl.formatMessage({
+ id: 'OBD.Component.UpdatePreCheck.Impact',
+ defaultMessage: '影响',
+ }),
+ dataIndex: 'advisement',
+ render: (text) => (text ? text : '-'),
+ },
+ ];
+
+ return (
+
+
+ {!ocpUpgradePrecheckTask ? (
+ <>
+
+
+
+
+
+
+ {cluster_name}
+
+
+ {intl.formatMessage({
+ id: 'OBD.Component.UpdatePreCheck.UpgradeAll',
+ defaultMessage: '全部升级',
+ })}
+
+
+
+
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.Component.UpdatePreCheck.PreUpgradeVersion',
+ defaultMessage: '升级前版本:',
+ })}
+ V {updateInfo?.ocp_version}
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.Component.UpdatePreCheck.UpgradedVersion',
+ defaultMessage: '升级后版本:',
+ })}
+
+ V {version}
+
+
+
+
+
+
{
+ if (!ip || ip === '') {
+ return '-';
+ }
+ return ip.map((item: string) => {item} );
+ },
+ },
+ ]}
+ rowKey="name"
+ pagination={false}
+ dataSource={
+ updateInfo?.component ? updateInfo?.component : []
+ }
+ />
+
+ {updateInfo?.tips && (
+
+ {intl.formatMessage({
+ id: 'OBD.Component.UpdatePreCheck.MetadbSharesMetaTenantResources',
+ defaultMessage:
+ 'MetaDB与MonitorDB共享Meta租户资源,容易造成OCP运行异常,强烈建议您新建Monitor租户,并进行MetaDB数据清理和MonitorDB数据迁移,详情请参考《',
+ })}
+
+ SOP
+
+ 》
+
+ )}
+
+ >
+ ) : (
+
+
+
+ {precheckOcpUpgradeResultFaild.length > 0 && (
+ {
+ setOcpUpgradePrecheckResult(
+ ocpUpgradePrecheckResult?.map((item) => ({
+ ...item,
+ result:
+ item?.result === 'FAILED' ? 'IGNORED' : item?.result,
+ })),
+ );
+ if (changePrecheckNoPassed) {
+ changePrecheckNoPassed(false);
+ }
+ }}
+ >
+
+
+ )}
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default UpdatePreCheck;
diff --git a/web/src/pages/OcpInstaller/Update/index.tsx b/web/src/pages/OcpInstaller/Update/index.tsx
new file mode 100644
index 0000000..3b1edb7
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Update/index.tsx
@@ -0,0 +1,536 @@
+import { intl } from '@/utils/intl';
+import React, { useState, useEffect } from 'react';
+import { history,useModel } from 'umi';
+import { Button, Form, Tooltip, Space,message } from '@oceanbase/design';
+import { PageContainer } from '@oceanbase/ui';
+import { useRequest } from 'ahooks';
+import { errorHandler } from '@/utils';
+import { find } from 'lodash';
+import * as Metadb from '@/services/ocp_installer_backend/Metadb';
+import * as OCP from '@/services/ocp_installer_backend/OCP';
+import { destroyDeployment,getDestroyTaskInfo } from '@/services/ob-deploy-web/Deployments';
+import ConnectionInfo from './Component/ConnectionInfo';
+import UpdatePreCheck from './Component/UpdatePreCheck';
+import InstallProcess from '@/component/InstallProcess';
+import DeployConfig from '@/component/DeployConfig';
+import Steps from '@/component/Steps';
+import CustomFooter from '@/component/CustomFooter';
+import ExitBtn from '@/component/ExitBtn';
+import { METADB_OCP_UPDATE, STEPS_KEYS_UPDATE } from '@/constant/configuration';
+
+export interface UpdateProps {
+ location: {
+ pathname: string;
+ query: { step: number; taskId: number };
+ };
+}
+
+const Update: React.FC = ({
+ location: {
+ query: { step },
+ },
+}) => {
+ const [form] = Form.useForm();
+ const [systemUserForm] = Form.useForm();
+ const { validateFields } = form;
+ const {
+ checkConnectInfo,
+ setCheckConnectInfo,
+ installStatus,
+ setInstallStatus,
+ installResult,
+ setInstallResult,
+ } = useModel('ocpInstallData');
+ const { ocpConfigData, setOcpConfigData } = useModel('global');
+ const [current, setCurrent] = useState(step ? Number(step) : -1);
+ const [precheckNoPassed, setPrecheckNoPassed] = useState(false);
+ const [preCheckLoading,setPreCheckLoading] = useState(false)
+ const [allowInputUser,setAllowInputUser] = useState(true)
+ // const [serverErrorInfo, setServerErrorInfo] = useState();
+ // 操作系统用户验证状态
+ const [checkStatus, setCheckStatus] = useState<
+ 'unchecked' | 'fail' | 'success'
+ >('unchecked');
+
+ const { components = {} } = ocpConfigData;
+ const { oceanbase = {}, ocpserver = {} } = components;
+ const cluster_name = oceanbase?.appname;
+ const version = ocpserver?.version;
+ const package_hash = ocpserver?.package_hash;
+
+ useEffect(() => {
+ if (cluster_name && !upgradeOcpInfo?.id && step === 2) {
+ upgradeOcp({
+ cluster_name,
+ version,
+ usable: package_hash,
+ });
+ }
+ }, [cluster_name, step]);
+
+ const {
+ data:connectReqData,
+ run: connectMetaDB,
+ loading,
+ } = useRequest(OCP.connectMetaDB, {
+ manual: true,
+ onSuccess: ({success,data}) => {
+ if (success) {
+ if(data?.user){
+ setAllowInputUser(false)
+ systemUserForm.setFieldValue('user',data.user)
+ }
+ setCheckConnectInfo('success');
+ } else {
+ setCheckConnectInfo('fail');
+ }
+ },
+ onError: ({ response, data }: any) => {
+ setCheckConnectInfo('fail');
+ // const errorInfo = data?.msg || data?.detail || response?.statusText;
+ // Modal.error({
+ // title: 'MetaDB 连接失败,请检查连接配置',
+ // icon: ,
+ // content: errorInfo,
+ // okText: '我知道了',
+ // });
+ },
+ });
+
+ const updateInfo = connectReqData?.data
+
+ const { run: createOcpPrecheck } = useRequest(OCP.createUpgradePrecheck, {
+ manual: true,
+ onSuccess: (res) => {
+ precheckOcpUpgrade({ cluster_name });
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ // 发起OCP的预检查
+ const {
+ run: precheckOcpUpgrade,
+ refresh: refreshPrecheckOcpUpgrade,
+ loading: precheckOcpUpgradeLoading,
+ } = useRequest(OCP.precheckOcpUpgrade, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success) {
+ history.push({
+ pathname: '/update',
+ query: {
+ step: `${current}`,
+ },
+ });
+ getOcpUpgradePrecheckTask({
+ cluster_name,
+ task_id: res?.data?.id,
+ });
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ // OCP的预检查结果
+ const {
+ data: ocpUpgradePrecheckTaskData,
+ run: getOcpUpgradePrecheckTask,
+ cancel:stopPreCheck
+ } = useRequest(OCP.getOcpUpgradePrecheckTask, {
+ manual: true,
+ pollingInterval:1000,
+ onSuccess: (res) => {
+ if (res?.success) {
+ if(res?.data?.task_info?.status !== 'RUNNING'){
+ stopPreCheck()
+ setPreCheckLoading(false)
+ }else{
+ setPreCheckLoading(true)
+ }
+ if (find(res.data?.precheck_result || [], ['result', 'FAILED'])) {
+ setPrecheckNoPassed(true);
+ } else {
+ setPrecheckNoPassed(false);
+ }
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const ocpUpgradePrecheckTask = ocpUpgradePrecheckTaskData?.data;
+ const precheckOcpUpgradeStatus = ocpUpgradePrecheckTask?.task_info?.status;
+ const precheckOcpUpgradeResult = ocpUpgradePrecheckTask?.task_info?.result;
+
+ // 升级ocp
+ const {
+ data: upgradeOcpData,
+ run: upgradeOcp,
+ refresh,
+ loading: upgradeOcpLoading,
+ } = useRequest(OCP.upgradeOcp, {
+ manual: true,
+ onSuccess: (res) => {
+ if (res?.success) {
+ getOcpInfo({
+ cluster_name,
+ });
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ // 清理环境
+ const {run:handleDestroyDeployment} = useRequest(destroyDeployment,{
+ manual:true,
+ onSuccess:({success})=>{
+ if(success){
+ handleGetDestroyTaskInfo({name:cluster_name})
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ })
+
+ // 获取清理结果
+ const {run:handleGetDestroyTaskInfo,cancel:stopGetDestroyTaskInfo} = useRequest(getDestroyTaskInfo,{
+ manual:true,
+ pollingInterval:1000,
+ onSuccess:({success,data})=>{
+ if(success && data?.status !== 'RUNNING'){
+ stopGetDestroyTaskInfo()
+ if(data?.status === 'SUCCESSFUL'){
+ refresh()
+ setInstallStatus('RUNNING');
+ }
+ if(data?.status === 'FAILED'){
+ message.error(data?.msg);
+ }
+ }
+ },
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ })
+
+ const upgradeOcpInfo = upgradeOcpData?.data || {};
+
+ // 获取ocp 信息
+ const {
+ data: ocpInfoData,
+ run: getOcpInfo,
+ loading: getOcpInfoLoading,
+ } = useRequest(OCP.getOcpInfo, {
+ manual: true,
+ onError: ({ response, data }: any) => {
+ errorHandler({ response, data });
+ },
+ });
+
+ const ocpInfo = ocpInfoData?.data || {};
+
+ const handleSubmit = (currentStep: number) => {
+ switch (currentStep) {
+ case 0:
+ break;
+ case 1:
+ createOcpPrecheck({ name: cluster_name });
+ break;
+ case 2:
+ if (cluster_name && version) {
+ upgradeOcp({
+ cluster_name,
+ version,
+ usable: package_hash,
+ }).then(() => {
+ setCurrent(current + 1);
+ });
+ }
+ break;
+ default:
+ break;
+ }
+ };
+
+ const handleCheck = () => {
+ validateFields().then((values) => {
+ const { host, port, database, accessUser, accessCode } = values;
+ setOcpConfigData({
+ ...ocpConfigData,
+ updateConnectInfo:{
+ ...ocpConfigData.updateConnectInfo,
+ ...values
+ }
+ });
+ connectMetaDB({
+ host,
+ port,
+ database,
+ user: accessUser,
+ password: accessCode,
+ cluster_name,
+ });
+ });
+ };
+
+ const preStep = () => {
+ setCurrent(current - 1);
+ };
+
+ const resetConnectState = ()=>{
+ systemUserForm.setFieldsValue({
+ user:'',
+ password:'',
+ systemPort:22
+ })
+ setCheckConnectInfo('unchecked')
+ }
+
+
+
+ return (
+
+ {installResult !== 'FAILED' && installResult !== 'SUCCESSFUL' && (
+
+ )}
+
+
+ {current == -1 && (
+
+ )}
+
+ {current == 0 && (
+
+ )}
+
+ {current == 1 && (
+ {
+ setPrecheckNoPassed(val);
+ }}
+ getOcpInfoLoading={getOcpInfoLoading}
+ ocpUpgradePrecheckTask={ocpUpgradePrecheckTask}
+ precheckOcpUpgradeLoading={
+ precheckOcpUpgradeLoading || preCheckLoading
+ }
+ cluster_name={cluster_name}
+ />
+ )}
+
+ {current == 2 && (
+
+ )}
+
+ {current === 2 && installStatus === 'RUNNING' ? null : (
+ <>
+ {current !== -1 && (
+
+
+ {current === 2 && installStatus === 'RUNNING' ? null : (
+
+ )}
+
+ {current < 2 ? (
+ <>
+ {current > 0 && (
+
+
+
+ )}
+
+ {current === 0 && (
+
+ )}
+
+
+
+
+ >
+ ) : (
+ <>
+ {installResult === 'FAILED' ? (
+
+ ) : null}
+ >
+ )}
+
+
+ )}
+ >
+ )}
+
+ );
+};
+
+export default Update;
diff --git a/web/src/pages/OcpInstaller/Welcome/index.tsx b/web/src/pages/OcpInstaller/Welcome/index.tsx
new file mode 100644
index 0000000..da520c6
--- /dev/null
+++ b/web/src/pages/OcpInstaller/Welcome/index.tsx
@@ -0,0 +1,88 @@
+import { intl } from '@/utils/intl';
+import { useEffect } from 'react';
+import { useModel } from 'umi';
+import { Button } from 'antd';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import NP from 'number-precision';
+import { getLocale, history } from 'umi';
+import EnStyles from '../../Obdeploy/indexEn.less';
+import ZhStyles from '../../Obdeploy/indexZh.less';
+
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+
+export default function Welcome() {
+ const { setCurrentStep, setErrorVisible, setErrorsList } = useModel('global');
+ let Video: any;
+
+ const aspectRatio = NP.divide(2498, 3940).toFixed(10);
+
+ const screenWidth = window.innerWidth * 1.3;
+ let videoWidth = 0;
+ let videoHeight = 0;
+
+ if (screenWidth < 1040) {
+ videoWidth = 1040;
+ } else {
+ videoWidth = screenWidth;
+ }
+
+ videoHeight = Math.ceil(NP.times(videoWidth, aspectRatio));
+
+ useEffect(() => {
+ const welcomeVideo = document.querySelector('.welcome-video');
+ if (welcomeVideo) {
+ Video = videojs(welcomeVideo, {
+ controls: false,
+ autoplay: true,
+ loop: true,
+ preload: 'auto',
+ });
+ }
+ return () => {
+ Video.dispose();
+ };
+ }, []);
+
+ return (
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.OcpInstaller.Welcome.WelcomeToTheOcpUpgrade',
+ defaultMessage: '欢迎您使用 OCP 升级向导',
+ })}
+
+
OceanBase Cloud Platfrom upgrade wizard
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/pages/constants/index.ts b/web/src/pages/constants/index.ts
index 2995ad7..0ba374f 100644
--- a/web/src/pages/constants/index.ts
+++ b/web/src/pages/constants/index.ts
@@ -1,8 +1,11 @@
import { intl } from '@/utils/intl';
export const commonStyle = { width: 216 };
+export const TIME_REFRESH = 5000;
+export const STABLE_OCP_VERSION = '421';
export const oceanbaseComponent = 'oceanbase';
export const obproxyComponent = 'obproxy';
export const ocpexpressComponent = 'ocp-express';
+export const ocpComponent = 'ocpserver';
export const obagentComponent = 'obagent';
export const ocpexpressComponentKey = 'ocpexpress';
@@ -101,6 +104,19 @@ export const componentsConfig = {
defaultMessage: 'OCP Express 参数名称',
}),
},
+ [ocpComponent]: {
+ name: 'OCP',
+ showComponentName: 'OCP',
+ type: intl.formatMessage({
+ id: 'OBD.pages.constants.Tools',
+ defaultMessage: '工具',
+ }),
+ componentKey: ocpexpressComponentKey,
+ labelName: intl.formatMessage({
+ id: 'OBD.pages.constants.OcpParameterName',
+ defaultMessage: 'OCP 参数名称',
+ }),
+ },
};
export const modeConfig = {
@@ -124,3 +140,87 @@ export const pathRule = {
'以 “/” 开头的绝对路径,只能包含字母、数字和特殊字符(~@%^_+=(){}[]:,./)',
}),
};
+//https://www.oceanbase.com/docs/community-ocp-cn-1000000000261244
+export const resourceMap = {
+ metaDB: [
+ {
+ hosts: 10,
+ cpu: 2,
+ memory: 4,
+ },
+ {
+ hosts: 50,
+ cpu: 4,
+ memory: 8,
+ },
+ {
+ hosts: 100,
+ cpu: 8,
+ memory: 16,
+ },
+ {
+ hosts: 200,
+ cpu: 16,
+ memory: 32,
+ },
+ {
+ hosts: 400,
+ cpu: 32,
+ memory: 64,
+ },
+ ],
+ monitorDB: [
+ {
+ hosts: 10,
+ cpu: 2,
+ memory: 8,
+ },
+ {
+ hosts: 50,
+ cpu: 4,
+ memory: 32,
+ },
+ {
+ hosts: 100,
+ cpu: 8,
+ memory: 64,
+ },
+ {
+ hosts: 200,
+ cpu: 16,
+ memory: 128,
+ },
+ {
+ hosts: 400,
+ cpu: 32,
+ memory: 256,
+ },
+ ],
+ OCP: [
+ {
+ hosts: 10,
+ cpu: 2,
+ memory: 4,
+ },
+ {
+ hosts: 50,
+ cpu: 4,
+ memory: 8,
+ },
+ {
+ hosts: 100,
+ cpu: 8,
+ memory: 16,
+ },
+ {
+ hosts: 200,
+ cpu: 16,
+ memory: 32,
+ },
+ {
+ hosts: 400,
+ cpu: 32,
+ memory: 64,
+ },
+ ],
+};
diff --git a/web/src/pages/index.less b/web/src/pages/index.less
index 5b6006b..e69de29 100644
--- a/web/src/pages/index.less
+++ b/web/src/pages/index.less
@@ -1,66 +0,0 @@
-@backgroundColor: #f5f8ff;
-
-.container {
- height: 100%;
-}
-
-.englishContainer {
- font-family: SourceSansPro-Semibold, SourceSansPro-Regular;
-}
-
-.pageHeader {
- position: fixed;
- top: 0;
- right: 0;
- left: 0;
- z-index: 99;
- height: 48px;
- padding: 0 20px;
- overflow: hidden;
- line-height: 48px;
- background-color: @backgroundColor;
- border-bottom: 1px solid #dde4ed;
- .logo {
- position: relative;
- top: -2px;
- width: 125px;
- vertical-align: middle;
- }
- .logoText {
- margin-left: 8px;
- font-size: 14px;
- }
- .actionContent {
- float: right;
- }
- .action {
- color: #5c6b8a;
- }
- .actionIcon {
- margin-right: 10px;
- }
-}
-
-.pageContainer {
- min-height: calc(100% - 240px);
- padding-top: 170px;
- padding-bottom: 70px;
- background-color: @backgroundColor;
- .pageMain {
- .pageContent {
- width: 1040px;
- margin: 0 auto;
- overflow: auto;
- }
- }
-}
-
-.mask {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 100;
- background: rgba(0, 0, 0, 0.45);
-}
diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx
index 799eb5f..1b3220b 100644
--- a/web/src/pages/index.tsx
+++ b/web/src/pages/index.tsx
@@ -1,309 +1,108 @@
+import { getLocale,history } from 'umi';
import { intl } from '@/utils/intl';
-import { useEffect, useState } from 'react';
-import { useModel } from 'umi';
-import { Space, ConfigProvider, notification, Dropdown, Modal } from 'antd';
-import {
- HomeOutlined,
- ReadOutlined,
- ProfileOutlined,
- GlobalOutlined,
- InfoCircleOutlined,
-} from '@ant-design/icons';
-import useRequest from '@/utils/useRequest';
-import { getErrorInfo, getRandomPassword } from '@/utils';
-import { getDeployment } from '@/services/ob-deploy-web/Deployments';
-import { validateOrSetKeepAliveToken } from '@/services/ob-deploy-web/Common';
-import Welcome from './components/Welcome';
-import InstallConfig from './components/InstallConfig';
-import NodeConfig from './components/NodeConfig';
-import ClusterConfig from './components/ClusterConfig';
-import PreCheck from './components/PreCheck';
-import InstallProcess from './components/InstallProcess';
-import InstallFinished from './components/InstallFinished';
-import ExitPage from './components/ExitPage';
-import ProgressQuit from './components/ProgressQuit';
-import Steps from './components/Steps';
-import { localeList, localeText } from '@/constants';
-import type { Locale } from 'antd/es/locale';
-import { setLocale, getLocale } from 'umi';
-import enUS from 'antd/es/locale/en_US';
-import zhCN from 'antd/es/locale/zh_CN';
-import theme from './theme';
-import styles from './index.less';
+import NP from 'number-precision';
+import videojs from 'video.js';
+import 'video.js/dist/video-js.css';
+import { useEffect } from 'react';
+import { Button } from 'antd';
-export default function IndexPage() {
- const uuid = window.localStorage.getItem('uuid');
- const locale = getLocale();
- const {
- setCurrentStep,
- setConfigData,
- currentStep,
- errorVisible,
- errorsList,
- setErrorVisible,
- setErrorsList,
- } = useModel('global');
- const [lastError, setLastError] = useState({});
- const [first, setFirst] = useState(true);
- const [token, setToken] = useState('');
- const [isInstall, setIsInstall] = useState(false);
- const [localeConfig, setLocalConfig] = useState(
- locale === 'zh-CN' ? zhCN : enUS,
- );
+import EnStyles from './Obdeploy/indexEn.less';
+import ZhStyles from './Obdeploy/indexZh.less';
- const { run: fetchDeploymentInfo } = useRequest(getDeployment, {
- onError: (e: any) => {
- const errorInfo = getErrorInfo(e);
- setErrorVisible(true);
- setErrorsList([...errorsList, errorInfo]);
- },
- });
+const locale = getLocale();
+const styles = locale === 'zh-CN' ? ZhStyles : EnStyles;
+export default function IndexPage(){
+ let Video: any;
- const { run: handleValidateOrSetKeepAliveToken } = useRequest(
- validateOrSetKeepAliveToken,
- {
- onSuccess: ({ success, data }: API.OBResponse) => {
- if (success) {
- if (!data) {
- if (first) {
- Modal.confirm({
- className: 'new-page-confirm',
- title: intl.formatMessage({
- id: 'OBD.src.pages.ItIsDetectedThatYou',
- defaultMessage:
- '检测到您打开了一个新的部署流程页面,请确认是否使用新页面继续部署工作?',
- }),
- width: 424,
- icon: ,
- content: intl.formatMessage({
- id: 'OBD.src.pages.UseTheNewPageTo',
- defaultMessage:
- '使用新的页面部署,原部署页面将无法再提交任何部署请求',
- }),
- onOk: () => {
- handleValidateOrSetKeepAliveToken({ token, overwrite: true });
- },
- onCancel: () => {
- setCurrentStep(8);
- },
- });
- setTimeout(() => {
- document.activeElement.blur();
- }, 100);
- } else {
- setCurrentStep(8);
- }
- } else if (currentStep > 4) {
- if (!isInstall) {
- handleValidateOrSetKeepAliveToken({
- token: token,
- is_clear: true,
- });
- setIsInstall(true);
- }
- } else {
- setTimeout(() => {
- handleValidateOrSetKeepAliveToken({ token });
- }, 1000);
- }
- setFirst(false);
- }
- },
- onError: () => {
- if (currentStep > 4) {
- handleValidateOrSetKeepAliveToken({ token: token, is_clear: true });
- } else {
- setTimeout(() => {
- handleValidateOrSetKeepAliveToken({ token });
- }, 1000);
- }
- },
- },
- );
+ const aspectRatio = NP.divide(2498, 3940).toFixed(10);
- const setCurrentLocale = (key: string) => {
- if (key !== locale) {
- setLocale(key);
- window.localStorage.setItem('uuid', token);
- }
- setLocalConfig(key === 'zh-CN' ? zhCN : enUS);
- };
-
- const getLocaleItems = () => {
- return localeList.map((item) => ({
- ...item,
- label: setCurrentLocale(item.key)}>{item.label},
- }));
- };
+ const screenWidth = window.innerWidth * 1.3;
+ let videoWidth = 0;
+ let videoHeight = 0;
- const contentConfig = {
- 1: ,
- 2: ,
- 3: ,
- 4: ,
- 5: ,
- 6: ,
- 7: ,
- 8: ,
- };
+ if (screenWidth < 1040) {
+ videoWidth = 1040;
+ } else {
+ videoWidth = screenWidth;
+ }
+ videoHeight = Math.ceil(NP.times(videoWidth, aspectRatio));
useEffect(() => {
- let token = '';
-
- fetchDeploymentInfo({ task_status: 'INSTALLING' }).then(
- ({ success, data }: API.OBResponse) => {
- if (success && data?.items?.length) {
- setCurrentStep(5);
- setConfigData({
- components: { oceanbase: { appname: data?.items[0]?.name } },
- });
- } else {
- if (uuid) {
- token = uuid;
- } else {
- token = `${Date.now()}${getRandomPassword(true)}`;
- }
- setToken(token);
- handleValidateOrSetKeepAliveToken({ token });
- window.localStorage.setItem('uuid', '');
- }
- },
- );
- const sendBeacon = () => {
- const url =
- window.location.origin +
- '/api/v1/connect/keep_alive?token=' +
- token +
- '&is_clear=true';
- navigator.sendBeacon(url);
+ const welcomeVideo = document.querySelector('.welcome-video');
+ if (welcomeVideo) {
+ Video = videojs(welcomeVideo, {
+ controls: false,
+ autoplay: true,
+ loop: true,
+ preload: 'auto',
+ });
+ }
+ return () => {
+ Video.dispose();
};
- window.addEventListener('beforeunload', function (e) {
- sendBeacon();
- });
}, []);
-
- useEffect(() => {
- const newLastError = errorsList?.[errorsList?.length - 1] || null;
- if (errorVisible) {
- if (newLastError?.desc !== lastError?.desc) {
- notification.error({
- description: newLastError?.desc,
- message: newLastError?.title,
- duration: null,
- });
- }
- } else {
- notification.destroy();
- }
- setLastError(newLastError);
- }, [errorVisible, errorsList, lastError]);
-
- const containerStyle = {
- minHeight: `${currentStep < 6 ? 'calc(100% - 240px)' : 'calc(100% - 140px)'
- }`,
- paddingTop: `${currentStep < 6 ? '170px' : '70px'}`,
- };
-
- return (
-
-
-
-
-
- {intl.formatMessage({
- id: 'OBD.src.pages.DeploymentWizard',
- defaultMessage: '部署向导',
- })}
-
-
-
- e.preventDefault()}
- data-aspm-click="c307509.d326700"
+ return(
+
+
+
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.Welcome.WelcomeToDeploy',
+ defaultMessage: '欢迎您部署',
+ })}
+
+ {locale === 'zh-CN' ? (
+
+ OceanBase
+ {intl.formatMessage({
+ id: 'OBD.pages.components.Welcome.DistributedDatabase',
+ defaultMessage: '分布式数据库',
+ })}
+
+ ) : (
+
+ {intl.formatMessage({
+ id: 'OBD.pages.components.Welcome.DistributedDatabase',
+ defaultMessage: '分布式数据库',
+ })}
+
+ )}
+
OceanBase comprehensive database
+
- )}
+
+
-
- );
-}
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/type.ts b/web/src/pages/type.ts
new file mode 100644
index 0000000..8b26cc3
--- /dev/null
+++ b/web/src/pages/type.ts
@@ -0,0 +1,8 @@
+// 通用type
+export type PathType =
+ | 'configuration'
+ | 'ocpInstaller'
+ | 'guide'
+ | 'install'
+ | 'obdeploy'
+ | 'update';
diff --git a/web/src/services/ocp_installer_backend/Info.ts b/web/src/services/ocp_installer_backend/Info.ts
new file mode 100644
index 0000000..8bfdf60
--- /dev/null
+++ b/web/src/services/ocp_installer_backend/Info.ts
@@ -0,0 +1,19 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+import { request } from 'umi';
+
+/** get_server_info get server info GET /api/v1/info */
+export async function getServerInfo(
+ params: {
+ // path
+ /** deployment id */
+ cluster_name?: string;
+ },
+ options?: { [key: string]: any },
+) {
+ const { cluster_name } = params;
+ return request
(`/api/v1/upgrade/info/${cluster_name}`, {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
diff --git a/web/src/services/ocp_installer_backend/Metadb.ts b/web/src/services/ocp_installer_backend/Metadb.ts
new file mode 100644
index 0000000..94f49d4
--- /dev/null
+++ b/web/src/services/ocp_installer_backend/Metadb.ts
@@ -0,0 +1,427 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+import { request } from 'umi';
+
+/** list_metadb_deployments list metadb deployments GET /api/v1/metadb/deployments */
+export async function listMetadbDeployments(options?: { [key: string]: any }) {
+ return request(
+ '/api/v1/metadb/deployments',
+ {
+ method: 'GET',
+ ...(options || {}),
+ },
+ );
+}
+
+/** create_metadb_deployment create deployment for metadb POST /api/v1/metadb/deployments */
+export async function createMetadbDeployment(
+ body?: API.MetadbDeploymentConfig,
+ options?: { [key: string]: any },
+) {
+ return request(
+ '/api/v1/metadb/deployments',
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_deployment get metadb deployments GET /api/v1/metadb/deployments/${param0} */
+export async function getMetadbDeployment(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** destroy_metadb destroy metadb DELETE /api/v1/metadb/deployments/${param0} */
+export async function destroyMetadb(
+ params: {
+ // path
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}`,
+ {
+ method: 'DELETE',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** check machine resource check path for check GET /api/v1/metadb/deployments/${param0}/resource_check */
+export async function checkMachineResource(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/resource_check`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+export async function checkOperatingUser(
+ body?: API.OperatingUser,
+ options?: { [key: string]: any },
+) {
+ return request('/api/v1/machine/check/user', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** get_metadb_deployment_resource get server resource for metadb deployment GET /api/v1/metadb/deployments/${param0}/resource */
+export async function getMetadbDeploymentResource(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/resource`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** precheck_metadb_deployment precheck for metadb deployment POST /api/v1/metadb/deployments/${param0}/precheck */
+export async function precheckMetadbDeployment(
+ params: {
+ // path
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/precheck`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_precheck_task precheck for metadb deployment GET /api/v1/metadb/deployments/${param0}/precheck/${param1} */
+export async function getMetadbPrecheckTask(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/precheck/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** recover_metadb_deployment recover metadb deployment config POST /api/v1/metadb/deployments/${param0}/recover */
+export async function recoverMetadbDeployment(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/recover`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** install_metadb install metadb POST /api/v1/metadb/deployments/${param0}/install */
+export async function installMetadb(
+ params: {
+ // path
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/install`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_install_task get metadb install task GET /api/v1/metadb/deployments/${param0}/install/${param1} */
+export async function getMetadbInstallTask(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/install/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_install_task_log get metadb install task log GET /api/v1/metadb/deployments/${param0}/install/${param1}/log */
+export async function getMetadbInstallTaskLog(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/install/${param1}/log`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** reinstall_metadb reinstall metadb POST /api/v1/metadb/deployments/${param0}/reinstall */
+export async function reinstallMetadb(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/reinstall`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_reinstall_task get metadb reinstall task GET /api/v1/metadb/deployments/${param0}/reinstall/${param1} */
+export async function getMetadbReinstallTask(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/reinstall/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_reinstall_task_log get metadb reinstall task log GET /api/v1/metadb/deployments/${param0}/reinstall/${param1}/log */
+export async function getMetadbReinstallTaskLog(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/reinstall/${param1}/log`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_reinstall_task_report get metadb reinstall task report GET /api/v1/metadb/deployments/${param0}/reinstall/${param1}/report */
+export async function getMetadbReinstallTaskReport(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/reinstall/${param1}/report`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_destroy_task get metadb destroy task GET /api/v1/metadb/deployments/${param0}/destroy/${param1} */
+export async function getMetadbDestroyTask(
+ params: {
+ // path
+ id?: number;
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/metadb/deployments/${param0}/destroy/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** list_metadb_connection list metadb connection GET /api/v1/metadb/connections */
+export async function listMetadbConnection(options?: { [key: string]: any }) {
+ return request(
+ '/api/v1/metadb/connections',
+ {
+ method: 'GET',
+ ...(options || {}),
+ },
+ );
+}
+
+/** create_metadb_connection create metadb connection POST /api/v1/metadb/connections */
+export async function createMetadbConnection(
+ params: {
+ // query
+ /** whether the incoming tenant is the sys tenant */
+ sys?: boolean;
+ },
+ body?: API.DatabaseConnection,
+ options?: { [key: string]: any },
+) {
+ return request(
+ '/api/v1/metadb/connections',
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ params: {
+ ...params,
+ },
+ data: body,
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_metadb_connection get metadb connection GET /api/v1/metadb/connections/${param0} */
+export async function getMetadbConnection(
+ params: {
+ // path
+ /** connection id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/metadb/connections/${param0}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
diff --git a/web/src/services/ocp_installer_backend/OCP.ts b/web/src/services/ocp_installer_backend/OCP.ts
new file mode 100644
index 0000000..41f9eb3
--- /dev/null
+++ b/web/src/services/ocp_installer_backend/OCP.ts
@@ -0,0 +1,644 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+import { request } from 'umi';
+
+/** get_installed_ocp_info get_installed_ocp_info GET /api/v1/ocp/info/${param0} */
+export async function getInstalledOcpInfo(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/info/${param0}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** list_ocp_deployments list ocp deployments GET /api/v1/ocp/deployments */
+export async function listOcpDeployments(options?: { [key: string]: any }) {
+ return request(
+ '/api/v1/ocp/deployments',
+ {
+ method: 'GET',
+ ...(options || {}),
+ },
+ );
+}
+
+/** create_ocp_deployment create deployment for ocp POST /api/v1/ocp/deployments */
+export async function createOcpDeployment(
+ body?: API.OcpDeploymentConfig,
+ options?: { [key: string]: any },
+) {
+ return request('/api/v1/ocp/deployments', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** get_ocp_deployment get ocp deployment GET /api/v1/ocp/deployments/${param0} */
+export async function getOcpDeployment(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** destroy_ocp destroy ocp DELETE /api/v1/ocp/deployments/${param0} */
+export async function destroyOcp(
+ params: {
+ // path
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}`,
+ {
+ method: 'DELETE',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_deployment_resource get server resource for ocp deployment GET /api/v1/ocp/deployments/${param0}/resource */
+export async function getOcpDeploymentResource(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/resource`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+export async function createOcpDeploymentConfig(
+ // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+ params: API.createDeploymentConfigParams,
+ body: API.DeploymentConfig,
+ options?: { [key: string]: any },
+) {
+ const { name: param0, ...queryParams } = params;
+ return request(`/api/v1/ocp_deployments/${param0}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ params: { ...queryParams },
+ data: body,
+ ...(options || {}),
+ });
+}
+/** precheck_ocp_deployment precheck for ocp deployment POST /api/v1/ocp/deployments/${param0}/precheck */
+export async function precheckOcpDeployment(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/precheck`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** precheck_ocp precheck for ocp deployment GET /api/v1/ocp/deployments/${param0}/precheck/${param1} */
+export async function precheckOcp(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/precheck/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** recover_ocp_deployment recover ocp deployment config POST /api/v1/ocp/deployments/${param0}/recover */
+export async function recoverOcpDeployment(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/recover`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** install_ocp install ocp POST /api/v1/ocp/deployments/${param0}/install */
+export async function installOcp(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/install`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_install_task get ocp install task GET /api/v1/ocp/deployments/${param0}/install/${param1} */
+export async function getOcpInstallTask(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/install/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_install_task_log get ocp install task log GET /api/v1/ocp/deployments/${param0}/install/${param1}/log */
+export async function getOcpInstallTaskLog(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/install/${param1}/log`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_install_task_report get ocp install task report GET /api/v1/ocp/deployments/${param0}/install/${param1}/report */
+export async function getOcpInstallTaskReport(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/install/${param1}/report`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** reinstall_ocp reinstall ocp POST /api/v1/ocp/deployments/${param0}/reinstall */
+export async function reinstallOcp(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/reinstall`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_reinstall_task get ocp reinstall task GET /api/v1/ocp/deployments/${param0}/reinstall/${param1} */
+export async function getOcpReinstallTask(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/reinstall/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_reinstall_task_log get ocp reinstall task log GET /api/v1/ocp/deployments/${param0}/reinstall/${param1}/log */
+export async function getOcpReinstallTaskLog(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/reinstall/${param1}/log`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_reinstall_task_report get ocp reinstall task report GET /api/v1/ocp/deployments/${param0}/reinstall/${param1}/report */
+export async function getOcpReinstallTaskReport(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/reinstall/${param1}/report`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_destroy_task get ocp destroy task GET /api/v1/ocp/deployments/${param0}/destroy/${param1} */
+export async function getOcpDestroyTask(
+ params: {
+ // path
+ /** deployment id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/ocp/deployments/${param0}/destroy/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** create_ocp_info create ocp info POST /api/v1/ocp */
+export async function createOcpInfo(
+ body?: API.DatabaseConnection,
+ options?: { [key: string]: any },
+) {
+ return request('/api/v1/ocp', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** get_ocp_info get ocp info GET /api/v1/ocp/${param0} */
+export async function getOcpInfo(
+ params: {
+ // path
+ /** ocp id */
+ // id?: number;
+ cluster_name?: string;
+ },
+ options?: { [key: string]: any },
+) {
+ const { cluster_name: param0 } = params;
+ return request(`/api/v1/ocp/${param0}`, {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ });
+}
+
+/** precheck_ocp_upgrade post precheck for ocp upgrade POST /api/v1/ocp/${param0}/upgrade/precheck */
+export async function precheckOcpUpgrade(
+ params: {
+ // path
+ /** deployment id */
+ // id?: number;
+ cluster_name?: string;
+ },
+ options?: { [key: string]: any },
+) {
+ const { cluster_name: param0 } = params;
+ return request(
+ `/api/v1/ocp/${param0}/upgrade/precheck`,
+ {
+ method: 'POST',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_upgrade_precheck_task get precheck for ocp upgrade GET /api/v1/ocp/${param0}/upgrade/precheck/${param1} */
+export async function getOcpUpgradePrecheckTask(
+ params: {
+ // path
+ /** ocp id */
+ // id?: number;
+ cluster_name: string;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { cluster_name: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/ocp/${param0}/upgrade/precheck/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** upgrade_ocp upgrade ocp POST /api/v1/ocp/${param0}/upgrade */
+export async function upgradeOcp(
+ params: {
+ // query
+ /** ocp upgrade version */
+ version: string;
+ /** ocp upgrade hash */
+ usable: string;
+ // path
+ // /** ocp id */
+ // id?: number;
+ cluster_name?: string;
+ },
+ options?: { [key: string]: any },
+) {
+ const { cluster_name: param0, ...queryParams } = params;
+ return request(`/api/v1/ocp/${param0}/upgrade`, {
+ method: 'POST',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ });
+}
+
+/** get_ocp_upgrade_task get ocp upgrade task GET /api/v1/ocp/${param0}/upgrade/${param1} */
+export async function getOcpUpgradeTask(
+ params: {
+ // path
+ /** ocp id */
+ cluster_name?: string;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { cluster_name: param0, task_id: param1 } = params;
+ return request(
+ `/api/v1/ocp/${param0}/upgrade/${param1}`,
+ {
+ method: 'GET',
+ params: { ...params },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_upgrade_task_log get ocp upgrade task log GET /api/v1/ocp/${param0}/upgrade/${param1}/log */
+export async function getOcpUpgradeTaskLog(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ cluster_name?: string;
+ /** ocp id */
+ // id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { cluster_name: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/ocp/${param0}/upgrade/${param1}/log`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_upgrade_task_report get ocp upgrade task report GET /api/v1/ocp/${param0}/upgrade/${param1}/report */
+export async function getOcpUpgradeTaskReport(
+ params: {
+ // query
+ /** offset to read task log */
+ offset?: number;
+ // path
+ /** ocp id */
+ id?: number;
+ /** task id */
+ task_id?: number;
+ },
+ options?: { [key: string]: any },
+) {
+ const { id: param0, task_id: param1, ...queryParams } = params;
+ return request(
+ `/api/v1/ocp/${param0}/upgrade/${param1}/report`,
+ {
+ method: 'GET',
+ params: {
+ ...queryParams,
+ },
+ ...(options || {}),
+ },
+ );
+}
+
+/** get_ocp_not_upgrading_host get ocp not upgrading host GET /api/v1/ocp/upgraade/agent/hosts */
+export async function getOcpNotUpgradingHost(options?: { [key: string]: any }) {
+ return request(
+ '/api/v1/ocp/upgraade/agent/hosts',
+ {
+ method: 'GET',
+ ...(options || {}),
+ },
+ );
+}
+
+export async function getClusterNames(options?: { [key: string]: any }) {
+ return request