The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6407
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 b840d3f61edddfd219c07fe13253b5067ec8184b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 6 Nov 2019 01:21:26 -0500 Subject: [PATCH 1/3] lxd/storage/dir: Add check for bad source path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/storage/drivers/driver_dir.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lxd/storage/drivers/driver_dir.go b/lxd/storage/drivers/driver_dir.go index a80c2ccb12..4e581b9054 100644 --- a/lxd/storage/drivers/driver_dir.go +++ b/lxd/storage/drivers/driver_dir.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "golang.org/x/sys/unix" @@ -51,6 +52,12 @@ func (d *dir) Create() error { return fmt.Errorf("Source path '%s' doesn't exist", d.config["source"]) } + // Check that if within LXD_DIR, we're at our expected spot. + cleanSource := filepath.Clean(d.config["source"]) + if strings.HasPrefix(cleanSource, shared.VarPath()) && cleanSource != GetPoolMountPath(d.name) { + return fmt.Errorf("Source path '%s' is within the LXD directory") + } + // Check that the path is currently empty. isEmpty, err := shared.PathIsEmpty(d.config["source"]) if err != nil { From d334b7ad17f10f6e53ebe89317fb42b5c66c0832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 6 Nov 2019 01:22:34 -0500 Subject: [PATCH 2/3] lxd/storage: Add localOnly handling of create/delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/storage/backend_lxd.go | 24 ++++++++++++++++++------ lxd/storage/backend_mock.go | 2 +- lxd/storage/interfaces.go | 2 +- lxd/storage/load.go | 4 ++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 1e8b81b2a8..3fcc152513 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -57,7 +57,7 @@ func (b *lxdBackend) MigrationTypes(contentType drivers.ContentType) []migration } // create creates the storage pool layout on the storage device. -func (b *lxdBackend) create(dbPool *api.StoragePool, op *operations.Operation) error { +func (b *lxdBackend) create(dbPool *api.StoragePoolsPost, localOnly bool, op *operations.Operation) error { logger := logging.AddContext(b.logger, log.Ctx{"args": dbPool}) logger.Debug("create started") defer logger.Debug("created finished") @@ -71,6 +71,11 @@ func (b *lxdBackend) create(dbPool *api.StoragePool, op *operations.Operation) e return err } + // If dealing with a remote storage pool, we're done now + if b.driver.Info().Remote && localOnly { + return nil + } + // Undo the storage path create if there is an error. defer func() { if !revertPath { @@ -122,20 +127,27 @@ func (b *lxdBackend) GetResources() (*api.ResourcesStoragePool, error) { } // Delete removes the pool. -func (b *lxdBackend) Delete(op *operations.Operation) error { +func (b *lxdBackend) Delete(localOnly bool, op *operations.Operation) error { logger := logging.AddContext(b.logger, nil) logger.Debug("Delete started") defer logger.Debug("Delete finished") // Delete the low-level storage. - err := b.driver.Delete(op) - if err != nil { - return err + if !localOnly || !b.driver.Info().Remote { + err := b.driver.Delete(op) + if err != nil { + return err + } + } else { + _, err := b.driver.Unmount() + if err != nil { + return err + } } // Delete the mountpoint. path := shared.VarPath("storage-pools", b.name) - err = os.Remove(path) + err := os.Remove(path) if err != nil { return err } diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go index a0ddb34707..7e8520d4cc 100644 --- a/lxd/storage/backend_mock.go +++ b/lxd/storage/backend_mock.go @@ -46,7 +46,7 @@ func (b *mockBackend) GetResources() (*api.ResourcesStoragePool, error) { return nil, nil } -func (b *mockBackend) Delete(op *operations.Operation) error { +func (b *mockBackend) Delete(localOnly bool, op *operations.Operation) error { return nil } diff --git a/lxd/storage/interfaces.go b/lxd/storage/interfaces.go index cc7d249a55..bd3d0d7cf5 100644 --- a/lxd/storage/interfaces.go +++ b/lxd/storage/interfaces.go @@ -33,7 +33,7 @@ type Pool interface { Driver() drivers.Driver GetResources() (*api.ResourcesStoragePool, error) - Delete(op *operations.Operation) error + Delete(localOnly bool, op *operations.Operation) error Mount() (bool, error) Unmount() (bool, error) diff --git a/lxd/storage/load.go b/lxd/storage/load.go index 5249f9a32e..ee3df9df32 100644 --- a/lxd/storage/load.go +++ b/lxd/storage/load.go @@ -61,7 +61,7 @@ func volIDFuncMake(state *state.State, poolID int64) func(volType drivers.Volume // CreatePool creates a new storage pool on disk and returns a Pool interface. // If the pool's driver is not recognised then drivers.ErrUnknownDriver is returned. -func CreatePool(state *state.State, poolID int64, dbPool *api.StoragePool, op *operations.Operation) (Pool, error) { +func CreatePool(state *state.State, poolID int64, dbPool *api.StoragePoolsPost, localOnly bool, op *operations.Operation) (Pool, error) { // Sanity checks. if dbPool == nil { return nil, ErrNilValue @@ -98,7 +98,7 @@ func CreatePool(state *state.State, poolID int64, dbPool *api.StoragePool, op *o pool.logger = logger // Create the pool itself on the storage device.. - err = pool.create(dbPool, op) + err = pool.create(dbPool, localOnly, op) if err != nil { return nil, err } From afa14dbb147ef3b6522fee9fd0fd3706d230cf8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 6 Nov 2019 01:23:52 -0500 Subject: [PATCH 3/3] lxd/storage: Switch Create to new logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/api_internal.go | 2 +- lxd/storage_pools.go | 26 +++++--- lxd/storage_pools_utils.go | 127 ++++++++++++++++++++++++------------- 3 files changed, 102 insertions(+), 53 deletions(-) diff --git a/lxd/api_internal.go b/lxd/api_internal.go index 46393aba2c..cdf6dbc1b9 100644 --- a/lxd/api_internal.go +++ b/lxd/api_internal.go @@ -506,7 +506,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response { if poolErr == db.ErrNoSuchObject { // Create the storage pool db entry if it doesn't exist. - err := storagePoolDBCreate(d.State(), containerPoolName, "", + _, err := storagePoolDBCreate(d.State(), containerPoolName, "", backup.Pool.Driver, backup.Pool.Config) if err != nil { err = errors.Wrap(err, "Create storage pool database entry") diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go index 6634c4c240..cd9e33b2e7 100644 --- a/lxd/storage_pools.go +++ b/lxd/storage_pools.go @@ -117,11 +117,17 @@ func storagePoolsPost(d *Daemon, r *http.Request) response.Response { if err != nil { return response.BadRequest(err) } - err = doStoragePoolCreateInternal( - d.State(), req.Name, req.Description, req.Driver, req.Config, true) + + poolID, err := d.cluster.StoragePoolGetID(req.Name) + if err != nil { + return response.NotFound(err) + } + + err = storagePoolCreateLocal(d.State(), poolID, req, true) if err != nil { return response.SmartError(err) } + return resp } @@ -136,8 +142,7 @@ func storagePoolsPost(d *Daemon, r *http.Request) response.Response { // No targetNode was specified and we're either a single-node // cluster or not clustered at all, so create the storage // pool immediately. - err = storagePoolCreateInternal( - d.State(), req.Name, req.Description, req.Driver, req.Config) + err = storagePoolCreateGlobal(d.State(), req) } else { // No targetNode was specified and we're clustered, so finalize the // config in the db and actually create the pool on all nodes. @@ -146,8 +151,8 @@ func storagePoolsPost(d *Daemon, r *http.Request) response.Response { if err != nil { return response.InternalError(err) } - return resp + return resp } // A targetNode was specified, let's just define the node's storage @@ -171,6 +176,7 @@ func storagePoolsPost(d *Daemon, r *http.Request) response.Response { if err == db.ErrAlreadyDefined { return response.BadRequest(fmt.Errorf("The storage pool already defined on node %s", targetNode)) } + return response.SmartError(err) } @@ -189,9 +195,12 @@ func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error { // configs and insert the global config. var configs map[string]map[string]string var nodeName string + var poolID int64 err := d.cluster.Transaction(func(tx *db.ClusterTx) error { + var err error + // Check that the pool was defined at all. - poolID, err := tx.StoragePoolID(req.Name) + poolID, err = tx.StoragePoolID(req.Name) if err != nil { return err } @@ -223,12 +232,13 @@ func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error { for key, value := range configs[nodeName] { nodeReq.Config[key] = value } + err = storagePoolValidate(req.Name, req.Driver, req.Config) if err != nil { return err } - err = doStoragePoolCreateInternal( - d.State(), req.Name, req.Description, req.Driver, req.Config, false) + + err = storagePoolCreateLocal(d.State(), poolID, req, false) if err != nil { return err } diff --git a/lxd/storage_pools_utils.go b/lxd/storage_pools_utils.go index 3da2e8d30e..3a846e98da 100644 --- a/lxd/storage_pools_utils.go +++ b/lxd/storage_pools_utils.go @@ -7,8 +7,10 @@ import ( "github.com/lxc/lxd/lxd/db" "github.com/lxc/lxd/lxd/state" - driver "github.com/lxc/lxd/lxd/storage" + storagePools "github.com/lxc/lxd/lxd/storage" + storageDrivers "github.com/lxc/lxd/lxd/storage/drivers" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" ) @@ -40,7 +42,7 @@ func storagePoolUpdate(state *state.State, name, newDescription string, newConfi } }() - changedConfig, userOnly := driver.ConfigDiff(oldConfig, newConfig) + changedConfig, userOnly := storagePools.ConfigDiff(oldConfig, newConfig) // Apply config changes if there are any if len(changedConfig) != 0 { newWritable.Description = newDescription @@ -159,11 +161,11 @@ func profilesUsingPoolGetNames(db *db.Cluster, poolName string) ([]string, error return usedBy, nil } -func storagePoolDBCreate(s *state.State, poolName, poolDescription string, driver string, config map[string]string) error { +func storagePoolDBCreate(s *state.State, poolName, poolDescription string, driver string, config map[string]string) (int64, error) { // Check that the storage pool does not already exist. _, err := s.Cluster.StoragePoolGetID(poolName) if err == nil { - return fmt.Errorf("The storage pool already exists") + return -1, fmt.Errorf("The storage pool already exists") } // Make sure that we don't pass a nil to the next function. @@ -172,27 +174,27 @@ func storagePoolDBCreate(s *state.State, poolName, poolDescription string, drive } err = storagePoolValidate(poolName, driver, config) if err != nil { - return err + return -1, err } // Fill in the defaults err = storagePoolFillDefault(poolName, driver, config) if err != nil { - return err + return -1, err } // Create the database entry for the storage pool. - _, err = dbStoragePoolCreateAndUpdateCache(s.Cluster, poolName, poolDescription, driver, config) + id, err := dbStoragePoolCreateAndUpdateCache(s.Cluster, poolName, poolDescription, driver, config) if err != nil { - return fmt.Errorf("Error inserting %s into database: %s", poolName, err) + return -1, fmt.Errorf("Error inserting %s into database: %s", poolName, err) } - return nil + return id, nil } func storagePoolValidate(poolName string, driverName string, config map[string]string) error { // Check if the storage pool name is valid. - err := driver.ValidName(poolName) + err := storagePools.ValidName(poolName) if err != nil { return err } @@ -206,11 +208,13 @@ func storagePoolValidate(poolName string, driverName string, config map[string]s return nil } -func storagePoolCreateInternal(state *state.State, poolName, poolDescription string, driver string, config map[string]string) error { - err := storagePoolDBCreate(state, poolName, poolDescription, driver, config) +func storagePoolCreateGlobal(state *state.State, req api.StoragePoolsPost) error { + // Create the database entry. + id, err := storagePoolDBCreate(state, req.Name, req.Description, req.Driver, req.Config) if err != nil { return err } + // Define a function which reverts everything. Defer this function // so that it doesn't need to be explicitly called in every failing // return path. Track whether or not we want to undo the changes @@ -220,40 +224,77 @@ func storagePoolCreateInternal(state *state.State, poolName, poolDescription str if !tryUndo { return } - dbStoragePoolDeleteAndUpdateCache(state.Cluster, poolName) + + dbStoragePoolDeleteAndUpdateCache(state.Cluster, req.Name) }() - err = doStoragePoolCreateInternal(state, poolName, poolDescription, driver, config, false) - tryUndo = err != nil - return err -} -// This performs all non-db related work needed to create the pool. -func doStoragePoolCreateInternal(state *state.State, poolName, poolDescription string, driverName string, config map[string]string, isNotification bool) error { - tryUndo := true - s, err := storagePoolInit(state, poolName) + err = storagePoolCreateLocal(state, id, req, false) if err != nil { return err } - // If this is a clustering notification for a ceph storage, we don't - // want this node to actually create the pool, as it's already been - // done by the node that triggered this notification. We just need to - // create the storage pool directory. - if s, ok := s.(*storageCeph); ok && isNotification { - volumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name) - return os.MkdirAll(volumeMntPoint, 0711) + tryUndo = false + return nil +} - } - err = s.StoragePoolCreate() - if err != nil { - return err - } - defer func() { - if !tryUndo { - return +// This performs all non-db related work needed to create the pool. +func storagePoolCreateLocal(state *state.State, id int64, req api.StoragePoolsPost, isNotification bool) error { + tryUndo := true + + // Make a copy of the req for later diff. + var updatedConfig map[string]string + var updatedReq api.StoragePoolsPost + shared.DeepCopy(&req, &updatedReq) + + // Attempt to create using the new storage pool logic. + pool, err := storagePools.CreatePool(state, id, &updatedReq, isNotification, nil) + if err != storageDrivers.ErrUnknownDriver { + if err != nil { + return err + } + + // Record the updated config. + updatedConfig = updatedReq.Config + + // Setup revert function. + defer func() { + if !tryUndo { + return + } + + pool.Delete(isNotification, nil) + }() + } else { + // Load the old storage struct + s, err := storagePoolInit(state, req.Name) + if err != nil { + return err } - s.StoragePoolDelete() - }() + + // If this is a clustering notification for a ceph storage, we don't + // want this node to actually create the pool, as it's already been + // done by the node that triggered this notification. We just need to + // create the storage pool directory. + if s, ok := s.(*storageCeph); ok && isNotification { + volumeMntPoint := storagePools.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name) + return os.MkdirAll(volumeMntPoint, 0711) + } + + // Create the pool + err = s.StoragePoolCreate() + if err != nil { + return err + } + + updatedConfig = s.GetStoragePoolWritable().Config + + defer func() { + if !tryUndo { + return + } + s.StoragePoolDelete() + }() + } // In case the storage pool config was changed during the pool creation, // we need to update the database to reflect this change. This can e.g. @@ -261,13 +302,12 @@ func doStoragePoolCreateInternal(state *state.State, poolName, poolDescription s // to the path the user gave us and update the config in the storage // callback. So diff the config here to see if something like this has // happened. - postCreateConfig := s.GetStoragePoolWritable().Config - configDiff, _ := driver.ConfigDiff(config, postCreateConfig) + configDiff, _ := storagePools.ConfigDiff(req.Config, updatedConfig) if len(configDiff) > 0 { // Create the database entry for the storage pool. - err = state.Cluster.StoragePoolUpdate(poolName, poolDescription, postCreateConfig) + err = state.Cluster.StoragePoolUpdate(req.Name, req.Description, updatedConfig) if err != nil { - return fmt.Errorf("Error inserting %s into database: %s", poolName, err) + return fmt.Errorf("Error inserting %s into database: %s", req.Name, err) } } @@ -277,8 +317,7 @@ func doStoragePoolCreateInternal(state *state.State, poolName, poolDescription s return nil } -// Helper around the low-level DB API, which also updates the driver names -// cache. +// Helper around the low-level DB API, which also updates the driver names cache. func dbStoragePoolCreateAndUpdateCache(db *db.Cluster, poolName string, poolDescription string, poolDriver string, poolConfig map[string]string) (int64, error) { id, err := db.StoragePoolCreate(poolName, poolDescription, poolDriver, poolConfig) if err != nil {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel