The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/4652
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From 5ee9d0a532641a4f87aeee4d8263413845bc9738 Mon Sep 17 00:00:00 2001 From: Free Ekanayaka <[email protected]> Date: Thu, 14 Jun 2018 08:31:37 +0000 Subject: [PATCH 1/3] Extract cmdInit.ApplyConfig into a separete initApplyConfig function No logic change, just moving around code. Signed-off-by: Free Ekanayaka <[email protected]> --- lxd/init.go | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lxd/main_init.go | 372 +---------------------------------------------------- 2 files changed, 384 insertions(+), 371 deletions(-) create mode 100644 lxd/init.go diff --git a/lxd/init.go b/lxd/init.go new file mode 100644 index 000000000..873faa00d --- /dev/null +++ b/lxd/init.go @@ -0,0 +1,383 @@ +package main + +import ( + "fmt" + + lxd "github.com/lxc/lxd/client" + "github.com/lxc/lxd/lxd/cluster" + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" + "github.com/pkg/errors" +) + +type initData struct { + api.ServerPut `yaml:",inline"` + Cluster *initDataCluster `json:"cluster" yaml:"cluster"` + Networks []api.NetworksPost `json:"networks" yaml:"networks"` + StoragePools []api.StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"` + Profiles []api.ProfilesPost `json:"profiles" yaml:"profiles"` +} + +type initDataCluster struct { + api.ClusterPut `yaml:",inline"` + ClusterPassword string `json:"cluster_password" yaml:"cluster_password"` +} + +// Helper to initialize a LXD instance using the definitions from an initData +// object. +// +// It's used both by the 'lxd init' command and by the PUT /1.0/cluster API. +func initApplyConfig(d lxd.ContainerServer, config initData) error { + // Handle reverts + revert := true + reverts := []func(){} + defer func() { + if !revert { + return + } + + // Lets undo things in reverse order + for i := len(reverts) - 1; i >= 0; i-- { + reverts[i]() + } + }() + + // Apply server configuration + if config.Config != nil && len(config.Config) > 0 { + // Get current config + currentServer, etag, err := d.GetServer() + if err != nil { + return errors.Wrap(err, "Failed to retrieve current server configuration") + } + + // Setup reverter + reverts = append(reverts, func() { + d.UpdateServer(currentServer.Writable(), "") + }) + + // Prepare the update + newServer := api.ServerPut{} + err = shared.DeepCopy(currentServer.Writable(), &newServer) + if err != nil { + return errors.Wrap(err, "Failed to copy server configuration") + } + + for k, v := range config.Config { + newServer.Config[k] = fmt.Sprintf("%v", v) + } + + // Apply it + err = d.UpdateServer(newServer, etag) + if err != nil { + return errors.Wrap(err, "Failed to update server configuration") + } + } + + // Apply network configuration + if config.Networks != nil && len(config.Networks) > 0 { + // Get the list of networks + networkNames, err := d.GetNetworkNames() + if err != nil { + return errors.Wrap(err, "Failed to retrieve list of networks") + } + + // Network creator + createNetwork := func(network api.NetworksPost) error { + // Create the network if doesn't exist + err := d.CreateNetwork(network) + if err != nil { + return errors.Wrapf(err, "Failed to create network '%s'", network.Name) + } + + // Setup reverter + reverts = append(reverts, func() { + d.DeleteNetwork(network.Name) + }) + + return nil + } + + // Network updater + updateNetwork := func(network api.NetworksPost) error { + // Get the current network + currentNetwork, etag, err := d.GetNetwork(network.Name) + if err != nil { + return errors.Wrapf(err, "Failed to retrieve current network '%s'", network.Name) + } + + // Setup reverter + reverts = append(reverts, func() { + d.UpdateNetwork(currentNetwork.Name, currentNetwork.Writable(), "") + }) + + // Prepare the update + newNetwork := api.NetworkPut{} + err = shared.DeepCopy(currentNetwork.Writable(), &newNetwork) + if err != nil { + return errors.Wrapf(err, "Failed to copy configuration of network '%s'", network.Name) + } + + // Description override + if network.Description != "" { + newNetwork.Description = network.Description + } + + // Config overrides + for k, v := range network.Config { + newNetwork.Config[k] = fmt.Sprintf("%v", v) + } + + // Apply it + err = d.UpdateNetwork(currentNetwork.Name, newNetwork, etag) + if err != nil { + return errors.Wrapf(err, "Failed to update network '%s'", network.Name) + } + + return nil + } + + for _, network := range config.Networks { + // New network + if !shared.StringInSlice(network.Name, networkNames) { + err := createNetwork(network) + if err != nil { + return err + } + + continue + } + + // Existing network + err := updateNetwork(network) + if err != nil { + return err + } + } + } + + // Apply storage configuration + if config.StoragePools != nil && len(config.StoragePools) > 0 { + // Get the list of storagePools + storagePoolNames, err := d.GetStoragePoolNames() + if err != nil { + return errors.Wrap(err, "Failed to retrieve list of storage pools") + } + + // StoragePool creator + createStoragePool := func(storagePool api.StoragePoolsPost) error { + // Create the storagePool if doesn't exist + err := d.CreateStoragePool(storagePool) + if err != nil { + return errors.Wrapf(err, "Failed to create storage pool '%s'", storagePool.Name) + } + + // Setup reverter + reverts = append(reverts, func() { + d.DeleteStoragePool(storagePool.Name) + }) + + return nil + } + + // StoragePool updater + updateStoragePool := func(storagePool api.StoragePoolsPost) error { + // Get the current storagePool + currentStoragePool, etag, err := d.GetStoragePool(storagePool.Name) + if err != nil { + return errors.Wrapf(err, "Failed to retrieve current storage pool '%s'", storagePool.Name) + } + + // Sanity check + if currentStoragePool.Driver != storagePool.Driver { + return fmt.Errorf("Storage pool '%s' is of type '%s' instead of '%s'", currentStoragePool.Name, currentStoragePool.Driver, storagePool.Driver) + } + + // Setup reverter + reverts = append(reverts, func() { + d.UpdateStoragePool(currentStoragePool.Name, currentStoragePool.Writable(), "") + }) + + // Prepare the update + newStoragePool := api.StoragePoolPut{} + err = shared.DeepCopy(currentStoragePool.Writable(), &newStoragePool) + if err != nil { + return errors.Wrapf(err, "Failed to copy configuration of storage pool '%s'", storagePool.Name) + } + + // Description override + if storagePool.Description != "" { + newStoragePool.Description = storagePool.Description + } + + // Config overrides + for k, v := range storagePool.Config { + newStoragePool.Config[k] = fmt.Sprintf("%v", v) + } + + // Apply it + err = d.UpdateStoragePool(currentStoragePool.Name, newStoragePool, etag) + if err != nil { + return errors.Wrapf(err, "Failed to update storage pool '%s'", storagePool.Name) + } + + return nil + } + + for _, storagePool := range config.StoragePools { + // New storagePool + if !shared.StringInSlice(storagePool.Name, storagePoolNames) { + err := createStoragePool(storagePool) + if err != nil { + return err + } + + continue + } + + // Existing storagePool + err := updateStoragePool(storagePool) + if err != nil { + return err + } + } + } + + // Apply profile configuration + if config.Profiles != nil && len(config.Profiles) > 0 { + // Get the list of profiles + profileNames, err := d.GetProfileNames() + if err != nil { + return errors.Wrap(err, "Failed to retrieve list of profiles") + } + + // Profile creator + createProfile := func(profile api.ProfilesPost) error { + // Create the profile if doesn't exist + err := d.CreateProfile(profile) + if err != nil { + return errors.Wrapf(err, "Failed to create profile '%s'", profile.Name) + } + + // Setup reverter + reverts = append(reverts, func() { + d.DeleteProfile(profile.Name) + }) + + return nil + } + + // Profile updater + updateProfile := func(profile api.ProfilesPost) error { + // Get the current profile + currentProfile, etag, err := d.GetProfile(profile.Name) + if err != nil { + return errors.Wrapf(err, "Failed to retrieve current profile '%s'", profile.Name) + } + + // Setup reverter + reverts = append(reverts, func() { + d.UpdateProfile(currentProfile.Name, currentProfile.Writable(), "") + }) + + // Prepare the update + newProfile := api.ProfilePut{} + err = shared.DeepCopy(currentProfile.Writable(), &newProfile) + if err != nil { + return errors.Wrapf(err, "Failed to copy configuration of profile '%s'", profile.Name) + } + + // Description override + if profile.Description != "" { + newProfile.Description = profile.Description + } + + // Config overrides + for k, v := range profile.Config { + newProfile.Config[k] = fmt.Sprintf("%v", v) + } + + // Device overrides + for k, v := range profile.Devices { + // New device + _, ok := newProfile.Devices[k] + if !ok { + newProfile.Devices[k] = v + continue + } + + // Existing device + for configKey, configValue := range v { + newProfile.Devices[k][configKey] = fmt.Sprintf("%v", configValue) + } + } + + // Apply it + err = d.UpdateProfile(currentProfile.Name, newProfile, etag) + if err != nil { + return errors.Wrapf(err, "Failed to update profile '%s'", profile.Name) + } + + return nil + } + + for _, profile := range config.Profiles { + // New profile + if !shared.StringInSlice(profile.Name, profileNames) { + err := createProfile(profile) + if err != nil { + return err + } + + continue + } + + // Existing profile + err := updateProfile(profile) + if err != nil { + return err + } + } + } + + // Apply clustering configuration + if config.Cluster != nil && config.Cluster.Enabled { + // Get the current cluster configuration + currentCluster, etag, err := d.GetCluster() + if err != nil { + return errors.Wrap(err, "Failed to retrieve current cluster config") + } + + // Check if already enabled + if !currentCluster.Enabled { + // Setup trust relationship + if config.Cluster.ClusterAddress != "" && config.Cluster.ClusterPassword != "" { + // Get our certificate + serverConfig, _, err := d.GetServer() + if err != nil { + return errors.Wrap(err, "Failed to retrieve server configuration") + } + + // Try to setup trust + err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.Cluster.ClusterAddress, + config.Cluster.ClusterCertificate, config.Cluster.ClusterPassword) + if err != nil { + return errors.Wrap(err, "Failed to setup cluster trust") + } + } + + // Configure the cluster + op, err := d.UpdateCluster(config.Cluster.ClusterPut, etag) + if err != nil { + return errors.Wrap(err, "Failed to configure cluster") + } + + err = op.Wait() + if err != nil { + return errors.Wrap(err, "Failed to configure cluster") + } + } + } + + revert = false + return nil +} diff --git a/lxd/main_init.go b/lxd/main_init.go index 6ed65867a..ee1994bf2 100644 --- a/lxd/main_init.go +++ b/lxd/main_init.go @@ -7,25 +7,10 @@ import ( "github.com/spf13/cobra" "github.com/lxc/lxd/client" - "github.com/lxc/lxd/lxd/cluster" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" - "github.com/lxc/lxd/shared/api" ) -type initData struct { - api.ServerPut `yaml:",inline"` - Cluster *initDataCluster `json:"cluster" yaml:"cluster"` - Networks []api.NetworksPost `json:"networks" yaml:"networks"` - StoragePools []api.StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"` - Profiles []api.ProfilesPost `json:"profiles" yaml:"profiles"` -} - -type initDataCluster struct { - api.ClusterPut `yaml:",inline"` - ClusterPassword string `json:"cluster_password" yaml:"cluster_password"` -} - type cmdInit struct { global *cmdGlobal @@ -114,7 +99,7 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error { } } - return c.ApplyConfig(cmd, args, d, *config) + return initApplyConfig(d, *config) } func (c *cmdInit) availableStorageDrivers(poolType string) []string { @@ -157,358 +142,3 @@ func (c *cmdInit) availableStorageDrivers(poolType string) []string { return drivers } - -func (c *cmdInit) ApplyConfig(cmd *cobra.Command, args []string, d lxd.ContainerServer, config initData) error { - // Handle reverts - revert := true - reverts := []func(){} - defer func() { - if !revert { - return - } - - // Lets undo things in reverse order - for i := len(reverts) - 1; i >= 0; i-- { - reverts[i]() - } - }() - - // Apply server configuration - if config.Config != nil && len(config.Config) > 0 { - // Get current config - currentServer, etag, err := d.GetServer() - if err != nil { - return errors.Wrap(err, "Failed to retrieve current server configuration") - } - - // Setup reverter - reverts = append(reverts, func() { - d.UpdateServer(currentServer.Writable(), "") - }) - - // Prepare the update - newServer := api.ServerPut{} - err = shared.DeepCopy(currentServer.Writable(), &newServer) - if err != nil { - return errors.Wrap(err, "Failed to copy server configuration") - } - - for k, v := range config.Config { - newServer.Config[k] = fmt.Sprintf("%v", v) - } - - // Apply it - err = d.UpdateServer(newServer, etag) - if err != nil { - return errors.Wrap(err, "Failed to update server configuration") - } - } - - // Apply network configuration - if config.Networks != nil && len(config.Networks) > 0 { - // Get the list of networks - networkNames, err := d.GetNetworkNames() - if err != nil { - return errors.Wrap(err, "Failed to retrieve list of networks") - } - - // Network creator - createNetwork := func(network api.NetworksPost) error { - // Create the network if doesn't exist - err := d.CreateNetwork(network) - if err != nil { - return errors.Wrapf(err, "Failed to create network '%s'", network.Name) - } - - // Setup reverter - reverts = append(reverts, func() { - d.DeleteNetwork(network.Name) - }) - - return nil - } - - // Network updater - updateNetwork := func(network api.NetworksPost) error { - // Get the current network - currentNetwork, etag, err := d.GetNetwork(network.Name) - if err != nil { - return errors.Wrapf(err, "Failed to retrieve current network '%s'", network.Name) - } - - // Setup reverter - reverts = append(reverts, func() { - d.UpdateNetwork(currentNetwork.Name, currentNetwork.Writable(), "") - }) - - // Prepare the update - newNetwork := api.NetworkPut{} - err = shared.DeepCopy(currentNetwork.Writable(), &newNetwork) - if err != nil { - return errors.Wrapf(err, "Failed to copy configuration of network '%s'", network.Name) - } - - // Description override - if network.Description != "" { - newNetwork.Description = network.Description - } - - // Config overrides - for k, v := range network.Config { - newNetwork.Config[k] = fmt.Sprintf("%v", v) - } - - // Apply it - err = d.UpdateNetwork(currentNetwork.Name, newNetwork, etag) - if err != nil { - return errors.Wrapf(err, "Failed to update network '%s'", network.Name) - } - - return nil - } - - for _, network := range config.Networks { - // New network - if !shared.StringInSlice(network.Name, networkNames) { - err := createNetwork(network) - if err != nil { - return err - } - - continue - } - - // Existing network - err := updateNetwork(network) - if err != nil { - return err - } - } - } - - // Apply storage configuration - if config.StoragePools != nil && len(config.StoragePools) > 0 { - // Get the list of storagePools - storagePoolNames, err := d.GetStoragePoolNames() - if err != nil { - return errors.Wrap(err, "Failed to retrieve list of storage pools") - } - - // StoragePool creator - createStoragePool := func(storagePool api.StoragePoolsPost) error { - // Create the storagePool if doesn't exist - err := d.CreateStoragePool(storagePool) - if err != nil { - return errors.Wrapf(err, "Failed to create storage pool '%s'", storagePool.Name) - } - - // Setup reverter - reverts = append(reverts, func() { - d.DeleteStoragePool(storagePool.Name) - }) - - return nil - } - - // StoragePool updater - updateStoragePool := func(storagePool api.StoragePoolsPost) error { - // Get the current storagePool - currentStoragePool, etag, err := d.GetStoragePool(storagePool.Name) - if err != nil { - return errors.Wrapf(err, "Failed to retrieve current storage pool '%s'", storagePool.Name) - } - - // Sanity check - if currentStoragePool.Driver != storagePool.Driver { - return fmt.Errorf("Storage pool '%s' is of type '%s' instead of '%s'", currentStoragePool.Name, currentStoragePool.Driver, storagePool.Driver) - } - - // Setup reverter - reverts = append(reverts, func() { - d.UpdateStoragePool(currentStoragePool.Name, currentStoragePool.Writable(), "") - }) - - // Prepare the update - newStoragePool := api.StoragePoolPut{} - err = shared.DeepCopy(currentStoragePool.Writable(), &newStoragePool) - if err != nil { - return errors.Wrapf(err, "Failed to copy configuration of storage pool '%s'", storagePool.Name) - } - - // Description override - if storagePool.Description != "" { - newStoragePool.Description = storagePool.Description - } - - // Config overrides - for k, v := range storagePool.Config { - newStoragePool.Config[k] = fmt.Sprintf("%v", v) - } - - // Apply it - err = d.UpdateStoragePool(currentStoragePool.Name, newStoragePool, etag) - if err != nil { - return errors.Wrapf(err, "Failed to update storage pool '%s'", storagePool.Name) - } - - return nil - } - - for _, storagePool := range config.StoragePools { - // New storagePool - if !shared.StringInSlice(storagePool.Name, storagePoolNames) { - err := createStoragePool(storagePool) - if err != nil { - return err - } - - continue - } - - // Existing storagePool - err := updateStoragePool(storagePool) - if err != nil { - return err - } - } - } - - // Apply profile configuration - if config.Profiles != nil && len(config.Profiles) > 0 { - // Get the list of profiles - profileNames, err := d.GetProfileNames() - if err != nil { - return errors.Wrap(err, "Failed to retrieve list of profiles") - } - - // Profile creator - createProfile := func(profile api.ProfilesPost) error { - // Create the profile if doesn't exist - err := d.CreateProfile(profile) - if err != nil { - return errors.Wrapf(err, "Failed to create profile '%s'", profile.Name) - } - - // Setup reverter - reverts = append(reverts, func() { - d.DeleteProfile(profile.Name) - }) - - return nil - } - - // Profile updater - updateProfile := func(profile api.ProfilesPost) error { - // Get the current profile - currentProfile, etag, err := d.GetProfile(profile.Name) - if err != nil { - return errors.Wrapf(err, "Failed to retrieve current profile '%s'", profile.Name) - } - - // Setup reverter - reverts = append(reverts, func() { - d.UpdateProfile(currentProfile.Name, currentProfile.Writable(), "") - }) - - // Prepare the update - newProfile := api.ProfilePut{} - err = shared.DeepCopy(currentProfile.Writable(), &newProfile) - if err != nil { - return errors.Wrapf(err, "Failed to copy configuration of profile '%s'", profile.Name) - } - - // Description override - if profile.Description != "" { - newProfile.Description = profile.Description - } - - // Config overrides - for k, v := range profile.Config { - newProfile.Config[k] = fmt.Sprintf("%v", v) - } - - // Device overrides - for k, v := range profile.Devices { - // New device - _, ok := newProfile.Devices[k] - if !ok { - newProfile.Devices[k] = v - continue - } - - // Existing device - for configKey, configValue := range v { - newProfile.Devices[k][configKey] = fmt.Sprintf("%v", configValue) - } - } - - // Apply it - err = d.UpdateProfile(currentProfile.Name, newProfile, etag) - if err != nil { - return errors.Wrapf(err, "Failed to update profile '%s'", profile.Name) - } - - return nil - } - - for _, profile := range config.Profiles { - // New profile - if !shared.StringInSlice(profile.Name, profileNames) { - err := createProfile(profile) - if err != nil { - return err - } - - continue - } - - // Existing profile - err := updateProfile(profile) - if err != nil { - return err - } - } - } - - // Apply clustering configuration - if config.Cluster != nil && config.Cluster.Enabled { - // Get the current cluster configuration - currentCluster, etag, err := d.GetCluster() - if err != nil { - return errors.Wrap(err, "Failed to retrieve current cluster config") - } - - // Check if already enabled - if !currentCluster.Enabled { - // Setup trust relationship - if config.Cluster.ClusterAddress != "" && config.Cluster.ClusterPassword != "" { - // Get our certificate - serverConfig, _, err := d.GetServer() - if err != nil { - return errors.Wrap(err, "Failed to retrieve server configuration") - } - - // Try to setup trust - err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.Cluster.ClusterAddress, - config.Cluster.ClusterCertificate, config.Cluster.ClusterPassword) - if err != nil { - return errors.Wrap(err, "Failed to setup cluster trust") - } - } - - // Configure the cluster - op, err := d.UpdateCluster(config.Cluster.ClusterPut, etag) - if err != nil { - return errors.Wrap(err, "Failed to configure cluster") - } - - err = op.Wait() - if err != nil { - return errors.Wrap(err, "Failed to configure cluster") - } - } - } - - revert = false - return nil -} From e469bee487d9704efc152374d3de38928f30b8fb Mon Sep 17 00:00:00 2001 From: Free Ekanayaka <[email protected]> Date: Thu, 14 Jun 2018 09:31:41 +0000 Subject: [PATCH 2/3] Split initApplyConfig into initDataNodeApply and initDataClusterApply The logic initDataNodeApply will be shared with the PUT /1.0/cluster API. Signed-off-by: Free Ekanayaka <[email protected]> --- lxd/init.go | 109 ++++++++++++++++++++++--------------------- lxd/main_init.go | 15 +++++- lxd/main_init_auto.go | 10 ++-- lxd/main_init_interactive.go | 78 +++++++++++++++---------------- lxd/main_init_preseed.go | 4 +- 5 files changed, 115 insertions(+), 101 deletions(-) diff --git a/lxd/init.go b/lxd/init.go index 873faa00d..49d441649 100644 --- a/lxd/init.go +++ b/lxd/init.go @@ -10,9 +10,8 @@ import ( "github.com/pkg/errors" ) -type initData struct { +type initDataNode struct { api.ServerPut `yaml:",inline"` - Cluster *initDataCluster `json:"cluster" yaml:"cluster"` Networks []api.NetworksPost `json:"networks" yaml:"networks"` StoragePools []api.StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"` Profiles []api.ProfilesPost `json:"profiles" yaml:"profiles"` @@ -23,31 +22,28 @@ type initDataCluster struct { ClusterPassword string `json:"cluster_password" yaml:"cluster_password"` } -// Helper to initialize a LXD instance using the definitions from an initData -// object. +// Helper to initialize node-specific entities on a LXD instance using the +// definitions from the given initDataNode object. // // It's used both by the 'lxd init' command and by the PUT /1.0/cluster API. -func initApplyConfig(d lxd.ContainerServer, config initData) error { +// +// In case of error, the returned function can be used to revert the changes. +func initDataNodeApply(d lxd.ContainerServer, config initDataNode) (func(), error) { // Handle reverts - revert := true reverts := []func(){} - defer func() { - if !revert { - return - } - + revert := func() { // Lets undo things in reverse order for i := len(reverts) - 1; i >= 0; i-- { reverts[i]() } - }() + } // Apply server configuration if config.Config != nil && len(config.Config) > 0 { // Get current config currentServer, etag, err := d.GetServer() if err != nil { - return errors.Wrap(err, "Failed to retrieve current server configuration") + return revert, errors.Wrap(err, "Failed to retrieve current server configuration") } // Setup reverter @@ -59,7 +55,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { newServer := api.ServerPut{} err = shared.DeepCopy(currentServer.Writable(), &newServer) if err != nil { - return errors.Wrap(err, "Failed to copy server configuration") + return revert, errors.Wrap(err, "Failed to copy server configuration") } for k, v := range config.Config { @@ -69,7 +65,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { // Apply it err = d.UpdateServer(newServer, etag) if err != nil { - return errors.Wrap(err, "Failed to update server configuration") + return revert, errors.Wrap(err, "Failed to update server configuration") } } @@ -78,7 +74,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { // Get the list of networks networkNames, err := d.GetNetworkNames() if err != nil { - return errors.Wrap(err, "Failed to retrieve list of networks") + return revert, errors.Wrap(err, "Failed to retrieve list of networks") } // Network creator @@ -141,7 +137,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { if !shared.StringInSlice(network.Name, networkNames) { err := createNetwork(network) if err != nil { - return err + return revert, err } continue @@ -150,7 +146,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { // Existing network err := updateNetwork(network) if err != nil { - return err + return revert, err } } } @@ -160,7 +156,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { // Get the list of storagePools storagePoolNames, err := d.GetStoragePoolNames() if err != nil { - return errors.Wrap(err, "Failed to retrieve list of storage pools") + return revert, errors.Wrap(err, "Failed to retrieve list of storage pools") } // StoragePool creator @@ -228,7 +224,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { if !shared.StringInSlice(storagePool.Name, storagePoolNames) { err := createStoragePool(storagePool) if err != nil { - return err + return revert, err } continue @@ -237,7 +233,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { // Existing storagePool err := updateStoragePool(storagePool) if err != nil { - return err + return revert, err } } } @@ -247,7 +243,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { // Get the list of profiles profileNames, err := d.GetProfileNames() if err != nil { - return errors.Wrap(err, "Failed to retrieve list of profiles") + return revert, errors.Wrap(err, "Failed to retrieve list of profiles") } // Profile creator @@ -325,7 +321,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { if !shared.StringInSlice(profile.Name, profileNames) { err := createProfile(profile) if err != nil { - return err + return revert, err } continue @@ -334,50 +330,57 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error { // Existing profile err := updateProfile(profile) if err != nil { - return err + return revert, err } } } - // Apply clustering configuration - if config.Cluster != nil && config.Cluster.Enabled { - // Get the current cluster configuration - currentCluster, etag, err := d.GetCluster() - if err != nil { - return errors.Wrap(err, "Failed to retrieve current cluster config") - } + return nil, nil +} - // Check if already enabled - if !currentCluster.Enabled { - // Setup trust relationship - if config.Cluster.ClusterAddress != "" && config.Cluster.ClusterPassword != "" { - // Get our certificate - serverConfig, _, err := d.GetServer() - if err != nil { - return errors.Wrap(err, "Failed to retrieve server configuration") - } +// Helper to initialize LXD clustering. +// +// Used by the 'lxd init' command. +func initDataClusterApply(d lxd.ContainerServer, config *initDataCluster) error { + if config == nil || !config.Enabled { + return nil + } - // Try to setup trust - err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.Cluster.ClusterAddress, - config.Cluster.ClusterCertificate, config.Cluster.ClusterPassword) - if err != nil { - return errors.Wrap(err, "Failed to setup cluster trust") - } - } + // Get the current cluster configuration + currentCluster, etag, err := d.GetCluster() + if err != nil { + return errors.Wrap(err, "Failed to retrieve current cluster config") + } - // Configure the cluster - op, err := d.UpdateCluster(config.Cluster.ClusterPut, etag) + // Check if already enabled + if !currentCluster.Enabled { + // Setup trust relationship + if config.ClusterAddress != "" && config.ClusterPassword != "" { + // Get our certificate + serverConfig, _, err := d.GetServer() if err != nil { - return errors.Wrap(err, "Failed to configure cluster") + return errors.Wrap(err, "Failed to retrieve server configuration") } - err = op.Wait() + // Try to setup trust + err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.ClusterAddress, + config.ClusterCertificate, config.ClusterPassword) if err != nil { - return errors.Wrap(err, "Failed to configure cluster") + return errors.Wrap(err, "Failed to setup cluster trust") } } + + // Configure the cluster + op, err := d.UpdateCluster(config.ClusterPut, etag) + if err != nil { + return errors.Wrap(err, "Failed to configure cluster") + } + + err = op.Wait() + if err != nil { + return errors.Wrap(err, "Failed to configure cluster") + } } - revert = false return nil } diff --git a/lxd/main_init.go b/lxd/main_init.go index ee1994bf2..7b819eea5 100644 --- a/lxd/main_init.go +++ b/lxd/main_init.go @@ -11,6 +11,11 @@ import ( "github.com/lxc/lxd/shared" ) +type cmdInitData struct { + Node initDataNode `yaml:",inline"` + Cluster *initDataCluster `json:"cluster" yaml:"cluster"` +} + type cmdInit struct { global *cmdGlobal @@ -73,7 +78,7 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error { } // Prepare the input data - var config *initData + var config *cmdInitData // Preseed mode if c.flagPreseed { @@ -99,7 +104,13 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error { } } - return initApplyConfig(d, *config) + revert, err := initDataNodeApply(d, config.Node) + if err != nil { + revert() + return err + } + + return initDataClusterApply(d, config.Cluster) } func (c *cmdInit) availableStorageDrivers(poolType string) []string { diff --git a/lxd/main_init_auto.go b/lxd/main_init_auto.go index 656b8d02f..a819bca3e 100644 --- a/lxd/main_init_auto.go +++ b/lxd/main_init_auto.go @@ -11,7 +11,7 @@ import ( "github.com/lxc/lxd/shared/api" ) -func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*initData, error) { +func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) { // Sanity checks if c.flagStorageBackend != "" && !shared.StringInSlice(c.flagStorageBackend, supportedStoragePoolDrivers) { return nil, fmt.Errorf("The requested backend '%s' isn't supported by lxd init", c.flagStorageBackend) @@ -59,8 +59,8 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ c.flagNetworkPort = 8443 } - // Fill in the configuration - config := initData{} + // Fill in the node configuration + config := initDataNode{} config.Config = map[string]interface{}{} // Network listening @@ -143,7 +143,7 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ idx := 0 for { if shared.PathExists(fmt.Sprintf("/sys/class/net/lxdbr%d", idx)) { - idx += 1 + idx++ continue } @@ -180,5 +180,5 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ } } - return &config, nil + return &cmdInitData{Node: config}, nil } diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go index 73ae50d59..fef37b1ec 100644 --- a/lxd/main_init_interactive.go +++ b/lxd/main_init_interactive.go @@ -23,13 +23,13 @@ import ( "github.com/lxc/lxd/shared/idmap" ) -func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*initData, error) { +func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) { // Initialize config - config := initData{} - config.Config = map[string]interface{}{} - config.Networks = []api.NetworksPost{} - config.StoragePools = []api.StoragePoolsPost{} - config.Profiles = []api.ProfilesPost{ + config := cmdInitData{} + config.Node.Config = map[string]interface{}{} + config.Node.Networks = []api.NetworksPost{} + config.Node.StoragePools = []api.StoragePoolsPost{} + config.Node.Profiles = []api.ProfilesPost{ { Name: "default", ProfilePut: api.ProfilePut{ @@ -85,7 +85,7 @@ func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.Contai return &config, nil } -func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error { +func (c *cmdInit) askClustering(config *cmdInitData, d lxd.ContainerServer) error { if cli.AskBool("Would you like to use LXD clustering? (yes/no) [default=no]: ", "no") { config.Cluster = &initDataCluster{} config.Cluster.Enabled = true @@ -103,7 +103,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error { address := util.NetworkInterfaceAddress() serverAddress := util.CanonicalNetworkAddress(cli.AskString( fmt.Sprintf("What IP address or DNS name should be used to reach this node? [default=%s]: ", address), address, nil)) - config.Config["core.https_address"] = serverAddress + config.Node.Config["core.https_address"] = serverAddress if cli.AskBool("Are you joining an existing cluster? (yes/no) [default=no]: ", "no") { // Existing cluster @@ -177,7 +177,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error { return errors.Wrap(err, "Failed to retrieve storage pools from the cluster") } - config.StoragePools = []api.StoragePoolsPost{} + config.Node.StoragePools = []api.StoragePoolsPost{} for _, pool := range targetPools { // Skip pending pools if pool.Status == "PENDING" { @@ -208,7 +208,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error { fmt.Sprintf(`Choose the local disk or dataset for storage pool "%s" (empty for loop disk): `, pool.Name), "", validator) } - config.StoragePools = append(config.StoragePools, newPool) + config.Node.StoragePools = append(config.Node.StoragePools, newPool) } // Prompt for network config @@ -217,7 +217,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error { return errors.Wrap(err, "Failed to retrieve networks from the cluster") } - config.Networks = []api.NetworksPost{} + config.Node.Networks = []api.NetworksPost{} for _, network := range targetNetworks { // Skip not-managed or pending networks if !network.Managed || network.Status == "PENDING" { @@ -240,12 +240,12 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error { fmt.Sprintf(`Choose the local network interface to connect to network "%s" (empty for none): `, network.Name), "", validator) } - config.Networks = append(config.Networks, newNetwork) + config.Node.Networks = append(config.Node.Networks, newNetwork) } } else { // Password authentication if cli.AskBool("Setup password authentication on the cluster? (yes/no) [default=yes]: ", "yes") { - config.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ") + config.Node.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ") } } } @@ -253,7 +253,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error { return nil } -func (c *cmdInit) askMAAS(config *initData, d lxd.ContainerServer) error { +func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.ContainerServer) error { if !cli.AskBool("Would you like to connect to a MAAS server? (yes/no) [default=no]: ", "no") { return nil } @@ -265,16 +265,16 @@ func (c *cmdInit) askMAAS(config *initData, d lxd.ContainerServer) error { maasHostname := cli.AskString(fmt.Sprintf("What's the name of this host in MAAS? [default=%s]: ", serverName), serverName, nil) if maasHostname != serverName { - config.Config["maas.machine"] = maasHostname + config.Node.Config["maas.machine"] = maasHostname } - config.Config["maas.api.url"] = cli.AskString("URL of your MAAS server (e.g. http://1.2.3.4:5240/MAAS): ", "", nil) - config.Config["maas.api.key"] = cli.AskString("API key for your MAAS server: ", "", nil) + config.Node.Config["maas.api.url"] = cli.AskString("URL of your MAAS server (e.g. http://1.2.3.4:5240/MAAS): ", "", nil) + config.Node.Config["maas.api.key"] = cli.AskString("API key for your MAAS server: ", "", nil) return nil } -func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error { +func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.ContainerServer) error { if config.Cluster != nil || !cli.AskBool("Would you like to create a new local network bridge? (yes/no) [default=yes]: ", "yes") { // At this time, only the Ubuntu kernel supports the Fan, detect it fanKernel := false @@ -295,7 +295,7 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error { } // Add to the default profile - config.Profiles[0].Devices["eth0"] = map[string]string{ + config.Node.Profiles[0].Devices["eth0"] = map[string]string{ "type": "nic", "nictype": "macvlan", "name": "eth0", @@ -303,22 +303,22 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error { } if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", name)) { - config.Profiles[0].Devices["eth0"]["nictype"] = "bridged" + config.Node.Profiles[0].Devices["eth0"]["nictype"] = "bridged" } - if config.Config["maas.api.url"] != nil && cli.AskBool("Is this interface connected to your MAAS server? (yes/no) [default=yes]: ", "yes") { + if config.Node.Config["maas.api.url"] != nil && cli.AskBool("Is this interface connected to your MAAS server? (yes/no) [default=yes]: ", "yes") { maasSubnetV4 := cli.AskString("MAAS IPv4 subnet name for this interface (empty for no subnet): ", "", func(input string) error { return nil }) if maasSubnetV4 != "" { - config.Profiles[0].Devices["eth0"]["maas.subnet.ipv4"] = maasSubnetV4 + config.Node.Profiles[0].Devices["eth0"]["maas.subnet.ipv4"] = maasSubnetV4 } maasSubnetV6 := cli.AskString("MAAS IPv6 subnet name for this interface (empty for no subnet): ", "", func(input string) error { return nil }) if maasSubnetV6 != "" { - config.Profiles[0].Devices["eth0"]["maas.subnet.ipv6"] = maasSubnetV6 + config.Node.Profiles[0].Devices["eth0"]["maas.subnet.ipv6"] = maasSubnetV6 } } @@ -333,10 +333,10 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error { } // Add the new network - config.Networks = append(config.Networks, network) + config.Node.Networks = append(config.Node.Networks, network) // Add to the default profile - config.Profiles[0].Devices["eth0"] = map[string]string{ + config.Node.Profiles[0].Devices["eth0"] = map[string]string{ "type": "nic", "nictype": "bridged", "name": "eth0", @@ -361,7 +361,7 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error { } // Add to the default profile - config.Profiles[0].Devices["eth0"] = map[string]string{ + config.Node.Profiles[0].Devices["eth0"] = map[string]string{ "type": "nic", "nictype": "bridged", "name": "eth0", @@ -397,14 +397,14 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error { } // Add the new network - config.Networks = append(config.Networks, network) + config.Node.Networks = append(config.Node.Networks, network) break } return nil } -func (c *cmdInit) askStorage(config *initData, d lxd.ContainerServer) error { +func (c *cmdInit) askStorage(config *cmdInitData, d lxd.ContainerServer) error { if config.Cluster != nil { if cli.AskBool("Do you want to configure a new local storage pool? (yes/no) [default=yes]: ", "yes") { err := c.askStoragePool(config, d, "local") @@ -430,7 +430,7 @@ func (c *cmdInit) askStorage(config *initData, d lxd.ContainerServer) error { return c.askStoragePool(config, d, "all") } -func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolType string) error { +func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.ContainerServer, poolType string) error { // Figure out the preferred storage driver availableBackends := c.availableStorageDrivers(poolType) @@ -474,7 +474,7 @@ func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolTy } // Add to the default profile - config.Profiles[0].Devices["root"] = map[string]string{ + config.Node.Profiles[0].Devices["root"] = map[string]string{ "type": "disk", "path": "/", "pool": pool.Name, @@ -490,7 +490,7 @@ func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolTy // Optimization for dir if pool.Driver == "dir" { - config.StoragePools = append(config.StoragePools, pool) + config.Node.StoragePools = append(config.Node.StoragePools, pool) break } @@ -498,7 +498,7 @@ func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolTy if pool.Driver == "btrfs" && backingFs == "btrfs" { if cli.AskBool(fmt.Sprintf("Would you like to create a new btrfs subvolume under %s? (yes/no) [default=yes]: ", shared.VarPath("")), "yes") { pool.Config["source"] = shared.VarPath("storage-pools", pool.Name) - config.StoragePools = append(config.StoragePools, pool) + config.Node.StoragePools = append(config.Node.StoragePools, pool) break } } @@ -577,14 +577,14 @@ your Linux distribution and run "lxd init" again afterwards. } } - config.StoragePools = append(config.StoragePools, pool) + config.Node.StoragePools = append(config.Node.StoragePools, pool) break } return nil } -func (c *cmdInit) askDaemon(config *initData, d lxd.ContainerServer) error { +func (c *cmdInit) askDaemon(config *cmdInitData, d lxd.ContainerServer) error { // Detect lack of uid/gid idmapset, err := idmap.DefaultIdmapSet("") if (err != nil || len(idmapset.Idmap) == 0 || idmapset.Usable() != nil) && shared.RunningInUserNS() { @@ -601,7 +601,7 @@ they otherwise would. `) if cli.AskBool("Would you like to have your containers share their parent's allocation? (yes/no) [default=yes]: ", "yes") { - config.Profiles[0].Config["security.privileged"] = "true" + config.Node.Profiles[0].Config["security.privileged"] = "true" } } @@ -625,16 +625,16 @@ they otherwise would. } netPort := cli.AskInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443") - config.Config["core.https_address"] = fmt.Sprintf("%s:%d", netAddr, netPort) - config.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ") - if config.Config["core.trust_password"] == "" { + config.Node.Config["core.https_address"] = fmt.Sprintf("%s:%d", netAddr, netPort) + config.Node.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ") + if config.Node.Config["core.trust_password"] == "" { fmt.Printf("No password set, client certificates will have to be manually trusted.") } } // Ask if the user wants images to be automatically refreshed if !cli.AskBool("Would you like stale cached images to be updated automatically? (yes/no) [default=yes] ", "yes") { - config.Config["images.auto_update_interval"] = "0" + config.Node.Config["images.auto_update_interval"] = "0" } return nil diff --git a/lxd/main_init_preseed.go b/lxd/main_init_preseed.go index e21cc2034..0f75481d6 100644 --- a/lxd/main_init_preseed.go +++ b/lxd/main_init_preseed.go @@ -11,7 +11,7 @@ import ( "github.com/lxc/lxd/client" ) -func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*initData, error) { +func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) { // Read the YAML bytes, err := ioutil.ReadAll(os.Stdin) if err != nil { @@ -19,7 +19,7 @@ func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerS } // Parse the YAML - config := initData{} + config := cmdInitData{} err = yaml.Unmarshal(bytes, &config) if err != nil { return nil, errors.Wrap(err, "Failed to parse the preseed") From 8b3a766c7c51f4897c4261479d42e24288b5590b Mon Sep 17 00:00:00 2001 From: Free Ekanayaka <[email protected]> Date: Thu, 14 Jun 2018 10:00:01 +0000 Subject: [PATCH 3/3] Extend api.Cluster schema Signed-off-by: Free Ekanayaka <[email protected]> --- shared/api/cluster.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/api/cluster.go b/shared/api/cluster.go index 3ae4db490..0d9d7d550 100644 --- a/shared/api/cluster.go +++ b/shared/api/cluster.go @@ -4,8 +4,16 @@ package api // // API extension: clustering type Cluster struct { - ServerName string `json:"server_name" yaml:"server_name"` - Enabled bool `json:"enabled" yaml:"enabled"` + ServerName string `json:"server_name" yaml:"server_name"` + Enabled bool `json:"enabled" yaml:"enabled"` + MemberConfig []ClusterMemberConfigKey `json:"member_config" yaml:"member_config"` +} + +// ClusterMemberConfigKey represents a single config key that a new member of +// the cluster is required to provide when joining. +type ClusterMemberConfigKey struct { + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"description"` } // ClusterPut represents the fields required to bootstrap or join a LXD
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
