Skip to content

Commit

Permalink
APPS-1500-restore-config-validation
Browse files Browse the repository at this point in the history
- added restore config validation for asbx
- fixed documentation
  • Loading branch information
filkeith committed Feb 3, 2025
1 parent 87e1c5b commit 9dcd080
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 8 deletions.
4 changes: 4 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ func (c *Client) Restore(

return handler, nil
case EncoderTypeASBX:
if err := config.isValidForASBX(); err != nil {
return nil, fmt.Errorf("failed to validate restore config: %w", err)
}

handler := newRestoreHandler[*models.ASBXToken](ctx, config, c.aerospikeClient, c.logger, streamingReader)
handler.run()

Expand Down
7 changes: 7 additions & 0 deletions cmd/asrestore/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ func NewCmd(appVersion, commitHash string) *cobra.Command {
helpFunc := func() {
fmt.Println("Welcome to the Aerospike restore CLI tool!")
fmt.Println("-----------------------------------------")

fmt.Println("Restore tool automatically detects file types in restoring directory.")
fmt.Println("And perform restore according to detected type: asb or asbx.")
fmt.Println("You can set restore mode manually with --mode flag.")
fmt.Println("Flags that are incompatible with restore mode,")
fmt.Println("are also incompatible in automatic mode (when mode is not set).")

fmt.Println("\nUsage:")
fmt.Println(" asrestore [flags]")

Expand Down
18 changes: 13 additions & 5 deletions cmd/asrestore/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Version artifacts are automatically built and uploaded under releases in GitHub.

## Supported flags
```
Restore tool automatically detects file types in restoring directory.
And perform restore according to detected type: asb or asbx.
You can set restore mode manually with --mode flag.
Flags that are incompatible with restore mode,
are also incompatible in automatic mode (when mode is not set).
Usage:
asrestore [flags]
Expand All @@ -28,9 +34,9 @@ Aerospike Client Flags:
--auth INTERNAL,EXTERNAL,PKI The authentication mode used by the Aerospike server. INTERNAL
uses standard user/pass. EXTERNAL uses external methods (like LDAP)
which are configured on the server. EXTERNAL requires TLS. PKI allows
TLS authentication and authorization based on a certificate. No user
name needs to be configured. (default INTERNAL)
--tls-enable Enable TLS authentication with Aerospike. If false, other tls
TLS authentication and authorization based on a certificate. No
username needs to be configured. (default INTERNAL)
--tls-enable Enable TLS authentication with Aerospike. If false, other TLS
options are ignored.
--tls-name string The server TLS context to use to authenticate the connection to
Aerospike.
Expand All @@ -39,7 +45,7 @@ Aerospike Client Flags:
--tls-certfile env-b64:<cert>,b64:<cert>,<cert-file-name> The certificate file for mutual TLS authentication with
Aerospike.
--tls-keyfile env-b64:<cert>,b64:<cert>,<cert-file-name> The key file used for mutual TLS authentication with Aerospike.
--tls-keyfile-password "env-b64:<env-var>,b64:<b64-pass>,file:<pass-file>,<clear-pass>" The password used to decrypt the key-file if encrypted.
--tls-keyfile-password "env-b64:<env-var>,b64:<b64-pass>,file:<pass-file>,<clear-pass>" The password used to decrypt the key file if encrypted.
--tls-protocols "[[+][-]all] [[+][-]TLSv1] [[+][-]TLSv1.1] [[+][-]TLSv1.2] [[+][-]TLSv1.3]" Set the TLS protocol selection criteria. This format is the same
as Apache's SSLProtocol documented at
https://httpd.apache.org/docs/current/mod/mod_ssl.html#ssl protocol. (default +TLSv1.2)
Expand All @@ -61,11 +67,13 @@ Aerospike Client Flags:
Restore Flags:
-d, --directory string The directory that holds the backup files. Required, unless -o or -e is used.
-n, --namespace string Used to restore to a different namespace. Example: source-ns,destination-ns
Restoring to different namespace is incompatible with --mode=asbx.
-s, --set string Only restore the given sets from the backup.
Default: restore all sets.
Incompatible with --mode=asbx.
-B, --bin-list string Only restore the given bins in the backup.
If empty, include all bins.
Incompatible with --mode=asbx.
-R, --no-records Don't restore any records.
Incompatible with --mode=asbx.
-I, --no-indexes Don't restore any secondary indexes.
Expand Down
15 changes: 15 additions & 0 deletions cmd/internal/app/asrestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ func initializeRestoreReader(ctx context.Context, params *ASRestoreParams, sa *b
return nil, nil, fmt.Errorf("failed to create asbx reader: %w", err)
}

// List all files first.
list, err := xdrReader.ListObjects(ctx, params.CommonParams.Directory)
if err != nil {
return nil, nil, fmt.Errorf("failed to list asbx files: %w", err)
}

// Sort files.
list, err = util.SortBackupFiles(list)
if err != nil {
return nil, nil, fmt.Errorf("failed to sort asbx files: %w", err)
}

// Load ASBX files for reading.
xdrReader.SetObjectsToStream(list)

return nil, xdrReader, nil
case models.RestoreModeAuto:
reader, err = newReader(ctx, params, sa, false)
Expand Down
9 changes: 6 additions & 3 deletions cmd/internal/flags/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,22 @@ const (
OperationRestore

descNamespaceBackup = "The namespace to be backed up. Required."
descNamespaceRestore = "Used to restore to a different namespace. Example: source-ns,destination-ns"
descNamespaceRestore = "Used to restore to a different namespace. Example: source-ns,destination-ns\n" +
"Restoring to different namespace is incompatible with --mode=asbx."

descSetListBackup = "The set(s) to be backed up. Accepts comma-separated values with no spaces: 'set1,set2,set3'\n" +
"If multiple sets are being backed up, filter-exp cannot be used.\n" +
"If empty, include all sets."
descSetListRestore = "Only restore the given sets from the backup.\n" +
"Default: restore all sets."
"Default: restore all sets.\n" +
"Incompatible with --mode=asbx."

descBinListBackup = "Only include the given bins in the backup.\n" +
"Accepts comma-separated values with no spaces: 'bin1,bin2,bin3'\n" +
"If empty include all bins."
descBinListRestore = "Only restore the given bins in the backup.\n" +
"If empty, include all bins.\n"
"If empty, include all bins.\n" +
"Incompatible with --mode=asbx."

descNoRecordsBackup = "Don't back up any records."
descNoRecordsRestore = "Don't restore any records.\nIncompatible with --mode=asbx."
Expand Down
37 changes: 37 additions & 0 deletions config_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,40 @@ func (c *ConfigRestore) validate() error {

return nil
}

// isValidForASBX check if config is valid for restoring from asbx.
func (c *ConfigRestore) isValidForASBX() error {
if *c.Namespace.Source != *c.Namespace.Destination {
return fmt.Errorf("changing namespace is not supported for ASBX")
}

if len(c.SetList) > 0 {
return fmt.Errorf("set list is not supported for ASBX")
}

if len(c.BinList) > 0 {
return fmt.Errorf("bin list is not supported for ASBX")
}

if c.NoRecords {
return fmt.Errorf("no records is not supported for ASBX")
}

if c.NoIndexes {
return fmt.Errorf("no indexes is not supported for ASBX")
}

if c.NoUDFs {
return fmt.Errorf("no udfs is not supported for ASBX")
}

if c.DisableBatchWrites {
return fmt.Errorf("disable batch writes is not supported for ASBX")
}

if c.ExtraTTL > 0 {
return fmt.Errorf("extra ttl value is not supported for ASBX")
}

return nil
}
124 changes: 124 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package backup

import (
"strings"
"testing"
"time"

Expand Down Expand Up @@ -141,3 +142,126 @@ func TestEncryptionPolicy_Validate(t *testing.T) {
policy.KeyEnv = &keyEnv
assert.ErrorContains(t, policy.validate(), "only one encryption key source may be specified")
}

func TestConfigRestore_IsValidForASBX(t *testing.T) {
source := "source-ns"
destination := "dest-ns"
sameNs := "same-ns"

tests := []struct {
name string
config *ConfigRestore
wantErr string
}{
{
name: "valid config with same namespace",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
},
wantErr: "",
},
{
name: "different source and destination namespaces",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &source,
Destination: &destination,
},
},
wantErr: "changing namespace is not supported for ASBX",
},
{
name: "set list not empty",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
SetList: []string{"set1"},
},
wantErr: "set list is not supported for ASBX",
},
{
name: "bin list not empty",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
BinList: []string{"bin1"},
},
wantErr: "bin list is not supported for ASBX",
},
{
name: "no records set to true",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
NoRecords: true,
},
wantErr: "no records is not supported for ASBX",
},
{
name: "no indexes set to true",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
NoIndexes: true,
},
wantErr: "no indexes is not supported for ASBX",
},
{
name: "no UDFs set to true",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
NoUDFs: true,
},
wantErr: "no udfs is not supported for ASBX",
},
{
name: "disable batch writes set to true",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
DisableBatchWrites: true,
},
wantErr: "disable batch writes is not supported for ASBX",
},
{
name: "extra TTL greater than zero",
config: &ConfigRestore{
Namespace: &RestoreNamespaceConfig{
Source: &sameNs,
Destination: &sameNs,
},
ExtraTTL: 1,
},
wantErr: "extra ttl value is not supported for ASBX",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.config.isValidForASBX()
if tt.wantErr != "" {
assert.Error(t, err)
assert.True(t, strings.Contains(err.Error(), tt.wantErr),
"expected error containing '%s', got '%s'", tt.wantErr, err.Error())
} else {
assert.NoError(t, err)
}
})
}
}

0 comments on commit 9dcd080

Please sign in to comment.