starrocks-br 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/PKG-INFO +1 -1
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/README.md +190 -13
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/pyproject.toml +1 -1
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/cli.py +104 -8
- starrocks_br-0.2.0/src/starrocks_br/config.py +64 -0
- starrocks_br-0.2.0/src/starrocks_br/db.py +154 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/executor.py +62 -12
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/planner.py +32 -12
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/restore.py +64 -12
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/schema.py +1 -1
- starrocks_br-0.2.0/src/starrocks_br/timezone.py +125 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br.egg-info/PKG-INFO +1 -1
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br.egg-info/SOURCES.txt +3 -1
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_cli.py +173 -0
- starrocks_br-0.2.0/tests/test_db.py +257 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_executor.py +131 -30
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_planner.py +11 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_restore.py +146 -2
- starrocks_br-0.2.0/tests/test_timezone.py +395 -0
- starrocks_br-0.1.0/src/starrocks_br/config.py +0 -41
- starrocks_br-0.1.0/src/starrocks_br/db.py +0 -88
- starrocks_br-0.1.0/tests/test_db.py +0 -69
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/setup.cfg +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/__init__.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/concurrency.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/health.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/history.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/labels.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/logger.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br/repository.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br.egg-info/dependency_links.txt +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br.egg-info/entry_points.txt +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br.egg-info/requires.txt +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/src/starrocks_br.egg-info/top_level.txt +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_concurrency.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_config.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_health_checks.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_history.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_labels.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_logger.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_repository_sql.py +0 -0
- {starrocks_br-0.1.0 → starrocks_br-0.2.0}/tests/test_schema_setup.py +0 -0
|
@@ -4,13 +4,83 @@
|
|
|
4
4
|
|
|
5
5
|
The StarRocks Backup & Restore tool provides production-grade automation for backup and restore operations.
|
|
6
6
|
|
|
7
|
+
**Important:** This tool requires StarRocks 3.5 or later. Earlier versions are not supported due to differences in the `SHOW FRONTENDS` and `SHOW BACKENDS` command output formats, which are used for cluster health checks.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Quick Start](#quick-start)
|
|
13
|
+
- [Configuration](#configuration)
|
|
14
|
+
- [Password Management](#password-management)
|
|
15
|
+
- [Connecting with TLS/SSL](#connecting-with-tlsssl)
|
|
16
|
+
- [Commands](#commands)
|
|
17
|
+
- [Example Usage Scenarios](#example-usage-scenarios)
|
|
18
|
+
- [Error Handling](#error-handling)
|
|
19
|
+
- [Monitoring](#monitoring)
|
|
20
|
+
- [Testing](#testing)
|
|
21
|
+
- [Project Status](#project-status)
|
|
22
|
+
|
|
7
23
|
## Installation
|
|
8
24
|
|
|
9
|
-
### Option 1:
|
|
25
|
+
### Option 1: Install from PyPI (Recommended for Production)
|
|
26
|
+
|
|
27
|
+
We recommend using a virtual environment to ensure proper script availability and dependency isolation:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Create and activate a virtual environment
|
|
31
|
+
python3 -m venv .venv
|
|
32
|
+
source .venv/bin/activate # On Linux/Mac
|
|
33
|
+
# .venv\Scripts\activate # On Windows
|
|
34
|
+
|
|
35
|
+
# Install the package from PyPI
|
|
36
|
+
pip install starrocks-br==0.1.0
|
|
37
|
+
|
|
38
|
+
# Verify the installation
|
|
39
|
+
starrocks-br --help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Note:** Always activate the virtual environment before using the tool. The `starrocks-br` command will only be available when the virtual environment is activated.
|
|
43
|
+
|
|
44
|
+
### Option 2: Standalone Executable (Alternative if PyPI Installation Fails)
|
|
45
|
+
|
|
46
|
+
If you encounter problems when installing via PyPI (often due to `mysql-connector-python` native extension issues), you can build a standalone executable that bundles all dependencies.
|
|
47
|
+
|
|
48
|
+
**Note:** This requires cloning the repository first.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Clone the repository
|
|
52
|
+
git clone https://github.com/deep-bi/starrocks-br
|
|
53
|
+
cd starrocks-br
|
|
54
|
+
|
|
55
|
+
# Create and activate virtual environment
|
|
56
|
+
python3 -m venv .venv
|
|
57
|
+
source .venv/bin/activate # On Linux/Mac
|
|
58
|
+
# .venv\Scripts\activate # On Windows
|
|
59
|
+
|
|
60
|
+
# Install dependencies
|
|
61
|
+
pip install -e .
|
|
62
|
+
|
|
63
|
+
# Build the standalone executable
|
|
64
|
+
./build_executable.sh
|
|
65
|
+
|
|
66
|
+
# The executable will be created in: dist/starrocks-br
|
|
67
|
+
# You can now distribute or use this executable directly
|
|
68
|
+
./dist/starrocks-br --help
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Note:** The executable is platform-specific. Build it on the same OS/architecture where you'll use it, or build it on the target machine.
|
|
72
|
+
|
|
73
|
+
### Option 3: Using Devbox (Recommended for Development)
|
|
74
|
+
|
|
75
|
+
**Note:** This requires cloning the repository first.
|
|
10
76
|
|
|
11
77
|
Devbox provides a reproducible development environment with all required tools.
|
|
12
78
|
|
|
13
79
|
```bash
|
|
80
|
+
# Clone the repository
|
|
81
|
+
git clone https://github.com/deep-bi/starrocks-br
|
|
82
|
+
cd starrocks-br
|
|
83
|
+
|
|
14
84
|
# Install devbox (if not already installed)
|
|
15
85
|
curl -fsSL https://get.jetpack.io/devbox | bash
|
|
16
86
|
|
|
@@ -26,18 +96,67 @@ starrocks-br --help
|
|
|
26
96
|
pytest
|
|
27
97
|
```
|
|
28
98
|
|
|
29
|
-
### Option
|
|
99
|
+
### Option 4: Manual Development Setup
|
|
30
100
|
|
|
31
101
|
```bash
|
|
32
|
-
#
|
|
102
|
+
# Clone the repository
|
|
103
|
+
git clone https://github.com/deep-bi/starrocks-br
|
|
104
|
+
cd starrocks-br
|
|
105
|
+
|
|
106
|
+
# Create and activate virtual environment
|
|
107
|
+
python3 -m venv .venv
|
|
33
108
|
source .venv/bin/activate
|
|
34
109
|
|
|
35
|
-
#
|
|
110
|
+
# Install in editable mode with development dependencies
|
|
111
|
+
pip install -e ".[dev]"
|
|
112
|
+
|
|
113
|
+
# The CLI is now available as: starrocks-br
|
|
36
114
|
```
|
|
37
115
|
|
|
116
|
+
## Quick Start
|
|
117
|
+
|
|
118
|
+
After installing from PyPI, follow these steps:
|
|
119
|
+
|
|
120
|
+
1. **Activate your virtual environment** (if not already active):
|
|
121
|
+
```bash
|
|
122
|
+
source .venv/bin/activate # On Linux/Mac
|
|
123
|
+
# .venv\Scripts\activate # On Windows
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
2. **Verify installation:**
|
|
127
|
+
```bash
|
|
128
|
+
starrocks-br --help
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
3. **Create your `config.yaml` file** (see Configuration section below)
|
|
132
|
+
|
|
133
|
+
4. **Set your password as an environment variable:**
|
|
134
|
+
```bash
|
|
135
|
+
export STARROCKS_PASSWORD="your_password"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
On Windows (PowerShell):
|
|
139
|
+
```powershell
|
|
140
|
+
$env:STARROCKS_PASSWORD="your_password"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
On Windows (Command Prompt):
|
|
144
|
+
```cmd
|
|
145
|
+
set STARROCKS_PASSWORD=your_password
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
5. **Initialize the ops schema:**
|
|
149
|
+
```bash
|
|
150
|
+
starrocks-br init --config config.yaml
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
6. **Start using the tool** - see Commands section below for details
|
|
154
|
+
|
|
38
155
|
## Configuration
|
|
39
156
|
|
|
40
|
-
|
|
157
|
+
**Important:** After installing the package, you need to create your own `config.yaml` file. This file is **not included in the package** - each user creates it with their own StarRocks connection details. You can place it anywhere and reference it using the `--config` parameter.
|
|
158
|
+
|
|
159
|
+
Create a `config.yaml` file in your working directory (or any location you prefer) with your StarRocks connection details:
|
|
41
160
|
|
|
42
161
|
```yaml
|
|
43
162
|
host: "127.0.0.1"
|
|
@@ -55,6 +174,53 @@ The database password must be provided via the `STARROCKS_PASSWORD` environment
|
|
|
55
174
|
export STARROCKS_PASSWORD="your_password"
|
|
56
175
|
```
|
|
57
176
|
|
|
177
|
+
### Connecting with TLS/SSL
|
|
178
|
+
|
|
179
|
+
The tool can make secure connections to StarRocks using TLS. Add an optional `tls` section to your `config.yaml` when you need encryption.
|
|
180
|
+
|
|
181
|
+
#### Scenario 1: Server Authentication (Most Common)
|
|
182
|
+
|
|
183
|
+
Use this setup when the client only needs to verify the StarRocks server certificate.
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
host: "127.0.0.1"
|
|
187
|
+
port: 9030
|
|
188
|
+
user: "root"
|
|
189
|
+
database: "your_database"
|
|
190
|
+
repository: "your_repo_name"
|
|
191
|
+
|
|
192
|
+
tls:
|
|
193
|
+
enabled: true
|
|
194
|
+
ca_cert: "/path/to/ca.pem"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `enabled`: Turns TLS on or off.
|
|
198
|
+
- `ca_cert`: Certificate Authority file used to validate the server certificate.
|
|
199
|
+
- `verify_server_cert` (optional, default `true`): Disable only if you need to skip certificate validation.
|
|
200
|
+
|
|
201
|
+
#### Scenario 2: Mutual TLS (mTLS)
|
|
202
|
+
|
|
203
|
+
Use this when both the client and server must present certificates.
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
host: "127.0.0.1"
|
|
207
|
+
port: 9030
|
|
208
|
+
user: "root"
|
|
209
|
+
database: "your_database"
|
|
210
|
+
repository: "your_repo_name"
|
|
211
|
+
|
|
212
|
+
tls:
|
|
213
|
+
enabled: true
|
|
214
|
+
ca_cert: "/path/to/ca.pem"
|
|
215
|
+
client_cert: "/path/to/client-cert.pem"
|
|
216
|
+
client_key: "/path/to/client-key.pem"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- `client_cert`: Client certificate presented to the server.
|
|
220
|
+
- `client_key`: Private key paired with the client certificate.
|
|
221
|
+
|
|
222
|
+
Regardless of the scenario, the connection defaults to modern TLS versions (`TLSv1.2`, `TLSv1.3`). Provide a `tls_versions` list if you need different protocol settings.
|
|
223
|
+
|
|
58
224
|
**Note:** The repository must be created in StarRocks using the `CREATE REPOSITORY` command before running backups. For example:
|
|
59
225
|
|
|
60
226
|
```sql
|
|
@@ -113,7 +279,7 @@ starrocks-br backup full --config config.yaml --group <group_name>
|
|
|
113
279
|
**Parameters:**
|
|
114
280
|
- `--group`: The inventory group to back up.
|
|
115
281
|
|
|
116
|
-
**
|
|
282
|
+
**Internal flow:**
|
|
117
283
|
1. Load config → verify cluster health → ensure repository exists
|
|
118
284
|
2. Reserve job slot (prevent concurrent backups)
|
|
119
285
|
3. Query `ops.table_inventory` for all tables in the specified group.
|
|
@@ -133,7 +299,7 @@ starrocks-br backup incremental --config config.yaml --group <group_name>
|
|
|
133
299
|
- `--group`: The inventory group to back up.
|
|
134
300
|
- `--baseline-backup` (Optional): Specify a backup label to use as the baseline instead of the latest full backup.
|
|
135
301
|
|
|
136
|
-
**
|
|
302
|
+
**Internal flow:**
|
|
137
303
|
1. Load config → verify cluster health → ensure repository exists
|
|
138
304
|
2. Reserve job slot
|
|
139
305
|
3. Find the latest successful full backup for the group to use as a baseline.
|
|
@@ -159,7 +325,8 @@ starrocks-br restore \
|
|
|
159
325
|
**Parameters:**
|
|
160
326
|
- `--config`: Path to config YAML file (required)
|
|
161
327
|
- `--target-label`: Backup label to restore to (required)
|
|
162
|
-
- `--group`: Optional inventory group to filter tables to restore
|
|
328
|
+
- `--group`: Optional inventory group to filter tables to restore (cannot be used with `--table`)
|
|
329
|
+
- `--table`: Optional table name to restore (table name only, database comes from config). Cannot be used with `--group`
|
|
163
330
|
- `--rename-suffix`: Suffix for temporary tables during restore (default: `_restored`)
|
|
164
331
|
|
|
165
332
|
**How it works:**
|
|
@@ -167,14 +334,18 @@ starrocks-br restore \
|
|
|
167
334
|
- **For incremental backups**: Automatically restores the base full backup first, then applies the incremental
|
|
168
335
|
- **Safety mechanism**: Uses temporary tables with the specified suffix, then performs atomic rename to make restored data live
|
|
169
336
|
|
|
170
|
-
**
|
|
171
|
-
- **Disaster Recovery**: Restore all tables from a backup (omit `--group`
|
|
172
|
-
- **Surgical Restore**: Restore only specific table groups (use `--group` parameter)
|
|
337
|
+
**Three Restore Modes:**
|
|
338
|
+
- **Disaster Recovery**: Restore all tables from a backup (omit both `--group` and `--table` parameters)
|
|
339
|
+
- **Surgical Restore by Group**: Restore only specific table groups (use `--group` parameter)
|
|
340
|
+
- **Single Table Restore**: Restore a specific table (use `--table` parameter). The table name should not include the database prefix - the database comes from the config file.
|
|
341
|
+
|
|
342
|
+
**Table Name Format:**
|
|
343
|
+
When using `--table`, provide only the table name (e.g., `fact_sales`), not `database.table_name`. The database is taken from the `database` field in your config file. For multiple tables, set up an inventory group and use `--group` instead.
|
|
173
344
|
|
|
174
345
|
**Purpose of `--rename-suffix`:**
|
|
175
346
|
The restore process creates temporary tables with the specified suffix (e.g., `table_restored`) to avoid conflicts with existing tables. Once the restore is complete and verified, the tool performs atomic renames to swap the original tables with the restored data. This ensures data safety and allows for rollback if needed.
|
|
176
347
|
|
|
177
|
-
**
|
|
348
|
+
**Internal flow:**
|
|
178
349
|
1. Load config → verify cluster health → ensure repository exists
|
|
179
350
|
2. Find the correct restore sequence (full backup + optional incremental)
|
|
180
351
|
3. Get tables from backup manifest (optionally filtered by group)
|
|
@@ -224,6 +395,12 @@ starrocks-br restore \
|
|
|
224
395
|
starrocks-br restore \
|
|
225
396
|
--config config.yaml \
|
|
226
397
|
--target-label sales_db_20251014_full
|
|
398
|
+
|
|
399
|
+
# Restore a single table from a backup
|
|
400
|
+
starrocks-br restore \
|
|
401
|
+
--config config.yaml \
|
|
402
|
+
--target-label sales_db_20251015_inc \
|
|
403
|
+
--table fact_sales
|
|
227
404
|
```
|
|
228
405
|
|
|
229
406
|
## Error Handling
|
|
@@ -301,4 +478,4 @@ pytest tests/test_cli.py -v
|
|
|
301
478
|
- Exponential backoff retry for job conflicts
|
|
302
479
|
- Disk space precheck (requires external monitoring)
|
|
303
480
|
- Formal runbooks and DR drill procedures
|
|
304
|
-
- Monitoring dashboards and alerting integration
|
|
481
|
+
- Monitoring dashboards and alerting integration
|
|
@@ -15,6 +15,40 @@ from . import schema
|
|
|
15
15
|
from . import logger
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def _handle_snapshot_exists_error(error_details: dict, label: str, config: str, repository: str, backup_type: str, group: str, baseline_backup: str = None) -> None:
|
|
19
|
+
"""Handle snapshot_exists error by providing helpful guidance to the user.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
error_details: Error details dict containing error_type and snapshot_name
|
|
23
|
+
label: The backup label that was generated
|
|
24
|
+
config: Path to config file
|
|
25
|
+
repository: Repository name
|
|
26
|
+
backup_type: Type of backup ('incremental' or 'full')
|
|
27
|
+
group: Inventory group name
|
|
28
|
+
baseline_backup: Optional baseline backup label (for incremental backups)
|
|
29
|
+
"""
|
|
30
|
+
snapshot_name = error_details.get('snapshot_name', label)
|
|
31
|
+
logger.error(f"Snapshot '{snapshot_name}' already exists in the repository.")
|
|
32
|
+
logger.info("")
|
|
33
|
+
logger.info("This typically happens when:")
|
|
34
|
+
logger.info(" • The CLI lost connectivity during a previous backup operation")
|
|
35
|
+
logger.info(" • The backup completed on the server, but backup_history wasn't updated")
|
|
36
|
+
logger.info("")
|
|
37
|
+
logger.info("To resolve this, retry the backup with a custom label using --name:")
|
|
38
|
+
|
|
39
|
+
if backup_type == 'incremental':
|
|
40
|
+
retry_cmd = f" starrocks-br backup incremental --config {config} --group {group} --name {snapshot_name}_retry"
|
|
41
|
+
if baseline_backup:
|
|
42
|
+
retry_cmd += f" --baseline-backup {baseline_backup}"
|
|
43
|
+
logger.info(retry_cmd)
|
|
44
|
+
else:
|
|
45
|
+
logger.info(f" starrocks-br backup full --config {config} --group {group} --name {snapshot_name}_retry")
|
|
46
|
+
|
|
47
|
+
logger.info("")
|
|
48
|
+
logger.tip("You can verify the existing backup by checking the repository or running:")
|
|
49
|
+
logger.tip(f" SHOW SNAPSHOT ON {repository} WHERE Snapshot = '{snapshot_name}'")
|
|
50
|
+
|
|
51
|
+
|
|
18
52
|
@click.group()
|
|
19
53
|
def cli():
|
|
20
54
|
"""StarRocks Backup & Restore automation tool."""
|
|
@@ -43,7 +77,8 @@ def init(config):
|
|
|
43
77
|
port=cfg['port'],
|
|
44
78
|
user=cfg['user'],
|
|
45
79
|
password=os.getenv('STARROCKS_PASSWORD'),
|
|
46
|
-
database=cfg['database']
|
|
80
|
+
database=cfg['database'],
|
|
81
|
+
tls_config=cfg.get('tls'),
|
|
47
82
|
)
|
|
48
83
|
|
|
49
84
|
with database:
|
|
@@ -102,7 +137,8 @@ def backup_incremental(config, baseline_backup, group, name):
|
|
|
102
137
|
port=cfg['port'],
|
|
103
138
|
user=cfg['user'],
|
|
104
139
|
password=os.getenv('STARROCKS_PASSWORD'),
|
|
105
|
-
database=cfg['database']
|
|
140
|
+
database=cfg['database'],
|
|
141
|
+
tls_config=cfg.get('tls'),
|
|
106
142
|
)
|
|
107
143
|
|
|
108
144
|
with database:
|
|
@@ -174,6 +210,13 @@ def backup_incremental(config, baseline_backup, group, name):
|
|
|
174
210
|
logger.success(f"Backup completed successfully: {result['final_status']['state']}")
|
|
175
211
|
sys.exit(0)
|
|
176
212
|
else:
|
|
213
|
+
error_details = result.get('error_details')
|
|
214
|
+
if error_details and error_details.get('error_type') == 'snapshot_exists':
|
|
215
|
+
_handle_snapshot_exists_error(
|
|
216
|
+
error_details, label, config, cfg['repository'], 'incremental', group, baseline_backup
|
|
217
|
+
)
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
177
220
|
state = result.get('final_status', {}).get('state', 'UNKNOWN')
|
|
178
221
|
if state == "LOST":
|
|
179
222
|
logger.critical("Backup tracking lost!")
|
|
@@ -215,7 +258,8 @@ def backup_full(config, group, name):
|
|
|
215
258
|
port=cfg['port'],
|
|
216
259
|
user=cfg['user'],
|
|
217
260
|
password=os.getenv('STARROCKS_PASSWORD'),
|
|
218
|
-
database=cfg['database']
|
|
261
|
+
database=cfg['database'],
|
|
262
|
+
tls_config=cfg.get('tls'),
|
|
219
263
|
)
|
|
220
264
|
|
|
221
265
|
with database:
|
|
@@ -274,6 +318,13 @@ def backup_full(config, group, name):
|
|
|
274
318
|
logger.success(f"Backup completed successfully: {result['final_status']['state']}")
|
|
275
319
|
sys.exit(0)
|
|
276
320
|
else:
|
|
321
|
+
error_details = result.get('error_details')
|
|
322
|
+
if error_details and error_details.get('error_type') == 'snapshot_exists':
|
|
323
|
+
_handle_snapshot_exists_error(
|
|
324
|
+
error_details, label, config, cfg['repository'], 'full', group
|
|
325
|
+
)
|
|
326
|
+
sys.exit(1)
|
|
327
|
+
|
|
277
328
|
state = result.get('final_status', {}).get('state', 'UNKNOWN')
|
|
278
329
|
if state == "LOST":
|
|
279
330
|
logger.critical("Backup tracking lost!")
|
|
@@ -300,8 +351,10 @@ def backup_full(config, group, name):
|
|
|
300
351
|
@click.option('--config', required=True, help='Path to config YAML file')
|
|
301
352
|
@click.option('--target-label', required=True, help='Backup label to restore to')
|
|
302
353
|
@click.option('--group', help='Optional inventory group to filter tables to restore')
|
|
354
|
+
@click.option('--table', help='Optional table name to restore (table name only, database comes from config). Cannot be used with --group.')
|
|
303
355
|
@click.option('--rename-suffix', default='_restored', help='Suffix for temporary tables during restore (default: _restored)')
|
|
304
|
-
|
|
356
|
+
@click.option('--yes', is_flag=True, help='Skip confirmation prompt and proceed automatically')
|
|
357
|
+
def restore_command(config, target_label, group, table, rename_suffix, yes):
|
|
305
358
|
"""Restore data to a specific point in time using intelligent backup chain resolution.
|
|
306
359
|
|
|
307
360
|
This command automatically determines the correct sequence of backups needed for restore:
|
|
@@ -311,9 +364,23 @@ def restore_command(config, target_label, group, rename_suffix):
|
|
|
311
364
|
The restore process uses temporary tables with the specified suffix for safety, then performs
|
|
312
365
|
an atomic rename to make the restored data live.
|
|
313
366
|
|
|
314
|
-
Flow: load config → find restore pair → get tables from backup → execute restore flow
|
|
367
|
+
Flow: load config → check health → ensure repository → find restore pair → get tables from backup → execute restore flow
|
|
315
368
|
"""
|
|
316
369
|
try:
|
|
370
|
+
if group and table:
|
|
371
|
+
logger.error("Cannot specify both --group and --table. Use --table for single table restore or --group for inventory group restore.")
|
|
372
|
+
sys.exit(1)
|
|
373
|
+
|
|
374
|
+
if table:
|
|
375
|
+
table = table.strip()
|
|
376
|
+
if not table:
|
|
377
|
+
logger.error("Table name cannot be empty")
|
|
378
|
+
sys.exit(1)
|
|
379
|
+
|
|
380
|
+
if '.' in table:
|
|
381
|
+
logger.error("Table name must not include database prefix. Use 'table_name' not 'database.table_name'. Database comes from config file.")
|
|
382
|
+
sys.exit(1)
|
|
383
|
+
|
|
317
384
|
cfg = config_module.load_config(config)
|
|
318
385
|
config_module.validate_config(cfg)
|
|
319
386
|
|
|
@@ -322,7 +389,8 @@ def restore_command(config, target_label, group, rename_suffix):
|
|
|
322
389
|
port=cfg['port'],
|
|
323
390
|
user=cfg['user'],
|
|
324
391
|
password=os.getenv('STARROCKS_PASSWORD'),
|
|
325
|
-
database=cfg['database']
|
|
392
|
+
database=cfg['database'],
|
|
393
|
+
tls_config=cfg.get('tls'),
|
|
326
394
|
)
|
|
327
395
|
|
|
328
396
|
with database:
|
|
@@ -332,6 +400,17 @@ def restore_command(config, target_label, group, rename_suffix):
|
|
|
332
400
|
logger.warning("Remember to populate ops.table_inventory with your backup groups!")
|
|
333
401
|
sys.exit(1) # Exit if schema was just created, requires user action
|
|
334
402
|
|
|
403
|
+
healthy, message = health.check_cluster_health(database)
|
|
404
|
+
if not healthy:
|
|
405
|
+
logger.error(f"Cluster health check failed: {message}")
|
|
406
|
+
sys.exit(1)
|
|
407
|
+
|
|
408
|
+
logger.success(f"Cluster health: {message}")
|
|
409
|
+
|
|
410
|
+
repository.ensure_repository(database, cfg['repository'])
|
|
411
|
+
|
|
412
|
+
logger.success(f"Repository '{cfg['repository']}' verified")
|
|
413
|
+
|
|
335
414
|
logger.info(f"Finding restore sequence for target backup: {target_label}")
|
|
336
415
|
|
|
337
416
|
try:
|
|
@@ -342,11 +421,24 @@ def restore_command(config, target_label, group, rename_suffix):
|
|
|
342
421
|
sys.exit(1)
|
|
343
422
|
|
|
344
423
|
logger.info("Determining tables to restore from backup manifest...")
|
|
345
|
-
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
tables_to_restore = restore.get_tables_from_backup(
|
|
427
|
+
database,
|
|
428
|
+
target_label,
|
|
429
|
+
group=group,
|
|
430
|
+
table=table,
|
|
431
|
+
database=cfg['database'] if table else None
|
|
432
|
+
)
|
|
433
|
+
except ValueError as e:
|
|
434
|
+
logger.error(str(e))
|
|
435
|
+
sys.exit(1)
|
|
346
436
|
|
|
347
437
|
if not tables_to_restore:
|
|
348
438
|
if group:
|
|
349
439
|
logger.warning(f"No tables found in backup '{target_label}' for group '{group}'")
|
|
440
|
+
elif table:
|
|
441
|
+
logger.warning(f"No tables found in backup '{target_label}' for table '{table}'")
|
|
350
442
|
else:
|
|
351
443
|
logger.warning(f"No tables found in backup '{target_label}'")
|
|
352
444
|
sys.exit(1)
|
|
@@ -359,7 +451,8 @@ def restore_command(config, target_label, group, rename_suffix):
|
|
|
359
451
|
cfg['repository'],
|
|
360
452
|
restore_pair,
|
|
361
453
|
tables_to_restore,
|
|
362
|
-
rename_suffix
|
|
454
|
+
rename_suffix,
|
|
455
|
+
skip_confirmation=yes
|
|
363
456
|
)
|
|
364
457
|
|
|
365
458
|
if result['success']:
|
|
@@ -375,6 +468,9 @@ def restore_command(config, target_label, group, rename_suffix):
|
|
|
375
468
|
except ValueError as e:
|
|
376
469
|
logger.error(f"Configuration error: {e}")
|
|
377
470
|
sys.exit(1)
|
|
471
|
+
except RuntimeError as e:
|
|
472
|
+
logger.error(f"{e}")
|
|
473
|
+
sys.exit(1)
|
|
378
474
|
except Exception as e:
|
|
379
475
|
logger.error(f"Unexpected error: {e}")
|
|
380
476
|
sys.exit(1)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def load_config(config_path: str) -> Dict[str, Any]:
|
|
6
|
+
"""Load and parse YAML configuration file.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
config_path: Path to the YAML config file
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
Dictionary containing configuration
|
|
13
|
+
|
|
14
|
+
Raises:
|
|
15
|
+
FileNotFoundError: If config file doesn't exist
|
|
16
|
+
yaml.YAMLError: If config file is not valid YAML
|
|
17
|
+
"""
|
|
18
|
+
with open(config_path, 'r') as f:
|
|
19
|
+
config = yaml.safe_load(f)
|
|
20
|
+
|
|
21
|
+
if not isinstance(config, dict):
|
|
22
|
+
raise ValueError("Config must be a dictionary")
|
|
23
|
+
|
|
24
|
+
return config
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_config(config: Dict[str, Any]) -> None:
|
|
28
|
+
"""Validate that config contains required fields.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config: Configuration dictionary
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ValueError: If required fields are missing
|
|
35
|
+
"""
|
|
36
|
+
required_fields = ['host', 'port', 'user', 'database', 'repository']
|
|
37
|
+
|
|
38
|
+
for field in required_fields:
|
|
39
|
+
if field not in config:
|
|
40
|
+
raise ValueError(f"Missing required config field: {field}")
|
|
41
|
+
|
|
42
|
+
_validate_tls_section(config.get('tls'))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _validate_tls_section(tls_config) -> None:
|
|
46
|
+
if tls_config is None:
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if not isinstance(tls_config, dict):
|
|
50
|
+
raise ValueError("TLS configuration must be a dictionary")
|
|
51
|
+
|
|
52
|
+
enabled = bool(tls_config.get('enabled', False))
|
|
53
|
+
|
|
54
|
+
if enabled and not tls_config.get('ca_cert'):
|
|
55
|
+
raise ValueError("TLS configuration requires 'ca_cert' when 'enabled' is true")
|
|
56
|
+
|
|
57
|
+
if 'verify_server_cert' in tls_config and not isinstance(tls_config['verify_server_cert'], bool):
|
|
58
|
+
raise ValueError("TLS configuration field 'verify_server_cert' must be a boolean if provided")
|
|
59
|
+
|
|
60
|
+
if 'tls_versions' in tls_config:
|
|
61
|
+
tls_versions = tls_config['tls_versions']
|
|
62
|
+
if not isinstance(tls_versions, list) or not all(isinstance(version, str) for version in tls_versions):
|
|
63
|
+
raise ValueError("TLS configuration field 'tls_versions' must be a list of strings if provided")
|
|
64
|
+
|