Skip to content

Commit

Permalink
Add Functionality to use BackupData using Kopia Server (#1857)
Browse files Browse the repository at this point in the history
* Add Functionality to use BackupData using Kopia Server

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

* organise imports

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

* Fix Linting Issues

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

* Update BackupDataUsingKopiaServer function

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

* Remove unused functions

* Added Suggestions

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

* Added Suggestions

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

* Remove TODO

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

* Add Documentation for BackupDataUsingKopiaServer

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

* Add Permalink

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

* clear namespace confusion

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

* Formatted getRepositoryServerAddress

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

* Update docs/functions.rst

Co-authored-by: Prasad Ghangal <prasad.ghangal@gmail.com>

* Improvements

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

* Add Parallelism for Snapshot Create

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

* Add Parallelism Support in Kanister via Helm

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

* Parallelism Refactor

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

* Parallelism Refactor

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

* Update docs/functions.rst

Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>

* Sort Imports in kopia/utils.go

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

* Update docs/functions.rst

Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>

* Remove userCredentials and certData from Blueprint

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

* Update Documentation

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

* Update Documentation

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

* Remove repositoryServerAddress

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

* Addressed Comments

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

* Update docs/functions.rst

Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>

* Change Default Parallelism value to 8

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

* Update pkg/function/backup_data_using_kopia_server.go

Co-authored-by: Prasad Ghangal <prasad.ghangal@gmail.com>

* Update pkg/function/backup_data_using_kopia_server.go

Co-authored-by: Prasad Ghangal <prasad.ghangal@gmail.com>

* Update pkg/function/backup_data_using_kopia_server.go

Co-authored-by: Prasad Ghangal <prasad.ghangal@gmail.com>

* Add License

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

* Add Comment

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

* Add Reference Link to Actionsets

* Address comments

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

* Fix Lint Problem

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

---------

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>
Co-authored-by: Prasad Ghangal <prasad.ghangal@gmail.com>
Co-authored-by: Vivek Singh <vsingh.ggits.2010@gmail.com>
  • Loading branch information
3 people committed May 3, 2023
1 parent 07038f0 commit 496f88a
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ As a reference, below is an example of a BlueprintAction.
- |
echo "Example Action"
.. _actionsets:

ActionSets
----------

Expand Down
59 changes: 59 additions & 0 deletions docs/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,65 @@ Example:
args:
name: "test-snapshot-content-content-dfc8fa67-8b11-4fdf-bf94-928589c2eed8"
.. _backupdatausingkopiaserver:

BackupDataUsingKopiaServer
--------------------------

This function backs up data from a container into any object store
supported by Kanister using Kopia Repository Server as data mover.

.. note::
It is important that the application includes a ``kanister-tools``
sidecar container. This sidecar is necessary to run the
tools that back up the volume and store it on the object store.

Additionally, in order to use this function, a RepositoryServer CR is
needed while creating the :ref:`actionsets`

Arguments:

.. csv-table::
:header: "Argument", "Required", "Type", "Description"
:align: left
:widths: 5,5,5,15

`namespace`, Yes, `string`, namespace of the container that you want to backup the data of
`pod`, Yes, `string`, pod name of the container that you want to backup the data of
`container`, Yes, `string`, name of the kanister sidecar container
`includePath`, Yes, `string`, path of the data to be backed up
`snapshotTags`, No, `string`, custom tags to be provided to the kopia snapshots

Outputs:

.. csv-table::
:header: "Output", "Type", "Description"
:align: left
:widths: 5,5,15

`backupID`,`string`, unique snapshot id generated during backup
`size`,`string`, size of the backup
`phySize`,`string`, physical size of the backup

Example:

.. code-block:: yaml
:linenos:
actions:
backup:
outputArtifacts:
backupIdentifier:
keyValue:
id: "{{ .Phases.backupToS3.Output.backupID }}"
phases:
- func: BackupDataUsingKopiaServer
name: backupToS3
args:
namespace: "{{ .Deployment.Namespace }}"
pod: "{{ index .Deployment.Pods 0 }}"
container: kanister-tools
includePath: /mnt/data
Registering Functions
---------------------
Expand Down
2 changes: 2 additions & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ Walkthrough
walkthrough
Workflow
webhook
Kopia
kopia
2 changes: 2 additions & 0 deletions helm/kanister-operator/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ spec:
value: {{ .Values.controller.updateCRDs | quote }}
- name: LOG_LEVEL
value: {{ .Values.controller.logLevel }}
- name: DATA_STORE_PARALLEL_UPLOAD
value: {{ .Values.controller.parallelism | quote }}
{{- if .Values.resources }}
resources:
{{ toYaml .Values.resources | indent 12 }}
Expand Down
1 change: 1 addition & 0 deletions helm/kanister-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ controller:
# false : CRDs would be created by helm
# true : CRDs would be created by kanister controller
updateCRDs: true
parallelism: 8
bpValidatingWebhook:
enabled: true
tls:
Expand Down
256 changes: 256 additions & 0 deletions pkg/function/backup_data_using_kopia_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// 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 function

import (
"context"
"encoding/base64"
"encoding/json"
"strings"

"github.com/dustin/go-humanize"
"github.com/pkg/errors"
"k8s.io/client-go/kubernetes"

kanister "github.com/kanisterio/kanister/pkg"
"github.com/kanisterio/kanister/pkg/format"
kankopia "github.com/kanisterio/kanister/pkg/kopia"
kopiacmd "github.com/kanisterio/kanister/pkg/kopia/command"
kerrors "github.com/kanisterio/kanister/pkg/kopia/errors"
"github.com/kanisterio/kanister/pkg/kube"
"github.com/kanisterio/kanister/pkg/param"
"github.com/kanisterio/kanister/pkg/utils"
)

const (
BackupDataUsingKopiaServerFuncName = "BackupDataUsingKopiaServer"
// BackupDataUsingKopiaServerSnapshotTagsArg is the key used for returning snapshot tags
BackupDataUsingKopiaServerSnapshotTagsArg = "snapshotTags"
)

type backupDataUsingKopiaServerFunc struct{}

func init() {
err := kanister.Register(&backupDataUsingKopiaServerFunc{})
if err != nil {
return
}
}

var _ kanister.Func = (*backupDataUsingKopiaServerFunc)(nil)

func (*backupDataUsingKopiaServerFunc) Name() string {
return BackupDataUsingKopiaServerFuncName
}

func (*backupDataUsingKopiaServerFunc) RequiredArgs() []string {
return []string{
BackupDataContainerArg,
BackupDataIncludePathArg,
BackupDataNamespaceArg,
BackupDataPodArg,
}
}

func (*backupDataUsingKopiaServerFunc) Arguments() []string {
return []string{
BackupDataContainerArg,
BackupDataIncludePathArg,
BackupDataNamespaceArg,
BackupDataPodArg,
BackupDataUsingKopiaServerSnapshotTagsArg,
}
}

func (*backupDataUsingKopiaServerFunc) Exec(ctx context.Context, tp param.TemplateParams, args map[string]any) (map[string]any, error) {
var (
container string
err error
includePath string
namespace string
pod string
tagsStr string
)
if err = Arg(args, BackupDataContainerArg, &container); err != nil {
return nil, err
}
if err = Arg(args, BackupDataIncludePathArg, &includePath); err != nil {
return nil, err
}
if err = Arg(args, BackupDataNamespaceArg, &namespace); err != nil {
return nil, err
}
if err = Arg(args, BackupDataPodArg, &pod); err != nil {
return nil, err
}
if err = OptArg(args, BackupDataUsingKopiaServerSnapshotTagsArg, &tagsStr, ""); err != nil {
return nil, err
}

var tags []string = nil
if tagsStr != "" {
tags = strings.Split(tagsStr, ",")
}

userPassphrase, cert, err := userCredentialsAndServerTLS(&tp)
if err != nil {
return nil, errors.Wrap(err, "Failed to fetch User Credentials/Certificate Data from Template Params")
}

fingerprint, err := kankopia.ExtractFingerprintFromCertificateJSON(cert)
if err != nil {
return nil, errors.Wrap(err, "Failed to fetch Kopia API Server Certificate Secret Data from Certificate")
}

username := tp.RepositoryServer.Username
hostname, userAccessPassphrase, err := hostNameAndUserPassPhraseFromRepoServer(userPassphrase)
if err != nil {
return nil, errors.Wrap(err, "Failed to fetch Hostname/User Passphrase from Secret")
}

cli, err := kube.NewClient()
if err != nil {
return nil, errors.Wrap(err, "Failed to create Kubernetes client")
}

snapInfo, err := backupDataUsingKopiaServer(
cli,
container,
hostname,
includePath,
namespace,
pod,
tp.RepositoryServer.Address,
fingerprint,
username,
userAccessPassphrase,
tags,
)
if err != nil {
return nil, errors.Wrap(err, "Failed to backup data using Kopia Repository Server")
}

var logSize, phySize int64
if snapInfo.Stats != nil {
stats := snapInfo.Stats
logSize = stats.SizeHashedB + stats.SizeCachedB
phySize = stats.SizeUploadedB
}

output := map[string]any{
BackupDataOutputBackupID: snapInfo.SnapshotID,
BackupDataOutputBackupSize: humanize.Bytes(uint64(logSize)),
BackupDataOutputBackupPhysicalSize: humanize.Bytes(uint64(phySize)),
}
return output, nil
}

func backupDataUsingKopiaServer(
cli kubernetes.Interface,
container,
hostname,
includePath,
namespace,
pod,
serverAddress,
fingerprint,
username,
userPassphrase string,
tags []string,
) (info *kopiacmd.SnapshotCreateInfo, err error) {
contentCacheMB, metadataCacheMB := kopiacmd.GetCacheSizeSettingsForSnapshot()
configFile, logDirectory := kankopia.CustomConfigFileAndLogDirectory(hostname)

cmd := kopiacmd.RepositoryConnectServerCommand(kopiacmd.RepositoryServerCommandArgs{
UserPassword: userPassphrase,
ConfigFilePath: configFile,
LogDirectory: logDirectory,
CacheDirectory: kopiacmd.DefaultCacheDirectory,
Hostname: hostname,
ServerURL: serverAddress,
Fingerprint: fingerprint,
Username: username,
ContentCacheMB: contentCacheMB,
MetadataCacheMB: metadataCacheMB,
})

stdout, stderr, err := kube.Exec(cli, namespace, pod, container, cmd, nil)
format.Log(pod, container, stdout)
format.Log(pod, container, stderr)
if err != nil {
return nil, errors.Wrap(err, "Failed to connect to Kopia Repository Server")
}

cmd = kopiacmd.SnapshotCreate(
kopiacmd.SnapshotCreateCommandArgs{
PathToBackup: includePath,
CommandArgs: &kopiacmd.CommandArgs{
RepoPassword: "",
ConfigFilePath: configFile,
LogDirectory: logDirectory,
},
Tags: tags,
ProgressUpdateInterval: 0,
Parallelism: utils.GetEnvAsIntOrDefault(kankopia.DataStoreParallelUploadName, kankopia.DefaultDataStoreParallelUpload),
})
if err != nil {
return nil, errors.Wrap(err, "Failed to construct snapshot create command")
}
stdout, stderr, err = kube.Exec(cli, namespace, pod, container, cmd, nil)
format.Log(pod, container, stdout)
format.Log(pod, container, stderr)

message := "Failed to create and upload backup"
if err != nil {
if strings.Contains(err.Error(), kerrors.ErrCodeOutOfMemoryStr) {
message = message + ": " + kerrors.ErrOutOfMemoryStr
}
return nil, errors.Wrap(err, message)
}
// Parse logs and return snapshot IDs and stats
return kopiacmd.ParseSnapshotCreateOutput(stdout, stderr)
}

func hostNameAndUserPassPhraseFromRepoServer(userCreds string) (string, string, error) {
var userAccessMap map[string]string
if err := json.Unmarshal([]byte(userCreds), &userAccessMap); err != nil {
return "", "", errors.Wrap(err, "Failed to unmarshal User Credentials Data")
}

var userPassPhrase string
var hostName string
for key, val := range userAccessMap {
hostName = key
userPassPhrase = val
}

decodedUserPassPhrase, err := base64.StdEncoding.DecodeString(userPassPhrase)
if err != nil {
return "", "", errors.Wrap(err, "Failed to Decode User Passphrase")
}
return hostName, string(decodedUserPassPhrase), nil
}

func userCredentialsAndServerTLS(tp *param.TemplateParams) (string, string, error) {
userCredJSON, err := json.Marshal(tp.RepositoryServer.Credentials.ServerUserAccess.Data)
if err != nil {
return "", "", errors.Wrap(err, "Error marshalling User Credentials Data")
}
certJSON, err := json.Marshal(tp.RepositoryServer.Credentials.ServerTLS.Data)
if err != nil {
return "", "", errors.Wrap(err, "Error marshalling Certificate Data")
}
return string(userCredJSON), string(certJSON), nil
}
15 changes: 15 additions & 0 deletions pkg/kopia/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,19 @@ const (

// DefaultClientCacheDirectory is the directory where kopia content cache is created
DefaultClientCacheDirectory = "/tmp/kopia-cache"

// DataStoreParallelUploadName is the Environmental Variable set in Kanister
// For Parallelism to be used by Kopia for backup action
DataStoreParallelUploadName = "DATA_STORE_PARALLEL_UPLOAD"
// DefaultDataStoreParallelUpload is the Default Value of Parallelism
DefaultDataStoreParallelUpload = 8
)

const (
KopiaAPIServerAddressArg = "serverAddress"
KopiaTLSCertSecretKey = "certs"
KopiaTLSCertSecretDataArg = "certData"
KopiaServerPassphraseArg = "serverPassphrase"
KopiaServerPassphraseSecretKey = "serverPassphraseKey"
KopiaUserPassphraseArg = "userPassphrase"
)
Loading

0 comments on commit 496f88a

Please sign in to comment.