improve error messages from chia db upgrade, specifically to help users if the disk is full (#10494)

This commit is contained in:
Arvid Norberg 2022-03-17 17:11:08 +01:00 committed by GitHub
parent 53da6ab41b
commit 144c4d1478
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 81 additions and 23 deletions

View File

@ -19,8 +19,14 @@ def db_cmd() -> None:
help="don't update config file to point to new database. When specifying a "
"custom output file, the config will not be updated regardless",
)
@click.option(
"--force",
default=False,
is_flag=True,
help="force conversion despite warnings",
)
@click.pass_context
def db_upgrade_cmd(ctx: click.Context, no_update_config: bool, **kwargs) -> None:
def db_upgrade_cmd(ctx: click.Context, no_update_config: bool, force: bool, **kwargs) -> None:
try:
in_db_path = kwargs.get("input")
@ -29,7 +35,8 @@ def db_upgrade_cmd(ctx: click.Context, no_update_config: bool, **kwargs) -> None
Path(ctx.obj["root_path"]),
None if in_db_path is None else Path(in_db_path),
None if out_db_path is None else Path(out_db_path),
no_update_config,
no_update_config=no_update_config,
force=force,
)
except RuntimeError as e:
print(f"FAILED: {e}")

View File

@ -1,7 +1,11 @@
from typing import Dict, Optional
import platform
from pathlib import Path
import shutil
import sys
from time import time
import textwrap
import os
from chia.util.config import load_config, save_config, get_config_lock
from chia.util.path import mkdir, path_from_root
@ -17,8 +21,10 @@ def db_upgrade_func(
root_path: Path,
in_db_path: Optional[Path] = None,
out_db_path: Optional[Path] = None,
*,
no_update_config: bool = False,
):
force: bool = False,
) -> None:
update_config: bool = in_db_path is None and out_db_path is None and not no_update_config
@ -40,16 +46,67 @@ def db_upgrade_func(
out_db_path = path_from_root(root_path, db_path_replaced)
mkdir(out_db_path.parent)
convert_v1_to_v2(in_db_path, out_db_path)
total, used, free = shutil.disk_usage(out_db_path.parent)
in_db_size = in_db_path.stat().st_size
if free < in_db_size:
no_free: bool = free < in_db_size * 0.6
strength: str
if no_free:
strength = "probably not enough"
else:
strength = "very little"
print(f"there is {strength} free space on the volume where the output database will be written:")
print(f" {out_db_path}")
print(
f"free space: {free / 1024 / 1024 / 1024:0.2f} GiB expected about "
f"{in_db_size / 1024 / 1024 / 1024:0.2f} GiB"
)
if no_free and not force:
print("to override this check and convert anyway, pass --force")
return
if update_config:
print("updating config.yaml")
with get_config_lock(root_path, "config.yaml"):
config = load_config(root_path, "config.yaml", acquire_lock=False)
new_db_path = db_pattern.replace("_v1_", "_v2_")
config["full_node"]["database_path"] = new_db_path
print(f"database_path: {new_db_path}")
save_config(root_path, "config.yaml", config)
try:
convert_v1_to_v2(in_db_path, out_db_path)
if update_config:
print("updating config.yaml")
with get_config_lock(root_path, "config.yaml"):
config = load_config(root_path, "config.yaml", acquire_lock=False)
new_db_path = db_pattern.replace("_v1_", "_v2_")
config["full_node"]["database_path"] = new_db_path
print(f"database_path: {new_db_path}")
save_config(root_path, "config.yaml", config)
except RuntimeError as e:
print(f"conversion failed with error: {e}.")
except Exception as e:
print(
textwrap.dedent(
f"""\
conversion failed with error: {e}.
The target v2 database is left in place (possibly in an incomplete state)
{out_db_path}
If the failure was caused by a full disk, ensure the volumes of your
temporary- and target directory have sufficient free space."""
)
)
if platform.system() == "Windows":
temp_dir = None
# this is where GetTempPath() looks
# https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppatha
if "TMP" in os.environ:
temp_dir = os.environ["TMP"]
elif "TEMP" in os.environ:
temp_dir = os.environ["TEMP"]
elif "USERPROFILE" in os.environ:
temp_dir = os.environ["USERPROFILE"]
if temp_dir is not None:
print(f"your temporary directory may be {temp_dir}")
temp_env = "TMP"
else:
temp_env = "SQLITE_TMPDIR"
print(f'you can specify the "{temp_env}" environment variable to control the temporary directory to be used')
print(f"\n\nLEAVING PREVIOUS DB FILE UNTOUCHED {in_db_path}\n")
@ -67,16 +124,13 @@ def convert_v1_to_v2(in_path: Path, out_path: Path) -> None:
from contextlib import closing
if not in_path.exists():
print(f"input file doesn't exist. {in_path}")
raise RuntimeError(f"can't find {in_path}")
raise RuntimeError(f"input file doesn't exist. {in_path}")
if in_path == out_path:
print(f"output file is the same as the input {in_path}")
raise RuntimeError("invalid conversion files")
raise RuntimeError(f"output file is the same as the input {in_path}")
if out_path.exists():
print(f"output file already exists. {out_path}")
raise RuntimeError("already exists")
raise RuntimeError(f"output file already exists. {out_path}")
print(f"opening file for reading: {in_path}")
with closing(sqlite3.connect(in_path)) as in_db:
@ -84,8 +138,7 @@ def convert_v1_to_v2(in_path: Path, out_path: Path) -> None:
with closing(in_db.execute("SELECT * from database_version")) as cursor:
row = cursor.fetchone()
if row is not None and row[0] != 1:
print(f"blockchain database already version {row[0]}\nDone")
raise RuntimeError("already v2")
raise RuntimeError(f"blockchain database already version {row[0]}. Won't convert")
except sqlite3.OperationalError:
pass
@ -120,8 +173,7 @@ def convert_v1_to_v2(in_path: Path, out_path: Path) -> None:
with closing(in_db.execute("SELECT header_hash, height from block_records WHERE is_peak = 1")) as cursor:
peak_row = cursor.fetchone()
if peak_row is None:
print("v1 database does not have a peak block, there is no blockchain to convert")
raise RuntimeError("no blockchain")
raise RuntimeError("v1 database does not have a peak block, there is no blockchain to convert")
peak_hash = bytes32(bytes.fromhex(peak_row[0]))
peak_height = uint32(peak_row[1])
print(f"peak: {peak_hash.hex()} height: {peak_height}")
@ -161,7 +213,6 @@ def convert_v1_to_v2(in_path: Path, out_path: Path) -> None:
while True:
row_2 = cursor_2.fetchone()
if row_2 is None:
print(f"ERROR: could not find block {hh.hex()}")
raise RuntimeError(f"block {hh.hex()} not found")
if bytes.fromhex(row_2[0]) == hh:
break