Skip to content

Commit

Permalink
Add Unit Tests for RepositoryServer Datamover (#2294)
Browse files Browse the repository at this point in the history
* Add utilities for repository server datamover test

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add tests for repository server datamover

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Remove unused consts

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* revert #2166 (#2293)

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update error message

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* remove KOPIA_EXE env var check

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Check Server Status before performing location pull, push and delete

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Not create a new repository everytime tests run

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Update pkg/datamover/repository_server_test.go

Co-authored-by: kale-amruta <41624751+kale-amruta@users.noreply.github.com>

* Add comments for kopia repository setup

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Fix lint issue

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Rename Function Command -> ExecCommand

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Check If Repository Connection is failed in error message

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Check if Server is in Idle or Ready State

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Use Map instead of Switch Case

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Refactor GetEnv

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

---------

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>
Co-authored-by: kale-amruta <41624751+kale-amruta@users.noreply.github.com>
  • Loading branch information
r4rajat and kale-amruta committed Sep 5, 2023
1 parent f858198 commit 97fb058
Show file tree
Hide file tree
Showing 6 changed files with 362 additions and 6 deletions.
1 change: 1 addition & 0 deletions build/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ check_dependencies() {
export S3_COMPLIANT_AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export S3_COMPLIANT_AWS_REGION="us-west-2"
export S3_COMPLIANT_LOCATION_ENDPOINT="http://minio.minio.svc.cluster.local:9000"
export TEST_REPOSITORY_ENCRYPTION_KEY="testKopiaRepoPassword"
else
echo "Please install MinIO using 'make install-minio' and try again."
exit 1
Expand Down
3 changes: 2 additions & 1 deletion pkg/datamover/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ func (p *profile) Push(ctx context.Context, sourcePath, destinationPath string)
if err := p.connectToKopiaRepositoryServer(ctx); err != nil {
return err
}
return kopiaLocationPush(ctx, destinationPath, p.outputName, sourcePath, p.profile.Credential.KopiaServerSecret.Password)
_, err := kopiaLocationPush(ctx, destinationPath, p.outputName, sourcePath, p.profile.Credential.KopiaServerSecret.Password)
return err
}
source, err := sourceReader(sourcePath)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion pkg/datamover/repository_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ func (rs *repositoryServer) Push(ctx context.Context, sourcePath, destinationPat
if err != nil {
return err
}
return kopiaLocationPush(ctx, destinationPath, rs.outputName, sourcePath, password)
_, err = kopiaLocationPush(ctx, destinationPath, rs.outputName, sourcePath, password)
return err
}

func (rs *repositoryServer) Delete(ctx context.Context, destinationPath string) error {
Expand Down
251 changes: 251 additions & 0 deletions pkg/datamover/repository_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// Copyright 2023 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datamover

import (
"context"
"fmt"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"

. "gopkg.in/check.v1"
"k8s.io/apimachinery/pkg/util/rand"

kopiacmd "github.com/kanisterio/kanister/pkg/kopia/command"
"github.com/kanisterio/kanister/pkg/kopia/repository"
"github.com/kanisterio/kanister/pkg/testutil"
)

type RepositoryServerSuite struct {
ctx context.Context
address string
kopiaCacheDir string
kopiaLogDir string
kopiaConfigDir string
tlsDir string
serverHost string
serverUsername string
serverPassword string
repositoryUser string
testUsername string
testUserPassword string
repositoryPassword string
repoPathPrefix string
fingerprint string
}

const (
TestRepositoryEncryptionKey = "TEST_REPOSITORY_ENCRYPTION_KEY"
)

var _ = Suite(&RepositoryServerSuite{})

func (rss *RepositoryServerSuite) SetUpSuite(c *C) {
// Check if kopia binary exists in PATH
if !CommandExists("kopia") {
c.Skip("Skipping repository server datamover unit test. Couldn't find kopia binary in the path.")
}

// Setting Up Repository Server Address
rss.address = fmt.Sprintf("%s:%s", "https://0.0.0.0", strconv.Itoa(rand.IntnRange(50000, 60000)))

// Setting Up Repository Server User Access
rss.serverUsername = "user@localhost"
rss.serverPassword = "testPassword"
rss.serverHost = "localhost"
rss.testUsername = fmt.Sprintf("%s-%s", "testuser", rand.String(5))
rss.testUserPassword = rand.String(8)

// Setting Up Repository Access
rss.repositoryUser = "repositoryUser"
rss.repositoryPassword = testutil.GetEnvOrSkip(c, TestRepositoryEncryptionKey)
rss.repoPathPrefix = path.Join("kopia-int", "repository-server-datamover-test")

rss.ctx = context.Background()

// Setting Up Kopia Cache, Log and Config Dir
rss.kopiaCacheDir = kopiacmd.DefaultCacheDirectory
rss.kopiaLogDir = kopiacmd.DefaultLogDirectory
rss.kopiaConfigDir = kopiacmd.DefaultConfigDirectory

// Setting Up TLS Dir
temp := c.MkDir()
rss.tlsDir = filepath.Join(temp, "tls-"+rand.String(5))
}

func (rss *RepositoryServerSuite) setupKopiaRepositoryServer(c *C) {
// Setting Up Kopia Repository
contentCacheMB, metadataCacheMB := kopiacmd.GetGeneralCacheSizeSettings()
repoCommandArgs := kopiacmd.RepositoryCommandArgs{
CommandArgs: &kopiacmd.CommandArgs{
RepoPassword: rss.repositoryPassword,
ConfigFilePath: rss.kopiaConfigDir,
LogDirectory: rss.kopiaLogDir,
},
CacheDirectory: rss.kopiaCacheDir,
Hostname: rss.serverHost,
ContentCacheMB: contentCacheMB,
MetadataCacheMB: metadataCacheMB,
RepoPathPrefix: rss.repoPathPrefix,
Username: rss.repositoryUser,
Location: testutil.GetDefaultS3CompliantStorageLocation(),
}
// First try to connect with Kopia Repository
c.Log("Connecting with Kopia Repository...")
repoConnectCmd, err := kopiacmd.RepositoryConnectCommand(repoCommandArgs)
c.Assert(err, IsNil)
_, err = ExecCommand(c, repoConnectCmd...)
if err != nil && strings.Contains(err.Error(), "error connecting to repository") {
// If connection fails, create Kopia Repository
c.Log("Creating Kopia Repository...")
repoCreateCmd, err := kopiacmd.RepositoryCreateCommand(repoCommandArgs)
c.Assert(err, IsNil)
_, err = ExecCommand(c, repoCreateCmd...)
c.Assert(err, IsNil)
}

// Setting Up Kopia Repository Server
tlsCertFile := rss.tlsDir + ".cert"
tlsKeyFile := rss.tlsDir + ".key"
serverStartCommandArgs := kopiacmd.ServerStartCommandArgs{
CommandArgs: &kopiacmd.CommandArgs{
RepoPassword: "",
ConfigFilePath: rss.kopiaConfigDir,
LogDirectory: rss.kopiaLogDir,
},
ServerAddress: rss.address,
TLSCertFile: tlsCertFile,
TLSKeyFile: tlsKeyFile,
ServerUsername: rss.serverUsername,
ServerPassword: rss.serverPassword,
AutoGenerateCert: true,
Background: true,
}
serverStartCmd := kopiacmd.ServerStart(serverStartCommandArgs)
_, err = ExecCommand(c, serverStartCmd...)
c.Assert(err, IsNil)

// Adding Users to Kopia Repository Server
serverAddUserCommandArgs := kopiacmd.ServerAddUserCommandArgs{
CommandArgs: &kopiacmd.CommandArgs{
RepoPassword: rss.repositoryPassword,
ConfigFilePath: rss.kopiaConfigDir,
LogDirectory: rss.kopiaLogDir,
},
NewUsername: fmt.Sprintf("%s@%s", rss.testUsername, rss.serverHost),
UserPassword: rss.testUserPassword,
}
serverAddUserCmd := kopiacmd.ServerAddUser(serverAddUserCommandArgs)
_, err = ExecCommand(c, serverAddUserCmd...)
c.Assert(err, IsNil)

// Getting Fingerprint of Kopia Repository Server
rss.fingerprint = fingerprintFromTLSCert(c, tlsCertFile)
c.Assert(rss.fingerprint, Not(Equals), "")

// Refreshing Kopia Repository Server
serverRefreshCommandArgs := kopiacmd.ServerRefreshCommandArgs{
CommandArgs: &kopiacmd.CommandArgs{
RepoPassword: rss.repositoryPassword,
ConfigFilePath: rss.kopiaConfigDir,
LogDirectory: rss.kopiaLogDir,
},
ServerAddress: rss.address,
ServerUsername: rss.serverUsername,
ServerPassword: rss.serverPassword,
Fingerprint: rss.fingerprint,
}
serverRefreshCmd := kopiacmd.ServerRefresh(serverRefreshCommandArgs)
_, err = ExecCommand(c, serverRefreshCmd...)
c.Assert(err, IsNil)

// Check Server Status
serverStatusCommandArgs := kopiacmd.ServerStatusCommandArgs{
CommandArgs: &kopiacmd.CommandArgs{
RepoPassword: rss.repositoryPassword,
ConfigFilePath: rss.kopiaConfigDir,
LogDirectory: rss.kopiaLogDir,
},
ServerAddress: rss.address,
ServerUsername: rss.serverUsername,
ServerPassword: rss.serverPassword,
Fingerprint: rss.fingerprint,
}
serverStatusCmd := kopiacmd.ServerStatus(serverStatusCommandArgs)
out, err := ExecCommand(c, serverStatusCmd...)
if !strings.Contains(out, "IDLE") && out != "" {
c.Fail()
}
c.Assert(err, IsNil)
}

func (rss *RepositoryServerSuite) connectWithTestKopiaRepositoryServer(c *C) error {
// Connect With Kopia Repository Server
tlsCertFile := rss.tlsDir + ".cert"
tlsCertStr := readTLSCert(c, tlsCertFile)
c.Assert(tlsCertStr, Not(Equals), "")
contentCacheMB, metadataCacheMB := kopiacmd.GetGeneralCacheSizeSettings()
return repository.ConnectToAPIServer(
rss.ctx,
tlsCertStr,
rss.testUserPassword,
rss.serverHost,
rss.address,
rss.testUsername,
contentCacheMB,
metadataCacheMB,
)
}

func (rss *RepositoryServerSuite) TestLocationOperationsForRepositoryServerDataMover(c *C) {
// Setup Kopia Repository Server
rss.setupKopiaRepositoryServer(c)

// Setup Test Data
sourceDir := c.MkDir()
filePath := filepath.Join(sourceDir, "test.txt")

cmd := exec.Command("touch", filePath)
_, err := cmd.Output()
c.Assert(err, IsNil)

targetDir := c.MkDir()

// Connect with Kopia Repository Server
err = rss.connectWithTestKopiaRepositoryServer(c)
c.Assert(err, IsNil)

// Test Kopia Repository Server Location Push
snapInfo, err := kopiaLocationPush(rss.ctx, rss.repoPathPrefix, "kandoOutput", sourceDir, rss.testUserPassword)
c.Assert(err, IsNil)

// Test Kopia Repository Server Location Pull
err = kopiaLocationPull(rss.ctx, snapInfo.ID, rss.repoPathPrefix, targetDir, rss.testUserPassword)
c.Assert(err, IsNil)

// TODO : Verify Data is Pulled from the Location (Issue #2230)

// Test Kopia Repository Location Delete
err = kopiaLocationDelete(rss.ctx, snapInfo.ID, rss.repoPathPrefix, rss.testUserPassword)
c.Assert(err, IsNil)

// Verify Data is Deleted from the Location
// Expect an Error while Pulling Data
err = kopiaLocationPull(rss.ctx, snapInfo.ID, rss.repoPathPrefix, targetDir, rss.testUserPassword)
c.Assert(err, NotNil)
}
102 changes: 102 additions & 0 deletions pkg/datamover/testutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2023 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datamover

import (
"os/exec"
"strings"

"gopkg.in/check.v1"
)

func CommandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}

func fingerprintFromTLSCert(c *check.C, tlsCert string) string {
var args []string
args = append(args, "openssl")
args = append(args, "x509")
args = append(args, "-fingerprint")
args = append(args, "-noout")
args = append(args, "-sha256")
args = append(args, "-in")
args = append(args, tlsCert)
output, err := ExecCommand(c, args...)
c.Assert(err, check.IsNil)
output = strings.TrimPrefix(output, "sha256 Fingerprint=")
output = strings.ReplaceAll(output, ":", "")
output = strings.ReplaceAll(output, "\n", "")
return output
}

func readTLSCert(c *check.C, tlsCert string) string {
var args []string
args = append(args, "cat")
args = append(args, tlsCert)
output, err := ExecCommand(c, args...)
c.Assert(err, check.IsNil)
return output
}

func ExecCommand(c *check.C, args ...string) (string, error) {
c.Log(redactArgs(splitArgs(args)))
out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
c.Log(string(out))

return string(out), err
}

func redactArgs(args []string) []string {
const redacted = "<redacted>"
var redactNext bool
r := make([]string, 0, len(args))
redactMap := map[string]bool{
"--access-key": true,
"--secret-access-key": true,
"--password": true,
"--storage-account": true,
"--storage-key": true,
}
for _, a := range args {
if redactNext {
r = append(r, redacted)
redactNext = false
continue
}
if _, ok := redactMap[a]; ok {
redactNext = true
}
if strings.HasPrefix(a, "--access-key=") ||
strings.HasPrefix(a, "--secret-access-key=") ||
strings.HasPrefix(a, "--password=") ||
strings.HasPrefix(a, "--storage-account=") ||
strings.HasPrefix(a, "--storage-key=") {
p := strings.Split(a, "=")
a = p[0] + "=" + redacted
}
r = append(r, a)
}
return r
}

func splitArgs(args []string) []string {
var r []string
for _, a := range args {
r = append(r, strings.Fields(a)...)
}
return r
}
Loading

0 comments on commit 97fb058

Please sign in to comment.