diff --git a/cmd/incusd/api_cluster.go b/cmd/incusd/api_cluster.go index 5c4dab83081..6e72fe89a54 100644 --- a/cmd/incusd/api_cluster.go +++ b/cmd/incusd/api_cluster.go @@ -629,6 +629,7 @@ func clusterPutJoin(d *Daemon, r *http.Request, req api.ClusterPut) response.Res // Get all defined storage pools and networks, so they can be compared to the ones in the cluster. pools := []api.StoragePool{} + networks := []api.InitNetworksProjectPost{} err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { poolNames, err := tx.GetStoragePoolNames(ctx) @@ -645,17 +646,9 @@ func clusterPutJoin(d *Daemon, r *http.Request, req api.ClusterPut) response.Res pools = append(pools, *pool) } - return nil - }) - if err != nil { - return err - } + // Get a list of projects for networks. + var projects []dbCluster.Project - // Get a list of projects for networks. - var projects []dbCluster.Project - networks := []api.InitNetworksProjectPost{} - - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { projects, err = dbCluster.GetProjects(ctx, tx.Tx()) if err != nil { return fmt.Errorf("Failed to load projects for networks: %w", err) @@ -2791,66 +2784,54 @@ type internalClusterPostHandoverRequest struct { } func clusterCheckStoragePoolsMatch(cluster *db.Cluster, reqPools []api.StoragePool) error { - var err error - var poolNames []string - - err = cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - poolNames, err = tx.GetCreatedStoragePoolNames(ctx) - - return err - }) - if err != nil && !response.IsNotFoundError(err) { - return err - } + return cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + poolNames, err := tx.GetCreatedStoragePoolNames(ctx) + if err != nil && !response.IsNotFoundError(err) { + return err + } - for _, name := range poolNames { - found := false - for _, reqPool := range reqPools { - if reqPool.Name != name { - continue - } + for _, name := range poolNames { + found := false + for _, reqPool := range reqPools { + if reqPool.Name != name { + continue + } - found = true + found = true - var pool *api.StoragePool + var pool *api.StoragePool - err = cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { _, pool, _, err = tx.GetStoragePoolInAnyState(ctx, name) + if err != nil { + return err + } - return err - }) - if err != nil { - return err - } + if pool.Driver != reqPool.Driver { + return fmt.Errorf("Mismatching driver for storage pool %s", name) + } + // Exclude the keys which are node-specific. + exclude := db.NodeSpecificStorageConfig + err = localUtil.CompareConfigs(pool.Config, reqPool.Config, exclude) + if err != nil { + return fmt.Errorf("Mismatching config for storage pool %s: %w", name, err) + } - if pool.Driver != reqPool.Driver { - return fmt.Errorf("Mismatching driver for storage pool %s", name) - } - // Exclude the keys which are node-specific. - exclude := db.NodeSpecificStorageConfig - err = localUtil.CompareConfigs(pool.Config, reqPool.Config, exclude) - if err != nil { - return fmt.Errorf("Mismatching config for storage pool %s: %w", name, err) + break } - break + if !found { + return fmt.Errorf("Missing storage pool %s", name) + } } - if !found { - return fmt.Errorf("Missing storage pool %s", name) - } - } - return nil + return nil + }) } func clusterCheckNetworksMatch(cluster *db.Cluster, reqNetworks []api.InitNetworksProjectPost) error { - var err error - - // Get a list of projects for networks. - var networkProjectNames []string - - err = cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - networkProjectNames, err = dbCluster.GetProjectNames(ctx, tx.Tx()) + return cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + // Get a list of projects for networks. + networkProjectNames, err := dbCluster.GetProjectNames(ctx, tx.Tx()) if err != nil { return fmt.Errorf("Failed to load projects for networks: %w", err) } @@ -2898,11 +2879,6 @@ func clusterCheckNetworksMatch(cluster *db.Cluster, reqNetworks []api.InitNetwor return nil }) - if err != nil { - return err - } - - return nil } // Used as low-level recovering helper. @@ -3181,7 +3157,7 @@ func internalClusterHeal(d *Daemon, r *http.Request) response.Response { } func evacuateClusterSetState(s *state.State, name string, state int) error { - err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + return s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get the node. node, err := tx.GetNodeByName(ctx, name) if err != nil { @@ -3211,11 +3187,6 @@ func evacuateClusterSetState(s *state.State, name string, state int) error { return nil }) - if err != nil { - return err - } - - return nil } // evacuateHostShutdownDefaultTimeout default timeout (in seconds) for waiting for clean shutdown to complete. diff --git a/cmd/incusd/daemon_images.go b/cmd/incusd/daemon_images.go index bcb71f1d25d..7d04233e495 100644 --- a/cmd/incusd/daemon_images.go +++ b/cmd/incusd/daemon_images.go @@ -207,34 +207,26 @@ func ImageDownload(r *http.Request, s *state.State, op *operations.Operation, ar // Check if the image is available locally or it's on another node. Do this before creating // the missing DB record so we don't include ourself in the search results. nodeAddress, err = tx.LocateImage(ctx, imgInfo.Fingerprint) + if err != nil { + return fmt.Errorf("Locate image %q in the cluster: %w", imgInfo.Fingerprint, err) + } - return err - }) - if err != nil { - return nil, fmt.Errorf("Locate image %q in the cluster: %w", imgInfo.Fingerprint, err) - } - - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // We need to insert the database entry for this project, including the node ID entry. - return tx.CreateImage(ctx, args.ProjectName, imgInfo.Fingerprint, imgInfo.Filename, imgInfo.Size, args.Public, imgInfo.AutoUpdate, imgInfo.Architecture, imgInfo.CreatedAt, imgInfo.ExpiresAt, imgInfo.Properties, imgInfo.Type, nil) - }) - if err != nil { - return nil, fmt.Errorf("Failed creating image record for project: %w", err) - } - - // Mark the image as "cached" if downloading for an instance. - if args.SetCached { - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return tx.SetImageCachedAndLastUseDate(ctx, args.ProjectName, imgInfo.Fingerprint, time.Now().UTC()) - }) + err = tx.CreateImage(ctx, args.ProjectName, imgInfo.Fingerprint, imgInfo.Filename, imgInfo.Size, args.Public, imgInfo.AutoUpdate, imgInfo.Architecture, imgInfo.CreatedAt, imgInfo.ExpiresAt, imgInfo.Properties, imgInfo.Type, nil) if err != nil { - return nil, fmt.Errorf("Failed setting cached flag and last use date: %w", err) + return fmt.Errorf("Failed creating image record for project: %w", err) } - } - var id int + // Mark the image as "cached" if downloading for an instance. + if args.SetCached { + err = tx.SetImageCachedAndLastUseDate(ctx, args.ProjectName, imgInfo.Fingerprint, time.Now().UTC()) + if err != nil { + return fmt.Errorf("Failed setting cached flag and last use date: %w", err) + } + } + + var id int - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { id, imgInfo, err = tx.GetImage(ctx, fp, cluster.ImageFilter{Project: &args.ProjectName}) if err != nil { return err @@ -245,6 +237,7 @@ func ImageDownload(r *http.Request, s *state.State, op *operations.Operation, ar if err != nil { return nil, err } + // Transfer image if needed (after database record has been created above). if nodeAddress != "" { // The image is available from another node, let's try to import it. @@ -269,20 +262,15 @@ func ImageDownload(r *http.Request, s *state.State, op *operations.Operation, ar ctxMap["pool"] = args.StoragePool var poolID int64 + var poolIDs []int64 err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get the ID of the target storage pool. poolID, err = tx.GetStoragePoolID(ctx, args.StoragePool) + if err != nil { + return err + } - return err - }) - if err != nil { - return nil, err - } - - var poolIDs []int64 - - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Check if the image is already in the pool. poolIDs, err = tx.GetPoolsWithImage(ctx, info.Fingerprint) diff --git a/cmd/incusd/daemon_storage.go b/cmd/incusd/daemon_storage.go index 13331d54672..d1fe8892be6 100644 --- a/cmd/incusd/daemon_storage.go +++ b/cmd/incusd/daemon_storage.go @@ -155,19 +155,16 @@ func daemonStorageValidate(s *state.State, target string) error { } var poolID int64 + var snapshots []db.StorageVolumeArgs err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Validate pool exists. poolID, _, _, err = tx.GetStoragePool(ctx, poolName) + if err != nil { + return fmt.Errorf("Unable to load storage pool %q: %w", poolName, err) + } - return err - }) - if err != nil { - return fmt.Errorf("Unable to load storage pool %q: %w", poolName, err) - } - - // Confirm volume exists. - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + // Confirm volume exists. dbVol, err := tx.GetStoragePoolVolume(ctx, poolID, api.ProjectDefaultName, db.StoragePoolVolumeTypeCustom, volumeName, true) if err != nil { return fmt.Errorf("Failed loading storage volume %q in %q project: %w", target, api.ProjectDefaultName, err) @@ -177,21 +174,15 @@ func daemonStorageValidate(s *state.State, target string) error { return fmt.Errorf("Storage volume %q in %q project is not filesystem content type", target, api.ProjectDefaultName) } - return nil - }) - if err != nil { - return err - } - - var snapshots []db.StorageVolumeArgs - - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { snapshots, err = tx.GetLocalStoragePoolVolumeSnapshotsWithType(ctx, api.ProjectDefaultName, volumeName, db.StoragePoolVolumeTypeCustom, poolID) + if err != nil { + return fmt.Errorf("Unable to load storage volume snapshots %q in %q project: %w", target, api.ProjectDefaultName, err) + } - return err + return nil }) if err != nil { - return fmt.Errorf("Unable to load storage volume snapshots %q in %q project: %w", target, api.ProjectDefaultName, err) + return err } if len(snapshots) != 0 { diff --git a/cmd/incusd/images.go b/cmd/incusd/images.go index eeb546fd302..cafb8bce148 100644 --- a/cmd/incusd/images.go +++ b/cmd/incusd/images.go @@ -435,30 +435,26 @@ func imgPostRemoteInfo(s *state.State, r *http.Request, req api.ImagesPost, op * return nil, err } - var id int - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - id, info, err = tx.GetImage(ctx, info.Fingerprint, dbCluster.ImageFilter{Project: &project}) + var id int - return err - }) - if err != nil { - return nil, err - } + id, info, err = tx.GetImage(ctx, info.Fingerprint, dbCluster.ImageFilter{Project: &project}) + if err != nil { + return err + } - // Allow overriding or adding properties - for k, v := range req.Properties { - info.Properties[k] = v - } + // Allow overriding or adding properties + for k, v := range req.Properties { + info.Properties[k] = v + } - // Get profile IDs - if req.Profiles == nil { - req.Profiles = []string{api.ProjectDefaultName} - } + // Get profile IDs + if req.Profiles == nil { + req.Profiles = []string{api.ProjectDefaultName} + } - profileIds := make([]int64, len(req.Profiles)) + profileIds := make([]int64, len(req.Profiles)) - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { for i, profile := range req.Profiles { profileID, _, err := tx.GetProfile(ctx, project, profile) if response.IsNotFoundError(err) { @@ -470,22 +466,20 @@ func imgPostRemoteInfo(s *state.State, r *http.Request, req api.ImagesPost, op * profileIds[i] = profileID } + // Update the DB record if needed + if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 || len(req.Profiles) > 0 { + err := tx.UpdateImage(ctx, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, project, profileIds) + if err != nil { + return err + } + } + return nil }) if err != nil { return nil, err } - // Update the DB record if needed - if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 || len(req.Profiles) > 0 { - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return tx.UpdateImage(ctx, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, project, profileIds) - }) - if err != nil { - return nil, err - } - } - return info, nil } @@ -550,31 +544,32 @@ func imgPostURLInfo(s *state.State, r *http.Request, req api.ImagesPost, op *ope return nil, err } - var id int - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + var id int + id, info, err = tx.GetImage(ctx, info.Fingerprint, dbCluster.ImageFilter{Project: &project}) + if err != nil { + return err + } - return err + // Allow overriding or adding properties + for k, v := range req.Properties { + info.Properties[k] = v + } + + if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 { + err := tx.UpdateImage(ctx, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, "", nil) + if err != nil { + return err + } + } + + return nil }) if err != nil { return nil, err } - // Allow overriding or adding properties - for k, v := range req.Properties { - info.Properties[k] = v - } - - if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 { - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return tx.UpdateImage(ctx, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, "", nil) - }) - if err != nil { - return nil, err - } - } - return info, nil } @@ -1808,15 +1803,18 @@ func autoUpdateImages(ctx context.Context, s *state.State) error { } } - for _, ID := range deleteIDs { - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + _ = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + for _, ID := range deleteIDs { // Remove the database entry for the image after distributing to cluster members. - return tx.DeleteImage(ctx, ID) - }) - if err != nil { - logger.Error("Error deleting old image from database", logger.Ctx{"err": err, "fingerprint": fingerprint, "ID": ID}) + err := tx.DeleteImage(ctx, ID) + if err != nil { + logger.Error("Error deleting old image from database", logger.Ctx{"err": err, "fingerprint": fingerprint, "ID": ID}) + } } - } + + return nil + }) + } } @@ -2072,23 +2070,19 @@ func autoUpdateImage(ctx context.Context, s *state.State, op *operations.Operati } } + var poolNames []string + err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { var err error _, source, err = tx.GetImageSource(ctx, id) - return err - }) - if err != nil { - logger.Error("Error getting source image", logger.Ctx{"err": err, "fingerprint": fingerprint}) - return nil, err - } - - var poolIDs []int64 - var poolNames []string + if err != nil { + logger.Error("Error getting source image", logger.Ctx{"err": err, "fingerprint": fingerprint}) + return err + } - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get the IDs of all storage pools on which a storage volume // for the requested image currently exists. - poolIDs, err = tx.GetPoolsWithImage(ctx, fingerprint) + poolIDs, err := tx.GetPoolsWithImage(ctx, fingerprint) if err != nil { logger.Error("Error getting image pools", logger.Ctx{"err": err, "fingerprint": fingerprint}) return err @@ -2654,21 +2648,24 @@ func imageDelete(d *Daemon, r *http.Request) response.Response { // physically delete it just yet, but just to remove the // relevant database entry. referenced, err = tx.ImageIsReferencedByOtherProjects(ctx, projectName, imgInfo.Fingerprint) + if err != nil { + return err + } - return err + if referenced { + err = tx.DeleteImage(ctx, imgID) + if err != nil { + return fmt.Errorf("Error deleting image info from the database: %w", err) + } + } + + return nil }) if err != nil { return err } if referenced { - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return tx.DeleteImage(ctx, imgID) - }) - if err != nil { - return fmt.Errorf("Error deleting image info from the database: %w", err) - } - return nil } @@ -3070,7 +3067,7 @@ func imagePut(d *Daemon, r *http.Request) response.Response { profileIds[i] = profileID } - return nil + return tx.UpdateImage(ctx, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties, projectName, profileIds) }) if err != nil { if response.IsNotFoundError(err) { @@ -3080,13 +3077,6 @@ func imagePut(d *Daemon, r *http.Request) response.Response { return response.SmartError(err) } - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return tx.UpdateImage(ctx, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties, projectName, profileIds) - }) - if err != nil { - return response.SmartError(err) - } - requestor := request.CreateRequestor(r) s.Events.SendLifecycle(projectName, lifecycle.ImageUpdated.Event(info.Fingerprint, projectName, requestor, nil)) @@ -4537,6 +4527,7 @@ func imageSyncBetweenNodes(s *state.State, r *http.Request, project string, fing defer logger.Info("Syncing image to members finished", logger.Ctx{"fingerprint": fingerprint, "project": project}) var desiredSyncNodeCount int64 + var syncNodeAddresses []string err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { desiredSyncNodeCount = s.GlobalConfig.ImagesMinimalReplica() @@ -4551,22 +4542,18 @@ func imageSyncBetweenNodes(s *state.State, r *http.Request, project string, fing desiredSyncNodeCount = int64(nodesCount) } - return nil - }) - if err != nil { - return err - } - - var syncNodeAddresses []string + var err error - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Check how many nodes already have this image syncNodeAddresses, err = tx.GetNodesWithImage(ctx, fingerprint) + if err != nil { + return fmt.Errorf("Failed to get nodes for the image synchronization: %w", err) + } - return err + return nil }) if err != nil { - return fmt.Errorf("Failed to get nodes for the image synchronization: %w", err) + return err } // If none of the nodes have the image, there's nothing to sync. diff --git a/cmd/incusd/storage_volumes.go b/cmd/incusd/storage_volumes.go index cafecd45f75..773a0a58085 100644 --- a/cmd/incusd/storage_volumes.go +++ b/cmd/incusd/storage_volumes.go @@ -692,19 +692,15 @@ func storagePoolVolumesPost(d *Daemon, r *http.Request) response.Response { } var poolID int64 + var dbVolume *db.StorageVolume err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { poolID, err = tx.GetStoragePoolID(ctx, poolName) + if err != nil { + return err + } - return err - }) - if err != nil { - return response.SmartError(err) - } - - // Check if destination volume exists. - var dbVolume *db.StorageVolume - err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { + // Check if destination volume exists. dbVolume, err = tx.GetStoragePoolVolume(ctx, poolID, projectName, db.StoragePoolVolumeTypeCustom, req.Name, true) if err != nil && !response.IsNotFoundError(err) { return err @@ -1152,29 +1148,29 @@ func storagePoolVolumePost(d *Daemon, r *http.Request) response.Response { } if srcPool.Driver().Info().Name == "ceph" { - var srcPoolID int64 + var dbVolume *db.StorageVolume + var volumeNotFound bool + var targetIsSet bool - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { // Load source volume. - srcPoolID, err = tx.GetStoragePoolID(ctx, srcPoolName) + srcPoolID, err := tx.GetStoragePoolID(ctx, srcPoolName) + if err != nil { + return err + } - return err - }) - if err != nil { - return response.SmartError(err) - } + dbVolume, err = tx.GetStoragePoolVolume(ctx, srcPoolID, projectName, db.StoragePoolVolumeTypeCustom, volumeName, true) + if err != nil { + // Check if the user provided an incorrect target query parameter and return a helpful error message. + _, volumeNotFound = api.StatusErrorMatch(err, http.StatusNotFound) + targetIsSet = r.URL.Query().Get("target") != "" - var dbVolume *db.StorageVolume + return err + } - err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { - dbVolume, err = tx.GetStoragePoolVolume(ctx, srcPoolID, projectName, db.StoragePoolVolumeTypeCustom, volumeName, true) - return err + return nil }) if err != nil { - // Check if the user provided an incorrect target query parameter and return a helpful error message. - _, volumeNotFound := api.StatusErrorMatch(err, http.StatusNotFound) - targetIsSet := r.URL.Query().Get("target") != "" - if s.ServerClustered && targetIsSet && volumeNotFound { return response.NotFound(fmt.Errorf("Storage volume not found on this cluster member")) } @@ -1312,28 +1308,29 @@ func storagePoolVolumePost(d *Daemon, r *http.Request) response.Response { return response.SmartError(fmt.Errorf("Volume is used by Incus itself and cannot be renamed")) } - var srcPoolID int64 + var dbVolume *db.StorageVolume + var volumeNotFound bool + var targetIsSet bool err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Load source volume. - srcPoolID, err = tx.GetStoragePoolID(ctx, srcPoolName) - - return err - }) - if err != nil { - return response.SmartError(err) - } + srcPoolID, err := tx.GetStoragePoolID(ctx, srcPoolName) + if err != nil { + return err + } - var dbVolume *db.StorageVolume - err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { dbVolume, err = tx.GetStoragePoolVolume(ctx, srcPoolID, projectName, volumeType, volumeName, true) - return err + if err != nil { + // Check if the user provided an incorrect target query parameter and return a helpful error message. + _, volumeNotFound = api.StatusErrorMatch(err, http.StatusNotFound) + targetIsSet = r.URL.Query().Get("target") != "" + + return err + } + + return nil }) if err != nil { - // Check if the user provided an incorrect target query parameter and return a helpful error message. - _, volumeNotFound := api.StatusErrorMatch(err, http.StatusNotFound) - targetIsSet := r.URL.Query().Get("target") != "" - if s.ServerClustered && targetIsSet && volumeNotFound { return response.NotFound(fmt.Errorf("Storage volume not found on this cluster member")) } @@ -1718,21 +1715,16 @@ func storagePoolVolumeGet(d *Daemon, r *http.Request) response.Response { return resp } - var poolID int64 + var dbVolume *db.StorageVolume err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get the ID of the storage pool the storage volume is supposed to be attached to. - poolID, err = tx.GetStoragePoolID(ctx, poolName) - - return err - }) - if err != nil { - return response.SmartError(err) - } + poolID, err := tx.GetStoragePoolID(ctx, poolName) + if err != nil { + return err + } - // Get the storage volume. - var dbVolume *db.StorageVolume - err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { + // Get the storage volume. dbVolume, err = tx.GetStoragePoolVolume(ctx, poolID, volumeProjectName, volumeType, volumeName, true) return err }) diff --git a/cmd/incusd/storage_volumes_snapshot.go b/cmd/incusd/storage_volumes_snapshot.go index 940595851a6..9e135ccad63 100644 --- a/cmd/incusd/storage_volumes_snapshot.go +++ b/cmd/incusd/storage_volumes_snapshot.go @@ -702,32 +702,27 @@ func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) response.Respo } var poolID int64 + var dbVolume *db.StorageVolume + var expiry time.Time err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get the snapshot. poolID, _, _, err = tx.GetStoragePool(ctx, poolName) + if err != nil { + return err + } - return err - }) - if err != nil { - return response.SmartError(err) - } - - var dbVolume *db.StorageVolume - err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { dbVolume, err = tx.GetStoragePoolVolume(ctx, poolID, projectName, volumeType, fullSnapshotName, true) - return err - }) - if err != nil { - return response.SmartError(err) - } - - var expiry time.Time + if err != nil { + return err + } - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { expiry, err = tx.GetStorageVolumeSnapshotExpiry(ctx, dbVolume.ID) + if err != nil { + return err + } - return err + return nil }) if err != nil { return response.SmartError(err) @@ -837,32 +832,27 @@ func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) response.Respo } var poolID int64 + var dbVolume *db.StorageVolume + var expiry time.Time err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get the snapshot. poolID, _, _, err = tx.GetStoragePool(ctx, poolName) + if err != nil { + return err + } - return err - }) - if err != nil { - return response.SmartError(err) - } - - var dbVolume *db.StorageVolume - err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { dbVolume, err = tx.GetStoragePoolVolume(ctx, poolID, projectName, volumeType, fullSnapshotName, true) - return err - }) - if err != nil { - return response.SmartError(err) - } - - var expiry time.Time + if err != nil { + return err + } - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { expiry, err = tx.GetStorageVolumeSnapshotExpiry(ctx, dbVolume.ID) + if err != nil { + return err + } - return err + return nil }) if err != nil { return response.SmartError(err) @@ -977,29 +967,27 @@ func storagePoolVolumeSnapshotTypePatch(d *Daemon, r *http.Request) response.Res } var poolID int64 + var dbVolume *db.StorageVolume + var expiry time.Time err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get the snapshot. poolID, _, _, err = tx.GetStoragePool(ctx, poolName) + if err != nil { + return err + } - return err - }) - if err != nil { - return response.SmartError(err) - } - - var dbVolume *db.StorageVolume - err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { dbVolume, err = tx.GetStoragePoolVolume(ctx, poolID, projectName, volumeType, fullSnapshotName, true) - return err - }) - - var expiry time.Time + if err != nil { + return err + } - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { expiry, err = tx.GetStorageVolumeSnapshotExpiry(ctx, dbVolume.ID) + if err != nil { + return err + } - return err + return nil }) if err != nil { return response.SmartError(err) @@ -1492,21 +1480,20 @@ func volumeDetermineNextSnapshotName(s *state.State, volume db.StorageVolumeArgs var snapshots []db.StorageVolumeArgs var projects []string + var pools []string err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { projects, err = dbCluster.GetProjectNames(ctx, tx.Tx()) - return err - }) - if err != nil { - return "", err - } - - var pools []string + if err != nil { + return err + } - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { pools, err = tx.GetStoragePoolNames(ctx) + if err != nil { + return err + } - return err + return nil }) if err != nil { return "", err @@ -1517,14 +1504,10 @@ func volumeDetermineNextSnapshotName(s *state.State, volume db.StorageVolumeArgs err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { poolID, err = tx.GetStoragePoolID(ctx, pool) + if err != nil { + return err + } - return err - }) - if err != nil { - return "", err - } - - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { for _, project := range projects { snaps, err := tx.GetLocalStoragePoolVolumeSnapshotsWithType(ctx, project, volume.Name, db.StoragePoolVolumeTypeCustom, poolID) if err != nil { diff --git a/internal/server/instance/drivers/driver_lxc.go b/internal/server/instance/drivers/driver_lxc.go index 779c8b488ff..1c580d56784 100644 --- a/internal/server/instance/drivers/driver_lxc.go +++ b/internal/server/instance/drivers/driver_lxc.go @@ -3917,27 +3917,27 @@ func (d *lxc) Rename(newName string, applyTemplateTrigger bool) error { // Rename all the instance snapshot database entries. results, err = tx.GetInstanceSnapshotsNames(ctx, d.project.Name, oldName) if err != nil { - return fmt.Errorf("Failed getting instance snapshot names: %w", err) + d.logger.Error("Failed to get instance snapshots", ctxMap) + + return fmt.Errorf("Failed to get instance snapshots: Failed getting instance snapshot names: %w", err) + } + + for _, sname := range results { + // Rename the snapshot. + oldSnapName := strings.SplitN(sname, internalInstance.SnapshotDelimiter, 2)[1] + baseSnapName := filepath.Base(sname) + + err := cluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldName, oldSnapName, baseSnapName) + if err != nil { + d.logger.Error("Failed renaming snapshot", ctxMap) + return fmt.Errorf("Failed renaming snapshot: %w", err) + } } return nil }) if err != nil { - d.logger.Error("Failed to get instance snapshots", ctxMap) - return fmt.Errorf("Failed to get instance snapshots: %w", err) - } - - for _, sname := range results { - // Rename the snapshot. - oldSnapName := strings.SplitN(sname, internalInstance.SnapshotDelimiter, 2)[1] - baseSnapName := filepath.Base(sname) - err := d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return cluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldName, oldSnapName, baseSnapName) - }) - if err != nil { - d.logger.Error("Failed renaming snapshot", ctxMap) - return fmt.Errorf("Failed renaming snapshot: %w", err) - } + return err } } diff --git a/internal/server/instance/drivers/driver_qemu.go b/internal/server/instance/drivers/driver_qemu.go index efd77105cf3..4827341b5ce 100644 --- a/internal/server/instance/drivers/driver_qemu.go +++ b/internal/server/instance/drivers/driver_qemu.go @@ -4970,27 +4970,26 @@ func (d *qemu) Rename(newName string, applyTemplateTrigger bool) error { // Rename all the instance snapshot database entries. results, err = tx.GetInstanceSnapshotsNames(ctx, d.project.Name, oldName) if err != nil { - return fmt.Errorf("Failed getting instance snapshot names: %w", err) + d.logger.Error("Failed to get instance snapshots", ctxMap) + return fmt.Errorf("Failed to get instance snapshots: Failed getting instance snapshot names: %w", err) + } + + for _, sname := range results { + // Rename the snapshot. + oldSnapName := strings.SplitN(sname, internalInstance.SnapshotDelimiter, 2)[1] + baseSnapName := filepath.Base(sname) + + err := dbCluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldName, oldSnapName, baseSnapName) + if err != nil { + d.logger.Error("Failed renaming snapshot", ctxMap) + return err + } } return nil }) if err != nil { - d.logger.Error("Failed to get instance snapshots", ctxMap) - return fmt.Errorf("Failed to get instance snapshots: %w", err) - } - - for _, sname := range results { - // Rename the snapshot. - oldSnapName := strings.SplitN(sname, internalInstance.SnapshotDelimiter, 2)[1] - baseSnapName := filepath.Base(sname) - err := d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return dbCluster.RenameInstanceSnapshot(ctx, tx.Tx(), d.project.Name, oldName, oldSnapName, baseSnapName) - }) - if err != nil { - d.logger.Error("Failed renaming snapshot", ctxMap) - return err - } + return err } } diff --git a/internal/server/network/acl/acl_load.go b/internal/server/network/acl/acl_load.go index 79c7943ab53..7c671f98618 100644 --- a/internal/server/network/acl/acl_load.go +++ b/internal/server/network/acl/acl_load.go @@ -105,6 +105,9 @@ func UsedBy(s *state.State, aclProjectName string, usageFunc func(ctx context.Co return nil } + var profiles []cluster.Profile + profileDevices := map[string]map[string]cluster.Device{} + err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Find networks using the ACLs. Cheapest to do. networkNames, err := tx.GetCreatedNetworkNamesByProject(ctx, aclProjectName) @@ -135,16 +138,7 @@ func UsedBy(s *state.State, aclProjectName string, usageFunc func(ctx context.Co } } - return nil - }) - if err != nil { - return err - } - - // Look for profiles. Next cheapest to do. - var profiles []cluster.Profile - profileDevices := map[string]map[string]cluster.Device{} - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + // Look for profiles. Next cheapest to do. profiles, err = cluster.GetProfiles(ctx, tx.Tx()) if err != nil { return err diff --git a/internal/server/network/acl/acl_ovn.go b/internal/server/network/acl/acl_ovn.go index 7e17e40a479..f7e0e9d5917 100644 --- a/internal/server/network/acl/acl_ovn.go +++ b/internal/server/network/acl/acl_ovn.go @@ -758,33 +758,34 @@ func OVNApplyNetworkBaselineRules(client *ovn.NB, switchName ovn.OVNSwitch, rout // from the database. func OVNPortGroupDeleteIfUnused(s *state.State, l logger.Logger, client *ovn.NB, aclProjectName string, ignoreUsageType any, ignoreUsageNicName string, keepACLs ...string) error { var aclNameIDs map[string]int64 + var aclNames []string + var projectID int64 err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { var err error // Get map of ACL names to DB IDs (used for generating OVN port group names). aclNameIDs, err = tx.GetNetworkACLIDsByNames(ctx, aclProjectName) + if err != nil { + return fmt.Errorf("Failed getting network ACL IDs for security ACL port group removal: %w", err) + } - return err - }) - if err != nil { - return fmt.Errorf("Failed getting network ACL IDs for security ACL port group removal: %w", err) - } - - // Convert aclNameIDs to aclNames slice for use with UsedBy. - aclNames := make([]string, 0, len(aclNameIDs)) - for aclName := range aclNameIDs { - aclNames = append(aclNames, aclName) - } + // Convert aclNameIDs to aclNames slice for use with UsedBy. + aclNames = make([]string, 0, len(aclNameIDs)) + for aclName := range aclNameIDs { + aclNames = append(aclNames, aclName) + } - // Get project ID. - var projectID int64 - err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + // Get project ID. projectID, err = cluster.GetProjectID(ctx, tx.Tx(), aclProjectName) - return err + if err != nil { + return fmt.Errorf("Failed getting project ID for project %q: %w", aclProjectName, err) + } + + return nil }) if err != nil { - return fmt.Errorf("Failed getting project ID for project %q: %w", aclProjectName, err) + return err } // Get list of OVN port groups associated to this project. diff --git a/internal/server/network/driver_ovn.go b/internal/server/network/driver_ovn.go index 5f2b1b72732..014ff2accfd 100644 --- a/internal/server/network/driver_ovn.go +++ b/internal/server/network/driver_ovn.go @@ -2764,25 +2764,23 @@ func (n *ovn) Delete(clientType request.ClientType) error { memberSpecific := false // OVN doesn't support per-member forwards. var forwardListenAddresses map[int64]string + var loadBalancerListenAddresses map[int64]string err = n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { forwardListenAddresses, err = tx.GetNetworkForwardListenAddresses(ctx, n.ID(), memberSpecific) + if err != nil { + return fmt.Errorf("Failed loading network forwards: %w", err) + } - return err - }) - if err != nil { - return fmt.Errorf("Failed loading network forwards: %w", err) - } - - var loadBalancerListenAddresses map[int64]string - - err = n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { loadBalancerListenAddresses, err = tx.GetNetworkLoadBalancerListenAddresses(ctx, n.ID(), memberSpecific) + if err != nil { + return fmt.Errorf("Failed loading network forwards: %w", err) + } - return err + return nil }) if err != nil { - return fmt.Errorf("Failed loading network forwards: %w", err) + return err } loadBalancers := make([]networkOVN.OVNLoadBalancer, 0, len(forwardListenAddresses)+len(loadBalancerListenAddresses)) @@ -4627,30 +4625,29 @@ func (n *ovn) ForwardCreate(forward api.NetworkForwardsPost, clientType request. // Load the project to get uplink network restrictions. var p *api.Project + var uplink *api.Network + err = n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { project, err := dbCluster.GetProject(ctx, tx.Tx(), n.project) if err != nil { - return err + return fmt.Errorf("Failed to load network restrictions from project %q: %w", n.project, err) } p, err = project.ToAPI(ctx, tx.Tx()) + if err != nil { + return fmt.Errorf("Failed to load network restrictions from project %q: %w", n.project, err) + } - return err - }) - if err != nil { - return fmt.Errorf("Failed to load network restrictions from project %q: %w", n.project, err) - } - - var uplink *api.Network - - err = n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get uplink routes. _, uplink, _, err = tx.GetNetworkInAnyState(ctx, api.ProjectDefaultName, n.config["network"]) + if err != nil { + return fmt.Errorf("Failed to load uplink network %q: %w", n.config["network"], err) + } - return err + return nil }) if err != nil { - return fmt.Errorf("Failed to load uplink network %q: %w", n.config["network"], err) + return err } uplinkRoutes, err := n.uplinkRoutes(uplink) @@ -4983,30 +4980,29 @@ func (n *ovn) LoadBalancerCreate(loadBalancer api.NetworkLoadBalancersPost, clie // Load the project to get uplink network restrictions. var p *api.Project + var uplink *api.Network + err = n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { project, err := dbCluster.GetProject(ctx, tx.Tx(), n.project) if err != nil { - return err + return fmt.Errorf("Failed to load network restrictions from project %q: %w", n.project, err) } p, err = project.ToAPI(ctx, tx.Tx()) + if err != nil { + return fmt.Errorf("Failed to load network restrictions from project %q: %w", n.project, err) + } - return err - }) - if err != nil { - return fmt.Errorf("Failed to load network restrictions from project %q: %w", n.project, err) - } - - var uplink *api.Network - - err = n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { // Get uplink routes. _, uplink, _, err = tx.GetNetworkInAnyState(ctx, api.ProjectDefaultName, n.config["network"]) + if err != nil { + return fmt.Errorf("Failed to load uplink network %q: %w", n.config["network"], err) + } - return err + return nil }) if err != nil { - return fmt.Errorf("Failed to load uplink network %q: %w", n.config["network"], err) + return err } uplinkRoutes, err := n.uplinkRoutes(uplink) diff --git a/internal/server/network/zone/record.go b/internal/server/network/zone/record.go index 90970643b75..e686e04a928 100644 --- a/internal/server/network/zone/record.go +++ b/internal/server/network/zone/record.go @@ -42,34 +42,32 @@ func (d *zone) GetRecords() ([]api.NetworkZoneRecord, error) { s := d.state var names []string + records := []api.NetworkZoneRecord{} + var record *api.NetworkZoneRecord err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { var err error // Get the record names. names, err = tx.GetNetworkZoneRecordNames(ctx, d.id) + if err != nil { + return err + } - return err - }) - if err != nil { - return nil, err - } - - // Load all the records. - records := []api.NetworkZoneRecord{} - var record *api.NetworkZoneRecord - - for _, name := range names { - err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + // Load all the records. + for _, name := range names { _, record, err = tx.GetNetworkZoneRecord(ctx, d.id, name) + if err != nil { + return err + } - return err - }) - if err != nil { - return nil, err + records = append(records, *record) } - records = append(records, *record) + return nil + }) + if err != nil { + return nil, err } return records, nil diff --git a/internal/server/network/zone/zone.go b/internal/server/network/zone/zone.go index 81170479f9d..7d23bc346a0 100644 --- a/internal/server/network/zone/zone.go +++ b/internal/server/network/zone/zone.go @@ -97,14 +97,10 @@ func (d *zone) usedBy(firstOnly bool) ([]string, error) { // Find networks using the zone. networkNames, err = tx.GetCreatedNetworkNamesByProject(ctx, d.projectName) + if err != nil && !response.IsNotFoundError(err) { + return fmt.Errorf("Failed loading networks for project %q: %w", d.projectName, err) + } - return err - }) - if err != nil && !response.IsNotFoundError(err) { - return nil, fmt.Errorf("Failed loading networks for project %q: %w", d.projectName, err) - } - - err = d.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { for _, networkName := range networkNames { _, network, _, err := tx.GetNetworkInAnyState(ctx, d.projectName, networkName) if err != nil {