core/lib/error.py (380 lines of code) (raw):
#!/usr/bin/env python3
"""
Copyright (c) 2017-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
"""
class OSCError(Exception):
ERR_MAPPING = {
"NON_ROOT_USER": {
"code": 100,
"desc": "Non-root user execution",
},
"OUTFILE_DIR_NOT_EXIST": {
"code": 101,
"desc": '--outfile-dir "{dir}" does not exist',
},
"NO_SUCH_MODE": {
"code": 102,
"desc": "{mode} is not a supported mode",
},
"OUTFILE_DIR_NOT_DIR": {
"code": 103,
"desc": '--outfile-dir "{dir}" is not a directory',
},
"DDL_FILE_LIST_NOT_SPECIFIED": {
"code": 104,
"desc": "no ddl_file_list specified",
},
"UNABLE_TO_GET_FREE_DISK_SPACE": {
"code": 105,
"desc": "Unable to read free disk size for path: {path}",
},
"FILE_ALREADY_EXIST": {
"code": 106,
"desc": (
"Outfile {file} already exists. Please cleanup or use "
"--force-cleanup if you are sure it's left behind by last "
"unclean OSC stop"
),
},
"UNABLE_TO_GET_PARTITION_SIZE": {
"code": 107,
"desc": "Unable to read partition size from path: {path}",
},
"DB_NOT_GIVEN": {
"code": 110,
"desc": ("At least one database name should be given for running " "OSC"),
},
"DB_NOT_EXIST": {
"code": 111,
"desc": ("Database: {db_list} do(es) not exist in MySQL"),
},
"INVALID_SYNTAX": {
"code": 112,
"desc": (
"Fail to parse: {filepath} {msg} "
"Most likely is not a valid CREATE TABLE sql. "
"Please make sure it has correct syntax and can be executed "
"in MySQL: {msg}"
),
},
"INVALID_REPL_STATUS": {
"code": 113,
"desc": (
"Invalid replication status: <{repl_status}>. "
"<master> and <slave> are the only supported ones"
),
},
"FAILED_TO_LOCK": {
"code": 115,
"desc": ("Failed to grab external lock"),
},
"TOO_MANY_OSC_RUNNING": {
"code": 116,
"desc": ("Too many osc is running. {limit} allowed, {running} " "running"),
},
"FAILED_TO_READ_DDL_FILE": {
"code": 117,
"desc": ("Failed to read DDL file: '{filepath}'"),
},
"ARGUMENT_ERROR": {
"code": 118,
"desc": ("Invalid value for argument {argu}: {errmsg}"),
},
"FAILED_TO_CONNECT_DB": {
"code": 119,
"desc": (
"Failed to connect to database using user: {user} " "through {socket}"
),
},
"REPL_ROLE_MISMATCH": {
"code": 120,
"desc": (
"Replication role fail to match what is given on CLI: " "{given_role}"
),
},
"FAILED_TO_FETCH_MYSQL_VARS": {
"code": 121,
"desc": ("Failed to fetch local mysql variables"),
},
"TABLE_ALREADY_EXIST": {
"code": 122,
"desc": (
"Table `{db}`.`{table}` already exists in MySQL. "
"Please cleanup before run osc again"
),
},
"TRIGGER_ALREADY_EXIST": {
"code": 123,
"desc": ("Following trigger(s) already exist on table: \n" "{triggers}"),
},
"MISSING_COLUMN": {
"code": 124,
"desc": (
"Column(s): {column} missing in new table schema "
"specify --allow-drop-columns if you really want to drop "
"the column"
),
},
"TABLE_NOT_EXIST": {
"code": 125,
"desc": ("Table: `{db}`.`{table}` does not exist in MySQL"),
},
"TABLE_PARSING_ERROR": {
"code": 126,
"desc": ("Fail to parse table: `{db}`.`{table}` {msg}"),
},
"NO_PK_EXIST": {
"code": 127,
"desc": ("Table: `{db}`.`{table}` does not have a primary key."),
},
"NOT_ENOUGH_SPACE": {
"code": 128,
"desc": (
"Not enough disk space to execute schema change. "
"Required: {need}, Available: {avail}"
),
},
"DDL_GUARD_ATTEMPTS": {
"code": 129,
"desc": (
"Max attempts exceeded, but the threads_running still "
"don't drop to an ideal number"
),
},
"UNLOCK_FAILED": {
"code": 130,
"desc": ("Failed to unlock external lock"),
},
"OSC_INTERNAL_ERROR": {
"code": 131,
"desc": ("Internal OSC Exception: {msg}"),
},
"REPLAY_TIMEOUT": {
"code": 132,
"desc": ("Timeout when replaying changes"),
},
"REPLAY_WRONG_AFFECTED": {
"code": 133,
"desc": (
"Unexpected affected number of rows when replaying events. "
"This usually happens when application was writing to table "
"without writing binlogs. For example `set session "
"sql_log_bin=0` was executed before DML statements. "
"Expected number: 1 row. Affected: {num}"
),
},
"CHECKSUM_MISMATCH": {
"code": 134,
"desc": (
"Checksum mismatch between origin table and intermediate "
"table. This means one of these: "
"1. you have some scripts running DDL against the origin "
"table while OSC is running. "
"2. some columns have changed their output format, "
"for example int -> decimal. "
"see also: --skip-checksum-for-modifed "
"3. it is a bug in OSC"
),
},
"OFFLINE_NOT_SUPPORTED": {
"code": 135,
"desc": (
"--offline-checksum only supported in slave mode, "
"however replication is not running at the moment"
),
},
"UNABLE_TO_GET_LOCK": {
"code": 136,
"desc": (
"Unable to get MySQL lock for OSC. Please check whether there "
"is another OSC job already running somewhere. Use `cleanup "
"--kill` subcommand to kill the running job if you are not "
"interested in it anymore"
),
},
"FAIL_TO_GUESS_CHUNK_SIZE": {
"code": 137,
"desc": ("Failed to decide optmial chunk size for dump"),
},
"NO_INDEX_COVERAGE": {
"code": 138,
"desc": (
"None of the indexes in new table schema can perfectly "
"cover current pk combination lookup: <{pk_names}>. "
"Use --skip-pk-coverage-check, if you are sure it will "
"not cause a problem"
),
},
"NEW_PK": {
"code": 139,
"desc": (
"You're adding new primary key to table. This will "
"cause a long running transaction open during the data "
"dump stage. Specify --allow-new-pk if you don't think "
"this will be a performance issue for you"
),
},
"MAX_ATTEMPT_EXCEEDED": {
"code": 140,
"desc": (
"Max attempt exceeded, but time spent in replay still "
"does not meet the requirement. We will not proceed. "
"There're probably too many write requests targeting the "
"table. If blocking the writes for more than {timeout} "
"seconds is not a problem for you, then specify "
"--bypass-replay-timeout"
),
},
"LONG_RUNNING_TRX": {
"code": 141,
"desc": (
"Long running transaction exist: \n"
"ID: {pid}\n"
"User: {user}\n"
"host: {host}\n"
"Time: {time}\n"
"Command: {command}\n"
"Info: {info}\n"
),
},
"UNKOWN_REPLAY_TYPE": {
"code": 142,
"desc": ("Unknown replay type: {type_value}"),
},
"FAILED_TO_LOCK_TABLE": {
"code": 143,
"desc": ("Failed to lock table: {tables}"),
},
"FOREIGN_KEY_FOUND": {
"code": 144,
"desc": (
"{db}.{table} is referencing or being referenced "
"in at least one foreign key: "
"{fk}"
),
},
"WRONG_ENGINE": {
"code": 145,
"desc": (
'Engine in the SQL file "{engine}" does not match "{expect}" '
"which is given on CLI"
),
},
"PRI_COL_DROPPED": {
"code": 146,
"desc": (
"<{pri_col}> which belongs to current primary key is "
"dropped in new schema. Dropping a column from current "
"primary key is dangerous, and can cause data loss. "
"Please separate into two OSC jobs if you really want to "
"perform this schema change. 1. move this column out of "
"current primary key. 2. drop this column after step1."
),
},
"INCORRECT_SESSION_OVERRIDE": {
"code": 147,
"desc": (
"Failed to parse the given session override "
"configuration. Failing part: {section}"
),
},
"NOT_RBR_SAFE": {
"code": 148,
"desc": (
"Running OSC with RBR is not safe for a non-FB MySQL version. "
'You will need to either have "sql_log_bin_triggers" '
"supported and enabled, or disable RBR before running OSC"
),
},
"IMPLICIT_CONVERSION_DETECTED": {
"code": 149,
"desc": (
"Implicity convesion happened after executing the CREATE "
"TABLE statement. It is a best practice to always store your "
"schema in a consistent way. Please make sure that the "
"statment provided in the file is copied from the output of "
"`SHOW CREATE TABLE`. Difference detected: \n {diff}"
),
},
"FAILED_TO_DECODE_DDL_FILE": {
"code": 150,
"desc": (
"Failed to decode DDL file '{filepath}' "
"with charset '{charset}'. Use --charset "
"to set the proper charset."
),
},
"REPLAY_TOO_MANY_DELTAS": {
"code": 151,
"desc": (
"Recorded too many changes to ever catchup "
"({deltas} > max replay changes {max_deltas})"
),
},
"UNSAFE_TS_BOOTSTRAP": {
"code": 152,
"desc": (
"Adding columns or changing columns to use CURRENT_TIMESTAMP as "
"default value is unsafe with OSC. Please consider a different "
"deployment method for this"
),
},
"CREATE_TRIGGER_ERROR": {
"code": 153,
"desc": ("Error when creating triggers, msg: {msg}"),
},
# reserved for special internal errors
"ASSERTION_ERROR": {
"code": 249,
"desc": ("Assertion error. \n" "Expected: {expected}\n" "Got : {got}"),
},
"CLEANUP_EXECUTION_ERROR": {
"code": 250,
"desc": ("Error when running clean up statement: {sql} msg: {msg}"),
},
"HOOK_EXECUTION_ERROR": {
"code": 251,
"desc": ("Error when executing hook: {hook} msg: {msg}"),
},
"SHELL_ERROR": {
"code": 252,
"desc": (
"Shell command exit with error when executing: {cmd} "
"STDERR: {stderr}"
),
},
"SHELL_TIMEOUT": {
"code": 253,
"desc": ("Timeout when executing shell command: {cmd}"),
},
"GENERIC_MYSQL_ERROR": {
"code": 254,
"desc": ('MySQL Error during stage "{stage}": [{errnum}] {errmsg}'),
},
"OUTFILE_DIR_NOT_SPECIFIED_WSENV": {
"code": 255,
"desc": ("--outfile-dir must be specified when using wsenv"),
},
"SKIP_DISK_SPACE_CHECK_VALUE_INCOMPATIBLE_WSENV": {
"code": 256,
"desc": ("-skip-disk-space-check must be true when using wsenv"),
},
}
def __init__(self, err_key, desc_kwargs=None, mysql_err_code=None):
self.err_key = err_key
if desc_kwargs:
self.desc_kwargs = desc_kwargs
else:
self.desc_kwargs = {}
self._mysql_err_code = mysql_err_code
self.err_entry = self.ERR_MAPPING[err_key]
@property
def code(self):
return self.ERR_MAPPING[self.err_key]["code"]
@property
def desc(self):
description = self.err_entry["desc"].format(**self.desc_kwargs)
return "{}: {}".format(self.err_key, description)
@property
def mysql_err_code(self):
if self._mysql_err_code:
return self._mysql_err_code
else:
return 0
def __str__(self):
return self.desc