diff --git a/lxd-agent/exec.go b/lxd-agent/exec.go index f42b89e63c2c..84ddc06a914e 100644 --- a/lxd-agent/exec.go +++ b/lxd-agent/exec.go @@ -136,7 +136,8 @@ func execPost(d *Daemon, r *http.Request) response.Response { resources := map[string][]api.URL{} - op, err := operations.OperationCreate(nil, "", operations.OperationClassWebsocket, operationtype.CommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect, r) + operationOpts := operations.Options().WithResources(resources).WithMetadata(ws.Metadata()).WithOnConnect(ws.Connect).WithRequest(r) + op, err := operations.OperationCreate(context.Background(), operations.OperationClassWebsocket, operationtype.CommandExec, "lxd-agent", d.events, ws.Do, operationOpts) if err != nil { return response.InternalError(err) } @@ -163,6 +164,7 @@ type execWs struct { cwd string } +// Metadata returns information required for the client to interact with the exec websocket. func (s *execWs) Metadata() any { fds := shared.Jmap{} for fd, secret := range s.fds { @@ -181,6 +183,7 @@ func (s *execWs) Metadata() any { } } +// Connect is the websocket connect hook for the exec operation. func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error { secret := r.FormValue("secret") if secret == "" { @@ -211,9 +214,9 @@ func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.Respo return nil } else if !found { return fmt.Errorf("Unknown websocket number") - } else { - return fmt.Errorf("Websocket number already connected") } + + return fmt.Errorf("Websocket number already connected") } } @@ -222,6 +225,7 @@ func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.Respo return os.ErrPermission } +// Do runs waits for the websocket to connect, then proxies the VM ttys through the websocket. func (s *execWs) Do(op *operations.Operation) error { // Once this function ends ensure that any connected websockets are closed. defer func() { @@ -239,7 +243,6 @@ func (s *execWs) Do(op *operations.Operation) error { logger.Debug("Waiting for exec websockets to connect") select { case <-s.requiredConnectedCtx.Done(): - break case <-time.After(time.Second * 5): return fmt.Errorf("Timed out waiting for websockets to connect") } diff --git a/lxd/acme.go b/lxd/acme.go index 7b9d131667ee..70ca2415f232 100644 --- a/lxd/acme.go +++ b/lxd/acme.go @@ -137,7 +137,7 @@ func autoRenewCertificate(ctx context.Context, d *Daemon, force bool) error { return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.RenewServerCertificate, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.RenewServerCertificate, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating renew server certificate operation", logger.Ctx{"err": err}) return err diff --git a/lxd/api_cluster.go b/lxd/api_cluster.go index a2e1a59728d6..eb2b3c5a0f17 100644 --- a/lxd/api_cluster.go +++ b/lxd/api_cluster.go @@ -461,7 +461,8 @@ func clusterPutBootstrap(d *Daemon, r *http.Request, req api.ClusterPut) respons d.localConfig = config d.globalConfigMu.Unlock() - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ClusterBootstrap, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ClusterBootstrap, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -869,7 +870,8 @@ func clusterPutJoin(d *Daemon, r *http.Request, req api.ClusterPut) response.Res resources := map[string][]api.URL{} resources["cluster"] = []api.URL{} - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ClusterJoin, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ClusterJoin, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -1433,7 +1435,8 @@ func clusterNodesPost(d *Daemon, r *http.Request) response.Response { resources := map[string][]api.URL{} resources["cluster"] = []api.URL{} - op, err := operations.OperationCreate(s, api.ProjectDefaultName, operations.OperationClassToken, operationtype.ClusterJoinToken, resources, meta, nil, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(api.ProjectDefaultName).WithResources(resources).WithMetadata(meta).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassToken, operationtype.ClusterJoinToken, s.ServerName, s.Events, nil, operationOpts) if err != nil { return response.InternalError(err) } @@ -3276,7 +3279,8 @@ func evacuateClusterMember(s *state.State, gateway *cluster.Gateway, r *http.Req return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ClusterMemberEvacuate, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ClusterMemberEvacuate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -3601,7 +3605,8 @@ func restoreClusterMember(d *Daemon, r *http.Request) response.Response { return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ClusterMemberRestore, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ClusterMemberRestore, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -4492,7 +4497,7 @@ func autoHealClusterTask(d *Daemon) (task.Func, task.Schedule) { return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ClusterHeal, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ClusterHeal, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating cluster instances heal operation", logger.Ctx{"err": err}) return diff --git a/lxd/api_project.go b/lxd/api_project.go index 9a2da43eac82..fa8735911a38 100644 --- a/lxd/api_project.go +++ b/lxd/api_project.go @@ -819,7 +819,8 @@ func projectPost(d *Daemon, r *http.Request) response.Response { return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ProjectRename, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ProjectRename, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/backup.go b/lxd/backup.go index 3dcd7ca67fc9..07657cfee846 100644 --- a/lxd/backup.go +++ b/lxd/backup.go @@ -314,7 +314,7 @@ func pruneExpiredBackupsTask(d *Daemon) (task.Func, task.Schedule) { return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.BackupsExpire, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.BackupsExpire, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating expired backups operation", logger.Ctx{"err": err}) return diff --git a/lxd/certificates.go b/lxd/certificates.go index 97639d8141a5..ae98056465b2 100644 --- a/lxd/certificates.go +++ b/lxd/certificates.go @@ -596,7 +596,8 @@ func certificatesPost(d *Daemon, r *http.Request) response.Response { meta["expiresAt"] = expiresAt } - op, err := operations.OperationCreate(s, api.ProjectDefaultName, operations.OperationClassToken, operationtype.CertificateAddToken, nil, meta, nil, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithRequest(r).WithMetadata(meta).WithProjectName(api.ProjectDefaultName) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassToken, operationtype.CertificateAddToken, s.ServerName, s.Events, nil, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/db/db.go b/lxd/db/db.go index a9a2bb6a3daf..2746f6666826 100644 --- a/lxd/db/db.go +++ b/lxd/db/db.go @@ -332,6 +332,13 @@ func (c *Cluster) Transaction(ctx context.Context, f func(context.Context, *Clus return c.transaction(ctx, f) } +// TransactionSQL is identical to Transaction but the hook has a sql.Tx instead (for import cycle reasons). +func (c *Cluster) TransactionSQL(ctx context.Context, f func(context.Context, *sql.Tx) error) error { + c.mu.RLock() + defer c.mu.RUnlock() + return c.transactionSQL(ctx, f) +} + // EnterExclusive acquires a lock on the cluster db, so any successive call to // Transaction will block until ExitExclusive has been called. func (c *Cluster) EnterExclusive() error { @@ -384,6 +391,21 @@ func (c *Cluster) transaction(ctx context.Context, f func(context.Context, *Clus }) } +func (c *Cluster) transactionSQL(ctx context.Context, f func(context.Context, *sql.Tx) error) error { + return query.Retry(ctx, func(ctx context.Context) error { + err := query.Transaction(ctx, c.db, f) + if errors.Is(err, context.DeadlineExceeded) { + // If the query timed out it likely means that the leader has abruptly become unreachable. + // Now that this query has been cancelled, a leader election should have taken place by now. + // So let's retry the transaction once more in case the global database is now available again. + logger.Warn("Transaction timed out. Retrying once", logger.Ctx{"member": c.nodeID, "err": err}) + return query.Transaction(ctx, c.db, f) + } + + return err + }) +} + // NodeID sets the node NodeID associated with this cluster instance. It's used for // backward-compatibility of all db-related APIs that were written before // clustering and don't accept a node NodeID, so in those cases we automatically @@ -432,7 +454,7 @@ func begin(db *sql.DB) (*sql.Tx, error) { } logger.Debugf("DbBegin: DB still locked") - logger.Debugf(logger.GetStack()) + logger.Debug(logger.GetStack()) return nil, fmt.Errorf("DB is locked") } diff --git a/lxd/images.go b/lxd/images.go index 32d6a4738633..f9e73d446ca2 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -1322,7 +1322,8 @@ func imagesPost(d *Daemon, r *http.Request) response.Response { } } - imageOp, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.ImageDownload, nil, metadata, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithMetadata(metadata).WithRequest(r) + imageOp, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImageDownload, s.ServerName, s.Events, run, operationOpts) if err != nil { cleanup(builddir, post) return response.InternalError(err) @@ -1763,7 +1764,8 @@ func autoUpdateImagesTask(d *Daemon) (task.Func, task.Schedule) { return autoUpdateImages(ctx, s) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ImagesUpdate, nil, nil, opRun, nil, nil, nil) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImagesUpdate, s.ServerName, s.Events, opRun, operationOpts) if err != nil { logger.Error("Failed creating image update operation", logger.Ctx{"err": err}) return @@ -2412,7 +2414,8 @@ func pruneExpiredImagesTask(d *Daemon) (task.Func, task.Schedule) { return pruneExpiredImages(ctx, s, op) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ImagesExpire, nil, nil, opRun, nil, nil, nil) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImagesExpire, s.ServerName, s.Events, opRun, operationOpts) if err != nil { logger.Error("Failed creating expired image prune operation", logger.Ctx{"err": err}) return @@ -2530,7 +2533,8 @@ func pruneLeftoverImages(s *state.State) { return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ImagesPruneLeftover, nil, nil, opRun, nil, nil, nil) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImagesPruneLeftover, s.ServerName, s.Events, opRun, operationOpts) if err != nil { logger.Error("Failed creating leftover image clean up operation", logger.Ctx{"err": err}) return @@ -2889,7 +2893,8 @@ func imageDelete(d *Daemon, r *http.Request) response.Response { resources := map[string][]api.URL{} resources["images"] = []api.URL{*api.NewURL().Path(version.APIVersion, "images", details.image.Fingerprint)} - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.ImageDelete, resources, nil, do, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImageDelete, s.ServerName, s.Events, do, operationOpts) if err != nil { return response.InternalError(err) } @@ -4400,7 +4405,8 @@ func imageExportPost(d *Daemon, r *http.Request) response.Response { return nil } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.ImageDownload, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImageDownload, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -4587,7 +4593,8 @@ func imageRefresh(d *Daemon, r *http.Request) response.Response { return err } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.ImageRefresh, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImageRefresh, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -4622,7 +4629,8 @@ func autoSyncImagesTask(d *Daemon) (task.Func, task.Schedule) { return autoSyncImages(ctx, s) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.ImagesSynchronize, nil, nil, opRun, nil, nil, nil) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.ImagesSynchronize, s.ServerName, s.Events, opRun, operationOpts) if err != nil { logger.Error("Failed creating image synchronization operation", logger.Ctx{"err": err}) return @@ -4825,7 +4833,8 @@ func createTokenResponse(s *state.State, r *http.Request, projectName string, fi resources := map[string][]api.URL{} resources["images"] = []api.URL{*api.NewURL().Path(version.APIVersion, "images", fingerprint)} - op, err := operations.OperationCreate(s, projectName, operations.OperationClassToken, operationtype.ImageToken, resources, meta, nil, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithMetadata(meta).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassToken, operationtype.ImageToken, s.ServerName, s.Events, nil, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance.go b/lxd/instance.go index 339b80665319..d88e31ad5de5 100644 --- a/lxd/instance.go +++ b/lxd/instance.go @@ -664,7 +664,7 @@ func pruneExpiredAndAutoCreateInstanceSnapshotsTask(d *Daemon) (task.Func, task. return pruneExpiredInstanceSnapshots(ctx, expiredSnapshotInstances) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.SnapshotsExpire, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.SnapshotsExpire, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating instance snapshots expiry operation", logger.Ctx{"err": err}) } else { @@ -690,7 +690,7 @@ func pruneExpiredAndAutoCreateInstanceSnapshotsTask(d *Daemon) (task.Func, task. return autoCreateInstanceSnapshots(ctx, s, instances) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.SnapshotCreate, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.SnapshotCreate, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating scheduled instance snapshot operation", logger.Ctx{"err": err}) } else { diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go index f2dc88ee0c14..e549a185447d 100644 --- a/lxd/instance/drivers/driver_lxc.go +++ b/lxd/instance/drivers/driver_lxc.go @@ -5467,12 +5467,11 @@ func (d *lxc) MigrateSend(args instance.MigrateSendArgs) error { } actionScriptOp, err := operations.OperationCreate( - d.state, - d.Project().Name, + d.state.ShutdownCtx, operations.OperationClassWebsocket, operationtype.InstanceLiveMigrate, - nil, - nil, + d.state.ServerName, + d.state.Events, func(op *operations.Operation) error { result := <-restoreSuccess if !result { @@ -5481,28 +5480,28 @@ func (d *lxc) MigrateSend(args instance.MigrateSendArgs) error { return nil }, - nil, - func(op *operations.Operation, r *http.Request, w http.ResponseWriter) error { - secret := r.FormValue("secret") - if secret == "" { - return fmt.Errorf("Missing action script secret") - } + operations.ClusterOptions(d.state.DB.Cluster.TransactionSQL). + WithProjectName(d.Project().Name). + WithOnConnect(func(op *operations.Operation, r *http.Request, w http.ResponseWriter) error { + secret := r.FormValue("secret") + if secret == "" { + return fmt.Errorf("Missing action script secret") + } - if secret != actionScriptOpSecret { - return os.ErrPermission - } + if secret != actionScriptOpSecret { + return os.ErrPermission + } - c, err := ws.Upgrader.Upgrade(w, r, nil) - if err != nil { - return err - } + c, err := ws.Upgrader.Upgrade(w, r, nil) + if err != nil { + return err + } - dumpDone <- true + dumpDone <- true - closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") - return c.WriteMessage(websocket.CloseMessage, closeMsg) - }, - nil, + closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") + return c.WriteMessage(websocket.CloseMessage, closeMsg) + }), ) if err != nil { _ = os.RemoveAll(checkpointDir) diff --git a/lxd/instance_backup.go b/lxd/instance_backup.go index 5fcc32a49b0a..216c1cdc9870 100644 --- a/lxd/instance_backup.go +++ b/lxd/instance_backup.go @@ -346,8 +346,8 @@ func instanceBackupsPost(d *Daemon, r *http.Request) response.Response { resources["backups"] = []api.URL{*api.NewURL().Path(version.APIVersion, "instances", name, "backups", req.Name)} - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, - operationtype.BackupCreate, resources, nil, backup, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.BackupCreate, s.ServerName, s.Events, backup, operationOpts) if err != nil { return response.InternalError(err) } @@ -536,8 +536,8 @@ func instanceBackupPost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, - operationtype.BackupRename, resources, nil, rename, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.BackupRename, s.ServerName, s.Events, rename, operationOpts) if err != nil { return response.InternalError(err) } @@ -625,8 +625,8 @@ func instanceBackupDelete(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, - operationtype.BackupRemove, resources, nil, remove, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.BackupRemove, s.ServerName, s.Events, remove, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance_console.go b/lxd/instance_console.go index 865efea46de4..1fb7a2819599 100644 --- a/lxd/instance_console.go +++ b/lxd/instance_console.go @@ -520,7 +520,8 @@ func instanceConsolePost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassWebsocket, operationtype.ConsoleShow, resources, ws.Metadata(), ws.Do, nil, ws.Connect, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithMetadata(ws.Metadata()).WithOnConnect(ws.Connect).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassWebsocket, operationtype.ConsoleShow, s.ServerName, s.Events, ws.Do, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance_delete.go b/lxd/instance_delete.go index f09f1c2fccc2..a25e7ac79552 100644 --- a/lxd/instance_delete.go +++ b/lxd/instance_delete.go @@ -95,7 +95,8 @@ func instanceDelete(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.InstanceDelete, resources, nil, rmct, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceDelete, s.ServerName, s.Events, rmct, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance_exec.go b/lxd/instance_exec.go index 419238be53fb..79375b0e5131 100644 --- a/lxd/instance_exec.go +++ b/lxd/instance_exec.go @@ -701,7 +701,8 @@ func instanceExecPost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassWebsocket, operationtype.CommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r).WithMetadata(ws.Metadata()).WithOnConnect(ws.Connect) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassWebsocket, operationtype.CommandExec, s.ServerName, s.Events, ws.Do, operationOpts) if err != nil { return response.InternalError(err) } @@ -777,7 +778,8 @@ func instanceExecPost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.CommandExec, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.CommandExec, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance_instance_types.go b/lxd/instance_instance_types.go index c6b3233a0dcd..caa7505e373a 100644 --- a/lxd/instance_instance_types.go +++ b/lxd/instance_instance_types.go @@ -75,7 +75,7 @@ func instanceRefreshTypesTask(d *Daemon) (task.Func, task.Schedule) { return instanceRefreshTypes(ctx, s) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.InstanceTypesUpdate, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceTypesUpdate, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating instance types update operation", logger.Ctx{"err": err}) return diff --git a/lxd/instance_post.go b/lxd/instance_post.go index 6a8686c6b478..a107172c3c44 100644 --- a/lxd/instance_post.go +++ b/lxd/instance_post.go @@ -337,7 +337,8 @@ func instancePost(d *Daemon, r *http.Request) response.Response { resources := map[string][]api.URL{} resources["instances"] = []api.URL{*api.NewURL().Path(version.APIVersion, "instances", name)} - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.InstanceMigrate, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceMigrate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -373,7 +374,8 @@ func instancePost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.InstanceMigrate, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceMigrate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -403,9 +405,10 @@ func instancePost(d *Daemon, r *http.Request) response.Response { return nil } + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) if req.Target != nil { // Push mode. - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.InstanceMigrate, resources, nil, run, nil, nil, r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceMigrate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -414,7 +417,8 @@ func instancePost(d *Daemon, r *http.Request) response.Response { } // Pull mode. - op, err := operations.OperationCreate(s, projectName, operations.OperationClassWebsocket, operationtype.InstanceMigrate, resources, ws.Metadata(), run, cancel, ws.Connect, r) + operationOpts = operationOpts.WithOnCancel(cancel).WithOnConnect(ws.Connect).WithMetadata(ws.Metadata()) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassWebsocket, operationtype.InstanceMigrate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -445,7 +449,8 @@ func instancePost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.InstanceRename, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceRename, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -737,7 +742,8 @@ func instancePostClusteringMigrate(s *state.State, r *http.Request, srcPool stor return nil } - srcOp, err := operations.OperationCreate(s, projectName, operations.OperationClassWebsocket, operationtype.InstanceMigrate, resources, srcMigration.Metadata(), run, cancel, srcMigration.Connect, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithMetadata(srcMigration.Metadata()).WithOnCancel(cancel).WithOnConnect(srcMigration.Connect).WithRequest(r) + srcOp, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassWebsocket, operationtype.InstanceMigrate, s.ServerName, s.Events, run, operationOpts) if err != nil { return err } diff --git a/lxd/instance_put.go b/lxd/instance_put.go index 47289d5461db..7322187e293d 100644 --- a/lxd/instance_put.go +++ b/lxd/instance_put.go @@ -195,7 +195,8 @@ func instancePut(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, opType, resources, nil, do, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, opType, s.ServerName, s.Events, do, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance_rebuild.go b/lxd/instance_rebuild.go index 46b3dc0bb700..9d77a7bc25d8 100644 --- a/lxd/instance_rebuild.go +++ b/lxd/instance_rebuild.go @@ -161,7 +161,8 @@ func instanceRebuildPost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, targetProject.Name, operations.OperationClassTask, operationtype.InstanceRebuild, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(targetProject.Name).WithRequest(r).WithResources(resources) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceRebuild, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance_snapshot.go b/lxd/instance_snapshot.go index 48ef3755e894..50d9171a0cc2 100644 --- a/lxd/instance_snapshot.go +++ b/lxd/instance_snapshot.go @@ -341,7 +341,8 @@ func instanceSnapshotsPost(d *Daemon, r *http.Request) response.Response { resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.SnapshotCreate, resources, nil, snapshot, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.SnapshotCreate, s.ServerName, s.Events, snapshot, operationOpts) if err != nil { return response.InternalError(err) } @@ -543,7 +544,8 @@ func snapshotPut(s *state.State, r *http.Request, snapInst instance.Instance) re resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, snapInst.Project().Name, operations.OperationClassTask, opType, resources, nil, do, nil, nil, r) + operationOptions := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(snapInst.Project().Name).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, opType, s.ServerName, s.Events, do, operationOptions) if err != nil { return response.InternalError(err) } @@ -699,9 +701,10 @@ func snapshotPost(s *state.State, r *http.Request, snapInst instance.Instance) r return ws.Do(s, op) } + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(snapInst.Project().Name).WithResources(resources).WithRequest(r) if req.Target != nil { // Push mode. - op, err := operations.OperationCreate(s, snapInst.Project().Name, operations.OperationClassTask, operationtype.SnapshotTransfer, resources, nil, run, nil, nil, r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.SnapshotTransfer, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -710,7 +713,8 @@ func snapshotPost(s *state.State, r *http.Request, snapInst instance.Instance) r } // Pull mode. - op, err := operations.OperationCreate(s, snapInst.Project().Name, operations.OperationClassWebsocket, operationtype.SnapshotTransfer, resources, ws.Metadata(), run, nil, ws.Connect, r) + operationOpts = operationOpts.WithMetadata(ws.Metadata()).WithOnConnect(ws.Connect) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassWebsocket, operationtype.SnapshotTransfer, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -756,7 +760,8 @@ func snapshotPost(s *state.State, r *http.Request, snapInst instance.Instance) r resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, snapInst.Project().Name, operations.OperationClassTask, operationtype.SnapshotRename, resources, nil, rename, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(snapInst.Project().Name).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.SnapshotRename, s.ServerName, s.Events, rename, operationOpts) if err != nil { return response.InternalError(err) } @@ -805,7 +810,8 @@ func snapshotDelete(s *state.State, r *http.Request, snapInst instance.Instance) resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, snapInst.Project().Name, operations.OperationClassTask, operationtype.SnapshotDelete, resources, nil, remove, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(snapInst.Project().Name).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.SnapshotDelete, s.ServerName, s.Events, remove, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instance_state.go b/lxd/instance_state.go index 769c8cacc3ae..e2da4b26b984 100644 --- a/lxd/instance_state.go +++ b/lxd/instance_state.go @@ -204,7 +204,8 @@ func instanceStatePut(d *Daemon, r *http.Request) response.Response { resources := map[string][]api.URL{} resources["instances"] = []api.URL{*api.NewURL().Path(version.APIVersion, "instances", name)} - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, opType, resources, nil, do, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, opType, s.ServerName, s.Events, do, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instances_post.go b/lxd/instances_post.go index 7df0a7850603..04de53ee48ee 100644 --- a/lxd/instances_post.go +++ b/lxd/instances_post.go @@ -141,7 +141,8 @@ func createFromImage(s *state.State, r *http.Request, p api.Project, profiles [] resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, p.Name, operations.OperationClassTask, operationtype.InstanceCreate, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(p.Name).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceCreate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -198,7 +199,8 @@ func createFromNone(s *state.State, r *http.Request, projectName string, profile resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.InstanceCreate, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceCreate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -360,17 +362,18 @@ func createFromMigration(s *state.State, r *http.Request, projectName string, pr resources["containers"] = resources["instances"] } - var op *operations.Operation + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + var operationClass operations.OperationClass if push { - op, err = operations.OperationCreate(s, projectName, operations.OperationClassWebsocket, operationtype.InstanceCreate, resources, sink.Metadata(), run, nil, sink.Connect, r) - if err != nil { - return response.InternalError(err) - } + operationClass = operations.OperationClassWebsocket + operationOpts = operationOpts.WithMetadata(sink.Metadata()).WithOnConnect(sink.Connect) } else { - op, err = operations.OperationCreate(s, projectName, operations.OperationClassTask, operationtype.InstanceCreate, resources, nil, run, nil, nil, r) - if err != nil { - return response.InternalError(err) - } + operationClass = operations.OperationClassTask + } + + op, err := operations.OperationCreate(s.ShutdownCtx, operationClass, operationtype.InstanceCreate, s.ServerName, s.Events, run, operationOpts) + if err != nil { + return response.InternalError(err) } revert.Success() @@ -474,7 +477,8 @@ func createFromConversion(s *state.State, r *http.Request, projectName string, p resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassWebsocket, operationtype.InstanceCreate, resources, sink.Metadata(), run, nil, sink.Connect, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithMetadata(sink.Metadata()).WithOnConnect(sink.Connect).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassWebsocket, operationtype.InstanceCreate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -637,7 +641,8 @@ func createFromCopy(s *state.State, r *http.Request, projectName string, profile resources["containers"] = resources["instances"] } - op, err := operations.OperationCreate(s, targetProject, operations.OperationClassTask, operationtype.InstanceCreate, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(targetProject).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.InstanceCreate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -859,7 +864,8 @@ func createFromBackup(s *state.State, r *http.Request, projectName string, data resources := map[string][]api.URL{} resources["instances"] = []api.URL{*api.NewURL().Path(version.APIVersion, "instances", bInfo.Name)} - op, err := operations.OperationCreate(s, bInfo.Project, operations.OperationClassTask, operationtype.BackupRestore, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(bInfo.Project).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.BackupRestore, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/instances_put.go b/lxd/instances_put.go index 92784e40b3cd..e77bd96c6fb8 100644 --- a/lxd/instances_put.go +++ b/lxd/instances_put.go @@ -275,7 +275,8 @@ func instancesPut(d *Daemon, r *http.Request) response.Response { resources["instances"] = append(resources["instances"], *api.NewURL().Path(version.APIVersion, "instances", instName)) } - op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, opType, resources, nil, do, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(projectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, opType, s.ServerName, s.Events, do, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/logging.go b/lxd/logging.go index b3f9312b5ec8..abfb4a562174 100644 --- a/lxd/logging.go +++ b/lxd/logging.go @@ -25,7 +25,7 @@ func expireLogsTask(state *state.State) (task.Func, task.Schedule) { return expireLogs(ctx, state) } - op, err := operations.OperationCreate(state, "", operations.OperationClassTask, operationtype.LogsExpire, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(state.ShutdownCtx, operations.OperationClassTask, operationtype.LogsExpire, state.ServerName, state.Events, opRun, operations.ClusterOptions(state.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating log files expiry operation", logger.Ctx{"err": err}) return diff --git a/lxd/operations.go b/lxd/operations.go index a714c73a4cbc..0a22dd2234dc 100644 --- a/lxd/operations.go +++ b/lxd/operations.go @@ -1171,7 +1171,7 @@ func autoRemoveOrphanedOperationsTask(d *Daemon) (task.Func, task.Schedule) { return autoRemoveOrphanedOperations(ctx, s) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.RemoveOrphanedOperations, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.RemoveOrphanedOperations, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating remove orphaned operations operation", logger.Ctx{"err": err}) return diff --git a/lxd/operations/linux.go b/lxd/operations/linux.go index 09e728cbc528..1478d9e47305 100644 --- a/lxd/operations/linux.go +++ b/lxd/operations/linux.go @@ -4,28 +4,33 @@ package operations import ( "context" + "database/sql" "fmt" - "github.com/canonical/lxd/lxd/db" "github.com/canonical/lxd/lxd/db/cluster" "github.com/canonical/lxd/lxd/db/operationtype" "github.com/canonical/lxd/shared/api" ) func registerDBOperation(op *Operation, opType operationtype.Type) error { - if op.state == nil { + if op.transaction == nil { return nil } - err := op.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { + err := op.transaction(context.TODO(), func(ctx context.Context, tx *sql.Tx) error { + nodeID, err := cluster.GetNodeID(ctx, tx, op.location) + if err != nil { + return fmt.Errorf("Failed getting node ID: %w", err) + } + opInfo := cluster.Operation{ UUID: op.id, Type: opType, - NodeID: tx.GetNodeID(), + NodeID: nodeID, } if op.projectName != "" { - projectID, err := cluster.GetProjectID(ctx, tx.Tx(), op.projectName) + projectID, err := cluster.GetProjectID(ctx, tx, op.projectName) if err != nil { return fmt.Errorf("Fetch project ID: %w", err) } @@ -33,7 +38,7 @@ func registerDBOperation(op *Operation, opType operationtype.Type) error { opInfo.ProjectID = &projectID } - _, err := cluster.CreateOrReplaceOperation(ctx, tx.Tx(), opInfo) + _, err = cluster.CreateOrReplaceOperation(ctx, tx, opInfo) return err }) if err != nil { @@ -44,12 +49,12 @@ func registerDBOperation(op *Operation, opType operationtype.Type) error { } func removeDBOperation(op *Operation) error { - if op.state == nil { + if op.transaction == nil { return nil } - err := op.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { - return cluster.DeleteOperation(ctx, tx.Tx(), op.id) + err := op.transaction(context.TODO(), func(ctx context.Context, tx *sql.Tx) error { + return cluster.DeleteOperation(ctx, tx, op.id) }) return err diff --git a/lxd/operations/notlinux.go b/lxd/operations/notlinux.go index 505a2f9558be..d358e419e8b6 100644 --- a/lxd/operations/notlinux.go +++ b/lxd/operations/notlinux.go @@ -10,7 +10,7 @@ import ( ) func registerDBOperation(op *Operation, opType operationtype.Type) error { - if op.state != nil { + if op.transaction != nil { return fmt.Errorf("registerDBOperation not supported on this platform") } @@ -18,7 +18,7 @@ func registerDBOperation(op *Operation, opType operationtype.Type) error { } func removeDBOperation(op *Operation) error { - if op.state != nil { + if op.transaction != nil { return fmt.Errorf("registerDBOperation not supported on this platform") } diff --git a/lxd/operations/operations.go b/lxd/operations/operations.go index f7138a4d02f3..97d55f169f7e 100644 --- a/lxd/operations/operations.go +++ b/lxd/operations/operations.go @@ -2,6 +2,8 @@ package operations import ( "context" + "database/sql" + "errors" "fmt" "net/http" "sync" @@ -14,7 +16,6 @@ import ( "github.com/canonical/lxd/lxd/events" "github.com/canonical/lxd/lxd/request" "github.com/canonical/lxd/lxd/response" - "github.com/canonical/lxd/lxd/state" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" "github.com/canonical/lxd/shared/cancel" @@ -123,21 +124,81 @@ type Operation struct { // Locking for concurent access to the Operation lock sync.Mutex - state *state.State - events *events.Server + shutdownCtx context.Context + location string + transaction func(ctx context.Context, f func(context.Context, *sql.Tx) error) error + events *events.Server +} + +// Opts contains optional fields of an operation. +type Opts struct { + projectName string + resources map[string][]api.URL + transaction func(ctx context.Context, f func(context.Context, *sql.Tx) error) error + metadata any + request *http.Request + onCancel func(*Operation) error + onConnect func(*Operation, *http.Request, http.ResponseWriter) error +} + +// Options returns an empty Opts. It can be used with the LXD agent. +func Options() *Opts { + return &Opts{} +} + +// ClusterOptions expects a transaction hook. This should be used for all LXD Daemon operations. +func ClusterOptions(transaction func(ctx context.Context, f func(context.Context, *sql.Tx) error) error) *Opts { + return &Opts{ + transaction: transaction, + } +} + +// WithProjectName sets the project name for the operation. +func (o *Opts) WithProjectName(projectName string) *Opts { + o.projectName = projectName + return o +} + +// WithResources sets the operation resources. +func (o *Opts) WithResources(resources map[string][]api.URL) *Opts { + o.resources = resources + return o +} + +// WithMetadata sets the operation metadata. +func (o *Opts) WithMetadata(metadata any) *Opts { + o.metadata = metadata + return o +} + +// WithRequest sets the request. +func (o *Opts) WithRequest(r *http.Request) *Opts { + o.request = r + return o +} + +// WithOnCancel sets the onCancel hook. +func (o *Opts) WithOnCancel(onCancel func(*Operation) error) *Opts { + o.onCancel = onCancel + return o +} + +// WithOnConnect sets the onConnect hook. +func (o *Opts) WithOnConnect(onConnect func(*Operation, *http.Request, http.ResponseWriter) error) *Opts { + o.onConnect = onConnect + return o } // OperationCreate creates a new operation and returns it. If it cannot be // created, it returns an error. -func OperationCreate(s *state.State, projectName string, opClass OperationClass, opType operationtype.Type, opResources map[string][]api.URL, opMetadata any, onRun func(*Operation) error, onCancel func(*Operation) error, onConnect func(*Operation, *http.Request, http.ResponseWriter) error, r *http.Request) (*Operation, error) { +func OperationCreate(shutdownCtx context.Context, opClass OperationClass, opType operationtype.Type, location string, eventsServer *events.Server, onRun func(*Operation) error, opts *Opts) (*Operation, error) { // Don't allow new operations when LXD is shutting down. - if s != nil && s.ShutdownCtx.Err() == context.Canceled { + if shutdownCtx != nil && errors.Is(shutdownCtx.Err(), context.Canceled) { return nil, fmt.Errorf("LXD is shutting down") } // Main attributes op := Operation{} - op.projectName = projectName op.id = uuid.New().String() op.description = opType.Description() op.entityType, op.entitlement = opType.Permission() @@ -147,26 +208,33 @@ func OperationCreate(s *state.State, projectName string, opClass OperationClass, op.updatedAt = op.createdAt op.status = api.Pending op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id) - op.resources = opResources op.finished = cancel.New(context.Background()) - op.state = s op.logger = logger.AddContext(logger.Ctx{"operation": op.id, "project": op.projectName, "class": op.class.String(), "description": op.description}) + op.SetEventServer(eventsServer) + op.location = location + + if opts != nil { + op.projectName = opts.projectName + op.resources = opts.resources + op.transaction = opts.transaction + var newMetadata map[string]any + if opts.metadata != nil { + var err error + newMetadata, err = shared.ParseMetadata(opts.metadata) + if err != nil { + return nil, err + } - if s != nil { - op.SetEventServer(s.Events) - } - - newMetadata, err := shared.ParseMetadata(opMetadata) - if err != nil { - return nil, err + op.metadata = newMetadata + } } - op.metadata = newMetadata - // Callback functions op.onRun = onRun - op.onCancel = onCancel - op.onConnect = onConnect + if opts != nil { + op.onCancel = opts.onCancel + op.onConnect = opts.onConnect + } // Quick check. if op.class != OperationClassWebsocket && op.onConnect != nil { @@ -186,15 +254,15 @@ func OperationCreate(s *state.State, projectName string, opClass OperationClass, } // Set requestor if request was provided. - if r != nil { - op.SetRequestor(r) + if opts != nil && opts.request != nil { + op.SetRequestor(opts.request) } operationsLock.Lock() operations[op.id] = &op operationsLock.Unlock() - err = registerDBOperation(&op, opType) + err := registerDBOperation(&op, opType) if err != nil { return nil, err } @@ -248,13 +316,8 @@ func (op *Operation) done() { op.lock.Unlock() go func() { - shutdownCtx := context.Background() - if op.state != nil { - shutdownCtx = op.state.ShutdownCtx - } - select { - case <-shutdownCtx.Done(): + case <-op.shutdownCtx.Done(): return // Expect all operation records to be removed by waitForOperations in one query. case <-time.After(time.Second * 5): // Wait 5s before removing from internal map and database. } @@ -269,7 +332,7 @@ func (op *Operation) done() { delete(operations, op.id) operationsLock.Unlock() - if op.state == nil { + if op.transaction == nil { return } @@ -510,10 +573,7 @@ func (op *Operation) Render() (string, *api.Operation, error) { Resources: renderedResources, Metadata: op.metadata, MayCancel: op.mayCancel(), - } - - if op.state != nil { - retOp.Location = op.state.ServerName + Location: op.location, } if op.err != nil { diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go index da061c307617..9298be0d7656 100644 --- a/lxd/storage_volumes.go +++ b/lxd/storage_volumes.go @@ -1184,7 +1184,8 @@ func doCustomVolumeRefresh(s *state.State, r *http.Request, requestProjectName s return nil } - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeCopy, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeCopy, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -1238,7 +1239,8 @@ func doVolumeCreateOrCopy(s *state.State, r *http.Request, requestProjectName st } // Volume copy operations potentially take a long time, so run as an async operation. - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeCopy, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeCopy, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -1311,17 +1313,21 @@ func doVolumeMigration(s *state.State, r *http.Request, requestProjectName strin return nil } - var op *operations.Operation + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + var operationType operationtype.Type + var operationClass operations.OperationClass if push { - op, err = operations.OperationCreate(s, requestProjectName, operations.OperationClassWebsocket, operationtype.VolumeCreate, resources, sink.Metadata(), run, nil, sink.Connect, r) - if err != nil { - return response.InternalError(err) - } + operationClass = operations.OperationClassWebsocket + operationType = operationtype.VolumeCreate + operationOpts = operationOpts.WithMetadata(sink.Metadata()).WithOnConnect(sink.Connect) } else { - op, err = operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeCopy, resources, nil, run, nil, nil, r) - if err != nil { - return response.InternalError(err) - } + operationClass = operations.OperationClassTask + operationType = operationtype.VolumeCopy + } + + op, err := operations.OperationCreate(s.ShutdownCtx, operationClass, operationType, s.ServerName, s.Events, run, operationOpts) + if err != nil { + return response.InternalError(err) } return operations.OperationResponse(op) @@ -1566,7 +1572,8 @@ func storagePoolVolumePost(d *Daemon, r *http.Request) response.Response { resources := map[string][]api.URL{} resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", "custom", details.volumeName)} - op, err := operations.OperationCreate(s, effectiveProjectName, operations.OperationClassTask, operationtype.VolumeMigrate, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(effectiveProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeMigrate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -1778,7 +1785,8 @@ func storageVolumePostClusteringMigrate(s *state.State, r *http.Request, srcPool return nil } - srcOp, err := operations.OperationCreate(s, srcProjectName, operations.OperationClassWebsocket, operationtype.VolumeMigrate, resources, srcMigration.Metadata(), run, cancel, srcMigration.Connect, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(srcProjectName).WithResources(resources).WithMetadata(srcMigration.Metadata()).WithOnCancel(cancel).WithOnConnect(srcMigration.Connect).WithRequest(r) + srcOp, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassWebsocket, operationtype.VolumeMigrate, s.ServerName, s.Events, run, operationOpts) if err != nil { return err } @@ -1837,9 +1845,10 @@ func storagePoolVolumeTypePostMigration(state *state.State, r *http.Request, req return ws.DoStorage(state, projectName, poolName, volumeName, op) } + operationOpts := operations.ClusterOptions(state.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) if req.Target != nil { // Push mode. - op, err := operations.OperationCreate(state, requestProjectName, operations.OperationClassTask, operationtype.VolumeMigrate, resources, nil, run, nil, nil, r) + op, err := operations.OperationCreate(state.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeMigrate, state.ServerName, state.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -1848,7 +1857,8 @@ func storagePoolVolumeTypePostMigration(state *state.State, r *http.Request, req } // Pull mode. - op, err := operations.OperationCreate(state, requestProjectName, operations.OperationClassWebsocket, operationtype.VolumeMigrate, resources, ws.Metadata(), run, nil, ws.Connect, r) + operationOpts = operationOpts.WithMetadata(ws.Metadata()).WithOnConnect(ws.Connect) + op, err := operations.OperationCreate(state.ShutdownCtx, operations.OperationClassWebsocket, operationtype.VolumeMigrate, state.ServerName, state.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -1936,7 +1946,8 @@ func storagePoolVolumeTypePostMove(s *state.State, r *http.Request, poolName str return nil } - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeMove, nil, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeMove, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -2481,7 +2492,8 @@ func createStoragePoolVolumeFromISO(s *state.State, r *http.Request, requestProj resources := map[string][]api.URL{} resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", pool, "volumes", "custom", volName)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeCreate, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeCreate, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } @@ -2647,7 +2659,8 @@ func createStoragePoolVolumeFromBackup(s *state.State, r *http.Request, requestP resources := map[string][]api.URL{} resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", bInfo.Pool, "volumes", string(bInfo.Type), bInfo.Name)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.CustomVolumeBackupRestore, resources, nil, run, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.CustomVolumeBackupRestore, s.ServerName, s.Events, run, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/storage_volumes_backup.go b/lxd/storage_volumes_backup.go index c841abd00b0e..a6938d43e2cc 100644 --- a/lxd/storage_volumes_backup.go +++ b/lxd/storage_volumes_backup.go @@ -392,7 +392,8 @@ func storagePoolVolumeTypeCustomBackupsPost(d *Daemon, r *http.Request) response resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName)} resources["backups"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName, "backups", req.Name)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.CustomVolumeBackupCreate, resources, nil, backup, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.CustomVolumeBackupCreate, s.ServerName, s.Events, backup, operationOpts) if err != nil { return response.InternalError(err) } @@ -596,7 +597,8 @@ func storagePoolVolumeTypeCustomBackupPost(d *Daemon, r *http.Request) response. resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName)} resources["backups"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName, "backups", oldName)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.CustomVolumeBackupRename, resources, nil, rename, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.CustomVolumeBackupRename, s.ServerName, s.Events, rename, operationOpts) if err != nil { return response.InternalError(err) } @@ -692,7 +694,8 @@ func storagePoolVolumeTypeCustomBackupDelete(d *Daemon, r *http.Request) respons resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName)} resources["backups"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName, "backups", backupName)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.CustomVolumeBackupRemove, resources, nil, remove, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.CustomVolumeBackupRemove, s.ServerName, s.Events, remove, operationOpts) if err != nil { return response.InternalError(err) } diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go index f464a708c592..c5eb64c6f1db 100644 --- a/lxd/storage_volumes_snapshot.go +++ b/lxd/storage_volumes_snapshot.go @@ -230,7 +230,8 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) response.Res resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName)} resources["storage_volume_snapshots"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName, "snapshots", req.Name)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeSnapshotCreate, resources, nil, snapshot, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeSnapshotCreate, s.ServerName, s.Events, snapshot, operationOpts) if err != nil { return response.InternalError(err) } @@ -530,7 +531,8 @@ func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) response.Resp resources := map[string][]api.URL{} resources["storage_volume_snapshots"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName, "snapshots", snapshotName)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeSnapshotRename, resources, nil, snapshotRename, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeSnapshotRename, s.ServerName, s.Events, snapshotRename, operationOpts) if err != nil { return response.InternalError(err) } @@ -978,7 +980,8 @@ func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) response.Re resources := map[string][]api.URL{} resources["storage_volume_snapshots"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", details.pool.Name(), "volumes", details.volumeTypeName, details.volumeName, "snapshots", snapshotName)} - op, err := operations.OperationCreate(s, requestProjectName, operations.OperationClassTask, operationtype.VolumeSnapshotDelete, resources, nil, snapshotDelete, nil, nil, r) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL).WithProjectName(requestProjectName).WithResources(resources).WithRequest(r) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeSnapshotDelete, s.ServerName, s.Events, snapshotDelete, operationOpts) if err != nil { return response.InternalError(err) } @@ -1154,7 +1157,8 @@ func pruneExpiredAndAutoCreateCustomVolumeSnapshotsTask(d *Daemon) (task.Func, t return pruneExpiredCustomVolumeSnapshots(ctx, s, expiredSnapshots) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.CustomVolumeSnapshotsExpire, nil, nil, opRun, nil, nil, nil) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.CustomVolumeSnapshotsExpire, s.ServerName, s.Events, opRun, operationOpts) if err != nil { logger.Error("Failed creating expired custom volume snapshots prune operation", logger.Ctx{"err": err}) } else { @@ -1179,7 +1183,8 @@ func pruneExpiredAndAutoCreateCustomVolumeSnapshotsTask(d *Daemon) (task.Func, t return autoCreateCustomVolumeSnapshots(ctx, s, volumes) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.VolumeSnapshotCreate, nil, nil, opRun, nil, nil, nil) + operationOpts := operations.ClusterOptions(s.DB.Cluster.TransactionSQL) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.VolumeSnapshotCreate, s.ServerName, s.Events, opRun, operationOpts) if err != nil { logger.Error("Failed creating scheduled volume snapshot operation", logger.Ctx{"err": err}) } else { diff --git a/lxd/tokens.go b/lxd/tokens.go index bd457d22dc97..f3b5fffa87f5 100644 --- a/lxd/tokens.go +++ b/lxd/tokens.go @@ -43,7 +43,7 @@ func autoRemoveExpiredTokens(ctx context.Context, s *state.State) { return nil } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.RemoveExpiredTokens, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.RemoveExpiredTokens, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating remove expired tokens operation", logger.Ctx{"err": err}) return diff --git a/lxd/warnings.go b/lxd/warnings.go index ad1eb8ad99de..57f57a586eb9 100644 --- a/lxd/warnings.go +++ b/lxd/warnings.go @@ -444,7 +444,7 @@ func pruneResolvedWarningsTask(d *Daemon) (task.Func, task.Schedule) { return pruneResolvedWarnings(ctx, s) } - op, err := operations.OperationCreate(s, "", operations.OperationClassTask, operationtype.WarningsPruneResolved, nil, nil, opRun, nil, nil, nil) + op, err := operations.OperationCreate(s.ShutdownCtx, operations.OperationClassTask, operationtype.WarningsPruneResolved, s.ServerName, s.Events, opRun, operations.ClusterOptions(s.DB.Cluster.TransactionSQL)) if err != nil { logger.Error("Failed creating prune resolved warnings operation", logger.Ctx{"err": err}) return diff --git a/test/godeps/lxd-agent.list b/test/godeps/lxd-agent.list index b42e34f8f304..72bbf36f5192 100644 --- a/test/godeps/lxd-agent.list +++ b/test/godeps/lxd-agent.list @@ -13,7 +13,6 @@ github.com/canonical/lxd/lxd/metrics github.com/canonical/lxd/lxd/operations github.com/canonical/lxd/lxd/request github.com/canonical/lxd/lxd/response -github.com/canonical/lxd/lxd/state github.com/canonical/lxd/lxd/storage/filesystem github.com/canonical/lxd/lxd/storage/memorypipe github.com/canonical/lxd/lxd/ucred