Skip to content

Commit

Permalink
Add device parameter for importing instance command
Browse files Browse the repository at this point in the history
Signed-off-by: Eric Fan <[email protected]>
  • Loading branch information
qnap-ericfan committed Feb 16, 2024
1 parent 6aca66d commit 97aa791
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 5 deletions.
3 changes: 3 additions & 0 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,9 @@ type InstanceBackupArgs struct {

// Name to import backup as
Name string

// If set, it would override devices
Devices map[string]map[string]string
}

// The InstanceCopyArgs struct is used to pass additional options during instance copy.
Expand Down
17 changes: 16 additions & 1 deletion client/lxd_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ func (r *ProtocolLXD) CreateInstanceFromBackup(args InstanceBackupArgs) (Operati
return nil, err
}

if args.PoolName == "" && args.Name == "" {
if args.PoolName == "" && args.Name == "" && len(args.Devices) == 0 {
// Send the request
op, _, err := r.queryOperation("POST", path, args.BackupFile, "", true)
if err != nil {
Expand Down Expand Up @@ -590,6 +590,21 @@ func (r *ProtocolLXD) CreateInstanceFromBackup(args InstanceBackupArgs) (Operati
req.Header.Set("X-LXD-name", args.Name)
}

if len(args.Devices) > 0 {
devProps := url.Values{}

for dev := range args.Devices {
props := url.Values{}
for k, v := range args.Devices[dev] {
props.Set(k, v)
}

devProps.Set(dev, props.Encode())
}

req.Header.Set("X-LXD-devices", devProps.Encode())
}

// Send the request
resp, err := r.DoHTTP(req)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2340,3 +2340,7 @@ the event as that's usually already covered by the `location` field.
Adds a new `volatile.uuid` configuration key to all storage volumes, snapshots and buckets.
This information can be used by storage drivers as a separate identifier besides the name
when working with volumes.

## `import_instance_devices`

This API extension provides the ability to use flags `--device` when importing an instance to override instance's devices.
8 changes: 8 additions & 0 deletions lxc/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type cmdImport struct {
global *cmdGlobal

flagStorage string
flagDevice []string
}

func (c *cmdImport) Command() *cobra.Command {
Expand All @@ -33,6 +34,7 @@ func (c *cmdImport) Command() *cobra.Command {

cmd.RunE = c.Run
cmd.Flags().StringVarP(&c.flagStorage, "storage", "s", "", i18n.G("Storage pool name")+"``")
cmd.Flags().StringArrayVarP(&c.flagDevice, "device", "d", nil, i18n.G("New key/value to apply to a specific device")+"``")

return cmd
}
Expand Down Expand Up @@ -95,6 +97,11 @@ func (c *cmdImport) Run(cmd *cobra.Command, args []string) error {
Quiet: c.global.flagQuiet,
}

deviceMap, err := parseDeviceOverrides(c.flagDevice)
if err != nil {
return err
}

createArgs := lxd.InstanceBackupArgs{
BackupFile: &ioprogress.ProgressReader{
ReadCloser: file,
Expand All @@ -107,6 +114,7 @@ func (c *cmdImport) Run(cmd *cobra.Command, args []string) error {
},
PoolName: c.flagStorage,
Name: instanceName,
Devices: deviceMap,
}

op, err := resource.server.CreateInstanceFromBackup(createArgs)
Expand Down
24 changes: 23 additions & 1 deletion lxd/api_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ func internalSQLExec(tx *sql.Tx, query string, result *internalSQLResult) error

// internalImportFromBackup creates instance, storage pool and volume DB records from an instance's backup file.
// It expects the instance volume to be mounted so that the backup.yaml file is readable.
func internalImportFromBackup(s *state.State, projectName string, instName string, allowNameOverride bool) error {
func internalImportFromBackup(s *state.State, projectName string, instName string, allowNameOverride bool, devices map[string]map[string]string) error {
if instName == "" {
return fmt.Errorf("The name of the instance is required")
}
Expand Down Expand Up @@ -661,6 +661,28 @@ func internalImportFromBackup(s *state.State, projectName string, instName strin
return err
}

// Override instance devices.
if len(devices) > 0 {
if backupConf.Container.Devices == nil {
backupConf.Container.Devices = map[string]map[string]string{}
}

for devName, devConfig := range devices {
if backupConf.Container.Devices[devName] == nil {
backupConf.Container.Devices[devName] = map[string]string{}
}

for k, v := range devConfig {
backupConf.Container.Devices[devName][k] = v
}
}

err = backup.OverrideConfigYamlFile(backupYamlPath, backupConf)
if err != nil {
return err
}
}

if allowNameOverride && instName != "" {
backupConf.Container.Name = instName
}
Expand Down
27 changes: 27 additions & 0 deletions lxd/backup/backup_config_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,33 @@ func ParseConfigYamlFile(path string) (*config.Config, error) {
return &backupConf, nil
}

// OverrideConfigYamlFile overrides the YAML file.
func OverrideConfigYamlFile(path string, backupConf *config.Config) error {
f, err := os.Create(path)
if err != nil {
return err
}

defer f.Close()

data, err := yaml.Marshal(backupConf)
if err != nil {
return err
}

err = f.Chmod(0400)
if err != nil {
return err
}

_, err = f.Write(data)
if err != nil {
return err
}

return nil
}

// updateRootDevicePool updates the root disk device in the supplied list of devices to the pool
// specified. Returns true if a root disk device has been found and updated otherwise false.
func updateRootDevicePool(devices map[string]map[string]string, poolName string) bool {
Expand Down
29 changes: 26 additions & 3 deletions lxd/instances_post.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"

Expand Down Expand Up @@ -557,7 +558,7 @@ func createFromCopy(s *state.State, r *http.Request, projectName string, profile
return operations.OperationResponse(op)
}

func createFromBackup(s *state.State, r *http.Request, projectName string, data io.Reader, pool string, instanceName string) response.Response {
func createFromBackup(s *state.State, r *http.Request, projectName string, data io.Reader, pool string, instanceName string, devices map[string]map[string]string) response.Response {
revert := revert.New()
defer revert.Fail()

Expand Down Expand Up @@ -718,7 +719,7 @@ func createFromBackup(s *state.State, r *http.Request, projectName string, data

runRevert.Add(revertHook)

err = internalImportFromBackup(s, bInfo.Project, bInfo.Name, instanceName != "")
err = internalImportFromBackup(s, bInfo.Project, bInfo.Name, instanceName != "", devices)
if err != nil {
return fmt.Errorf("Failed importing backup: %w", err)
}
Expand Down Expand Up @@ -810,7 +811,29 @@ func instancesPost(d *Daemon, r *http.Request) response.Response {

// If we're getting binary content, process separately
if r.Header.Get("Content-Type") == "application/octet-stream" {
return createFromBackup(s, r, targetProjectName, r.Body, r.Header.Get("X-LXD-pool"), r.Header.Get("X-LXD-name"))
deviceMap := map[string]map[string]string{}

if r.Header.Get("X-LXD-devices") != "" {
devProps, err := url.ParseQuery(r.Header.Get("X-LXD-devices"))
if err != nil {
return response.BadRequest(err)
}

for devKey := range devProps {
deviceMap[devKey] = map[string]string{}

props, err := url.ParseQuery(devProps.Get(devKey))
if err != nil {
return response.BadRequest(err)
}

for k := range props {
deviceMap[devKey][k] = props.Get(k)
}
}
}

return createFromBackup(s, r, targetProjectName, r.Body, r.Header.Get("X-LXD-pool"), r.Header.Get("X-LXD-name"), deviceMap)
}

// Parse the request
Expand Down
1 change: 1 addition & 0 deletions shared/version/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ var APIExtensions = []string{
"oidc_groups_claim",
"loki_config_instance",
"storage_volatile_uuid",
"import_instance_devices",
}

// APIExtensionsCount returns the number of available API extensions.
Expand Down

0 comments on commit 97aa791

Please sign in to comment.