未验证 提交 bacf661a 编写于 作者: L LIN 提交者: GitHub

Merge pull request #135 from frf12/v1.6.0

V1.6.0
......@@ -25,15 +25,17 @@ import os
import sys
import time
import logging
import textwrap
from logging import handlers
from uuid import uuid1 as uuid
from optparse import OptionParser, OptionGroup, BadOptionError, Option
from optparse import OptionParser, OptionGroup, BadOptionError, Option, IndentedHelpFormatter
from core import ObdHome
from _stdio import IO
from log import Logger
from tool import DirectoryUtil, FileUtil, COMMAND_ENV
from _errno import DOC_LINK_MSG, LockError
from _environ import ENV_DEV_MODE
ROOT_IO = IO(1)
......@@ -49,7 +51,32 @@ FORBIDDEN_VARS = (CONST_OBD_HOME, CONST_OBD_INSTALL_PRE)
OBD_HOME_PATH = os.path.join(os.environ.get(CONST_OBD_HOME, os.getenv('HOME')), '.obd')
COMMAND_ENV.load(os.path.join(OBD_HOME_PATH, '.obd_environ'), ROOT_IO)
DEV_MODE = "OBD_DEV_MODE"
class OptionHelpFormatter(IndentedHelpFormatter):
def format_option(self, option):
result = []
opts = self.option_strings[option]
opt_width = self.help_position - self.current_indent - 2
if len(opts) > opt_width:
opts = "%*s%s\n" % (self.current_indent, "", opts)
indent_first = self.help_position
else: # start help on same line as opts
opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
indent_first = 0
result.append(opts)
if option.help:
help_text = self.expand_default(option)
help_lines = help_text.split('\n')
if len(help_lines) == 1:
help_lines = textwrap.wrap(help_text, self.help_width)
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
result.extend(["%*s%s\n" % (self.help_position, "", line)
for line in help_lines[1:]])
elif opts[-1] != "\n":
result.append("\n")
return "".join(result)
class AllowUndefinedOptionParser(OptionParser):
......@@ -66,12 +93,15 @@ class AllowUndefinedOptionParser(OptionParser):
add_help_option=True,
prog=None,
epilog=None,
allow_undefine=True):
allow_undefine=True,
undefine_warn=True
):
OptionParser.__init__(
self, usage, option_list, option_class, version, conflict_handler,
description, formatter, add_help_option, prog, epilog
)
self.allow_undefine = allow_undefine
self.undefine_warn = undefine_warn
def warn(self, msg, file=None):
if self.IS_TTY:
......@@ -88,7 +118,7 @@ class AllowUndefinedOptionParser(OptionParser):
key = e.opt_str
value = value[len(key)+1:]
setattr(values, key.strip('-').replace('-', '_'), value if value != '' else True)
return self.warn(e)
self.undefine_warn and self.warn(e)
else:
raise e
......@@ -101,7 +131,7 @@ class AllowUndefinedOptionParser(OptionParser):
key = e.opt_str
value = value[len(key)+1:]
setattr(values, key.strip('-').replace('-', '_'), value if value != '' else True)
return self.warn(e)
self.undefine_warn and self.warn(e)
else:
raise e
......@@ -148,7 +178,7 @@ class BaseCommand(object):
self.parser.exit(1)
def _mk_usage(self):
return self.parser.format_help()
return self.parser.format_help(OptionHelpFormatter())
class ObdCommand(BaseCommand):
......@@ -163,7 +193,7 @@ class ObdCommand(BaseCommand):
version_fobj.seek(0)
version = version_fobj.read()
if VERSION != version:
for part in ['plugins', 'config_parser', 'mirror/remote']:
for part in ['plugins', 'config_parser', 'optimize', 'mirror/remote']:
obd_part_dir = os.path.join(self.OBD_PATH, part)
if DirectoryUtil.mkdir(self.OBD_PATH):
root_part_path = os.path.join(self.OBD_INSTALL_PRE, 'usr/obd/', part)
......@@ -177,14 +207,11 @@ class ObdCommand(BaseCommand):
@property
def dev_mode(self):
return COMMAND_ENV.get(DEV_MODE) == "1"
return COMMAND_ENV.get(ENV_DEV_MODE) == "1"
def parse_command(self):
self.parser.allow_undefine = self.dev_mode
return super(ObdCommand, self).parse_command()
def parse_command(self):
self.parser.allow_undefine = self.dev_mode
if self.parser.allow_undefine != True:
self.parser.allow_undefine = self.dev_mode
return super(ObdCommand, self).parse_command()
def do_command(self):
......@@ -196,11 +223,7 @@ class ObdCommand(BaseCommand):
log_dir = os.path.join(self.OBD_PATH, 'log')
DirectoryUtil.mkdir(log_dir)
log_path = os.path.join(log_dir, 'obd')
logger = Logger('obd')
handler = handlers.TimedRotatingFileHandler(log_path, when='midnight', interval=1, backupCount=30)
handler.setFormatter(logging.Formatter("[%%(asctime)s.%%(msecs)03d] [%s] [%%(levelname)s] %%(message)s" % trace_id, "%Y-%m-%d %H:%M:%S"))
logger.addHandler(handler)
ROOT_IO.trace_logger = logger
ROOT_IO.init_trace_logger(log_path, 'obd', trace_id)
obd = ObdHome(self.OBD_PATH, self.dev_mode, ROOT_IO)
ROOT_IO.track_limit += 1
ROOT_IO.verbose('cmd: %s' % self.cmds)
......@@ -294,7 +317,7 @@ class DevModeEnableCommand(HiddenObdCommand):
super(DevModeEnableCommand, self).__init__('enable', 'Enable Dev Mode')
def _do_command(self, obd):
if COMMAND_ENV.set(DEV_MODE, "1", save=True, stdio=obd.stdio):
if COMMAND_ENV.set(ENV_DEV_MODE, "1", save=True, stdio=obd.stdio):
obd.stdio.print("Dev Mode: ON")
return True
return False
......@@ -306,7 +329,7 @@ class DevModeDisableCommand(HiddenObdCommand):
super(DevModeDisableCommand, self).__init__('disable', 'Disable Dev Mode')
def _do_command(self, obd):
if COMMAND_ENV.set(DEV_MODE, "0", save=True, stdio=obd.stdio):
if COMMAND_ENV.set(ENV_DEV_MODE, "0", save=True, stdio=obd.stdio):
obd.stdio.print("Dev Mode: OFF")
return True
return False
......@@ -434,6 +457,11 @@ class MirrorListCommand(ObdCommand):
def __init__(self):
super(MirrorListCommand, self).__init__('list', 'List mirrors.')
def init(self, cmd, args):
super(MirrorListCommand, self).init(cmd, args)
self.parser.set_usage('%s [section name] [options]\n\nExample: %s local' % (self.prev_cmd, self.prev_cmd))
return self
def show_pkg(self, name, pkgs):
ROOT_IO.print_list(
pkgs,
......@@ -469,6 +497,7 @@ class MirrorListCommand(ObdCommand):
lambda x: [x.section_name, x.mirror_type.value, x.enabled, time.strftime("%Y-%m-%d %H:%M", time.localtime(x.repo_age))],
title='Mirror Repository List'
)
ROOT_IO.print("Use `obd mirror list <section name>` for more details")
return True
......@@ -588,6 +617,25 @@ class ClusterCheckForOCPChange(ClusterMirrorCommand):
return self._show_help()
class DemoCommand(ClusterMirrorCommand):
def __init__(self):
super(DemoCommand, self).__init__('demo', 'Quickly start')
self.parser.add_option('-c', '--components', type='string', help="List the components. Multiple components are separated with commas. [oceanbase-ce,obproxy-ce,obagent,prometheus,grafana]\nExample: \nstart oceanbase-ce: obd demo -c oceanbase-ce\n"
+ "start -c oceanbase-ce V3.2.3: obd demo -c oceanbase-ce --oceanbase-ce.version=3.2.3\n"
+ "start oceanbase-ce and obproxy-ce: obd demo -c oceanbase-ce,obproxy-ce", default='oceanbase-ce,obproxy-ce,obagent,prometheus,grafana')
self.parser.allow_undefine = True
self.parser.undefine_warn = False
def _do_command(self, obd):
setattr(self.opts, 'mini', True)
setattr(self.opts, 'force', True)
setattr(self.opts, 'clean', True)
setattr(self.opts, 'force', True)
setattr(self.opts, 'force_delete', True)
return obd.demo(self.opts)
class ClusterAutoDeployCommand(ClusterMirrorCommand):
def __init__(self):
......@@ -931,6 +979,7 @@ class MySQLTestCommand(TestMirrorCommand):
self.parser.add_option('--log-pattern', type='string', help='The pattern for collected servers log ', default='*.log')
self.parser.add_option('--cluster-mode', type='string', help="The mode of mysqltest")
self.parser.add_option('--disable-reboot', action='store_true', help='Never reboot during test.', default=False)
self.parser.add_option('--fast-reboot', action='store_true', help='Reboot using snapshots.', default=False)
def _do_command(self, obd):
if self.cmds:
......@@ -955,14 +1004,16 @@ class SysBenchCommand(TestMirrorCommand):
self.parser.add_option('--sysbench-script-dir', type='string', help='The directory of the sysbench lua script file. [/usr/sysbench/share/sysbench]', default='/usr/sysbench/share/sysbench')
self.parser.add_option('--table-size', type='int', help='Number of data initialized per table. [20000]', default=20000)
self.parser.add_option('--tables', type='int', help='Number of initialization tables. [30]', default=30)
self.parser.add_option('--threads', type='int', help='Number of threads to use. [32]', default=16)
self.parser.add_option('--threads', type='int', help='Number of threads to use. [16]', default=16)
self.parser.add_option('--time', type='int', help='Limit for total execution time in seconds. [60]', default=60)
self.parser.add_option('--interval', type='int', help='Periodically report intermediate statistics with a specified time interval in seconds. 0 disables intermediate reports. [10]', default=10)
self.parser.add_option('--events', type='int', help='Limit for total number of events.')
self.parser.add_option('--rand-type', type='string', help='Random numbers distribution {uniform,gaussian,special,pareto}.')
self.parser.add_option('--percentile', type='int', help='Percentile to calculate in latency statistics. Available values are 1-100. 0 means to disable percentile calculations.')
self.parser.add_option('--skip-trx', dest='{on/off}', type='string', help='Open or close a transaction in a read-only test. ')
self.parser.add_option('--skip-trx', type='string', help='Open or close a transaction in a read-only test. {on/off}')
self.parser.add_option('-O', '--optimization', type='int', help='optimization level {0/1}', default=1)
self.parser.add_option('-S', '--skip-cluster-status-check', action='store_true', help='Skip cluster status check', default=False)
self.parser.add_option('--mysql-ignore-errors', type='string', help='list of errors to ignore, or "all". ', default='1062')
def _do_command(self, obd):
if self.cmds:
......@@ -993,6 +1044,7 @@ class TPCHCommand(TestMirrorCommand):
self.parser.add_option('--dss-config', type='string', help='Directory for dists.dss. [/usr/tpc-h-tools/tpc-h-tools]', default='/usr/tpc-h-tools/tpc-h-tools/')
self.parser.add_option('-O', '--optimization', type='int', help='Optimization level {0/1}. [1]', default=1)
self.parser.add_option('--test-only', action='store_true', help='Only testing SQLs are executed. No initialization is executed.')
self.parser.add_option('-S', '--skip-cluster-status-check', action='store_true', help='Skip cluster status check', default=False)
def _do_command(self, obd):
if self.cmds:
......@@ -1001,6 +1053,41 @@ class TPCHCommand(TestMirrorCommand):
return self._show_help()
class TPCDSCommand(TestMirrorCommand):
def __init__(self):
super(TPCDSCommand, self).__init__('tpcds', 'Run a TPC-DS test for a deployment.')
self.parser.add_option('--component', type='string', help='Components for a test.')
self.parser.add_option('--test-server', type='string', help='The server for a test. By default, the first root server in the component is the test server.')
self.parser.add_option('--user', type='string', help='Username for a test.')
self.parser.add_option('--password', type='string', help='Password for a test.')
self.parser.add_option('-t', '--tenant', type='string', help='Tenant for a test. [test]', default='test')
self.parser.add_option('--mode', type='string', help='Tenant compatibility mode. {mysql,oracle} [mysql]', default='mysql')
self.parser.add_option('--database', type='string', help='Database for a test. [test]', default='test')
self.parser.add_option('--obclient-bin', type='string', help='OBClient bin path. [obclient]', default='obclient')
self.parser.add_option('--tool-dir', type='string', help='tpc-ds tool dir. [/usr/tpc-ds-tools]')
self.parser.add_option('--dsdgen-bin', type='string', help='dsdgen bin path. [$TOOL_DIR/bin/dsdgen]')
self.parser.add_option('--idx-file', type='string', help='tpcds.idx file path. [$TOOL_DIR/bin/tpcds.idx]')
self.parser.add_option('--dsqgen-bin', type='string', help='dsqgen bin path. [$TOOL_DIR/bin/dsqgen]')
self.parser.add_option('--query-templates-dir', type='string', help='Query templates dir. [$TOOL_DIR/query_templates]')
self.parser.add_option('-s', '--scale', type='int', help='Set Scale Factor (SF) to <n>. [1] ', default=1)
self.parser.add_option('--disable-generate', '--dg', action='store_true', help='Do not generate test data.')
self.parser.add_option('-p', '--generate-parallel', help='Generate data parallel number. [0]', default=0)
self.parser.add_option('--tmp-dir', type='string', help='The temporary directory for executing TPC-H. [./tmp]', default='./tmp')
self.parser.add_option('--ddl-path', type='string', help='Directory for DDL files.')
self.parser.add_option('--sql-path', type='string', help='Directory for SQL files.')
self.parser.add_option('--create-foreign-key', '--fk', action='store_true', help='create foreign key.')
self.parser.add_option('--foreign-key-file', '--fk-file', action='store_true', help='SQL file for creating foreign key.')
self.parser.add_option('--remote-dir', type='string', help='Directory for the data file on target observers. Make sure that you have read and write access to the directory when you start observer.')
self.parser.add_option('--test-only', action='store_true', help='Only testing SQLs are executed. No initialization is executed.')
def _do_command(self, obd):
if self.cmds:
return obd.tpcds(self.cmds[0], self.opts)
else:
return self._show_help()
class TPCCCommand(TestMirrorCommand):
def __init__(self):
......@@ -1024,6 +1111,7 @@ class TPCCCommand(TestMirrorCommand):
self.parser.add_option('--run-mins', type='int', help='To run for specified minutes.[10]', default=10)
self.parser.add_option('--test-only', action='store_true', help='Only testing SQLs are executed. No initialization is executed.')
self.parser.add_option('-O', '--optimization', type='int', help='Optimization level {0/1/2}. [1] 0 - No optimization. 1 - Optimize some of the parameters which do not need to restart servers. 2 - Optimize all the parameters and maybe RESTART SERVERS for better performance.', default=1)
self.parser.add_option('-S', '--skip-cluster-status-check', action='store_true', help='Skip cluster status check', default=False)
def _do_command(self, obd):
if self.cmds:
......@@ -1040,6 +1128,7 @@ class TestMajorCommand(MajorCommand):
self.register_command(SysBenchCommand())
self.register_command(TPCHCommand())
self.register_command(TPCCCommand())
# self.register_command(TPCDSCommand())
class DbConnectCommand(HiddenObdCommand):
......@@ -1054,8 +1143,7 @@ class DbConnectCommand(HiddenObdCommand):
self.parser.add_option('-c', '--component', type='string', help='The component used by database connection.')
self.parser.add_option('-s', '--server', type='string',
help='The server used by database connection. The first server in the configuration will be used by default')
self.parser.add_option('-u', '--user', type='string', help='The username used by d'
'atabase connection. [root]', default='root')
self.parser.add_option('-u', '--user', type='string', help='The username used by database connection. [root]', default='root')
self.parser.add_option('-p', '--password', type='string', help='The password used by database connection.')
self.parser.add_option('-t', '--tenant', type='string', help='The tenant used by database connection. [sys]', default='sys')
self.parser.add_option('-D', '--database', type='string', help='The database name used by database connection.')
......@@ -1068,6 +1156,30 @@ class DbConnectCommand(HiddenObdCommand):
return self._show_help()
class DoobaCommand(HiddenObdCommand):
def init(self, cmd, args):
super(DoobaCommand, self).init(cmd, args)
self.parser.set_usage('%s <deploy name> [options]' % self.prev_cmd)
return self
def __init__(self):
super(DoobaCommand, self).__init__('dooba', 'A curses powerful tool for OceanBase admin, more than a monitor')
self.parser.add_option('-c', '--component', type='string', help='The component used by database connection.')
self.parser.add_option('-s', '--server', type='string',
help='The server used by database connection. The first server in the configuration will be used by default')
self.parser.add_option('-u', '--user', type='string', help='The username used by database connection. [root]',
default='root')
self.parser.add_option('-p', '--password', type='string', help='The password used by database connection.')
self.parser.add_option('--dooba-bin', type='string', help='Dooba bin path.')
def _do_command(self, obd):
if self.cmds:
return obd.dooba(self.cmds[0], self.opts)
else:
return self._show_help()
class CommandsCommand(HiddenObdCommand):
def init(self, cmd, args):
......@@ -1093,6 +1205,7 @@ class ToolCommand(HiddenMajorCommand):
super(ToolCommand, self).__init__('tool', 'Tools')
self.register_command(DbConnectCommand())
self.register_command(CommandsCommand())
self.register_command(DoobaCommand())
class BenchMajorCommand(MajorCommand):
......@@ -1121,6 +1234,7 @@ class MainCommand(MajorCommand):
def __init__(self):
super(MainCommand, self).__init__('obd', '')
self.register_command(DevModeMajorCommand())
self.register_command(DemoCommand())
self.register_command(MirrorMajorCommand())
self.register_command(ClusterMajorCommand())
self.register_command(RepositoryMajorCommand())
......
......@@ -22,8 +22,10 @@ from __future__ import absolute_import, division, print_function
import os
import re
import sys
import pickle
import getpass
import hashlib
from copy import deepcopy
from enum import Enum
......@@ -33,12 +35,12 @@ from tool import ConfigUtil, FileUtil, YamlLoader, OrderedDict, COMMAND_ENV
from _manager import Manager
from _repository import Repository
from _stdio import SafeStdio
from _environ import ENV_BASE_DIR
yaml = YamlLoader()
DEFAULT_CONFIG_PARSER_MANAGER = None
ENV = 'env'
BASE_DIR_KEY = "OBD_DEPLOY_BASE_DIR"
class ParserError(Exception):
......@@ -383,6 +385,30 @@ class ClusterConfig(object):
self._depends = {}
self.parser = parser
self._has_package_pattern = None
self._object_hash = None
if sys.version_info.major == 2:
def __hash__(self):
if self._object_hash is None:
m_sum = hashlib.md5()
m_sum.update(str(self.package_hash).encode('utf-8'))
m_sum.update(str(self.get_global_conf()).encode('utf-8'))
for server in self.servers:
m_sum.update(str(self.get_server_conf(server)).encode('utf-8'))
m_sum.update(str(self.depends).encode('utf-8'))
self._object_hash = int(''.join(['%03d' % ord(v) for v in m_sum.digest()]))
return self._object_hash
else:
def __hash__(self):
if self._object_hash is None:
m_sum = hashlib.md5()
m_sum.update(str(self.package_hash).encode('utf-8'))
m_sum.update(str(self.get_global_conf()).encode('utf-8'))
for server in self.servers:
m_sum.update(str(self.get_server_conf(server)).encode('utf-8'))
m_sum.update(str(self.depends).encode('utf-8'))
self._object_hash = (int(''.join(['%03d' % v for v in m_sum.digest()])))
return self._object_hash
def __eq__(self, other):
if not isinstance(other, self.__class__):
......@@ -446,6 +472,9 @@ class ClusterConfig(object):
raise Exception('Circular Dependency: %s and %s' % (self.name, name))
self._depends[name] = cluster_conf
def add_depend_component(self, depend_component_name):
return self._deploy_config.add_depend_for_component(self.name, depend_component_name, save=False)
def del_depend(self, name, component_name):
if component_name in self._depends:
del self._depends[component_name]
......@@ -468,6 +497,8 @@ class ClusterConfig(object):
return False
if server not in self._server_conf:
return False
if self._temp_conf and key in self._temp_conf:
value = self._temp_conf[key].param_type(value).value
if not self._deploy_config.update_component_server_conf(self.name, server, key, value, save):
return False
self._server_conf[server][key] = value
......@@ -478,6 +509,8 @@ class ClusterConfig(object):
def update_global_conf(self, key, value, save=True):
if self._deploy_config is None:
return False
if self._temp_conf and key in self._temp_conf:
value = self._temp_conf[key].param_type(value).value
if not self._deploy_config.update_component_global_conf(self.name, key, value, save):
return False
self._update_global_conf(key, value)
......@@ -488,7 +521,8 @@ class ClusterConfig(object):
def _update_global_conf(self, key, value):
self._original_global_conf[key] = value
self._global_conf[key] = value
if self._global_conf:
self._global_conf[key] = value
def update_rsync_list(self, rsync_list, save=True):
if self._deploy_config is None:
......@@ -558,6 +592,13 @@ class ClusterConfig(object):
self._global_conf = None
self._clear_cache_server()
def _apply_temp_conf(self, conf):
if self._temp_conf:
for key in conf:
if key in self._temp_conf:
conf[key] = self._temp_conf[key].param_type(conf[key]).value
return conf
def get_temp_conf_item(self, key):
if self._temp_conf:
return self._temp_conf.get(key)
......@@ -613,7 +654,9 @@ class ClusterConfig(object):
if self._global_conf is None:
self._global_conf = deepcopy(self._default_conf)
self._global_conf.update(self._get_include_config('config', {}))
self._global_conf.update(self._original_global_conf)
if self._original_global_conf:
self._global_conf.update(self._original_global_conf)
self._global_conf = self._apply_temp_conf(self._global_conf)
return self._global_conf
def _add_base_dir(self, path):
......@@ -622,7 +665,7 @@ class ClusterConfig(object):
path = os.path.join(self._base_dir, path)
else:
raise Exception("`{}` need to use absolute paths. If you want to use relative paths, please enable developer mode "
"and set environment variables {}".format(RsyncConfig.RSYNC, BASE_DIR_KEY))
"and set environment variables {}".format(RsyncConfig.RSYNC, ENV_BASE_DIR))
return path
@property
......@@ -717,9 +760,9 @@ class ClusterConfig(object):
if server not in self._server_conf:
return None
if self._cache_server[server] is None:
conf = deepcopy(self._inner_config.get(server.name, {}))
conf = self._apply_temp_conf(deepcopy(self._inner_config.get(server.name, {})))
conf.update(self.get_global_conf())
conf.update(self._server_conf[server])
conf.update(self._apply_temp_conf(self._server_conf[server]))
self._cache_server[server] = conf
return self._cache_server[server]
......@@ -788,7 +831,7 @@ class DeployConfig(SafeStdio):
self.stdio = stdio
self._ignore_include_error = False
if self.config_parser_manager is None:
raise ParserError('ConfigParaserManager Not Set')
raise ParserError('ConfigParserManager Not Set')
self._load()
@property
......@@ -853,32 +896,35 @@ class DeployConfig(SafeStdio):
return False
def _load(self):
with open(self.yaml_path, 'rb') as f:
depends = {}
self._src_data = self.yaml_loader.load(f)
for key in self._src_data:
if key == 'user':
self.set_user_conf(UserConfig(
ConfigUtil.get_value_from_dict(self._src_data[key], 'username'),
ConfigUtil.get_value_from_dict(self._src_data[key], 'password'),
ConfigUtil.get_value_from_dict(self._src_data[key], 'key_file'),
ConfigUtil.get_value_from_dict(self._src_data[key], 'port', 0, int),
ConfigUtil.get_value_from_dict(self._src_data[key], 'timeout', 0, int),
))
elif key == 'unuse_lib_repository':
self.unuse_lib_repository = self._src_data['unuse_lib_repository']
elif key == 'auto_create_tenant':
self.auto_create_tenant = self._src_data['auto_create_tenant']
elif issubclass(type(self._src_data[key]), dict):
self._add_component(key, self._src_data[key])
depends[key] = self._src_data[key].get('depends', [])
for comp in depends:
conf = self.components[comp]
for name in depends[comp]:
if name == comp:
continue
if name in self.components:
conf.add_depend(name, self.components[name])
try:
with open(self.yaml_path, 'rb') as f:
depends = {}
self._src_data = self.yaml_loader.load(f)
for key in self._src_data:
if key == 'user':
self.set_user_conf(UserConfig(
ConfigUtil.get_value_from_dict(self._src_data[key], 'username'),
ConfigUtil.get_value_from_dict(self._src_data[key], 'password'),
ConfigUtil.get_value_from_dict(self._src_data[key], 'key_file'),
ConfigUtil.get_value_from_dict(self._src_data[key], 'port', 0, int),
ConfigUtil.get_value_from_dict(self._src_data[key], 'timeout', 0, int),
))
elif key == 'unuse_lib_repository':
self.unuse_lib_repository = self._src_data['unuse_lib_repository']
elif key == 'auto_create_tenant':
self.auto_create_tenant = self._src_data['auto_create_tenant']
elif issubclass(type(self._src_data[key]), dict):
self._add_component(key, self._src_data[key])
depends[key] = self._src_data[key].get('depends', [])
for comp in depends:
conf = self.components[comp]
for name in depends[comp]:
if name == comp:
continue
if name in self.components:
conf.add_depend(name, self.components[name])
except:
pass
if not self.user:
self.set_user_conf(UserConfig())
......@@ -889,7 +935,7 @@ class DeployConfig(SafeStdio):
def load_include_file(self, path):
if not os.path.isabs(path):
raise Exception("`{}` need to use absolute path. If you want to use relative paths, please enable developer mode "
"and set environment variables {}".format('include', BASE_DIR_KEY))
"and set environment variables {}".format('include', ENV_BASE_DIR))
if os.path.isfile(path):
with open(path, 'rb') as f:
return self.yaml_loader.load(f)
......@@ -909,7 +955,7 @@ class DeployConfig(SafeStdio):
if parser:
inner_config = parser.extract_inner_config(cluster_config, src_data)
self.inner_config.update_component_config(component_name, inner_config)
def _dump_inner_config(self):
if self.inner_config:
self._separate_config()
......
# 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 <https://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
# obd dev mode. {0/1}
ENV_DEV_MODE = "OBD_DEV_MODE"
# base path which will be used by runtime dependencies sync and include config. {absolute path style}
ENV_BASE_DIR = "OBD_DEPLOY_BASE_DIR"
# the installation mode of remote repository. {cp/ln}
ENV_REPO_INSTALL_MODE = "OBD_REPO_INSTALL_MODE"
# disable rsync mode even if the rsync exists. {0/1}
ENV_DISABLE_RSYNC = "OBD_DISABLE_RSYNC"
......@@ -50,7 +50,7 @@ class InitDirFailedErrorMessage(object):
DOC_LINK = '<DOC_LINK>'
DOC_LINK_MSG = 'See {}'.format(DOC_LINK if DOC_LINK else "https://open.oceanbase.com/docs/obd-cn/V1.4.0/10000000000436999 .")
DOC_LINK_MSG = 'See {}'.format(DOC_LINK if DOC_LINK else "https://www.oceanbase.com/product/ob-deployer/error-codes .")
EC_CONFIG_CONFLICT_PORT = OBDErrorCode(1000, 'Configuration conflict {server1}:{port} port is used for {server2}\'s {key}')
EC_CONFLICT_PORT = OBDErrorCode(1001, '{server}:{port} port is already used')
......@@ -76,4 +76,4 @@ EC_OBAGENT_RELOAD_FAILED = OBDErrorCode(4000, 'Fail to reload {server}')
EC_OBAGENT_SEND_CONFIG_FAILED = OBDErrorCode(4001, 'Fail to send config file to {server}')
# WARN CODE
WC_ULIMIT_CHECK = OBDErrorCode(1007, '({server}) The recommended number of {key} is {need} (Current value: %s)')
\ No newline at end of file
WC_ULIMIT_CHECK = OBDErrorCode(1007, '({server}) The recommended number of {key} is {need} (Current value: {now})')
\ No newline at end of file
# 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 <https://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
import enum
import os
from _manager import Manager
from _rpm import Version
from tool import YamlLoader, FileUtil, DynamicLoading
yaml_loader = YamlLoader()
class OptimizeManager(Manager):
RELATIVE_PATH = "optimize/"
def __init__(self, home_path, loader=yaml_loader, stdio=None):
self.loader = loader
self.components = {}
self._parser = None
self._optimize_config = None
super(OptimizeManager, self).__init__(home_path, stdio=stdio)
@property
def optimize_config(self):
if not self._parser:
raise Exception("Optimize parser not load")
return self._parser.optimize_config
def load_config(self, path, stdio=None):
self._optimize_config = None
with FileUtil.open(path, 'rb') as f:
config = self.loader.load(f)
parser_version = config.get("optimize_version", None)
parser = self._get_parser(version=parser_version)
self._parser = parser
self._load_default_optimizers(parser, stdio=stdio)
self._optimize_config = parser.load(config)
def _search_yaml_file(self, component, version, yaml_name, stdio=None):
component_dir = os.path.join(self.path, component)
if not os.path.exists(component_dir):
stdio.verbose("no optimize config for component {}".format(component))
return None
yaml_file = os.path.join(component_dir, version, yaml_name)
if not os.path.exists(yaml_file):
stdio.verbose(
'yaml file {} not found, try to get earlier version.'.format(yaml_file))
final_version = Version('')
versions = sorted([Version(v) for v in os.listdir(component_dir)], reverse=True)
for v in versions:
yaml_file = os.path.join(component_dir, v, yaml_name)
if os.path.exists(yaml_file) and v <= version:
self.stdio.verbose('find earlier version yaml file: {}'.format(yaml_file))
break
else:
yaml_file = os.path.join(component_dir, final_version, yaml_name)
stdio.verbose('try to use top yaml file: {}'.format(yaml_file))
if not os.path.exists(yaml_file):
stdio.verbose('No such yaml file: {}'.format(yaml_file))
return None
return yaml_file
def load_default_config(self, test_name, stdio=None):
self._optimize_config = None
parser = self._get_parser()
self._load_default_optimizers(parser, stdio=stdio)
yaml_name = '{}.yaml'.format(test_name)
for component, version in self.components.items():
config_path = self._search_yaml_file(component, version, yaml_name, stdio=stdio)
if config_path:
with FileUtil.open(config_path, 'rb', stdio=stdio) as f:
config = self.loader.load(f)
parser.load_config_by_component(component, config, stdio=stdio)
self._parser = parser
def _load_default_optimizers(self, parser, stdio=None):
yaml_name = 'optimizer.yaml'
for component, version in self.components.items():
optimizer_path = self._search_yaml_file(component, version, yaml_name, stdio=stdio)
if optimizer_path:
with FileUtil.open(optimizer_path, 'rb') as f:
config = self.loader.load(f)
parser.load_optimizer_by_component(component, config, stdio=stdio)
@staticmethod
def _get_latest_version(path):
latest_version = Version('')
for name in os.listdir(path):
latest_version = max(latest_version, Version(name))
return latest_version
def _get_parser(self, version=None):
if self._parser:
return self._parser
module_name = 'optimize_parser'
class_name = 'OptimizeParser'
file_name = '{}.py'.format(module_name)
parser_base = os.path.join(self.path, module_name)
if version is None:
version = self._get_latest_version(parser_base)
lib_path = os.path.join(parser_base, version)
path = os.path.join(lib_path, file_name)
if os.path.isfile(path):
DynamicLoading.add_lib_path(lib_path)
self.stdio.verbose('load optimize parser: {}'.format(path))
module = DynamicLoading.import_module(module_name, self.stdio)
try:
self._parser = getattr(module, class_name)()
return self._parser
except:
self.stdio.exception("")
return None
finally:
DynamicLoading.remove_lib_path(lib_path)
else:
self.stdio.verbose('No such optimize parser: {}'.format(path))
return None
def register_component(self, name, version):
self.stdio.verbose('register component {}-{} to optimize manager'.format(name, version))
self.components[name] = Version(version)
......@@ -30,7 +30,7 @@ from copy import deepcopy
from _manager import Manager
from _rpm import Version
from ssh import ConcurrentExecutor
from tool import ConfigUtil, DynamicLoading, YamlLoader
from tool import ConfigUtil, DynamicLoading, YamlLoader, FileUtil
yaml = YamlLoader()
......@@ -38,9 +38,11 @@ yaml = YamlLoader()
class PluginType(Enum):
# 插件类型 = 插件加载类
START = 'StartPlugin'
PARAM = 'ParamPlugin'
INSTALL = 'InstallPlugin'
SNAP_CONFIG = 'SnapConfigPlugin'
PY_SCRIPT = 'PyScriptPlugin'
......@@ -125,7 +127,7 @@ class PluginContext(object):
self.options = options
self.dev_mode = dev_mode
self.stdio = stdio
self.concurrent_exector = ConcurrentExecutor(32)
self.concurrent_executor = ConcurrentExecutor(32)
self._return = PluginReturn()
def get_return(self):
......@@ -265,18 +267,28 @@ class PyScriptPlugin(ScriptPlugin):
# def init(self, components, ssh_clients, cluster_config, cmd, options, stdio, *arg, **kwargs):
# pass
class Null(object):
def __init__(self):
pass
class ParamPlugin(Plugin):
class ConfigItemType(object):
TYPE_STR = None
NULL = Null()
def __init__(self, s):
try:
self._origin = s
self._value = 0
self.value = self.NULL
self._format()
if self.value == self.NULL:
self.value = self._origin
except:
raise Exception("'%s' is not %s" % (self._origin, self._type_str))
......@@ -401,10 +413,48 @@ class ParamPlugin(Plugin):
else:
self._value = []
class Dict(ConfigItemType):
def _format(self):
if self._origin:
if not isinstance(self._origin, dict):
raise Exception("Invalid Value")
self._value = self._origin
else:
self._value = self.value = {}
class List(ConfigItemType):
def _format(self):
if self._origin:
if not isinstance(self._origin, list):
raise Exception("Invalid value: {} is not a list.".format(self._origin))
self._value = self._origin
else:
self._value = self.value = []
class StringOrKvList(ConfigItemType):
def _format(self):
if self._origin:
if not isinstance(self._origin, list):
raise Exception("Invalid value: {} is not a list.".format(self._origin))
for item in self._origin:
if not item:
continue
if not isinstance(item, (str, dict)):
raise Exception("Invalid value: {} should be string or key-value format.".format(item))
if isinstance(item, dict):
if len(item.keys()) != 1:
raise Exception("Invalid value: {} should be single key-value format".format(item))
self._value = self._origin
else:
self._value = self.value = []
class Double(ConfigItemType):
def _format(self):
self._value = float(self._origin) if self._origin else 0
self.value = self._value = float(self._origin) if self._origin else 0
class Boolean(ConfigItemType):
......@@ -413,10 +463,15 @@ class ParamPlugin(Plugin):
self._value = self._origin
else:
_origin = str(self._origin).lower()
if _origin.isdigit() or _origin in ['true', 'false']:
if _origin == 'true':
self._value = True
elif _origin == 'false':
self._value = False
elif _origin.isdigit():
self._value = bool(self._origin)
else:
raise Exception('%s is not Boolean')
raise Exception('%s is not Boolean' % _origin)
self.value = self._value
class Integer(ConfigItemType):
......@@ -426,15 +481,15 @@ class ParamPlugin(Plugin):
self._origin = 0
else:
_origin = str(self._origin)
if _origin.isdigit():
self._value = int(_origin)
else:
raise Exception('%s is not Integer')
try:
self.value = self._value = int(_origin)
except:
raise Exception('%s is not Integer' % _origin)
class String(ConfigItemType):
def _format(self):
self._value = str(self._origin) if self._origin else ''
self.value = self._value = str(self._origin) if self._origin else ''
class ConfigItem(object):
......@@ -519,29 +574,35 @@ class ParamPlugin(Plugin):
'MOMENT': ParamPlugin.Moment,
'TIME': ParamPlugin.Time,
'CAPACITY': ParamPlugin.Capacity,
'STRING_LIST': ParamPlugin.StringList
'STRING_LIST': ParamPlugin.StringList,
'DICT': ParamPlugin.Dict,
'LIST': ParamPlugin.List,
'PARAM_LIST': ParamPlugin.StringOrKvList
}
self._src_data = {}
with open(self.def_param_yaml_path, 'rb') as f:
configs = yaml.load(f)
for conf in configs:
param_type = ConfigUtil.get_value_from_dict(conf, 'type', 'STRING').upper()
if param_type in TYPES:
param_type = TYPES[param_type]
else:
param_type = ParamPlugin.String
self._src_data[conf['name']] = ParamPlugin.ConfigItem(
name=conf['name'],
param_type=param_type,
default=ConfigUtil.get_value_from_dict(conf, 'default', None),
min_value=ConfigUtil.get_value_from_dict(conf, 'min_value', None),
max_value=ConfigUtil.get_value_from_dict(conf, 'max_value', None),
modify_limit=ConfigUtil.get_value_from_dict(conf, 'modify_limit', None),
require=ConfigUtil.get_value_from_dict(conf, 'require', False),
need_restart=ConfigUtil.get_value_from_dict(conf, 'need_restart', False),
need_redeploy=ConfigUtil.get_value_from_dict(conf, 'need_redeploy', False)
)
try:
param_type = ConfigUtil.get_value_from_dict(conf, 'type', 'STRING').upper()
if param_type in TYPES:
param_type = TYPES[param_type]
else:
param_type = ParamPlugin.String
self._src_data[conf['name']] = ParamPlugin.ConfigItem(
name=conf['name'],
param_type=param_type,
default=ConfigUtil.get_value_from_dict(conf, 'default', None),
min_value=ConfigUtil.get_value_from_dict(conf, 'min_value', None),
max_value=ConfigUtil.get_value_from_dict(conf, 'max_value', None),
modify_limit=ConfigUtil.get_value_from_dict(conf, 'modify_limit', None),
require=ConfigUtil.get_value_from_dict(conf, 'require', False),
need_restart=ConfigUtil.get_value_from_dict(conf, 'need_restart', False),
need_redeploy=ConfigUtil.get_value_from_dict(conf, 'need_redeploy', False)
)
except:
pass
except:
pass
return self._src_data
......@@ -590,6 +651,40 @@ class ParamPlugin(Plugin):
return self._params_default
class SnapConfigPlugin(Plugin):
PLUGIN_TYPE = PluginType.SNAP_CONFIG
CONFIG_YAML = 'snap_config.yaml'
FLAG_FILE = CONFIG_YAML
_KEYCRE = re.compile(r"\$(\w+)")
def __init__(self, component_name, plugin_path, version, dev_mode):
super(SnapConfigPlugin, self).__init__(component_name, plugin_path, version, dev_mode)
self.config_path = os.path.join(self.plugin_path, self.CONFIG_YAML)
self._config = None
self._file_hash = None
def __hash__(self):
if self._file_hash is None:
self._file_hash = int(''.join(['%03d' % (ord(v) if isinstance(v, str) else v) for v in FileUtil.checksum(self.config_path)]))
return self._file_hash
@property
def config(self):
if self._config is None:
with open(self.config_path, 'rb') as f:
self._config = yaml.load(f)
return self._config
@property
def backup(self):
return self.config.get('backup', [])
@property
def clean(self):
return self.config.get('clean', [])
class InstallPlugin(Plugin):
class FileItemType(Enum):
......@@ -621,6 +716,7 @@ class InstallPlugin(Plugin):
self.file_map_path = os.path.join(self.plugin_path, self.FILES_MAP_YAML)
self._file_map = {}
self._file_map_data = None
self._check_value = None
@classmethod
def var_replace(cls, string, var):
......@@ -644,6 +740,12 @@ class InstallPlugin(Plugin):
return ''.join(done)
@property
def check_value(self):
if self._check_value is None:
self._check_value = os.path.getmtime(self.file_map_path)
return self._check_value
@property
def file_map_data(self):
if self._file_map_data is None:
......
......@@ -25,13 +25,14 @@ import sys
import time
import hashlib
from glob import glob
from multiprocessing import cpu_count
from multiprocessing.pool import Pool
from _rpm import Package, PackageInfo, Version
from _arch import getBaseArch
from tool import DirectoryUtil, FileUtil, YamlLoader
from _manager import Manager
from _plugin import InstallPlugin
from ssh import LocalClient
class LocalPackage(Package):
......@@ -122,15 +123,7 @@ class LocalPackage(Package):
filelinktos.append(os.readlink(target_path))
filemodes.append(-24065)
else:
ret = LocalClient().execute_command('md5sum {}'.format(target_path))
if ret:
m_value = ret.stdout.strip().split(' ')[0].encode('utf-8')
else:
m = hashlib.md5()
with open(target_path, 'rb') as f:
m.update(f.read())
m_value = m.hexdigest().encode(sys.getdefaultencoding())
# raise Exception('Failed to get md5sum for {}, error: {}'.format(target_path, ret.stderr))
m_value = FileUtil.checksum(target_path)
m_sum.update(m_value)
filemd5s.append(m_value)
filelinktos.append('')
......@@ -149,6 +142,73 @@ class LocalPackage(Package):
return self.RpmObject(self.headers, self.files)
class ExtractFileInfo(object):
def __init__(self, src_path, target_path, mode):
self.src_path = src_path
self.target_path = target_path
self.mode = mode
class ParallerExtractWorker(object):
def __init__(self, pkg, files, stdio=None):
self.pkg = pkg
self.files = files
self.stdio = stdio
@staticmethod
def extract(worker):
with worker.pkg.open() as rpm:
for info in worker.files:
if os.path.exists(info.target_path):
continue
fd = rpm.extractfile(info.src_path)
with FileUtil.open(info.target_path, 'wb', stdio=worker.stdio) as f:
FileUtil.copy_fileobj(fd, f)
if info.mode != 0o744:
os.chmod(info.target_path, info.mode)
class ParallerExtractor(object):
MAX_PARALLER = cpu_count() if cpu_count() else 8
def __init__(self, pkg, files, stdio=None):
self.pkg = pkg
self.files = files
self.stdio = stdio
def extract(self):
workers = []
file_num = len(self.files)
paraler = int(min(self.MAX_PARALLER, file_num))
size = min(100, int(file_num / paraler))
size = int(max(10, size))
index = 0
while index < file_num:
p_index = index + size
workers.append(ParallerExtractWorker(
self.pkg,
self.files[index:p_index],
stdio=self.stdio
))
index = p_index
pool = Pool(processes=paraler)
try:
results = pool.map(ParallerExtractWorker.extract, workers)
for r in results:
if not r:
return False
except KeyboardInterrupt:
if pool:
pool.close()
pool = None
finally:
pool and pool.close()
class Repository(PackageInfo):
_DATA_FILE = '.data'
......@@ -251,7 +311,7 @@ class Repository(PackageInfo):
self.stdio and getattr(self.stdio, 'print', '%s is a shadow repository' % self)
return False
hash_path = os.path.join(self.repository_dir, '.hash')
if self.hash == pkg.md5 and self.file_check(plugin):
if self.hash == pkg.md5 and self.file_check(plugin) and self.install_time > plugin.check_value:
return True
self.clear()
try:
......@@ -291,6 +351,8 @@ class Repository(PackageInfo):
if path.startswith(n_dir):
need_files[path] = os.path.join(need_dirs[n_dir], path[len(n_dir):])
break
need_extract_files = []
for src_path in need_files:
if src_path not in files:
raise Exception('%s not found in packge' % src_path)
......@@ -299,17 +361,17 @@ class Repository(PackageInfo):
return
idx = files[src_path]
if filemd5s[idx]:
fd = rpm.extractfile(src_path)
self.stdio and getattr(self.stdio, 'verbose', print)('extract %s to %s' % (src_path, target_path))
with FileUtil.open(target_path, 'wb', stdio=self.stdio) as f:
FileUtil.copy_fileobj(fd, f)
mode = filemodes[idx] & 0x1ff
if mode != 0o744:
os.chmod(target_path, mode)
need_extract_files.append(ExtractFileInfo(
src_path,
target_path,
filemodes[idx] & 0x1ff
))
elif filelinktos[idx]:
links[target_path] = filelinktos[idx]
else:
raise Exception('%s is directory' % src_path)
ParallerExtractor(pkg, need_extract_files, stdio=self.stdio).extract()
for link in links:
self.stdio and getattr(self.stdio, 'verbose', print)('link %s to %s' % (links[link], link))
......
......@@ -23,9 +23,13 @@ from __future__ import absolute_import, division, print_function
import os
import signal
import sys
import fcntl
import traceback
import inspect2
import six
import logging
from copy import deepcopy
from logging import handlers
from enum import Enum
from halo import Halo, cursor
......@@ -35,6 +39,8 @@ from progressbar import AdaptiveETA, Bar, SimpleProgress, ETA, FileTransferSpeed
from types import MethodType
from inspect2 import Parameter
from log import Logger
if sys.version_info.major == 3:
raw_input = input
......@@ -55,6 +61,87 @@ class BufferIO(object):
return s
class SysStdin(object):
NONBLOCK = False
STATS = None
FD = None
@classmethod
def fileno(cls):
if cls.FD is None:
cls.FD = sys.stdin.fileno()
return cls.FD
@classmethod
def stats(cls):
if cls.STATS is None:
cls.STATS = fcntl.fcntl(cls.fileno(), fcntl.F_GETFL)
return cls.STATS
@classmethod
def nonblock(cls):
if cls.NONBLOCK is False:
fcntl.fcntl(cls.fileno(), fcntl.F_SETFL, cls.stats() | os.O_NONBLOCK)
cls.NONBLOCK = True
@classmethod
def block(cls):
if cls.NONBLOCK:
fcntl.fcntl(cls.fileno(), fcntl.F_SETFL, cls.stats())
cls.NONBLOCK = True
@classmethod
def readline(cls, blocked=False):
if blocked:
cls.block()
else:
cls.nonblock()
return cls._readline()
@classmethod
def read(cls, blocked=False):
return ''.join(cls.readlines(blocked=blocked))
@classmethod
def readlines(cls, blocked=False):
if blocked:
cls.block()
else:
cls.nonblock()
return cls._readlines()
@classmethod
def _readline(cls):
if cls.NONBLOCK:
try:
for line in sys.stdin:
return line
except IOError:
return ''
finally:
cls.block()
else:
return sys.stdin.readline()
@classmethod
def _readlines(cls):
if cls.NONBLOCK:
lines = []
try:
for line in sys.stdin:
lines.append(line)
except IOError:
pass
finally:
cls.block()
return lines
else:
return sys.stdin.readlines()
class FormtatText(object):
@staticmethod
......@@ -234,11 +321,11 @@ class IO(object):
WARNING_PREV = FormtatText.warning('[WARN]')
ERROR_PREV = FormtatText.error('[ERROR]')
IS_TTY = sys.stdin.isatty()
INPUT = SysStdin
def __init__(self,
level,
msg_lv=MsgLevel.DEBUG,
trace_logger=None,
use_cache=False,
track_limit=0,
root_io=None,
......@@ -246,7 +333,11 @@ class IO(object):
):
self.level = level
self.msg_lv = msg_lv
self.trace_logger = trace_logger
self.log_path = None
self.trace_id = None
self.log_name = 'default'
self.log_path = None
self._trace_logger = None
self._log_cache = [] if use_cache else None
self._root_io = root_io
self.track_limit = track_limit
......@@ -257,6 +348,34 @@ class IO(object):
self._cur_out_obj = self._out_obj
self._before_critical = None
def init_trace_logger(self, log_path, log_name=None, trace_id=None):
if self._trace_logger is None:
self.log_path = log_path
if trace_id:
self.trace_id = trace_id
if log_name:
self.log_name = log_name
def __getstate__(self):
state = {}
for key in self.__dict__:
state[key] = self.__dict__[key]
for key in ['_trace_logger', 'sync_obj', '_out_obj', '_cur_out_obj', '_before_critical']:
state[key] = None
return state
@property
def trace_logger(self):
if self.log_path and self._trace_logger is None:
self._trace_logger = Logger(self.log_name)
handler = handlers.TimedRotatingFileHandler(self.log_path, when='midnight', interval=1, backupCount=30)
if self.trace_id:
handler.setFormatter(logging.Formatter("[%%(asctime)s.%%(msecs)03d] [%s] [%%(levelname)s] %%(message)s" % self.trace_id, "%Y-%m-%d %H:%M:%S"))
else:
handler.setFormatter(logging.Formatter("[%%(asctime)s.%%(msecs)03d] [%%(levelname)s] %%(message)s", "%Y-%m-%d %H:%M:%S"))
self._trace_logger.addHandler(handler)
return self._trace_logger
@property
def log_cache(self):
if self._root_io:
......@@ -417,13 +536,17 @@ class IO(object):
msg_lv = self.msg_lv
key = "%s-%s" % (pid, msg_lv)
if key not in self.sub_ios:
self.sub_ios[key] = self.__class__(
sub_io = self.__class__(
self.level + 1,
msg_lv=msg_lv,
trace_logger=self.trace_logger,
track_limit=self.track_limit,
root_io=self._root_io if self._root_io else self
)
sub_io.log_name = self.log_name
sub_io.log_path = self.log_path
sub_io.trace_id = self.trace_id
sub_io._trace_logger = self.trace_logger
self.sub_ios[key] = sub_io
return self.sub_ios[key]
def print_list(self, ary, field_names=None, exp=lambda x: x if isinstance(x, (list, tuple)) else [x], show_index=False, start=0, **kwargs):
......@@ -445,11 +568,18 @@ class IO(object):
table.add_row(row)
self.print(table)
def read(self, msg='', blocked=False):
if msg:
self._print(MsgLevel.INFO, msg)
return self.INPUT.read(blocked)
def confirm(self, msg):
msg = '%s [y/n]: ' % msg
self.print(msg, end='')
if self.IS_TTY:
while True:
try:
ans = raw_input('%s [y/n]: ' % msg)
ans = raw_input()
if ans == 'y':
return True
if ans == 'n':
......@@ -595,6 +725,8 @@ class StdIO(object):
self._warn_func = getattr(self.io, "warn", print)
def __getattr__(self, item):
if item.startswith('__'):
return super(StdIO, self).__getattribute__(item)
if self.io is None:
return FAKE_RETURN
if item not in self._attrs:
......
此差异已折叠。
3.user-guide=使用指南
3.obd-command=OBD 命令
5.faq=常见问题
3.user-guide=User Guide
3.obd-command=OBD command
\ No newline at end of file
......@@ -4,30 +4,39 @@ To start an OceanBase cluster, follow these steps:
## Step 1: Select a configuration file
Select a configuration file based on your resource configurations:
OBD provides different configuration files for different deployment scenarios. These configuration file examples are placed in the directory `/usr/OBD/example/`. Select a configuration file based on your resource configurations:
### Small-scale deployment mode
This deployment mode applies to personal devices with at least 8 GB of memory.
- [Sample configuration file for local single-node deployment](https://github.com/oceanbase/obdeploy/blob/master/example/mini-local-example.yaml)
- [Sample configuration file for single-node deployment](https://github.com/oceanbase/obdeploy/blob/master/example/mini-single-example.yaml)
- [Sample configuration file for three-node deployment](https://github.com/oceanbase/obdeploy/blob/master/example/mini-distributed-example.yaml)
- [Sample configuration file for single-node deployment with ODP](https://github.com/oceanbase/obdeploy/blob/master/example/mini-single-with-obproxy-example.yaml)
- [Sample configuration file for three-node deployment with ODP](https://github.com/oceanbase/obdeploy/blob/master/example/mini-distributed-with-obproxy-example.yaml)
- Sample configuration file for local single-node deployment: /usr/obd/example/mini-local-example.yaml
- Sample configuration file for single-node deployment: /usr/obd/example/mini-single-example.yaml
- Sample configuration file for three-node deployment: /usr/obd/example/mini-distributed-example.yaml
- Sample configuration file for single-node deployment with ODP: /usr/obd/example/mini-single-with-obproxy-example.yaml
- Sample configuration file for three-node deployment with ODP: /usr/obd/example/mini-distributed-with-obproxy-example.yaml
### Professional deployment mode
This deployment mode applies to advanced Elastic Compute Service (ECS) instances or physical servers with at least 16 CPU cores and 64 GB of memory.
- [Sample configuration file for local single-node deployment](https://github.com/oceanbase/obdeploy/blob/master/example/local-example.yaml)
- [Sample configuration file for single-node deployment](https://github.com/oceanbase/obdeploy/blob/master/example/single-example.yaml)
- [Sample configuration file for three-node deployment](https://github.com/oceanbase/obdeploy/blob/master/example/distributed-example.yaml)
- [Sample configuration file for single-node deployment with ODP](https://github.com/oceanbase/obdeploy/blob/master/example/single-with-obproxy-example.yaml)
- [Sample configuration file for three-node deployment with ODP](https://github.com/oceanbase/obdeploy/blob/master/example/distributed-with-obproxy-example.yaml)
- [Sample configuration file for three-node deployment with ODP and obagent](https://github.com/oceanbase/obdeploy/blob/master/example/obagent/distributed-with-obproxy-and-obagent-example.yaml)
- Sample configuration file for local single-node deployment: /usr/obd/example/local-example.yaml
- Sample configuration file for single-node deployment: /usr/obd/example/single-example.yaml
This section describes how to start a local single-node OceanBase cluster by using the [sample configuration file for local single-node deployment in the small-scale deployment mode](https://github.com/oceanbase/obdeploy/blob/master/example/mini-local-example.yaml).
- Sample configuration file for three-node deployment: /usr/obd/example/distributed-example.yaml
- Sample configuration file for single-node deployment with ODP: /usr/obd/example/single-with-obproxy-example.yaml
- Sample configuration file for three-node deployment with ODP: /usr/obd/example/distributed-with-obproxy-example.yaml
- Sample configuration file for three-node deployment with ODP and obagent: /usr/obd/example/obagent/distributed-with-obproxy-and-obagent-example.yaml
This section describes how to start a local single-node OceanBase cluster by using the sample configuration file for local single-node deployment in the small-scale deployment mode: /usr/obd/example/mini-local-example.yaml.
```shell
# Modify the working directory of the OceanBase cluster: home_path.
......@@ -47,7 +56,10 @@ user:
```
`username` specifies the username used to log on to the target server. Make sure that your username has the write permission on the `home_path` directory. `password` and `key_file` are used to authenticate the user. Generally, only one of them is required.
> **NOTE:** After you specify the path of the key, add an annotation to the `password` field or delete it if your key does not require a password. Otherwise, `password` will be deemed as the password of the key and used for login, leading to a logon verification failure.
> **NOTE:**
>
> After you specify the path of the key, add an annotation to the `password` field or delete it if your key does not require a password. Otherwise, `password` will be deemed as the password of the key and used for login, leading to a logon verification failure.
## Step 2: Deploy and start a cluster
......
......@@ -181,12 +181,28 @@ obd cluster upgrade <deploy_name> -c <component_name> -V <version> [tags]
| Option | Required | Data type | Default value | Description |
--- | --- | --- |--- |---
-c/--component | Yes | string | empty | The component name you want to upgrade.
-V/--version | Yes | string | The target upgrade version number.
-V/--version | Yes | string | empty | The target upgrade version number.
--skip-check | No | bool | false | Skip check.
--usable | No | string | empty | The hash list for the mirrors that you use during upgrade. Separated with `,`.
--disable | No | string | empty | The hash list for the mirrors that you disable during upgrade. Separated with `,`.
-e/--executer-path | No | string | /usr/obd/lib/executer | The executer path for the upgrade script.
## obd cluster reinstall
You can run this command to reinstall the repository of a deployed component. The new repository must have the same version number as the previous repository. If this command is used to replace the repository when the deployment status is 'running', the component is restarted after the replacement without loading parameters.
```bash
obd cluster reinstall <deploy name> -c <component name> --hash <hash> [-f/--force]
```
The `deploy name` parameter indicates the name of the deployed cluster, which is also the alias of the configuration file.
| Option name | Required | Data type | Default value | Description |
|---------|----------|-------------|-------------|--------------|
| -c/--component | Yes | string | Null | The name of the component whose repository is to be replaced. |
|--hash | Yes | string | Null | The target repository. It must be of the same version as the current repository. |
| -f/--force | No | Bool | false | Specifies whether to enable forced replacement even if the restart fails. |
## `obd cluster tenant create`
Creates a tenant. This command applies only to an OceanBase cluster. This command automatically creates resource units and resource pools.
......@@ -232,3 +248,41 @@ obd cluster tenant drop <deploy name> [-n <tenant name>]
`deploy name` specifies the name of the deployment configuration file.
`-n` is `--tenant-name`. This option specifies the name of the tenant to be deleted. This option is required.
## obd cluster chst
You can run this command to change the configuration style.
```shell
obd cluster chst <deploy name> --style <STYLE> [-c/--components]
```
The `deploy name` parameter specifies the name of the deployed cluster. You can consider it as an alias for the configuration file.
The following table describes details about the available options.
| Option | Required | Data type | Default value | Description |
|-----------------------|------|--------|--------------------------|------------------------------------------------------------------------|
| --style | Yes | string | N/A | The preferred configuration style. Valid values: default and cluster. |
| -c/--components | No | string | Empty | The components whose configuration style is to be changed. Separate multiple components with commas `,`. |
## obd cluster check4ocp
You can run this command to check whether the current configurations can be taken over by OceanBase Cloud Platform (OCP).
```shell
obd cluster check4ocp <deploy name> [-c/--components] [-V/--version]
```
The `deploy name` parameter specifies the name of the deployed cluster. You can consider it as an alias for the configuration file.
can be understood as an alias for the configuration file
The following table describes details about the available options.
| Option | Required | Data type | Default value | Description |
|-----------------------|------|--------|--------------------------|------------------------------------------------------------------------|
| -c/--components | No | string | Empty | The components whose configuration style is to be changed. Separate multiple components with commas `,`. |
| -V/--version | Yes | string | 3.1.0 | The OCP version. |
......@@ -22,7 +22,7 @@ Creates a mirror based on the local directory. When OBD starts a user-compiled o
obd mirror create -n <component name> -p <your compile dir> -V <component version> [-t <tag>] [-f]
```
For example, you can [compile an OceanBase cluster based on the source code](https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.1/get-the-oceanbase-database-by-using-source-code). Then, you can run the `make DESTDIR=./ install && obd mirror create -n oceanbase-ce -V 3.1.0 -p ./usr/local` command to add the compilation output to the local repository of OBD.
For example, you can [compile an OceanBase cluster based on the source code](https://www.oceanbase.com/en/docs/community-observer-en-10000000000209369). Then, you can run the `make DESTDIR=./ install && obd mirror create -n oceanbase-ce -V 3.1.0 -p ./usr/local` command to add the compilation output to the local repository of OBD.
This table describes the corresponding options.
......
......@@ -16,17 +16,24 @@ This table describes the corresponding options.
| Option | Required | Data type | Default value | Description |
--- | --- | --- |--- | ---
| -c/--component | No | string | | The name of the component to be tested. Valid values: oceanbase-ce and obproxy. If this option is not specified, OBD will search for obproxy and oceanbase-ce in sequence. If obproxy is found, OBD will stop the search and use obproxy for the subsequent tests. If obproxy is not found, OBD will continue to search for oceanbase-ce. |
| -c/--component | No | string | | The name of the component to be tested. Valid values: `oceanbase-ce`, `oceanbase`, `obproxy-ce` and `obproxy`. If you do not specify a value, the existence of `obproxy`, `obproxy-ce`, `oceanbase`, `oceanbase-ce` is checked sequentially. The traversal stops when a component is found, and the component is then tested. |
| --test-server | No | string | The first node of the specified component. | It must be the name of a node of the specified component. |
| --user | No | string | root | The username for running the test. |
| --password | No | string | | The password for running the test. |
| --database | No | String | test | The database where the test is to be performed. |
| --mysqltest-bin | No | string | mysqltest | The path of the mysqltest binary file. |
| --obclient-bin | No | string | obclient | The path of the OBClient binary file. |
| --test-dir | No | string | ./mysql_test/t | The directory that stores the test file required for the mysqltest. If no test file is found in the directory, OBD will search for a built-in test file. |
| --test-file-suffix | No | String | .test | The suffix of the test file required by mysqltest. |
| --result-dir | No | string | ./mysql_test/r | The directory that stores the result file required for the mysqltest. If no result file is found in the directory, OBD will search for a built-in result file. |
| --result-file-suffix | No | String | .result | The suffix of the result file required by mysqltest. |
| --record | No | Bool | false | Specifies whether to record only the execution results of mysqltest in the record files. |
| --record-dir | No | String | ./record | The directory where the record files that record the execution results of mysqltest are stored. |
| --record-file-suffix | No | String | .record | The suffix of the record files that record the execution results of mysqltest. |
| --tmp-dir | No | string | ./tmp | The mysqltest tmpdir option. |
| --var-dir | No | string | ./var | The directory to create the log directory. The log directory will be added to the mysqltest command as the logdir option. |
| --test-set | No | string | None | The test case array. Separate multiple test cases with commas (,). |
| --test-set | No | string | None | The array of test cases. Separate multiple cases with commas (`,`). |
| --exclude | No | String | None | The array of test cases to be excluded. Separate multiple cases with commas (`,`). |
| --test-pattern | No | string | None | The regular expression for matching test file names. Test cases matching the regular expression will overwrite the values of the test-set option. |
| --suite | No | string | None | The suite array. A suite contains multiple tests. Separate multiple tests with commas (,). If this option is enabled, the --test-pattern and --test-set options will become invalid. |
| --suite-dir | No | string | ./mysql_test/test_suite | The directory that stores the suite directory. If no suite directory is found in the directory, OBD will search for a built-in suite directory. |
......@@ -35,6 +42,19 @@ This table describes the corresponding options.
| --init-sql-dir | No | string | ../ | The directory that stores the init sql files. If no init sql file is found in the directory, OBD will search for built-in init sql files. |
| --init-sql-files | No | string | | The init sql files to be run when initialization is required. Separate multiple init sql files with commas (,). If this option is not specified but initialization is required, OBD will run the built-in init files based on the cluster configurations. |
| --auto-retry | No | bool | false | Specifies whether to automatically redeploy the cluster for a retry after a test fails. |
| --psmall | No | Bool | false | Specifies whether to execute the cases in psmall mode. |
| --slices | No | Int | Empty | The number of slices into which the case to be executed is divided. |
| --slice-idx | No | Int | Empty | The ID of the current slice. |
| --slb-host | No | String | Empty | The host for soft load balancing. |
| --exec-id | No | String | Empty | The ID of the execution. |
| --case-filter | No | String | ./mysql_test/filter.py | The filter.py file, which contains lists of cases to be filtered out. |
| --reboot-timeout | No | Int | 0 | The timeout period for the restart. |
| --reboot-retries | No | Int | 5 | The number of retries allowed if the restart fails. |
| --collect-all | No | Bool | false | Specifies whether to collect component logs. |
| --log-dir | No | String | `tmp_dir/log` | The path to the directory where the mysqltest logs are stored. |
| --log-pattern | No | String | *.log | The regular expression that is used to match log file names. Files that match the expression are collected. |
| --case-timeout | No | Int | 3600 | The timeout period for a single test of mysqltest. |
| --disable-reboot | No | Bool | false | Specifies whether to disable restart during the test. |
## `obd test sysbench`
......@@ -48,7 +68,7 @@ obd test sysbench <deploy name> [flags]
| Option | Required | Data type | Default value | Description |
--- | --- | --- |--- | ---
| -c/--component | No | string | | The name of the component to be tested. Valid values: oceanbase-ce and obproxy. If this option is not specified, OBD will search for obproxy and oceanbase-ce in sequence. If obproxy is found, OBD will stop the search and use obproxy for subsequent tests. If obproxy is not found, OBD will continue to search for oceanbase-ce. |
| -c/--component | No | string | | The name of the component to be tested. Valid values: `oceanbase-ce`, `oceanbase`, `obproxy-ce` and `obproxy`. If you do not specify a value, the existence of `obproxy`, `obproxy-ce`, `oceanbase`, `oceanbase-ce` is checked sequentially. The traversal stops when a component is found, and the component is then tested. |
| --test-server | No | string | The first node of the specified component. | It must be the name of a node of the specified component. |
| --user | No | string | root | The username for running the test. |
| --password | No | string | | The password for running the test. |
......@@ -91,7 +111,7 @@ obd test tpch <deploy name> [flags]
| --dbgen-bin | No | string | /usr/local/tpc-h-tools/bin/dbgen | The path of the dbgen binary file. |
| --dss-config | No | string | /usr/local/tpc-h-tools/ | The directory that stores the dists.dss files. |
| -s/--scale-factor | No | int | 1 | Automatically generate the scale of test data, the data is measured in Gigabytes. |
| -tmp-dir | No | string | ./tmp | Temporary directory when executing tpch. When enabled, this option will automatically generate test data, auto-tuned SQL files, log files for executing test SQL, and so on. |
| --tmp-dir | No | string | ./tmp | Temporary directory when executing tpch. When enabled, this option will automatically generate test data, auto-tuned SQL files, log files for executing test SQL, and so on. |
| --ddl-path | No | string | | The path or directory of the ddl file. If it is empty, OBD will use the ddl file that comes with it. |
| --tbl-path | No | string | | The path or directory of the tbl file. If it is empty, use dbgen to generate test data. |
| --sql-path | No | string | | The path or directory of the sql file. If it is empty, OBD will use the sql file that comes with it. |
......@@ -99,3 +119,39 @@ obd test tpch <deploy name> [flags]
| --test-only | No | bool | false | When you enable this option, initialization will not be done, only the test SQL is exectued. |
| --dt/--disable-transfer | No | bool | false | Disable transfer. When you enable this option, OBD will not transfer the local tbl to the remote remote-tbl-dir, and OBD will directly use the tbl file under the target machine remote-tbl-dir. |
| -O/--optimization | No | int | 1 | Auto tuning level. Off when 0. |
## obd test tpcc
You can run this command to perform a TPC-C test on a specified node of an OceanBase cluster or an OceanBase Database Proxy (ODP) component.
Make sure that you have installed OBClient and obtpcc, which are required to perform a TPC-C test.
```shell
obd test tpcc <deploy name> [flags]
```
The `deploy name` parameter specifies the name of the deployed cluster. You can consider it as an alias for the configuration file.
The following table describes details about the available options.
| Option | Required | Data type | Default value | Description |
--- | --- | --- |--- | ---
| --component | No | string | Empty | The name of the component to be tested. Valid values: `oceanbase-ce`, `oceanbase`, `obproxy-ce` and `obproxy`. If you do not specify a value, the existence of `obproxy`, `obproxy-ce`, `oceanbase`, `oceanbase-ce` is checked sequentially. The traversal stops when a component is found, and the component is then tested. |
| --test-server | No | string | The name of the first node under the specified component. | The name of the node to be tested under the specified component. |
| --user | No | string | root | The username used to perform the test. |
| --password | No | string | Empty | The user password used to perform the test. |
| --tenant | No | string | test | The tenant name used to perform the test. |
| --database | No | string | test | The database where the test is to be performed. |
| --obclient-bin | No | string | obclient | The path to the directory where the binary files of OBClient are stored. |
| --java-bin | No | string | java | The path to the directory where the Java binary files are stored. |
| --tmp-dir | No | string | ./tmp | The temporary directory to be used for the TPC-C test. Automatically generated configuration files, auto-tuned SQL files, and test output files will be stored in this directory. |
| --bmsql-dir | No | string | Empty | The installation directory of BenchmarkSQL. You need to specify this option only if you manually compile and install BenchmarkSQL. If you use obtpcc, this option is not required. |
| --bmsql-jar | No | string | Empty | The path to the directory where the JAR file of BenchmarkSQL is stored. If you do not specify the path, and the BenchmarkSQL directory is not specified, the default installation directory generated by obtpcc is used. If the BenchmarkSQL directory is specified, the JAR file in the `<bmsql-dir>/dist` directory is used. |
| --bmsql-libs | No | string | Empty | If the BenchmarkSQL directory is specified, the JAR files in the `<bmsql-dir>/lib` and `<bmsql-dir>/lib/oceanbase` directories are used. If you use obtpcc, this option is not required. |
| --bmsql-sql-dir | No | string | Empty | The path to the directory where the SQL files for the TPC-C test are stored. If you do not specify the path, OceanBase Deployer (OBD) uses the SQL files that are automatically generated. |
| --warehouses | No | int | Empty | The number of warehouses for the TPC-C test data set. If you do not specify a value, the assigned value is 20 times the number of CPU cores allocated to the OceanBase cluster. |
| --load-workers | No | int | Empty | The number of concurrent worker threads for building the test data set. If you do not specify a value, the number of CPU cores per server or the size of tenant memory (GB)/2, whichever is smaller, is used. |
| --terminals | No | int | Empty | The number of virtual terminals to be used for the TPC-C test. If you do not specify a value, the number of CPU cores for the OceanBase cluster × 15 or the number of warehouses × 10, whichever is smaller, is used. |
| --run-mins | No | int | 10 | The amount of time allocated for the execution of the TPC-C test. |
| --test-only | No | bool | false | Specifies that the test is performed without data construction. |
| -O/--optimization | No | int | 1 | The degree of auto-tuning. Valid values: `0`, `1`, and `2`. `0` indicates that auto-tuning is disabled. `1` indicates that the auto-tuning parameters that take effect without a cluster restart are modified. `2` indicates that all auto-tuning parameters are modified. If necessary, the cluster is restarted to make all parameters take effect. |
# Tool commands
OceanBase Deployer (OBD) provides a series of tool commands, including general commands that deliver a better experience for developers.
## obd devmode enable
You can run this command to enable the developer mode, which is a prerequisite for using other tool commands. After you enable the developer mode, OBD will downgrade the level of some exceptions and ignore some parameter exceptions. If you are not a kernel developer, use this command with caution.
```shell
obd devmode enable
```
## obd devmode disable
You can run this command to disable the developer mode.
```shell
obd devmode disable
```
## obd env show
You can run this command to display the environment variables of OBD.
```shell
obd env show
```
## obd env set
You can run this command to set the environment variables of OBD. Environment variables can affect the performance of OBD. Therefore, we recommend that you use this command only when it is necessary.
```shell
obd env set [key] [value]
```
You can set the following variables:
* `OBD_DISABLE_RSYNC`: OBD allows you to run the `rsync` command for remote data transmission when the prerequisites are met. If this environment variable is set to `1`, the `rsync` command is disabled. Valid values: `0` and `1`.
* `OBD_DEV_MODE`: specifies whether to enable the developer mode. Valid values: `0` and `1`.
## obd env unset
You can run this command to delete the specified environment variable.
```shell
obd env unset [key] [value]
```
## obd env clear
You can run this command to clear environment variables of OBD. Use this command with caution.
```shell
obd env clear
```
## obd tool command
You can use this command to run some general commands.
```shell
obd tool command <deploy name> <command> [options]
```
Valid values of the `command` field:
* `pid`: allows you to view the process ID (PID) of a service. This is a non-interactive command.
* `ssh`: allows you to log on to the specified server and enter the log directory. This is an interactive command.
* `less`: allows you to view the logs of the specified service. This is an interactive command.
* `gdb`: allows you to attach GNU Debugger (GDB) to a template service. This is an interactive command.
The following table describes the options of the command.
| Option | Required | Data type | Default value | Description |
|-----------------|------|--------|-------------------------------------------------------|---------------------------|
| -c/--components | No | String | In an interactive command, the first component specified in the configuration file is used by default. In a non-interactive command, all components are used. | The name of the component on which the command is executed. Separate multiple component names with commas (`,`). |
| -s/--servers | No | String | In an interactive command, the first node of the current component specified in the configuration file is used by default. In a non-interactive command, all available nodes are used. | The name of the node under the specified component. Separate multiple node names with commas (`,`). |
## obd tool db_connect
You can run this command to create a connection to the database.
```shell
obd tool db_connect <deploy name> [options]
```
The `deploy name` parameter indicates the name of the deployed cluster, which is also the alias of the configuration file.
The following table describes the options of the command.
| Option | Required | Data type | Default value | Description |
|---------------------|------|--------|---------------------------|-------------------------------------------------------------------|
| -c or --component | No | String | By default, the first component specified in the configuration file is used. | The name of the component to be connected. Valid values: `obproxy`, `obproxy-ce`, `oceanbase`, and `oceanbase-ce`. |
| -s or --server | No | String | By default, the first node of the current component specified in the configuration file is used. | The name of the node under the specified component. |
| -u or --user | No | String | root | The username for connecting to the database. |
| -p or --password | No | String | Empty | The password for connecting to the database. |
| -t or --tenant | No | String | sys | The tenant for connecting to the database. |
| -D or --database | No | String | Empty | The name of the database to be connected. |
| --obclient-bin | No | String | obclient | The path to the directory where the binary files of OBClient are stored. |
......@@ -11,8 +11,8 @@ user: # The SSH login configuration.
port: your ssh port, default 22
timeout: ssh connection timeout (second), default 30
oceanbase-ce: # The name of the component that is configured as follows.
# version: 3.1.0 # Specify the version of the component, which is usually not required.
# pacakge_hash: 9decc4788a7fc6cc2082ac1668f287f60f4a3b8d05a58da094605aa2f19d18fc # Specify the hash of the component, which is usually not required.
# version: 3.1.3 # Specify the version of the component, which is usually not required.
# pacakge_hash: 589c4f8ed2662835148a95d5c1b46a07e36c2d346804791364a757aef4f7b60d # Specify the hash of the component, which is usually not required.
# tag: dev # Specify the tag of the component, which is usually not required.
servers: # The list of nodes.
- name: z1 # The node name, which can be left blank. The default node name is the same as the IP address if this name is left blank. The node name is z1 in this example.
......@@ -60,9 +60,9 @@ oceanbase-ce: # The name of the component that is configured as follows.
# The working directory for OceanBase Database. OceanBase Database is started under this directory. This is a required field.
home_path: /root/observer
zone: zone3
obproxy: # The name of the component that is configured as follows.
# version: 3.1.0 # Specify the version of the component, which is usually not required.
# pacakge_hash: 62770120d2651738d808b7025396b9b3049f63761225ec7603805d318b6ed726 # Specify the hash of the component, which is usually not required.
obproxy-ce: # The name of the component that is configured as follows.
# version: 3.2.3 # Specify the version of the component, which is usually not required.
# pacakge_hash: 73cccf4d05508de0950ad1164aec03003c4ddbe1415530e031ac8b6469815fea # Specify the hash of the component, which is usually not required.
# tag: dev # Specify the tag of the component, which is usually not required.
servers:
- 192.168.1.5
......
# Q&A
# FAQ
## Q: How can I specify the version of a component?
......@@ -6,7 +6,7 @@ A: You can add the version declaration to the deployment configuration file. For
```yaml
oceanbase-ce:
version: 3.1.0
version: 3.1.3
```
## Q: How can I use a component of a specific version?
......@@ -23,7 +23,7 @@ You can also use package_hash to specify a specific version. When you run an `ob
```yaml
oceanbase-ce:
package_hash: 929df53459404d9b0c1f945e7e23ea4b89972069
package_hash: 589c4f8ed2662835148a95d5c1b46a07e36c2d346804791364a757aef4f7b60d
```
## Q: How can I modify the startup process after I modify the code of OceanBase-CE?
......@@ -37,13 +37,13 @@ A: When your machine with OBD installed cannot connect to the public network, bu
The following shows how to update the OBD mirror in the local repository:
```shell
# First, download the OBD 1.2.1 el7 RPM package on a machine that can connect to the public network.
# First, download the latest RPM package of OBD on a machine that can connect to the public network.
# Links to the latest RPM packages are available in the release notes of the corresponding component's git repository or on the OceanBase open source website (https://open.oceanbase.com/softwareCenter/community).
wget https://github.com/oceanbase/obdeploy/releases/download/v1.2.1/ob-deploy-1.2.1-9.el7.x86_64.rpm
wget https://github.com/oceanbase/obdeploy/releases/download/vx.x.x/ob-deploy-x.x.x-xxx.rpm
# Copy the downloaded RPM package to the machine where OBD is installed, i.e. obd_server.
sh ob-deploy-1.2.1-9.el7.x86_64.rpm obd_server:~
sh ob-deploy-x.x.x-xxx.rpm obd_server:~
# Add the downloaded mirror to local.
obd mirror clone ob-deploy-1.2.1-9.el7.x86_64.rpm
obd mirror clone ob-deploy-x.x.x-xxx.rpm
# Close the remote mirror source.
obd mirror disable remote
```
......@@ -55,17 +55,17 @@ A:There are two ways to update your OBD, which you can choose from depending o
+ If your machine can connect to the public network or have the RPM package for the updated OBD in the mirror you configured, you can directly use the `obd update` command to update the OBD. When you finish with the update, use the `obd --version` command to check the version of OBD and confirm whether the update is successful.
+ If your machine cannot connect to the public network and there is no RPM package for the updated OBD in the mirror you configured. Please add the RPM package that used to update OBD to the local mirror via `obd mirror clone` command first, and then use the `obd update` command to update the OBD.
The following shows how to update OBD to V1.2.1 on CentOS7 offline mode:
The following shows how to update OBD to the latest version in centos7 system:
```shell
# First, download the OBD 1.2.1 el7 RPM package on a machine that can connect to the public network.
# First, download the latest RPM package of OBD on a machine that can connect to the public network.
# Links to the latest RPM packages are available in the release notes of the corresponding component's git repository or on the OceanBase open source website (https://open.oceanbase.com/softwareCenter/community).
wget https://github.com/oceanbase/obdeploy/releases/download/v1.2.1/ob-deploy-1.2.1-9.el7.x86_64.rpm
wget https://github.com/oceanbase/obdeploy/releases/download/vx.x.x/ob-deploy-x.x.x-xxx.rpm
# Copy the downloaded RPM package to the machine where OBD is installed, i.e. obd_server.
sh ob-deploy-1.2.1-9.el7.x86_64.rpm obd_server:~
sh ob-deploy-x.x.x-xxx.rpm obd_server:~
# Execute the following command on the OBD machine to complete the upgrade.
# 1.Add the downloaded mirror to local.
obd mirror clone ob-deploy-1.2.1-9.el7.x86_64.rpm
obd mirror clone ob-deploy-x.x.x-xxx.rpm
# 2.Close the remote mirror source.
obd mirror disable remote
# 3.Update.
......@@ -104,3 +104,56 @@ You may encounter a `Too many match` error, just select a `hash` on `Candidates`
```shell
obd cluster upgrade <deploy name> -c oceanbase-ce -V 3.1.2 --usable 7fafba0fac1e90cbd1b5b7ae5fa129b64dc63aed
```
## How do I upgrade an OBProxy to obproxy-ce 3.2.3?
The open source OBProxy component is formally renamed as obproxy-ce after v3.2.3. Therefore, you need to modify the metadata as the execution user of OBD. For more information, see [Execute the script] (2.how-to-upgrade-obproxy-to-obproxy-ce-3.2.3.md). Then, run the following command for upgrade:
```shell
obd cluster upgrade <deploy name> -c obproxy-ce -V 3.2.3
```
## What should I do if an exception occurs when I use OBD to upgrade an OBProxy?
The following problem may occur during the upgrade of OBProxy:
```bash
Stop obproxy ok
Start obproxy ok
obproxy program health check ok
Connect to obproxy x
```
The problem indicates that OBD cannot connect to the OBProxy, the reason may be either of the following:
1. The access from other IP addresses except 127.0.0.1 is disabled by proxysys. As a result, the OBD server cannot establish a connection. In this case, run the following command to connect to proxysys:
```bash
obclient -h<obproxy_ip> -uroot@proxysys -P<obproxy_post> -p<obproxy_pwd>
```
> **Note**
>
> If the connection fails with a password set by yourself, change the password to empty or to `proxysys`.
Then, run the `alter proxyconfig set skip_proxy_sys_private_check = true` command.
2. The password of proxysys is inconsistent with the one stored in OBD. In this case, run the corresponding command to connect to proxysys, and then run the `alter proxyconfig set obproxy_sys_password = <obproxy_pwd>` command to change the password of proxysys.
You can change the password to empty (`obproxy_sys_password = ''`) or to the password stored in the configuration file of OBD.
If the problem persists, submit an issue on GitHub (<https://github.com/oceanbase/obdeploy/issues>), and designated professionals will help you fix the issue.
## What should I do if the OBProxy service cannot be started after OBD is upgraded?
An OBD upgrade will initialize the OBProxy password. If `obproxy_sys_password` is specified, run the following command to connect to proxysys:
```bash
obclient -h<obproxy_ip> -uroot@proxysys -P<obproxy_post> -p<obproxy_pwd>
```
> **Note**
>
> If the connection fails with a password set by yourself, change the password to empty or to `proxysys`.
Then, run the `alter proxyconfig set obproxy_sys_password = ''` command to set the password of proxysys to empty, or set the password to the one specified by `obproxy_sys_password` in the configuration file.
# How do I upgrade an OBProxy to obproxy-ce 3.2.3?
The open source OBProxy component is formally renamed as obproxy-ce. Therefore, the error `No such package obproxy-3.2.3` will be reported if you run the following command for an upgrade:
```shell
obd cluster upgrade <deploy name> -c obproxy -V 3.2.3
```
You need to run the following **script** as the execution user of OBD to modify the metadata, and then run the following command to upgrade the OBProxy:
```shell
obd cluster upgrade <deploy name> -c obproxy-ce -V 3.2.3
```
## script
```bash
OBD_HOME=${OBD_HOME:-${HOME}}/.obd
obproxy_repository=${OBD_HOME}/repository/obproxy
obproxy_ce_repository=${OBD_HOME}/repository/obproxy-ce
function shadow_repo() {
repository_path=$1
ce_repository_path=$2
[[ $repository_path =~ ^/ ]] && a=$repository_path || a=`pwd`/$repository_path
while [ -h $a ]
do
b=`ls -ld $a|awk '{print $NF}'`
c=`ls -ld $a|awk '{print $(NF-2)}'`
[[ $real_patn =~ ^/ ]] && a=$b || a=`dirname $c`/$b
done
instance_hash=`basename $a`
ce_version_path=`dirname ${ce_repository_path}`
ln -sf ${ce_version_path}/${instance_hash} ${ce_repository_path}
}
function copy_repository() {
VS=(`ls $obproxy_repository`)
for version in ${VS[@]}; do
version_path="${obproxy_repository}/${version}"
ce_version_path="${obproxy_ce_repository}/${version}"
repositories=(`ls $version_path`)
mkdir -p $ce_version_path
for repository in ${repositories[@]}; do
repository_path="${version_path}/${repository}"
ce_repository_path="${ce_version_path}/${repository}"
if [ -d "$ce_repository_path" ];
then
echo "${ce_repository_path} exist"
else
if [ -L ${repository_path} ];
then
shadow_repo ${repository_path} ${ce_repository_path}
else
cp -r ${repository_path} ${ce_repository_path}
fi
fi
done
done
}
function change_cluster_meta() {
cluster_home_path=${OBD_HOME}/cluster
CS=(`ls ${cluster_home_path}`)
for cluster in ${CS[@]}; do
cluster_path=${cluster_home_path}/$cluster
if [ -f ${cluster_path}/.data ]; then
sed -i 's/^ obproxy:/ obproxy-ce:/g' ${cluster_path}/.data
fi
sed -i 's/^obproxy:/obproxy-ce:/' ${cluster_path}/*.yaml
done
}
copy_repository && change_cluster_meta && echo 'ok'
```
# Error codes
This topic summarizes the errors that may occur during the use of OBD.
## General errors
### OBD-1000: Configuration conflict x.x.x.x: xxx port is used for x.x.x.x
Cause: Port conflicts occur in the configuration file.
Solution: Check and modify the configuration.
### OBD-1001: x.x.x.x:xxx port is already used
Cause: The port has been occupied.
Solution: Check the configuration and change the port.
### OBD-1002: Fail to init x.x.x.x path
Cause:
1. `user` in the configuration file (the current user by default, if unspecified) does not have the write permission on the corresponding directory.
2. home_path is not empty.
You can determine the cause based on the error information.
Solution:
For case 1, you can resolve the problem in two ways.
- Run the following command to add or modify `user` information:
```shell
obd cluster edit-config <deploy name>
```
- Log on to the target server and grant the current account the write permission on the corresponding directory.
For case 2, you can also resolve the problem in two ways.
- Select another directory.
- If you are sure that the current directory can be cleared, you can use the `-f` option. OBD will clear this directory by using the current user.
### OBD-1003: fail to clean x.x.x.x:xxx
Cause: `user` in the configuration file (the current user by default, if unspecified) does not have the write permission on the home_path directory.
Solution: You can resolve the problem in two ways.
- Run the following command to add or modify `user` information:
```shell
obd cluster edit-config <deploy name>
```
- Log on to the target server and grant the current account the write permission on the corresponding directory.
### OBD-1004: Configuration conflict x.x.x.x: xxx is used for x.x.x.x
Cause: Path conflicts occur in the configuration file.
Solution: Check and modify the configuration.
### OBD-1005: Some of the servers in the cluster have been stopped
Cause: Some servers in the current configuration have been stopped, but subsequent operations require the services of all servers to be online.
Solution: Run the `obd cluster start <deploy_name> --wop` command to start all services without loading parameters.
### OBD-1006: Failed to connect to xxx
Cause:
1. OceanBase Deployer (OBD) and the specified server are disconnected.
2. The corresponding component process has exited or does not provide service.
3. The account and password do not match.
Solution:
If the error is due to cause 1, you need to solve the network connection issue.
If the error is due to cause 2, you can try restarting the component first. If the startup still fails, please refer to the error of startup failure for troubleshooting, such as **OBD-2002**.
If the error is due to cause 3, it is likely that you have changed the password by executing SQL statements, and the account password is different from that stored in the configuration file. As a result, OBD cannot connect to the component. In this case, you can use any of the following two solutions:
1. Execute SQL statements to change the password back to that stored in the configuration file of OBD.
2. Run the `vi ~/.obd/cluster/<deploy name>/config.yaml` command to change the password to the one that is in use for the component.
### OBD-1007: (x.x.x.x) xxx must not be less than xxx (Current value: xxx)
Cause: The configuration of the ulimit parameter does not meet the requirements.
Solution: You can modify the corresponding files in the /etc/security/limits.d/ directory and the limits.conf file in the /etc/security/ directory as needed.
## OceanBase deployment errors
### OBD-2000: x.x.x.x not enough memory
Cause: The memory is insufficient.
Solution: When OBD starts, the memory is strictly calculated based on MemAvailable. If any cached memory can be released, run the following command:
```shell
echo 3 > /proc/sys/vm/drop_caches
```
If the memory is still insufficient, run `edit-config` and then adjust `memory_limt` and `system_memory`. Ensure that the following condition is met: `memory_limt/3 ≤ system_memory ≤ memory_limt/2`.
> **Note**
>
> `memory_limt` cannot be lower than 8 GB. In other words, your available memory must be greater than 8 GB.
### OBD-2001: server can not migrate in
Cause: The number of available units is smaller than `--unit-num`.
Solution: Modify the passed value of `--unit-num`. Run the following command to view the number of available units:
```sql
select count(*) num from oceanbase.__all_server where status = 'active' and start_service_time > 0
```
### OBD-2002: failed to start x.x.x.x observer
Cause: There are multiple causes for this error. Two most common causes are as follows.
- `memory_limit` is lower than 8 GB.
- `system_memory` is too large or small. Generally, the following condition must be met: `memory_limt/3 ≤ system_memory ≤ memory_limt/2`.
Solution:
- If the problem is caused by either of the preceding reasons, take actions accordingly.
- If the problem persists, submit an issue on GitHub (<https://github.com/oceanbase/obdeploy/issues>), and designated professionals will help you fix the issue.
### OBD-2003: not enough disk space for clog. Use redo_dir to set other disk for clog, or reduce the value of datafile_size
Cause: The disk usage exceeds the limit.
Solution: Adjust the storage of disks.
- For automatic deployment, the disk usage cannot exceed 72%.
- For manual deployment, the disk usage cannot exceed 64%, if the configuration is not modified.
> **Note**
>
> If redo_dir and data_dir are on the same disk, the space to be occupied by data files is included when the disk usage is calculated.
### OBD-2004: Invalid: xxx is not a single server configuration item
Cause: The modified parameter is a global one and cannot be separately modified for a single server.
Solution: Place the parameter to modify under global.
## Test errors
### OBD-3000: parse cmd failed
Cause: The mysqltest initialization file is not an `.sql` file.
Solution: Check the `--init-sql-files` parameter.
### OBD-3001: xxx.sql not found
Cause: The initialization file cannot be found during the initialization of mysqltest.
Solution: Check whether the file declared by `--init-sql-files` is located under the `--init-sql-dir` directory.
### OBD-3002: Failed to load data
Cause: There are multiple causes for this error. Two most common causes are as follows.
1. The tenant has insufficient resources or is under excessive test stress.
2. An error occurred in the data build script.
Solution:
If the error is due to cause 1, you can use a tenant with larger resource specifications or adjust parameters such as warehouses and load-workers to reduce the test stress.
If the error is due to cause 2, you can rerun the test because the data build script is obtained from the TPC official website. If the issue persists, submit an issue on GitHub (<https://github.com/oceanbase/obdeploy/issues>), and designated professionals will help you fix the issue.
### OBD-3003: Failed to run TPC-C benchmark
Cause:
1. The test process was stuck and then terminated due to timeout.
2. An error occurred for the TPC-C test command.
Solution:
- You can try to rerun the test directly, or you can adjust parameters such as terminals to reduce the test pressure before you rerun the test.
- If you did not use the obtpcc package provided on the OceanBase Database official website, use obtpcc for testing.
If the issue persists, submit an issue on GitHub (<https://github.com/oceanbase/obdeploy/issues>), and designated professionals will help you fix the issue.
## OBAgent errors
### OBD-4000: Fail to reload x.x.x.x
Cause: The `http_basic_auth_password` of the node is not the same as that stored in OBD, which causes OBD to fail to access OBAgent.
Solution: If the two passwords are the same, check whether an unsupported parameter is included among the modified options, or whether the name of a parameter is incorrect.
### OBD-4001: Fail to send config file to x.x.x.x
Cause: (Check whether the error is caused by either of the reasons.)
- The disk space for the home_path directory on OBAgent is insufficient.
- `user` in the configuration file (the current user by default, if unspecified) does not have the write permission on the home_path directory on OBAgent.
Solution: You can resolve the problem in two ways.
- Run the following command to add or modify `user` information:
```shell
obd cluster edit-config <deploy name>
```
- Log on to the target server and grant the current account the write permission on the corresponding directory.
# 快速启动 OceanBase 数据库
安装 OBD 后,您可以使用 root 用户执行下文命令快速启动本地单节点 OceanBase 数据库。
在此之前您需要确认以下信息:
安装 OBD 后,您可执行 `obd demo` 命令快速启动本地单节点 OceanBase 数据库。在此之前您需要确认以下信息:
* 当前用户为 root
* `2881``2882` 端口没有被占用
* `2882``2883` 端口没有被占用
* 机器可用内存不低于 `6 G`
* 您的机器内存不低于 `8 G`
* 您的机器 CPU 数目不低于 `2`
* 机器 CPU 数目不低于 `2`
> **说明**
>
> * 如果以上条件不满足,您可参考文档 [使用 OBD 启动 OceanBase 数据库集群](../3.user-guide/2.start-the-oceanbase-cluster-by-using-obd.md)。
> * 此处为了方便使用 root,OBD 和 OceanBase 数据库没有对运行用户做出任何限制,我们不建议生产环境中直接使用 root。
> 如果以上条件不满足,您可参考文档 [使用 OBD 启动 OceanBase 数据库集群](../3.user-guide/2.start-the-oceanbase-cluster-by-using-obd.md)。
```shell
obd cluster deploy c1 -c ./example/mini-local-example.yaml
obd cluster start c1
# 使用 MySQL 客户端链接到到 OceanBase 数据库。
mysql -h127.1 -uroot -P2883
# 部署并启动 OceanBase 数据库
obd demo
# 使用 OBClient 客户端连接到 OceanBase 数据库。
obclient -h127.0.0.1 -uroot -P2881
```
......@@ -4,39 +4,39 @@
## 选择配置文件
请根据您的资源条件选择正确的配置文件。
OBD 针对不同的部署场景提供了不同的配置文件。这些配置文件示例放在目录 `/usr/obd/example/` 下。请根据您的资源条件选择正确的配置文件。
### 小规格开发模式
适用于个人设备(内存不低于 8G)。
* [本地单节点配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/mini-local-example.yaml)
* 本地单节点配置样例:/usr/obd/example/mini-local-example.yaml
* [单节点配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/mini-single-example.yaml)
* 单节点配置样例:/usr/obd/example/mini-single-example.yaml
* [三节点配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/mini-distributed-example.yaml)
* 三节点配置样例:/usr/obd/example/mini-distributed-example.yaml
* [单节点 + ODP 配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/mini-single-with-obproxy-example.yaml)
* 单节点 + ODP 配置样例:/usr/obd/example/mini-single-with-obproxy-example.yaml
* [三节点 + ODP 配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/mini-distributed-with-obproxy-example.yaml)
* 三节点 + ODP 配置样例:/usr/obd/example/mini-distributed-with-obproxy-example.yaml
### 专业开发模式
适用于高配置 ECS 或物理服务器(不低于 16 核 64G 内存)。
* [本地单节点配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/local-example.yaml)
* 本地单节点配置样例:/usr/obd/example/local-example.yaml
* [单节点配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/single-example.yaml)
* 单节点配置样例:/usr/obd/example/single-example.yaml
* [三节点配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/distributed-example.yaml)
* 三节点配置样例:/usr/obd/example/distributed-example.yaml
* [单节点 + ODP 配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/single-with-obproxy-example.yaml)
* 单节点 + ODP 配置样例:/usr/obd/example/single-with-obproxy-example.yaml
* [三节点 + ODP 配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/distributed-with-obproxy-example.yaml)
* 三节点 + ODP 配置样例:/usr/obd/example/distributed-with-obproxy-example.yaml
* [三节点 + ODP + obagent 配置样例](https://github.com/oceanbase/obdeploy/blob/master/example/obagent/distributed-with-obproxy-and-obagent-example.yaml)
* 三节点 + ODP + obagent 配置样例:/usr/obd/example/obagent/distributed-with-obproxy-and-obagent-example.yaml
本文以 [小规格开发模式-本地单节点](https://github.com/oceanbase/obdeploy/blob/master/example/mini-local-example.yaml) 为例,启动一个本地单节点的 OceanBase 数据库。
本文以小规格开发模式-本地单节点(/usr/obd/example/mini-local-example.yaml)为例,启动一个本地单节点的 OceanBase 数据库。
```bash
# 修改 OceanBase 数据库的工作目录 home_path。
......@@ -46,6 +46,7 @@ vi ./example/mini-local-example.yaml
```
> **注意**
>
> 如果您的目标机器(OceanBase 数据库程序运行的机器)不是当前机器,请不要使用 `本地单节点配置样例`,改用其他样例。 同时您还需要修改配置文件顶部的用户密码信息。
```yaml
......@@ -58,6 +59,7 @@ key_file: <您的私钥路径>
`username` 为登录到目标机器的用户名,确保您的用户名有 `home_path` 的写权限。`password``key_file` 均用于验证用户,通常情况下只需要填写一个。
> **注意**
>
> 在配置秘钥路径后,如果您的秘钥不需要口令,请注释或者删除 `password`,以免 `password` 被视为秘钥口令用于登录,导致校验失败。
## 部署和启动数据库
......
# 快速部署命令
## obd demo
使用该命令可在不传入配置文件的情况下直接在本机部署并启动指定的组件,固定部署名 `demo`,部署后使用命令 `obd cluster list` 查看集群列表时可以查看到该集群,也可以通过其他的集群命令进行管理,比如 `obd cluster display demo` 等。
```bash
obd demo [-c/--components]
```
选项说明见下表:
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|------------------|---------|------------|----------|--------------------------------------------------------------------|
| -c/--components | 否 | string | oceanbase-ce,obproxy-ce,obagent,prometheus,grafana | 组件列表,使用英文逗号(`,`)间隔。用于指定需要部署的组件。 |
该命令默认在当前用户的家目录下进行最小规格部署,部署的组件版本默认为最新版本。当前支持组件为:oceanbase-ce、obproxy-ce、obagent、grafana、Prometheus。
使用时可以通过选择控制部署版本和配置,比如:
```bash
# 部署指定组件版本
obd demo -c oceanbase-ce,obproxy-ce --oceanbase-ce.version=3.1.3
# 指定部署特定组件——hash
obd demo -c oceanbase-ce,obproxy-ce --oceanbase-ce.package_hash=f38723204d49057d3e062ffad778edc1552a7c114622bf2a86fea769fbd202ea
# 指定部署全部组件的安装路径
## 将 oceanbase-ce 和 obproxy-ce 部署到 /data/demo 下并根据组件建立对应的工作目录
obd demo -c oceanbase-ce,obproxy-ce --home_path=/data/demo
# 指定部署全部组件的安装路径
obd demo --home_path=/path
# 指定部署特定组件的安装路径
## 将 oceanbase-ce 部署到家目录下并根据组件建立对应的工作目录,而 obproxy-ce 部署到 /data/demo/obproxy-ce
obd demo -c oceanbase-ce,obproxy-ce --obproxy-ce.home_path=/data/demo/
# 指定自定义组件配置
## 指定 oceanbase-ce 组件的 mysql_port
obd demo --oceanbase-ce.mysql_port=3881
```
> **注意**
>
> 该命令只支持通过选项传入一级配置(即 global 下第一级配置)。
......@@ -14,7 +14,7 @@ OBD 集群命令操作的最小单位为一个部署配置。部署配置是一
obd cluster autodeploy <deploy name> -c <yaml path> [-f] [-U] [-A] [-s]
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项说明见下表:
......@@ -34,28 +34,28 @@ obd cluster autodeploy <deploy name> -c <yaml path> [-f] [-U] [-A] [-s]
obd cluster edit-config <deploy name>
```
参数 `deploy name` 为部署配置名称,可以理解为配置文件名称
参数 `deploy name` 为部署集群名,可以理解为配置文件的别名
## obd cluster deploy
使用该命令可以根据配置部署集群。
此命令会根据部署配置文件中组件的信息查找合适的镜像,并安装到本地仓库,此过程称为本地安装。 再将本地仓库中存在合适版本的组件分发给目标服务器,此过程称为远程安装。
此命令会根据部署配置文件中组件的信息查找合适的镜像,并安装到本地仓库,此过程称为本地安装。再将本地仓库中存在合适版本的组件分发给目标服务器,此过程称为远程安装。
在本地安装和远程安装时都会检查服务器是否存在组件运行所需的依赖。 此命令可以直接使用 OBD 中已注册的 `deploy name` 部署,也可以通过传入 `yaml` 的配置信息。
在本地安装和远程安装时都会检查服务器是否存在组件运行所需的依赖。此命令可以直接使用 OBD 中已注册的 `deploy name` 部署,也可以通过传入 `yaml` 的配置信息。
```shell
obd cluster deploy <deploy name> [-c <yaml path>] [-f] [-U] [-A]
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项说明见下表:
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|-------------------------------|------|--------|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| -c/--config | 否 | string | 无 | 使用指定的 yaml 文件部署,并将部署配置注册到 OBD 中。当 `deploy name` 存在时覆盖配置。</br>如果不使用该选项,则会根据 `deploy name` 查找已注册到 OBD 中的配置信息。 |
| -f/--force | 否 | bool | false | 开启时,强制清空工作目录。当组件要求工作目录为空且不使用选项时,工作目录不为空会返回错误。 |
| -f/--force | 否 | bool | false | 开启时,强制清空工作目录。当组件要求工作目录为空且不使用选项时,工作目录不为空会返回错误。 |
| -U/--ulp/ --unuselibrepo | 否 | bool | false | 使用该选项将禁止 OBD 自动处理依赖。不开启的情况下,OBD 将在检查到缺失依赖时搜索相关的 libs 镜像并安装。</br>使用该选项将会在对应的配置文件中添加 `unuse_lib_repository: true`。也可以在配置文件中使用 `unuse_lib_repository: true` 开启。 |
| -A/--act/--auto-create-tenant | 否 | bool | false | 开启该选项 OBD 将会在 bootstrap 阶段使用集群全部可用资源创建一个名为 `test` 的租户。</br>使用该选项将会在对应的配置文件中添加 `auto_create_tenant: true`。也可以在配置文件中使用 `auto_create_tenant: true` 开启。 |
......@@ -67,7 +67,7 @@ obd cluster deploy <deploy name> [-c <yaml path>] [-f] [-U] [-A]
obd cluster start <deploy name> [flags]
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项说明见下表:
......@@ -94,33 +94,35 @@ obd cluster list
obd cluster display <deploy name>
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
## obd cluster reload
重载一个运行中集群。当您使用 `edit-config` 修改一个运行的集群的配置信息后,可以通过 `reload` 命令应用修改。
> **注意**
>
> 并非全部的配置项都可以通过 `reload` 来应用。有些配置项需要重启集群,甚至是重新部署集群才能生效。请根据 `edit-config` 后返回的信息进行操作。
```shell
obd cluster reload <deploy name>
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
## obd cluster restart
重启一个运行中集群。重启默认是无参重启。当您使用 edit-config 修改一个运行的集群的配置信息后,可以通过 `obd cluster restart <deploy name> --wp` 命令应用修改。
> **注意**
>
> 并非所有的配置项都可以通过 `restart` 来应用。有些配置项需要重部署集群才能生效。请根据 `edit-config` 后返回的信息进行操作。
```shell
obd cluster restart <deploy name>
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项说明见下表:
......@@ -135,13 +137,14 @@ obd cluster restart <deploy name>
重启一个运行中集群。当您使用 `edit-config` 修改一个运行的集群的配置信息后,可以通过 `redeploy` 命令应用修改。
> **注意**
>
> 该命令会销毁集群,重新部署,您集群中的数据会丢失,请先做好备份。
```shell
obd cluster redeploy <deploy name>
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
## obd cluster stop
......@@ -151,7 +154,7 @@ obd cluster redeploy <deploy name>
obd cluster stop <deploy name>
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项说明见下表:
......@@ -168,7 +171,7 @@ obd cluster stop <deploy name>
obd cluster destroy <deploy name> [-f]
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项 `-f``--force-kill`。作用为:检查到工作目录下有运行中的进程时,强制停止。销毁前会做检查是有还有进程在运行中。这些运行中的进程可能是 `start` 失败留下的,也可能是因为配置与其他集群重叠,进程是其他集群的。但无论是哪个原因导致工作目录下有进程未退出,`destroy` 都会直接停止。
......@@ -182,17 +185,33 @@ obd cluster destroy <deploy name> [-f]
obd cluster upgrade <deploy name> -c <component name> -V <version> [tags]
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|--------------------|------|--------|-----------------------|-----------------------------|
| -c/--component | 是 | string | 空 | 需要升级的组件名。 |
| -V/--version | 是 | string | 目标版本号 | |
| -V/--version | 是 | string | 空 | 目标版本号 |
| --skip-check | 否 | bool | false | 跳过可以跳过的检查。 |
| --usable | 否 | string | 空 | 升级中使用到的镜像 hash 列表,用 `,` 间隔。 |
| --disable | 否 | string | 空 | 升级中禁用到的镜像 hash 列表,用 `,` 间隔。 |
| -e/--executer-path | 否 | string | /usr/obd/lib/executer | 升级脚本使用的解释器所在路径。 |
## obd cluster reinstall
使用该命令可重新安装一个已部署的组件的仓库,新的仓库必须与当前仓库版本号相同。该命令在部署状态为 `running` 时,替换仓库后会使用无参启动,重新拉起组件。
```shell
obd cluster reinstall <deploy name> -c <component name> --hash <hash> [-f/--force]
```
参数 `deploy name` 为部署集群名,可以理解为配置文件的别名。
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|------- | ------- | ------- |------- |-----|
| -c/--component | 是 | string | 空 | 要替换仓库的组件名。|
| --hash | 是 | string | 空 | 目标仓库。必须与当前仓库版本号相同。|
| -f/--force | 否 | bool | false | 启动失败也强制替换。|
## obd cluster tenant create
创建租户。该命令仅对 OceanBase 数据库有效。该命令会自动创建资源单元和资源池,用户不需要手动创建。
......@@ -201,7 +220,7 @@ obd cluster upgrade <deploy name> -c <component name> -V <version> [tags]
obd cluster tenant create <deploy name> [-n <tenant name>] [flags]
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项说明见下表:
......@@ -214,14 +233,14 @@ obd cluster tenant create <deploy name> [-n <tenant name>] [flags]
| --min-memory | 否 | int | 0 | 租户可用最小内存。为 `0` 时等同于 `--max-memory`。 |
| --max-disk-size | 否 | int | 0 | 租户可用最大磁盘空间。为 `0` 时使用集群全部可用空间。实际值低于 `512M` 时报错。 |
| --max-iops | 否 | int | 128 | 租户 IOPS 最多数量,取值范围为 \[128,+∞)。 |
| --min-iops | 否 | int | 0 | 租户 IOPS 最少数量。取值范围为 \[128,+∞)。为 0 时等同于 `--max-iops`。 |
| --min-iops | 否 | int | 0 | 租户 IOPS 最少数量。取值范围为 \[128,+∞)。为 `0` 时等同于 `--max-iops`。 |
| --max-session-num | 否 | int | 64 | 租户最大 SESSION 数,取值范围为 \[64,+∞)。 |
| --unit-num | 否 | int | 0 | 指定要创建的单个 ZONE 下的单元个数,取值要小于单个 ZONE 中的 OBServer 个数。为 `0` 自动获取最大值。 |
| -z/--zone-list | 否 | string | 空 | 指定租户的 ZONE 列表,多个 ZONE 用英文逗号 `,` 间隔。为空时等于集群全部 ZONE。 |
| --unit-num | 否 | int | 0 | 指定要创建的单个 Zone 下的单元个数,取值要小于单个 Zone 中的 OBServer 个数。为 `0` 自动获取最大值。 |
| -z/--zone-list | 否 | string | 空 | 指定租户的 Zone 列表,多个 Zone 用英文逗号 `,` 间隔。为空时等于集群全部 ZONE。 |
| --primary-zone | 否 | string | RANDOM | 租户的主 Zone。 |
| --charset | 否 | string | 空 | 租户的字符集。 |
| --collate | 否 | string | 空 | 租户校对规则。 |
| --replica-num | 否 | int | 0 | 租户副本数。为 `0` 时等于 ZONE 的数目。 |
| --replica-num | 否 | int | 0 | 租户副本数。为 `0` 时等于 Zone 的数目。 |
| --logonly-replica-num | 否 | string | 0 | 租户日志副本数。为 `0` 时等同于 `--replica-num`。 |
| --tablegroup | 否 | string | 空 | 租户默认表组信息 |
| --locality | 否 | string | 空 | 描述副本在 Zone 间的分布情况,如:F@z1,F@z2,F@z3,R@z4 表示 z1, z2, z3 为全功能副本,z4 为只读副本。 |
......@@ -237,6 +256,42 @@ obd cluster tenant create <deploy name> [-n <tenant name>] [flags]
obd cluster tenant drop <deploy name> [-n <tenant name>]
```
参数 `deploy name`集群名称,一个集群只能有一个名称,且集群名称不能重复
参数 `deploy name`部署集群名,可以理解为配置文件的别名
选项 `-n``--tenant-name`,此选项为必填项,表示要删除的租户名。
## obd cluster chst
使用该命令可以转换配置风格。
```shell
obd cluster chst <deploy name> --style <STYLE> [-c/--components]
```
参数 `deploy name` 为部署集群名,可以理解为配置文件的别名。
选项说明见下表:
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|-----------------------|------|--------|--------------------------|------------------------------------------------------------------------|
| --style | 是 | string | 无 | 目标配置风格。目前支持 default 和 cluster。 |
| -c/--components | 否 | string | 空 | 组件列表,用 `,` 间隔。用于指定转换风格的组件。 |
## obd cluster check4ocp
检查当前配置是否满足 OCP 接管的条件。
```shell
obd cluster check4ocp <deploy name> [-c/--components] [-V/--version]
```
参数 `deploy name` 为部署集群名,可以理解为配置文件的别名。
选项说明见下表:
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|-----------------------|------|--------|--------------------------|------------------------------------------------------------------------|
| -c/--components | 否 | string | 空 | 组件列表,用 `,` 间隔。用于指定转换风格的组件。 |
| -V/--version | 是 | string | 3.1.0 | OCP 版本号。 |
......@@ -22,7 +22,7 @@ obd mirror clone <path> [-f]
obd mirror create -n <component name> -p <your compile dir> -V <component version> [-t <tag>] [-f]
```
例如,如果您根据文档 [使用源码构建 OceanBase 数据库](https://open.oceanbase.com/docs/observer-cn/V3.1.2/10000000000014810) 编译 OceanBase 数据库,在编译成功后,可以使用 `make DESTDIR=./ install && obd mirror create -n oceanbase-ce -V <component version> -p ./usr/local` 命令将编译产物添加至 OBD 本地仓库。
例如,如果您根据文档 [使用源码构建 OceanBase 数据库](https://www.oceanbase.com/docs/community-observer-cn-0000000000160092) 编译 OceanBase 数据库,在编译成功后,可以使用 `make DESTDIR=./ install && obd mirror create -n oceanbase-ce -V <component version> -p ./usr/local` 命令将编译产物添加至 OBD 本地仓库。
选项说明见下表:
......
# 工具命令组
OBD 提供了一系列工具命令,其中封装了一些常用命令,可用于提升开发者的使用体验。
## obd devmode enable
使用该命令可以开启开发者模式,是使用工具命令组的前提。开启开发者模式后,会出现部分异常报错被降级,OBD 忽略异常参数等情况。非内核开发人员请谨慎使用。
```shell
obd devmode enable
```
## obd devmode disable
使用该命令可关闭开发者模式。
```shell
obd devmode disable
```
## obd env show
使用该命令可展示 OBD 的环境变量。
```shell
obd env show
```
## obd env set
使用该命令可设置 OBD 的环境变量,这些环境变量会一定程度的影响 OBD 的表现,若没有特别需求不建议使用该命令。
```shell
obd env set [key] [value]
```
可设置的参数有:
* OBD_DISABLE_RSYNC:参数值可设置为 0 或 1,在符合条件的情况下 OBD 会使用 rsync 进行远程传输,当该环境变量为 1 时,禁止使用 rsync 命令。
* OBD_DEV_MODE::开发者模式是否开启,可选值为 0 或 1。
## obd env unset
使用该命令可删除指定环境变量。
```shell
obd env unset [key] [value]
```
## obd env clear
使用该命令可清理 OBD 的环境变量,请谨慎使用。
```shell
obd env clear
```
## obd tool command
使用该命令可执行一些常用的命令。
```shell
obd tool command <deploy name> <command> [options]
```
命令包含:
* pid:查看服务的 pid(非交互式命令)
* ssh:登录到目标 server 并进入 log 目录(交互式命令)
* less:查看目标服务的日志(交互式命令)
* gdb:gdb attach 到模板服务(交互式命令)
参数说明见下表
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|-----------------|------|--------|-------------------------------------------------------|---------------------------|
| -c/--components | 否 | string | 如果是交互式命令默认按照配置文件顺序选择第一个组件,如果是非交互式命令,则默认使用所有组件 | 需要执行命令的组件名。多个组件名以 `,` 相隔。 |
| -s/--servers | 否 | string | 如果是交互式命令默认按照配置文件顺序选择当前组件的第一个节点名,如果是非交互式命令,则默认使用所有可用节点 | 指定的组件下的节点名。多个节点名以 `,` 相隔。 |
## obd tool db_connect
使用该命令可建立数据库连接。
```shell
obd tool db_connect <deploy name> [options]
```
参数 `deploy name` 为部署集群名,可以理解为配置文件的别名。
参数说明见下表
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|---------------------|------|--------|---------------------------|-------------------------------------------------------------------|
| -c/--component | 否 | string | 默认按照配置文件顺序选择第一个组件 | 待连接的组件名。候选项为 `obproxy``obproxy-ce``oceanbase``oceanbase-ce`。 |
| -s/--server | 否 | string | 默认按照配置文件顺序选择当前组件的第一个节点名 | 指定的组件下的节点名。 |
| -u/--user | 否 | string | root | 数据库连接使用的用户名。 |
| -p/--password | 否 | string | 默认为空 | 数据库连接使用的密码。 |
| -t/--tenant | 否 | string | sys | 数据库连接使用的租户。 |
| -D/--database | 否 | string | 默认为空 | 数据库连接使用的数据库名称。 |
| --obclient-bin | 否 | string | obclient | OBClient 二进制文件路径。 |
# 使用 OCP 接管 OBD 部署的集群
本文将以一个使用配置文件 distributed-example.yaml 启动的 test 部署为例,介绍如何使用 OCP 接管 OBD 部署的集群。
## 前提条件
- 请确保您安装的 OBD 版本在 V1.3.0 及以上。
- 请确保您安装的 OCP 版本在 V3.1.1-ce及以上。
## 修改 OceanBase 集群
### 检查是否满足条件
在使用 OCP 接管 OBD 部署的集群前,您需先使用如下命令检查是否满足接管条件。如条件不满足,则可以根据提示参考下文进行修改。
```shell
obd cluster check4ocp <deploy-name>
# 示例
obd cluster check4ocp test
```
有关 `obd cluster check4ocp` 命令的具体信息请参考 [obd cluster check4ocp](3.obd-command/1.cluster-command-groups.md)
### 设置 IDC 信息
默认风格的配置文件不支持配置 IDC 信息,因此需要使用 OBD 1.3.0 版本的新功能,将配置文件风格转换成 cluster 风格。
您可使用如下命令进行转换:
```shell
obd cluster chst <deploy name> --style <STYLE> [-c/--components]
# 示例
obd cluster chst test -c oceanbase-ce --style cluster
```
有关 `obd cluster chst` 命令的具体信息请参考 [obd cluster chst](3.obd-command/1.cluster-command-groups.md)
配置风格文件转换完成后,您需使用如下命令进入到编辑模式为 Zone 添加 IDC 信息。
```shell
obd cluster edit-config <deploy name>
# 示例
obd cluster edit-config test
```
有关 `obd cluster edit-config` 命令的具体信息请参考 [obd cluster edit-config](3.obd-command/1.cluster-command-groups.md)
参考配置如下:
```yaml
## Only need to configure when remote login is required
# user:
# username: your username
# password: your password if need
# key_file: your ssh-key file path if need
# port: your ssh port, default 22
# timeout: ssh connection timeout (second), default 30
oceanbase-ce:
style: cluster
config:
devname: eth0
memory_limit: 64G
system_memory: 30G
datafile_disk_percentage: 20
syslog_level: INFO
enable_syslog_wf: false
enable_syslog_recycle: true
max_syslog_file_count: 4
skip_proxy_sys_private_check: true
enable_strict_kernel_release: false
mysql_port: 2881
rpc_port: 2882
home_path: /root/observer
root_password: xxxxxx
zones:
zone1:
idc: idc1
servers:
- name: server1
ip: xxx.xxx.xxx.xxx
zone2:
idc: idc2
servers:
- name: server2
ip: xxx.xxx.xxx.xxx
zone3:
idc: idc3
servers:
- name: server3
ip: xxx.xxx.xxx.xxx
```
配置文件修改后,您需运行如下命令使改动生效。
```shell
obd cluster reload <deploy name>
# 示例
obd cluster reload test
```
有关 `obd cluster reload` 命令的具体信息请参考 [obd cluster reload](3.obd-command/1.cluster-command-groups.md)
### 配置密码
使用 OCP 接管集群时需要填写 sys 租户下 root 用户连接集群的密码,您可使用如下命令编辑配置文件,并使用 `root_passwd` 来配置密码。
```shell
obd cluster edit-config <deploy name>
# 示例
obd cluster edit-config test
```
部分配置文件示例如下:
```yaml
## Only need to configure when remote login is required
# user:
# username: your username
# password: your password if need
# key_file: your ssh-key file path if need
# port: your ssh port, default 22
# timeout: ssh connection timeout (second), default 30
oceanbase-ce:
servers:
- name: server1
# Please don't use hostname, only IP can be supported
ip: xxx.xxx.xxx.xxx
- name: server2
ip: xxx.xxx.xxx.xxx
- name: server3
ip: xxx.xxx.xxx.xxx
global:
# The working directory for OceanBase Database. OceanBase Database is started under this directory. This is a required field.
home_path: /root/observer
# External port for OceanBase Database. The default value is 2881. DO NOT change this value after the cluster is started.
mysql_port: 2881
# Internal port for OceanBase Database. The default value is 2882. DO NOT change this value after the cluster is started.
rpc_port: 2882
# The maximum running memory for an observer. When ignored, autodeploy calculates this value based on the current server available resource.
memory_limit: 64G
# The reserved system memory. system_memory is reserved for general tenants. The default value is 30G. Autodeploy calculates this value based on the current server available resource.
system_memory: 30G
# Password for root. The default value is empty.
root_password: xxxxxx
# Password for proxyro. proxyro_password must be the same as observer_sys_password. The default value is empty.
# proxyro_password:
server1:
zone: zone1
server2:
zone: zone2
server3:
zone: zone3
```
上述为默认风格的配置文件示例,cluster 风格的配置文件请参考上文 **设置 IDC 信息** 中的配置示例。
配置文件修改后,您需运行如下命令使改动生效。
```shell
obd cluster reload <deploy name>
# 示例
obd cluster reload test
```
### 修改用户
OCP 要求进程必须是使用 admin 用户启动,且 admin 用户需要有免密 sudo 的权限。因此我们需要准备好可以免密 sudo 的 admin 用户。如果你已经满足此条件可以直接参考下文 **更改用户** 进行操作。
#### 创建用户
您可使用 root 用户参考如下操作在部署了 OBServer 的机器中创建 admin 用户。
```shell
# 创建用户组
groupadd admin
# 创建用户
useradd admin -g admin
```
创建 admin 用户后,您需为 admin 用户配置免密登录。有关如何配置免密 SSH 登录,详情请参考 [设置无密码 SSH 登录](https://www.oceanbase.com/docs/community-observer-cn-0000000000160095)
> **注意**
>
> 1. 您需要为 admin 用户配置 SSH 免密登录。
>
> 2. 这里需要配置的为私钥,即 `id_rsa`。
#### 免密 sudo
以下操作请在 root 用户下进行:
```yaml
# 添加 sudoers 文件的写权限
chmod u+w /etc/sudoers
# vi /etc/sudoers
echo 'admin ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
# 撤销 sudoers 文件写权限
chmod u-w /etc/sudoers
```
#### 更改用户
您可使用如下命令进入编辑模式修改 user 字段。
```shell
obd cluster edit-config <deploy name>
# 示例
obd cluster edit-config test
```
修改后的配置示例:
```yaml
## Only need to configure when remote login is required
user:
username: admin
# password: your password if need
key_file: your ssh-key file path if need # 设置为 admin 的 id_rsa 文件路径
# port: your ssh port, default 22
# timeout: ssh connection timeout (second), default 30
```
修改配置文件后,您需使用如下命令使改动生效。
```shell
obd cluster restart <deploy name>
# 示例
obd cluster restart test --wp
```
有关 `obd cluster restart` 命令的具体信息请参考 [obd cluster restart](3.obd-command/1.cluster-command-groups.md)
### 单机多 Server
OCP 要求一个台机器只能有一个 OBServer,目前没有适配单机多 Server 的场景。如果需要 OCP 接管单机多 Server 的集群,您需手动停止其他的 OBServer,保证一台机器只有一个 OBServer 在运行。
> **说明**
>
> 上述所有操作完成后,您可再次执行 `obd cluster check4ocp <deploy name>` 命令检查是否满足接管条件,若条件不满足,则可以根据提示进行修改。
## 使用 OCP 接管集群
### 处理 proxyro 密码
在 OCP 接管集群之前,需要确认待接管集群中的 proxyro 用户的密码,如果该密码非默认值,则需将 OCP 中的 proxyro 密码修改为待接管集群中的 proxyro 用户的密码。
您可调用 OCP API 进行修改
```bash
curl --user user:pass -X POST "http://ocp-site-url:port/api/v2/obproxy/password" -H "Content-Type:application/json" -d '{"username":"proxyro","password":"*****"}'
```
说明:
- `user:pass` 分别为 OCP 的用户和密码,并且要求调用的用户需要有 admin 权限。
- `-d` 参数后面的 `password` 为待接管集群的 proxyro 用户的密码。
该操作会生成运维任务,将 OCP 中现有 Oceanbase 集群的 proxyro 密码修改,同时修改 OBProxy 集群对应的配置。
您需等运维任务成功结束后才可进行后述步骤,如果任务失败,则需要重试并将任务执行成功之后才能执行后面的步骤。
![任务示例](https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/doc/img/obd/V1.3.0/zh-CN/4.configuration-file-description-01.png)
### OCP 接管 OceanBase 集群
您可直接在 OCP 的白屏页面进行接管 OceanBase 集群的操作,具体步骤请参考 [接管集群](https://www.oceanbase.com/docs/community-ocp-cn-10000000000407628)
使用 OCP 接管 OceanBase 集群后,您需新建 OBProxy 集群并关联接管的 OceanBase 集群,具体步骤请参考 [创建 OBProxy 集群](https://www.oceanbase.com/docs/community-ocp-cn-10000000000407609)
如果原 OBProxy 使用了 VIP,可以将 OCP 上新建的 OBProxy 逐个添加到 VIP 中,再逐个从 VIP 中下线原 OBProxy。
### FAQ
1. 为什么要修改 OCP 中的 proxyro 账号的密码?
因为 OCP 中管理的 OBProxy 一般是通过 configserver 拉起的,设计上是可以连接多个 OceanBase 集群,但是 OBProxy 只能全局的修改 proxyro 的用户密码,所以这个配置在 OCP 中是一个全局的配置,proxyro 用户的密码仅用来让 OBProxy 查询一些元数据,修改并不影响业务租户。
2. 切换新的 OBProxy,可以复用原来的机器吗?
如果原来部署了多台 OBProxy 并且通过 VIP 来访问,可以逐个下线,并且用相同的机器在 OCP 中部署 OBProxy,再添加回 VIP,通过这种方式实现机器复用。
3. 不切换 OBProxy 是否可以?
可以的,原 OBProxy 仍然可以正常连接接管的 OceanBase 集群,但是还是建议用 OCP 新建 OBProxy 进行替换,方便以后的运维管理。
......@@ -11,8 +11,8 @@ user: # ssh 登录配置
port: your ssh port, default 22
timeout: ssh connection timeout (second), default 30
oceanbase-ce: # 组件名,其下内容是对该组件的配置
# version: 3.1.0 # 指定组件版本,通常情况下不需要指定
# pacakge_hash: 9decc4788a7fc6cc2082ac1668f287f60f4a3b8d05a58da094605aa2f19d18fc # 指定组件 hash,通常情况下不需要指定
# version: 3.1.3 # 指定组件版本,通常情况下不需要指定
# pacakge_hash: 589c4f8ed2662835148a95d5c1b46a07e36c2d346804791364a757aef4f7b60d # 指定组件 hash,通常情况下不需要指定
# tag: dev # 指定组件 tag,通常情况下不需要指定
servers: # 节点列表
- name: z1 # name 后可不填,不填默认节点名与 IP 相同,这里指该节点名为 z1
......@@ -60,9 +60,9 @@ oceanbase-ce: # 组件名,其下内容是对该组件的配置
# The working directory for OceanBase Database. OceanBase Database is started under this directory. This is a required field.
home_path: /root/observer
zone: zone3
obproxy: # 组件名,其下内容是对组件 obproxy 的配置
# version: 3.1.0 # 指定组件版本,通常情况下不需要指定
# pacakge_hash: 62770120d2651738d808b7025396b9b3049f63761225ec7603805d318b6ed726 # 指定组件 hash,通常情况下不需要指定
obproxy-ce: # 组件名,其下内容是对组件 obproxy 的配置
# version: 3.2.3 # 指定组件版本,通常情况下不需要指定
# pacakge_hash: 73cccf4d05508de0950ad1164aec03003c4ddbe1415530e031ac8b6469815fea # 指定组件 hash,通常情况下不需要指定
# tag: dev # 指定组件 tag,通常情况下不需要指定
servers:
- 192.168.1.5
......
# 常见问题
# 常见问题汇总
## 如何指定使用组件的版本?
......@@ -6,7 +6,7 @@
```yaml
oceanbase-ce:
version: 3.1.0
version: 3.1.3
```
## 如何指定使用特定版本的组件?
......@@ -15,25 +15,25 @@ version: 3.1.0
```yaml
oceanbase-ce:
tag: my-oceanbase
tag: my-oceanbase
```
您也可以通过 `package_hash` 来指定特定的版本。当您使用 obd mirror 相关命令时会打印出组件的 `md5` 值,这个值即为 `package_hash`
```yaml
oceanbase-ce:
package_hash: 929df53459404d9b0c1f945e7e23ea4b89972069
package_hash: 589c4f8ed2662835148a95d5c1b46a07e36c2d346804791364a757aef4f7b60d
```
## 我修改了 OceanBase-CE 代码,需要修改启动流程怎么办?
## 我修改了 OceanBase-CE 代码,需要修改启动流程怎么办?
您可修改 `~/.obd/plugins/oceanbase-ce/` 下的启动相关插件。比如您为 3.1.0 版本的 OceanBase-CE 添加了一个新的启动配置,可以修改 `~/.obd/plugins/oceanbase-ce/3.1.0/start.py`
## 如何离线模式下更新 OBD 本地镜像?
## 如何离线模式下更新 OBD 本地镜像?
当您安装 OBD 的机器不能连接公网,却需要更新 OBD 或其他组件时,您可按以下步骤进行操作:
1. 在一台可以连通公网的机器下载好您需要的 RPM 包。
1. 在一台可以连通公网的机器下载好您需要的 RPM 包。
2. 将 RPM 包拷贝到安装有 OBD 的机器中。
......@@ -42,13 +42,13 @@ package_hash: 929df53459404d9b0c1f945e7e23ea4b89972069
此处以更新本地仓库中的 OBD 镜像为例:
```shell
# 先在一台可以连通公网的机器上下载 OBD 1.2.1 el7 RPM 包
# 先在一台可以连通公网的机器上下载 OBD 最新的 RPM 包
# 最新的 RPM 包链接可以在对应的组件的 git 仓库中的 release note 或 OceanBase 开源官网(https://open.oceanbase.com/softwareCenter/community)中获得
wget https://github.com/oceanbase/obdeploy/releases/download/v1.2.1/ob-deploy-1.2.1-9.el7.x86_64.rpm
wget https://github.com/oceanbase/obdeploy/releases/download/vx.x.x/ob-deploy-x.x.x-xxx.rpm
# 将下载好的 RPM 包拷贝到安装有 OBD 的机器(obd_server)上
sh ob-deploy-1.2.1-9.el7.x86_64.rpm obd_server:~
sh ob-deploy-x.x.x-xxx.rpm obd_server:~
# 将下载好的镜像加入到 local 中
obd mirror clone ob-deploy-1.2.1-9.el7.x86_64.rpm
obd mirror clone ob-deploy-x.x.x-xxx.rpm
# 关闭远程镜像源
obd mirror disable remote
```
......@@ -61,17 +61,17 @@ obd mirror disable remote
* 如果您的机器不能连通公网且您配置的 mirror 中没有用于更新的 OBD 的 RPM 包,请先通过 `obd mirror clone` 命令将用于更新的 OBD 的 RPM 包添加到 local mirror 中,之后再使用 `obd update` 命令升级 OBD。
下面展示在离线模式下,如何在 CentOS7 系统中将 OBD 升级到 V1.2.1
下面展示在离线模式下,如何在 CentOS7 系统中将 OBD 升级到最新版本
```shell
# 先在一台可以连通公网的机器上下载 OBD 1.2.1 el7 RPM 包
# 先在一台可以连通公网的机器上下载 OBD 最新的 RPM 包
# 最新的 RPM 包链接可以在 git 仓库中的 release note 或 OceanBase 开源官网(https://open.oceanbase.com/softwareCenter/community)中获得
wget https://github.com/oceanbase/obdeploy/releases/download/v1.2.1/ob-deploy-1.2.1-9.el7.x86_64.rpm
wget https://github.com/oceanbase/obdeploy/releases/download/vx.x.x/ob-deploy-x.x.x-xxx.rpm
# 将下载好的 RPM 包拷贝到安装有 OBD 的机器(obd_server)中
sh ob-deploy-1.2.1-9.el7.x86_64.rpm obd_server:~
sh ob-deploy-x.x.x-xxx.rpm obd_server:~
# 在 OBD 机器上执行以下命令完成升级
# 1.将下载好的镜像加入到 local 中
obd mirror clone ob-deploy-1.2.1-9.el7.x86_64.rpm
obd mirror clone ob-deploy-x.x.x-xxx.rpm
# 2.关闭远程镜像源
obd mirror disable remote
# 3.升级
......@@ -111,3 +111,64 @@ obd cluster upgrade <deploy name> -c oceanbase-ce -V 3.1.2
```shell
obd cluster upgrade <deploy name> -c oceanbase-ce -V 3.1.2 --usable 7fafba0fac1e90cbd1b5b7ae5fa129b64dc63aed
```
## 如何升级 obproxy 到 obproxy-ce 3.2.3?
由于开源 OBProxy 组件在 V3.2.3 之后正式更名为 obproxy-ce,所以您需在 OBD 的执行用户下 [执行脚本](2.how-to-upgrade-obproxy-to-obproxy-ce-3.2.3.md) 修改 meta 信息。而后使用以下命令进行升级。
```shell
obd cluster upgrade <deploy name> -c obproxy-ce -V 3.2.3
```
OBD 从 V1.3.0 开始仅支持使用 obproxy-ce 的组件名部署 V3.2.3 及之后版本的 OBProxy。但若您是使用 `obd update` 命令将 OBD 从低版本升级到 V1.3.0 及以上版本,仍支持使用 obproxy 组件名安装 V3.2.3 之前版本的 OBProxy(即:OBD 从 V1.3.0 开始不再提供 obproxy 插件库,但如果本地插件库中存在 obproxy 的插件库,则会被保留)。
> **说明**
>
> * 如果 OBD 升级后发现旧插件无法使用,可直接通过 RPM 包安装旧版本 OBD 进行覆盖。
>
> * 如果您安装的是新版本 OBD,但想使用 obproxy,也可安装 V1.3.0 之前版本的 OBD,在完成 obproxy 的部署后执行 `obd update` 命令升级 OBD,或安装新版本的 OBD 进行覆盖。
## 使用 OBD 升级 OBProxy 出现异常如何解决?
若您在升级 OBProxy 过程中出现如下问题:
```bash
Stop obproxy ok
Start obproxy ok
obproxy program health check ok
Connect to obproxy x
```
即 OBD 机器无法连接 OBProxy,可能原因有以下两种:
1. proxysys 禁用了非 127.0.0.1 的 IP 访问,导致 OBD 所在的机器不能建连,这种情况下请先执行如下命令连接到 proxysys:
```bash
obclient -h<obproxy_ip> -uroot@proxysys -P<obproxy_post> -p<obproxy_pwd>
```
> **说明**
>
> 若您在连接 proxysys 时,使用自己设置的 proxysys 密码无法连接,请尝试将密码设置为空或者 `proxysys` 进行连接。
之后执行 `alter proxyconfig set skip_proxy_sys_private_check = true` 命令。
2. proxysys 的密码与 OBD 中存储的不一致,这种情况下请先执行上述命令连接到 proxysys ,之后执行命令 `alter proxyconfig set obproxy_sys_password = <obproxy_pwd>` 修改 proxysys 密码。
您可将其密码修改为空(即 `obproxy_sys_password = ''`),或使其与 OBD 配置文件中存储的密码一致。
若排查后发现不是由上述两条原因引起的异常,您可到官网 [问答区](https://open.oceanbase.com/answer) 进行提问,会有专业人员为您解答。
## OBD 升级后无法启动 OBProxy 服务如何解决?
OBD 升级后会初始化 OBProxy 的密码,若您设置了 `obproxy_sys_password`,则需执行如下命令连接到 proxysys:
```bash
obclient -h<obproxy_ip> -uroot@proxysys -P<obproxy_post> -p<obproxy_pwd>
```
> **说明**
>
> 若您连接 proxysys 时,使用自己设置的 proxysys 命令无法连接,请尝试使用空密码或者 `proxysys` 进行连接。
之后使用命令 `alter proxyconfig set obproxy_sys_password = ''` 将 proxysys 的密码设置为空,或者使其与配置文件中 `obproxy_sys_password` 的密码保持一致。
# 如何升级 obproxy 到 obproxy-ce 3.2.3
由于开源 OBProxy 组件正式更名为 obproxy-ce,因此使用以下命令升级会报 `No such package obproxy-3.2.3` 错误。
```shell
obd cluster upgrade <deploy name> -c obproxy -V 3.2.3
```
您需在 OBD 的执行用户下执行下述 **脚本** 修改 meta 信息,而后使用以下命令对 OBProxy 进行升级。
```shell
obd cluster upgrade <deploy name> -c obproxy-ce -V 3.2.3
```
## 脚本
```bash
OBD_HOME=${OBD_HOME:-${HOME}}/.obd
obproxy_repository=${OBD_HOME}/repository/obproxy
obproxy_ce_repository=${OBD_HOME}/repository/obproxy-ce
function shadow_repo() {
repository_path=$1
ce_repository_path=$2
[[ $repository_path =~ ^/ ]] && a=$repository_path || a=`pwd`/$repository_path
while [ -h $a ]
do
b=`ls -ld $a|awk '{print $NF}'`
c=`ls -ld $a|awk '{print $(NF-2)}'`
[[ $real_patn =~ ^/ ]] && a=$b || a=`dirname $c`/$b
done
instance_hash=`basename $a`
ce_version_path=`dirname ${ce_repository_path}`
ln -sf ${ce_version_path}/${instance_hash} ${ce_repository_path}
}
function copy_repository() {
VS=(`ls $obproxy_repository`)
for version in ${VS[@]}; do
version_path="${obproxy_repository}/${version}"
ce_version_path="${obproxy_ce_repository}/${version}"
repositories=(`ls $version_path`)
mkdir -p $ce_version_path
for repository in ${repositories[@]}; do
repository_path="${version_path}/${repository}"
ce_repository_path="${ce_version_path}/${repository}"
if [ -d "$ce_repository_path" ];
then
echo "${ce_repository_path} exist"
else
if [ -L ${repository_path} ];
then
shadow_repo ${repository_path} ${ce_repository_path}
else
cp -r ${repository_path} ${ce_repository_path}
fi
fi
done
done
}
function change_cluster_meta() {
cluster_home_path=${OBD_HOME}/cluster
CS=(`ls ${cluster_home_path}`)
for cluster in ${CS[@]}; do
cluster_path=${cluster_home_path}/$cluster
if [ -f ${cluster_path}/.data ]; then
sed -i 's/^ obproxy:/ obproxy-ce:/g' ${cluster_path}/.data
fi
sed -i 's/^obproxy:/obproxy-ce:/' ${cluster_path}/*.yaml
done
}
copy_repository && change_cluster_meta && echo 'ok'
```
......@@ -10,23 +10,25 @@
解决方法:请您检查配置并进行修改。
### OBD-1001:Configuration conflict x.x.x.x: xxx is used for x.x.x.x
### OBD-1001:x.x.x.x:xxx port is already used
错误原因:配置文件中存在路径冲突
错误原因:端口已经被占用
解决方法:请您检查配置并进行修改
解决方法:请您检查配置并更换端口
### OBD-1002:x.x.x.x:xxx port is already used
### OBD-1002:Fail to init x.x.x.x path
错误原因:端口已经被占用。
错误原因:
解决方法:请您检查配置并更换端口。
1. 配置文件中的 user 用户(未填的情况下默认为当前用户)没有对应目录的写权限。
2. home_path 不为空
### OBD-1003:Fail to init x.x.x.x path
您可根据报错的具体信息进行判断。
错误原因:配置文件中的 user 用户(未填的情况下默认为当前用户)没有对应目录的写权限。
解决方法:
解决方法:您可通过以下两种方式解决。
对于情况 1,您可通过以下两种方式解决。
- 运行命令添加或修改 user 信息。
......@@ -36,7 +38,13 @@
- 登陆到目标机器,为当前账号赋予对应目录的写权限。
### OBD-1004:fail to clean x.x.x.x:xxx
对于情况 2,您也可通过以下两种方式解决。
- 选择其他目录。
- 若您确认该目录可以被清空,也可使用 `-f` 选项,OBD 将会使用当前用户去清空该目录。
### OBD-1003:fail to clean x.x.x.x:xxx
错误原因:检查配置文件中的 user 用户(未填的情况下默认为当前用户)是否有 home_path 的写权限。
......@@ -50,12 +58,46 @@
- 登陆到目标机器,为当前账号赋予对应目录的写权限。
### OBD-1004:Configuration conflict x.x.x.x: xxx is used for x.x.x.x
错误原因:配置文件中存在路径冲突。
解决方法:请您检查配置并进行修改。
### OBD-1005:Some of the servers in the cluster have been stopped
错误原因:后续的操作需要所有的机器的服务全部在线,而当前配置内的部分机器已经停止。
解决方法:您可使用 `obd cluster start <deploy_name> --wop` 无参启动,将全部的服务拉起。
### OBD-1006:Failed to connect to xxx
错误原因:
1. OBD 和目标机器之间网络不连通。
2. 对应的组件进程已经退出或者不提供服务。
3. 账号密码不匹配。
解决办法:
对于情况 1,请自行修复网络。
对于情况 2,可尝试再次启动组件,如果依旧启动失败,请参考启动失败的错误进行排查,如 **OBD-2002**
对于情况 3,常见原因是用户直接执行 SQL 命令修改了密码,账号密码与配置文件中存储的不同导致 OBD 连接不到组件。该种情况下有以下两种解决办法。
1. 执行 SQL 命令将密码改回与 OBD 储存的密码一致。
2. 执行 `vi ~/.obd/cluster/<deploy name>/config.yaml` 修改对应的密码使其与组件中实际密码一致。
### OBD-1007:(x.x.x.x) xxx must not be less than xxx (Current value: xxx)
错误原因:ulimits 配置不满足要求。
解决办法:可通过修改 /etc/security/limits.d/ 目录下对应文件和 /etc/security/limits.conf 使其满足要求。
## OceanBase 部署相关报错
### OBD-2000:x.x.x.x not enough memory
......@@ -96,7 +138,7 @@ select count(*) num from oceanbase.__all_server where status = 'active' and star
- 若排查后发现该报错为上述两条原因造成,根据对应原因进行调整即可;
- 若排查后发现不是由上述两条原因引起的报错,您可到官网 [问答区](https://open.oceanbase.com/answer) 进行提问,会有专业人员为您解答。
- 若排查后发现不是由上述两条原因引起的报错,您可到官网 [问答区](https://ask.oceanbase.com/) 进行提问,会有专业人员为您解答。
### OBD-2003:not enough disk space for clog. Use redo_dir to set other disk for clog, or reduce the value of datafile_size
......@@ -118,7 +160,7 @@ select count(*) num from oceanbase.__all_server where status = 'active' and star
解决方法:您可将需修改的配置改放到 global 下。
## mysqltest 相关报错
## 测试相关报错
### OBD-3000:parse cmd failed
......@@ -132,6 +174,36 @@ select count(*) num from oceanbase.__all_server where status = 'active' and star
解决方法:请您检查 `--init-sql-dir` 目录下是否包含 `--init-sql-files` 声明的文件。
### OBD-3002:Failed to load data
错误原因:出现该报错的原因有很多,常见的原因有以下两种。
1. 租户资源不足或者压力过大。
2. 数据构建脚本报错。
解决方法:
对于情况 1,可使用资源规格更大的租户,或者调整 warehouses、load-workers 等参数值以减少构建压力。
对于情况 2,由于数据构建脚本是由 TPC 官网提供,可以先尝试重新执行脚本,如果问题仍然存在请到官网 [问答区](https://ask.oceanbase.com/) 提问,会有专业人员为您解答。
### OBD-3003:Failed to run TPC-C benchmark
错误原因:
1. 测试进程卡死后因为超时被杀死。
2. TPC-C 测试命令返回报错。
解决方法:
- 直接重新测试,或通过调整 terminals 等参数减少测试压力后重新测试。
- 如果没有使用官网提供的 obtpcc 包,请使用 obtpcc 进行测试。
如果上述方法均无法解决问题,请到官网 [问答区](https://ask.oceanbase.com/) 提问,会有专业人员为您解答。
## obagent 相关报错
### OBD-4000:Fail to reload x.x.x.x
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -39,8 +39,8 @@ oceanbase-ce:
# system_memory: 22G
# The size of a data file. When ignored, autodeploy calculates this value based on the current server available resource.
# datafile_size: 200G
# The percentage of the data_dir space to the total disk space. This value takes effect only when datafile_size is 0. The default value is 90.
# datafile_disk_percentage: 90
# The size of disk space used by the clog files. When ignored, autodeploy calculates this value based on the current server available resource.
# log_disk_size: 66G
# System log level. The default value is INFO.
# syslog_level: INFO
# Print system logs whose levels are higher than WARNING to a separate log file. The default value is true. The default value for autodeploy mode is false.
......
......@@ -39,8 +39,8 @@ oceanbase-ce:
# system_memory: 22G
# The size of a data file. When ignored, autodeploy calculates this value based on the current server available resource.
# datafile_size: 200G
# The percentage of the data_dir space to the total disk space. This value takes effect only when datafile_size is 0. The default value is 90.
# datafile_disk_percentage: 90
# The size of disk space used by the clog files. When ignored, autodeploy calculates this value based on the current server available resource.
# log_disk_size: 66G
# System log level. The default value is INFO.
# syslog_level: INFO
# Print system logs whose levels are higher than WARNING to a separate log file. The default value is true. The default value for autodeploy mode is false.
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册