From c0067c1125133dcaf85a540282ebfd0476fc2758 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Thu, 9 Oct 2025 16:23:41 -0700 Subject: [PATCH 01/28] Starting transfer orchestrator refactor in earnest. --- transfers/dispatcher.go | 110 +++++++ transfers/errors.go | 77 +++++ transfers/store.go | 57 ++++ transfers/transfers.go | 697 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 941 insertions(+) create mode 100644 transfers/dispatcher.go create mode 100644 transfers/errors.go create mode 100644 transfers/store.go create mode 100644 transfers/transfers.go diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go new file mode 100644 index 00000000..3f748890 --- /dev/null +++ b/transfers/dispatcher.go @@ -0,0 +1,110 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "fmt" + "log/slog" + + "github.com/google/uuid" +) + +type dispatcherState struct { + Channels dispatcherChannels +} + +type dispatcherChannels struct { + RequestTransfer chan Specification // used by client to create a new transfer + FetchTransferId chan uuid.UUID // returns task ID to client + CancelTransfer chan uuid.UUID // used by client to cancel a transfer + RequestStatus chan uuid.UUID // used by client to request transfer status + FetchStatus chan TransferStatus // returns task status to client + Error chan error // internal -> client error propagation + Stop chan struct{} // used by client to stop task management +} + +// dispatcher global state +var dispatcher dispatcherState + +func startDispatcher() error { + dispatcher.Channels = dispatcherChannels{ + RequestTransfer: make(chan Specification, 32), + FetchTransferId: make(chan uuid.UUID, 32), + CancelTransfer: make(chan uuid.UUID, 32), + RequestStatus: make(chan uuid.UUID, 32), + FetchStatus: make(chan TransferStatus, 32), + Error: make(chan error, 32), + Stop: make(chan struct{}), + } + go dispatcher_() + + return nil +} + +// This goroutine handles all client interactions, sending data along channels to internal +// goroutines as needed. +func dispatcher_() { + + // client input channels + var newTransferRequested <-chan Specification = dispatcher.Channels.RequestTransfer + var cancellationRequested <-chan uuid.UUID = dispatcher.Channels.CancelTransfer + var statusRequested <-chan uuid.UUID = dispatcher.Channels.RequestStatus + var stopRequested <-chan struct{} = dispatcher.Channels.Stop + + // client output channels + var returnTransferId chan<- uuid.UUID = dispatcher.Channels.FetchTransferId + var returnStatus chan<- TransferStatus = dispatcher.Channels.FetchStatus + var returnError chan<- error = dispatcher.Channels.Error + + // respond to client requests + running := true + for running { + select { + case spec := <-newTransferRequested: + transferId, numFiles, err := createNewTransfer(spec) + if err != nil { + returnError <- err + break + } + returnTransferId <- transferId + slog.Info(fmt.Sprintf("Created new transfer task %s (%d file(s) requested)", + transferId.String(), numFiles)) + + case transferId := <-cancellationRequested: + if err := cancelTransfer(transferId); err != nil { + slog.Error(fmt.Sprintf("Transfer %s: %s", transferId.String(), err.Error())) + returnError <- err + } + case transferId := <-statusRequested: + status, err := getTransferStatus(transferId) + if err != nil { + returnError <- err + break + } + returnStatus <- status + case <-stopRequested: + err := stopStore() + returnError <- err + running = false + } + } +} diff --git a/transfers/errors.go b/transfers/errors.go new file mode 100644 index 00000000..c93d517d --- /dev/null +++ b/transfers/errors.go @@ -0,0 +1,77 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "fmt" + + "github.com/google/uuid" + + "github.com/kbase/dts/config" +) + +// indicates that a transfer is sought but not found +type NotFoundError struct { + Id uuid.UUID +} + +func (t NotFoundError) Error() string { + return fmt.Sprintf("The transfer %s was not found.", t.Id.String()) +} + +// indicates that Start() has been called when tasks are being processed +type AlreadyRunningError struct{} + +func (t AlreadyRunningError) Error() string { + return fmt.Sprintf("Transfer orchestration is already running and cannot be started again.") +} + +// indicates that Stop() has been called when tasks are not being processed +type NotRunningError struct{} + +func (t NotRunningError) Error() string { + return fmt.Sprintf("Transfers are not currently being processed.") +} + +// indicates that no databases are currently available +type NoDatabasesAvailable struct{} + +func (t NoDatabasesAvailable) Error() string { + return fmt.Sprintf("No databases are currently available for transfer.") +} + +// indicates that a transfer has been requested with no files(!) +type NoFilesRequestedError struct{} + +func (t NoFilesRequestedError) Error() string { + return fmt.Sprintf("Requested transfer includes no file IDs!") +} + +// indicates that a payload has been requested that is too large +type PayloadTooLargeError struct { + Size float64 // size of the requested payload in gigabytes +} + +func (e PayloadTooLargeError) Error() string { + return fmt.Sprintf("Requested transfer payload is too large: %g GB (limit is %g GB).", + e.Size, config.Service.MaxPayloadSize) +} diff --git a/transfers/store.go b/transfers/store.go new file mode 100644 index 00000000..97fc28a0 --- /dev/null +++ b/transfers/store.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "github.com/google/uuid" +) + +func startStore() error { + return nil +} + +func stopStore() error { + return nil +} + +func createNewTransfer(spec Specification) (uuid.UUID, int, error) { + var transferId uuid.UUID + var numFiles int + return transferId, numFiles, nil +} + +func cancelTransfer(transferId uuid.UUID) error { + return nil +} + +func getTransferStatus(transferId uuid.UUID) (TransferStatus, error) { + var status TransferStatus + var err error + return status, err +} + +// goroutine for transfer data store +func store() { + running := true + for running { + } +} diff --git a/transfers/transfers.go b/transfers/transfers.go new file mode 100644 index 00000000..15b27be2 --- /dev/null +++ b/transfers/transfers.go @@ -0,0 +1,697 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "bytes" + "encoding/gob" + "errors" + "fmt" + "io/fs" + "log/slog" + "os" + "path/filepath" + "time" + + "github.com/google/uuid" + + "github.com/kbase/dts/auth" + "github.com/kbase/dts/config" + "github.com/kbase/dts/databases" + "github.com/kbase/dts/databases/jdp" + "github.com/kbase/dts/databases/kbase" + "github.com/kbase/dts/databases/nmdc" + "github.com/kbase/dts/endpoints" + "github.com/kbase/dts/endpoints/globus" + "github.com/kbase/dts/endpoints/local" + "github.com/kbase/dts/journal" +) + +// useful type aliases +type Database = databases.Database +type Endpoint = endpoints.Endpoint +type FileTransfer = endpoints.FileTransfer +type TransferStatus = endpoints.TransferStatus + +// useful constants +const ( + TransferStatusUnknown = endpoints.TransferStatusUnknown + TransferStatusStaging = endpoints.TransferStatusStaging + TransferStatusActive = endpoints.TransferStatusActive + TransferStatusFailed = endpoints.TransferStatusFailed + TransferStatusFinalizing = endpoints.TransferStatusFinalizing + TransferStatusInactive = endpoints.TransferStatusInactive + TransferStatusSucceeded = endpoints.TransferStatusSucceeded +) + +// starts processing transfers according to the given configuration, returning an +// informative error if anything prevents this +func Start() error { + if orchestrator.Running { + return &AlreadyRunningError{} + } + + // if this is the first call to Start(), register our built-in endpoint and database providers + if !orchestrator.Started { + if err := registerEndpointProviders(); err != nil { + return err + } + if err := registerDatabases(); err != nil { + return err + } + orchestrator.Started = true + } + + // do the necessary directories exist, and are they writable/readable? + if err := validateDirectories(); err != nil { + return err + } + + // can we access the local endpoint? + if _, err := endpoints.NewEndpoint(config.Service.Endpoint); err != nil { + return err + } + + // fire up the transfer journal + if err := journal.Init(); err != nil { + return err + } + + startOrchestration() + + // okay, we're running now + orchestrator.Running = true + + return nil +} + +// Stops processing tasks. Adding new tasks and requesting task statuses are +// disallowed in a stopped state. +func Stop() error { + var err error + if global.Running { + global.Channels.Stop <- struct{}{} + err = <-global.Channels.Error + if err != nil { + return err + } + err = journal.Finalize() + if err != nil { + return err + } + global.Running = false + } else { + err = &NotRunningError{} + } + return err +} + +// Returns true if tasks are currently being processed, false if not. +func Running() bool { + return global.Running +} + +// this type holds a specification used to create a valid transfer task +type Specification struct { + // a Markdown description of the transfer task + Description string + // the name of destination database to which files are transferred (as + // specified in the DTS config file) OR a custom destination spec (::) + Destination string + // machine-readable instructions for processing the payload at its destination + Instructions map[string]any + // an array of identifiers for files to be transferred from Source to Destination + FileIds []string + // the name of source database from which files are transferred (as specified + // in the DTS config file) + Source string + // information about the user requesting the task + User auth.User +} + +// Creates a new transfer task associated with the user with the specified Orcid +// ID to the manager's set, returning a UUID for the task. The task is defined +// by specifying the names of the source and destination databases and a set of +// file IDs associated with the source. +func Create(spec Specification) (uuid.UUID, error) { + var taskId uuid.UUID + + // have we requested files to be transferred? + if len(spec.FileIds) == 0 { + return taskId, &NoFilesRequestedError{} + } + + // verify the source and destination strings + _, err := databases.NewDatabase(spec.Source) // source must refer to a database + if err != nil { + return taskId, err + } + + // destination can be a database OR a custom location + if _, err = databases.NewDatabase(spec.Destination); err != nil { + if _, err = endpoints.ParseCustomSpec(spec.Destination); err != nil { + return taskId, err + } + } + + // create a new task and send it along for processing + global.Channels.Client.RequestTransfer <- spec + select { + case taskId = <-global.Channels.Client.FetchTransferId: + case err = <-global.Channels.Client.Error: + } + + return taskId, err +} + +// Given a task UUID, returns its transfer status (or a non-nil error +// indicating any issues encountered). +func Status(taskId uuid.UUID) (TransferStatus, error) { + var status TransferStatus + var err error + global.Channels.Client.RequestStatus <- taskId + select { + case status = <-global.Channels.Client.FetchStatus: + case err = <-global.Channels.Client.Error: + } + return status, err +} + +// Requests that the task with the given UUID be canceled. Clients should check +// the status of the task separately. +func Cancel(taskId uuid.UUID) error { + var err error + global.Channels.Client.CancelTransfer <- taskId + select { // default block provides non-blocking error check + case err = <-global.Channels.Client.Error: + default: + } + return err +} + +//=========== +// Internals +//=========== + +//----------------------------------------------- +// Provider Registration and Resource Validation +//----------------------------------------------- + +func registerEndpointProviders() error { + // NOTE: it's okay if these endpoint providers have already been registered, + // NOTE: as they can be used in testing + err := endpoints.RegisterEndpointProvider("globus", globus.NewEndpointFromConfig) + if err == nil { + err = endpoints.RegisterEndpointProvider("local", local.NewEndpoint) + } + if err != nil { + if _, matches := err.(*endpoints.AlreadyRegisteredError); !matches { + return err + } + } + return nil +} + +// registers databases; if at least one database is available, no error is propagated +func registerDatabases() error { + numAvailable := 0 + if _, found := config.Databases["jdp"]; found { + if err := databases.RegisterDatabase("jdp", jdp.NewDatabase); err != nil { + slog.Error(err.Error()) + } else { + numAvailable++ + } + } + if _, found := config.Databases["kbase"]; found { + if err := databases.RegisterDatabase("kbase", kbase.NewDatabase); err != nil { + slog.Error(err.Error()) + } else { + numAvailable++ + } + } + if _, found := config.Databases["nmdc"]; found { + if err := databases.RegisterDatabase("nmdc", nmdc.NewDatabase); err != nil { + slog.Error(err.Error()) + } else { + numAvailable++ + } + } + if numAvailable == 0 { + return &NoDatabasesAvailable{} + } + return nil +} + +func validateDirectories() error { + err := validateDirectory("data", config.Service.DataDirectory) + if err != nil { + return err + } + return validateDirectory("manifest", config.Service.ManifestDirectory) +} + +// checks for the existence of a directory and whether it is readable/writeable, returning an error +// if these conditions are not met +func validateDirectory(dirType, dir string) error { + if dir == "" { + return fmt.Errorf("no %s directory was specified!", dirType) + } + info, err := os.Stat(dir) + if err != nil { + return err + } + if !info.IsDir() { + return &os.PathError{ + Op: "validateDirectory", + Path: dir, + Err: fmt.Errorf("%s is not a valid %s directory!", dir, dirType), + } + } + + // can we write a file and read it? + testFile := filepath.Join(dir, "test.txt") + writtenTestData := []byte("test") + err = os.WriteFile(testFile, writtenTestData, 0644) + if err != nil { + return &os.PathError{ + Op: "validateDirectory", + Path: dir, + Err: fmt.Errorf("Could not write to %s directory %s!", dirType, dir), + } + } + readTestData, err := os.ReadFile(testFile) + if err == nil { + os.Remove(testFile) + } + if err != nil || !bytes.Equal(readTestData, writtenTestData) { + return &os.PathError{ + Op: "validateDirectory", + Path: dir, + Err: fmt.Errorf("Could not read from %s directory %s!", dirType, dir), + } + } + return nil +} + +//------------------------ +// Transfer Orchestration +//------------------------ + +// The DTS orchestrates data transfer by requesting operations from service providers and monitoring +// their status. Transfers and status checks are handled by a family of goroutines that communicate +// with each other and the main goroutine via channels. These goroutines include: +// +// * dispatch: handles all client requests, communicates with other goroutines as needed +// * stageFiles: handles file staging by communicating with provider databases and endpoints +// * transferFiles: handles file transfers by communicatig with provider databases and endpoints +// * sendManifests: generates a transfer manifest after each transfer has completed and sends it to +// the correct destination +// +// Transfer Datastore +// +// Transfer information is stored in a centralized data store maintained by a goroutine +// named transferStore, which offers a simple API to create, update, and fetch information +// (see data_store.go): +// +// * createNewTransfer() -> (transferId, numFiles, error) +// * cancelTransfer() -> error +// * getTransferStatus(transferId) -> (TransferStatus, error) +// * stopTransfers() -> error + +type orchestratorState struct { + Channels orchestratorChannels + Running, Started bool +} + +type orchestratorChannels struct { + Dispatcher dispatcherChannels + Stager stagerChannels + Mover moverChannels + Manifestor manifestorChannels + Store storeChannels +} + +var orchestrator orchestratorState + +// channels used internally by non-main goroutines +type internalChannels struct { + StageFiles chan []any // sends file descriptors to stageFiles + TransferFiles chan []any // sends file descriptors to transferFiles + GenerateManifest chan []any // sends file descriptors IDs to sendManifests + UpdateStatus chan transferInfo // sends status updates to monitorTransfers +} + +func startOrchestration() { + orchestrator.Channels.Dispatcher = startDispatcher() + orchestrator.Channels.Stager = startStager() + orchestrator.Channels.Mover = startMover() + orchestrator.Channels.Manifestor = startManifestor() + orchestrator.Channels.Store = startStore() +} + +func stopOrchestration() error { + if err := stopDispatcher(); err != nil { + return err + } + if err := stopStager(); err != nil { + return err + } + if err := startMover(); err != nil { + return err + } + if err := startManifestor(); err != nil { + return err + } + return startStore() +} + +func stageFiles() { + for global.Running { + } +} + +// this goroutine handles the transfer of file payloads +func transferFiles() { + for global.Running { + } +} + +// this goroutine accepts a set of file descriptors from the transferFiles goroutine, generating a +// manifest from it and sending it along to its destination and updating the transfer status +func sendManifests() { + for global.Running { + } +} + +// This goroutine maintains transfer statuses, accepting updates from other goroutines and managing +// state transitions. +func updateStatuses() { + // create or recreate a persistent table of transfer-related tasks + dataStore := filepath.Join(config.Service.DataDirectory, "dts.gob") + tasks := createOrLoadTasks(dataStore) + + // parse the task channels into directional types as needed + var createTaskChan <-chan transferTask = taskChannels.CreateTask + var cancelTaskChan <-chan uuid.UUID = taskChannels.CancelTask + var getTaskStatusChan <-chan uuid.UUID = taskChannels.GetTaskStatus + var returnTaskIdChan chan<- uuid.UUID = taskChannels.ReturnTaskId + var returnTaskStatusChan chan<- TransferStatus = taskChannels.ReturnTaskStatus + var errorChan chan<- error = taskChannels.Error + var pollChan <-chan struct{} = taskChannels.Poll + var stopChan <-chan struct{} = taskChannels.Stop + + // the task deletion period is specified in seconds + deleteAfter := time.Duration(config.Service.DeleteAfter) * time.Second + + // start scurrying around + running := true + for running { + select { + case newTask := <-createTaskChan: // Create() called + newTask.Id = uuid.New() + newTask.StartTime = time.Now() + tasks[newTask.Id] = newTask + returnTaskIdChan <- newTask.Id + slog.Info(fmt.Sprintf("Created new transfer task %s (%d file(s) requested)", + newTask.Id.String(), len(newTask.FileIds))) + case taskId := <-cancelTaskChan: // Cancel() called + if task, found := tasks[taskId]; found { + slog.Info(fmt.Sprintf("Task %s: received cancellation request", taskId.String())) + err := task.Cancel() + if err != nil { + task.Status.Code = TransferStatusUnknown + task.Status.Message = fmt.Sprintf("error in cancellation: %s", err.Error()) + task.CompletionTime = time.Now() + slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), task.Status.Message)) + tasks[task.Id] = task + } + } else { + err := &NotFoundError{Id: taskId} + errorChan <- err + } + case taskId := <-getTaskStatusChan: // Status() called + if task, found := tasks[taskId]; found { + returnTaskStatusChan <- task.Status + } else { + err := &NotFoundError{Id: taskId} + errorChan <- err + } + case <-pollChan: // time to move things along + for taskId, task := range tasks { + if !task.Completed() { + oldStatus := task.Status + err := task.Update() + if err != nil { + // We log task update errors but do not propagate them. All + // task errors result in a failed status. + task.Status.Code = TransferStatusFailed + task.Status.Message = err.Error() + task.CompletionTime = time.Now() + slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), err.Error())) + } + if task.Status.Code != oldStatus.Code { + switch task.Status.Code { + case TransferStatusStaging: + slog.Info(fmt.Sprintf("Task %s: staging %d file(s) (%g GB)", + task.Id.String(), len(task.FileIds), task.PayloadSize)) + case TransferStatusActive: + slog.Info(fmt.Sprintf("Task %s: beginning transfer (%d file(s), %g GB)", + task.Id.String(), len(task.FileIds), task.PayloadSize)) + case TransferStatusInactive: + slog.Info(fmt.Sprintf("Task %s: suspended transfer", task.Id.String())) + case TransferStatusFinalizing: + slog.Info(fmt.Sprintf("Task %s: finalizing transfer", task.Id.String())) + case TransferStatusSucceeded: + slog.Info(fmt.Sprintf("Task %s: completed successfully", task.Id.String())) + case TransferStatusFailed: + slog.Info(fmt.Sprintf("Task %s: failed", task.Id.String())) + err := journal.RecordTransfer(journal.Record{ + Id: task.Id, + Source: task.Source, + Destination: task.Destination, + Orcid: task.User.Orcid, + StartTime: task.StartTime, + StopTime: time.Now(), + Status: "failed", + PayloadSize: int64(1024 * 1024 * 1024 * task.PayloadSize), // GB -> B + NumFiles: len(task.FileIds), + }) + if err != nil { + slog.Error(err.Error()) + } + } + } + } + + // if the task completed a long enough time go, delete its entry + if task.Age() > deleteAfter { + slog.Debug(fmt.Sprintf("Task %s: purging transfer record", task.Id.String())) + delete(tasks, taskId) + } else { // update its entry + tasks[taskId] = task + } + } + case <-stopChan: // Stop() called + err := saveTasks(tasks, dataStore) // don't forget to save our state! + errorChan <- err + running = false + } + } +} + +// loads a map of task IDs to tasks from a previously saved file if available, +// or creates an empty map if no such file is available or valid +func createOrLoadTasks(dataFile string) map[uuid.UUID]transferTask { + file, err := os.Open(dataFile) + if err != nil { + return make(map[uuid.UUID]transferTask) + } + slog.Debug(fmt.Sprintf("Found previous tasks in %s.", dataFile)) + defer file.Close() + enc := gob.NewDecoder(file) + var tasks map[uuid.UUID]transferTask + var databaseStates databases.DatabaseSaveStates + if err = enc.Decode(&tasks); err == nil { + err = enc.Decode(&databaseStates) + } + if err != nil { // file not readable + slog.Error(fmt.Sprintf("Reading task file %s: %s", dataFile, err.Error())) + return make(map[uuid.UUID]transferTask) + } + if err = databases.Load(databaseStates); err != nil { + slog.Error(fmt.Sprintf("Restoring database states: %s", err.Error())) + } + slog.Debug(fmt.Sprintf("Restored %d tasks from %s", len(tasks), dataFile)) + return tasks +} + +// saves a map of task IDs to tasks to the given file +func saveTasks(tasks map[uuid.UUID]transferTask, dataFile string) error { + if len(tasks) > 0 { + slog.Debug(fmt.Sprintf("Saving %d tasks to %s", len(tasks), dataFile)) + file, err := os.OpenFile(dataFile, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return fmt.Errorf("Opening task file %s: %s", dataFile, err.Error()) + } + enc := gob.NewEncoder(file) + if err = enc.Encode(tasks); err == nil { + var databaseStates databases.DatabaseSaveStates + if databaseStates, err = databases.Save(); err == nil { + err = enc.Encode(databaseStates) + } + } + if err != nil { + file.Close() + os.Remove(dataFile) + return fmt.Errorf("Saving tasks: %s", err.Error()) + } + err = file.Close() + if err != nil { + os.Remove(dataFile) + return fmt.Errorf("Writing task file %s: %s", dataFile, err.Error()) + } + slog.Debug(fmt.Sprintf("Saved %d tasks to %s", len(tasks), dataFile)) + } else { + _, err := os.Stat(dataFile) + if !errors.Is(err, fs.ErrNotExist) { // file exists + os.Remove(dataFile) + } + } + return nil +} + +// this function runs in its own goroutine, using the given local endpoint +// for local file transfers, and the given channels to communicate with +// the main thread +func processTasks() { + // create or recreate a persistent table of transfer-related tasks + dataStore := filepath.Join(config.Service.DataDirectory, "dts.gob") + tasks := createOrLoadTasks(dataStore) + + // parse the task channels into directional types as needed + var createTaskChan <-chan transferTask = taskChannels.CreateTask + var cancelTaskChan <-chan uuid.UUID = taskChannels.CancelTask + var getTaskStatusChan <-chan uuid.UUID = taskChannels.GetTaskStatus + var returnTaskIdChan chan<- uuid.UUID = taskChannels.ReturnTaskId + var returnTaskStatusChan chan<- TransferStatus = taskChannels.ReturnTaskStatus + var errorChan chan<- error = taskChannels.Error + var pollChan <-chan struct{} = taskChannels.Poll + var stopChan <-chan struct{} = taskChannels.Stop + + // the task deletion period is specified in seconds + deleteAfter := time.Duration(config.Service.DeleteAfter) * time.Second + + // start scurrying around + running := true + for running { + select { + case newTask := <-createTaskChan: // Create() called + newTask.Id = uuid.New() + newTask.StartTime = time.Now() + tasks[newTask.Id] = newTask + returnTaskIdChan <- newTask.Id + slog.Info(fmt.Sprintf("Created new transfer task %s (%d file(s) requested)", + newTask.Id.String(), len(newTask.FileIds))) + case taskId := <-cancelTaskChan: // Cancel() called + if task, found := tasks[taskId]; found { + slog.Info(fmt.Sprintf("Task %s: received cancellation request", taskId.String())) + err := task.Cancel() + if err != nil { + task.Status.Code = TransferStatusUnknown + task.Status.Message = fmt.Sprintf("error in cancellation: %s", err.Error()) + task.CompletionTime = time.Now() + slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), task.Status.Message)) + tasks[task.Id] = task + } + } else { + err := &NotFoundError{Id: taskId} + errorChan <- err + } + case taskId := <-getTaskStatusChan: // Status() called + if task, found := tasks[taskId]; found { + returnTaskStatusChan <- task.Status + } else { + err := &NotFoundError{Id: taskId} + errorChan <- err + } + case <-pollChan: // time to move things along + for taskId, task := range tasks { + if !task.Completed() { + oldStatus := task.Status + err := task.Update() + if err != nil { + // We log task update errors but do not propagate them. All + // task errors result in a failed status. + task.Status.Code = TransferStatusFailed + task.Status.Message = err.Error() + task.CompletionTime = time.Now() + slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), err.Error())) + } + if task.Status.Code != oldStatus.Code { + switch task.Status.Code { + case TransferStatusStaging: + slog.Info(fmt.Sprintf("Task %s: staging %d file(s) (%g GB)", + task.Id.String(), len(task.FileIds), task.PayloadSize)) + case TransferStatusActive: + slog.Info(fmt.Sprintf("Task %s: beginning transfer (%d file(s), %g GB)", + task.Id.String(), len(task.FileIds), task.PayloadSize)) + case TransferStatusInactive: + slog.Info(fmt.Sprintf("Task %s: suspended transfer", task.Id.String())) + case TransferStatusFinalizing: + slog.Info(fmt.Sprintf("Task %s: finalizing transfer", task.Id.String())) + case TransferStatusSucceeded: + slog.Info(fmt.Sprintf("Task %s: completed successfully", task.Id.String())) + case TransferStatusFailed: + slog.Info(fmt.Sprintf("Task %s: failed", task.Id.String())) + err := journal.RecordTransfer(journal.Record{ + Id: task.Id, + Source: task.Source, + Destination: task.Destination, + Orcid: task.User.Orcid, + StartTime: task.StartTime, + StopTime: time.Now(), + Status: "failed", + PayloadSize: int64(1024 * 1024 * 1024 * task.PayloadSize), // GB -> B + NumFiles: len(task.FileIds), + }) + if err != nil { + slog.Error(err.Error()) + } + } + } + } + + // if the task completed a long enough time go, delete its entry + if task.Age() > deleteAfter { + slog.Debug(fmt.Sprintf("Task %s: purging transfer record", task.Id.String())) + delete(tasks, taskId) + } else { // update its entry + tasks[taskId] = task + } + } + case <-stopChan: // Stop() called + err := saveTasks(tasks, dataStore) // don't forget to save our state! + errorChan <- err + running = false + } + } +} From c1891548be3b12b64fa9e535f1b507a1833f70af Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Fri, 10 Oct 2025 18:17:46 -0700 Subject: [PATCH 02/28] Breaking tasks logic up into concurrent sequential programs. --- transfers/dispatcher.go | 74 +++++-- transfers/mover.go | 186 +++++++++++++++++ transfers/stager.go | 162 +++++++++++++++ transfers/store.go | 128 ++++++++++-- transfers/transfers.go | 448 ++++++---------------------------------- 5 files changed, 573 insertions(+), 425 deletions(-) create mode 100644 transfers/mover.go create mode 100644 transfers/stager.go diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 3f748890..0ab936b8 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -28,41 +28,73 @@ import ( "github.com/google/uuid" ) +//------------ +// Dispatcher +//------------ + +// dispatcher global state +var dispatcher dispatcherState + type dispatcherState struct { Channels dispatcherChannels } type dispatcherChannels struct { - RequestTransfer chan Specification // used by client to create a new transfer - FetchTransferId chan uuid.UUID // returns task ID to client - CancelTransfer chan uuid.UUID // used by client to cancel a transfer - RequestStatus chan uuid.UUID // used by client to request transfer status - FetchStatus chan TransferStatus // returns task status to client - Error chan error // internal -> client error propagation - Stop chan struct{} // used by client to stop task management -} + RequestTransfer chan Specification // used by client to create a new transfer + ReturnTransferId chan uuid.UUID // returns task ID to client -// dispatcher global state -var dispatcher dispatcherState + CancelTransfer chan uuid.UUID // used by client to cancel a transfer + + RequestStatus chan uuid.UUID // used by client to request transfer status + ReturnStatus chan TransferStatus // returns task status to client + + Error chan error // internal -> client error propagation + Stop chan struct{} // used by client to stop task management +} -func startDispatcher() error { - dispatcher.Channels = dispatcherChannels{ +func (d *dispatcherState) Start() error { + d.Channels = dispatcherChannels{ RequestTransfer: make(chan Specification, 32), - FetchTransferId: make(chan uuid.UUID, 32), + ReturnTransferId: make(chan uuid.UUID, 32), CancelTransfer: make(chan uuid.UUID, 32), RequestStatus: make(chan uuid.UUID, 32), - FetchStatus: make(chan TransferStatus, 32), + ReturnStatus: make(chan TransferStatus, 32), Error: make(chan error, 32), Stop: make(chan struct{}), } - go dispatcher_() + go d.process() return nil } +func (d *dispatcherState) Stop() error { + d.Channels.Stop <- struct{}{} + return <-d.Channels.Error +} + +func (d *dispatcherState) CreateTransfer(spec Specification) (uuid.UUID, error) { + d.Channels.RequestTransfer <- spec + select { + case id := <-d.Channels.ReturnTransferId: + return id, nil + case err := <-d.Channels.Error: + return uuid.UUID{}, err + } +} + +func (d *dispatcherState) GetTransferStatus(id uuid.UUID) (TransferStatus, error) { + d.Channels.RequestStatus <- id + select { + case status := <-d.Channels.ReturnStatus: + return status, nil + case err := <-d.Channels.Error: + return TransferStatus{}, err + } +} + // This goroutine handles all client interactions, sending data along channels to internal // goroutines as needed. -func dispatcher_() { +func (d *dispatcherState) process() { // client input channels var newTransferRequested <-chan Specification = dispatcher.Channels.RequestTransfer @@ -71,8 +103,8 @@ func dispatcher_() { var stopRequested <-chan struct{} = dispatcher.Channels.Stop // client output channels - var returnTransferId chan<- uuid.UUID = dispatcher.Channels.FetchTransferId - var returnStatus chan<- TransferStatus = dispatcher.Channels.FetchStatus + var returnTransferId chan<- uuid.UUID = dispatcher.Channels.ReturnTransferId + var returnStatus chan<- TransferStatus = dispatcher.Channels.ReturnStatus var returnError chan<- error = dispatcher.Channels.Error // respond to client requests @@ -80,7 +112,7 @@ func dispatcher_() { for running { select { case spec := <-newTransferRequested: - transferId, numFiles, err := createNewTransfer(spec) + transferId, numFiles, err := store.NewTransfer(spec) if err != nil { returnError <- err break @@ -95,15 +127,13 @@ func dispatcher_() { returnError <- err } case transferId := <-statusRequested: - status, err := getTransferStatus(transferId) + status, err := store.GetStatus(transferId) if err != nil { returnError <- err break } returnStatus <- status case <-stopRequested: - err := stopStore() - returnError <- err running = false } } diff --git a/transfers/mover.go b/transfers/mover.go new file mode 100644 index 00000000..415d2528 --- /dev/null +++ b/transfers/mover.go @@ -0,0 +1,186 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "errors" + "fmt" + "time" + + "github.com/google/uuid" + + "github.com/kbase/dts/config" + "github.com/kbase/dts/endpoints" +) + +//------- +// Mover +//------- + +// The mover manages actual file transfer operations and cancellations. + +// mover global state +var mover moverState + +type moverState struct { + Channels moverChannels + Endpoints map[string]endpoints.Endpoint +} + +type moverChannels struct { + RequestMove chan moveRequest + ReturnMoveId chan uuid.UUID + + RequestStatus chan uuid.UUID + ReturnStatus chan TransferStatus + + Error chan error + Stop chan struct{} +} + +type moveRequest struct { + Descriptors []map[string]any + Source, Destination string +} + +// starts the mover +func (m *moverState) Start() error { + m.Channels = moverChannels{ + RequestMove: make(chan moveRequest, 32), + ReturnMoveId: make(chan uuid.UUID, 32), + RequestStatus: make(chan uuid.UUID, 32), + ReturnStatus: make(chan TransferStatus, 32), + Error: make(chan error, 32), + Stop: make(chan struct{}), + } + m.Endpoints = make(map[string]endpoints.Endpoint) + go m.process() + return nil +} + +// stops the store goroutine +func (m *moverState) Stop() error { + m.Channels.Stop <- struct{}{} + return <-m.Channels.Error +} + +func (m *moverState) Move(descriptors []map[string]any, source, destination string) (uuid.UUID, error) { + m.Channels.RequestMove <- moveRequest{ + Descriptors: descriptors, + Source: source, + Destination: destination, + } + select { + case id := <-mover.Channels.ReturnMoveId: + return id, nil + case err := <-mover.Channels.Error: + return uuid.UUID{}, err + } +} + +func (m *moverState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { + m.Channels.RequestStatus <- transferId + select { + case status := <-mover.Channels.ReturnStatus: + return status, nil + case err := <-mover.Channels.Error: + return TransferStatus{}, err + } +} + +//---------------------------------------------------- +// everything past here runs in the mover's goroutine +//---------------------------------------------------- + +// the goroutine itself +func (m *moverState) process() { + running := true + pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond + moves := make(map[uuid.UUID]moveRequest) + for running { + select { + case move := <-mover.Channels.RequestMove: + id, err := m.start(move) + if err != nil { + mover.Channels.Error <- err + } + moves[id] = move + mover.Channels.ReturnMoveId <- id + case id := <-mover.Channels.RequestStatus: + move, found := moves[id] + if !found { + mover.Channels.Error <- errors.New(fmt.Sprintf("Invalid move ID: %s", id.String())) + break + } + source := mover.Endpoints[move.Source] + status, err := source.Status(id) + if err != nil { + mover.Channels.Error <- err + break + } + mover.Channels.ReturnStatus <- status + case <-mover.Channels.Stop: + running = false + } + + time.Sleep(pollInterval) + } +} + +func (m *moverState) start(move moveRequest) (uuid.UUID, error) { + // obtain (and/or record) the properly-resolved source and destination endpoints + if _, found := mover.Endpoints[move.Source]; !found { + source, err := endpoints.NewEndpoint(move.Source) + if err != nil { + return uuid.UUID{}, err + } + mover.Endpoints[move.Source] = source + } + if _, found := mover.Endpoints[move.Destination]; !found { + destination, err := destinationEndpoint(move.Destination) + if err != nil { + return uuid.UUID{}, err + } + mover.Endpoints[move.Destination] = destination + } + + source := mover.Endpoints[move.Source] + destination := mover.Endpoints[move.Destination] + + // assemble file transfers from the descriptors + files := make([]endpoints.FileTransfer, len(move.Descriptors)) + for i, d := range move.Descriptors { + path := d["path"].(string) + destinationPath := destinationFolder(move.Destination, path) + files[i] = FileTransfer{ + SourcePath: path, + DestinationPath: destinationPath, + Hash: d["hash"].(string), + } + } + + id, err := source.Transfer(destination, files) + if err != nil { + return uuid.UUID{}, err + } + return id, nil +} diff --git a/transfers/stager.go b/transfers/stager.go new file mode 100644 index 00000000..c3fb8475 --- /dev/null +++ b/transfers/stager.go @@ -0,0 +1,162 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "errors" + "fmt" + "time" + + "github.com/google/uuid" + + "github.com/kbase/dts/config" + "github.com/kbase/dts/databases" + "github.com/kbase/dts/endpoints" +) + +//-------- +// Stager +//-------- + +// The stager coordinates the staging of files at a source endpoint in preparation for transfer. + +// stager global state +var stager stagerState + +type stagerState struct { + Channels stagerChannels + Endpoints map[string]endpoints.Endpoint +} + +type stagerChannels struct { + RequestStaging chan stagingRequest + ReturnStagingId chan uuid.UUID + + RequestStatus chan uuid.UUID + ReturnStatus chan TransferStatus + + Error chan error + Stop chan struct{} +} + +type stagingRequest struct { + Descriptors []map[string]any + Endpoint string +} + +// starts the stager +func (s *stagerState) Start() error { + s.Channels = stagerChannels{ + RequestStaging: make(chan stagingRequest, 32), + ReturnStagingId: make(chan uuid.UUID, 32), + RequestStatus: make(chan uuid.UUID, 32), + ReturnStatus: make(chan TransferStatus, 32), + Error: make(chan error, 32), + Stop: make(chan struct{}), + } + s.Endpoints = make(map[string]endpoints.Endpoint) + go s.process() + return nil +} + +// stops the store goroutine +func (s *stagerState) Stop() error { + s.Channels.Stop <- struct{}{} + return <-s.Channels.Error +} + +func (s *stagerState) StageFiles(descriptors []map[string]any, endpoint string) (uuid.UUID, error) { + s.Channels.RequestStaging <- stagingRequest{ + Descriptors: descriptors, + Endpoint: endpoint, + } + select { + case id := <-stager.Channels.ReturnStagingId: + return id, nil + case err := <-stager.Channels.Error: + return uuid.UUID{}, err + } +} + +func (s *stagerState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { + s.Channels.RequestStatus <- transferId + select { + case status := <-stager.Channels.ReturnStatus: + return status, nil + case err := <-stager.Channels.Error: + return TransferStatus{}, err + } +} + +//---------------------------------------------------- +// everything past here runs in the stager's goroutine +//---------------------------------------------------- + +// the goroutine itself +func (s *stagerState) process() { + running := true + pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond + stagings := make(map[uuid.UUID]stagingRequest) + for running { + select { + case staging := <-stager.Channels.RequestStaging: + id, err := s.start(staging) + if err != nil { + stager.Channels.Error <- err + } + stagings[id] = staging + stager.Channels.ReturnStagingId <- id + case id := <-stager.Channels.RequestStatus: + staging, found := stagings[id] + if !found { + stager.Channels.Error <- errors.New(fmt.Sprintf("Invalid staging ID: %s", id.String())) + break + } + source := stager.Endpoints[staging.Endpoint] + status, err := source.Status(id) + if err != nil { + stager.Channels.Error <- err + break + } + stager.Channels.ReturnStatus <- status + case <-stager.Channels.Stop: + running = false + } + + time.Sleep(pollInterval) + } +} + +func (s *stagerState) start(staging stagingRequest) (uuid.UUID, error) { + // assemble file IDs from the descriptors + fileIds := make([]string, len(staging.Descriptors)) + for i, d := range staging.Descriptors { + fileIds[i] = d["id"].(string) + } + + db, err := databases.NewDatabase(staging.Endpoint) + id, err := db.StageFiles(staging.Endpoint, fileIds) + if err != nil { + return uuid.UUID{}, err + } + return id, nil +} diff --git a/transfers/store.go b/transfers/store.go index 97fc28a0..a9d73d11 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -25,33 +25,135 @@ import ( "github.com/google/uuid" ) -func startStore() error { +//------- +// Store +//------- + +// The transfer metadata store maintains a table of active and completed transfers and all related +// metadata. The store only tracks the state of the transfers--it doesn't initiate any activity. + +// store global state +var store storeState + +type storeState struct { + Channels storeChannels +} + +type storeChannels struct { + RequestNewTransfer chan Specification + ReturnNewTransfer chan transferIdAndNumFiles + + SetStatus chan transferIdAndStatus + RequestStatus chan uuid.UUID + ReturnStatus chan TransferStatus + + RequestDescriptors chan uuid.UUID + ReturnDescriptors chan []map[string]any + + Error chan error + Stop chan struct{} +} + +type transferIdAndNumFiles struct { + Id uuid.UUID + NumFiles int +} + +type transferIdAndStatus struct { + Id uuid.UUID + Status TransferStatus +} + +// starts the store goroutine +func (s *storeState) Start() error { + s.Channels = storeChannels{ + RequestNewTransfer: make(chan Specification, 32), + ReturnNewTransfer: make(chan transferIdAndNumFiles, 32), + SetStatus: make(chan transferIdAndStatus, 32), + RequestStatus: make(chan uuid.UUID, 32), + ReturnStatus: make(chan TransferStatus, 32), + Error: make(chan error, 32), + Stop: make(chan struct{}), + } + go s.process() return nil } -func stopStore() error { +// stops the store goroutine +func (s *storeState) Stop() error { return nil } -func createNewTransfer(spec Specification) (uuid.UUID, int, error) { - var transferId uuid.UUID - var numFiles int - return transferId, numFiles, nil +// creates a new entry for a transfer within the store, populating it with relevant metadata +func (s *storeState) NewTransfer(spec Specification) (uuid.UUID, int, error) { + s.Channels.RequestNewTransfer <- spec + select { + case idAndNumFiles := <-store.Channels.ReturnNewTransfer: + return idAndNumFiles.Id, idAndNumFiles.NumFiles, nil + case err := <-store.Channels.Error: + return uuid.UUID{}, 0, err + } } -func cancelTransfer(transferId uuid.UUID) error { - return nil +func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) error { + s.Channels.SetStatus <- transferIdAndStatus{ + Id: transferId, + Status: status, + } + return <-store.Channels.Error } -func getTransferStatus(transferId uuid.UUID) (TransferStatus, error) { - var status TransferStatus - var err error - return status, err +func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { + s.Channels.RequestStatus <- transferId + select { + case status := <-store.Channels.ReturnStatus: + return status, nil + case err := <-store.Channels.Error: + return TransferStatus{}, err + } +} + +func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, error) { + store.Channels.RequestDescriptors <- transferId + select { + case descriptors := <-store.Channels.ReturnDescriptors: + return descriptors, nil + case err := <-store.Channels.Error: + return nil, err + } } // goroutine for transfer data store -func store() { +func (s *storeState) process() { running := true + transfers := make(map[uuid.UUID]transferStoreEntry) for running { + select { + case spec := <-store.Channels.RequestNewTransfer: + + case idAndStatus := <-store.Channels.SetStatus: + if transfer, found := transfers[idAndStatus.Id]; found { + transfer.Status = idAndStatus.Status + transfers[idAndStatus.Id] = transfer + } + case id := <-store.Channels.RequestStatus: + case <-store.Channels.Stop: + running = false + } } } + +// an entry in the transfer metadata store +type transferStoreEntry struct { + Destination string // name of destination database (in config) OR custom spec + Source string // name of source database (in config) + Status TransferStatus + Tasks []transferTaskEntry // single source -> destination transfer tasks +} + +// Transfers consist of one or more "tasks", each of which transfers files from a single source to a +// single destination portion of a transfer +type transferTaskEntry struct { + Source string // name of source endpoint (in config) + Destination string // name of destination endpoint (in config) OR custom spec +} diff --git a/transfers/transfers.go b/transfers/transfers.go index 15b27be2..f147af66 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -23,14 +23,11 @@ package transfers import ( "bytes" - "encoding/gob" - "errors" "fmt" - "io/fs" "log/slog" "os" "path/filepath" - "time" + "strings" "github.com/google/uuid" @@ -66,19 +63,19 @@ const ( // starts processing transfers according to the given configuration, returning an // informative error if anything prevents this func Start() error { - if orchestrator.Running { + if global.Running { return &AlreadyRunningError{} } // if this is the first call to Start(), register our built-in endpoint and database providers - if !orchestrator.Started { + if !global.Started { if err := registerEndpointProviders(); err != nil { return err } if err := registerDatabases(); err != nil { return err } - orchestrator.Started = true + global.Started = true } // do the necessary directories exist, and are they writable/readable? @@ -96,10 +93,11 @@ func Start() error { return err } - startOrchestration() + if err := startOrchestration(); err != nil { + return err + } - // okay, we're running now - orchestrator.Running = true + global.Running = true return nil } @@ -109,8 +107,7 @@ func Start() error { func Stop() error { var err error if global.Running { - global.Channels.Stop <- struct{}{} - err = <-global.Channels.Error + err := dispatcher.Stop() if err != nil { return err } @@ -174,44 +171,30 @@ func Create(spec Specification) (uuid.UUID, error) { } // create a new task and send it along for processing - global.Channels.Client.RequestTransfer <- spec - select { - case taskId = <-global.Channels.Client.FetchTransferId: - case err = <-global.Channels.Client.Error: - } - - return taskId, err + return dispatcher.CreateTransfer(spec) } // Given a task UUID, returns its transfer status (or a non-nil error // indicating any issues encountered). -func Status(taskId uuid.UUID) (TransferStatus, error) { - var status TransferStatus - var err error - global.Channels.Client.RequestStatus <- taskId - select { - case status = <-global.Channels.Client.FetchStatus: - case err = <-global.Channels.Client.Error: - } - return status, err +func Status(transferId uuid.UUID) (TransferStatus, error) { + return dispatcher.GetTransferStatus(transferId) } // Requests that the task with the given UUID be canceled. Clients should check // the status of the task separately. func Cancel(taskId uuid.UUID) error { - var err error - global.Channels.Client.CancelTransfer <- taskId - select { // default block provides non-blocking error check - case err = <-global.Channels.Client.Error: - default: - } - return err + return dispatcher.CancelTransfer(transferId) } //=========== // Internals //=========== +// globals +var global struct { + Running, Started bool +} + //----------------------------------------------- // Provider Registration and Resource Validation //----------------------------------------------- @@ -320,378 +303,63 @@ func validateDirectory(dirType, dir string) error { // their status. Transfers and status checks are handled by a family of goroutines that communicate // with each other and the main goroutine via channels. These goroutines include: // -// * dispatch: handles all client requests, communicates with other goroutines as needed -// * stageFiles: handles file staging by communicating with provider databases and endpoints -// * transferFiles: handles file transfers by communicatig with provider databases and endpoints -// * sendManifests: generates a transfer manifest after each transfer has completed and sends it to -// the correct destination -// -// Transfer Datastore -// -// Transfer information is stored in a centralized data store maintained by a goroutine -// named transferStore, which offers a simple API to create, update, and fetch information -// (see data_store.go): -// -// * createNewTransfer() -> (transferId, numFiles, error) -// * cancelTransfer() -> error -// * getTransferStatus(transferId) -> (TransferStatus, error) -// * stopTransfers() -> error - -type orchestratorState struct { - Channels orchestratorChannels - Running, Started bool -} - -type orchestratorChannels struct { - Dispatcher dispatcherChannels - Stager stagerChannels - Mover moverChannels - Manifestor manifestorChannels - Store storeChannels -} - -var orchestrator orchestratorState - -// channels used internally by non-main goroutines -type internalChannels struct { - StageFiles chan []any // sends file descriptors to stageFiles - TransferFiles chan []any // sends file descriptors to transferFiles - GenerateManifest chan []any // sends file descriptors IDs to sendManifests - UpdateStatus chan transferInfo // sends status updates to monitorTransfers -} - -func startOrchestration() { - orchestrator.Channels.Dispatcher = startDispatcher() - orchestrator.Channels.Stager = startStager() - orchestrator.Channels.Mover = startMover() - orchestrator.Channels.Manifestor = startManifestor() - orchestrator.Channels.Store = startStore() -} - -func stopOrchestration() error { - if err := stopDispatcher(); err != nil { +// * dispatcher: handles all client requests, communicates with other goroutines as needed +// * stager: handles file staging by communicating with provider databases and endpoints +// * mover: handles file transfers by communicatig with provider databases and endpoints +// * manifestor: generates a transfer manifest after each transfer has completed and sends it to +// the correct destination +// * store: maintains metadata records and status info for ongoing and completed transfers + +func startOrchestration() error { + if err := dispatcher.Start(); err != nil { return err } - if err := stopStager(); err != nil { + if err := stager.Start(); err != nil { return err } - if err := startMover(); err != nil { + if err := mover.Start(); err != nil { return err } - if err := startManifestor(); err != nil { + if err := manifestor.Start(); err != nil { return err } - return startStore() + return store.Start() } -func stageFiles() { - for global.Running { - } -} - -// this goroutine handles the transfer of file payloads -func transferFiles() { - for global.Running { - } -} - -// this goroutine accepts a set of file descriptors from the transferFiles goroutine, generating a -// manifest from it and sending it along to its destination and updating the transfer status -func sendManifests() { - for global.Running { - } -} - -// This goroutine maintains transfer statuses, accepting updates from other goroutines and managing -// state transitions. -func updateStatuses() { - // create or recreate a persistent table of transfer-related tasks - dataStore := filepath.Join(config.Service.DataDirectory, "dts.gob") - tasks := createOrLoadTasks(dataStore) - - // parse the task channels into directional types as needed - var createTaskChan <-chan transferTask = taskChannels.CreateTask - var cancelTaskChan <-chan uuid.UUID = taskChannels.CancelTask - var getTaskStatusChan <-chan uuid.UUID = taskChannels.GetTaskStatus - var returnTaskIdChan chan<- uuid.UUID = taskChannels.ReturnTaskId - var returnTaskStatusChan chan<- TransferStatus = taskChannels.ReturnTaskStatus - var errorChan chan<- error = taskChannels.Error - var pollChan <-chan struct{} = taskChannels.Poll - var stopChan <-chan struct{} = taskChannels.Stop - - // the task deletion period is specified in seconds - deleteAfter := time.Duration(config.Service.DeleteAfter) * time.Second - - // start scurrying around - running := true - for running { - select { - case newTask := <-createTaskChan: // Create() called - newTask.Id = uuid.New() - newTask.StartTime = time.Now() - tasks[newTask.Id] = newTask - returnTaskIdChan <- newTask.Id - slog.Info(fmt.Sprintf("Created new transfer task %s (%d file(s) requested)", - newTask.Id.String(), len(newTask.FileIds))) - case taskId := <-cancelTaskChan: // Cancel() called - if task, found := tasks[taskId]; found { - slog.Info(fmt.Sprintf("Task %s: received cancellation request", taskId.String())) - err := task.Cancel() - if err != nil { - task.Status.Code = TransferStatusUnknown - task.Status.Message = fmt.Sprintf("error in cancellation: %s", err.Error()) - task.CompletionTime = time.Now() - slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), task.Status.Message)) - tasks[task.Id] = task - } - } else { - err := &NotFoundError{Id: taskId} - errorChan <- err - } - case taskId := <-getTaskStatusChan: // Status() called - if task, found := tasks[taskId]; found { - returnTaskStatusChan <- task.Status - } else { - err := &NotFoundError{Id: taskId} - errorChan <- err - } - case <-pollChan: // time to move things along - for taskId, task := range tasks { - if !task.Completed() { - oldStatus := task.Status - err := task.Update() - if err != nil { - // We log task update errors but do not propagate them. All - // task errors result in a failed status. - task.Status.Code = TransferStatusFailed - task.Status.Message = err.Error() - task.CompletionTime = time.Now() - slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), err.Error())) - } - if task.Status.Code != oldStatus.Code { - switch task.Status.Code { - case TransferStatusStaging: - slog.Info(fmt.Sprintf("Task %s: staging %d file(s) (%g GB)", - task.Id.String(), len(task.FileIds), task.PayloadSize)) - case TransferStatusActive: - slog.Info(fmt.Sprintf("Task %s: beginning transfer (%d file(s), %g GB)", - task.Id.String(), len(task.FileIds), task.PayloadSize)) - case TransferStatusInactive: - slog.Info(fmt.Sprintf("Task %s: suspended transfer", task.Id.String())) - case TransferStatusFinalizing: - slog.Info(fmt.Sprintf("Task %s: finalizing transfer", task.Id.String())) - case TransferStatusSucceeded: - slog.Info(fmt.Sprintf("Task %s: completed successfully", task.Id.String())) - case TransferStatusFailed: - slog.Info(fmt.Sprintf("Task %s: failed", task.Id.String())) - err := journal.RecordTransfer(journal.Record{ - Id: task.Id, - Source: task.Source, - Destination: task.Destination, - Orcid: task.User.Orcid, - StartTime: task.StartTime, - StopTime: time.Now(), - Status: "failed", - PayloadSize: int64(1024 * 1024 * 1024 * task.PayloadSize), // GB -> B - NumFiles: len(task.FileIds), - }) - if err != nil { - slog.Error(err.Error()) - } - } - } - } - - // if the task completed a long enough time go, delete its entry - if task.Age() > deleteAfter { - slog.Debug(fmt.Sprintf("Task %s: purging transfer record", task.Id.String())) - delete(tasks, taskId) - } else { // update its entry - tasks[taskId] = task - } - } - case <-stopChan: // Stop() called - err := saveTasks(tasks, dataStore) // don't forget to save our state! - errorChan <- err - running = false - } - } -} - -// loads a map of task IDs to tasks from a previously saved file if available, -// or creates an empty map if no such file is available or valid -func createOrLoadTasks(dataFile string) map[uuid.UUID]transferTask { - file, err := os.Open(dataFile) - if err != nil { - return make(map[uuid.UUID]transferTask) +func stopOrchestration() error { + if err := dispatcher.Stop(); err != nil { + return err } - slog.Debug(fmt.Sprintf("Found previous tasks in %s.", dataFile)) - defer file.Close() - enc := gob.NewDecoder(file) - var tasks map[uuid.UUID]transferTask - var databaseStates databases.DatabaseSaveStates - if err = enc.Decode(&tasks); err == nil { - err = enc.Decode(&databaseStates) + if err := stager.Stop(); err != nil { + return err } - if err != nil { // file not readable - slog.Error(fmt.Sprintf("Reading task file %s: %s", dataFile, err.Error())) - return make(map[uuid.UUID]transferTask) + if err := mover.Stop(); err != nil { + return err } - if err = databases.Load(databaseStates); err != nil { - slog.Error(fmt.Sprintf("Restoring database states: %s", err.Error())) + if err := manifestor.Stop(); err != nil { + return err } - slog.Debug(fmt.Sprintf("Restored %d tasks from %s", len(tasks), dataFile)) - return tasks + return store.Stop() } -// saves a map of task IDs to tasks to the given file -func saveTasks(tasks map[uuid.UUID]transferTask, dataFile string) error { - if len(tasks) > 0 { - slog.Debug(fmt.Sprintf("Saving %d tasks to %s", len(tasks), dataFile)) - file, err := os.OpenFile(dataFile, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - return fmt.Errorf("Opening task file %s: %s", dataFile, err.Error()) - } - enc := gob.NewEncoder(file) - if err = enc.Encode(tasks); err == nil { - var databaseStates databases.DatabaseSaveStates - if databaseStates, err = databases.Save(); err == nil { - err = enc.Encode(databaseStates) - } - } - if err != nil { - file.Close() - os.Remove(dataFile) - return fmt.Errorf("Saving tasks: %s", err.Error()) - } - err = file.Close() - if err != nil { - os.Remove(dataFile) - return fmt.Errorf("Writing task file %s: %s", dataFile, err.Error()) - } - slog.Debug(fmt.Sprintf("Saved %d tasks to %s", len(tasks), dataFile)) - } else { - _, err := os.Stat(dataFile) - if !errors.Is(err, fs.ErrNotExist) { // file exists - os.Remove(dataFile) - } - } - return nil +// resolves the given destination (name) string, accounting for custom transfers +func destinationEndpoint(destination string) (endpoints.Endpoint, error) { + // everything's been validated at this point, so no need to check for errors + if strings.Contains(destination, ":") { // custom transfer spec + customSpec, _ := endpoints.ParseCustomSpec(destination) + endpointId, _ := uuid.Parse(customSpec.Id) + credential := config.Credentials[customSpec.Credential] + clientId, _ := uuid.Parse(credential.Id) + return globus.NewEndpoint("Custom endpoint", endpointId, customSpec.Path, clientId, credential.Secret) + } + return endpoints.NewEndpoint(config.Databases[destination].Endpoint) } -// this function runs in its own goroutine, using the given local endpoint -// for local file transfers, and the given channels to communicate with -// the main thread -func processTasks() { - // create or recreate a persistent table of transfer-related tasks - dataStore := filepath.Join(config.Service.DataDirectory, "dts.gob") - tasks := createOrLoadTasks(dataStore) - - // parse the task channels into directional types as needed - var createTaskChan <-chan transferTask = taskChannels.CreateTask - var cancelTaskChan <-chan uuid.UUID = taskChannels.CancelTask - var getTaskStatusChan <-chan uuid.UUID = taskChannels.GetTaskStatus - var returnTaskIdChan chan<- uuid.UUID = taskChannels.ReturnTaskId - var returnTaskStatusChan chan<- TransferStatus = taskChannels.ReturnTaskStatus - var errorChan chan<- error = taskChannels.Error - var pollChan <-chan struct{} = taskChannels.Poll - var stopChan <-chan struct{} = taskChannels.Stop - - // the task deletion period is specified in seconds - deleteAfter := time.Duration(config.Service.DeleteAfter) * time.Second - - // start scurrying around - running := true - for running { - select { - case newTask := <-createTaskChan: // Create() called - newTask.Id = uuid.New() - newTask.StartTime = time.Now() - tasks[newTask.Id] = newTask - returnTaskIdChan <- newTask.Id - slog.Info(fmt.Sprintf("Created new transfer task %s (%d file(s) requested)", - newTask.Id.String(), len(newTask.FileIds))) - case taskId := <-cancelTaskChan: // Cancel() called - if task, found := tasks[taskId]; found { - slog.Info(fmt.Sprintf("Task %s: received cancellation request", taskId.String())) - err := task.Cancel() - if err != nil { - task.Status.Code = TransferStatusUnknown - task.Status.Message = fmt.Sprintf("error in cancellation: %s", err.Error()) - task.CompletionTime = time.Now() - slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), task.Status.Message)) - tasks[task.Id] = task - } - } else { - err := &NotFoundError{Id: taskId} - errorChan <- err - } - case taskId := <-getTaskStatusChan: // Status() called - if task, found := tasks[taskId]; found { - returnTaskStatusChan <- task.Status - } else { - err := &NotFoundError{Id: taskId} - errorChan <- err - } - case <-pollChan: // time to move things along - for taskId, task := range tasks { - if !task.Completed() { - oldStatus := task.Status - err := task.Update() - if err != nil { - // We log task update errors but do not propagate them. All - // task errors result in a failed status. - task.Status.Code = TransferStatusFailed - task.Status.Message = err.Error() - task.CompletionTime = time.Now() - slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), err.Error())) - } - if task.Status.Code != oldStatus.Code { - switch task.Status.Code { - case TransferStatusStaging: - slog.Info(fmt.Sprintf("Task %s: staging %d file(s) (%g GB)", - task.Id.String(), len(task.FileIds), task.PayloadSize)) - case TransferStatusActive: - slog.Info(fmt.Sprintf("Task %s: beginning transfer (%d file(s), %g GB)", - task.Id.String(), len(task.FileIds), task.PayloadSize)) - case TransferStatusInactive: - slog.Info(fmt.Sprintf("Task %s: suspended transfer", task.Id.String())) - case TransferStatusFinalizing: - slog.Info(fmt.Sprintf("Task %s: finalizing transfer", task.Id.String())) - case TransferStatusSucceeded: - slog.Info(fmt.Sprintf("Task %s: completed successfully", task.Id.String())) - case TransferStatusFailed: - slog.Info(fmt.Sprintf("Task %s: failed", task.Id.String())) - err := journal.RecordTransfer(journal.Record{ - Id: task.Id, - Source: task.Source, - Destination: task.Destination, - Orcid: task.User.Orcid, - StartTime: task.StartTime, - StopTime: time.Now(), - Status: "failed", - PayloadSize: int64(1024 * 1024 * 1024 * task.PayloadSize), // GB -> B - NumFiles: len(task.FileIds), - }) - if err != nil { - slog.Error(err.Error()) - } - } - } - } - - // if the task completed a long enough time go, delete its entry - if task.Age() > deleteAfter { - slog.Debug(fmt.Sprintf("Task %s: purging transfer record", task.Id.String())) - delete(tasks, taskId) - } else { // update its entry - tasks[taskId] = task - } - } - case <-stopChan: // Stop() called - err := saveTasks(tasks, dataStore) // don't forget to save our state! - errorChan <- err - running = false - } +// resolves the folder at the given destination in which transferred files are deposited, with the +// given subfolder appended +func destinationFolder(destination, subfolder string) string { + if customSpec, err := endpoints.ParseCustomSpec(destination); err == nil { // custom transfer? + return filepath.Join(customSpec.Path, subfolder) } + return subfolder } From 552dcd08bcf4da5358e0403f552ef95f4aafbd4c Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 14 Oct 2025 16:24:52 -0700 Subject: [PATCH 03/28] Replacing tasks package with transfers package. The transfers package uses concurrent sequential programs to divide up the labor in transferring files. --- endpoints/endpoints.go | 2 +- endpoints/globus/endpoint.go | 5 +- endpoints/local/endpoint.go | 7 +- tasks/subtask.go | 8 +- tasks/task.go | 6 +- transfers/dispatcher.go | 79 +++++++++-- transfers/errors.go | 8 ++ transfers/manifestor.go | 254 +++++++++++++++++++++++++++++++++++ transfers/mover.go | 224 ++++++++++++++++++------------ transfers/stager.go | 155 +++++++++++---------- transfers/store.go | 123 ++++++++++++----- transfers/transfers.go | 52 ++++++- 12 files changed, 705 insertions(+), 218 deletions(-) create mode 100644 transfers/manifestor.go diff --git a/endpoints/endpoints.go b/endpoints/endpoints.go index ebbc7f7d..fdd38f7e 100644 --- a/endpoints/endpoints.go +++ b/endpoints/endpoints.go @@ -71,7 +71,7 @@ type Endpoint interface { Root() string // Returns true if the files associated with the given Frictionless // descriptors are staged at this endpoint AND are valid, false otherwise. - FilesStaged(files []any) (bool, error) + FilesStaged(descriptors []map[string]any) (bool, error) // Returns a list of UUIDs for all transfers associated with this endpoint. Transfers() ([]uuid.UUID, error) // Begins a transfer task that moves the files identified by the FileTransfer diff --git a/endpoints/globus/endpoint.go b/endpoints/globus/endpoint.go index f9f21e82..136b182e 100644 --- a/endpoints/globus/endpoint.go +++ b/endpoints/globus/endpoint.go @@ -142,11 +142,10 @@ func (ep *Endpoint) Root() string { return ep.RootDir } -func (ep *Endpoint) FilesStaged(files []any) (bool, error) { +func (ep *Endpoint) FilesStaged(descriptors []map[string]any) (bool, error) { // find all the directories in which these files reside filesInDir := make(map[string][]string) - for _, resource := range files { - descriptor := resource.(map[string]any) + for _, descriptor := range descriptors { dir, file := filepath.Split(descriptor["path"].(string)) dir = filepath.Join(ep.RootDir, dir) if _, found := filesInDir[dir]; !found { diff --git a/endpoints/local/endpoint.go b/endpoints/local/endpoint.go index 7fc71bc2..2d22d5c7 100644 --- a/endpoints/local/endpoint.go +++ b/endpoints/local/endpoint.go @@ -93,9 +93,8 @@ func (ep *Endpoint) Root() string { return ep.root } -func (ep *Endpoint) FilesStaged(files []any) (bool, error) { - for _, resource := range files { - descriptor := resource.(map[string]any) +func (ep *Endpoint) FilesStaged(descriptors []map[string]any) (bool, error) { + for _, descriptor := range descriptors { absPath := filepath.Join(ep.root, descriptor["path"].(string)) _, err := os.Stat(absPath) if err != nil { @@ -191,7 +190,7 @@ func (ep *Endpoint) Transfer(dst endpoints.Endpoint, files []endpoints.FileTrans } // first, we check that all requested files are staged on this endpoint - requestedFiles := make([]any, len(files)) + requestedFiles := make([]map[string]any, len(files)) for i, file := range files { requestedFiles[i] = map[string]any{ "path": file.SourcePath, // only the Path field is required diff --git a/tasks/subtask.go b/tasks/subtask.go index 3168e770..86894a95 100644 --- a/tasks/subtask.go +++ b/tasks/subtask.go @@ -41,7 +41,7 @@ import ( type transferSubtask struct { Destination string // name of destination database (in config) OR custom spec DestinationFolder string // folder path to which files are transferred - Descriptors []any // Frictionless file descriptors + Descriptors []map[string]any // Frictionless file descriptors Source string // name of source database (in config) SourceEndpoint string // name of source endpoint (in config) Staging uuid.NullUUID // staging UUID (if any) @@ -72,8 +72,7 @@ func (subtask *transferSubtask) start() error { return err } fileIds := make([]string, len(subtask.Descriptors)) - for i, d := range subtask.Descriptors { - descriptor := d.(map[string]any) + for i, descriptor := range subtask.Descriptors { fileIds[i] = descriptor["id"].(string) } taskId, err := source.StageFiles(subtask.User.Orcid, fileIds) @@ -195,8 +194,7 @@ func (subtask *transferSubtask) beginTransfer() error { len(subtask.Descriptors), subtask.SourceEndpoint, subtask.Destination)) // assemble a list of file transfers fileXfers := make([]FileTransfer, len(subtask.Descriptors)) - for i, d := range subtask.Descriptors { - descriptor := d.(map[string]any) + for i, descriptor := range subtask.Descriptors { path := descriptor["path"].(string) destinationPath := filepath.Join(subtask.DestinationFolder, path) fileXfers[i] = FileTransfer{ diff --git a/tasks/task.go b/tasks/task.go index 5ad839ac..6e0f0bdf 100644 --- a/tasks/task.go +++ b/tasks/task.go @@ -48,7 +48,7 @@ type transferTask struct { Canceled bool // set if a cancellation request has been made StartTime time.Time // time at which the transfer was requested CompletionTime time.Time // time at which the transfer completed - DataDescriptors []any // in-line data descriptors + DataDescriptors []map[string]any // in-line data descriptors Description string // Markdown description of the task Destination string // name of destination database (in config) OR custom spec DestinationFolder string // folder path to which files are transferred @@ -152,7 +152,7 @@ func (task *transferTask) start() error { for sourceEndpoint := range distinctEndpoints { // pick out the files corresponding to the source endpoint // NOTE: this is slow, but preserves file ID ordering - descriptorsForEndpoint := make([]any, 0) + descriptorsForEndpoint := make([]map[string]any, 0) for _, descriptor := range fileDescriptors { endpoint := descriptor["endpoint"].(string) if endpoint == sourceEndpoint { @@ -350,7 +350,7 @@ func (task transferTask) Completed() bool { // creates a DataPackage that serves as the transfer manifest func (task *transferTask) createManifest() (*datapackage.Package, error) { // gather all file and data descriptors - descriptors := make([]any, 0) + descriptors := make([]map[string]any, 0) for _, subtask := range task.Subtasks { descriptors = append(descriptors, subtask.Descriptors...) } diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 0ab936b8..4e38df72 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -26,6 +26,8 @@ import ( "log/slog" "github.com/google/uuid" + + "github.com/kbase/dts/endpoints" ) //------------ @@ -92,8 +94,16 @@ func (d *dispatcherState) GetTransferStatus(id uuid.UUID) (TransferStatus, error } } -// This goroutine handles all client interactions, sending data along channels to internal -// goroutines as needed. +func (d *dispatcherState) CancelTransfer(id uuid.UUID) error { + d.Channels.CancelTransfer <- id + return <-d.Channels.Error +} + +//---------------------------------------------------- +// everything past here runs in the dispatcher's goroutine +//---------------------------------------------------- + +// the goroutine itself func (d *dispatcherState) process() { // client input channels @@ -112,17 +122,16 @@ func (d *dispatcherState) process() { for running { select { case spec := <-newTransferRequested: - transferId, numFiles, err := store.NewTransfer(spec) + transferId, numFiles, err := d.create(spec) if err != nil { returnError <- err - break } returnTransferId <- transferId - slog.Info(fmt.Sprintf("Created new transfer task %s (%d file(s) requested)", - transferId.String(), numFiles)) - + slog.Info(fmt.Sprintf("Created new transfer %s (%d file(s) requested)", transferId.String(), + numFiles)) case transferId := <-cancellationRequested: - if err := cancelTransfer(transferId); err != nil { + slog.Info(fmt.Sprintf("Canceling transfer %s", transferId.String())) + if err := d.cancel(transferId); err != nil { slog.Error(fmt.Sprintf("Transfer %s: %s", transferId.String(), err.Error())) returnError <- err } @@ -138,3 +147,57 @@ func (d *dispatcherState) process() { } } } + +// creates a transfer from the given specification and starts things moving; returns a UUID for the +// transfer, the number of files in the payload, and/or an error +func (d *dispatcherState) create(spec Specification) (uuid.UUID, int, error) { + transferId, numFiles, err := store.NewTransfer(spec) + if err != nil { + return uuid.UUID{}, 0, err + } + descriptors, err := store.GetDescriptors(transferId) + if err != nil { + return uuid.UUID{}, 0, err + } + + // do we need to stage files for the source database? + filesStaged := true + descriptorsForEndpoint, err := descriptorsByEndpoint(spec, descriptors) + for source, descriptorsForSource := range descriptorsForEndpoint { + sourceEndpoint, err := endpoints.NewEndpoint(source) + if err != nil { + return uuid.UUID{}, 0, err + } + filesStaged, err = sourceEndpoint.FilesStaged(descriptorsForSource) + if err != nil { + return uuid.UUID{}, 0, err + } + if !filesStaged { + break + } + } + + if !filesStaged { + stager.StageFiles(transferId) + } else { + mover.MoveFiles(transferId) + } + + return transferId, numFiles, err +} + +func (d *dispatcherState) cancel(transferId uuid.UUID) error { + status, err := store.GetStatus(transferId) + if err != nil { + return err + } + switch status.Code { + case TransferStatusUnknown, TransferStatusSucceeded, TransferStatusFailed: + case TransferStatusStaging: + return stager.Cancel(transferId) + case TransferStatusActive, TransferStatusInactive: + return mover.Cancel(transferId) + case TransferStatusFinalizing: + return manifestor.Cancel(transferId) + } +} diff --git a/transfers/errors.go b/transfers/errors.go index c93d517d..47b22c54 100644 --- a/transfers/errors.go +++ b/transfers/errors.go @@ -75,3 +75,11 @@ func (e PayloadTooLargeError) Error() string { return fmt.Sprintf("Requested transfer payload is too large: %g GB (limit is %g GB).", e.Size, config.Service.MaxPayloadSize) } + +type TransferNotFoundError struct { + Id uuid.UUID +} + +func (e TransferNotFoundError) Error() string { + return fmt.Sprintf("Transfer not found: %s.", e.Id.String()) +} diff --git a/transfers/manifestor.go b/transfers/manifestor.go new file mode 100644 index 00000000..bee31789 --- /dev/null +++ b/transfers/manifestor.go @@ -0,0 +1,254 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "fmt" + "path/filepath" + "time" + + "github.com/frictionlessdata/datapackage-go/datapackage" + "github.com/google/uuid" + + "github.com/kbase/dts/config" + "github.com/kbase/dts/databases" + "github.com/kbase/dts/endpoints" +) + +//------- +// Mover +//------- + +// The manifestor generates a JSON manifest for each successful transfer and sends it to the +// transfer's destination. The manifest contains a Frictionless DataPackage containing all +// descriptors relevant to the transfer. + +// manifestor global state +var manifestor manifestorState + +type manifestorState struct { + Channels manifestorChannels + Endpoints map[string]endpoints.Endpoint +} + +type manifestorChannels struct { + RequestGeneration chan uuid.UUID + RequestCancellation chan uuid.UUID + Error chan error + Stop chan struct{} +} + +// starts the mover +func (m *manifestorState) Start() error { + m.Channels = manifestorChannels{ + RequestGeneration: make(chan uuid.UUID, 32), + RequestCancellation: make(chan uuid.UUID, 32), + Error: make(chan error, 32), + Stop: make(chan struct{}), + } + m.Endpoints = make(map[string]endpoints.Endpoint) + go m.process() + return nil +} + +// stops the manifestor goroutine +func (m *manifestorState) Stop() error { + m.Channels.Stop <- struct{}{} + return <-m.Channels.Error +} + +// starts generating a manifest for the given transfer, moving it subsequently to that transfer's +// destination +func (m *manifestorState) Generate(transferId uuid.UUID) error { + m.Channels.RequestGeneration <- transferId + return <-mover.Channels.Error +} + +// cancels the generation/transfer of a manifest +// destination +func (m *manifestorState) Cancel(transferId uuid.UUID) error { + m.Channels.RequestCancellation <- transferId + return <-mover.Channels.Error +} + +//---------------------------------------------------- +// everything past here runs in the mover's goroutine +//---------------------------------------------------- + +// the goroutine itself +func (m *manifestorState) process() { + running := true + pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond + manifestTransfers := make(map[uuid.UUID]uuid.UUID) + for running { + select { + case transferId := <-manifestor.Channels.RequestGeneration: + manifestXferId, err := m.generateAndSendManifest(transferId) + if err != nil { + manifestor.Channels.Error <- err + } + manifestTransfers[transferId] = manifestXferId + case transferId := <-manifestor.Channels.RequestCancellation: + if manifestXferId, found := manifestTransfers[transferId]; found { + err := m.cancel(manifestXferId) + if err == nil { + delete(manifestTransfers, transferId) + } + manifestor.Channels.Error <- err + } else { + manifestor.Channels.Error <- NotFoundError{Id: transferId} + } + case <-mover.Channels.Stop: + running = false + } + + time.Sleep(pollInterval) + + // monitor the manifest transfers + for transferId, manifestXferId := range manifestTransfers { + completed, err := m.updateStatus(transferId, manifestXferId) + if err != nil { + mover.Channels.Error <- err + continue + } + if completed { + delete(manifestTransfers, transferId) + } + } + } +} + +func (m *manifestorState) generateAndSendManifest(transferId uuid.UUID) (uuid.UUID, error) { + spec, err := store.GetSpecification(transferId) + if err != nil { + return uuid.UUID{}, err + } + manifest, err := m.generateManifest(transferId, spec) + if err != nil { + return uuid.UUID{}, err + } + + manifestFile := filepath.Join(config.Service.ManifestDirectory, fmt.Sprintf("manifest-%s.json", transferId.String())) + if err := manifest.SaveDescriptor(manifestFile); err != nil { + return uuid.UUID{}, fmt.Errorf("creating manifest file: %s", err.Error()) + } + + // begin transferring the manifest + source, err := endpoints.NewEndpoint(config.Service.Endpoint) + if err != nil { + return uuid.UUID{}, err + } + destination, err := destinationEndpoint(spec.Destination) + if err != nil { + return uuid.UUID{}, err + } + manifestXferId, err := source.Transfer(destination, []FileTransfer{ + { + SourcePath: manifestFile, + DestinationPath: filepath.Join(destinationFolder(spec.Destination), "manifest.json"), + }, + }) + if err != nil { + return uuid.UUID{}, fmt.Errorf("transferring manifest file: %s", err.Error()) + } + + status, err := store.GetStatus(transferId) + if err != nil { + return uuid.UUID{}, err + } + status.Code = TransferStatusFinalizing + return manifestXferId, store.SetStatus(transferId, status) +} + +// generates a manifest for the transfer with the given ID and begins transferring it to its +// destination +func (m *manifestorState) generateManifest(transferId uuid.UUID, spec Specification) (*datapackage.Package, error) { + descriptors, err := store.GetDescriptors(transferId) + if err != nil { + return nil, err + } + + user := map[string]any{ + "id": transferId.String(), + "title": spec.User.Name, + "role": "author", + } + if spec.User.Organization != "" { + user["organization"] = spec.User.Organization + } + if spec.User.Email != "" { + user["email"] = spec.User.Email + } + + // NOTE: for non-custom transfers, we embed the local username for the destination database in + // this record in case it's useful (e.g. for the KBase staging service) + var username string + if _, err := endpoints.ParseCustomSpec(spec.Destination); err != nil { // custom transfer? + destination, err := databases.NewDatabase(spec.Destination) + if err != nil { + return nil, err + } + username, err = destination.LocalUser(spec.User.Orcid) + if err != nil { + return nil, err + } + } + + packageDescriptor := map[string]any{ + "name": "manifest", + "resources": descriptors, + "created": time.Now().Format(time.RFC3339), + "profile": "data-package", + "keywords": []any{"dts", "manifest"}, + "contributors": []any{ + user, + }, + "description": spec.Description, + "instructions": spec.Instructions, + "username": username, + } + + return datapackage.New(packageDescriptor, ".") +} + +// update the status of the manifest transfer with the given ID, returning true if the transfer has +// completed (successfully or unsuccessfully), false otherwise +func (m *manifestorState) updateStatus(transferId, manifestXferId uuid.UUID) (bool, error) { + source, err := endpoints.NewEndpoint(config.Service.Endpoint) + if err != nil { + return false, err + } + status, err := source.Status(manifestXferId) + if err != nil { + return false, err + } + store.SetStatus(transferId, status) + return status.Code == TransferStatusSucceeded || status.Code == TransferStatusFailed, nil +} + +func (m *manifestorState) cancel(manifestXferId uuid.UUID) error { + endpoint, err := endpoints.NewEndpoint(config.Service.Endpoint) + if err != nil { + return err + } + return endpoint.Cancel(manifestXferId) +} diff --git a/transfers/mover.go b/transfers/mover.go index 415d2528..8ace593d 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -22,8 +22,7 @@ package transfers import ( - "errors" - "fmt" + "path/filepath" "time" "github.com/google/uuid" @@ -47,64 +46,40 @@ type moverState struct { } type moverChannels struct { - RequestMove chan moveRequest - ReturnMoveId chan uuid.UUID - - RequestStatus chan uuid.UUID - ReturnStatus chan TransferStatus - - Error chan error - Stop chan struct{} -} - -type moveRequest struct { - Descriptors []map[string]any - Source, Destination string + RequestMove chan uuid.UUID + RequestCancellation chan uuid.UUID + Error chan error + Stop chan struct{} } // starts the mover func (m *moverState) Start() error { m.Channels = moverChannels{ - RequestMove: make(chan moveRequest, 32), - ReturnMoveId: make(chan uuid.UUID, 32), - RequestStatus: make(chan uuid.UUID, 32), - ReturnStatus: make(chan TransferStatus, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), + RequestMove: make(chan uuid.UUID, 32), + Error: make(chan error, 32), + Stop: make(chan struct{}), } m.Endpoints = make(map[string]endpoints.Endpoint) go m.process() return nil } -// stops the store goroutine +// stops the mover goroutine func (m *moverState) Stop() error { m.Channels.Stop <- struct{}{} return <-m.Channels.Error } -func (m *moverState) Move(descriptors []map[string]any, source, destination string) (uuid.UUID, error) { - m.Channels.RequestMove <- moveRequest{ - Descriptors: descriptors, - Source: source, - Destination: destination, - } - select { - case id := <-mover.Channels.ReturnMoveId: - return id, nil - case err := <-mover.Channels.Error: - return uuid.UUID{}, err - } +// starts moving files associated with the given transfer ID +func (m *moverState) MoveFiles(transferId uuid.UUID) error { + m.Channels.RequestMove <- transferId + return <-mover.Channels.Error } -func (m *moverState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { - m.Channels.RequestStatus <- transferId - select { - case status := <-mover.Channels.ReturnStatus: - return status, nil - case err := <-mover.Channels.Error: - return TransferStatus{}, err - } +// cancels a file move operation +func (m *moverState) Cancel(transferId uuid.UUID) error { + m.Channels.RequestCancellation <- transferId + return <-mover.Channels.Error } //---------------------------------------------------- @@ -115,72 +90,151 @@ func (m *moverState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { func (m *moverState) process() { running := true pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond - moves := make(map[uuid.UUID]moveRequest) + moveOperations := make(map[uuid.UUID][]moveOperation) // a single transfer can be several move operations! for running { select { - case move := <-mover.Channels.RequestMove: - id, err := m.start(move) + case transferId := <-mover.Channels.RequestMove: + entries, err := m.start(transferId) if err != nil { mover.Channels.Error <- err } - moves[id] = move - mover.Channels.ReturnMoveId <- id - case id := <-mover.Channels.RequestStatus: - move, found := moves[id] - if !found { - mover.Channels.Error <- errors.New(fmt.Sprintf("Invalid move ID: %s", id.String())) - break - } - source := mover.Endpoints[move.Source] - status, err := source.Status(id) - if err != nil { + moveOperations[transferId] = entries + case transferId := <-mover.Channels.RequestCancellation: + if moves, found := moveOperations[transferId]; found { + err := m.cancel(moves) + if err == nil { + delete(moveOperations, transferId) + } mover.Channels.Error <- err - break + } else { + mover.Channels.Error <- NotFoundError{Id: transferId} } - mover.Channels.ReturnStatus <- status case <-mover.Channels.Stop: running = false } time.Sleep(pollInterval) + + // check the move statuses and advance as needed + for transferId, moves := range moveOperations { + completed, err := m.updateStatus(transferId, moves) + if err != nil { + mover.Channels.Error <- err + continue + } + if completed { + delete(moveOperations, transferId) + } + } } } -func (m *moverState) start(move moveRequest) (uuid.UUID, error) { - // obtain (and/or record) the properly-resolved source and destination endpoints - if _, found := mover.Endpoints[move.Source]; !found { - source, err := endpoints.NewEndpoint(move.Source) +type moveOperation struct { + Id uuid.UUID // move ID (distinct from transfer ID) + SourceEndpoint, DestinationEndpoint string + Completed bool +} + +// starts moving files for the transfer with the given ID, returning one or more move operations, +// depending on the number of relevant source endpoints +func (m *moverState) start(transferId uuid.UUID) ([]moveOperation, error) { + spec, err := store.GetSpecification(transferId) + if err != nil { + return nil, err + } + descriptors, err := store.GetDescriptors(transferId) + if err != nil { + return nil, err + } + + // start transfers for each endpoint + descriptorsForEndpoint, err := descriptorsByEndpoint(spec, descriptors) + moves := make([]moveOperation, 0) + for source, descriptorsForSource := range descriptorsForEndpoint { + files := make([]endpoints.FileTransfer, len(descriptorsForSource)) + for i, descriptor := range descriptorsForSource { + path := descriptor["path"].(string) + destinationPath := filepath.Join(destinationFolder(spec.Destination), path) + files[i] = endpoints.FileTransfer{ + SourcePath: path, + DestinationPath: destinationPath, + Hash: descriptor["hash"].(string), + } + } + sourceEndpoint, err := endpoints.NewEndpoint(source) if err != nil { - return uuid.UUID{}, err + return nil, err } - mover.Endpoints[move.Source] = source - } - if _, found := mover.Endpoints[move.Destination]; !found { - destination, err := destinationEndpoint(move.Destination) + destinationEndpoint, err := endpoints.NewEndpoint(spec.Destination) + if err != nil { + return nil, err + } + id, err := sourceEndpoint.Transfer(destinationEndpoint, files) if err != nil { - return uuid.UUID{}, err + return nil, err } - mover.Endpoints[move.Destination] = destination + moves = append(moves, moveOperation{ + Id: id, + SourceEndpoint: source, + DestinationEndpoint: spec.Destination, + }) } + return moves, nil +} + +// update the status of the transfer with the given ID given its distinct file move operations, +// returning true if the transfer has completed (successfully or unsuccessfully), false otherwise +func (m *moverState) updateStatus(transferId uuid.UUID, moves []moveOperation) (bool, error) { + var transferStatus TransferStatus - source := mover.Endpoints[move.Source] - destination := mover.Endpoints[move.Destination] - - // assemble file transfers from the descriptors - files := make([]endpoints.FileTransfer, len(move.Descriptors)) - for i, d := range move.Descriptors { - path := d["path"].(string) - destinationPath := destinationFolder(move.Destination, path) - files[i] = FileTransfer{ - SourcePath: path, - DestinationPath: destinationPath, - Hash: d["hash"].(string), + atLeastOneMoveFailed := false + movesAllSucceeded := true + for i, move := range moves { + source, err := endpoints.NewEndpoint(move.SourceEndpoint) + if err != nil { + return false, err + } + moveStatus, err := source.Status(transferId) + if err != nil { + return false, err + } + transferStatus.NumFiles += moveStatus.NumFiles + transferStatus.NumFilesTransferred += moveStatus.NumFilesTransferred + transferStatus.NumFilesSkipped += moveStatus.NumFilesSkipped + + if moveStatus.Code == TransferStatusSucceeded { + moves[i].Completed = true + } else { + movesAllSucceeded = false + if moveStatus.Code == TransferStatusFailed { + transferStatus.Message = moveStatus.Message + atLeastOneMoveFailed = true + moves[i].Completed = true + } } } - id, err := source.Transfer(destination, files) - if err != nil { - return uuid.UUID{}, err + // take stock and update + if movesAllSucceeded { + manifestor.Generate(transferId) + } else if atLeastOneMoveFailed { + transferStatus.Code = TransferStatusFailed + } + + return movesAllSucceeded || atLeastOneMoveFailed, store.SetStatus(transferId, transferStatus) +} + +func (m *moverState) cancel(moves []moveOperation) error { + var e error + for _, move := range moves { + endpoint, err := endpoints.NewEndpoint(move.SourceEndpoint) + if err != nil { + return err + } + err = endpoint.Cancel(move.Id) + if err != nil && e == nil { + e = err + } } - return id, nil + return e } diff --git a/transfers/stager.go b/transfers/stager.go index c3fb8475..042c3589 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -22,89 +22,62 @@ package transfers import ( - "errors" - "fmt" "time" "github.com/google/uuid" "github.com/kbase/dts/config" "github.com/kbase/dts/databases" - "github.com/kbase/dts/endpoints" ) //-------- // Stager //-------- -// The stager coordinates the staging of files at a source endpoint in preparation for transfer. +// The stager coordinates the staging of files at a source database in preparation for transfer. // stager global state var stager stagerState type stagerState struct { Channels stagerChannels - Endpoints map[string]endpoints.Endpoint + Databases map[string]databases.Database } type stagerChannels struct { - RequestStaging chan stagingRequest - ReturnStagingId chan uuid.UUID - - RequestStatus chan uuid.UUID - ReturnStatus chan TransferStatus - - Error chan error - Stop chan struct{} -} - -type stagingRequest struct { - Descriptors []map[string]any - Endpoint string + RequestStaging chan uuid.UUID + RequestCancellation chan uuid.UUID + Error chan error + Stop chan struct{} } // starts the stager func (s *stagerState) Start() error { s.Channels = stagerChannels{ - RequestStaging: make(chan stagingRequest, 32), - ReturnStagingId: make(chan uuid.UUID, 32), - RequestStatus: make(chan uuid.UUID, 32), - ReturnStatus: make(chan TransferStatus, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), + RequestStaging: make(chan uuid.UUID, 32), + Error: make(chan error, 32), + Stop: make(chan struct{}), } - s.Endpoints = make(map[string]endpoints.Endpoint) go s.process() return nil } -// stops the store goroutine +// stops the stager goroutine func (s *stagerState) Stop() error { s.Channels.Stop <- struct{}{} return <-s.Channels.Error } -func (s *stagerState) StageFiles(descriptors []map[string]any, endpoint string) (uuid.UUID, error) { - s.Channels.RequestStaging <- stagingRequest{ - Descriptors: descriptors, - Endpoint: endpoint, - } - select { - case id := <-stager.Channels.ReturnStagingId: - return id, nil - case err := <-stager.Channels.Error: - return uuid.UUID{}, err - } +// requests that files be staged for the transfer with the given ID +func (s *stagerState) StageFiles(id uuid.UUID) error { + s.Channels.RequestStaging <- id + return <-stager.Channels.Error } -func (s *stagerState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { - s.Channels.RequestStatus <- transferId - select { - case status := <-stager.Channels.ReturnStatus: - return status, nil - case err := <-stager.Channels.Error: - return TransferStatus{}, err - } +// cancels a file staging operation +func (s *stagerState) Cancel(transferId uuid.UUID) error { + s.Channels.RequestCancellation <- transferId + return <-stager.Channels.Error } //---------------------------------------------------- @@ -115,48 +88,88 @@ func (s *stagerState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { func (s *stagerState) process() { running := true pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond - stagings := make(map[uuid.UUID]stagingRequest) + stagings := make(map[uuid.UUID]stagingEntry) for running { select { - case staging := <-stager.Channels.RequestStaging: - id, err := s.start(staging) + case transferId := <-stager.Channels.RequestStaging: + entry, err := s.start(transferId) if err != nil { stager.Channels.Error <- err } - stagings[id] = staging - stager.Channels.ReturnStagingId <- id - case id := <-stager.Channels.RequestStatus: - staging, found := stagings[id] - if !found { - stager.Channels.Error <- errors.New(fmt.Sprintf("Invalid staging ID: %s", id.String())) - break + stagings[transferId] = entry + case transferId := <-mover.Channels.RequestCancellation: + if _, found := stagings[transferId]; found { + delete(stagings, transferId) // simply remove the entry and stop tracking file staging + stager.Channels.Error <- nil + } else { + stager.Channels.Error <- NotFoundError{Id: transferId} } - source := stager.Endpoints[staging.Endpoint] - status, err := source.Status(id) - if err != nil { - stager.Channels.Error <- err - break - } - stager.Channels.ReturnStatus <- status case <-stager.Channels.Stop: running = false } time.Sleep(pollInterval) + + // check the staging status and advance to a transfer if it's finished + for transferId, staging := range stagings { + if err := s.updateStatus(transferId, staging); err != nil { + stager.Channels.Error <- err + } + } } } -func (s *stagerState) start(staging stagingRequest) (uuid.UUID, error) { - // assemble file IDs from the descriptors - fileIds := make([]string, len(staging.Descriptors)) - for i, d := range staging.Descriptors { - fileIds[i] = d["id"].(string) +type stagingEntry struct { + Id uuid.UUID // staging ID (distinct from transfer ID) +} + +func (s *stagerState) start(transferId uuid.UUID) (stagingEntry, error) { + spec, err := store.GetSpecification(transferId) + if err != nil { + return stagingEntry{}, err + } + db, err := databases.NewDatabase(spec.Source) + if err != nil { + return stagingEntry{}, err + } + id, err := db.StageFiles(spec.User.Orcid, spec.FileIds) + if err != nil { + return stagingEntry{}, err + } + return stagingEntry{Id: id}, nil +} + +func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) error { + spec, err := store.GetSpecification(transferId) + if err != nil { + return err + } + source, err := databases.NewDatabase(spec.Source) + if err != nil { + return err } - db, err := databases.NewDatabase(staging.Endpoint) - id, err := db.StageFiles(staging.Endpoint, fileIds) + status, err := source.StagingStatus(staging.Id) if err != nil { - return uuid.UUID{}, err + return err + } + + if status == databases.StagingStatusSucceeded { + err := mover.MoveFiles(transferId) + if err != nil { + return err + } + } else if status == databases.StagingStatusFailed { + // FIXME: handle staging failures here! + } else { // still staging + xferStatus, err := store.GetStatus(transferId) + if err != nil { + return err + } + if xferStatus.Code != TransferStatusStaging { + xferStatus.Code = TransferStatusStaging + store.SetStatus(transferId, xferStatus) + } } - return id, nil + return nil } diff --git a/transfers/store.go b/transfers/store.go index a9d73d11..836d319f 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -23,6 +23,8 @@ package transfers import ( "github.com/google/uuid" + + "github.com/kbase/dts/databases" ) //------- @@ -41,24 +43,24 @@ type storeState struct { type storeChannels struct { RequestNewTransfer chan Specification - ReturnNewTransfer chan transferIdAndNumFiles + ReturnNewTransfer chan uuid.UUID + + RequestSpec chan uuid.UUID + ReturnSpec chan Specification + + RequestDescriptors chan uuid.UUID + ReturnDescriptors chan []map[string]any SetStatus chan transferIdAndStatus RequestStatus chan uuid.UUID ReturnStatus chan TransferStatus - RequestDescriptors chan uuid.UUID - ReturnDescriptors chan []map[string]any + RequestRemoval chan uuid.UUID Error chan error Stop chan struct{} } -type transferIdAndNumFiles struct { - Id uuid.UUID - NumFiles int -} - type transferIdAndStatus struct { Id uuid.UUID Status TransferStatus @@ -68,10 +70,15 @@ type transferIdAndStatus struct { func (s *storeState) Start() error { s.Channels = storeChannels{ RequestNewTransfer: make(chan Specification, 32), - ReturnNewTransfer: make(chan transferIdAndNumFiles, 32), + ReturnNewTransfer: make(chan uuid.UUID, 32), + RequestSpec: make(chan uuid.UUID, 32), + ReturnSpec: make(chan Specification, 32), + RequestDescriptors: make(chan uuid.UUID, 32), + ReturnDescriptors: make(chan []map[string]any, 32), SetStatus: make(chan transferIdAndStatus, 32), RequestStatus: make(chan uuid.UUID, 32), ReturnStatus: make(chan TransferStatus, 32), + RequestRemoval: make(chan uuid.UUID, 32), Error: make(chan error, 32), Stop: make(chan struct{}), } @@ -84,17 +91,38 @@ func (s *storeState) Stop() error { return nil } -// creates a new entry for a transfer within the store, populating it with relevant metadata +// creates a new entry for a transfer within the store, populating it with relevant metadata and +// returning a UUID, number of files, and/or error condition for the request func (s *storeState) NewTransfer(spec Specification) (uuid.UUID, int, error) { s.Channels.RequestNewTransfer <- spec select { - case idAndNumFiles := <-store.Channels.ReturnNewTransfer: - return idAndNumFiles.Id, idAndNumFiles.NumFiles, nil + case id := <-store.Channels.ReturnNewTransfer: + return id, len(spec.FileIds), nil case err := <-store.Channels.Error: return uuid.UUID{}, 0, err } } +func (s *storeState) GetSpecification(transferId uuid.UUID) (Specification, error) { + store.Channels.RequestSpec <- transferId + select { + case spec := <-store.Channels.ReturnSpec: + return spec, nil + case err := <-store.Channels.Error: + return Specification{}, err + } +} + +func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, error) { + store.Channels.RequestDescriptors <- transferId + select { + case descriptors := <-store.Channels.ReturnDescriptors: + return descriptors, nil + case err := <-store.Channels.Error: + return nil, err + } +} + func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) error { s.Channels.SetStatus <- transferIdAndStatus{ Id: transferId, @@ -113,30 +141,69 @@ func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { } } -func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, error) { - store.Channels.RequestDescriptors <- transferId - select { - case descriptors := <-store.Channels.ReturnDescriptors: - return descriptors, nil - case err := <-store.Channels.Error: - return nil, err - } +func (s *storeState) Remove(transferId uuid.UUID) error { + s.Channels.RequestRemoval <- transferId + return <-store.Channels.Error } -// goroutine for transfer data store +//---------------------------------------------------- +// everything past here runs in the store's goroutine +//---------------------------------------------------- + +// the goroutine itself func (s *storeState) process() { running := true transfers := make(map[uuid.UUID]transferStoreEntry) for running { select { case spec := <-store.Channels.RequestNewTransfer: - + id := uuid.New() + source, err := databases.NewDatabase(spec.Source) + if err != nil { + store.Channels.Error <- err + break + } + descriptors, err := source.Descriptors(spec.User.Orcid, spec.FileIds) + if err != nil { + store.Channels.Error <- err + break + } + transfers[id] = transferStoreEntry{ + Descriptors: descriptors, + Spec: spec, + } + store.Channels.ReturnNewTransfer <- id + case id := <-store.Channels.RequestDescriptors: + if transfer, found := transfers[id]; found { + store.Channels.ReturnDescriptors <- transfer.Descriptors + } else { + store.Channels.Error <- TransferNotFoundError{Id: id} + } + case id := <-store.Channels.RequestSpec: + if transfer, found := transfers[id]; found { + store.Channels.ReturnSpec <- transfer.Spec + } else { + store.Channels.Error <- TransferNotFoundError{Id: id} + } case idAndStatus := <-store.Channels.SetStatus: if transfer, found := transfers[idAndStatus.Id]; found { transfer.Status = idAndStatus.Status transfers[idAndStatus.Id] = transfer + } else { + store.Channels.Error <- TransferNotFoundError{Id: idAndStatus.Id} } case id := <-store.Channels.RequestStatus: + if transfer, found := transfers[id]; found { + store.Channels.ReturnStatus <- transfer.Status + } else { + store.Channels.Error <- TransferNotFoundError{Id: id} + } + case id := <-store.Channels.RequestRemoval: + if _, found := transfers[id]; found { + delete(transfers, id) + } else { + store.Channels.Error <- TransferNotFoundError{Id: id} + } case <-store.Channels.Stop: running = false } @@ -145,15 +212,7 @@ func (s *storeState) process() { // an entry in the transfer metadata store type transferStoreEntry struct { - Destination string // name of destination database (in config) OR custom spec - Source string // name of source database (in config) + Descriptors []map[string]any + Spec Specification Status TransferStatus - Tasks []transferTaskEntry // single source -> destination transfer tasks -} - -// Transfers consist of one or more "tasks", each of which transfers files from a single source to a -// single destination portion of a transfer -type transferTaskEntry struct { - Source string // name of source endpoint (in config) - Destination string // name of destination endpoint (in config) OR custom spec } diff --git a/transfers/transfers.go b/transfers/transfers.go index f147af66..f3e99bc4 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -23,6 +23,7 @@ package transfers import ( "bytes" + "errors" "fmt" "log/slog" "os" @@ -182,7 +183,7 @@ func Status(transferId uuid.UUID) (TransferStatus, error) { // Requests that the task with the given UUID be canceled. Clients should check // the status of the task separately. -func Cancel(taskId uuid.UUID) error { +func Cancel(transferId uuid.UUID) error { return dispatcher.CancelTransfer(transferId) } @@ -355,11 +356,50 @@ func destinationEndpoint(destination string) (endpoints.Endpoint, error) { return endpoints.NewEndpoint(config.Databases[destination].Endpoint) } -// resolves the folder at the given destination in which transferred files are deposited, with the -// given subfolder appended -func destinationFolder(destination, subfolder string) string { +// resolves the folder at the given destination in which transferred files are deposited +func destinationFolder(destination string) string { if customSpec, err := endpoints.ParseCustomSpec(destination); err == nil { // custom transfer? - return filepath.Join(customSpec.Path, subfolder) + return customSpec.Path } - return subfolder + return "" +} + +// given a set of Frictionless DataResource descriptors, returns a map mapping the name of each +// distinct endpoint to the descriptors associated with that endpoint; in the case of a single +// endpoint, all descriptors are assigned to it; in the case of more than one endpoint, the +// "endpoint" field is read from each descriptor, and an error is returned if a descriptor is +// encountered without this field +func descriptorsByEndpoint(spec Specification, + descriptors []map[string]any) (map[string][]map[string]any, error) { + descriptorsForEndpoint := make(map[string][]map[string]any) + if len(config.Databases[spec.Source].Endpoints) > 1 { // more than one endpoint possible! + distinctEndpoints := make(map[string]any) + for _, descriptor := range descriptors { + var endpoint string + if key, found := descriptor["endpoint"]; found { + endpoint = key.(string) + } else { + return nil, databases.ResourceEndpointNotFoundError{ + Database: spec.Source, + ResourceId: descriptor["id"].(string), + } + } + if _, found := distinctEndpoints[endpoint]; !found { + distinctEndpoints[endpoint] = struct{}{} + } + } + + for endpoint := range distinctEndpoints { + endpointDescriptors := make([]map[string]any, 0) + for _, descriptor := range descriptors { + if endpoint == descriptor["endpoint"].(string) { + endpointDescriptors = append(endpointDescriptors, descriptor) + } + } + descriptorsForEndpoint[endpoint] = endpointDescriptors + } + } else { // assign all descriptors to the single endpoint + descriptorsForEndpoint[config.Databases[spec.Source].Endpoint] = descriptors + } + return descriptorsForEndpoint, nil } From 88e529aa11584878caaa1592a6bed0d8d1535e5d Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 14 Oct 2025 16:40:02 -0700 Subject: [PATCH 04/28] Fixed some test failures. --- dtstest/dtstest.go | 5 ++--- endpoints/globus/endpoint_test.go | 6 +++--- endpoints/local/endpoint_test.go | 6 +++--- transfers/dispatcher.go | 2 ++ transfers/transfers.go | 1 - 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dtstest/dtstest.go b/dtstest/dtstest.go index cdcd5e40..21d95f30 100644 --- a/dtstest/dtstest.go +++ b/dtstest/dtstest.go @@ -129,11 +129,10 @@ func (ep *Endpoint) Root() string { return ep.RootPath } -func (ep *Endpoint) FilesStaged(files []any) (bool, error) { +func (ep *Endpoint) FilesStaged(files []map[string]any) (bool, error) { if ep.Database != nil { // are there any unrecognized files? - for _, file := range files { - descriptor := file.(map[string]any) + for _, descriptor := range files { fileId := descriptor["id"].(string) if _, found := ep.Database.descriptors[fileId]; !found { return false, fmt.Errorf("unrecognized file: %s", fileId) diff --git a/endpoints/globus/endpoint_test.go b/endpoints/globus/endpoint_test.go index 40fbfc5f..b3a05245 100644 --- a/endpoints/globus/endpoint_test.go +++ b/endpoints/globus/endpoint_test.go @@ -122,13 +122,13 @@ func TestGlobusFilesStaged(t *testing.T) { endpoint, _ := NewEndpointFromConfig("source") // provide an empty slice of filenames, which should return true - staged, err := endpoint.FilesStaged([]any{}) + staged, err := endpoint.FilesStaged([]map[string]any{}) assert.True(staged) assert.Nil(err) // provide a file that's known to be on the source endpoint, which // should return true - descriptors := make([]any, 0) + descriptors := make([]map[string]any, 0) for i := 1; i <= 3; i++ { id := fmt.Sprintf("%d", i) d := map[string]any{ // descriptor @@ -147,7 +147,7 @@ func TestGlobusFilesStaged(t *testing.T) { "path": "yaddayadda/yadda/yaddayadda/yaddayaddayadda.xml", } assert.Nil(err) - descriptors = []any{nonexistent} + descriptors = []map[string]any{nonexistent} staged, err = endpoint.FilesStaged(descriptors) assert.False(staged) assert.Nil(err) diff --git a/endpoints/local/endpoint_test.go b/endpoints/local/endpoint_test.go index 61e51934..5157b9a8 100644 --- a/endpoints/local/endpoint_test.go +++ b/endpoints/local/endpoint_test.go @@ -144,12 +144,12 @@ func TestGlobusFilesStaged(t *testing.T) { endpoint, _ := NewEndpoint("source") // provide an empty slice of filenames, which should return true - staged, err := endpoint.FilesStaged([]any{}) + staged, err := endpoint.FilesStaged([]map[string]any{}) assert.True(staged) assert.Nil(err) // provide files that are known to be on the source endpoint - descriptors := make([]any, 0) + descriptors := make([]map[string]any, 0) for i := 1; i <= 3; i++ { id := fmt.Sprintf("%d", i) d := map[string]any{ @@ -167,7 +167,7 @@ func TestGlobusFilesStaged(t *testing.T) { "id": "yadda", "path": "yaddayadda/yadda/yaddayadda/yaddayaddayadda.xml", } - descriptors = []any{nonexistent} + descriptors = []map[string]any{nonexistent} staged, err = endpoint.FilesStaged(descriptors) assert.False(staged) assert.Nil(err) diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 4e38df72..316c6754 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -193,6 +193,7 @@ func (d *dispatcherState) cancel(transferId uuid.UUID) error { } switch status.Code { case TransferStatusUnknown, TransferStatusSucceeded, TransferStatusFailed: + return nil case TransferStatusStaging: return stager.Cancel(transferId) case TransferStatusActive, TransferStatusInactive: @@ -200,4 +201,5 @@ func (d *dispatcherState) cancel(transferId uuid.UUID) error { case TransferStatusFinalizing: return manifestor.Cancel(transferId) } + return nil } diff --git a/transfers/transfers.go b/transfers/transfers.go index f3e99bc4..a192b5fc 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -23,7 +23,6 @@ package transfers import ( "bytes" - "errors" "fmt" "log/slog" "os" From e835116cd9bc5b24a31bdb2cb72f7dfdf8a007b4 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Wed, 15 Oct 2025 15:18:04 -0700 Subject: [PATCH 05/28] Roughing in some transfer tests. --- databases/databases.go | 9 ++ transfers/dispatcher.go | 20 +++- transfers/dispatcher_test.go | 36 ++++++ transfers/manifestor.go | 14 ++- transfers/manifestor_test.go | 36 ++++++ transfers/mover.go | 13 +- transfers/mover_test.go | 36 ++++++ transfers/stager.go | 13 +- transfers/stager_test.go | 36 ++++++ transfers/store.go | 21 +++- transfers/store_test.go | 72 +++++++++++ transfers/transfers.go | 24 ++-- transfers/transfers_test.go | 224 +++++++++++++++++++++++++++++++++++ 13 files changed, 530 insertions(+), 24 deletions(-) create mode 100644 transfers/dispatcher_test.go create mode 100644 transfers/manifestor_test.go create mode 100644 transfers/mover_test.go create mode 100644 transfers/stager_test.go create mode 100644 transfers/store_test.go create mode 100644 transfers/transfers_test.go diff --git a/databases/databases.go b/databases/databases.go index 4db34a12..4d195da6 100644 --- a/databases/databases.go +++ b/databases/databases.go @@ -155,6 +155,15 @@ func RegisterDatabase(dbName string, createDb func() (Database, error)) error { } } +// returns a list of names of registered databases +func RegisteredDatabases() []string { + dbs := make([]string, 0) + for name, _ := range createDatabaseFuncs_ { + dbs = append(dbs, name) + } + return dbs +} + // returns true if a database has been registered with the given name, false if not func HaveDatabase(dbName string) bool { _, found := createDatabaseFuncs_[dbName] diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 316c6754..c782a9d1 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -54,6 +54,16 @@ type dispatcherChannels struct { Stop chan struct{} // used by client to stop task management } +func (channels *dispatcherChannels) close() { + close(channels.RequestTransfer) + close(channels.ReturnTransferId) + close(channels.CancelTransfer) + close(channels.RequestStatus) + close(channels.ReturnStatus) + close(channels.Error) + close(channels.Stop) +} + func (d *dispatcherState) Start() error { d.Channels = dispatcherChannels{ RequestTransfer: make(chan Specification, 32), @@ -71,7 +81,9 @@ func (d *dispatcherState) Start() error { func (d *dispatcherState) Stop() error { d.Channels.Stop <- struct{}{} - return <-d.Channels.Error + err := <-d.Channels.Error + d.Channels.close() + return err } func (d *dispatcherState) CreateTransfer(spec Specification) (uuid.UUID, error) { @@ -99,9 +111,9 @@ func (d *dispatcherState) CancelTransfer(id uuid.UUID) error { return <-d.Channels.Error } -//---------------------------------------------------- +//--------------------------------------------------------- // everything past here runs in the dispatcher's goroutine -//---------------------------------------------------- +//--------------------------------------------------------- // the goroutine itself func (d *dispatcherState) process() { @@ -125,6 +137,7 @@ func (d *dispatcherState) process() { transferId, numFiles, err := d.create(spec) if err != nil { returnError <- err + break } returnTransferId <- transferId slog.Info(fmt.Sprintf("Created new transfer %s (%d file(s) requested)", transferId.String(), @@ -144,6 +157,7 @@ func (d *dispatcherState) process() { returnStatus <- status case <-stopRequested: running = false + returnError <- nil } } } diff --git a/transfers/dispatcher_test.go b/transfers/dispatcher_test.go new file mode 100644 index 00000000..6a2e3ee0 --- /dev/null +++ b/transfers/dispatcher_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStartAndStopDispatcher(t *testing.T) { + assert := assert.New(t) + err := dispatcher.Start() + assert.Nil(err) + err = dispatcher.Stop() + assert.Nil(err) +} diff --git a/transfers/manifestor.go b/transfers/manifestor.go index bee31789..9a466485 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -57,6 +57,13 @@ type manifestorChannels struct { Stop chan struct{} } +func (channels *manifestorChannels) close() { + close(channels.RequestGeneration) + close(channels.RequestCancellation) + close(channels.Error) + close(channels.Stop) +} + // starts the mover func (m *manifestorState) Start() error { m.Channels = manifestorChannels{ @@ -73,7 +80,9 @@ func (m *manifestorState) Start() error { // stops the manifestor goroutine func (m *manifestorState) Stop() error { m.Channels.Stop <- struct{}{} - return <-m.Channels.Error + err := <-m.Channels.Error + m.Channels.close() + return err } // starts generating a manifest for the given transfer, moving it subsequently to that transfer's @@ -117,8 +126,9 @@ func (m *manifestorState) process() { } else { manifestor.Channels.Error <- NotFoundError{Id: transferId} } - case <-mover.Channels.Stop: + case <-manifestor.Channels.Stop: running = false + manifestor.Channels.Error <- nil } time.Sleep(pollInterval) diff --git a/transfers/manifestor_test.go b/transfers/manifestor_test.go new file mode 100644 index 00000000..29d31e9e --- /dev/null +++ b/transfers/manifestor_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStartAndStopManifestor(t *testing.T) { + assert := assert.New(t) + err := manifestor.Start() + assert.Nil(err) + err = manifestor.Stop() + assert.Nil(err) +} diff --git a/transfers/mover.go b/transfers/mover.go index 8ace593d..5c165ceb 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -52,10 +52,18 @@ type moverChannels struct { Stop chan struct{} } +func (channels *moverChannels) close() { + close(channels.RequestMove) + close(channels.RequestCancellation) + close(channels.Error) + close(channels.Stop) +} + // starts the mover func (m *moverState) Start() error { m.Channels = moverChannels{ RequestMove: make(chan uuid.UUID, 32), + RequestCancellation: make(chan uuid.UUID, 32), Error: make(chan error, 32), Stop: make(chan struct{}), } @@ -67,7 +75,9 @@ func (m *moverState) Start() error { // stops the mover goroutine func (m *moverState) Stop() error { m.Channels.Stop <- struct{}{} - return <-m.Channels.Error + err := <-m.Channels.Error + m.Channels.close() + return err } // starts moving files associated with the given transfer ID @@ -111,6 +121,7 @@ func (m *moverState) process() { } case <-mover.Channels.Stop: running = false + mover.Channels.Error <- nil } time.Sleep(pollInterval) diff --git a/transfers/mover_test.go b/transfers/mover_test.go new file mode 100644 index 00000000..b80009a9 --- /dev/null +++ b/transfers/mover_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStartAndStopMover(t *testing.T) { + assert := assert.New(t) + err := mover.Start() + assert.Nil(err) + err = mover.Stop() + assert.Nil(err) +} diff --git a/transfers/stager.go b/transfers/stager.go index 042c3589..68d50565 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -51,10 +51,18 @@ type stagerChannels struct { Stop chan struct{} } +func (channels *stagerChannels) close() { + close(channels.RequestStaging) + close(channels.RequestCancellation) + close(channels.Error) + close(channels.Stop) +} + // starts the stager func (s *stagerState) Start() error { s.Channels = stagerChannels{ RequestStaging: make(chan uuid.UUID, 32), + RequestCancellation: make(chan uuid.UUID, 32), Error: make(chan error, 32), Stop: make(chan struct{}), } @@ -65,7 +73,9 @@ func (s *stagerState) Start() error { // stops the stager goroutine func (s *stagerState) Stop() error { s.Channels.Stop <- struct{}{} - return <-s.Channels.Error + err := <-s.Channels.Error + s.Channels.close() + return err } // requests that files be staged for the transfer with the given ID @@ -106,6 +116,7 @@ func (s *stagerState) process() { } case <-stager.Channels.Stop: running = false + stager.Channels.Error <- nil } time.Sleep(pollInterval) diff --git a/transfers/stager_test.go b/transfers/stager_test.go new file mode 100644 index 00000000..ed547f7f --- /dev/null +++ b/transfers/stager_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStartAndStopStager(t *testing.T) { + assert := assert.New(t) + err := stager.Start() + assert.Nil(err) + err = stager.Stop() + assert.Nil(err) +} diff --git a/transfers/store.go b/transfers/store.go index 836d319f..23f237e8 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -61,6 +61,21 @@ type storeChannels struct { Stop chan struct{} } +func (channels *storeChannels) close() { + close(channels.RequestNewTransfer) + close(channels.ReturnNewTransfer) + close(channels.RequestSpec) + close(channels.ReturnSpec) + close(channels.RequestDescriptors) + close(channels.ReturnDescriptors) + close(channels.SetStatus) + close(channels.RequestStatus) + close(channels.ReturnStatus) + close(channels.RequestRemoval) + close(channels.Error) + close(channels.Stop) +} + type transferIdAndStatus struct { Id uuid.UUID Status TransferStatus @@ -88,7 +103,10 @@ func (s *storeState) Start() error { // stops the store goroutine func (s *storeState) Stop() error { - return nil + s.Channels.Stop <- struct{}{} + err := <-s.Channels.Error + s.Channels.close() + return err } // creates a new entry for a transfer within the store, populating it with relevant metadata and @@ -205,6 +223,7 @@ func (s *storeState) process() { store.Channels.Error <- TransferNotFoundError{Id: id} } case <-store.Channels.Stop: + store.Channels.Error <- nil running = false } } diff --git a/transfers/store_test.go b/transfers/store_test.go new file mode 100644 index 00000000..dc4f14f0 --- /dev/null +++ b/transfers/store_test.go @@ -0,0 +1,72 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package transfers + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestStartAndStopStore(t *testing.T) { + assert := assert.New(t) + err := store.Start() + assert.Nil(err) + err = store.Stop() + assert.Nil(err) +} + +func TestStoreNewTransfer(t *testing.T) { + assert := assert.New(t) + + err := store.Start() + assert.Nil(err) + + spec := Specification{ + } + transferId, numFiles, err := store.NewTransfer(spec) + assert.Nil(err) + assert.NotEqual(uuid.UUID{}, transferId) + assert.Greater(0, numFiles) + + spec1, err := store.GetSpecification(transferId) + assert.Nil(err) + assert.Equal(spec, spec1) + + desc1 := make([]map[string]any, 0) + descriptors, err := store.GetDescriptors(transferId) + assert.Nil(err) + assert.Equal(desc1, descriptors) + + status, err := store.GetStatus(transferId) + assert.Nil(err) + status.Code = TransferStatusStaging + err = store.SetStatus(transferId, status) + assert.Nil(err) + status1, err := store.GetStatus(transferId) + assert.Nil(err) + assert.Equal(status1, status) + + err = store.Stop() + assert.Nil(err) +} diff --git a/transfers/transfers.go b/transfers/transfers.go index a192b5fc..68a25620 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -73,6 +73,7 @@ func Start() error { return err } if err := registerDatabases(); err != nil { + print("pooped 'em!\n") return err } global.Started = true @@ -107,12 +108,10 @@ func Start() error { func Stop() error { var err error if global.Running { - err := dispatcher.Stop() - if err != nil { + if err := stopOrchestration(); err != nil { return err } - err = journal.Finalize() - if err != nil { + if err = journal.Finalize(); err != nil { return err } global.Running = false @@ -216,29 +215,22 @@ func registerEndpointProviders() error { // registers databases; if at least one database is available, no error is propagated func registerDatabases() error { - numAvailable := 0 if _, found := config.Databases["jdp"]; found { if err := databases.RegisterDatabase("jdp", jdp.NewDatabase); err != nil { slog.Error(err.Error()) - } else { - numAvailable++ } } if _, found := config.Databases["kbase"]; found { if err := databases.RegisterDatabase("kbase", kbase.NewDatabase); err != nil { slog.Error(err.Error()) - } else { - numAvailable++ } } if _, found := config.Databases["nmdc"]; found { if err := databases.RegisterDatabase("nmdc", nmdc.NewDatabase); err != nil { slog.Error(err.Error()) - } else { - numAvailable++ } } - if numAvailable == 0 { + if len(databases.RegisteredDatabases()) == 0 { return &NoDatabasesAvailable{} } return nil @@ -327,9 +319,6 @@ func startOrchestration() error { } func stopOrchestration() error { - if err := dispatcher.Stop(); err != nil { - return err - } if err := stager.Stop(); err != nil { return err } @@ -339,7 +328,10 @@ func stopOrchestration() error { if err := manifestor.Stop(); err != nil { return err } - return store.Stop() + if err := store.Stop(); err != nil { + return err + } + return dispatcher.Stop() } // resolves the given destination (name) string, accounting for custom transfers diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go new file mode 100644 index 00000000..85b4907e --- /dev/null +++ b/transfers/transfers_test.go @@ -0,0 +1,224 @@ +// Copyright (c) 2023 The KBase Project and its Contributors +// Copyright (c) 2023 Cohere Consulting, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// These tests must be run serially, since tasks are coordinated by a +// single instance. + +package transfers + +import ( + "log" + "os" + "strings" + "testing" + "time" + + //"github.com/google/uuid" + "github.com/stretchr/testify/assert" + + //"github.com/kbase/dts/auth" + "github.com/kbase/dts/config" + "github.com/kbase/dts/dtstest" +) + +// runs all tests serially +func TestRunner(t *testing.T) { + tester := SerialTests{Test: t} + tester.TestStartAndStopTransfers() + //tester.TestStopAndRestartTransfers() +} + +// This runs setup, runs all tests, and does breakdown. +func TestMain(m *testing.M) { + var status int + setup() + status = m.Run() + breakdown() + os.Exit(status) +} + +// this function gets called at the begÑ–nning of a test session +func setup() { + dtstest.EnableDebugLogging() + + log.Print("Creating testing directory...\n") + var err error + TESTING_DIR, err = os.MkdirTemp(os.TempDir(), "data-transfer-service-tests-") + if err != nil { + log.Panicf("Couldn't create testing directory: %s", err) + } + os.Chdir(TESTING_DIR) + + // read in the config file with SOURCE_ROOT and DESTINATION_ROOT replaced + myConfig := strings.ReplaceAll(transfersConfig, "TESTING_DIR", TESTING_DIR) + err = config.Init([]byte(myConfig)) + if err != nil { + log.Panicf("Couldn't initialize configuration: %s", err) + } + + // create test resources + testDescriptors := map[string]map[string]any{ + "file1": { + "id": "file1", + "name": "file1.dat", + "path": "dir1/file1.dat", + "format": "text", + "bytes": 1024, + "hash": "d91f97974d06563cab48d4d43a17e08a", + "endpoint": "source-endpoint", + }, + "file2": { + "id": "file2", + "name": "file2.dat", + "path": "dir2/file2.dat", + "format": "text", + "bytes": 2048, + "hash": "d91f9e974d0e563cab48d4d43a17e08a", + "endpoint": "source-endpoint", + }, + "file3": { + "id": "file3", + "name": "file3.dat", + "path": "dir3/file3.dat", + "format": "text", + "bytes": 4096, + "hash": "e91f9e974d0e563cab48d4d43a17e08e", + "endpoint": "source-endpoint", + }, + } + + // register test databases/endpoints referred to in config file + dtstest.RegisterTestFixturesFromConfig(endpointOptions, testDescriptors) + + // Create the data and manifest directories + os.Mkdir(config.Service.DataDirectory, 0755) + os.Mkdir(config.Service.ManifestDirectory, 0755) +} + +// this function gets called after all tests have been run +func breakdown() { + if TESTING_DIR != "" { + log.Printf("Deleting testing directory %s...\n", TESTING_DIR) + os.RemoveAll(TESTING_DIR) + } +} + +// To run the tests serially, we attach them to a SerialTests type and +// have them run by a a single test runner. +type SerialTests struct{ Test *testing.T } + +func (t *SerialTests) TestStartAndStopTransfers() { + assert := assert.New(t.Test) + + assert.False(Running()) + err := Start() + assert.Nil(err) + assert.True(Running()) + err = Stop() + assert.Nil(err) + assert.False(Running()) +} + +func (t *SerialTests) TestStopAndRestartTransfers() { + /* + assert := assert.New(t.Test) + + // start up, add a bunch of tasks, then immediately close + err := Start() + assert.Nil(err) + numTasks := 10 + taskIds := make([]uuid.UUID, numTasks) + for i := 0; i < numTasks; i++ { + taskId, _ := Create(Specification{ + User: auth.User{ + Name: "Joe-bob", + Orcid: "1234-5678-9012-3456", + }, + Source: "test-source", + Destination: "test-destination", + FileIds: []string{"file1", "file2"}, + }) + taskIds[i] = taskId + } + time.Sleep(100 * time.Millisecond) // let things settle + err = Stop() + assert.Nil(err) + + // now restart the task manager and make sure all the tasks are there + err = Start() + assert.Nil(err) + for i := 0; i < numTasks; i++ { + _, err := Status(taskIds[i]) + assert.Nil(err) + } + + err = Stop() + assert.Nil(err) + */ +} + +// temporary testing directory +var TESTING_DIR string + +// endpoint testing options +var endpointOptions = dtstest.EndpointOptions{ + StagingDuration: time.Duration(150) * time.Millisecond, + TransferDuration: time.Duration(500) * time.Millisecond, +} + +// a pause to give the task manager a bit of time +var pause time.Duration = time.Duration(25) * time.Millisecond + +// configuration +const transfersConfig string = ` +service: + port: 8080 + max_connections: 100 + poll_interval: 50 # milliseconds + data_dir: TESTING_DIR/data + manifest_dir: TESTING_DIR/manifests + delete_after: 2 # seconds + endpoint: local-endpoint +databases: + test-source: + name: Source Test Database + organization: The Source Company + endpoint: source-endpoint + test-destination: + name: Destination Test Database + organization: Fabulous Destinations, Inc. + endpoint: destination-endpoint +endpoints: + local-endpoint: + name: Local endpoint + id: 8816ec2d-4a48-4ded-b68a-5ab46a4417b6 + provider: test + source-endpoint: + name: Endpoint 1 + id: 26d61236-39f6-4742-a374-8ec709347f2f + provider: test + root: SOURCE_ROOT + destination-endpoint: + name: Endpoint 2 + id: f1865b86-2c64-4b8b-99f3-5aaa945ec3d9 + provider: test + root: DESTINATION_ROOT +` From 183c1637140dce1515a86ef64f0f8112452d4846 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Thu, 16 Oct 2025 16:06:35 -0700 Subject: [PATCH 06/28] Getting tests organized. --- transfers/dispatcher_test.go | 13 ++- transfers/manifestor.go | 6 +- transfers/manifestor_test.go | 13 ++- transfers/mover.go | 6 +- transfers/mover_test.go | 13 ++- transfers/stager.go | 6 +- transfers/stager_test.go | 13 ++- transfers/store_test.go | 21 +++-- transfers/transfers_test.go | 168 +++++++++++++++++------------------ 9 files changed, 151 insertions(+), 108 deletions(-) diff --git a/transfers/dispatcher_test.go b/transfers/dispatcher_test.go index 6a2e3ee0..9bea490b 100644 --- a/transfers/dispatcher_test.go +++ b/transfers/dispatcher_test.go @@ -27,8 +27,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestStartAndStopDispatcher(t *testing.T) { - assert := assert.New(t) +// We attach the tests to this type, which runs them one by one. +// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go +type DispatcherTests struct{ Test *testing.T } + +func TestDispatcher(t *testing.T) { + tester := DispatcherTests{Test: t} + tester.TestStartAndStop() +} + +func (t *DispatcherTests) TestStartAndStop() { + assert := assert.New(t.Test) err := dispatcher.Start() assert.Nil(err) err = dispatcher.Stop() diff --git a/transfers/manifestor.go b/transfers/manifestor.go index 9a466485..f293c470 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -34,9 +34,9 @@ import ( "github.com/kbase/dts/endpoints" ) -//------- -// Mover -//------- +//------------ +// Manifestor +//------------ // The manifestor generates a JSON manifest for each successful transfer and sends it to the // transfer's destination. The manifest contains a Frictionless DataPackage containing all diff --git a/transfers/manifestor_test.go b/transfers/manifestor_test.go index 29d31e9e..59810785 100644 --- a/transfers/manifestor_test.go +++ b/transfers/manifestor_test.go @@ -27,8 +27,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestStartAndStopManifestor(t *testing.T) { - assert := assert.New(t) +// We attach the tests to this type, which runs them one by one. +// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go +type ManifestorTests struct{ Test *testing.T } + +func TestManifestor(t *testing.T) { + tester := ManifestorTests{Test: t} + tester.TestStartAndStop() +} + +func (t *ManifestorTests) TestStartAndStop() { + assert := assert.New(t.Test) err := manifestor.Start() assert.Nil(err) err = manifestor.Stop() diff --git a/transfers/mover.go b/transfers/mover.go index 5c165ceb..9f2ecd4a 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -62,10 +62,10 @@ func (channels *moverChannels) close() { // starts the mover func (m *moverState) Start() error { m.Channels = moverChannels{ - RequestMove: make(chan uuid.UUID, 32), + RequestMove: make(chan uuid.UUID, 32), RequestCancellation: make(chan uuid.UUID, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), + Error: make(chan error, 32), + Stop: make(chan struct{}), } m.Endpoints = make(map[string]endpoints.Endpoint) go m.process() diff --git a/transfers/mover_test.go b/transfers/mover_test.go index b80009a9..a33aaf74 100644 --- a/transfers/mover_test.go +++ b/transfers/mover_test.go @@ -27,8 +27,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestStartAndStopMover(t *testing.T) { - assert := assert.New(t) +// We attach the tests to this type, which runs them one by one. +// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go +func TestMover(t *testing.T) { + tester := MoverTests{Test: t} + tester.TestStartAndStop() +} + +type MoverTests struct{ Test *testing.T } + +func (t *MoverTests) TestStartAndStop() { + assert := assert.New(t.Test) err := mover.Start() assert.Nil(err) err = mover.Stop() diff --git a/transfers/stager.go b/transfers/stager.go index 68d50565..38620459 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -61,10 +61,10 @@ func (channels *stagerChannels) close() { // starts the stager func (s *stagerState) Start() error { s.Channels = stagerChannels{ - RequestStaging: make(chan uuid.UUID, 32), + RequestStaging: make(chan uuid.UUID, 32), RequestCancellation: make(chan uuid.UUID, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), + Error: make(chan error, 32), + Stop: make(chan struct{}), } go s.process() return nil diff --git a/transfers/stager_test.go b/transfers/stager_test.go index ed547f7f..bd3d0121 100644 --- a/transfers/stager_test.go +++ b/transfers/stager_test.go @@ -27,8 +27,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestStartAndStopStager(t *testing.T) { - assert := assert.New(t) +// We attach the tests to this type, which runs them one by one. +// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go +type StagerTests struct{ Test *testing.T } + +func TestStager(t *testing.T) { + tester := StagerTests{Test: t} + tester.TestStartAndStop() +} + +func (t *StagerTests) TestStartAndStop() { + assert := assert.New(t.Test) err := stager.Start() assert.Nil(err) err = stager.Stop() diff --git a/transfers/store_test.go b/transfers/store_test.go index dc4f14f0..4b531f85 100644 --- a/transfers/store_test.go +++ b/transfers/store_test.go @@ -28,22 +28,31 @@ import ( "github.com/stretchr/testify/assert" ) -func TestStartAndStopStore(t *testing.T) { - assert := assert.New(t) +// We attach the tests to this type, which runs them one by one. +// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go +type StoreTests struct{ Test *testing.T } + +func TestStore(t *testing.T) { + tester := StoreTests{Test: t} + tester.TestStartAndStop() + // tester.TestNewTransfer() +} + +func (t *StoreTests) TestStartAndStop() { + assert := assert.New(t.Test) err := store.Start() assert.Nil(err) err = store.Stop() assert.Nil(err) } -func TestStoreNewTransfer(t *testing.T) { - assert := assert.New(t) +func (t *StoreTests) TestNewTransfer() { + assert := assert.New(t.Test) err := store.Start() assert.Nil(err) - spec := Specification{ - } + spec := Specification{} transferId, numFiles, err := store.NewTransfer(spec) assert.Nil(err) assert.NotEqual(uuid.UUID{}, transferId) diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 85b4907e..150ecbb7 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -39,93 +39,16 @@ import ( "github.com/kbase/dts/dtstest" ) -// runs all tests serially -func TestRunner(t *testing.T) { - tester := SerialTests{Test: t} - tester.TestStartAndStopTransfers() - //tester.TestStopAndRestartTransfers() -} - -// This runs setup, runs all tests, and does breakdown. -func TestMain(m *testing.M) { - var status int - setup() - status = m.Run() - breakdown() - os.Exit(status) -} - -// this function gets called at the begÑ–nning of a test session -func setup() { - dtstest.EnableDebugLogging() - - log.Print("Creating testing directory...\n") - var err error - TESTING_DIR, err = os.MkdirTemp(os.TempDir(), "data-transfer-service-tests-") - if err != nil { - log.Panicf("Couldn't create testing directory: %s", err) - } - os.Chdir(TESTING_DIR) - - // read in the config file with SOURCE_ROOT and DESTINATION_ROOT replaced - myConfig := strings.ReplaceAll(transfersConfig, "TESTING_DIR", TESTING_DIR) - err = config.Init([]byte(myConfig)) - if err != nil { - log.Panicf("Couldn't initialize configuration: %s", err) - } - - // create test resources - testDescriptors := map[string]map[string]any{ - "file1": { - "id": "file1", - "name": "file1.dat", - "path": "dir1/file1.dat", - "format": "text", - "bytes": 1024, - "hash": "d91f97974d06563cab48d4d43a17e08a", - "endpoint": "source-endpoint", - }, - "file2": { - "id": "file2", - "name": "file2.dat", - "path": "dir2/file2.dat", - "format": "text", - "bytes": 2048, - "hash": "d91f9e974d0e563cab48d4d43a17e08a", - "endpoint": "source-endpoint", - }, - "file3": { - "id": "file3", - "name": "file3.dat", - "path": "dir3/file3.dat", - "format": "text", - "bytes": 4096, - "hash": "e91f9e974d0e563cab48d4d43a17e08e", - "endpoint": "source-endpoint", - }, - } - - // register test databases/endpoints referred to in config file - dtstest.RegisterTestFixturesFromConfig(endpointOptions, testDescriptors) - - // Create the data and manifest directories - os.Mkdir(config.Service.DataDirectory, 0755) - os.Mkdir(config.Service.ManifestDirectory, 0755) -} +// We attach the tests to this type, which runs them one by one. +type TransferTests struct{ Test *testing.T } -// this function gets called after all tests have been run -func breakdown() { - if TESTING_DIR != "" { - log.Printf("Deleting testing directory %s...\n", TESTING_DIR) - os.RemoveAll(TESTING_DIR) - } +func TestTransfers(t *testing.T) { + tester := TransferTests{Test: t} + tester.TestStartAndStop() + //tester.TestStopAndRestartTransfers() } -// To run the tests serially, we attach them to a SerialTests type and -// have them run by a a single test runner. -type SerialTests struct{ Test *testing.T } - -func (t *SerialTests) TestStartAndStopTransfers() { +func (t *TransferTests) TestStartAndStop() { assert := assert.New(t.Test) assert.False(Running()) @@ -137,7 +60,7 @@ func (t *SerialTests) TestStartAndStopTransfers() { assert.False(Running()) } -func (t *SerialTests) TestStopAndRestartTransfers() { +func (t *TransferTests) TestStopAndRestart() { /* assert := assert.New(t.Test) @@ -175,6 +98,81 @@ func (t *SerialTests) TestStopAndRestartTransfers() { */ } +// This runs setup, runs all tests, and does breakdown. +func TestMain(m *testing.M) { + var status int + setup() + status = m.Run() + breakdown() + os.Exit(status) +} + +// this function gets called at the begÑ–nning of a test session +func setup() { + dtstest.EnableDebugLogging() + + log.Print("Creating testing directory...\n") + var err error + TESTING_DIR, err = os.MkdirTemp(os.TempDir(), "data-transfer-service-tests-") + if err != nil { + log.Panicf("Couldn't create testing directory: %s", err) + } + os.Chdir(TESTING_DIR) + + // read in the config file with SOURCE_ROOT and DESTINATION_ROOT replaced + myConfig := strings.ReplaceAll(transfersConfig, "TESTING_DIR", TESTING_DIR) + err = config.Init([]byte(myConfig)) + if err != nil { + log.Panicf("Couldn't initialize configuration: %s", err) + } + + // create test resources + testDescriptors := map[string]map[string]any{ + "file1": { + "id": "file1", + "name": "file1.dat", + "path": "dir1/file1.dat", + "format": "text", + "bytes": 1024, + "hash": "d91f97974d06563cab48d4d43a17e08a", + "endpoint": "source-endpoint", + }, + "file2": { + "id": "file2", + "name": "file2.dat", + "path": "dir2/file2.dat", + "format": "text", + "bytes": 2048, + "hash": "d91f9e974d0e563cab48d4d43a17e08a", + "endpoint": "source-endpoint", + }, + "file3": { + "id": "file3", + "name": "file3.dat", + "path": "dir3/file3.dat", + "format": "text", + "bytes": 4096, + "hash": "e91f9e974d0e563cab48d4d43a17e08e", + "endpoint": "source-endpoint", + }, + } + + // register test databases/endpoints referred to in config file + dtstest.RegisterTestFixturesFromConfig(endpointOptions, testDescriptors) + + // Create the data and manifest directories + os.Mkdir(config.Service.DataDirectory, 0755) + os.Mkdir(config.Service.ManifestDirectory, 0755) +} + +// this function gets called after all tests have been run +func breakdown() { + if TESTING_DIR != "" { + log.Printf("Deleting testing directory %s...\n", TESTING_DIR) + os.RemoveAll(TESTING_DIR) + } +} + // temporary testing directory var TESTING_DIR string From 2475179a075d596c17eb5f41ed07216ec768e6a1 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Sun, 19 Oct 2025 22:09:36 -0700 Subject: [PATCH 07/28] Adding a clock for polling other routines. --- transfers/clock.go | 64 +++++++++++++++++++++++ transfers/dispatcher_test.go | 6 --- transfers/manifestor.go | 30 +++++------ transfers/manifestor_test.go | 6 --- transfers/mover.go | 32 ++++++------ transfers/mover_test.go | 6 --- transfers/stager.go | 51 +++++++++---------- transfers/stager_test.go | 34 ++++++++++--- transfers/store.go | 41 ++++++++++----- transfers/store_test.go | 70 ++++++++++++++++++++++---- transfers/transfers.go | 7 ++- transfers/transfers_test.go | 98 ++++++++++++++++++++++-------------- 12 files changed, 298 insertions(+), 147 deletions(-) create mode 100644 transfers/clock.go diff --git a/transfers/clock.go b/transfers/clock.go new file mode 100644 index 00000000..ab9805d6 --- /dev/null +++ b/transfers/clock.go @@ -0,0 +1,64 @@ +package transfers + +import ( + "sync" + "time" + + "github.com/kbase/dts/config" +) + +// this singleton sends a pulse to transfer-related goroutines at the configured poll interval +var clock clockType + +type clockType struct { + Initialized bool + Mutex sync.Mutex + NumSubscribers int + Pulses []chan struct{} + Tick time.Duration +} + +// subscribes the caller to the clock, returning a new channel on which the pulse is sent +func (c *clockType) Subscribe() chan struct{} { + c.Mutex.Lock() + c.NumSubscribers++ + if len(c.Pulses) < c.NumSubscribers { + c.Pulses = append(c.Pulses, make(chan struct{})) + } else { + c.Pulses[c.NumSubscribers-1] = make(chan struct{}) + } + if !c.Initialized { + c.Tick = time.Duration(config.Service.PollInterval) * time.Millisecond + c.Initialized = true + go c.process() + } + c.Mutex.Unlock() + return c.Pulses[len(c.Pulses)-1] +} + +// unsubscribes the caller from the clock +func (c *clockType) Unsubscribe() { + c.Mutex.Lock() + c.NumSubscribers-- + close(c.Pulses[c.NumSubscribers]) + c.Mutex.Unlock() +} + +func (c *clockType) process() { + c.Mutex.Lock() + tick := c.Tick + c.Mutex.Unlock() + + for { + time.Sleep(tick) + c.Mutex.Lock() + if c.NumSubscribers == 0 { + c.Mutex.Unlock() + break + } + c.Mutex.Unlock() + for i := range c.NumSubscribers { + c.Pulses[i] <- struct{}{} + } + } +} diff --git a/transfers/dispatcher_test.go b/transfers/dispatcher_test.go index 9bea490b..42d52985 100644 --- a/transfers/dispatcher_test.go +++ b/transfers/dispatcher_test.go @@ -28,14 +28,8 @@ import ( ) // We attach the tests to this type, which runs them one by one. -// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go type DispatcherTests struct{ Test *testing.T } -func TestDispatcher(t *testing.T) { - tester := DispatcherTests{Test: t} - tester.TestStartAndStop() -} - func (t *DispatcherTests) TestStartAndStop() { assert := assert.New(t.Test) err := dispatcher.Start() diff --git a/transfers/manifestor.go b/transfers/manifestor.go index f293c470..88c3f9a6 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -106,8 +106,9 @@ func (m *manifestorState) Cancel(transferId uuid.UUID) error { // the goroutine itself func (m *manifestorState) process() { running := true - pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond manifestTransfers := make(map[uuid.UUID]uuid.UUID) + pulse := clock.Subscribe() + for running { select { case transferId := <-manifestor.Channels.RequestGeneration: @@ -126,25 +127,24 @@ func (m *manifestorState) process() { } else { manifestor.Channels.Error <- NotFoundError{Id: transferId} } + case <-pulse: + // check the manifest transfers + for transferId, manifestXferId := range manifestTransfers { + completed, err := m.updateStatus(transferId, manifestXferId) + if err != nil { + mover.Channels.Error <- err + continue + } + if completed { + delete(manifestTransfers, transferId) + } + } case <-manifestor.Channels.Stop: running = false manifestor.Channels.Error <- nil } - - time.Sleep(pollInterval) - - // monitor the manifest transfers - for transferId, manifestXferId := range manifestTransfers { - completed, err := m.updateStatus(transferId, manifestXferId) - if err != nil { - mover.Channels.Error <- err - continue - } - if completed { - delete(manifestTransfers, transferId) - } - } } + clock.Unsubscribe() } func (m *manifestorState) generateAndSendManifest(transferId uuid.UUID) (uuid.UUID, error) { diff --git a/transfers/manifestor_test.go b/transfers/manifestor_test.go index 59810785..e70a74bb 100644 --- a/transfers/manifestor_test.go +++ b/transfers/manifestor_test.go @@ -28,14 +28,8 @@ import ( ) // We attach the tests to this type, which runs them one by one. -// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go type ManifestorTests struct{ Test *testing.T } -func TestManifestor(t *testing.T) { - tester := ManifestorTests{Test: t} - tester.TestStartAndStop() -} - func (t *ManifestorTests) TestStartAndStop() { assert := assert.New(t.Test) err := manifestor.Start() diff --git a/transfers/mover.go b/transfers/mover.go index 9f2ecd4a..d9b61be0 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -23,11 +23,9 @@ package transfers import ( "path/filepath" - "time" "github.com/google/uuid" - "github.com/kbase/dts/config" "github.com/kbase/dts/endpoints" ) @@ -99,8 +97,9 @@ func (m *moverState) Cancel(transferId uuid.UUID) error { // the goroutine itself func (m *moverState) process() { running := true - pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond moveOperations := make(map[uuid.UUID][]moveOperation) // a single transfer can be several move operations! + pulse := clock.Subscribe() + for running { select { case transferId := <-mover.Channels.RequestMove: @@ -119,25 +118,24 @@ func (m *moverState) process() { } else { mover.Channels.Error <- NotFoundError{Id: transferId} } + case <-pulse: + // check the move statuses and advance as needed + for transferId, moves := range moveOperations { + completed, err := m.updateStatus(transferId, moves) + if err != nil { + mover.Channels.Error <- err + continue + } + if completed { + delete(moveOperations, transferId) + } + } case <-mover.Channels.Stop: running = false mover.Channels.Error <- nil } - - time.Sleep(pollInterval) - - // check the move statuses and advance as needed - for transferId, moves := range moveOperations { - completed, err := m.updateStatus(transferId, moves) - if err != nil { - mover.Channels.Error <- err - continue - } - if completed { - delete(moveOperations, transferId) - } - } } + clock.Unsubscribe() } type moveOperation struct { diff --git a/transfers/mover_test.go b/transfers/mover_test.go index a33aaf74..8e5feccf 100644 --- a/transfers/mover_test.go +++ b/transfers/mover_test.go @@ -28,12 +28,6 @@ import ( ) // We attach the tests to this type, which runs them one by one. -// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go -func TestMover(t *testing.T) { - tester := MoverTests{Test: t} - tester.TestStartAndStop() -} - type MoverTests struct{ Test *testing.T } func (t *MoverTests) TestStartAndStop() { diff --git a/transfers/stager.go b/transfers/stager.go index 38620459..2fb2bebc 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -22,11 +22,8 @@ package transfers import ( - "time" - "github.com/google/uuid" - "github.com/kbase/dts/config" "github.com/kbase/dts/databases" ) @@ -81,13 +78,13 @@ func (s *stagerState) Stop() error { // requests that files be staged for the transfer with the given ID func (s *stagerState) StageFiles(id uuid.UUID) error { s.Channels.RequestStaging <- id - return <-stager.Channels.Error + return <-s.Channels.Error } // cancels a file staging operation func (s *stagerState) Cancel(transferId uuid.UUID) error { s.Channels.RequestCancellation <- transferId - return <-stager.Channels.Error + return <-s.Channels.Error } //---------------------------------------------------- @@ -97,44 +94,45 @@ func (s *stagerState) Cancel(transferId uuid.UUID) error { // the goroutine itself func (s *stagerState) process() { running := true - pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond stagings := make(map[uuid.UUID]stagingEntry) + pulse := clock.Subscribe() + for running { select { - case transferId := <-stager.Channels.RequestStaging: - entry, err := s.start(transferId) + case transferId := <-s.Channels.RequestStaging: + entry, err := s.stageFiles(transferId) if err != nil { - stager.Channels.Error <- err + s.Channels.Error <- err } stagings[transferId] = entry + s.Channels.Error <- nil case transferId := <-mover.Channels.RequestCancellation: if _, found := stagings[transferId]; found { delete(stagings, transferId) // simply remove the entry and stop tracking file staging - stager.Channels.Error <- nil + s.Channels.Error <- nil } else { - stager.Channels.Error <- NotFoundError{Id: transferId} + s.Channels.Error <- NotFoundError{Id: transferId} } - case <-stager.Channels.Stop: - running = false - stager.Channels.Error <- nil - } - - time.Sleep(pollInterval) - - // check the staging status and advance to a transfer if it's finished - for transferId, staging := range stagings { - if err := s.updateStatus(transferId, staging); err != nil { - stager.Channels.Error <- err + case <-pulse: + // check the staging status and advance to a transfer if it's finished + for transferId, staging := range stagings { + if err := s.updateStatus(transferId, staging); err != nil { + s.Channels.Error <- err + } } + case <-s.Channels.Stop: + running = false + s.Channels.Error <- nil } } + clock.Unsubscribe() } type stagingEntry struct { Id uuid.UUID // staging ID (distinct from transfer ID) } -func (s *stagerState) start(transferId uuid.UUID) (stagingEntry, error) { +func (s *stagerState) stageFiles(transferId uuid.UUID) (stagingEntry, error) { spec, err := store.GetSpecification(transferId) if err != nil { return stagingEntry{}, err @@ -165,14 +163,15 @@ func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) e return err } - if status == databases.StagingStatusSucceeded { + switch status { + case databases.StagingStatusSucceeded: err := mover.MoveFiles(transferId) if err != nil { return err } - } else if status == databases.StagingStatusFailed { + case databases.StagingStatusFailed: // FIXME: handle staging failures here! - } else { // still staging + default: // still staging xferStatus, err := store.GetStatus(transferId) if err != nil { return err diff --git a/transfers/stager_test.go b/transfers/stager_test.go index bd3d0121..c195b2c7 100644 --- a/transfers/stager_test.go +++ b/transfers/stager_test.go @@ -23,23 +23,45 @@ package transfers import ( "testing" + "time" "github.com/stretchr/testify/assert" ) -// We attach the tests to this type, which runs them one by one. -// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go +// We attach the tests to this type, which runs them one by one using logic in transfers_test.go. type StagerTests struct{ Test *testing.T } -func TestStager(t *testing.T) { - tester := StagerTests{Test: t} - tester.TestStartAndStop() +func (t *StagerTests) TestStartAndStop() { + assert := assert.New(t.Test) + err := stager.Start() + assert.Nil(err) + err = stager.Stop() + assert.Nil(err) } -func (t *StagerTests) TestStartAndStop() { +func (t *StagerTests) TestStageFiles() { assert := assert.New(t.Test) err := stager.Start() assert.Nil(err) + err = store.Start() // need one of these too + assert.Nil(err) + + // add a transfer to the store and begin staging the files + spec := Specification{ + Source: "test-source", + Destination: "test-destination", + FileIds: []string{"file1", "file2", "file3"}, + } + transferId, _, err := store.NewTransfer(spec) + assert.Nil(err) + + err = stager.StageFiles(transferId) + assert.Nil(err) + + time.Sleep(time.Second) + err = stager.Stop() assert.Nil(err) + err = store.Stop() + assert.Nil(err) } diff --git a/transfers/store.go b/transfers/store.go index 23f237e8..49cc2068 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -22,6 +22,9 @@ package transfers import ( + "cmp" + "slices" + "github.com/google/uuid" "github.com/kbase/dts/databases" @@ -175,22 +178,13 @@ func (s *storeState) process() { for running { select { case spec := <-store.Channels.RequestNewTransfer: - id := uuid.New() - source, err := databases.NewDatabase(spec.Source) + id, transfer, err := s.newTransfer(spec) if err != nil { store.Channels.Error <- err - break - } - descriptors, err := source.Descriptors(spec.User.Orcid, spec.FileIds) - if err != nil { - store.Channels.Error <- err - break - } - transfers[id] = transferStoreEntry{ - Descriptors: descriptors, - Spec: spec, + } else { + transfers[id] = transfer + store.Channels.ReturnNewTransfer <- id } - store.Channels.ReturnNewTransfer <- id case id := <-store.Channels.RequestDescriptors: if transfer, found := transfers[id]; found { store.Channels.ReturnDescriptors <- transfer.Descriptors @@ -207,6 +201,7 @@ func (s *storeState) process() { if transfer, found := transfers[idAndStatus.Id]; found { transfer.Status = idAndStatus.Status transfers[idAndStatus.Id] = transfer + store.Channels.Error <- nil } else { store.Channels.Error <- TransferNotFoundError{Id: idAndStatus.Id} } @@ -219,6 +214,7 @@ func (s *storeState) process() { case id := <-store.Channels.RequestRemoval: if _, found := transfers[id]; found { delete(transfers, id) + store.Channels.Error <- nil } else { store.Channels.Error <- TransferNotFoundError{Id: id} } @@ -235,3 +231,22 @@ type transferStoreEntry struct { Spec Specification Status TransferStatus } + +func (s *storeState) newTransfer(spec Specification) (uuid.UUID, transferStoreEntry, error) { + id := uuid.New() + source, err := databases.NewDatabase(spec.Source) + if err != nil { + return id, transferStoreEntry{}, err + } + descriptors, err := source.Descriptors(spec.User.Orcid, spec.FileIds) + if err != nil { + return id, transferStoreEntry{}, err + } + slices.SortFunc(descriptors, func(a, b map[string]any) int { + return cmp.Compare(a["id"].(string), b["id"].(string)) + }) + return id, transferStoreEntry{ + Descriptors: descriptors, + Spec: spec, + }, err +} diff --git a/transfers/store_test.go b/transfers/store_test.go index 4b531f85..c3293294 100644 --- a/transfers/store_test.go +++ b/transfers/store_test.go @@ -22,6 +22,8 @@ package transfers import ( + "cmp" + "slices" "testing" "github.com/google/uuid" @@ -29,15 +31,8 @@ import ( ) // We attach the tests to this type, which runs them one by one. -// NOTE: All tests are bookended by calls to the setup and breakdown functions in transfer_test.go type StoreTests struct{ Test *testing.T } -func TestStore(t *testing.T) { - tester := StoreTests{Test: t} - tester.TestStartAndStop() - // tester.TestNewTransfer() -} - func (t *StoreTests) TestStartAndStop() { assert := assert.New(t.Test) err := store.Start() @@ -52,30 +47,83 @@ func (t *StoreTests) TestNewTransfer() { err := store.Start() assert.Nil(err) - spec := Specification{} + spec := Specification{ + Source: "test-source", + Destination: "test-destination", + FileIds: []string{"file1", "file2", "file3"}, + } transferId, numFiles, err := store.NewTransfer(spec) assert.Nil(err) assert.NotEqual(uuid.UUID{}, transferId) - assert.Greater(0, numFiles) + assert.Equal(len(spec.FileIds), numFiles) spec1, err := store.GetSpecification(transferId) assert.Nil(err) assert.Equal(spec, spec1) - desc1 := make([]map[string]any, 0) + var desc1 []map[string]any + for _, d := range testDescriptors { + desc1 = append(desc1, d) + } + slices.SortFunc(desc1, func(a, b map[string]any) int { + return cmp.Compare(a["id"].(string), b["id"].(string)) + }) descriptors, err := store.GetDescriptors(transferId) assert.Nil(err) assert.Equal(desc1, descriptors) status, err := store.GetStatus(transferId) assert.Nil(err) - status.Code = TransferStatusStaging + assert.Equal(TransferStatusUnknown, status.Code) + + err = store.Stop() + assert.Nil(err) +} + +func (t *StoreTests) TestSetStatus() { + assert := assert.New(t.Test) + + err := store.Start() + assert.Nil(err) + + spec := Specification{ + Source: "test-source", + Destination: "test-destination", + FileIds: []string{"file1", "file2", "file3"}, + } + transferId, _, err := store.NewTransfer(spec) + assert.Nil(err) + + status, _ := store.GetStatus(transferId) err = store.SetStatus(transferId, status) assert.Nil(err) status1, err := store.GetStatus(transferId) assert.Nil(err) assert.Equal(status1, status) + status, err = store.GetStatus(uuid.New()) + assert.NotNil(err) + + err = store.Stop() + assert.Nil(err) +} + +func (t *StoreTests) TestRemove() { + assert := assert.New(t.Test) + + err := store.Start() + assert.Nil(err) + + spec := Specification{ + Source: "test-source", + Destination: "test-destination", + FileIds: []string{"file1", "file2", "file3"}, + } + transferId, _, err := store.NewTransfer(spec) + + err = store.Remove(transferId) + assert.Nil(err) + err = store.Stop() assert.Nil(err) } diff --git a/transfers/transfers.go b/transfers/transfers.go index 68a25620..1dfce127 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -130,15 +130,14 @@ func Running() bool { type Specification struct { // a Markdown description of the transfer task Description string - // the name of destination database to which files are transferred (as - // specified in the DTS config file) OR a custom destination spec (::) + // the name of destination database to which files are transferred (as specified in the config + // file) OR a custom destination spec (::) Destination string // machine-readable instructions for processing the payload at its destination Instructions map[string]any // an array of identifiers for files to be transferred from Source to Destination FileIds []string - // the name of source database from which files are transferred (as specified - // in the DTS config file) + // the name of source database from which files are transferred (as specified in the config file) Source string // information about the user requesting the task User auth.User diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 150ecbb7..a9171ed3 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -39,15 +39,40 @@ import ( "github.com/kbase/dts/dtstest" ) -// We attach the tests to this type, which runs them one by one. -type TransferTests struct{ Test *testing.T } - -func TestTransfers(t *testing.T) { - tester := TransferTests{Test: t} - tester.TestStartAndStop() +// this runner runs all tests for all the singletons in this package +func TestRunner(t *testing.T) { + storeTests := StoreTests{Test: t} + storeTests.TestStartAndStop() + storeTests.TestNewTransfer() + storeTests.TestSetStatus() + storeTests.TestRemove() + + stagerTests := StagerTests{Test: t} + print("stager start/stop\n") + stagerTests.TestStartAndStop() + print("stager stage files\n") + stagerTests.TestStageFiles() + + moverTests := MoverTests{Test: t} + print("mover start/stop\n") + moverTests.TestStartAndStop() + + manifestorTests := ManifestorTests{Test: t} + print("manifestor start/stop\n") + manifestorTests.TestStartAndStop() + + dispatcherTests := DispatcherTests{Test: t} + print("dispatcher start/stop\n") + dispatcherTests.TestStartAndStop() + + transfers := TransferTests{Test: t} + transfers.TestStartAndStop() //tester.TestStopAndRestartTransfers() } +// We attach the tests to this type, which runs them one by one. +type TransferTests struct{ Test *testing.T } + func (t *TransferTests) TestStartAndStop() { assert := assert.New(t.Test) @@ -126,37 +151,6 @@ func setup() { log.Panicf("Couldn't initialize configuration: %s", err) } - // create test resources - testDescriptors := map[string]map[string]any{ - "file1": { - "id": "file1", - "name": "file1.dat", - "path": "dir1/file1.dat", - "format": "text", - "bytes": 1024, - "hash": "d91f97974d06563cab48d4d43a17e08a", - "endpoint": "source-endpoint", - }, - "file2": { - "id": "file2", - "name": "file2.dat", - "path": "dir2/file2.dat", - "format": "text", - "bytes": 2048, - "hash": "d91f9e974d0e563cab48d4d43a17e08a", - "endpoint": "source-endpoint", - }, - "file3": { - "id": "file3", - "name": "file3.dat", - "path": "dir3/file3.dat", - "format": "text", - "bytes": 4096, - "hash": "e91f9e974d0e563cab48d4d43a17e08e", - "endpoint": "source-endpoint", - }, - } - // register test databases/endpoints referred to in config file dtstest.RegisterTestFixturesFromConfig(endpointOptions, testDescriptors) @@ -220,3 +214,33 @@ endpoints: provider: test root: DESTINATION_ROOT ` + +var testDescriptors map[string]map[string]any = map[string]map[string]any{ + "file1": { + "id": "file1", + "name": "file1.dat", + "path": "dir1/file1.dat", + "format": "text", + "bytes": 1024, + "hash": "d91f97974d06563cab48d4d43a17e08a", + "endpoint": "source-endpoint", + }, + "file2": { + "id": "file2", + "name": "file2.dat", + "path": "dir2/file2.dat", + "format": "text", + "bytes": 2048, + "hash": "d91f9e974d0e563cab48d4d43a17e08a", + "endpoint": "source-endpoint", + }, + "file3": { + "id": "file3", + "name": "file3.dat", + "path": "dir3/file3.dat", + "format": "text", + "bytes": 4096, + "hash": "e91f9e974d0e563cab48d4d43a17e08e", + "endpoint": "source-endpoint", + }, +} From 4287c9c24e4ebf4d6ffdd3ffe756924a987b3d2c Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Mon, 20 Oct 2025 16:00:39 -0700 Subject: [PATCH 08/28] Debugging tests. --- transfers/clock.go | 50 ++++++++++++--------- transfers/dispatcher.go | 88 ++++++++++++++++++------------------- transfers/errors.go | 10 +---- transfers/manifestor.go | 27 +++++++++--- transfers/mover.go | 83 ++++++++++++++++++++++------------ transfers/stager.go | 44 +++++++++++++------ transfers/stager_test.go | 6 +-- transfers/store.go | 23 ++++++++-- transfers/store_test.go | 7 ++- transfers/transfers_test.go | 79 +++++++++++++++++++++++---------- 10 files changed, 259 insertions(+), 158 deletions(-) diff --git a/transfers/clock.go b/transfers/clock.go index ab9805d6..6c86b6d7 100644 --- a/transfers/clock.go +++ b/transfers/clock.go @@ -7,44 +7,52 @@ import ( "github.com/kbase/dts/config" ) -// this singleton sends a pulse to transfer-related goroutines at the configured poll interval -var clock clockType +// This singleton sends a pulse to transfer-related goroutines at the configured poll interval. It's +// managed entirely by calls to its Subscribe and Unsubscribe methods. DO NOT ATTEMPT TO ACCESS ANY +// OTHER DATA. +var clock clockState -type clockType struct { - Initialized bool +type clockState struct { + Running bool Mutex sync.Mutex NumSubscribers int - Pulses []chan struct{} + Pulse chan struct{} Tick time.Duration } -// subscribes the caller to the clock, returning a new channel on which the pulse is sent -func (c *clockType) Subscribe() chan struct{} { +// Subscribes the caller to the clock, returning the channel on which the pulse is sent. This +// channel is closed when all the subscribers have unsubscribed. +func (c *clockState) Subscribe() chan struct{} { c.Mutex.Lock() - c.NumSubscribers++ - if len(c.Pulses) < c.NumSubscribers { - c.Pulses = append(c.Pulses, make(chan struct{})) - } else { - c.Pulses[c.NumSubscribers-1] = make(chan struct{}) - } - if !c.Initialized { + if !c.Running { c.Tick = time.Duration(config.Service.PollInterval) * time.Millisecond - c.Initialized = true go c.process() + c.Running = true + } + if c.NumSubscribers == 0 { + c.Pulse = make(chan struct{}, 32) } + c.NumSubscribers++ c.Mutex.Unlock() - return c.Pulses[len(c.Pulses)-1] + return c.Pulse } // unsubscribes the caller from the clock -func (c *clockType) Unsubscribe() { +func (c *clockState) Unsubscribe() { c.Mutex.Lock() c.NumSubscribers-- - close(c.Pulses[c.NumSubscribers]) + if c.NumSubscribers == 0 { + close(c.Pulse) + } + c.Running = false c.Mutex.Unlock() } -func (c *clockType) process() { +//---------------------------------------------------- +// everything past here runs in the clock's goroutine +//---------------------------------------------------- + +func (c *clockState) process() { c.Mutex.Lock() tick := c.Tick c.Mutex.Unlock() @@ -57,8 +65,8 @@ func (c *clockType) process() { break } c.Mutex.Unlock() - for i := range c.NumSubscribers { - c.Pulses[i] <- struct{}{} + for range c.NumSubscribers { + c.Pulse <- struct{}{} } } } diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index c782a9d1..7b37f7b5 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -65,6 +65,7 @@ func (channels *dispatcherChannels) close() { } func (d *dispatcherState) Start() error { + slog.Debug("dispatcher.Start()") d.Channels = dispatcherChannels{ RequestTransfer: make(chan Specification, 32), ReturnTransferId: make(chan uuid.UUID, 32), @@ -75,11 +76,11 @@ func (d *dispatcherState) Start() error { Stop: make(chan struct{}), } go d.process() - - return nil + return <-d.Channels.Error } func (d *dispatcherState) Stop() error { + slog.Debug("dispatcher.Stop") d.Channels.Stop <- struct{}{} err := <-d.Channels.Error d.Channels.close() @@ -87,17 +88,21 @@ func (d *dispatcherState) Stop() error { } func (d *dispatcherState) CreateTransfer(spec Specification) (uuid.UUID, error) { + slog.Debug("dispatcher.CreateTransfer") d.Channels.RequestTransfer <- spec select { case id := <-d.Channels.ReturnTransferId: + slog.Info(fmt.Sprintf("Created new transfer %s (%d file(s) requested)", id.String(), + len(spec.FileIds))) return id, nil case err := <-d.Channels.Error: return uuid.UUID{}, err } } -func (d *dispatcherState) GetTransferStatus(id uuid.UUID) (TransferStatus, error) { - d.Channels.RequestStatus <- id +func (d *dispatcherState) GetTransferStatus(transferId uuid.UUID) (TransferStatus, error) { + slog.Debug("dispatcher.GetTransferStatus") + d.Channels.RequestStatus <- transferId select { case status := <-d.Channels.ReturnStatus: return status, nil @@ -106,9 +111,15 @@ func (d *dispatcherState) GetTransferStatus(id uuid.UUID) (TransferStatus, error } } -func (d *dispatcherState) CancelTransfer(id uuid.UUID) error { - d.Channels.CancelTransfer <- id - return <-d.Channels.Error +func (d *dispatcherState) CancelTransfer(transferId uuid.UUID) error { + slog.Debug("dispatcher.CancelTransfer") + slog.Info(fmt.Sprintf("Canceling transfer %s", transferId.String())) + d.Channels.CancelTransfer <- transferId + err := <-d.Channels.Error + if err != nil { + slog.Error(fmt.Sprintf("Transfer %s: %s", transferId.String(), err.Error())) + } + return err } //--------------------------------------------------------- @@ -117,61 +128,46 @@ func (d *dispatcherState) CancelTransfer(id uuid.UUID) error { // the goroutine itself func (d *dispatcherState) process() { - - // client input channels - var newTransferRequested <-chan Specification = dispatcher.Channels.RequestTransfer - var cancellationRequested <-chan uuid.UUID = dispatcher.Channels.CancelTransfer - var statusRequested <-chan uuid.UUID = dispatcher.Channels.RequestStatus - var stopRequested <-chan struct{} = dispatcher.Channels.Stop - - // client output channels - var returnTransferId chan<- uuid.UUID = dispatcher.Channels.ReturnTransferId - var returnStatus chan<- TransferStatus = dispatcher.Channels.ReturnStatus - var returnError chan<- error = dispatcher.Channels.Error - - // respond to client requests running := true + d.Channels.Error <- nil + for running { select { - case spec := <-newTransferRequested: - transferId, numFiles, err := d.create(spec) + case spec := <-dispatcher.Channels.RequestTransfer: + transferId, err := d.create(spec) if err != nil { - returnError <- err - break + dispatcher.Channels.Error <- err + } else { + dispatcher.Channels.ReturnTransferId <- transferId } - returnTransferId <- transferId - slog.Info(fmt.Sprintf("Created new transfer %s (%d file(s) requested)", transferId.String(), - numFiles)) - case transferId := <-cancellationRequested: - slog.Info(fmt.Sprintf("Canceling transfer %s", transferId.String())) + case transferId := <-dispatcher.Channels.CancelTransfer: if err := d.cancel(transferId); err != nil { - slog.Error(fmt.Sprintf("Transfer %s: %s", transferId.String(), err.Error())) - returnError <- err + dispatcher.Channels.Error <- err } - case transferId := <-statusRequested: + case transferId := <-dispatcher.Channels.RequestStatus: status, err := store.GetStatus(transferId) if err != nil { - returnError <- err - break + dispatcher.Channels.Error <- err + } else { + dispatcher.Channels.ReturnStatus <- status } - returnStatus <- status - case <-stopRequested: + case <-dispatcher.Channels.Stop: running = false - returnError <- nil + dispatcher.Channels.Error <- nil } } } // creates a transfer from the given specification and starts things moving; returns a UUID for the // transfer, the number of files in the payload, and/or an error -func (d *dispatcherState) create(spec Specification) (uuid.UUID, int, error) { - transferId, numFiles, err := store.NewTransfer(spec) +func (d *dispatcherState) create(spec Specification) (uuid.UUID, error) { + transferId, err := store.NewTransfer(spec) if err != nil { - return uuid.UUID{}, 0, err + return uuid.UUID{}, err } descriptors, err := store.GetDescriptors(transferId) if err != nil { - return uuid.UUID{}, 0, err + return uuid.UUID{}, err } // do we need to stage files for the source database? @@ -180,11 +176,11 @@ func (d *dispatcherState) create(spec Specification) (uuid.UUID, int, error) { for source, descriptorsForSource := range descriptorsForEndpoint { sourceEndpoint, err := endpoints.NewEndpoint(source) if err != nil { - return uuid.UUID{}, 0, err + return uuid.UUID{}, err } filesStaged, err = sourceEndpoint.FilesStaged(descriptorsForSource) if err != nil { - return uuid.UUID{}, 0, err + return uuid.UUID{}, err } if !filesStaged { break @@ -192,12 +188,12 @@ func (d *dispatcherState) create(spec Specification) (uuid.UUID, int, error) { } if !filesStaged { - stager.StageFiles(transferId) + err = stager.StageFiles(transferId) } else { - mover.MoveFiles(transferId) + err = mover.MoveFiles(transferId) } - return transferId, numFiles, err + return transferId, err } func (d *dispatcherState) cancel(transferId uuid.UUID) error { diff --git a/transfers/errors.go b/transfers/errors.go index 47b22c54..0a481e07 100644 --- a/transfers/errors.go +++ b/transfers/errors.go @@ -29,15 +29,6 @@ import ( "github.com/kbase/dts/config" ) -// indicates that a transfer is sought but not found -type NotFoundError struct { - Id uuid.UUID -} - -func (t NotFoundError) Error() string { - return fmt.Sprintf("The transfer %s was not found.", t.Id.String()) -} - // indicates that Start() has been called when tasks are being processed type AlreadyRunningError struct{} @@ -76,6 +67,7 @@ func (e PayloadTooLargeError) Error() string { e.Size, config.Service.MaxPayloadSize) } +// indicates that the transfer with a given ID is not found type TransferNotFoundError struct { Id uuid.UUID } diff --git a/transfers/manifestor.go b/transfers/manifestor.go index 88c3f9a6..1b88261f 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -23,6 +23,7 @@ package transfers import ( "fmt" + "log/slog" "path/filepath" "time" @@ -66,6 +67,7 @@ func (channels *manifestorChannels) close() { // starts the mover func (m *manifestorState) Start() error { + slog.Debug("manifestor.Start") m.Channels = manifestorChannels{ RequestGeneration: make(chan uuid.UUID, 32), RequestCancellation: make(chan uuid.UUID, 32), @@ -74,11 +76,12 @@ func (m *manifestorState) Start() error { } m.Endpoints = make(map[string]endpoints.Endpoint) go m.process() - return nil + return <-m.Channels.Error } // stops the manifestor goroutine func (m *manifestorState) Stop() error { + slog.Debug("manifestor.Stop") m.Channels.Stop <- struct{}{} err := <-m.Channels.Error m.Channels.close() @@ -88,6 +91,7 @@ func (m *manifestorState) Stop() error { // starts generating a manifest for the given transfer, moving it subsequently to that transfer's // destination func (m *manifestorState) Generate(transferId uuid.UUID) error { + slog.Debug("manifestor.Generate") m.Channels.RequestGeneration <- transferId return <-mover.Channels.Error } @@ -95,6 +99,7 @@ func (m *manifestorState) Generate(transferId uuid.UUID) error { // cancels the generation/transfer of a manifest // destination func (m *manifestorState) Cancel(transferId uuid.UUID) error { + slog.Debug("manifestor.Cancel") m.Channels.RequestCancellation <- transferId return <-mover.Channels.Error } @@ -108,6 +113,7 @@ func (m *manifestorState) process() { running := true manifestTransfers := make(map[uuid.UUID]uuid.UUID) pulse := clock.Subscribe() + m.Channels.Error <- nil for running { select { @@ -125,7 +131,7 @@ func (m *manifestorState) process() { } manifestor.Channels.Error <- err } else { - manifestor.Channels.Error <- NotFoundError{Id: transferId} + manifestor.Channels.Error <- TransferNotFoundError{Id: transferId} } case <-pulse: // check the manifest transfers @@ -243,16 +249,27 @@ func (m *manifestorState) generateManifest(transferId uuid.UUID, spec Specificat // update the status of the manifest transfer with the given ID, returning true if the transfer has // completed (successfully or unsuccessfully), false otherwise func (m *manifestorState) updateStatus(transferId, manifestXferId uuid.UUID) (bool, error) { + oldStatus, err := store.GetStatus(transferId) + if err != nil { + return false, err + } + newStatus := oldStatus + source, err := endpoints.NewEndpoint(config.Service.Endpoint) if err != nil { return false, err } - status, err := source.Status(manifestXferId) + manifestStatus, err := source.Status(manifestXferId) if err != nil { return false, err } - store.SetStatus(transferId, status) - return status.Code == TransferStatusSucceeded || status.Code == TransferStatusFailed, nil + if manifestStatus.Code == TransferStatusSucceeded || manifestStatus.Code == TransferStatusFailed { + newStatus.Code = manifestStatus.Code + err = store.SetStatus(transferId, newStatus) + return true, err + } else { + return false, nil + } } func (m *manifestorState) cancel(manifestXferId uuid.UUID) error { diff --git a/transfers/mover.go b/transfers/mover.go index d9b61be0..9f0ddd88 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -22,10 +22,12 @@ package transfers import ( + "log/slog" "path/filepath" "github.com/google/uuid" + "github.com/kbase/dts/config" "github.com/kbase/dts/endpoints" ) @@ -59,6 +61,7 @@ func (channels *moverChannels) close() { // starts the mover func (m *moverState) Start() error { + slog.Debug("mover.Start") m.Channels = moverChannels{ RequestMove: make(chan uuid.UUID, 32), RequestCancellation: make(chan uuid.UUID, 32), @@ -67,11 +70,12 @@ func (m *moverState) Start() error { } m.Endpoints = make(map[string]endpoints.Endpoint) go m.process() - return nil + return <-m.Channels.Error } // stops the mover goroutine func (m *moverState) Stop() error { + slog.Debug("mover.Stop") m.Channels.Stop <- struct{}{} err := <-m.Channels.Error m.Channels.close() @@ -80,12 +84,14 @@ func (m *moverState) Stop() error { // starts moving files associated with the given transfer ID func (m *moverState) MoveFiles(transferId uuid.UUID) error { + slog.Debug("mover.MoveFiles") m.Channels.RequestMove <- transferId return <-mover.Channels.Error } // cancels a file move operation func (m *moverState) Cancel(transferId uuid.UUID) error { + slog.Debug("mover.Cancel") m.Channels.RequestCancellation <- transferId return <-mover.Channels.Error } @@ -99,15 +105,16 @@ func (m *moverState) process() { running := true moveOperations := make(map[uuid.UUID][]moveOperation) // a single transfer can be several move operations! pulse := clock.Subscribe() + mover.Channels.Error <- nil for running { select { case transferId := <-mover.Channels.RequestMove: - entries, err := m.start(transferId) - if err != nil { - mover.Channels.Error <- err + moves, err := m.moveFiles(transferId) + if err == nil { + moveOperations[transferId] = moves } - moveOperations[transferId] = entries + mover.Channels.Error <- err case transferId := <-mover.Channels.RequestCancellation: if moves, found := moveOperations[transferId]; found { err := m.cancel(moves) @@ -116,18 +123,19 @@ func (m *moverState) process() { } mover.Channels.Error <- err } else { - mover.Channels.Error <- NotFoundError{Id: transferId} + mover.Channels.Error <- TransferNotFoundError{Id: transferId} } case <-pulse: // check the move statuses and advance as needed for transferId, moves := range moveOperations { - completed, err := m.updateStatus(transferId, moves) - if err != nil { + if status, err := m.updateStatus(transferId, moves); err != nil { mover.Channels.Error <- err - continue - } - if completed { + } else if status.Code >= TransferStatusFinalizing { // finalizing or failed + if status.Code == TransferStatusFinalizing { + err = manifestor.Generate(transferId) + } delete(moveOperations, transferId) + mover.Channels.Error <- err } } case <-mover.Channels.Stop: @@ -146,7 +154,7 @@ type moveOperation struct { // starts moving files for the transfer with the given ID, returning one or more move operations, // depending on the number of relevant source endpoints -func (m *moverState) start(transferId uuid.UUID) ([]moveOperation, error) { +func (m *moverState) moveFiles(transferId uuid.UUID) ([]moveOperation, error) { spec, err := store.GetSpecification(transferId) if err != nil { return nil, err @@ -174,16 +182,28 @@ func (m *moverState) start(transferId uuid.UUID) ([]moveOperation, error) { if err != nil { return nil, err } - destinationEndpoint, err := endpoints.NewEndpoint(spec.Destination) + destination := config.Databases[spec.Destination].Endpoint + destinationEndpoint, err := endpoints.NewEndpoint(destination) if err != nil { return nil, err } - id, err := sourceEndpoint.Transfer(destinationEndpoint, files) + moveId, err := sourceEndpoint.Transfer(destinationEndpoint, files) if err != nil { return nil, err } + + // update the transfer status + if status, err := store.GetStatus(transferId); err == nil { + status.Code = TransferStatusActive + if err := store.SetStatus(transferId, status); err != nil { + return nil, err + } + } else { + return nil, err + } + moves = append(moves, moveOperation{ - Id: id, + Id: moveId, SourceEndpoint: source, DestinationEndpoint: spec.Destination, }) @@ -193,44 +213,51 @@ func (m *moverState) start(transferId uuid.UUID) ([]moveOperation, error) { // update the status of the transfer with the given ID given its distinct file move operations, // returning true if the transfer has completed (successfully or unsuccessfully), false otherwise -func (m *moverState) updateStatus(transferId uuid.UUID, moves []moveOperation) (bool, error) { - var transferStatus TransferStatus +func (m *moverState) updateStatus(transferId uuid.UUID, moves []moveOperation) (TransferStatus, error) { + oldStatus, err := store.GetStatus(transferId) + if err != nil { + return oldStatus, err + } + newStatus := oldStatus atLeastOneMoveFailed := false movesAllSucceeded := true for i, move := range moves { source, err := endpoints.NewEndpoint(move.SourceEndpoint) if err != nil { - return false, err + return oldStatus, err } - moveStatus, err := source.Status(transferId) + moveStatus, err := source.Status(move.Id) if err != nil { - return false, err + return oldStatus, err } - transferStatus.NumFiles += moveStatus.NumFiles - transferStatus.NumFilesTransferred += moveStatus.NumFilesTransferred - transferStatus.NumFilesSkipped += moveStatus.NumFilesSkipped + newStatus.NumFiles += moveStatus.NumFiles + newStatus.NumFilesTransferred += moveStatus.NumFilesTransferred + newStatus.NumFilesSkipped += moveStatus.NumFilesSkipped if moveStatus.Code == TransferStatusSucceeded { moves[i].Completed = true } else { movesAllSucceeded = false if moveStatus.Code == TransferStatusFailed { - transferStatus.Message = moveStatus.Message + newStatus.Message = moveStatus.Message atLeastOneMoveFailed = true moves[i].Completed = true } } } - // take stock and update + // take stock and update status as needed if movesAllSucceeded { - manifestor.Generate(transferId) + newStatus.Code = TransferStatusFinalizing } else if atLeastOneMoveFailed { - transferStatus.Code = TransferStatusFailed + newStatus.Code = TransferStatusFailed + } + if newStatus != oldStatus { + err = store.SetStatus(transferId, newStatus) } - return movesAllSucceeded || atLeastOneMoveFailed, store.SetStatus(transferId, transferStatus) + return newStatus, err } func (m *moverState) cancel(moves []moveOperation) error { diff --git a/transfers/stager.go b/transfers/stager.go index 2fb2bebc..a99fb9d0 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -22,6 +22,9 @@ package transfers import ( + "fmt" + "log/slog" + "github.com/google/uuid" "github.com/kbase/dts/databases" @@ -57,6 +60,7 @@ func (channels *stagerChannels) close() { // starts the stager func (s *stagerState) Start() error { + slog.Debug("stager.Start") s.Channels = stagerChannels{ RequestStaging: make(chan uuid.UUID, 32), RequestCancellation: make(chan uuid.UUID, 32), @@ -64,11 +68,12 @@ func (s *stagerState) Start() error { Stop: make(chan struct{}), } go s.process() - return nil + return <-s.Channels.Error } // stops the stager goroutine func (s *stagerState) Stop() error { + slog.Debug("stager.Stop") s.Channels.Stop <- struct{}{} err := <-s.Channels.Error s.Channels.close() @@ -77,12 +82,14 @@ func (s *stagerState) Stop() error { // requests that files be staged for the transfer with the given ID func (s *stagerState) StageFiles(id uuid.UUID) error { + slog.Debug("stager.StageFiles") s.Channels.RequestStaging <- id return <-s.Channels.Error } // cancels a file staging operation func (s *stagerState) Cancel(transferId uuid.UUID) error { + slog.Debug("stager.Cancel") s.Channels.RequestCancellation <- transferId return <-s.Channels.Error } @@ -96,6 +103,7 @@ func (s *stagerState) process() { running := true stagings := make(map[uuid.UUID]stagingEntry) pulse := clock.Subscribe() + s.Channels.Error <- nil for running { select { @@ -111,7 +119,7 @@ func (s *stagerState) process() { delete(stagings, transferId) // simply remove the entry and stop tracking file staging s.Channels.Error <- nil } else { - s.Channels.Error <- NotFoundError{Id: transferId} + s.Channels.Error <- TransferNotFoundError{Id: transferId} } case <-pulse: // check the staging status and advance to a transfer if it's finished @@ -158,27 +166,35 @@ func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) e return err } - status, err := source.StagingStatus(staging.Id) + stagingStatus, err := source.StagingStatus(staging.Id) if err != nil { return err } - switch status { + oldStatus, err := store.GetStatus(transferId) + if err == nil { + return err + } + newStatus := oldStatus + switch stagingStatus { case databases.StagingStatusSucceeded: - err := mover.MoveFiles(transferId) - if err != nil { - return err - } + newStatus.Code = TransferStatusActive case databases.StagingStatusFailed: - // FIXME: handle staging failures here! + newStatus.Code = TransferStatusFailed + newStatus.Message = fmt.Sprintf("file staging failed for transfer %s", transferId.String()) default: // still staging - xferStatus, err := store.GetStatus(transferId) - if err != nil { + newStatus.Code = TransferStatusStaging + } + + if newStatus.Code != oldStatus.Code { + if err := store.SetStatus(transferId, newStatus); err != nil { return err } - if xferStatus.Code != TransferStatusStaging { - xferStatus.Code = TransferStatusStaging - store.SetStatus(transferId, xferStatus) + } + + if newStatus.Code == TransferStatusActive { + if err := mover.MoveFiles(transferId); err != nil { + return err } } return nil diff --git a/transfers/stager_test.go b/transfers/stager_test.go index c195b2c7..543fc3e5 100644 --- a/transfers/stager_test.go +++ b/transfers/stager_test.go @@ -39,7 +39,7 @@ func (t *StagerTests) TestStartAndStop() { assert.Nil(err) } -func (t *StagerTests) TestStageFiles() { +func (t *StagerTests) fTestStageFiles() { assert := assert.New(t.Test) err := stager.Start() assert.Nil(err) @@ -52,13 +52,13 @@ func (t *StagerTests) TestStageFiles() { Destination: "test-destination", FileIds: []string{"file1", "file2", "file3"}, } - transferId, _, err := store.NewTransfer(spec) + transferId, err := store.NewTransfer(spec) assert.Nil(err) err = stager.StageFiles(transferId) assert.Nil(err) - time.Sleep(time.Second) + time.Sleep(10 * time.Second) err = stager.Stop() assert.Nil(err) diff --git a/transfers/store.go b/transfers/store.go index 49cc2068..11595b55 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -23,6 +23,8 @@ package transfers import ( "cmp" + "fmt" + "log/slog" "slices" "github.com/google/uuid" @@ -86,6 +88,7 @@ type transferIdAndStatus struct { // starts the store goroutine func (s *storeState) Start() error { + slog.Debug("store.Start") s.Channels = storeChannels{ RequestNewTransfer: make(chan Specification, 32), ReturnNewTransfer: make(chan uuid.UUID, 32), @@ -101,11 +104,12 @@ func (s *storeState) Start() error { Stop: make(chan struct{}), } go s.process() - return nil + return <-s.Channels.Error } // stops the store goroutine func (s *storeState) Stop() error { + slog.Debug("store.Stop") s.Channels.Stop <- struct{}{} err := <-s.Channels.Error s.Channels.close() @@ -114,17 +118,19 @@ func (s *storeState) Stop() error { // creates a new entry for a transfer within the store, populating it with relevant metadata and // returning a UUID, number of files, and/or error condition for the request -func (s *storeState) NewTransfer(spec Specification) (uuid.UUID, int, error) { +func (s *storeState) NewTransfer(spec Specification) (uuid.UUID, error) { + slog.Debug("store.NewTransfer") s.Channels.RequestNewTransfer <- spec select { case id := <-store.Channels.ReturnNewTransfer: - return id, len(spec.FileIds), nil + return id, nil case err := <-store.Channels.Error: - return uuid.UUID{}, 0, err + return uuid.UUID{}, err } } func (s *storeState) GetSpecification(transferId uuid.UUID) (Specification, error) { + slog.Debug(fmt.Sprintf("store.GetSpecification (%s)", transferId.String())) store.Channels.RequestSpec <- transferId select { case spec := <-store.Channels.ReturnSpec: @@ -135,6 +141,7 @@ func (s *storeState) GetSpecification(transferId uuid.UUID) (Specification, erro } func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, error) { + slog.Debug(fmt.Sprintf("store.GetDescriptors (%s)", transferId.String())) store.Channels.RequestDescriptors <- transferId select { case descriptors := <-store.Channels.ReturnDescriptors: @@ -145,6 +152,7 @@ func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, err } func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) error { + slog.Debug(fmt.Sprintf("store.SetStatus (%s, %d)", transferId.String(), status.Code)) s.Channels.SetStatus <- transferIdAndStatus{ Id: transferId, Status: status, @@ -153,6 +161,7 @@ func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) erro } func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { + slog.Debug(fmt.Sprintf("store.GetStatus (%s)", transferId.String())) s.Channels.RequestStatus <- transferId select { case status := <-store.Channels.ReturnStatus: @@ -163,6 +172,7 @@ func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { } func (s *storeState) Remove(transferId uuid.UUID) error { + slog.Debug(fmt.Sprintf("store.Remove (%s)", transferId.String())) s.Channels.RequestRemoval <- transferId return <-store.Channels.Error } @@ -175,6 +185,8 @@ func (s *storeState) Remove(transferId uuid.UUID) error { func (s *storeState) process() { running := true transfers := make(map[uuid.UUID]transferStoreEntry) + s.Channels.Error <- nil + for running { select { case spec := <-store.Channels.RequestNewTransfer: @@ -248,5 +260,8 @@ func (s *storeState) newTransfer(spec Specification) (uuid.UUID, transferStoreEn return id, transferStoreEntry{ Descriptors: descriptors, Spec: spec, + Status: TransferStatus{ + NumFiles: len(spec.FileIds), + }, }, err } diff --git a/transfers/store_test.go b/transfers/store_test.go index c3293294..27e23da3 100644 --- a/transfers/store_test.go +++ b/transfers/store_test.go @@ -52,10 +52,9 @@ func (t *StoreTests) TestNewTransfer() { Destination: "test-destination", FileIds: []string{"file1", "file2", "file3"}, } - transferId, numFiles, err := store.NewTransfer(spec) + transferId, err := store.NewTransfer(spec) assert.Nil(err) assert.NotEqual(uuid.UUID{}, transferId) - assert.Equal(len(spec.FileIds), numFiles) spec1, err := store.GetSpecification(transferId) assert.Nil(err) @@ -91,7 +90,7 @@ func (t *StoreTests) TestSetStatus() { Destination: "test-destination", FileIds: []string{"file1", "file2", "file3"}, } - transferId, _, err := store.NewTransfer(spec) + transferId, err := store.NewTransfer(spec) assert.Nil(err) status, _ := store.GetStatus(transferId) @@ -119,7 +118,7 @@ func (t *StoreTests) TestRemove() { Destination: "test-destination", FileIds: []string{"file1", "file2", "file3"}, } - transferId, _, err := store.NewTransfer(spec) + transferId, err := store.NewTransfer(spec) err = store.Remove(transferId) assert.Nil(err) diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index a9171ed3..dd41e60b 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -31,7 +31,7 @@ import ( "testing" "time" - //"github.com/google/uuid" + "github.com/google/uuid" "github.com/stretchr/testify/assert" //"github.com/kbase/dts/auth" @@ -41,32 +41,32 @@ import ( // this runner runs all tests for all the singletons in this package func TestRunner(t *testing.T) { - storeTests := StoreTests{Test: t} - storeTests.TestStartAndStop() - storeTests.TestNewTransfer() - storeTests.TestSetStatus() - storeTests.TestRemove() - - stagerTests := StagerTests{Test: t} - print("stager start/stop\n") - stagerTests.TestStartAndStop() - print("stager stage files\n") - stagerTests.TestStageFiles() - - moverTests := MoverTests{Test: t} - print("mover start/stop\n") - moverTests.TestStartAndStop() - - manifestorTests := ManifestorTests{Test: t} - print("manifestor start/stop\n") - manifestorTests.TestStartAndStop() - - dispatcherTests := DispatcherTests{Test: t} - print("dispatcher start/stop\n") - dispatcherTests.TestStartAndStop() + /* + storeTests := StoreTests{Test: t} + storeTests.TestStartAndStop() + storeTests.TestNewTransfer() + storeTests.TestSetStatus() + storeTests.TestRemove() + + stagerTests := StagerTests{Test: t} + stagerTests.TestStartAndStop() + stagerTests.TestStageFiles() + + moverTests := MoverTests{Test: t} + moverTests.TestStartAndStop() + + manifestorTests := ManifestorTests{Test: t} + print("manifestor start/stop\n") + manifestorTests.TestStartAndStop() + + dispatcherTests := DispatcherTests{Test: t} + print("dispatcher start/stop\n") + dispatcherTests.TestStartAndStop() + */ transfers := TransferTests{Test: t} transfers.TestStartAndStop() + transfers.TestCreate() //tester.TestStopAndRestartTransfers() } @@ -74,6 +74,7 @@ func TestRunner(t *testing.T) { type TransferTests struct{ Test *testing.T } func (t *TransferTests) TestStartAndStop() { + log.Print("=== TestStartAndStop ===") assert := assert.New(t.Test) assert.False(Running()) @@ -85,6 +86,36 @@ func (t *TransferTests) TestStartAndStop() { assert.False(Running()) } +func (t *TransferTests) TestCreate() { + log.Print("=== TestCreate ===") + assert := assert.New(t.Test) + err := Start() + assert.Nil(err) + assert.True(Running()) + + pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond + + transferId, err := Create(Specification{ + Destination: "test-destination", + FileIds: []string{"file1", "file2", "file3"}, + Source: "test-source", + }) + assert.Nil(err) + assert.NotEqual(uuid.UUID{}, transferId) + + status, err := Status(transferId) + assert.Nil(err) + assert.GreaterOrEqual(status.Code, TransferStatusStaging) + assert.Equal(3, status.NumFiles) + + time.Sleep(3 * pollInterval) + assert.GreaterOrEqual(status.Code, TransferStatusActive) + + err = Stop() + assert.Nil(err) + assert.False(Running()) +} + func (t *TransferTests) TestStopAndRestart() { /* assert := assert.New(t.Test) From 64701f0e552a7152b306f95ab44807767377a85a Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 21 Oct 2025 16:09:05 -0700 Subject: [PATCH 09/28] Fixed various issues with transfer tests. --- dtstest/dtstest.go | 2 +- transfers/dispatcher.go | 20 ++++++------- transfers/manifestor.go | 34 +++++++++++---------- transfers/mover.go | 26 ++++++++-------- transfers/stager.go | 9 +++--- transfers/store.go | 66 ++++++++++++++++++++--------------------- transfers/transfers.go | 1 - 7 files changed, 80 insertions(+), 78 deletions(-) diff --git a/dtstest/dtstest.go b/dtstest/dtstest.go index 21d95f30..4acf4780 100644 --- a/dtstest/dtstest.go +++ b/dtstest/dtstest.go @@ -179,7 +179,7 @@ func (ep *Endpoint) Status(id uuid.UUID) (endpoints.TransferStatus, error) { } return info.Status, nil } - return endpoints.TransferStatus{}, fmt.Errorf("invalid transfer ID: %s", id.String()) + return endpoints.TransferStatus{}, fmt.Errorf("invalid endpoint transfer ID: %s", id.String()) } func (ep *Endpoint) Cancel(id uuid.UUID) error { diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 7b37f7b5..010f5211 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -133,27 +133,27 @@ func (d *dispatcherState) process() { for running { select { - case spec := <-dispatcher.Channels.RequestTransfer: + case spec := <-d.Channels.RequestTransfer: transferId, err := d.create(spec) if err != nil { - dispatcher.Channels.Error <- err + d.Channels.Error <- err } else { - dispatcher.Channels.ReturnTransferId <- transferId + d.Channels.ReturnTransferId <- transferId } - case transferId := <-dispatcher.Channels.CancelTransfer: + case transferId := <-d.Channels.CancelTransfer: if err := d.cancel(transferId); err != nil { - dispatcher.Channels.Error <- err + d.Channels.Error <- err } - case transferId := <-dispatcher.Channels.RequestStatus: + case transferId := <-d.Channels.RequestStatus: status, err := store.GetStatus(transferId) if err != nil { - dispatcher.Channels.Error <- err + d.Channels.Error <- err } else { - dispatcher.Channels.ReturnStatus <- status + d.Channels.ReturnStatus <- status } - case <-dispatcher.Channels.Stop: + case <-d.Channels.Stop: running = false - dispatcher.Channels.Error <- nil + d.Channels.Error <- nil } } } diff --git a/transfers/manifestor.go b/transfers/manifestor.go index 1b88261f..d02e5bdf 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -65,7 +65,6 @@ func (channels *manifestorChannels) close() { close(channels.Stop) } -// starts the mover func (m *manifestorState) Start() error { slog.Debug("manifestor.Start") m.Channels = manifestorChannels{ @@ -93,7 +92,7 @@ func (m *manifestorState) Stop() error { func (m *manifestorState) Generate(transferId uuid.UUID) error { slog.Debug("manifestor.Generate") m.Channels.RequestGeneration <- transferId - return <-mover.Channels.Error + return <-m.Channels.Error } // cancels the generation/transfer of a manifest @@ -101,11 +100,11 @@ func (m *manifestorState) Generate(transferId uuid.UUID) error { func (m *manifestorState) Cancel(transferId uuid.UUID) error { slog.Debug("manifestor.Cancel") m.Channels.RequestCancellation <- transferId - return <-mover.Channels.Error + return <-m.Channels.Error } //---------------------------------------------------- -// everything past here runs in the mover's goroutine +// everything past here runs in the manifestor's goroutine //---------------------------------------------------- // the goroutine itself @@ -117,37 +116,37 @@ func (m *manifestorState) process() { for running { select { - case transferId := <-manifestor.Channels.RequestGeneration: + case transferId := <-m.Channels.RequestGeneration: manifestXferId, err := m.generateAndSendManifest(transferId) - if err != nil { - manifestor.Channels.Error <- err + if err == nil { + manifestTransfers[transferId] = manifestXferId } - manifestTransfers[transferId] = manifestXferId - case transferId := <-manifestor.Channels.RequestCancellation: + m.Channels.Error <- err + case transferId := <-m.Channels.RequestCancellation: if manifestXferId, found := manifestTransfers[transferId]; found { err := m.cancel(manifestXferId) if err == nil { delete(manifestTransfers, transferId) } - manifestor.Channels.Error <- err + m.Channels.Error <- err } else { - manifestor.Channels.Error <- TransferNotFoundError{Id: transferId} + m.Channels.Error <- TransferNotFoundError{Id: transferId} } case <-pulse: // check the manifest transfers for transferId, manifestXferId := range manifestTransfers { completed, err := m.updateStatus(transferId, manifestXferId) if err != nil { - mover.Channels.Error <- err + slog.Error(err.Error()) continue } if completed { delete(manifestTransfers, transferId) } } - case <-manifestor.Channels.Stop: + case <-m.Channels.Stop: running = false - manifestor.Channels.Error <- nil + m.Channels.Error <- nil } } clock.Unsubscribe() @@ -229,9 +228,14 @@ func (m *manifestorState) generateManifest(transferId uuid.UUID, spec Specificat } } + jsonDescriptors := make([]any, len(descriptors)) + for i, descriptor := range descriptors { + jsonDescriptors[i] = descriptor + } + packageDescriptor := map[string]any{ "name": "manifest", - "resources": descriptors, + "resources": jsonDescriptors, "created": time.Now().Format(time.RFC3339), "profile": "data-package", "keywords": []any{"dts", "manifest"}, diff --git a/transfers/mover.go b/transfers/mover.go index 9f0ddd88..edb87be9 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -86,14 +86,14 @@ func (m *moverState) Stop() error { func (m *moverState) MoveFiles(transferId uuid.UUID) error { slog.Debug("mover.MoveFiles") m.Channels.RequestMove <- transferId - return <-mover.Channels.Error + return <-m.Channels.Error } // cancels a file move operation func (m *moverState) Cancel(transferId uuid.UUID) error { slog.Debug("mover.Cancel") m.Channels.RequestCancellation <- transferId - return <-mover.Channels.Error + return <-m.Channels.Error } //---------------------------------------------------- @@ -105,42 +105,44 @@ func (m *moverState) process() { running := true moveOperations := make(map[uuid.UUID][]moveOperation) // a single transfer can be several move operations! pulse := clock.Subscribe() - mover.Channels.Error <- nil + m.Channels.Error <- nil for running { select { - case transferId := <-mover.Channels.RequestMove: + case transferId := <-m.Channels.RequestMove: moves, err := m.moveFiles(transferId) if err == nil { moveOperations[transferId] = moves } - mover.Channels.Error <- err - case transferId := <-mover.Channels.RequestCancellation: + m.Channels.Error <- err + case transferId := <-m.Channels.RequestCancellation: if moves, found := moveOperations[transferId]; found { err := m.cancel(moves) if err == nil { delete(moveOperations, transferId) } - mover.Channels.Error <- err + m.Channels.Error <- err } else { - mover.Channels.Error <- TransferNotFoundError{Id: transferId} + m.Channels.Error <- TransferNotFoundError{Id: transferId} } case <-pulse: // check the move statuses and advance as needed for transferId, moves := range moveOperations { if status, err := m.updateStatus(transferId, moves); err != nil { - mover.Channels.Error <- err + slog.Error(err.Error()) } else if status.Code >= TransferStatusFinalizing { // finalizing or failed if status.Code == TransferStatusFinalizing { err = manifestor.Generate(transferId) + if err != nil { + slog.Error(err.Error()) + } } delete(moveOperations, transferId) - mover.Channels.Error <- err } } - case <-mover.Channels.Stop: + case <-m.Channels.Stop: running = false - mover.Channels.Error <- nil + m.Channels.Error <- nil } } clock.Unsubscribe() diff --git a/transfers/stager.go b/transfers/stager.go index a99fb9d0..47ce7b9e 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -109,12 +109,11 @@ func (s *stagerState) process() { select { case transferId := <-s.Channels.RequestStaging: entry, err := s.stageFiles(transferId) - if err != nil { - s.Channels.Error <- err + if err == nil { + stagings[transferId] = entry } - stagings[transferId] = entry s.Channels.Error <- nil - case transferId := <-mover.Channels.RequestCancellation: + case transferId := <-s.Channels.RequestCancellation: if _, found := stagings[transferId]; found { delete(stagings, transferId) // simply remove the entry and stop tracking file staging s.Channels.Error <- nil @@ -125,7 +124,7 @@ func (s *stagerState) process() { // check the staging status and advance to a transfer if it's finished for transferId, staging := range stagings { if err := s.updateStatus(transferId, staging); err != nil { - s.Channels.Error <- err + slog.Error(err.Error()) } } case <-s.Channels.Stop: diff --git a/transfers/store.go b/transfers/store.go index 11595b55..0bb207f3 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -86,7 +86,6 @@ type transferIdAndStatus struct { Status TransferStatus } -// starts the store goroutine func (s *storeState) Start() error { slog.Debug("store.Start") s.Channels = storeChannels{ @@ -107,7 +106,6 @@ func (s *storeState) Start() error { return <-s.Channels.Error } -// stops the store goroutine func (s *storeState) Stop() error { slog.Debug("store.Stop") s.Channels.Stop <- struct{}{} @@ -122,31 +120,31 @@ func (s *storeState) NewTransfer(spec Specification) (uuid.UUID, error) { slog.Debug("store.NewTransfer") s.Channels.RequestNewTransfer <- spec select { - case id := <-store.Channels.ReturnNewTransfer: + case id := <-s.Channels.ReturnNewTransfer: return id, nil - case err := <-store.Channels.Error: + case err := <-s.Channels.Error: return uuid.UUID{}, err } } func (s *storeState) GetSpecification(transferId uuid.UUID) (Specification, error) { slog.Debug(fmt.Sprintf("store.GetSpecification (%s)", transferId.String())) - store.Channels.RequestSpec <- transferId + s.Channels.RequestSpec <- transferId select { - case spec := <-store.Channels.ReturnSpec: + case spec := <-s.Channels.ReturnSpec: return spec, nil - case err := <-store.Channels.Error: + case err := <-s.Channels.Error: return Specification{}, err } } func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, error) { slog.Debug(fmt.Sprintf("store.GetDescriptors (%s)", transferId.String())) - store.Channels.RequestDescriptors <- transferId + s.Channels.RequestDescriptors <- transferId select { - case descriptors := <-store.Channels.ReturnDescriptors: + case descriptors := <-s.Channels.ReturnDescriptors: return descriptors, nil - case err := <-store.Channels.Error: + case err := <-s.Channels.Error: return nil, err } } @@ -157,16 +155,16 @@ func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) erro Id: transferId, Status: status, } - return <-store.Channels.Error + return <-s.Channels.Error } func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { slog.Debug(fmt.Sprintf("store.GetStatus (%s)", transferId.String())) s.Channels.RequestStatus <- transferId select { - case status := <-store.Channels.ReturnStatus: + case status := <-s.Channels.ReturnStatus: return status, nil - case err := <-store.Channels.Error: + case err := <-s.Channels.Error: return TransferStatus{}, err } } @@ -174,7 +172,7 @@ func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { func (s *storeState) Remove(transferId uuid.UUID) error { slog.Debug(fmt.Sprintf("store.Remove (%s)", transferId.String())) s.Channels.RequestRemoval <- transferId - return <-store.Channels.Error + return <-s.Channels.Error } //---------------------------------------------------- @@ -189,49 +187,49 @@ func (s *storeState) process() { for running { select { - case spec := <-store.Channels.RequestNewTransfer: + case spec := <-s.Channels.RequestNewTransfer: id, transfer, err := s.newTransfer(spec) if err != nil { - store.Channels.Error <- err + s.Channels.Error <- err } else { transfers[id] = transfer - store.Channels.ReturnNewTransfer <- id + s.Channels.ReturnNewTransfer <- id } - case id := <-store.Channels.RequestDescriptors: + case id := <-s.Channels.RequestDescriptors: if transfer, found := transfers[id]; found { - store.Channels.ReturnDescriptors <- transfer.Descriptors + s.Channels.ReturnDescriptors <- transfer.Descriptors } else { - store.Channels.Error <- TransferNotFoundError{Id: id} + s.Channels.Error <- TransferNotFoundError{Id: id} } - case id := <-store.Channels.RequestSpec: + case id := <-s.Channels.RequestSpec: if transfer, found := transfers[id]; found { - store.Channels.ReturnSpec <- transfer.Spec + s.Channels.ReturnSpec <- transfer.Spec } else { - store.Channels.Error <- TransferNotFoundError{Id: id} + s.Channels.Error <- TransferNotFoundError{Id: id} } - case idAndStatus := <-store.Channels.SetStatus: + case idAndStatus := <-s.Channels.SetStatus: if transfer, found := transfers[idAndStatus.Id]; found { transfer.Status = idAndStatus.Status transfers[idAndStatus.Id] = transfer - store.Channels.Error <- nil + s.Channels.Error <- nil } else { - store.Channels.Error <- TransferNotFoundError{Id: idAndStatus.Id} + s.Channels.Error <- TransferNotFoundError{Id: idAndStatus.Id} } - case id := <-store.Channels.RequestStatus: + case id := <-s.Channels.RequestStatus: if transfer, found := transfers[id]; found { - store.Channels.ReturnStatus <- transfer.Status + s.Channels.ReturnStatus <- transfer.Status } else { - store.Channels.Error <- TransferNotFoundError{Id: id} + s.Channels.Error <- TransferNotFoundError{Id: id} } - case id := <-store.Channels.RequestRemoval: + case id := <-s.Channels.RequestRemoval: if _, found := transfers[id]; found { delete(transfers, id) - store.Channels.Error <- nil + s.Channels.Error <- nil } else { - store.Channels.Error <- TransferNotFoundError{Id: id} + s.Channels.Error <- TransferNotFoundError{Id: id} } - case <-store.Channels.Stop: - store.Channels.Error <- nil + case <-s.Channels.Stop: + s.Channels.Error <- nil running = false } } diff --git a/transfers/transfers.go b/transfers/transfers.go index 1dfce127..e915bafc 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -73,7 +73,6 @@ func Start() error { return err } if err := registerDatabases(); err != nil { - print("pooped 'em!\n") return err } global.Started = true From 831f634791a4e7956149d68b9874bb9198eab31f Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Wed, 22 Oct 2025 12:08:43 -0700 Subject: [PATCH 10/28] Fixed an issue with JSON descriptors. --- tasks/task.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tasks/task.go b/tasks/task.go index 6e0f0bdf..3ed40305 100644 --- a/tasks/task.go +++ b/tasks/task.go @@ -383,9 +383,14 @@ func (task *transferTask) createManifest() (*datapackage.Package, error) { } } + jsonDescriptors := make([]any, len(descriptors)) + for i, descriptor := range descriptors { + jsonDescriptors[i] = descriptor + } + descriptor := map[string]any{ "name": "manifest", - "resources": descriptors, + "resources": jsonDescriptors, "created": time.Now().Format(time.RFC3339), "profile": "data-package", "keywords": []any{"dts", "manifest"}, From 6cc3d8db48878f8f90e8382427efe6b204e10a30 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Wed, 22 Oct 2025 15:29:23 -0700 Subject: [PATCH 11/28] Added transfer status callbacks useful for testing. --- services/prototype.go | 4 +- transfers/store.go | 11 ++++- transfers/transfers.go | 82 ++++++++++++++++++++++--------------- transfers/transfers_test.go | 45 ++++++++++++++++---- 4 files changed, 97 insertions(+), 45 deletions(-) diff --git a/services/prototype.go b/services/prototype.go index 433cceb2..e58378e1 100644 --- a/services/prototype.go +++ b/services/prototype.go @@ -136,8 +136,8 @@ func (service *prototype) Close() { // Version numbers var majorVersion = 0 -var minorVersion = 9 -var patchVersion = 7 +var minorVersion = 10 +var patchVersion = 0 // Version string var version = fmt.Sprintf("%d.%d.%d", majorVersion, minorVersion, patchVersion) diff --git a/transfers/store.go b/transfers/store.go index 0bb207f3..04f95f49 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -210,6 +210,9 @@ func (s *storeState) process() { case idAndStatus := <-s.Channels.SetStatus: if transfer, found := transfers[idAndStatus.Id]; found { transfer.Status = idAndStatus.Status + for _, callback := range global.Callbacks { + callback(idAndStatus.Id, idAndStatus.Status) + } transfers[idAndStatus.Id] = transfer s.Channels.Error <- nil } else { @@ -255,11 +258,15 @@ func (s *storeState) newTransfer(spec Specification) (uuid.UUID, transferStoreEn slices.SortFunc(descriptors, func(a, b map[string]any) int { return cmp.Compare(a["id"].(string), b["id"].(string)) }) - return id, transferStoreEntry{ + entry := transferStoreEntry{ Descriptors: descriptors, Spec: spec, Status: TransferStatus{ NumFiles: len(spec.FileIds), }, - }, err + } + for _, callback := range global.Callbacks { // new transfers have TransferStatusUnknown + callback(id, entry.Status) + } + return id, entry, err } diff --git a/transfers/transfers.go b/transfers/transfers.go index e915bafc..a5b2c3a3 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -60,6 +60,27 @@ const ( TransferStatusSucceeded = endpoints.TransferStatusSucceeded ) +// A StatusCallback function is called when a transfer a given ID is created or has its status +// updated. Every new transfer gets created with status TransferStatusUnknown. Callbacks of this +// sort can be registered before Start() is called, and can be useful for testing. +type StatusCallback = func(transferId uuid.UUID, transferStatus TransferStatus) + +// Registers a status callback function with the transfer orchestrator. If this function is called +// while Running() returns true, it returns AlreadyRunningError. Callbacks remain registered after +// Stop() has been called. +func RegisterStatusCallback(callback StatusCallback) error { + if global.Running { + return &AlreadyRunningError{} + } + global.Callbacks = append(global.Callbacks, callback) + return nil +} + +// unregisters all status callback functions +func ClearStatusCallbacks() { + clear(global.Callbacks) +} + // starts processing transfers according to the given configuration, returning an // informative error if anything prevents this func Start() error { @@ -93,7 +114,19 @@ func Start() error { return err } - if err := startOrchestration(); err != nil { + if err := store.Start(); err != nil { + return err + } + if err := dispatcher.Start(); err != nil { + return err + } + if err := stager.Start(); err != nil { + return err + } + if err := mover.Start(); err != nil { + return err + } + if err := manifestor.Start(); err != nil { return err } @@ -107,7 +140,19 @@ func Start() error { func Stop() error { var err error if global.Running { - if err := stopOrchestration(); err != nil { + if err := stager.Stop(); err != nil { + return err + } + if err := mover.Stop(); err != nil { + return err + } + if err := manifestor.Stop(); err != nil { + return err + } + if err := store.Stop(); err != nil { + return err + } + if err := dispatcher.Stop(); err != nil { return err } if err = journal.Finalize(); err != nil { @@ -190,6 +235,7 @@ func Cancel(transferId uuid.UUID) error { // globals var global struct { Running, Started bool + Callbacks []StatusCallback } //----------------------------------------------- @@ -300,38 +346,6 @@ func validateDirectory(dirType, dir string) error { // the correct destination // * store: maintains metadata records and status info for ongoing and completed transfers -func startOrchestration() error { - if err := dispatcher.Start(); err != nil { - return err - } - if err := stager.Start(); err != nil { - return err - } - if err := mover.Start(); err != nil { - return err - } - if err := manifestor.Start(); err != nil { - return err - } - return store.Start() -} - -func stopOrchestration() error { - if err := stager.Stop(); err != nil { - return err - } - if err := mover.Stop(); err != nil { - return err - } - if err := manifestor.Stop(); err != nil { - return err - } - if err := store.Stop(); err != nil { - return err - } - return dispatcher.Stop() -} - // resolves the given destination (name) string, accounting for custom transfers func destinationEndpoint(destination string) (endpoints.Endpoint, error) { // everything's been validated at this point, so no need to check for errors diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index dd41e60b..7316e225 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -66,7 +66,7 @@ func TestRunner(t *testing.T) { transfers := TransferTests{Test: t} transfers.TestStartAndStop() - transfers.TestCreate() + transfers.TestCreateWithoutStaging() //tester.TestStopAndRestartTransfers() } @@ -86,14 +86,37 @@ func (t *TransferTests) TestStartAndStop() { assert.False(Running()) } -func (t *TransferTests) TestCreate() { +func (t *TransferTests) TestCreateWithoutStaging() { log.Print("=== TestCreate ===") assert := assert.New(t.Test) - err := Start() + + // record the task's journey through the aether + var journey struct { + Unknown, Staging, Active, Failed, Finalizing, Inactive, Succeeded bool + } + err := RegisterStatusCallback(func(id uuid.UUID, status TransferStatus) { + switch status.Code { + case TransferStatusUnknown: + journey.Unknown = true + case TransferStatusStaging: + journey.Staging = true + case TransferStatusActive: + journey.Active = true + case TransferStatusFailed: + journey.Failed = true + case TransferStatusFinalizing: + journey.Finalizing = true + case TransferStatusInactive: + journey.Inactive = true + case TransferStatusSucceeded: + journey.Succeeded = true + } + }) assert.Nil(err) - assert.True(Running()) - pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond + err = Start() + assert.Nil(err) + assert.True(Running()) transferId, err := Create(Specification{ Destination: "test-destination", @@ -108,8 +131,16 @@ func (t *TransferTests) TestCreate() { assert.GreaterOrEqual(status.Code, TransferStatusStaging) assert.Equal(3, status.NumFiles) - time.Sleep(3 * pollInterval) - assert.GreaterOrEqual(status.Code, TransferStatusActive) + time.Sleep(2 * time.Second) + + // how'd our journey go, friend? + assert.True(journey.Unknown) + assert.False(journey.Staging) + assert.True(journey.Active) + assert.False(journey.Failed) + assert.True(journey.Finalizing) + assert.False(journey.Inactive) + assert.True(journey.Succeeded) err = Stop() assert.Nil(err) From ebc9d0892c2a7eb9a599dab4dc33411001429e4e Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Fri, 24 Oct 2025 13:41:57 -0700 Subject: [PATCH 12/28] Replaced callbacks with a simple publish/subscribe system that delivers messages. This mechanism is useful for testing and can also be wired into a message queue down the line. --- transfers/manifestor.go | 12 +++++- transfers/messages.go | 84 +++++++++++++++++++++++++++++++++++++ transfers/mover.go | 13 +++++- transfers/stager.go | 14 +++++-- transfers/store.go | 15 ++++--- transfers/transfers.go | 26 ++---------- transfers/transfers_test.go | 52 ++++++++++++++--------- 7 files changed, 162 insertions(+), 54 deletions(-) create mode 100644 transfers/messages.go diff --git a/transfers/manifestor.go b/transfers/manifestor.go index d02e5bdf..c64f2797 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -269,8 +269,16 @@ func (m *manifestorState) updateStatus(transferId, manifestXferId uuid.UUID) (bo } if manifestStatus.Code == TransferStatusSucceeded || manifestStatus.Code == TransferStatusFailed { newStatus.Code = manifestStatus.Code - err = store.SetStatus(transferId, newStatus) - return true, err + if err := store.SetStatus(transferId, newStatus); err != nil { + return true, err + } + publish(Message{ + Description: newStatus.Message, + TransferId: transferId, + TransferStatus: newStatus, + Time: time.Now(), + }) + return true, nil } else { return false, nil } diff --git a/transfers/messages.go b/transfers/messages.go new file mode 100644 index 00000000..37685aee --- /dev/null +++ b/transfers/messages.go @@ -0,0 +1,84 @@ +package transfers + +import ( + "errors" + "fmt" + "slices" + "sync" + "time" + + "github.com/google/uuid" +) + +// This small publish/subscribe subsystem allows other DTS packages to subscribe to a feed that +// publishes messages related to the transfer of files and metadata. + +// A message sent to a subscriber relating to a transfer +type Message struct { + Description string + TransferId uuid.UUID + TransferStatus TransferStatus + Time time.Time +} + +// A subscription that identifies the subscriber and provides it with a channel on which to receive +// messages +type Subscription struct { + Id int + Channel <-chan Message +} + +// Subscribes the caller to the transfer message feed, returning a channel with the given buffer +// size on which messages are queued. +func Subscribe(bufferSize int) Subscription { + messageBroker_.Mutex.Lock() + subscriberId := len(messageBroker_.Subscriptions) + messageBroker_.Subscriptions = append(messageBroker_.Subscriptions, make(chan Message, bufferSize)) + sub := Subscription{ + Id: subscriberId, + Channel: messageBroker_.Subscriptions[subscriberId], + } + messageBroker_.Mutex.Unlock() + return sub +} + +// Unsubscribes the caller from its messages. +func Unsubscribe(sub Subscription) error { + messageBroker_.Mutex.Lock() + if sub.Id < 0 || sub.Id >= len(messageBroker_.Subscriptions) { + return errors.New(fmt.Sprintf("invalid subscription ID: %d\n", sub.Id)) + } + close(messageBroker_.Subscriptions[sub.Id]) + messageBroker_.Subscriptions = slices.Delete(messageBroker_.Subscriptions, sub.Id, sub.Id) + messageBroker_.Mutex.Unlock() + return nil +} + +// Publishes the given message to all subscribers. +func publish(message Message) error { + if message.TransferStatus.Code < TransferStatusUnknown || message.TransferStatus.Code > TransferStatusFailed { + return errors.New("invalid transfer status for message") + } + messageBroker_.Mutex.Lock() + for _, channel := range messageBroker_.Subscriptions { + channel <- message + } + messageBroker_.Mutex.Unlock() + return nil +} + +// Unsubscribes all subscribers. +func unsubscribeAll() error { + messageBroker_.Mutex.Lock() + for _, channel := range messageBroker_.Subscriptions { + close(channel) + } + messageBroker_.Subscriptions = nil + messageBroker_.Mutex.Unlock() + return nil +} + +var messageBroker_ struct { + Mutex sync.Mutex + Subscriptions []chan Message +} diff --git a/transfers/mover.go b/transfers/mover.go index edb87be9..405100e6 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -24,6 +24,7 @@ package transfers import ( "log/slog" "path/filepath" + "time" "github.com/google/uuid" @@ -256,10 +257,18 @@ func (m *moverState) updateStatus(transferId uuid.UUID, moves []moveOperation) ( newStatus.Code = TransferStatusFailed } if newStatus != oldStatus { - err = store.SetStatus(transferId, newStatus) + if err := store.SetStatus(transferId, newStatus); err != nil { + return newStatus, err + } + publish(Message{ + Description: newStatus.Message, + TransferId: transferId, + TransferStatus: newStatus, + Time: time.Now(), + }) } - return newStatus, err + return newStatus, nil } func (m *moverState) cancel(moves []moveOperation) error { diff --git a/transfers/stager.go b/transfers/stager.go index 47ce7b9e..29f22b3a 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -24,6 +24,7 @@ package transfers import ( "fmt" "log/slog" + "time" "github.com/google/uuid" @@ -62,9 +63,9 @@ func (channels *stagerChannels) close() { func (s *stagerState) Start() error { slog.Debug("stager.Start") s.Channels = stagerChannels{ - RequestStaging: make(chan uuid.UUID, 32), - RequestCancellation: make(chan uuid.UUID, 32), - Error: make(chan error, 32), + RequestStaging: make(chan uuid.UUID, 31), + RequestCancellation: make(chan uuid.UUID, 31), + Error: make(chan error, 31), Stop: make(chan struct{}), } go s.process() @@ -177,6 +178,7 @@ func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) e newStatus := oldStatus switch stagingStatus { case databases.StagingStatusSucceeded: + newStatus.Message = fmt.Sprintf("file staging succeeded for transfer %s", transferId.String()) newStatus.Code = TransferStatusActive case databases.StagingStatusFailed: newStatus.Code = TransferStatusFailed @@ -189,6 +191,12 @@ func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) e if err := store.SetStatus(transferId, newStatus); err != nil { return err } + publish(Message{ + Description: newStatus.Message, + TransferId: transferId, + TransferStatus: newStatus, + Time: time.Now(), + }) } if newStatus.Code == TransferStatusActive { diff --git a/transfers/store.go b/transfers/store.go index 04f95f49..b3dda39c 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -26,6 +26,7 @@ import ( "fmt" "log/slog" "slices" + "time" "github.com/google/uuid" @@ -210,9 +211,6 @@ func (s *storeState) process() { case idAndStatus := <-s.Channels.SetStatus: if transfer, found := transfers[idAndStatus.Id]; found { transfer.Status = idAndStatus.Status - for _, callback := range global.Callbacks { - callback(idAndStatus.Id, idAndStatus.Status) - } transfers[idAndStatus.Id] = transfer s.Channels.Error <- nil } else { @@ -265,8 +263,13 @@ func (s *storeState) newTransfer(spec Specification) (uuid.UUID, transferStoreEn NumFiles: len(spec.FileIds), }, } - for _, callback := range global.Callbacks { // new transfers have TransferStatusUnknown - callback(id, entry.Status) - } + + publish(Message{ + Description: "New transfer created.", + TransferId: id, + TransferStatus: entry.Status, + Time: time.Now(), + }) + return id, entry, err } diff --git a/transfers/transfers.go b/transfers/transfers.go index a5b2c3a3..eae1e411 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -60,27 +60,6 @@ const ( TransferStatusSucceeded = endpoints.TransferStatusSucceeded ) -// A StatusCallback function is called when a transfer a given ID is created or has its status -// updated. Every new transfer gets created with status TransferStatusUnknown. Callbacks of this -// sort can be registered before Start() is called, and can be useful for testing. -type StatusCallback = func(transferId uuid.UUID, transferStatus TransferStatus) - -// Registers a status callback function with the transfer orchestrator. If this function is called -// while Running() returns true, it returns AlreadyRunningError. Callbacks remain registered after -// Stop() has been called. -func RegisterStatusCallback(callback StatusCallback) error { - if global.Running { - return &AlreadyRunningError{} - } - global.Callbacks = append(global.Callbacks, callback) - return nil -} - -// unregisters all status callback functions -func ClearStatusCallbacks() { - clear(global.Callbacks) -} - // starts processing transfers according to the given configuration, returning an // informative error if anything prevents this func Start() error { @@ -140,6 +119,10 @@ func Start() error { func Stop() error { var err error if global.Running { + if err := unsubscribeAll(); err != nil { + return err + } + if err := stager.Stop(); err != nil { return err } @@ -235,7 +218,6 @@ func Cancel(transferId uuid.UUID) error { // globals var global struct { Running, Started bool - Callbacks []StatusCallback } //----------------------------------------------- diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 7316e225..4931ee79 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -94,27 +94,41 @@ func (t *TransferTests) TestCreateWithoutStaging() { var journey struct { Unknown, Staging, Active, Failed, Finalizing, Inactive, Succeeded bool } - err := RegisterStatusCallback(func(id uuid.UUID, status TransferStatus) { - switch status.Code { - case TransferStatusUnknown: - journey.Unknown = true - case TransferStatusStaging: - journey.Staging = true - case TransferStatusActive: - journey.Active = true - case TransferStatusFailed: - journey.Failed = true - case TransferStatusFinalizing: - journey.Finalizing = true - case TransferStatusInactive: - journey.Inactive = true - case TransferStatusSucceeded: - journey.Succeeded = true + subscription := Subscribe(32) + go func() { + var finished bool + for !finished { + message := <-subscription.Channel + switch message.TransferStatus.Code { + case TransferStatusUnknown: + assert.False(journey.Staging || journey.Active || journey.Failed || journey.Finalizing || + journey.Inactive || journey.Succeeded) + journey.Unknown = true + case TransferStatusStaging: + assert.False(journey.Active || journey.Failed || journey.Finalizing || + journey.Inactive || journey.Succeeded) + journey.Staging = true + case TransferStatusActive: + assert.False(journey.Failed || journey.Finalizing || journey.Inactive || journey.Succeeded) + journey.Active = true + case TransferStatusFailed: + assert.False(journey.Finalizing || journey.Inactive || journey.Succeeded) + journey.Failed = true + case TransferStatusFinalizing: + assert.False(journey.Failed || journey.Inactive || journey.Succeeded) + journey.Finalizing = true + case TransferStatusInactive: + assert.False(journey.Failed || journey.Succeeded) + journey.Inactive = true + case TransferStatusSucceeded: + assert.False(journey.Failed) + journey.Succeeded = true + finished = true + } } - }) - assert.Nil(err) + }() - err = Start() + err := Start() assert.Nil(err) assert.True(Running()) From 8376abe15564bbe3bccfc267bf5300a406f87229 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Mon, 27 Oct 2025 16:14:35 -0700 Subject: [PATCH 13/28] Implemented saving and loading. --- transfers/dispatcher.go | 100 ++++++++++++++++++++++++++- transfers/errors.go | 10 +++ transfers/manifestor.go | 65 ++++++++++++------ transfers/manifestor_test.go | 39 ----------- transfers/mover.go | 56 ++++++++++----- transfers/mover_test.go | 39 ----------- transfers/stager.go | 84 ++++++++++++++++------- transfers/stager_test.go | 67 ------------------ transfers/store.go | 94 +++++++++++++++++-------- transfers/store_test.go | 128 ----------------------------------- transfers/transfers.go | 31 ++------- transfers/transfers_test.go | 95 +++++++++++--------------- 12 files changed, 360 insertions(+), 448 deletions(-) delete mode 100644 transfers/manifestor_test.go delete mode 100644 transfers/mover_test.go delete mode 100644 transfers/stager_test.go delete mode 100644 transfers/store_test.go diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 010f5211..90e04157 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -22,11 +22,16 @@ package transfers import ( + "encoding/gob" "fmt" "log/slog" + "os" + "path/filepath" "github.com/google/uuid" + "github.com/kbase/dts/config" + "github.com/kbase/dts/databases" "github.com/kbase/dts/endpoints" ) @@ -129,7 +134,7 @@ func (d *dispatcherState) CancelTransfer(transferId uuid.UUID) error { // the goroutine itself func (d *dispatcherState) process() { running := true - d.Channels.Error <- nil + d.Channels.Error <- d.start() for running { select { @@ -152,12 +157,62 @@ func (d *dispatcherState) process() { d.Channels.ReturnStatus <- status } case <-d.Channels.Stop: + err := d.stop() + d.Channels.Error <- err running = false - d.Channels.Error <- nil } } } +func (d *dispatcherState) start() error { + saveFilename := filepath.Join(config.Service.DataDirectory, "dts.gob") + saveFile, err := os.Open(saveFilename) + if err != nil { // no save file -- fresh start + slog.Debug("no previous transfers found") + if err := store.Start(); err != nil { + return err + } + if err := stager.Start(); err != nil { + return err + } + if err := mover.Start(); err != nil { + return err + } + if err := manifestor.Start(); err != nil { + return err + } + } + + slog.Debug(fmt.Sprintf("found previous tasks in %s", saveFilename)) + defer saveFile.Close() + decoder := gob.NewDecoder(saveFile) + var databaseStates databases.DatabaseSaveStates + if err := decoder.Decode(&databaseStates); err == nil { + if err = databases.Load(databaseStates); err != nil { + slog.Error(fmt.Sprintf("Restoring database states: %s", err.Error())) + } + if err := store.Load(decoder); err != nil { + return err + } + if err := stager.Load(decoder); err != nil { + return err + } + if err := mover.Load(decoder); err != nil { + return err + } + if err := manifestor.Load(decoder); err != nil { + return err + } + } else { + return &SaveFileError{ + Filename: saveFilename, + Message: fmt.Sprintf("Reading save file: %s", err.Error()), + } + } + slog.Debug(fmt.Sprintf("Restored transfers from %s", saveFilename)) + return nil +} + // creates a transfer from the given specification and starts things moving; returns a UUID for the // transfer, the number of files in the payload, and/or an error func (d *dispatcherState) create(spec Specification) (uuid.UUID, error) { @@ -213,3 +268,44 @@ func (d *dispatcherState) cancel(transferId uuid.UUID) error { } return nil } + +func (d *dispatcherState) stop() error { + // save states into a file using a gob encoder + saveFilename := filepath.Join(config.Service.DataDirectory, "dts.gob") + saveFile, err := os.OpenFile(saveFilename, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return &SaveFileError{ + Filename: saveFilename, + Message: fmt.Sprintf("Opening save file: %s", err.Error()), + } + } + + encoder := gob.NewEncoder(saveFile) + if databaseStates, err := databases.Save(); err == nil { + err = encoder.Encode(databaseStates) + if err := stager.SaveAndStop(encoder); err != nil { + os.Remove(saveFilename) + return err + } + if err := mover.SaveAndStop(encoder); err != nil { + os.Remove(saveFilename) + return err + } + if err := manifestor.SaveAndStop(encoder); err != nil { + os.Remove(saveFilename) + return err + } + if err := store.SaveAndStop(encoder); err != nil { + os.Remove(saveFilename) + return err + } + slog.Debug(fmt.Sprintf("saving transfer data to %s", saveFilename)) + } else { + return &SaveFileError{ + Filename: saveFilename, + Message: fmt.Sprintf("Writing save file: %s", err.Error()), + } + } + + return err +} diff --git a/transfers/errors.go b/transfers/errors.go index 0a481e07..d1093ad7 100644 --- a/transfers/errors.go +++ b/transfers/errors.go @@ -67,6 +67,16 @@ func (e PayloadTooLargeError) Error() string { e.Size, config.Service.MaxPayloadSize) } +// indicates an error encountered in saving transfer data to a persistent file for loading later +type SaveFileError struct { + Filename string + Message string +} + +func (e SaveFileError) Error() string { + return fmt.Sprintf("%s (save file: %s)", e.Message, e.Filename) +} + // indicates that the transfer with a given ID is not found type TransferNotFoundError struct { Id uuid.UUID diff --git a/transfers/manifestor.go b/transfers/manifestor.go index c64f2797..d091a5e0 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -22,6 +22,7 @@ package transfers import ( + "encoding/gob" "fmt" "log/slog" "path/filepath" @@ -55,33 +56,45 @@ type manifestorChannels struct { RequestGeneration chan uuid.UUID RequestCancellation chan uuid.UUID Error chan error - Stop chan struct{} + SaveAndStop chan *gob.Encoder +} + +func newManifestorChannels() manifestorChannels { + return manifestorChannels{ + RequestGeneration: make(chan uuid.UUID, 32), + RequestCancellation: make(chan uuid.UUID, 32), + Error: make(chan error, 32), + SaveAndStop: make(chan *gob.Encoder), + } } func (channels *manifestorChannels) close() { close(channels.RequestGeneration) close(channels.RequestCancellation) close(channels.Error) - close(channels.Stop) + close(channels.SaveAndStop) } func (m *manifestorState) Start() error { slog.Debug("manifestor.Start") - m.Channels = manifestorChannels{ - RequestGeneration: make(chan uuid.UUID, 32), - RequestCancellation: make(chan uuid.UUID, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), - } + m.Channels = newManifestorChannels() m.Endpoints = make(map[string]endpoints.Endpoint) - go m.process() + go m.process(nil) + return <-m.Channels.Error +} + +func (m *manifestorState) Load(decoder *gob.Decoder) error { + slog.Debug("manifestor.Load") + m.Channels = newManifestorChannels() + m.Endpoints = make(map[string]endpoints.Endpoint) + go m.process(decoder) return <-m.Channels.Error } // stops the manifestor goroutine -func (m *manifestorState) Stop() error { +func (m *manifestorState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("manifestor.Stop") - m.Channels.Stop <- struct{}{} + m.Channels.SaveAndStop <- encoder err := <-m.Channels.Error m.Channels.close() return err @@ -107,10 +120,20 @@ func (m *manifestorState) Cancel(transferId uuid.UUID) error { // everything past here runs in the manifestor's goroutine //---------------------------------------------------- -// the goroutine itself -func (m *manifestorState) process() { +// the goroutine itself (accepts optional decoder for loading saved data) +func (m *manifestorState) process(decoder *gob.Decoder) { + // load or create transfer records + var transfers map[uuid.UUID]uuid.UUID + if decoder != nil { + if err := decoder.Decode(&transfers); err != nil { + m.Channels.Error <- err + return + } + } else { + transfers = make(map[uuid.UUID]uuid.UUID) + } + running := true - manifestTransfers := make(map[uuid.UUID]uuid.UUID) pulse := clock.Subscribe() m.Channels.Error <- nil @@ -119,14 +142,14 @@ func (m *manifestorState) process() { case transferId := <-m.Channels.RequestGeneration: manifestXferId, err := m.generateAndSendManifest(transferId) if err == nil { - manifestTransfers[transferId] = manifestXferId + transfers[transferId] = manifestXferId } m.Channels.Error <- err case transferId := <-m.Channels.RequestCancellation: - if manifestXferId, found := manifestTransfers[transferId]; found { + if manifestXferId, found := transfers[transferId]; found { err := m.cancel(manifestXferId) if err == nil { - delete(manifestTransfers, transferId) + delete(transfers, transferId) } m.Channels.Error <- err } else { @@ -134,19 +157,19 @@ func (m *manifestorState) process() { } case <-pulse: // check the manifest transfers - for transferId, manifestXferId := range manifestTransfers { + for transferId, manifestXferId := range transfers { completed, err := m.updateStatus(transferId, manifestXferId) if err != nil { slog.Error(err.Error()) continue } if completed { - delete(manifestTransfers, transferId) + delete(transfers, transferId) } } - case <-m.Channels.Stop: + case encoder := <-m.Channels.SaveAndStop: + m.Channels.Error <- encoder.Encode(transfers) running = false - m.Channels.Error <- nil } } clock.Unsubscribe() diff --git a/transfers/manifestor_test.go b/transfers/manifestor_test.go deleted file mode 100644 index e70a74bb..00000000 --- a/transfers/manifestor_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package transfers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// We attach the tests to this type, which runs them one by one. -type ManifestorTests struct{ Test *testing.T } - -func (t *ManifestorTests) TestStartAndStop() { - assert := assert.New(t.Test) - err := manifestor.Start() - assert.Nil(err) - err = manifestor.Stop() - assert.Nil(err) -} diff --git a/transfers/mover.go b/transfers/mover.go index 405100e6..899bffbe 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -22,6 +22,7 @@ package transfers import ( + "encoding/gob" "log/slog" "path/filepath" "time" @@ -50,34 +51,47 @@ type moverChannels struct { RequestMove chan uuid.UUID RequestCancellation chan uuid.UUID Error chan error - Stop chan struct{} + SaveAndStop chan *gob.Encoder +} + +func newMoverChannels() moverChannels { + return moverChannels{ + RequestMove: make(chan uuid.UUID, 32), + RequestCancellation: make(chan uuid.UUID, 32), + Error: make(chan error, 32), + SaveAndStop: make(chan *gob.Encoder), + } } func (channels *moverChannels) close() { close(channels.RequestMove) close(channels.RequestCancellation) close(channels.Error) - close(channels.Stop) + close(channels.SaveAndStop) } // starts the mover func (m *moverState) Start() error { slog.Debug("mover.Start") - m.Channels = moverChannels{ - RequestMove: make(chan uuid.UUID, 32), - RequestCancellation: make(chan uuid.UUID, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), - } + m.Channels = newMoverChannels() m.Endpoints = make(map[string]endpoints.Endpoint) - go m.process() + go m.process(nil) + return <-m.Channels.Error +} + +// loads the mover from saved data +func (m *moverState) Load(decoder *gob.Decoder) error { + slog.Debug("mover.Start") + m.Channels = newMoverChannels() + m.Endpoints = make(map[string]endpoints.Endpoint) + go m.process(decoder) return <-m.Channels.Error } // stops the mover goroutine -func (m *moverState) Stop() error { +func (m *moverState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("mover.Stop") - m.Channels.Stop <- struct{}{} + m.Channels.SaveAndStop <- encoder err := <-m.Channels.Error m.Channels.close() return err @@ -102,9 +116,19 @@ func (m *moverState) Cancel(transferId uuid.UUID) error { //---------------------------------------------------- // the goroutine itself -func (m *moverState) process() { +func (m *moverState) process(decoder *gob.Decoder) { + // load or create move operation records + var moveOperations map[uuid.UUID][]moveOperation // a single transfer is one or more moves + if decoder != nil { + if err := decoder.Decode(&moveOperations); err != nil { + m.Channels.Error <- err + return + } + } else { + moveOperations = make(map[uuid.UUID][]moveOperation) + } + running := true - moveOperations := make(map[uuid.UUID][]moveOperation) // a single transfer can be several move operations! pulse := clock.Subscribe() m.Channels.Error <- nil @@ -127,7 +151,7 @@ func (m *moverState) process() { m.Channels.Error <- TransferNotFoundError{Id: transferId} } case <-pulse: - // check the move statuses and advance as needed + // check the move statuses and advance as needed, purging records as needed for transferId, moves := range moveOperations { if status, err := m.updateStatus(transferId, moves); err != nil { slog.Error(err.Error()) @@ -141,9 +165,9 @@ func (m *moverState) process() { delete(moveOperations, transferId) } } - case <-m.Channels.Stop: + case encoder := <-m.Channels.SaveAndStop: + m.Channels.Error <- encoder.Encode(moveOperations) running = false - m.Channels.Error <- nil } } clock.Unsubscribe() diff --git a/transfers/mover_test.go b/transfers/mover_test.go deleted file mode 100644 index 8e5feccf..00000000 --- a/transfers/mover_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package transfers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// We attach the tests to this type, which runs them one by one. -type MoverTests struct{ Test *testing.T } - -func (t *MoverTests) TestStartAndStop() { - assert := assert.New(t.Test) - err := mover.Start() - assert.Nil(err) - err = mover.Stop() - assert.Nil(err) -} diff --git a/transfers/stager.go b/transfers/stager.go index 29f22b3a..b23b90d7 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -22,6 +22,7 @@ package transfers import ( + "encoding/gob" "fmt" "log/slog" "time" @@ -49,33 +50,45 @@ type stagerChannels struct { RequestStaging chan uuid.UUID RequestCancellation chan uuid.UUID Error chan error - Stop chan struct{} + SaveAndStop chan *gob.Encoder +} + +func newStagerChannels() stagerChannels { + return stagerChannels{ + RequestStaging: make(chan uuid.UUID, 31), + RequestCancellation: make(chan uuid.UUID, 31), + Error: make(chan error, 31), + SaveAndStop: make(chan *gob.Encoder), + } } func (channels *stagerChannels) close() { close(channels.RequestStaging) close(channels.RequestCancellation) close(channels.Error) - close(channels.Stop) + close(channels.SaveAndStop) } // starts the stager func (s *stagerState) Start() error { slog.Debug("stager.Start") - s.Channels = stagerChannels{ - RequestStaging: make(chan uuid.UUID, 31), - RequestCancellation: make(chan uuid.UUID, 31), - Error: make(chan error, 31), - Stop: make(chan struct{}), - } - go s.process() + s.Channels = newStagerChannels() + go s.process(nil) + return <-s.Channels.Error +} + +// loads the stager from saved data +func (s *stagerState) Load(decoder *gob.Decoder) error { + slog.Debug("stager.Load") + s.Channels = newStagerChannels() + go s.process(decoder) return <-s.Channels.Error } // stops the stager goroutine -func (s *stagerState) Stop() error { +func (s *stagerState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("stager.Stop") - s.Channels.Stop <- struct{}{} + s.Channels.SaveAndStop <- encoder err := <-s.Channels.Error s.Channels.close() return err @@ -100,9 +113,19 @@ func (s *stagerState) Cancel(transferId uuid.UUID) error { //---------------------------------------------------- // the goroutine itself -func (s *stagerState) process() { +func (s *stagerState) process(decoder *gob.Decoder) { + // load or create staging records + var stagings map[uuid.UUID]stagingEntry + if decoder != nil { + if err := decoder.Decode(&stagings); err != nil { + s.Channels.Error <- err + return + } + } else { + stagings = make(map[uuid.UUID]stagingEntry) + } + running := true - stagings := make(map[uuid.UUID]stagingEntry) pulse := clock.Subscribe() s.Channels.Error <- nil @@ -122,15 +145,19 @@ func (s *stagerState) process() { s.Channels.Error <- TransferNotFoundError{Id: transferId} } case <-pulse: - // check the staging status and advance to a transfer if it's finished + // check the staging status and advance to a transfer if it's finished, purging its record for transferId, staging := range stagings { - if err := s.updateStatus(transferId, staging); err != nil { + if completed, err := s.updateStatus(transferId, staging); err == nil { + if completed { + delete(stagings, transferId) + } + } else { slog.Error(err.Error()) } } - case <-s.Channels.Stop: + case encoder := <-s.Channels.SaveAndStop: + s.Channels.Error <- encoder.Encode(stagings) running = false - s.Channels.Error <- nil } } clock.Unsubscribe() @@ -156,40 +183,45 @@ func (s *stagerState) stageFiles(transferId uuid.UUID) (stagingEntry, error) { return stagingEntry{Id: id}, nil } -func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) error { +// update transfer status, returning true iff completed +func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) (bool, error) { spec, err := store.GetSpecification(transferId) if err != nil { - return err + return false, err } source, err := databases.NewDatabase(spec.Source) if err != nil { - return err + return false, err } stagingStatus, err := source.StagingStatus(staging.Id) if err != nil { - return err + return false, err } oldStatus, err := store.GetStatus(transferId) - if err == nil { - return err + if err != nil { + return false, err } + + completed := false newStatus := oldStatus switch stagingStatus { case databases.StagingStatusSucceeded: newStatus.Message = fmt.Sprintf("file staging succeeded for transfer %s", transferId.String()) newStatus.Code = TransferStatusActive + completed = true case databases.StagingStatusFailed: newStatus.Code = TransferStatusFailed newStatus.Message = fmt.Sprintf("file staging failed for transfer %s", transferId.String()) + completed = true default: // still staging newStatus.Code = TransferStatusStaging } if newStatus.Code != oldStatus.Code { if err := store.SetStatus(transferId, newStatus); err != nil { - return err + return completed, err } publish(Message{ Description: newStatus.Message, @@ -201,8 +233,8 @@ func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) e if newStatus.Code == TransferStatusActive { if err := mover.MoveFiles(transferId); err != nil { - return err + return completed, err } } - return nil + return completed, nil } diff --git a/transfers/stager_test.go b/transfers/stager_test.go deleted file mode 100644 index 543fc3e5..00000000 --- a/transfers/stager_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package transfers - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -// We attach the tests to this type, which runs them one by one using logic in transfers_test.go. -type StagerTests struct{ Test *testing.T } - -func (t *StagerTests) TestStartAndStop() { - assert := assert.New(t.Test) - err := stager.Start() - assert.Nil(err) - err = stager.Stop() - assert.Nil(err) -} - -func (t *StagerTests) fTestStageFiles() { - assert := assert.New(t.Test) - err := stager.Start() - assert.Nil(err) - err = store.Start() // need one of these too - assert.Nil(err) - - // add a transfer to the store and begin staging the files - spec := Specification{ - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2", "file3"}, - } - transferId, err := store.NewTransfer(spec) - assert.Nil(err) - - err = stager.StageFiles(transferId) - assert.Nil(err) - - time.Sleep(10 * time.Second) - - err = stager.Stop() - assert.Nil(err) - err = store.Stop() - assert.Nil(err) -} diff --git a/transfers/store.go b/transfers/store.go index b3dda39c..1e05c71b 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -23,6 +23,7 @@ package transfers import ( "cmp" + "encoding/gob" "fmt" "log/slog" "slices" @@ -30,6 +31,7 @@ import ( "github.com/google/uuid" + "github.com/kbase/dts/config" "github.com/kbase/dts/databases" ) @@ -63,8 +65,25 @@ type storeChannels struct { RequestRemoval chan uuid.UUID - Error chan error - Stop chan struct{} + Error chan error + SaveAndStop chan *gob.Encoder +} + +func newStoreChannels() storeChannels { + return storeChannels{ + RequestNewTransfer: make(chan Specification, 32), + ReturnNewTransfer: make(chan uuid.UUID, 32), + RequestSpec: make(chan uuid.UUID, 32), + ReturnSpec: make(chan Specification, 32), + RequestDescriptors: make(chan uuid.UUID, 32), + ReturnDescriptors: make(chan []map[string]any, 32), + SetStatus: make(chan transferIdAndStatus, 32), + RequestStatus: make(chan uuid.UUID, 32), + ReturnStatus: make(chan TransferStatus, 32), + RequestRemoval: make(chan uuid.UUID, 32), + Error: make(chan error, 32), + SaveAndStop: make(chan *gob.Encoder), + } } func (channels *storeChannels) close() { @@ -79,7 +98,7 @@ func (channels *storeChannels) close() { close(channels.ReturnStatus) close(channels.RequestRemoval) close(channels.Error) - close(channels.Stop) + close(channels.SaveAndStop) } type transferIdAndStatus struct { @@ -89,27 +108,21 @@ type transferIdAndStatus struct { func (s *storeState) Start() error { slog.Debug("store.Start") - s.Channels = storeChannels{ - RequestNewTransfer: make(chan Specification, 32), - ReturnNewTransfer: make(chan uuid.UUID, 32), - RequestSpec: make(chan uuid.UUID, 32), - ReturnSpec: make(chan Specification, 32), - RequestDescriptors: make(chan uuid.UUID, 32), - ReturnDescriptors: make(chan []map[string]any, 32), - SetStatus: make(chan transferIdAndStatus, 32), - RequestStatus: make(chan uuid.UUID, 32), - ReturnStatus: make(chan TransferStatus, 32), - RequestRemoval: make(chan uuid.UUID, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), - } - go s.process() + s.Channels = newStoreChannels() + go s.process(nil) return <-s.Channels.Error } -func (s *storeState) Stop() error { +func (s *storeState) Load(decoder *gob.Decoder) error { + slog.Debug("store.Start") + s.Channels = newStoreChannels() + go s.process(decoder) + return <-s.Channels.Error +} + +func (s *storeState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("store.Stop") - s.Channels.Stop <- struct{}{} + s.Channels.SaveAndStop <- encoder err := <-s.Channels.Error s.Channels.close() return err @@ -181,11 +194,25 @@ func (s *storeState) Remove(transferId uuid.UUID) error { //---------------------------------------------------- // the goroutine itself -func (s *storeState) process() { +func (s *storeState) process(decoder *gob.Decoder) { + // load or create transfer records + var transfers map[uuid.UUID]transferStoreEntry + if decoder != nil { + if err := decoder.Decode(&transfers); err != nil { + s.Channels.Error <- err + return + } + } else { + transfers = make(map[uuid.UUID]transferStoreEntry) + } + running := true - transfers := make(map[uuid.UUID]transferStoreEntry) + pulse := clock.Subscribe() s.Channels.Error <- nil + // time period after which to delete completed transfers + deleteAfter := time.Duration(config.Service.DeleteAfter) * time.Second + for running { select { case spec := <-s.Channels.RequestNewTransfer: @@ -211,6 +238,9 @@ func (s *storeState) process() { case idAndStatus := <-s.Channels.SetStatus: if transfer, found := transfers[idAndStatus.Id]; found { transfer.Status = idAndStatus.Status + if idAndStatus.Status.Code == TransferStatusFailed || idAndStatus.Status.Code == TransferStatusSucceeded { + transfer.CompletionTime = time.Now() + } transfers[idAndStatus.Id] = transfer s.Channels.Error <- nil } else { @@ -229,8 +259,17 @@ func (s *storeState) process() { } else { s.Channels.Error <- TransferNotFoundError{Id: id} } - case <-s.Channels.Stop: - s.Channels.Error <- nil + case <-pulse: // prune old records + for id, transfer := range transfers { + if transfer.Status.Code == TransferStatusFailed || transfer.Status.Code == TransferStatusSucceeded { + if time.Since(transfer.CompletionTime) > deleteAfter { + slog.Debug("*plonk*") + delete(transfers, id) + } + } + } + case encoder := <-s.Channels.SaveAndStop: + s.Channels.Error <- encoder.Encode(transfers) running = false } } @@ -238,9 +277,10 @@ func (s *storeState) process() { // an entry in the transfer metadata store type transferStoreEntry struct { - Descriptors []map[string]any - Spec Specification - Status TransferStatus + CompletionTime time.Time + Descriptors []map[string]any + Spec Specification + Status TransferStatus } func (s *storeState) newTransfer(spec Specification) (uuid.UUID, transferStoreEntry, error) { diff --git a/transfers/store_test.go b/transfers/store_test.go deleted file mode 100644 index 27e23da3..00000000 --- a/transfers/store_test.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package transfers - -import ( - "cmp" - "slices" - "testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -// We attach the tests to this type, which runs them one by one. -type StoreTests struct{ Test *testing.T } - -func (t *StoreTests) TestStartAndStop() { - assert := assert.New(t.Test) - err := store.Start() - assert.Nil(err) - err = store.Stop() - assert.Nil(err) -} - -func (t *StoreTests) TestNewTransfer() { - assert := assert.New(t.Test) - - err := store.Start() - assert.Nil(err) - - spec := Specification{ - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2", "file3"}, - } - transferId, err := store.NewTransfer(spec) - assert.Nil(err) - assert.NotEqual(uuid.UUID{}, transferId) - - spec1, err := store.GetSpecification(transferId) - assert.Nil(err) - assert.Equal(spec, spec1) - - var desc1 []map[string]any - for _, d := range testDescriptors { - desc1 = append(desc1, d) - } - slices.SortFunc(desc1, func(a, b map[string]any) int { - return cmp.Compare(a["id"].(string), b["id"].(string)) - }) - descriptors, err := store.GetDescriptors(transferId) - assert.Nil(err) - assert.Equal(desc1, descriptors) - - status, err := store.GetStatus(transferId) - assert.Nil(err) - assert.Equal(TransferStatusUnknown, status.Code) - - err = store.Stop() - assert.Nil(err) -} - -func (t *StoreTests) TestSetStatus() { - assert := assert.New(t.Test) - - err := store.Start() - assert.Nil(err) - - spec := Specification{ - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2", "file3"}, - } - transferId, err := store.NewTransfer(spec) - assert.Nil(err) - - status, _ := store.GetStatus(transferId) - err = store.SetStatus(transferId, status) - assert.Nil(err) - status1, err := store.GetStatus(transferId) - assert.Nil(err) - assert.Equal(status1, status) - - status, err = store.GetStatus(uuid.New()) - assert.NotNil(err) - - err = store.Stop() - assert.Nil(err) -} - -func (t *StoreTests) TestRemove() { - assert := assert.New(t.Test) - - err := store.Start() - assert.Nil(err) - - spec := Specification{ - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2", "file3"}, - } - transferId, err := store.NewTransfer(spec) - - err = store.Remove(transferId) - assert.Nil(err) - - err = store.Stop() - assert.Nil(err) -} diff --git a/transfers/transfers.go b/transfers/transfers.go index eae1e411..37b4cf87 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -48,6 +48,7 @@ type Database = databases.Database type Endpoint = endpoints.Endpoint type FileTransfer = endpoints.FileTransfer type TransferStatus = endpoints.TransferStatus +type TransferStatusCode = endpoints.TransferStatusCode // useful constants const ( @@ -93,21 +94,10 @@ func Start() error { return err } - if err := store.Start(); err != nil { - return err - } + // start the dispatcher, which starts everything else if err := dispatcher.Start(); err != nil { return err } - if err := stager.Start(); err != nil { - return err - } - if err := mover.Start(); err != nil { - return err - } - if err := manifestor.Start(); err != nil { - return err - } global.Running = true @@ -119,23 +109,10 @@ func Start() error { func Stop() error { var err error if global.Running { - if err := unsubscribeAll(); err != nil { - return err - } - - if err := stager.Stop(); err != nil { - return err - } - if err := mover.Stop(); err != nil { - return err - } - if err := manifestor.Stop(); err != nil { - return err - } - if err := store.Stop(); err != nil { + if err := dispatcher.Stop(); err != nil { return err } - if err := dispatcher.Stop(); err != nil { + if err := unsubscribeAll(); err != nil { return err } if err = journal.Finalize(); err != nil { diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 4931ee79..3fb56935 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -25,8 +25,11 @@ package transfers import ( + "cmp" "log" "os" + "path/filepath" + "slices" "strings" "testing" "time" @@ -42,23 +45,6 @@ import ( // this runner runs all tests for all the singletons in this package func TestRunner(t *testing.T) { /* - storeTests := StoreTests{Test: t} - storeTests.TestStartAndStop() - storeTests.TestNewTransfer() - storeTests.TestSetStatus() - storeTests.TestRemove() - - stagerTests := StagerTests{Test: t} - stagerTests.TestStartAndStop() - stagerTests.TestStageFiles() - - moverTests := MoverTests{Test: t} - moverTests.TestStartAndStop() - - manifestorTests := ManifestorTests{Test: t} - print("manifestor start/stop\n") - manifestorTests.TestStartAndStop() - dispatcherTests := DispatcherTests{Test: t} print("dispatcher start/stop\n") dispatcherTests.TestStartAndStop() @@ -90,41 +76,17 @@ func (t *TransferTests) TestCreateWithoutStaging() { log.Print("=== TestCreate ===") assert := assert.New(t.Test) - // record the task's journey through the aether - var journey struct { - Unknown, Staging, Active, Failed, Finalizing, Inactive, Succeeded bool - } + saveFilename := filepath.Join(config.Service.DataDirectory, "dts.gob") + os.Remove(saveFilename) + + // subscribe to the transfer mechanism's feed and collect messages describing the task's journey + // through the aether + var messages []Message subscription := Subscribe(32) go func() { var finished bool for !finished { - message := <-subscription.Channel - switch message.TransferStatus.Code { - case TransferStatusUnknown: - assert.False(journey.Staging || journey.Active || journey.Failed || journey.Finalizing || - journey.Inactive || journey.Succeeded) - journey.Unknown = true - case TransferStatusStaging: - assert.False(journey.Active || journey.Failed || journey.Finalizing || - journey.Inactive || journey.Succeeded) - journey.Staging = true - case TransferStatusActive: - assert.False(journey.Failed || journey.Finalizing || journey.Inactive || journey.Succeeded) - journey.Active = true - case TransferStatusFailed: - assert.False(journey.Finalizing || journey.Inactive || journey.Succeeded) - journey.Failed = true - case TransferStatusFinalizing: - assert.False(journey.Failed || journey.Inactive || journey.Succeeded) - journey.Finalizing = true - case TransferStatusInactive: - assert.False(journey.Failed || journey.Succeeded) - journey.Inactive = true - case TransferStatusSucceeded: - assert.False(journey.Failed) - journey.Succeeded = true - finished = true - } + messages = append(messages, <-subscription.Channel) } }() @@ -147,14 +109,32 @@ func (t *TransferTests) TestCreateWithoutStaging() { time.Sleep(2 * time.Second) - // how'd our journey go, friend? - assert.True(journey.Unknown) - assert.False(journey.Staging) - assert.True(journey.Active) - assert.False(journey.Failed) - assert.True(journey.Finalizing) - assert.False(journey.Inactive) - assert.True(journey.Succeeded) + // check that our messages are already in ascending order w.r.rt. timestamps and status codes + assert.True(slices.IsSortedFunc(messages, func(a, b Message) int { + return a.Time.Compare(b.Time) + })) + assert.True(slices.IsSortedFunc(messages, func(a, b Message) int { + return cmp.Compare(a.TransferStatus.Code, b.TransferStatus.Code) + })) + + // make sure we hit all the desired statuses and none of the undesired (values not used) + occurred := map[TransferStatusCode]bool{ + TransferStatusUnknown: true, + TransferStatusActive: true, + TransferStatusFinalizing: true, + TransferStatusSucceeded: true, + } + didNotOccur := map[TransferStatusCode]bool{ + TransferStatusStaging: false, + TransferStatusFailed: false, + TransferStatusInactive: false, + } + for _, message := range messages { + _, found := occurred[message.TransferStatus.Code] + assert.True(found) + _, found = didNotOccur[message.TransferStatus.Code] + assert.False(found) + } err = Stop() assert.Nil(err) @@ -165,6 +145,9 @@ func (t *TransferTests) TestStopAndRestart() { /* assert := assert.New(t.Test) + saveFilename := filepath.Join(config.Service.DataDirectory, "dts.gob") + os.Remove(saveFilename) + // start up, add a bunch of tasks, then immediately close err := Start() assert.Nil(err) From 4d43a189afd8443c2a37e395c0defed39a617ceb Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 28 Oct 2025 14:44:06 -0700 Subject: [PATCH 14/28] Fixed transfer tests. --- dtstest/dtstest.go | 7 ++++++- transfers/dispatcher.go | 12 +++++------- transfers/mover.go | 1 + transfers/transfers_test.go | 35 +++++++++++++++++++++++------------ 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/dtstest/dtstest.go b/dtstest/dtstest.go index 4acf4780..e6b0877e 100644 --- a/dtstest/dtstest.go +++ b/dtstest/dtstest.go @@ -197,6 +197,7 @@ type stagingRequest struct { // This type implements a databases.Database test fixture type Database struct { + Name string Endpt endpoints.Endpoint descriptors map[string]map[string]any Staging map[uuid.UUID]stagingRequest @@ -211,6 +212,7 @@ func RegisterDatabase(databaseName string, descriptors map[string]map[string]any return nil, err } db := Database{ + Name: databaseName, Endpt: endpoint, descriptors: descriptors, Staging: make(map[uuid.UUID]stagingRequest), @@ -286,9 +288,12 @@ func (db *Database) LocalUser(orcid string) (string, error) { } func (db *Database) Save() (databases.DatabaseSaveState, error) { - return databases.DatabaseSaveState{}, nil + return databases.DatabaseSaveState{ + Name: db.Name, + }, nil } func (db *Database) Load(state databases.DatabaseSaveState) error { + db.Name = state.Name return nil } diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 90e04157..f8965800 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -178,9 +178,7 @@ func (d *dispatcherState) start() error { if err := mover.Start(); err != nil { return err } - if err := manifestor.Start(); err != nil { - return err - } + return manifestor.Start() } slog.Debug(fmt.Sprintf("found previous tasks in %s", saveFilename)) @@ -283,19 +281,19 @@ func (d *dispatcherState) stop() error { encoder := gob.NewEncoder(saveFile) if databaseStates, err := databases.Save(); err == nil { err = encoder.Encode(databaseStates) - if err := stager.SaveAndStop(encoder); err != nil { + if err := store.SaveAndStop(encoder); err != nil { os.Remove(saveFilename) return err } - if err := mover.SaveAndStop(encoder); err != nil { + if err := stager.SaveAndStop(encoder); err != nil { os.Remove(saveFilename) return err } - if err := manifestor.SaveAndStop(encoder); err != nil { + if err := mover.SaveAndStop(encoder); err != nil { os.Remove(saveFilename) return err } - if err := store.SaveAndStop(encoder); err != nil { + if err := manifestor.SaveAndStop(encoder); err != nil { os.Remove(saveFilename) return err } diff --git a/transfers/mover.go b/transfers/mover.go index 899bffbe..c28e3cde 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -249,6 +249,7 @@ func (m *moverState) updateStatus(transferId uuid.UUID, moves []moveOperation) ( atLeastOneMoveFailed := false movesAllSucceeded := true + newStatus.NumFiles = 0 for i, move := range moves { source, err := endpoints.NewEndpoint(move.SourceEndpoint) if err != nil { diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 3fb56935..adfb476c 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -25,11 +25,9 @@ package transfers import ( - "cmp" "log" "os" "path/filepath" - "slices" "strings" "testing" "time" @@ -76,6 +74,7 @@ func (t *TransferTests) TestCreateWithoutStaging() { log.Print("=== TestCreate ===") assert := assert.New(t.Test) + // start clean -- remove any existing save file saveFilename := filepath.Join(config.Service.DataDirectory, "dts.gob") os.Remove(saveFilename) @@ -107,15 +106,17 @@ func (t *TransferTests) TestCreateWithoutStaging() { assert.GreaterOrEqual(status.Code, TransferStatusStaging) assert.Equal(3, status.NumFiles) + // wait for the (local) transfers to complete time.Sleep(2 * time.Second) - // check that our messages are already in ascending order w.r.rt. timestamps and status codes - assert.True(slices.IsSortedFunc(messages, func(a, b Message) int { - return a.Time.Compare(b.Time) - })) - assert.True(slices.IsSortedFunc(messages, func(a, b Message) int { - return cmp.Compare(a.TransferStatus.Code, b.TransferStatus.Code) - })) + status, err = Status(transferId) + assert.Nil(err) + assert.Equal(status.Code, TransferStatusSucceeded) + assert.Equal(3, status.NumFiles) + + err = Stop() + assert.Nil(err) + assert.False(Running()) // make sure we hit all the desired statuses and none of the undesired (values not used) occurred := map[TransferStatusCode]bool{ @@ -136,12 +137,22 @@ func (t *TransferTests) TestCreateWithoutStaging() { assert.False(found) } - err = Stop() + // restart and check the status of the completed transfer + err = Start() assert.Nil(err) - assert.False(Running()) + assert.True(Running()) + + status, err = Status(transferId) + assert.Nil(err) + assert.Equal(status.Code, TransferStatusSucceeded) + assert.Equal(3, status.NumFiles) + + // clean up + err = Stop() + os.Remove(saveFilename) } -func (t *TransferTests) TestStopAndRestart() { +func (t *TransferTests) TestCreateWithStaging() { /* assert := assert.New(t.Test) From a0e19357a9498c69a173ec837669c627f598f399 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 28 Oct 2025 15:28:59 -0700 Subject: [PATCH 15/28] Test fixtures now mock file staging properly. --- dtstest/dtstest.go | 34 ++++++++++++---- transfers/transfers_test.go | 81 +++++++++---------------------------- 2 files changed, 44 insertions(+), 71 deletions(-) diff --git a/dtstest/dtstest.go b/dtstest/dtstest.go index e6b0877e..c701af6d 100644 --- a/dtstest/dtstest.go +++ b/dtstest/dtstest.go @@ -103,6 +103,8 @@ type Endpoint struct { Xfers map[uuid.UUID]transferInfo // root path RootPath string + // a set of files on this endpoint that have been staged + StagedFiles map[string]bool } // Registers an endpoint test fixture with the given name in the configuration, @@ -112,9 +114,10 @@ func RegisterEndpoint(endpointName string, options EndpointOptions) error { slog.Debug(fmt.Sprintf("Registering test endpoint %s...", endpointName)) newEndpointFunc := func(name string) (endpoints.Endpoint, error) { return &Endpoint{ - Options: options, - Xfers: make(map[uuid.UUID]transferInfo), - RootPath: config.Endpoints[endpointName].Root, + Options: options, + Xfers: make(map[uuid.UUID]transferInfo), + RootPath: config.Endpoints[endpointName].Root, + StagedFiles: make(map[string]bool), }, nil } provider := config.Endpoints[endpointName].Provider @@ -138,11 +141,20 @@ func (ep *Endpoint) FilesStaged(files []map[string]any) (bool, error) { return false, fmt.Errorf("unrecognized file: %s", fileId) } } - // the source endpoint should report true for the staged files as long - // as the source database has had time to stage them - for _, req := range ep.Database.Staging { - if time.Since(req.Time) < ep.Options.StagingDuration { - return false, nil + if ep.Options.StagingDuration > 0 { + for _, descriptor := range files { + fileId := descriptor["id"].(string) + if _, staged := ep.StagedFiles[fileId]; !staged { + return false, nil + } + } + // the source endpoint should report true for the staged files as long + // as the source database has had time to stage them + for _, req := range ep.Database.Staging { + slog.Debug(fmt.Sprintf("Request time: %s", req.Time.String())) + if time.Since(req.Time) < ep.Options.StagingDuration { + return false, nil + } } } } @@ -268,6 +280,12 @@ func (db *Database) StagingStatus(id uuid.UUID) (databases.StagingStatus, error) if info, found := db.Staging[id]; found { endpoint := db.Endpt.(*Endpoint) if time.Since(info.Time) >= endpoint.Options.StagingDuration { // FIXME: not always so! + // update the staged status on the test endpoint + stagingRequest := db.Staging[id] + for _, fileId := range stagingRequest.FileIds { + endpoint.StagedFiles[fileId] = true + } + return databases.StagingStatusSucceeded, nil } return databases.StagingStatusActive, nil diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index adfb476c..2d3ee2ff 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -28,6 +28,7 @@ import ( "log" "os" "path/filepath" + "slices" "strings" "testing" "time" @@ -50,8 +51,7 @@ func TestRunner(t *testing.T) { transfers := TransferTests{Test: t} transfers.TestStartAndStop() - transfers.TestCreateWithoutStaging() - //tester.TestStopAndRestartTransfers() + transfers.TestCreate() } // We attach the tests to this type, which runs them one by one. @@ -70,7 +70,7 @@ func (t *TransferTests) TestStartAndStop() { assert.False(Running()) } -func (t *TransferTests) TestCreateWithoutStaging() { +func (t *TransferTests) TestCreate() { log.Print("=== TestCreate ===") assert := assert.New(t.Test) @@ -103,7 +103,7 @@ func (t *TransferTests) TestCreateWithoutStaging() { status, err := Status(transferId) assert.Nil(err) - assert.GreaterOrEqual(status.Code, TransferStatusStaging) + assert.GreaterOrEqual(status.Code, TransferStatusUnknown) assert.Equal(3, status.NumFiles) // wait for the (local) transfers to complete @@ -119,22 +119,21 @@ func (t *TransferTests) TestCreateWithoutStaging() { assert.False(Running()) // make sure we hit all the desired statuses and none of the undesired (values not used) - occurred := map[TransferStatusCode]bool{ - TransferStatusUnknown: true, - TransferStatusActive: true, - TransferStatusFinalizing: true, - TransferStatusSucceeded: true, + for _, occurred := range []TransferStatusCode{ + TransferStatusUnknown, + TransferStatusStaging, + TransferStatusActive, + TransferStatusFinalizing, + TransferStatusSucceeded, + } { + assert.True(slices.ContainsFunc(messages, func(message Message) bool { + return message.TransferStatus.Code == occurred + })) } - didNotOccur := map[TransferStatusCode]bool{ - TransferStatusStaging: false, - TransferStatusFailed: false, - TransferStatusInactive: false, - } - for _, message := range messages { - _, found := occurred[message.TransferStatus.Code] - assert.True(found) - _, found = didNotOccur[message.TransferStatus.Code] - assert.False(found) + for _, didntOccur := range []TransferStatusCode{TransferStatusFailed, TransferStatusInactive} { + assert.False(slices.ContainsFunc(messages, func(message Message) bool { + return message.TransferStatus.Code == didntOccur + })) } // restart and check the status of the completed transfer @@ -152,47 +151,6 @@ func (t *TransferTests) TestCreateWithoutStaging() { os.Remove(saveFilename) } -func (t *TransferTests) TestCreateWithStaging() { - /* - assert := assert.New(t.Test) - - saveFilename := filepath.Join(config.Service.DataDirectory, "dts.gob") - os.Remove(saveFilename) - - // start up, add a bunch of tasks, then immediately close - err := Start() - assert.Nil(err) - numTasks := 10 - taskIds := make([]uuid.UUID, numTasks) - for i := 0; i < numTasks; i++ { - taskId, _ := Create(Specification{ - User: auth.User{ - Name: "Joe-bob", - Orcid: "1234-5678-9012-3456", - }, - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2"}, - }) - taskIds[i] = taskId - } - time.Sleep(100 * time.Millisecond) // let things settle - err = Stop() - assert.Nil(err) - - // now restart the task manager and make sure all the tasks are there - err = Start() - assert.Nil(err) - for i := 0; i < numTasks; i++ { - _, err := Status(taskIds[i]) - assert.Nil(err) - } - - err = Stop() - assert.Nil(err) - */ -} - // This runs setup, runs all tests, and does breakdown. func TestMain(m *testing.M) { var status int @@ -246,9 +204,6 @@ var endpointOptions = dtstest.EndpointOptions{ TransferDuration: time.Duration(500) * time.Millisecond, } -// a pause to give the task manager a bit of time -var pause time.Duration = time.Duration(25) * time.Millisecond - // configuration const transfersConfig string = ` service: From 9d23b7fec896ad18618b576ebbf4f6da7bd1c532 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 28 Oct 2025 15:47:33 -0700 Subject: [PATCH 16/28] Switched prototype from tasks to transfers package. --- services/prototype.go | 18 +++++++++--------- transfers/transfers.go | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/services/prototype.go b/services/prototype.go index e58378e1..f78c59c7 100644 --- a/services/prototype.go +++ b/services/prototype.go @@ -28,7 +28,7 @@ import ( "github.com/kbase/dts/databases" "github.com/kbase/dts/endpoints" "github.com/kbase/dts/journal" - "github.com/kbase/dts/tasks" + "github.com/kbase/dts/transfers" ) // This type implements the TransferService interface, allowing file transfers @@ -95,8 +95,8 @@ func (service *prototype) Start(conf config.Config) error { defer listener.Close() listener = netutil.LimitListener(listener, conf.Service.MaxConnections) - // start tasks processing - err = tasks.Start(conf) + // start processing transfers + err = transfers.Start(conf) if err != nil { return err } @@ -115,7 +115,7 @@ func (service *prototype) Start(conf config.Config) error { // gracefully shuts down the service without interrupting active connections func (service *prototype) Shutdown(ctx context.Context) error { - tasks.Stop() + transfers.Stop() if service.Server != nil { return service.Server.Shutdown(ctx) } @@ -124,7 +124,7 @@ func (service *prototype) Shutdown(ctx context.Context) error { // closes down the service abruptly, freeing all resources func (service *prototype) Close() { - tasks.Stop() + transfers.Stop() if service.Server != nil { service.Server.Close() } @@ -684,7 +684,7 @@ func (service *prototype) createTransfer(ctx context.Context, } } - taskId, err := tasks.Create(tasks.Specification{ + taskId, err := transfers.Create(transfers.Specification{ User: user, Source: input.Body.Source, Destination: input.Body.Destination, @@ -695,7 +695,7 @@ func (service *prototype) createTransfer(ctx context.Context, if err != nil { slog.Error(err.Error()) switch err.(type) { - case *tasks.NoFilesRequestedError: + case *transfers.NoFilesRequestedError: return nil, huma.Error400BadRequest(err.Error()) case *databases.NotFoundError: return nil, huma.Error404NotFound(err.Error()) @@ -747,7 +747,7 @@ func (service *prototype) getTransferStatus(ctx context.Context, } // fetch the status for the job using the appropriate task data - status, err := tasks.Status(input.Id) + status, err := transfers.Status(input.Id) if err != nil { return nil, huma.Error404NotFound(err.Error()) } @@ -774,7 +774,7 @@ func (service *prototype) deleteTransfer(ctx context.Context, }) (*TaskDeletionOutput, error) { // request that the task be canceled - err := tasks.Cancel(input.Id) + err := transfers.Cancel(input.Id) if err != nil { return nil, err } diff --git a/transfers/transfers.go b/transfers/transfers.go index 37b4cf87..d3eb31ba 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -63,7 +63,7 @@ const ( // starts processing transfers according to the given configuration, returning an // informative error if anything prevents this -func Start() error { +func Start(conf config.Config) error { if global.Running { return &AlreadyRunningError{} } @@ -73,7 +73,7 @@ func Start() error { if err := registerEndpointProviders(); err != nil { return err } - if err := registerDatabases(); err != nil { + if err := registerDatabases(conf); err != nil { return err } global.Started = true @@ -85,7 +85,7 @@ func Start() error { } // can we access the local endpoint? - if _, err := endpoints.NewEndpoint(config.Service.Endpoint); err != nil { + if _, err := endpoints.NewEndpoint(conf.Service.Endpoint); err != nil { return err } @@ -217,14 +217,14 @@ func registerEndpointProviders() error { } // registers databases; if at least one database is available, no error is propagated -func registerDatabases() error { +func registerDatabases(conf config.Config) error { if _, found := config.Databases["jdp"]; found { - if err := databases.RegisterDatabase("jdp", jdp.NewDatabase); err != nil { + if err := databases.RegisterDatabase("jdp", jdp.DatabaseConstructor(conf)); err != nil { slog.Error(err.Error()) } } if _, found := config.Databases["kbase"]; found { - if err := databases.RegisterDatabase("kbase", kbase.NewDatabase); err != nil { + if err := databases.RegisterDatabase("kbase", kbase.DatabaseConstructor(conf)); err != nil { slog.Error(err.Error()) } } From 775d53cae4f8d08642fd94e017a41ccc068353cd Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 28 Oct 2025 15:51:38 -0700 Subject: [PATCH 17/28] Removing unused code. --- tasks/errors.go | 70 ----- tasks/subtask.go | 233 ---------------- tasks/task.go | 502 --------------------------------- tasks/tasks.go | 527 ----------------------------------- tasks/tasks_test.go | 342 ----------------------- transfers/dispatcher_test.go | 39 --- 6 files changed, 1713 deletions(-) delete mode 100644 tasks/errors.go delete mode 100644 tasks/subtask.go delete mode 100644 tasks/task.go delete mode 100644 tasks/tasks.go delete mode 100644 tasks/tasks_test.go delete mode 100644 transfers/dispatcher_test.go diff --git a/tasks/errors.go b/tasks/errors.go deleted file mode 100644 index 02b7450a..00000000 --- a/tasks/errors.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package tasks - -import ( - "fmt" - - "github.com/google/uuid" - - "github.com/kbase/dts/config" -) - -// indicates that a task is sought but not found -type NotFoundError struct { - Id uuid.UUID -} - -func (t NotFoundError) Error() string { - return fmt.Sprintf("The task %s was not found.", t.Id.String()) -} - -// indicates that Start() has been called when tasks are being processed -type AlreadyRunningError struct{} - -func (t AlreadyRunningError) Error() string { - return "Tasks are already running and cannot be started again." -} - -// indicates that Stop() has been called when tasks are not being processed -type NotRunningError struct{} - -func (t NotRunningError) Error() string { - return "Tasks are not currently being processed." -} - -// indicates that a transfer has been requested with no files(!) -type NoFilesRequestedError struct{} - -func (t NoFilesRequestedError) Error() string { - return "Requested transfer task includes no file IDs!" -} - -// indicates that a payload has been requested that is too large -type PayloadTooLargeError struct { - Size float64 // size of the requested payload in gigabytes -} - -func (e PayloadTooLargeError) Error() string { - return fmt.Sprintf("Requested payload is too large: %g GB (limit is %g GB).", - e.Size, config.Service.MaxPayloadSize) -} diff --git a/tasks/subtask.go b/tasks/subtask.go deleted file mode 100644 index 86894a95..00000000 --- a/tasks/subtask.go +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package tasks - -import ( - "fmt" - "log/slog" - "path/filepath" - - "github.com/google/uuid" - - "github.com/kbase/dts/auth" - "github.com/kbase/dts/config" - "github.com/kbase/dts/databases" - "github.com/kbase/dts/endpoints" -) - -// This type tracks subtasks within a transfer (e.g. files transferred from -// multiple endpoints attached to a single source/destination database pair). -// It holds multiple (possibly null) UUIDs corresponding to different -// states in the file transfer lifecycle -type transferSubtask struct { - Destination string // name of destination database (in config) OR custom spec - DestinationFolder string // folder path to which files are transferred - Descriptors []map[string]any // Frictionless file descriptors - Source string // name of source database (in config) - SourceEndpoint string // name of source endpoint (in config) - Staging uuid.NullUUID // staging UUID (if any) - StagingStatus databases.StagingStatus // staging status - Transfer uuid.NullUUID // file transfer UUID (if any) - TransferStatus TransferStatus // status of file transfer operation - User auth.User // info about user requesting transfer -} - -func (subtask *transferSubtask) start() error { - // are the files already staged? (only works for public data) - sourceEndpoint, err := endpoints.NewEndpoint(subtask.SourceEndpoint) - if err != nil { - return err - } - staged, err := sourceEndpoint.FilesStaged(subtask.Descriptors) - if err != nil { - return err - } - - if staged { - err = subtask.beginTransfer() - } else { - // tell the source DB to stage the files, stash the task, and return - // its new ID - source, err := databases.NewDatabase(subtask.Source) - if err != nil { - return err - } - fileIds := make([]string, len(subtask.Descriptors)) - for i, descriptor := range subtask.Descriptors { - fileIds[i] = descriptor["id"].(string) - } - taskId, err := source.StageFiles(subtask.User.Orcid, fileIds) - if err != nil { - return err - } - subtask.Staging = uuid.NullUUID{ - UUID: taskId, - Valid: true, - } - subtask.TransferStatus = TransferStatus{ - Code: TransferStatusStaging, - NumFiles: len(subtask.Descriptors), - } - } - return err -} - -// updates the state of a subtask, setting its status as necessary -func (subtask *transferSubtask) update() error { - var err error - if subtask.Staging.Valid { // we're staging - err = subtask.checkStaging() - } else if subtask.Transfer.Valid { // we're transferring - err = subtask.checkTransfer() - } - return err -} - -// checks whether files for a subtask are finished staging and, if so, -// initiates the transfer process -func (subtask *transferSubtask) checkStaging() error { - source, err := databases.NewDatabase(subtask.Source) - if err != nil { - return err - } - // check with the database first to see whether the files are staged - subtask.StagingStatus, err = source.StagingStatus(subtask.Staging.UUID) - if err != nil { - return err - } - - if subtask.StagingStatus == databases.StagingStatusSucceeded { // staged! - if config.Service.DoubleCheckStaging { - // the database thinks the files are staged. Does its endpoint agree? - endpoint, err := endpoints.NewEndpoint(subtask.SourceEndpoint) - if err != nil { - return err - } - staged, err := endpoint.FilesStaged(subtask.Descriptors) - if err != nil { - return err - } - if !staged { - return fmt.Errorf("Database %s reports staged files, but endpoint %s cannot see them. Is the endpoint's root set properly?", - subtask.Source, subtask.SourceEndpoint) - } - } - return subtask.beginTransfer() // move along - } - return nil -} - -// checks whether files for a task are finished transferring and, if so, -// initiates the generation of the file manifest -func (subtask *transferSubtask) checkTransfer() error { - // has the data transfer completed? - sourceEndpoint, err := endpoints.NewEndpoint(subtask.SourceEndpoint) - if err != nil { - return err - } - subtask.TransferStatus, err = sourceEndpoint.Status(subtask.Transfer.UUID) - if err != nil { - return err - } - if subtask.TransferStatus.Code == TransferStatusSucceeded || - subtask.TransferStatus.Code == TransferStatusFailed { // transfer finished - subtask.Transfer = uuid.NullUUID{} - } - return nil -} - -// issues a cancellation request to the endpoint associated with the subtask -func (subtask *transferSubtask) cancel() error { - if subtask.Transfer.Valid { // we're transferring - // fetch the source endpoint - endpoint, err := endpoints.NewEndpoint(subtask.SourceEndpoint) - if err != nil { - return err - } - - // request that the task be canceled using its UUID - return endpoint.Cancel(subtask.Transfer.UUID) - } - return nil -} - -// updates the status of a canceled subtask depending on where it is in its -// lifecycle -func (subtask *transferSubtask) checkCancellation() error { - if subtask.Transfer.Valid { - endpoint, err := endpoints.NewEndpoint(subtask.SourceEndpoint) - if err != nil { - return err - } - subtask.TransferStatus, err = endpoint.Status(subtask.Transfer.UUID) - return err - } - - // at any other point in the lifecycle, terminate the task - subtask.TransferStatus.Code = TransferStatusFailed - subtask.TransferStatus.Message = "Task canceled at user request" - return nil -} - -// initiates a file transfer on a set of staged files -func (subtask *transferSubtask) beginTransfer() error { - slog.Debug(fmt.Sprintf("Transferring %d file(s) from %s to %s", - len(subtask.Descriptors), subtask.SourceEndpoint, subtask.Destination)) - // assemble a list of file transfers - fileXfers := make([]FileTransfer, len(subtask.Descriptors)) - for i, descriptor := range subtask.Descriptors { - path := descriptor["path"].(string) - destinationPath := filepath.Join(subtask.DestinationFolder, path) - fileXfers[i] = FileTransfer{ - SourcePath: path, - DestinationPath: destinationPath, - Hash: descriptor["hash"].(string), - } - } - - sourceEndpoint, err := endpoints.NewEndpoint(subtask.SourceEndpoint) - if err != nil { - return err - } - - // figure out the destination endpoint - destinationEndpoint, err := resolveDestinationEndpoint(subtask.Destination) - if err != nil { - return err - } - - // initiate the transfer - transferId, err := sourceEndpoint.Transfer(destinationEndpoint, fileXfers) - if err != nil { - return err - } - subtask.Transfer = uuid.NullUUID{ - UUID: transferId, - Valid: true, - } - subtask.TransferStatus = TransferStatus{ - Code: TransferStatusActive, - NumFiles: len(subtask.Descriptors), - } - subtask.Staging = uuid.NullUUID{} - return nil -} diff --git a/tasks/task.go b/tasks/task.go deleted file mode 100644 index 3ed40305..00000000 --- a/tasks/task.go +++ /dev/null @@ -1,502 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package tasks - -import ( - "fmt" - "log/slog" - "os" - "path/filepath" - "strings" - "time" - - "github.com/frictionlessdata/datapackage-go/datapackage" - //"github.com/frictionlessdata/datapackage-go/validator" - "github.com/google/uuid" - - "github.com/kbase/dts/auth" - "github.com/kbase/dts/config" - "github.com/kbase/dts/databases" - "github.com/kbase/dts/endpoints" - "github.com/kbase/dts/endpoints/globus" - //"github.com/kbase/dts/journal" -) - -// This type tracks the lifecycle of a file transfer task that copies files from -// a source database to a destination database. A transferTask can have one or -// more subtasks, depending on how many transfer endpoints are involved. -type transferTask struct { - Canceled bool // set if a cancellation request has been made - StartTime time.Time // time at which the transfer was requested - CompletionTime time.Time // time at which the transfer completed - DataDescriptors []map[string]any // in-line data descriptors - Description string // Markdown description of the task - Destination string // name of destination database (in config) OR custom spec - DestinationFolder string // folder path to which files are transferred - FileIds []string // IDs of all files being transferred - Id uuid.UUID // task identifier - Instructions map[string]any // machine-readable task processing instructions - Manifest uuid.NullUUID // manifest generation UUID (if any) - ManifestFile string // name of locally-created manifest file - PayloadSize float64 // Size of payload (gigabytes) - Source string // name of source database (in config) - Status TransferStatus // status of file transfer operation - Subtasks []transferSubtask // list of constituent file transfer subtasks - User auth.User // info about user requesting transfer -} - -// computes the size of a payload for a transfer task (in Gigabytes) -func payloadSize(descriptors []map[string]any) float64 { - var size uint64 - for _, descriptor := range descriptors { - size += uint64(descriptor["bytes"].(int)) - } - return float64(size) / float64(1024*1024*1024) -} - -// starts a task going, initiating staging if needed -func (task *transferTask) start() error { - source, err := databases.NewDatabase(task.Source) - if err != nil { - return err - } - - // resolve resource data using file IDs - fileDescriptors := make([]map[string]any, 0) - { - descriptors, err := source.Descriptors(task.User.Orcid, task.FileIds) - if err != nil { - return err - } - - // sift through the descriptors and separate files from in-line data - for _, descriptor := range descriptors { - if _, found := descriptor["path"]; found { // file to be transferred - fileDescriptors = append(fileDescriptors, descriptor) - } else if _, found := descriptor["data"]; found { // inline data - task.DataDescriptors = append(task.DataDescriptors, descriptor) - } else { // neither! - return fmt.Errorf("descriptor '%s' (ID: %s) has no 'path' or 'data' field", - descriptor["name"], descriptor["id"]) - } - } - } - - // if the database stores its files in more than one location, check that each - // resource is associated with a valid endpoint - if len(config.Databases[task.Source].Endpoints) > 1 { - for _, descriptor := range fileDescriptors { - id := descriptor["id"].(string) - endpoint := descriptor["endpoint"].(string) - if endpoint == "" { - return databases.ResourceEndpointNotFoundError{ - Database: task.Source, - ResourceId: id, - } - } - if _, found := config.Endpoints[endpoint]; !found { - return databases.InvalidResourceEndpointError{ - Database: task.Source, - ResourceId: id, - Endpoint: endpoint, - } - } - } - } else { // otherwise, just assign the database's endpoint to the resources - for _, descriptor := range fileDescriptors { - descriptor["endpoint"] = config.Databases[task.Source].Endpoint - } - } - - // make sure the size of the payload doesn't exceed our specified limit - task.PayloadSize = payloadSize(fileDescriptors) // (in GB) - if task.PayloadSize > config.Service.MaxPayloadSize { - return &PayloadTooLargeError{Size: task.PayloadSize} - } - - // determine the destination endpoint and folder - // FIXME: this conflicts with our redesign!! - task.DestinationFolder, err = determineDestinationFolder(*task) - if err != nil { - return err - } - - // assemble distinct endpoints and create a subtask for each - distinctEndpoints := make(map[string]any) - for _, descriptor := range fileDescriptors { - endpoint := descriptor["endpoint"].(string) - if _, found := distinctEndpoints[endpoint]; !found { - distinctEndpoints[endpoint] = struct{}{} - } - } - task.Subtasks = make([]transferSubtask, 0) - for sourceEndpoint := range distinctEndpoints { - // pick out the files corresponding to the source endpoint - // NOTE: this is slow, but preserves file ID ordering - descriptorsForEndpoint := make([]map[string]any, 0) - for _, descriptor := range fileDescriptors { - endpoint := descriptor["endpoint"].(string) - if endpoint == sourceEndpoint { - descriptorsForEndpoint = append(descriptorsForEndpoint, descriptor) - } - } - - // set up a subtask for the endpoint - task.Subtasks = append(task.Subtasks, transferSubtask{ - Destination: task.Destination, - DestinationFolder: task.DestinationFolder, - Descriptors: descriptorsForEndpoint, - Source: task.Source, - SourceEndpoint: sourceEndpoint, - User: task.User, - }) - } - - // start the subtasks - for i := range task.Subtasks { - subErr := task.Subtasks[i].start() - if subErr != nil { - err = subErr - } - } - if err != nil { - return err - } - - // provisionally, we set the tasks's status to "staging" - task.Status.Code = TransferStatusStaging - return err -} - -// updates the state of a task, setting its status as necessary -func (task *transferTask) Update() error { - var err error - if len(task.Subtasks) == 0 { // new task! - err = task.start() - } else if task.Canceled { // cancellation requested - for i := range task.Subtasks { - err = task.Subtasks[i].checkCancellation() - } - if task.Completed() { - task.CompletionTime = time.Now() - } - } else if task.Manifest.Valid { // we're generating/sending a manifest - err = task.checkManifest() - } else { // update subtasks - // track subtask failures - var subtaskFailed bool - var failedSubtaskStatus TransferStatus - - // update each subtask and check for failures - subtaskStaging := false - allTransfersSucceeded := true - for i := range task.Subtasks { - err := task.Subtasks[i].update() - if err != nil { - return err - } - - if task.Subtasks[i].StagingStatus == databases.StagingStatusFailed { - subtaskFailed = true - failedSubtaskStatus.Code = TransferStatusUnknown - failedSubtaskStatus.Message = "task canceled because of staging failure" - } else if task.Subtasks[i].TransferStatus.Code == TransferStatusFailed { - subtaskFailed = true - failedSubtaskStatus.Code = TransferStatusFailed - failedSubtaskStatus.Message = "task canceled because of transfer failure" - } - if task.Subtasks[i].TransferStatus.Code != TransferStatusSucceeded { - allTransfersSucceeded = false - } - } - - // if a subtask failed, cancel the task -- otherwise, update the task's - // status based on those of its subtasks - if subtaskFailed { - // overwrite only the error code and message fields - task.Status.Code = failedSubtaskStatus.Code - task.Status.Message = failedSubtaskStatus.Message - task.Cancel() - } else { - // accumulate statistics - task.Status.Code = TransferStatusActive - task.Status.NumFiles = 0 - task.Status.NumFilesTransferred = 0 - task.Status.NumFilesSkipped = 0 - for _, subtask := range task.Subtasks { - task.Status.NumFiles += subtask.TransferStatus.NumFiles - if subtask.Staging.Valid { - subtaskStaging = true - } else { - task.Status.NumFilesTransferred += subtask.TransferStatus.NumFilesTransferred - task.Status.NumFilesSkipped += subtask.TransferStatus.NumFilesSkipped - } - } - } - - if subtaskStaging && task.Status.NumFilesTransferred == 0 { - task.Status.Code = TransferStatusStaging - } else if allTransfersSucceeded { // write a manifest - localEndpoint, err := endpoints.NewEndpoint(config.Service.Endpoint) - if err != nil { - return err - } - - // generate a manifest for the transfer - manifest, err := task.createManifest() - if err != nil { - return fmt.Errorf("generating manifest file content: %s", err.Error()) - } - - // write the manifest to disk and begin transferring it to the - // destination endpoint - task.ManifestFile = filepath.Join(config.Service.ManifestDirectory, fmt.Sprintf("manifest-%s.json", task.Id.String())) - err = manifest.SaveDescriptor(task.ManifestFile) - if err != nil { - return fmt.Errorf("creating manifest file: %s", err.Error()) - } - - // construct the source/destination file manifest paths - fileXfers := []FileTransfer{ - { - SourcePath: task.ManifestFile, - DestinationPath: filepath.Join(task.DestinationFolder, "manifest.json"), - }, - } - - // begin transferring the manifest - destinationEndpoint, err := resolveDestinationEndpoint(task.Destination) - if err != nil { - return err - } - task.Manifest.UUID, err = localEndpoint.Transfer(destinationEndpoint, fileXfers) - if err != nil { - return fmt.Errorf("transferring manifest file: %s", err.Error()) - } - - task.Status.Code = TransferStatusFinalizing - task.Manifest.Valid = true - } - } - return err -} - -// requests that the task be canceled -func (task *transferTask) Cancel() error { - // mark the task as canceled - task.Canceled = true - - // cancel each subtask and record the canceled task - numFiles := 0 - for i := range task.Subtasks { // cancel subtasks - numFiles += task.Subtasks[i].TransferStatus.NumFiles - task.Subtasks[i].cancel() - } - return nil - /* - payloadSizeBytes := int64(1024 * 1024 * 1024 * task.PayloadSize) - return journal.RecordTransfer(journal.Record{ - Id: task.Id, - Source: task.Source, - Destination: task.Destination, - Orcid: task.User.Orcid, - Status: "canceled", - PayloadSize: payloadSizeBytes, - NumFiles: numFiles, - }) - */ -} - -// returns the duration since the task completed (successfully or otherwise), -// or 0 if the task has not completed -func (task transferTask) Age() time.Duration { - if task.Status.Code == TransferStatusSucceeded || - task.Status.Code == TransferStatusFailed { - return time.Since(task.CompletionTime) - } else { - return time.Duration(0) - } -} - -// returns true if the task has completed (successfully or not), false otherwise -func (task transferTask) Completed() bool { - if task.Status.Code == TransferStatusSucceeded || - task.Status.Code == TransferStatusFailed { - return true - } else { - return false - } -} - -// creates a DataPackage that serves as the transfer manifest -func (task *transferTask) createManifest() (*datapackage.Package, error) { - // gather all file and data descriptors - descriptors := make([]map[string]any, 0) - for _, subtask := range task.Subtasks { - descriptors = append(descriptors, subtask.Descriptors...) - } - descriptors = append(descriptors, task.DataDescriptors...) - - taskUser := map[string]any{ - "id": task.Id.String(), - "title": task.User.Name, - "role": "author", - } - if task.User.Organization != "" { - taskUser["organization"] = task.User.Organization - } - if task.User.Email != "" { - taskUser["email"] = task.User.Email - } - - // NOTE: for non-custom transfers, we embed the local username for the destination database in - // this record in case it's useful (e.g. for the KBase staging service) - var err error - var username string - if _, err := endpoints.ParseCustomSpec(task.Destination); err != nil { // custom transfer? - destination, err := databases.NewDatabase(task.Destination) - if err != nil { - return nil, err - } - username, err = destination.LocalUser(task.User.Orcid) - if err != nil { - return nil, err - } - } - - jsonDescriptors := make([]any, len(descriptors)) - for i, descriptor := range descriptors { - jsonDescriptors[i] = descriptor - } - - descriptor := map[string]any{ - "name": "manifest", - "resources": jsonDescriptors, - "created": time.Now().Format(time.RFC3339), - "profile": "data-package", - "keywords": []any{"dts", "manifest"}, - "contributors": []any{ - taskUser, - }, - "description": task.Description, - "instructions": task.Instructions, - "username": username, - } - - manifest, err := datapackage.New(descriptor, ".") - if err != nil { - slog.Error(err.Error()) - } - - return manifest, nil -} - -// checks whether the file manifest for a task has been transferred and, if so, finalizes the -// transfer and marks the task as completed -func (task *transferTask) checkManifest() error { - // has the manifest transfer completed? - localEndpoint, err := endpoints.NewEndpoint(config.Service.Endpoint) - if err != nil { - return err - } - xferStatus, err := localEndpoint.Status(task.Manifest.UUID) - if err != nil { - return err - } - if xferStatus.Code == TransferStatusSucceeded || - xferStatus.Code == TransferStatusFailed { - task.CompletionTime = time.Now() - - /* - var manifest *datapackage.Package - var statusString string - if xferStatus.Code == TransferStatusSucceeded { - manifest, _ = datapackage.Load(task.ManifestFile, validator.InMemoryLoader()) - statusString = "succeeded" - - // finalize any non-custom transfers - if !strings.Contains(task.Destination, ":") { - destination, err := databases.NewDatabase(task.Destination) - if err != nil { - return err - } - err = destination.Finalize(task.User.Orcid, task.Id) - if err != nil { - return err - } - } - } else { - statusString = "failed" - } - err := journal.RecordTransfer(journal.Record{ - Id: task.Id, - Source: task.Source, - Destination: task.Destination, - Orcid: task.User.Orcid, - StartTime: task.StartTime, - StopTime: task.CompletionTime, - Status: statusString, - PayloadSize: int64(1024 * 1024 * 1024 * task.PayloadSize), // GB -> B - NumFiles: len(task.FileIds), - Manifest: manifest, - }) - if err != nil { - slog.Error(err.Error()) - } - */ - task.Manifest = uuid.NullUUID{} - os.Remove(task.ManifestFile) - - task.ManifestFile = "" - task.Status.Code = xferStatus.Code - task.Status.Message = "" - } - return nil -} - -func determineDestinationFolder(task transferTask) (string, error) { - // construct a destination folder name - if customSpec, err := endpoints.ParseCustomSpec(task.Destination); err == nil { // custom transfer? - return filepath.Join(customSpec.Path, "dts-"+task.Id.String()), nil - } - destination, err := databases.NewDatabase(task.Destination) - if err != nil { - return "", err - } - username, err := destination.LocalUser(task.User.Orcid) - if err != nil { - return "", err - } - return filepath.Join(username, "dts-"+task.Id.String()), nil -} - -func resolveDestinationEndpoint(destination string) (endpoints.Endpoint, error) { - // everything's been validated at this point, so no need to check for errors - if strings.Contains(destination, ":") { // custom transfer spec - customSpec, _ := endpoints.ParseCustomSpec(destination) - endpointId, _ := uuid.Parse(customSpec.Id) - credential := config.Credentials[customSpec.Credential] - clientId, _ := uuid.Parse(credential.Id) - return globus.NewEndpoint("Custom endpoint", endpointId, customSpec.Path, clientId, credential.Secret) - } - return endpoints.NewEndpoint(config.Databases[destination].Endpoint) -} diff --git a/tasks/tasks.go b/tasks/tasks.go deleted file mode 100644 index 841a38c4..00000000 --- a/tasks/tasks.go +++ /dev/null @@ -1,527 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package tasks - -import ( - "bytes" - "encoding/gob" - "errors" - "fmt" - "io/fs" - "log/slog" - "os" - "path/filepath" - "time" - - "github.com/google/uuid" - - "github.com/kbase/dts/auth" - "github.com/kbase/dts/config" - "github.com/kbase/dts/databases" - "github.com/kbase/dts/databases/jdp" - "github.com/kbase/dts/databases/kbase" - "github.com/kbase/dts/databases/nmdc" - "github.com/kbase/dts/endpoints" - "github.com/kbase/dts/endpoints/globus" - "github.com/kbase/dts/endpoints/local" - "github.com/kbase/dts/journal" -) - -// useful type aliases -type Database = databases.Database -type Endpoint = endpoints.Endpoint -type FileTransfer = endpoints.FileTransfer -type TransferStatus = endpoints.TransferStatus - -// useful constants -const ( - TransferStatusUnknown = endpoints.TransferStatusUnknown - TransferStatusStaging = endpoints.TransferStatusStaging - TransferStatusActive = endpoints.TransferStatusActive - TransferStatusFailed = endpoints.TransferStatusFailed - TransferStatusFinalizing = endpoints.TransferStatusFinalizing - TransferStatusInactive = endpoints.TransferStatusInactive - TransferStatusSucceeded = endpoints.TransferStatusSucceeded -) - -// starts processing tasks according to the given configuration, returning an -// informative error if anything prevents this -func Start(conf config.Config) error { - if running { - return &AlreadyRunningError{} - } - - // if this is the first call to Start(), register our built-in endpoint - // and database providers - if firstCall { - - // NOTE: it's okay if these endpoint providers have already been registered, - // NOTE: as they can be used in testing - err := endpoints.RegisterEndpointProvider("globus", globus.NewEndpointFromConfig) - if err == nil { - err = endpoints.RegisterEndpointProvider("local", local.NewEndpoint) - } - if err != nil { - if _, matches := err.(*endpoints.AlreadyRegisteredError); !matches { - return err - } - } - - // register databases - // NOTE: if a registration fails, we log it and continue, and the database is not available - if _, found := conf.Databases["jdp"]; found { - err = databases.RegisterDatabase("jdp", jdp.DatabaseConstructor(conf)) - } - if err != nil { - slog.Error(err.Error()) - } - if _, found := conf.Databases["kbase"]; found { - err = databases.RegisterDatabase("kbase", kbase.DatabaseConstructor(conf)) - } - if err != nil { - slog.Error(err.Error()) - } - if _, found := conf.Databases["nmdc"]; found { - err = databases.RegisterDatabase("nmdc", nmdc.DatabaseConstructor(conf)) - } - if err != nil { - slog.Error(err.Error()) - } - - firstCall = false - } - - // do the necessary directories exist, and are they writable/readable? - err := validateDirectory("data", config.Service.DataDirectory) - if err != nil { - return err - } - err = validateDirectory("manifest", config.Service.ManifestDirectory) - if err != nil { - return err - } - - // can we access the local endpoint? - _, err = endpoints.NewEndpoint(config.Service.Endpoint) - if err != nil { - return err - } - - // fire up the transfer journal - err = journal.Init() - if err != nil { - return err - } - - // allocate channels - taskChannels = channelsType{ - CreateTask: make(chan transferTask, 32), - CancelTask: make(chan uuid.UUID, 32), - GetTaskStatus: make(chan uuid.UUID, 32), - ReturnTaskId: make(chan uuid.UUID, 32), - ReturnTaskStatus: make(chan TransferStatus, 32), - Error: make(chan error, 32), - Poll: make(chan struct{}), - Stop: make(chan struct{}), - } - - // start processing tasks - go processTasks() - - // start the polling heartbeat - slog.Info(fmt.Sprintf("Task statuses are updated every %d ms", - config.Service.PollInterval)) - pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond - go heartbeat(pollInterval, taskChannels.Poll) - - // okay, we're running now - running = true - - return nil -} - -// Stops processing tasks. Adding new tasks and requesting task statuses are -// disallowed in a stopped state. -func Stop() error { - var err error - if running { - taskChannels.Stop <- struct{}{} - err = <-taskChannels.Error - if err != nil { - return err - } - err = journal.Finalize() - if err != nil { - return err - } - running = false - } else { - err = &NotRunningError{} - } - return err -} - -// Returns true if tasks are currently being processed, false if not. -func Running() bool { - return running -} - -// this type holds a specification used to create a valid transfer task -type Specification struct { - // a Markdown description of the transfer task - Description string - // the name of destination database to which files are transferred (as - // specified in the DTS config file) OR a custom destination spec (::) - Destination string - // machine-readable instructions for processing the payload at its destination - Instructions map[string]any - // an array of identifiers for files to be transferred from Source to Destination - FileIds []string - // the name of source database from which files are transferred (as specified - // in the DTS config file) - Source string - // information about the user requesting the task - User auth.User -} - -// Creates a new transfer task associated with the user with the specified Orcid -// ID to the manager's set, returning a UUID for the task. The task is defined -// by specifying the names of the source and destination databases and a set of -// file IDs associated with the source. -func Create(spec Specification) (uuid.UUID, error) { - var taskId uuid.UUID - - // have we requested files to be transferred? - if len(spec.FileIds) == 0 { - return taskId, &NoFilesRequestedError{} - } - - // verify the source and destination strings - _, err := databases.NewDatabase(spec.Source) // source must refer to a database - if err != nil { - return taskId, err - } - - // destination can be a database OR a custom location - if _, err = databases.NewDatabase(spec.Destination); err != nil { - if _, err = endpoints.ParseCustomSpec(spec.Destination); err != nil { - return taskId, err - } - } - - // create a new task and send it along for processing - taskChannels.CreateTask <- transferTask{ - User: spec.User, - Source: spec.Source, - Destination: spec.Destination, - FileIds: spec.FileIds, - Description: spec.Description, - Instructions: spec.Instructions, - } - select { - case taskId = <-taskChannels.ReturnTaskId: - case err = <-taskChannels.Error: - } - - return taskId, err -} - -// Given a task UUID, returns its transfer status (or a non-nil error -// indicating any issues encountered). -func Status(taskId uuid.UUID) (TransferStatus, error) { - var status TransferStatus - var err error - taskChannels.GetTaskStatus <- taskId - select { - case status = <-taskChannels.ReturnTaskStatus: - case err = <-taskChannels.Error: - } - return status, err -} - -// Requests that the task with the given UUID be canceled. Clients should check -// the status of the task separately. -func Cancel(taskId uuid.UUID) error { - var err error - taskChannels.CancelTask <- taskId - select { // default block provides non-blocking error check - case err = <-taskChannels.Error: - default: - } - return err -} - -//----------- -// Internals -//----------- - -// global variables for managing tasks -var firstCall = true // indicates first call to Start() -var running bool // true if tasks are processing, false if not -var taskChannels channelsType // channels used for processing tasks - -// loads a map of task IDs to tasks from a previously saved file if available, -// or creates an empty map if no such file is available or valid -func createOrLoadTasks(dataFile string) map[uuid.UUID]transferTask { - file, err := os.Open(dataFile) - if err != nil { - return make(map[uuid.UUID]transferTask) - } - slog.Debug(fmt.Sprintf("Found previous tasks in %s.", dataFile)) - defer file.Close() - enc := gob.NewDecoder(file) - var tasks map[uuid.UUID]transferTask - var databaseStates databases.DatabaseSaveStates - if err = enc.Decode(&tasks); err == nil { - err = enc.Decode(&databaseStates) - } - if err != nil { // file not readable - slog.Error(fmt.Sprintf("Reading task file %s: %s", dataFile, err.Error())) - return make(map[uuid.UUID]transferTask) - } - if err = databases.Load(databaseStates); err != nil { - slog.Error(fmt.Sprintf("Restoring database states: %s", err.Error())) - } - slog.Debug(fmt.Sprintf("Restored %d tasks from %s", len(tasks), dataFile)) - return tasks -} - -// saves a map of task IDs to tasks to the given file -func saveTasks(tasks map[uuid.UUID]transferTask, dataFile string) error { - if len(tasks) > 0 { - slog.Debug(fmt.Sprintf("Saving %d tasks to %s", len(tasks), dataFile)) - file, err := os.OpenFile(dataFile, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - return fmt.Errorf("opening task file %s: %s", dataFile, err.Error()) - } - enc := gob.NewEncoder(file) - if err = enc.Encode(tasks); err == nil { - var databaseStates databases.DatabaseSaveStates - if databaseStates, err = databases.Save(); err == nil { - err = enc.Encode(databaseStates) - } - } - if err != nil { - file.Close() - os.Remove(dataFile) - return fmt.Errorf("saving tasks: %s", err.Error()) - } - err = file.Close() - if err != nil { - os.Remove(dataFile) - return fmt.Errorf("writing task file %s: %s", dataFile, err.Error()) - } - slog.Debug(fmt.Sprintf("Saved %d tasks to %s", len(tasks), dataFile)) - } else { - _, err := os.Stat(dataFile) - if !errors.Is(err, fs.ErrNotExist) { // file exists - os.Remove(dataFile) - } - } - return nil -} - -// this type holds various channels used by the task manager to communicate -// with its worker goroutine -type channelsType struct { - CreateTask chan transferTask // used by client to request task creation - CancelTask chan uuid.UUID // used by client to request task cancellation - GetTaskStatus chan uuid.UUID // used by client to request task status - ReturnTaskId chan uuid.UUID // returns task ID to client - ReturnTaskStatus chan TransferStatus // returns task status to client - Error chan error // returns error to client - Poll chan struct{} // carries heartbeat signal for task updates - Stop chan struct{} // used by client to stop task management -} - -// this function runs in its own goroutine, using the given local endpoint -// for local file transfers, and the given channels to communicate with -// the main thread -func processTasks() { - // create or recreate a persistent table of transfer-related tasks - dataStore := filepath.Join(config.Service.DataDirectory, "dts.gob") - tasks := createOrLoadTasks(dataStore) - - // parse the task channels into directional types as needed - var createTaskChan <-chan transferTask = taskChannels.CreateTask - var cancelTaskChan <-chan uuid.UUID = taskChannels.CancelTask - var getTaskStatusChan <-chan uuid.UUID = taskChannels.GetTaskStatus - var returnTaskIdChan chan<- uuid.UUID = taskChannels.ReturnTaskId - var returnTaskStatusChan chan<- TransferStatus = taskChannels.ReturnTaskStatus - var errorChan chan<- error = taskChannels.Error - var pollChan <-chan struct{} = taskChannels.Poll - var stopChan <-chan struct{} = taskChannels.Stop - - // the task deletion period is specified in seconds - deleteAfter := time.Duration(config.Service.DeleteAfter) * time.Second - - // start scurrying around - running := true - for running { - select { - case newTask := <-createTaskChan: // Create() called - newTask.Id = uuid.New() - newTask.StartTime = time.Now() - tasks[newTask.Id] = newTask - returnTaskIdChan <- newTask.Id - slog.Info(fmt.Sprintf("Created new transfer task %s (%d file(s) requested)", - newTask.Id.String(), len(newTask.FileIds))) - case taskId := <-cancelTaskChan: // Cancel() called - if task, found := tasks[taskId]; found { - slog.Info(fmt.Sprintf("Task %s: received cancellation request", taskId.String())) - err := task.Cancel() - if err != nil { - task.Status.Code = TransferStatusUnknown - task.Status.Message = fmt.Sprintf("error in cancellation: %s", err.Error()) - task.CompletionTime = time.Now() - slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), task.Status.Message)) - tasks[task.Id] = task - } - } else { - err := &NotFoundError{Id: taskId} - errorChan <- err - } - case taskId := <-getTaskStatusChan: // Status() called - if task, found := tasks[taskId]; found { - returnTaskStatusChan <- task.Status - } else { - err := &NotFoundError{Id: taskId} - errorChan <- err - } - case <-pollChan: // time to move things along - for taskId, task := range tasks { - if !task.Completed() { - oldStatus := task.Status - err := task.Update() - if err != nil { - // We log task update errors but do not propagate them. All - // task errors result in a failed status. - task.Status.Code = TransferStatusFailed - task.Status.Message = err.Error() - task.CompletionTime = time.Now() - slog.Error(fmt.Sprintf("Task %s: %s", task.Id.String(), err.Error())) - } - if task.Status.Code != oldStatus.Code { - switch task.Status.Code { - case TransferStatusStaging: - slog.Info(fmt.Sprintf("Task %s: staging %d file(s) (%g GB)", - task.Id.String(), len(task.FileIds), task.PayloadSize)) - case TransferStatusActive: - slog.Info(fmt.Sprintf("Task %s: beginning transfer (%d file(s), %g GB)", - task.Id.String(), len(task.FileIds), task.PayloadSize)) - case TransferStatusInactive: - slog.Info(fmt.Sprintf("Task %s: suspended transfer", task.Id.String())) - case TransferStatusFinalizing: - slog.Info(fmt.Sprintf("Task %s: finalizing transfer", task.Id.String())) - case TransferStatusSucceeded: - slog.Info(fmt.Sprintf("Task %s: completed successfully", task.Id.String())) - case TransferStatusFailed: - slog.Info(fmt.Sprintf("Task %s: failed", task.Id.String())) - err := journal.RecordTransfer(journal.Record{ - Id: task.Id, - Source: task.Source, - Destination: task.Destination, - Orcid: task.User.Orcid, - StartTime: task.StartTime, - StopTime: time.Now(), - Status: "failed", - PayloadSize: int64(1024 * 1024 * 1024 * task.PayloadSize), // GB -> B - NumFiles: len(task.FileIds), - }) - if err != nil { - slog.Error(err.Error()) - } - } - } - } - - // if the task completed a long enough time go, delete its entry - if task.Age() > deleteAfter { - slog.Debug(fmt.Sprintf("Task %s: purging transfer record", task.Id.String())) - delete(tasks, taskId) - } else { // update its entry - tasks[taskId] = task - } - } - case <-stopChan: // Stop() called - err := saveTasks(tasks, dataStore) // don't forget to save our state! - errorChan <- err - running = false - } - } -} - -// this function sends a regular pulse on its poll channel until the global -// variable running is found to be false -func heartbeat(pollInterval time.Duration, pollChan chan<- struct{}) { - for { - time.Sleep(pollInterval) - pollChan <- struct{}{} - if !running { - break - } - } -} - -// this function checks for the existence of the data directory and whether it -// is readable/writeable, returning a non-nil error if any of these conditions -// are not met -func validateDirectory(dirType, dir string) error { - if dir == "" { - return fmt.Errorf("no %s directory was specified", dirType) - } - info, err := os.Stat(dir) - if err != nil { - return err - } - if !info.IsDir() { - return &os.PathError{ - Op: "validateDirectory", - Path: dir, - Err: fmt.Errorf("%s is not a valid %s directory", dir, dirType), - } - } - - // can we write a file and read it? - testFile := filepath.Join(dir, "test.txt") - writtenTestData := []byte("test") - err = os.WriteFile(testFile, writtenTestData, 0644) - if err != nil { - return &os.PathError{ - Op: "validateDirectory", - Path: dir, - Err: fmt.Errorf("could not write to %s directory %s", dirType, dir), - } - } - readTestData, err := os.ReadFile(testFile) - if err == nil { - os.Remove(testFile) - } - if err != nil || !bytes.Equal(readTestData, writtenTestData) { - return &os.PathError{ - Op: "validateDirectory", - Path: dir, - Err: fmt.Errorf("could not read from %s directory %s", dirType, dir), - } - } - return nil -} diff --git a/tasks/tasks_test.go b/tasks/tasks_test.go deleted file mode 100644 index 11a43f25..00000000 --- a/tasks/tasks_test.go +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// These tests must be run serially, since tasks are coordinated by a -// single instance. - -package tasks - -import ( - "log" - "os" - "strings" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - - "github.com/kbase/dts/auth" - "github.com/kbase/dts/config" - "github.com/kbase/dts/dtstest" -) - -// runs all tests serially -func TestRunner(t *testing.T) { - tester := SerialTests{Test: t} - tester.TestStartAndStop() - tester.TestCreateTask() - tester.TestCancelTask() - tester.TestStopAndRestart() -} - -// This runs setup, runs all tests, and does breakdown. -func TestMain(m *testing.M) { - var status int - setup() - status = m.Run() - breakdown() - os.Exit(status) -} - -// this function gets called at the begÑ–nning of a test session -func setup() { - dtstest.EnableDebugLogging() - - log.Print("Creating testing directory...\n") - var err error - TESTING_DIR, err = os.MkdirTemp(os.TempDir(), "data-transfer-service-tests-") - if err != nil { - log.Panicf("Couldn't create testing directory: %s", err) - } - os.Chdir(TESTING_DIR) - - // read in the config file with SOURCE_ROOT and DESTINATION_ROOT replaced - myConfig := strings.ReplaceAll(tasksConfig, "TESTING_DIR", TESTING_DIR) - err = config.Init([]byte(myConfig)) - if err != nil { - log.Panicf("Couldn't initialize configuration: %s", err) - } - conf, err = config.NewConfig([]byte(myConfig)) - if err != nil { - log.Panicf("Couldn't create config instance: %s", err) - } - - // create test resources - testDescriptors := map[string]map[string]any{ - "file1": { - "id": "file1", - "name": "file1.dat", - "path": "dir1/file1.dat", - "format": "text", - "bytes": 1024, - "hash": "d91f97974d06563cab48d4d43a17e08a", - "endpoint": "source-endpoint", - }, - "file2": { - "id": "file2", - "name": "file2.dat", - "path": "dir2/file2.dat", - "format": "text", - "bytes": 2048, - "hash": "d91f9e974d0e563cab48d4d43a17e08a", - "endpoint": "source-endpoint", - }, - "file3": { - "id": "file3", - "name": "file3.dat", - "path": "dir3/file3.dat", - "format": "text", - "bytes": 4096, - "hash": "e91f9e974d0e563cab48d4d43a17e08e", - "endpoint": "source-endpoint", - }, - } - - // register test databases/endpoints referred to in config file - dtstest.RegisterTestFixturesFromConfig(endpointOptions, testDescriptors) - - // Create the data and manifest directories - os.Mkdir(config.Service.DataDirectory, 0755) - os.Mkdir(config.Service.ManifestDirectory, 0755) -} - -// this function gets called after all tests have been run -func breakdown() { - if TESTING_DIR != "" { - log.Printf("Deleting testing directory %s...\n", TESTING_DIR) - os.RemoveAll(TESTING_DIR) - } -} - -// To run the tests serially, we attach them to a SerialTests type and -// have them run by a a single test runner. -type SerialTests struct{ Test *testing.T } - -func (t *SerialTests) TestStartAndStop() { - assert := assert.New(t.Test) - - assert.False(Running()) - err := Start(conf) - assert.Nil(err) - assert.True(Running()) - err = Stop() - assert.Nil(err) - assert.False(Running()) -} - -func (t *SerialTests) TestCreateTask() { - assert := assert.New(t.Test) - - err := Start(conf) - assert.Nil(err) - - pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond - deleteAfter := time.Duration(config.Service.DeleteAfter) * time.Second - - // queue up a transfer task between two phony databases - taskId, err := Create(Specification{ - User: auth.User{ - Name: "Joe-bob", - Orcid: "1234-5678-9012-3456", - }, - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2"}, - }) - assert.Nil(err) - assert.True(taskId != uuid.UUID{}) - - // the initial status of the task should be Unknown - status, err := Status(taskId) - assert.Nil(err) - assert.Equal(TransferStatusUnknown, status.Code) - - // make sure the status switches to staging or active - time.Sleep(pause + pollInterval) - status, err = Status(taskId) - assert.Nil(err) - assert.True(status.Code == TransferStatusStaging || status.Code == TransferStatusActive) - - // wait for the staging to complete and then check its status - // again (should be actively transferring) - time.Sleep(pause + endpointOptions.StagingDuration) - status, err = Status(taskId) - assert.Nil(err) - assert.Equal(TransferStatusActive, status.Code) - - // wait again for the transfer to complete and then check its status - // (should be finalizing or have successfully completed) - time.Sleep(pause + endpointOptions.TransferDuration) - status, err = Status(taskId) - assert.Nil(err) - assert.True(status.Code == TransferStatusFinalizing || status.Code == TransferStatusSucceeded) - - // if the transfer was finalizing, check once more for completion - if status.Code != TransferStatusSucceeded { - time.Sleep(pause + endpointOptions.TransferDuration) - status, err = Status(taskId) - assert.Nil(err) - assert.Equal(TransferStatusSucceeded, status.Code) - } - - // now wait for the task to age out and make sure it's not found - time.Sleep(pause + deleteAfter) - status, err = Status(taskId) - assert.NotNil(err) - - err = Stop() - assert.Nil(err) -} - -func (t *SerialTests) TestCancelTask() { - assert := assert.New(t.Test) - - err := Start(conf) - assert.Nil(err) - - pollInterval := time.Duration(config.Service.PollInterval) * time.Millisecond - - // queue up a transfer task between two phony databases - taskId, err := Create(Specification{ - User: auth.User{ - Name: "Joe-bob", - Orcid: "1234-5678-9012-3456", - }, - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2"}, - }) - assert.Nil(err) - assert.True(taskId != uuid.UUID{}) - - // get things going and make sure we can check its status - time.Sleep(pause + pollInterval) - _, err = Status(taskId) - assert.Nil(err) - - // cancel the thing - err = Cancel(taskId) - assert.Nil(err) - - // wait for the task to complete - status, err := Status(taskId) - assert.Nil(err) - for { - if status.Code == TransferStatusSucceeded || - status.Code == TransferStatusFailed { - break - } - time.Sleep(pause + pollInterval) - status, err = Status(taskId) - assert.Nil(err) - } - - err = Stop() - assert.Nil(err) -} - -func (t *SerialTests) TestStopAndRestart() { - assert := assert.New(t.Test) - - // start up, add a bunch of tasks, then immediately close - err := Start(conf) - assert.Nil(err) - numTasks := 10 - taskIds := make([]uuid.UUID, numTasks) - for i := 0; i < numTasks; i++ { - taskId, _ := Create(Specification{ - User: auth.User{ - Name: "Joe-bob", - Orcid: "1234-5678-9012-3456", - }, - Source: "test-source", - Destination: "test-destination", - FileIds: []string{"file1", "file2"}, - }) - taskIds[i] = taskId - } - time.Sleep(100 * time.Millisecond) // let things settle - err = Stop() - assert.Nil(err) - - // now restart the task manager and make sure all the tasks are there - err = Start(conf) - assert.Nil(err) - for i := 0; i < numTasks; i++ { - _, err := Status(taskIds[i]) - assert.Nil(err) - } - - err = Stop() - assert.Nil(err) -} - -// temporary testing directory -var TESTING_DIR string - -// endpoint testing options -var endpointOptions = dtstest.EndpointOptions{ - StagingDuration: time.Duration(150) * time.Millisecond, - TransferDuration: time.Duration(500) * time.Millisecond, -} - -// a pause to give the task manager a bit of time -var pause time.Duration = time.Duration(25) * time.Millisecond - -// configuration -const tasksConfig string = ` -service: - port: 8080 - max_connections: 100 - poll_interval: 50 # milliseconds - data_dir: TESTING_DIR/data - manifest_dir: TESTING_DIR/manifests - delete_after: 2 # seconds - endpoint: local-endpoint -databases: - test-source: - name: Source Test Database - organization: The Source Company - endpoint: source-endpoint - test-destination: - name: Destination Test Database - organization: Fabulous Destinations, Inc. - endpoint: destination-endpoint -endpoints: - local-endpoint: - name: Local endpoint - id: 8816ec2d-4a48-4ded-b68a-5ab46a4417b6 - provider: test - source-endpoint: - name: Endpoint 1 - id: 26d61236-39f6-4742-a374-8ec709347f2f - provider: test - root: SOURCE_ROOT - destination-endpoint: - name: Endpoint 2 - id: f1865b86-2c64-4b8b-99f3-5aaa945ec3d9 - provider: test - root: DESTINATION_ROOT -` - -// configuration instance -var conf config.Config diff --git a/transfers/dispatcher_test.go b/transfers/dispatcher_test.go deleted file mode 100644 index 42d52985..00000000 --- a/transfers/dispatcher_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2023 The KBase Project and its Contributors -// Copyright (c) 2023 Cohere Consulting, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -package transfers - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// We attach the tests to this type, which runs them one by one. -type DispatcherTests struct{ Test *testing.T } - -func (t *DispatcherTests) TestStartAndStop() { - assert := assert.New(t.Test) - err := dispatcher.Start() - assert.Nil(err) - err = dispatcher.Stop() - assert.Nil(err) -} From 8025c64374f288fee358e72f13f2c05bd1d2c380 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 28 Oct 2025 16:32:11 -0700 Subject: [PATCH 18/28] Partially propagated config instance into orchestration logic. --- transfers/transfers.go | 14 +++++++------- transfers/transfers_test.go | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/transfers/transfers.go b/transfers/transfers.go index d3eb31ba..fde1f8d9 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -80,7 +80,7 @@ func Start(conf config.Config) error { } // do the necessary directories exist, and are they writable/readable? - if err := validateDirectories(); err != nil { + if err := validateDirectories(conf); err != nil { return err } @@ -218,17 +218,17 @@ func registerEndpointProviders() error { // registers databases; if at least one database is available, no error is propagated func registerDatabases(conf config.Config) error { - if _, found := config.Databases["jdp"]; found { + if _, found := conf.Databases["jdp"]; found { if err := databases.RegisterDatabase("jdp", jdp.DatabaseConstructor(conf)); err != nil { slog.Error(err.Error()) } } - if _, found := config.Databases["kbase"]; found { + if _, found := conf.Databases["kbase"]; found { if err := databases.RegisterDatabase("kbase", kbase.DatabaseConstructor(conf)); err != nil { slog.Error(err.Error()) } } - if _, found := config.Databases["nmdc"]; found { + if _, found := conf.Databases["nmdc"]; found { if err := databases.RegisterDatabase("nmdc", nmdc.NewDatabase); err != nil { slog.Error(err.Error()) } @@ -239,12 +239,12 @@ func registerDatabases(conf config.Config) error { return nil } -func validateDirectories() error { - err := validateDirectory("data", config.Service.DataDirectory) +func validateDirectories(conf config.Config) error { + err := validateDirectory("data", conf.Service.DataDirectory) if err != nil { return err } - return validateDirectory("manifest", config.Service.ManifestDirectory) + return validateDirectory("manifest", conf.Service.ManifestDirectory) } // checks for the existence of a directory and whether it is readable/writeable, returning an error diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 2d3ee2ff..83ec8890 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -62,7 +62,7 @@ func (t *TransferTests) TestStartAndStop() { assert := assert.New(t.Test) assert.False(Running()) - err := Start() + err := Start(conf) assert.Nil(err) assert.True(Running()) err = Stop() @@ -75,7 +75,7 @@ func (t *TransferTests) TestCreate() { assert := assert.New(t.Test) // start clean -- remove any existing save file - saveFilename := filepath.Join(config.Service.DataDirectory, "dts.gob") + saveFilename := filepath.Join(conf.Service.DataDirectory, "dts.gob") os.Remove(saveFilename) // subscribe to the transfer mechanism's feed and collect messages describing the task's journey @@ -89,7 +89,7 @@ func (t *TransferTests) TestCreate() { } }() - err := Start() + err := Start(conf) assert.Nil(err) assert.True(Running()) @@ -137,7 +137,7 @@ func (t *TransferTests) TestCreate() { } // restart and check the status of the completed transfer - err = Start() + err = Start(conf) assert.Nil(err) assert.True(Running()) @@ -178,13 +178,17 @@ func setup() { if err != nil { log.Panicf("Couldn't initialize configuration: %s", err) } + conf, err = config.NewConfig([]byte(myConfig)) + if err != nil { + log.Panicf("Couldn't create config instance: %s", err) + } // register test databases/endpoints referred to in config file dtstest.RegisterTestFixturesFromConfig(endpointOptions, testDescriptors) // Create the data and manifest directories - os.Mkdir(config.Service.DataDirectory, 0755) - os.Mkdir(config.Service.ManifestDirectory, 0755) + os.Mkdir(conf.Service.DataDirectory, 0755) + os.Mkdir(conf.Service.ManifestDirectory, 0755) } // this function gets called after all tests have been run @@ -269,3 +273,6 @@ var testDescriptors map[string]map[string]any = map[string]map[string]any{ "endpoint": "source-endpoint", }, } + +// configuration instance +var conf config.Config From 8eda76b81e19e569a21bd0a8dd37bfee5091c31f Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Tue, 28 Oct 2025 16:38:54 -0700 Subject: [PATCH 19/28] Placating static analyzer. --- databases/databases.go | 2 +- transfers/dispatcher.go | 8 +++++++- transfers/errors.go | 12 ++++++------ transfers/messages.go | 2 +- transfers/mover.go | 3 +++ transfers/transfers.go | 8 ++++---- transfers/transfers_test.go | 1 + 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/databases/databases.go b/databases/databases.go index 4d195da6..3e36db0f 100644 --- a/databases/databases.go +++ b/databases/databases.go @@ -158,7 +158,7 @@ func RegisterDatabase(dbName string, createDb func() (Database, error)) error { // returns a list of names of registered databases func RegisteredDatabases() []string { dbs := make([]string, 0) - for name, _ := range createDatabaseFuncs_ { + for name := range createDatabaseFuncs_ { dbs = append(dbs, name) } return dbs diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index f8965800..de96791d 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -226,6 +226,9 @@ func (d *dispatcherState) create(spec Specification) (uuid.UUID, error) { // do we need to stage files for the source database? filesStaged := true descriptorsForEndpoint, err := descriptorsByEndpoint(spec, descriptors) + if err != nil { + return uuid.UUID{}, err + } for source, descriptorsForSource := range descriptorsForEndpoint { sourceEndpoint, err := endpoints.NewEndpoint(source) if err != nil { @@ -280,7 +283,10 @@ func (d *dispatcherState) stop() error { encoder := gob.NewEncoder(saveFile) if databaseStates, err := databases.Save(); err == nil { - err = encoder.Encode(databaseStates) + if err := encoder.Encode(databaseStates); err != nil { + os.Remove(saveFilename) + return err + } if err := store.SaveAndStop(encoder); err != nil { os.Remove(saveFilename) return err diff --git a/transfers/errors.go b/transfers/errors.go index d1093ad7..df086f8d 100644 --- a/transfers/errors.go +++ b/transfers/errors.go @@ -33,28 +33,28 @@ import ( type AlreadyRunningError struct{} func (t AlreadyRunningError) Error() string { - return fmt.Sprintf("Transfer orchestration is already running and cannot be started again.") + return "transfer orchestration is already running and cannot be started again" } // indicates that Stop() has been called when tasks are not being processed type NotRunningError struct{} func (t NotRunningError) Error() string { - return fmt.Sprintf("Transfers are not currently being processed.") + return "transfers are not currently being processed" } // indicates that no databases are currently available type NoDatabasesAvailable struct{} func (t NoDatabasesAvailable) Error() string { - return fmt.Sprintf("No databases are currently available for transfer.") + return "no databases are currently available for transfer" } // indicates that a transfer has been requested with no files(!) type NoFilesRequestedError struct{} func (t NoFilesRequestedError) Error() string { - return fmt.Sprintf("Requested transfer includes no file IDs!") + return "requested transfer includes no file IDs" } // indicates that a payload has been requested that is too large @@ -63,7 +63,7 @@ type PayloadTooLargeError struct { } func (e PayloadTooLargeError) Error() string { - return fmt.Sprintf("Requested transfer payload is too large: %g GB (limit is %g GB).", + return fmt.Sprintf("requested transfer payload is too large: %g GB (limit is %g GB)", e.Size, config.Service.MaxPayloadSize) } @@ -83,5 +83,5 @@ type TransferNotFoundError struct { } func (e TransferNotFoundError) Error() string { - return fmt.Sprintf("Transfer not found: %s.", e.Id.String()) + return fmt.Sprintf("transfer not found: %s", e.Id.String()) } diff --git a/transfers/messages.go b/transfers/messages.go index 37685aee..fca1eb41 100644 --- a/transfers/messages.go +++ b/transfers/messages.go @@ -46,7 +46,7 @@ func Subscribe(bufferSize int) Subscription { func Unsubscribe(sub Subscription) error { messageBroker_.Mutex.Lock() if sub.Id < 0 || sub.Id >= len(messageBroker_.Subscriptions) { - return errors.New(fmt.Sprintf("invalid subscription ID: %d\n", sub.Id)) + return fmt.Errorf("invalid subscription ID: %d", sub.Id) } close(messageBroker_.Subscriptions[sub.Id]) messageBroker_.Subscriptions = slices.Delete(messageBroker_.Subscriptions, sub.Id, sub.Id) diff --git a/transfers/mover.go b/transfers/mover.go index c28e3cde..44bfd30f 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -193,6 +193,9 @@ func (m *moverState) moveFiles(transferId uuid.UUID) ([]moveOperation, error) { // start transfers for each endpoint descriptorsForEndpoint, err := descriptorsByEndpoint(spec, descriptors) + if err != nil { + return nil, err + } moves := make([]moveOperation, 0) for source, descriptorsForSource := range descriptorsForEndpoint { files := make([]endpoints.FileTransfer, len(descriptorsForSource)) diff --git a/transfers/transfers.go b/transfers/transfers.go index fde1f8d9..5da7286e 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -251,7 +251,7 @@ func validateDirectories(conf config.Config) error { // if these conditions are not met func validateDirectory(dirType, dir string) error { if dir == "" { - return fmt.Errorf("no %s directory was specified!", dirType) + return fmt.Errorf("no %s directory was specified", dirType) } info, err := os.Stat(dir) if err != nil { @@ -261,7 +261,7 @@ func validateDirectory(dirType, dir string) error { return &os.PathError{ Op: "validateDirectory", Path: dir, - Err: fmt.Errorf("%s is not a valid %s directory!", dir, dirType), + Err: fmt.Errorf("%s is not a valid %s directory", dir, dirType), } } @@ -273,7 +273,7 @@ func validateDirectory(dirType, dir string) error { return &os.PathError{ Op: "validateDirectory", Path: dir, - Err: fmt.Errorf("Could not write to %s directory %s!", dirType, dir), + Err: fmt.Errorf("could not write to %s directory %s", dirType, dir), } } readTestData, err := os.ReadFile(testFile) @@ -284,7 +284,7 @@ func validateDirectory(dirType, dir string) error { return &os.PathError{ Op: "validateDirectory", Path: dir, - Err: fmt.Errorf("Could not read from %s directory %s!", dirType, dir), + Err: fmt.Errorf("could not read from %s directory %s", dirType, dir), } } return nil diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 83ec8890..6f341ac4 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -148,6 +148,7 @@ func (t *TransferTests) TestCreate() { // clean up err = Stop() + assert.Nil(err) os.Remove(saveFilename) } From b136f15a215e7ba59dc1a94f43dbdff115b6f992 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Wed, 29 Oct 2025 15:17:13 -0700 Subject: [PATCH 20/28] Addressed PR comments aside from channel buffer sizes. --- transfers/dispatcher.go | 13 +++++++++++++ transfers/manifestor.go | 7 ++++++- transfers/mover.go | 8 +++++++- transfers/stager.go | 8 +++++++- transfers/store.go | 15 ++++++++++++++- transfers/transfers.go | 16 ++++++++++------ 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index de96791d..02ede893 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -39,6 +39,19 @@ import ( // Dispatcher //------------ +// The dispatcher handles transfer-related requests from clients. When the dispatcher is started, +// it starts the store, stager, mover, and manifestor, loading any previous transfers from disk. +// +// When a client requests that the dispatcher create a transfer, the dispatcher asks the store to +// create a new transfer record, and then dispatches a request to the stager or the mover, based on +// whether the files to be transferred require staging. +// +// A client can request the status of ongoing and completed transfers, which the dispatcher fetches +// from the store. A client can also request that an ongoing transfer be deleted, which the +// dispatcher propagates to the store, stager, mover, and manifestor. +// +// When the dispatcher is stopped, it stops the store, stager, mover, and manifestor. + // dispatcher global state var dispatcher dispatcherState diff --git a/transfers/manifestor.go b/transfers/manifestor.go index d091a5e0..221bc836 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -41,8 +41,13 @@ import ( //------------ // The manifestor generates a JSON manifest for each successful transfer and sends it to the -// transfer's destination. The manifest contains a Frictionless DataPackage containing all +// transfer's destination. This manifest contains a Frictionless DataPackage containing all // descriptors relevant to the transfer. +// +// The manifestor responds to requests from the mover to generate a manifest and transfer it to the +// transfer destination, updating the status of the manifest's transfer via the store as needed. +// +// The manifestor is started and stopped by the dispatcher. // manifestor global state var manifestor manifestorState diff --git a/transfers/mover.go b/transfers/mover.go index 44bfd30f..c8e9892a 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -37,7 +37,13 @@ import ( // Mover //------- -// The mover manages actual file transfer operations and cancellations. +// The mover manages the transfer of file payloads. It responds to requests from the dispatcher or +// the stager to move files associated with a given transfer ID. The mover than monitors the +// transfer process, updating the status of the transfer via the store as needed. When a set of +// files has been successfully transferred, the mover sends a request to the manifestor to generate +// a manifest and transfer it into place at the destination. +// +// The mover is started and stopped by the dispatcher. // mover global state var mover moverState diff --git a/transfers/stager.go b/transfers/stager.go index b23b90d7..f195aaaa 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -36,7 +36,13 @@ import ( // Stager //-------- -// The stager coordinates the staging of files at a source database in preparation for transfer. +// The stager coordinates the staging of files at a source database in preparation for transfer. It +// responds to requests from the dispatcher to stage files associated with a given transfer ID. The +// dispatcher then monitors the staging process, updating the status of the transfer via the store +// as needed. When a set of files has been staged, the stager sends a request to the mover to move +// the files to their destination. +// +// The stager is started and stopped by the dispatcher. // stager global state var stager stagerState diff --git a/transfers/store.go b/transfers/store.go index 1e05c71b..0e1177cb 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -41,6 +41,20 @@ import ( // The transfer metadata store maintains a table of active and completed transfers and all related // metadata. The store only tracks the state of the transfers--it doesn't initiate any activity. +// +// The store can create a new transfer record given a specification. This operation returns a UUID +// that can be used to fetch and manipulate the record in the following ways: +// +// * the specification for the transfer can be requested +// * an array of Frictionless DataResources ("descriptors") for the transfer can be requested +// * the status of a transfer can be requested or updated +// * a transfer record can be removed +// +// The store removes records that are older than the given maximum record "age". The age of a +// transfer record is the amount of time that has elapsed since the transfer completed (successfully +// or unsuccessfully). +// +// The store is started and stopped by the dispatcher. // store global state var store storeState @@ -263,7 +277,6 @@ func (s *storeState) process(decoder *gob.Decoder) { for id, transfer := range transfers { if transfer.Status.Code == TransferStatusFailed || transfer.Status.Code == TransferStatusSucceeded { if time.Since(transfer.CompletionTime) > deleteAfter { - slog.Debug("*plonk*") delete(transfers, id) } } diff --git a/transfers/transfers.go b/transfers/transfers.go index 5da7286e..c93b74c6 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -204,13 +204,17 @@ var global struct { func registerEndpointProviders() error { // NOTE: it's okay if these endpoint providers have already been registered, // NOTE: as they can be used in testing - err := endpoints.RegisterEndpointProvider("globus", globus.NewEndpointFromConfig) - if err == nil { - err = endpoints.RegisterEndpointProvider("local", local.NewEndpoint) + endpointsToRegister := map[string]func(endpointName string) (endpoints.Endpoint, error){ + "globus": globus.NewEndpointFromConfig, + "local": local.NewEndpoint, } - if err != nil { - if _, matches := err.(*endpoints.AlreadyRegisteredError); !matches { - return err + for name, constructor := range endpointsToRegister { + err := endpoints.RegisterEndpointProvider(name, constructor) + if err != nil { + // ignore AlreadyRegisteredError but propagate others + if _, matches := err.(*endpoints.AlreadyRegisteredError); !matches { + return err + } } } return nil From 9ed81ef07632391f91d202b7ce222f9b1ee61d8b Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Wed, 29 Oct 2025 15:32:59 -0700 Subject: [PATCH 21/28] Added orchestration README and diagram. --- transfers/README.md | 22 ++++++++++++++++++++++ transfers/orchestration.png | Bin 0 -> 721624 bytes 2 files changed, 22 insertions(+) create mode 100644 transfers/README.md create mode 100644 transfers/orchestration.png diff --git a/transfers/README.md b/transfers/README.md new file mode 100644 index 00000000..8d6c5a1f --- /dev/null +++ b/transfers/README.md @@ -0,0 +1,22 @@ +# DTS Transfer Orchestration + +The `transfer` package implements the orchestration logic that DTS uses to perform file transfers. +It consists of a family of concurrent sequential programs, each implemented by a singleton struct +backed by a goroutine: + +* The **dispatcher** accepts transfer-related requests from "clients" (properly-routed requests + from the web service layer) and dispatching them to other programs. +* The **store** maintains the state of transfer records, allowing other programs to fetch transfer + metadata and manipulate status information. +* The **stager** handles file staging operations, initiating file transfers via the **mover** on + success. +* The **mover** handles the transfer of the file payloads, initiating the generation and transfer of + manifests via the **manifestor** on success. +* The **manifestor** generates metadata (JSON) manifests for completed transfers, moving each into + place with the rest of its payload. + +Here's a sequence diagram illustrating what happens when everything goes right: + +![A sequence diagram illustrating DTS transfer orchestration](orchestration.png) + +More detailed documentation is available in the source files for these programs. diff --git a/transfers/orchestration.png b/transfers/orchestration.png new file mode 100644 index 0000000000000000000000000000000000000000..71a84c041b36e65203adc08af49aad8856fe1fac GIT binary patch literal 721624 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

*jTANkx01%vvwPA!sjWXxv0HaKQBA+D6vN)5?=R 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 !Y_U_QXfhDWVnv4NYEo`HPvHx|L?gVvGrS;Oa3m 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

F$d6`z6BmJHYCds>3F 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 V%DPB5WEc2PT0-tp@&z4_{{uxIwvqq< literal 0 HcmV?d00001 From a82d1dac74768eb43813e526c5e9eabea6800047 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Thu, 30 Oct 2025 15:03:38 -0700 Subject: [PATCH 22/28] Reduced channel buffering. --- transfers/dispatcher.go | 26 +++++++++++++++----------- transfers/manifestor.go | 10 +++++----- transfers/mover.go | 11 ++++++----- transfers/stager.go | 10 +++++----- transfers/store.go | 27 ++++++++++++++------------- 5 files changed, 45 insertions(+), 39 deletions(-) diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index 02ede893..fe6218d9 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -72,7 +72,19 @@ type dispatcherChannels struct { Stop chan struct{} // used by client to stop task management } -func (channels *dispatcherChannels) close() { +func newDispatcherChannels(maxConnections int) dispatcherChannels { + return dispatcherChannels{ + RequestTransfer: make(chan Specification, maxConnections), + ReturnTransferId: make(chan uuid.UUID), + CancelTransfer: make(chan uuid.UUID, maxConnections), + RequestStatus: make(chan uuid.UUID, maxConnections), + ReturnStatus: make(chan TransferStatus), + Error: make(chan error), + Stop: make(chan struct{}), + } +} + +func (channels *dispatcherChannels) Close() { close(channels.RequestTransfer) close(channels.ReturnTransferId) close(channels.CancelTransfer) @@ -84,15 +96,7 @@ func (channels *dispatcherChannels) close() { func (d *dispatcherState) Start() error { slog.Debug("dispatcher.Start()") - d.Channels = dispatcherChannels{ - RequestTransfer: make(chan Specification, 32), - ReturnTransferId: make(chan uuid.UUID, 32), - CancelTransfer: make(chan uuid.UUID, 32), - RequestStatus: make(chan uuid.UUID, 32), - ReturnStatus: make(chan TransferStatus, 32), - Error: make(chan error, 32), - Stop: make(chan struct{}), - } + d.Channels = newDispatcherChannels(config.Service.MaxConnections) go d.process() return <-d.Channels.Error } @@ -101,7 +105,7 @@ func (d *dispatcherState) Stop() error { slog.Debug("dispatcher.Stop") d.Channels.Stop <- struct{}{} err := <-d.Channels.Error - d.Channels.close() + d.Channels.Close() return err } diff --git a/transfers/manifestor.go b/transfers/manifestor.go index 221bc836..39d76c5d 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -66,14 +66,14 @@ type manifestorChannels struct { func newManifestorChannels() manifestorChannels { return manifestorChannels{ - RequestGeneration: make(chan uuid.UUID, 32), - RequestCancellation: make(chan uuid.UUID, 32), - Error: make(chan error, 32), + RequestGeneration: make(chan uuid.UUID), + RequestCancellation: make(chan uuid.UUID), + Error: make(chan error), SaveAndStop: make(chan *gob.Encoder), } } -func (channels *manifestorChannels) close() { +func (channels *manifestorChannels) Close() { close(channels.RequestGeneration) close(channels.RequestCancellation) close(channels.Error) @@ -101,7 +101,7 @@ func (m *manifestorState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("manifestor.Stop") m.Channels.SaveAndStop <- encoder err := <-m.Channels.Error - m.Channels.close() + m.Channels.Close() return err } diff --git a/transfers/mover.go b/transfers/mover.go index c8e9892a..5ab572bc 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -61,15 +61,16 @@ type moverChannels struct { } func newMoverChannels() moverChannels { + numClients := 2 // dispatcher, stager return moverChannels{ - RequestMove: make(chan uuid.UUID, 32), - RequestCancellation: make(chan uuid.UUID, 32), - Error: make(chan error, 32), + RequestMove: make(chan uuid.UUID, numClients), + RequestCancellation: make(chan uuid.UUID, numClients), + Error: make(chan error), SaveAndStop: make(chan *gob.Encoder), } } -func (channels *moverChannels) close() { +func (channels *moverChannels) Close() { close(channels.RequestMove) close(channels.RequestCancellation) close(channels.Error) @@ -99,7 +100,7 @@ func (m *moverState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("mover.Stop") m.Channels.SaveAndStop <- encoder err := <-m.Channels.Error - m.Channels.close() + m.Channels.Close() return err } diff --git a/transfers/stager.go b/transfers/stager.go index f195aaaa..b55b18d1 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -61,14 +61,14 @@ type stagerChannels struct { func newStagerChannels() stagerChannels { return stagerChannels{ - RequestStaging: make(chan uuid.UUID, 31), - RequestCancellation: make(chan uuid.UUID, 31), - Error: make(chan error, 31), + RequestStaging: make(chan uuid.UUID), + RequestCancellation: make(chan uuid.UUID), + Error: make(chan error), SaveAndStop: make(chan *gob.Encoder), } } -func (channels *stagerChannels) close() { +func (channels *stagerChannels) Close() { close(channels.RequestStaging) close(channels.RequestCancellation) close(channels.Error) @@ -96,7 +96,7 @@ func (s *stagerState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("stager.Stop") s.Channels.SaveAndStop <- encoder err := <-s.Channels.Error - s.Channels.close() + s.Channels.Close() return err } diff --git a/transfers/store.go b/transfers/store.go index 0e1177cb..5b5fb56b 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -84,23 +84,24 @@ type storeChannels struct { } func newStoreChannels() storeChannels { + numClients := 4 // dispatcher, stager, mover, manifestor return storeChannels{ - RequestNewTransfer: make(chan Specification, 32), - ReturnNewTransfer: make(chan uuid.UUID, 32), - RequestSpec: make(chan uuid.UUID, 32), - ReturnSpec: make(chan Specification, 32), - RequestDescriptors: make(chan uuid.UUID, 32), - ReturnDescriptors: make(chan []map[string]any, 32), - SetStatus: make(chan transferIdAndStatus, 32), - RequestStatus: make(chan uuid.UUID, 32), - ReturnStatus: make(chan TransferStatus, 32), - RequestRemoval: make(chan uuid.UUID, 32), - Error: make(chan error, 32), + RequestNewTransfer: make(chan Specification), + ReturnNewTransfer: make(chan uuid.UUID), + RequestSpec: make(chan uuid.UUID, numClients), + ReturnSpec: make(chan Specification), + RequestDescriptors: make(chan uuid.UUID, numClients), + ReturnDescriptors: make(chan []map[string]any), + SetStatus: make(chan transferIdAndStatus, numClients), + RequestStatus: make(chan uuid.UUID, numClients), + ReturnStatus: make(chan TransferStatus), + RequestRemoval: make(chan uuid.UUID, numClients), + Error: make(chan error), SaveAndStop: make(chan *gob.Encoder), } } -func (channels *storeChannels) close() { +func (channels *storeChannels) Close() { close(channels.RequestNewTransfer) close(channels.ReturnNewTransfer) close(channels.RequestSpec) @@ -138,7 +139,7 @@ func (s *storeState) SaveAndStop(encoder *gob.Encoder) error { slog.Debug("store.Stop") s.Channels.SaveAndStop <- encoder err := <-s.Channels.Error - s.Channels.close() + s.Channels.Close() return err } From 18a6882f655e57388901b2b7168524f78d110225 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Thu, 30 Oct 2025 15:06:50 -0700 Subject: [PATCH 23/28] Rebased and patched up nmdc database constructor. --- transfers/transfers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfers/transfers.go b/transfers/transfers.go index c93b74c6..d44898c1 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -233,7 +233,7 @@ func registerDatabases(conf config.Config) error { } } if _, found := conf.Databases["nmdc"]; found { - if err := databases.RegisterDatabase("nmdc", nmdc.NewDatabase); err != nil { + if err := databases.RegisterDatabase("nmdc", nmdc.DatabaseConstructor(conf)); err != nil { slog.Error(err.Error()) } } From 2453c9c03fb5fa1bea17a32332df003d4da017c8 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Wed, 5 Nov 2025 15:21:52 -0800 Subject: [PATCH 24/28] Changes from testing DTS in Spin. --- transfers/dispatcher.go | 18 +++++++++++++----- transfers/manifestor.go | 5 +++++ transfers/mover.go | 21 +++++++++++++++++---- transfers/stager.go | 25 +++++++++++++++++++++++-- transfers/store.go | 31 ++++++++++++++++++++++++++++++- transfers/transfers.go | 9 +++++++++ 6 files changed, 97 insertions(+), 12 deletions(-) diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index fe6218d9..bf056310 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -27,6 +27,7 @@ import ( "log/slog" "os" "path/filepath" + "time" "github.com/google/uuid" @@ -114,8 +115,6 @@ func (d *dispatcherState) CreateTransfer(spec Specification) (uuid.UUID, error) d.Channels.RequestTransfer <- spec select { case id := <-d.Channels.ReturnTransferId: - slog.Info(fmt.Sprintf("Created new transfer %s (%d file(s) requested)", id.String(), - len(spec.FileIds))) return id, nil case err := <-d.Channels.Error: return uuid.UUID{}, err @@ -135,7 +134,6 @@ func (d *dispatcherState) GetTransferStatus(transferId uuid.UUID) (TransferStatu func (d *dispatcherState) CancelTransfer(transferId uuid.UUID) error { slog.Debug("dispatcher.CancelTransfer") - slog.Info(fmt.Sprintf("Canceling transfer %s", transferId.String())) d.Channels.CancelTransfer <- transferId err := <-d.Channels.Error if err != nil { @@ -163,9 +161,19 @@ func (d *dispatcherState) process() { d.Channels.ReturnTransferId <- transferId } case transferId := <-d.Channels.CancelTransfer: - if err := d.cancel(transferId); err != nil { - d.Channels.Error <- err + err := d.cancel(transferId) + if err == nil { + status, err := store.GetStatus(transferId) + if err == nil { + publish(Message{ + Description: fmt.Sprintf("Canceling transfer %s", transferId), + TransferId: transferId, + TransferStatus: status, + Time: time.Now(), + }) + } } + d.Channels.Error <- err case transferId := <-d.Channels.RequestStatus: status, err := store.GetStatus(transferId) if err != nil { diff --git a/transfers/manifestor.go b/transfers/manifestor.go index 39d76c5d..5b6992c0 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -297,6 +297,11 @@ func (m *manifestorState) updateStatus(transferId, manifestXferId uuid.UUID) (bo } if manifestStatus.Code == TransferStatusSucceeded || manifestStatus.Code == TransferStatusFailed { newStatus.Code = manifestStatus.Code + if newStatus.Code == TransferStatusSucceeded { + newStatus.Message = fmt.Sprintf("Transfer %s: completed successfully", transferId.String()) + } else { + newStatus.Message = fmt.Sprintf("Transfer %s: failed (%s)", transferId.String(), newStatus.Message) + } if err := store.SetStatus(transferId, newStatus); err != nil { return true, err } diff --git a/transfers/mover.go b/transfers/mover.go index 5ab572bc..f9d74a33 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -23,13 +23,13 @@ package transfers import ( "encoding/gob" + "fmt" "log/slog" "path/filepath" "time" "github.com/google/uuid" - "github.com/kbase/dts/config" "github.com/kbase/dts/endpoints" ) @@ -219,22 +219,33 @@ func (m *moverState) moveFiles(transferId uuid.UUID) ([]moveOperation, error) { if err != nil { return nil, err } - destination := config.Databases[spec.Destination].Endpoint - destinationEndpoint, err := endpoints.NewEndpoint(destination) + destinationEp, err := destinationEndpoint(spec.Destination) if err != nil { return nil, err } - moveId, err := sourceEndpoint.Transfer(destinationEndpoint, files) + moveId, err := sourceEndpoint.Transfer(destinationEp, files) if err != nil { return nil, err } // update the transfer status if status, err := store.GetStatus(transferId); err == nil { + payloadSize, err := store.GetPayloadSize(transferId) + if err != nil { + return nil, err + } status.Code = TransferStatusActive + status.Message = fmt.Sprintf("Transfer %s: beginning transfer (%d file(s), %g GB)", + transferId.String(), status.NumFiles, float64(payloadSize)/float64(1024*1024*1024)) if err := store.SetStatus(transferId, status); err != nil { return nil, err } + publish(Message{ + Description: status.Message, + TransferId: transferId, + TransferStatus: status, + Time: time.Now(), + }) } else { return nil, err } @@ -288,8 +299,10 @@ func (m *moverState) updateStatus(transferId uuid.UUID, moves []moveOperation) ( // take stock and update status as needed if movesAllSucceeded { newStatus.Code = TransferStatusFinalizing + newStatus.Message = fmt.Sprintf("Transfer %s: finalizing", transferId.String()) } else if atLeastOneMoveFailed { newStatus.Code = TransferStatusFailed + newStatus.Message = fmt.Sprintf("Transfer %s: failed (%s)", transferId.String(), newStatus.Message) } if newStatus != oldStatus { if err := store.SetStatus(transferId, newStatus); err != nil { diff --git a/transfers/stager.go b/transfers/stager.go index b55b18d1..d8f7da1a 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -186,6 +186,27 @@ func (s *stagerState) stageFiles(transferId uuid.UUID) (stagingEntry, error) { if err != nil { return stagingEntry{}, err } + status, err := store.GetStatus(id) + if err != nil { + return stagingEntry{}, err + } + payloadSize, err := store.GetPayloadSize(id) + if err != nil { + return stagingEntry{}, err + } + status.Code = TransferStatusStaging + status.Message = fmt.Sprintf("Transfer %s: staging %d file(s) (%g GB)", + id.String(), status.NumFiles, float64(payloadSize)/float64(1024*1024*1024)) + err = store.SetStatus(id, status) + if err != nil { + return stagingEntry{}, err + } + publish(Message{ + Description: status.Message, + TransferId: transferId, + TransferStatus: status, + Time: time.Now(), + }) return stagingEntry{Id: id}, nil } @@ -214,12 +235,12 @@ func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) ( newStatus := oldStatus switch stagingStatus { case databases.StagingStatusSucceeded: - newStatus.Message = fmt.Sprintf("file staging succeeded for transfer %s", transferId.String()) + newStatus.Message = fmt.Sprintf("Transfer %s: file staging succeeded", transferId.String()) newStatus.Code = TransferStatusActive completed = true case databases.StagingStatusFailed: newStatus.Code = TransferStatusFailed - newStatus.Message = fmt.Sprintf("file staging failed for transfer %s", transferId.String()) + newStatus.Message = fmt.Sprintf("Transfer %s: file staging failed", transferId.String()) completed = true default: // still staging newStatus.Code = TransferStatusStaging diff --git a/transfers/store.go b/transfers/store.go index 5b5fb56b..56f0df01 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -73,6 +73,9 @@ type storeChannels struct { RequestDescriptors chan uuid.UUID ReturnDescriptors chan []map[string]any + RequestPayloadSize chan uuid.UUID + ReturnPayloadSize chan uint64 + SetStatus chan transferIdAndStatus RequestStatus chan uuid.UUID ReturnStatus chan TransferStatus @@ -92,6 +95,8 @@ func newStoreChannels() storeChannels { ReturnSpec: make(chan Specification), RequestDescriptors: make(chan uuid.UUID, numClients), ReturnDescriptors: make(chan []map[string]any), + RequestPayloadSize: make(chan uuid.UUID, numClients), + ReturnPayloadSize: make(chan uint64), SetStatus: make(chan transferIdAndStatus, numClients), RequestStatus: make(chan uuid.UUID, numClients), ReturnStatus: make(chan TransferStatus), @@ -178,6 +183,16 @@ func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, err } } +func (s *storeState) GetPayloadSize(transferId uuid.UUID) (uint64, error) { + s.Channels.RequestPayloadSize <- transferId + select { + case size := <-s.Channels.ReturnPayloadSize: + return size, nil + case err := <-s.Channels.Error: + return 0, err + } +} + func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) error { slog.Debug(fmt.Sprintf("store.SetStatus (%s, %d)", transferId.String(), status.Code)) s.Channels.SetStatus <- transferIdAndStatus{ @@ -244,6 +259,16 @@ func (s *storeState) process(decoder *gob.Decoder) { } else { s.Channels.Error <- TransferNotFoundError{Id: id} } + case id := <-s.Channels.RequestPayloadSize: + if transfer, found := transfers[id]; found { + var size uint64 + for _, descriptor := range transfer.Descriptors { + size += uint64(descriptor["bytes"].(int)) + } + s.Channels.ReturnPayloadSize <- size + } else { + s.Channels.Error <- TransferNotFoundError{Id: id} + } case id := <-s.Channels.RequestSpec: if transfer, found := transfers[id]; found { s.Channels.ReturnSpec <- transfer.Spec @@ -318,8 +343,12 @@ func (s *storeState) newTransfer(spec Specification) (uuid.UUID, transferStoreEn }, } + var size uint64 + for _, descriptor := range entry.Descriptors { + size += uint64(descriptor["bytes"].(int)) + } publish(Message{ - Description: "New transfer created.", + Description: fmt.Sprintf("Created new transfer %s (%d file(s), %g GB)", id, entry.Status.NumFiles, float64(size)/float64(1024*1024*1024)), TransferId: id, TransferStatus: entry.Status, Time: time.Now(), diff --git a/transfers/transfers.go b/transfers/transfers.go index d44898c1..a02d3a14 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -101,6 +101,15 @@ func Start(conf config.Config) error { global.Running = true + // subscribe to the transfer feed to log events + subscription := Subscribe(32) + go func() { + for global.Running { + message := <-subscription.Channel + slog.Info(message.Description) + } + }() + return nil } From cf1217cbdbe6de72c9a69c500ff1aa06bd98e968 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Thu, 6 Nov 2025 13:28:34 -0800 Subject: [PATCH 25/28] Fixed a staging issue. --- transfers/stager.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/transfers/stager.go b/transfers/stager.go index d8f7da1a..ccfb6c53 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -142,7 +142,7 @@ func (s *stagerState) process(decoder *gob.Decoder) { if err == nil { stagings[transferId] = entry } - s.Channels.Error <- nil + s.Channels.Error <- err case transferId := <-s.Channels.RequestCancellation: if _, found := stagings[transferId]; found { delete(stagings, transferId) // simply remove the entry and stop tracking file staging @@ -182,22 +182,22 @@ func (s *stagerState) stageFiles(transferId uuid.UUID) (stagingEntry, error) { if err != nil { return stagingEntry{}, err } - id, err := db.StageFiles(spec.User.Orcid, spec.FileIds) + stagingId, err := db.StageFiles(spec.User.Orcid, spec.FileIds) if err != nil { return stagingEntry{}, err } - status, err := store.GetStatus(id) + status, err := store.GetStatus(transferId) if err != nil { return stagingEntry{}, err } - payloadSize, err := store.GetPayloadSize(id) + payloadSize, err := store.GetPayloadSize(transferId) if err != nil { return stagingEntry{}, err } status.Code = TransferStatusStaging status.Message = fmt.Sprintf("Transfer %s: staging %d file(s) (%g GB)", - id.String(), status.NumFiles, float64(payloadSize)/float64(1024*1024*1024)) - err = store.SetStatus(id, status) + transferId.String(), status.NumFiles, float64(payloadSize)/float64(1024*1024*1024)) + err = store.SetStatus(transferId, status) if err != nil { return stagingEntry{}, err } @@ -207,7 +207,7 @@ func (s *stagerState) stageFiles(transferId uuid.UUID) (stagingEntry, error) { TransferStatus: status, Time: time.Now(), }) - return stagingEntry{Id: id}, nil + return stagingEntry{Id: stagingId}, nil } // update transfer status, returning true iff completed From 0ff128aed467e38d4cb4bf320976e3d6bec86f3c Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Thu, 6 Nov 2025 14:53:23 -0800 Subject: [PATCH 26/28] Fixing last batch of defects. --- journal/journal.go | 2 +- journal/journal_test.go | 4 +- transfers/manifestor.go | 97 +++++++++++++++++++++++++++++++---------- transfers/mover.go | 31 ++++++++++++- transfers/stager.go | 25 +++++++++++ transfers/transfers.go | 28 +++++++++--- 6 files changed, 153 insertions(+), 34 deletions(-) diff --git a/journal/journal.go b/journal/journal.go index 4333f217..b1ddd622 100644 --- a/journal/journal.go +++ b/journal/journal.go @@ -54,7 +54,7 @@ type Record struct { // status of the transfer ("succeeded", "failed", or "canceled") Status string `json:"status"` // size of the transfer's payload in bytes - PayloadSize int64 `json:"payload_size"` + PayloadSize uint64 `json:"payload_size"` // number of files in the transfer's payload NumFiles int `json:"num_files"` // manifest containing metadata for the transfer's payload (stored separate from record) diff --git a/journal/journal_test.go b/journal/journal_test.go index 8f8db948..0ad7619e 100644 --- a/journal/journal_test.go +++ b/journal/journal_test.go @@ -129,7 +129,7 @@ func (t *SerialTests) TestRecordSuccessfulTransfer() { Status: "succeeded", StartTime: time.Now().Add(-time.Hour), StopTime: time.Now(), - PayloadSize: int64(12853294), + PayloadSize: uint64(12853294), NumFiles: 12, Manifest: manifest, } @@ -170,7 +170,7 @@ func (t *SerialTests) TestRecordFailedTransfer() { Status: "failed", StartTime: time.Now(), StopTime: time.Now(), - PayloadSize: int64(12853294), + PayloadSize: uint64(12853294), NumFiles: 12, } err = RecordTransfer(record) diff --git a/transfers/manifestor.go b/transfers/manifestor.go index 5b6992c0..c6c9da62 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -25,7 +25,9 @@ import ( "encoding/gob" "fmt" "log/slog" + "os" "path/filepath" + "strings" "time" "github.com/frictionlessdata/datapackage-go/datapackage" @@ -34,6 +36,7 @@ import ( "github.com/kbase/dts/config" "github.com/kbase/dts/databases" "github.com/kbase/dts/endpoints" + "github.com/kbase/dts/journal" ) //------------ @@ -125,17 +128,23 @@ func (m *manifestorState) Cancel(transferId uuid.UUID) error { // everything past here runs in the manifestor's goroutine //---------------------------------------------------- +type manifestEntry struct { + ManifestTransferId uuid.UUID + Manifest *datapackage.Package + Filename string +} + // the goroutine itself (accepts optional decoder for loading saved data) func (m *manifestorState) process(decoder *gob.Decoder) { // load or create transfer records - var transfers map[uuid.UUID]uuid.UUID + var transfers map[uuid.UUID]manifestEntry if decoder != nil { if err := decoder.Decode(&transfers); err != nil { m.Channels.Error <- err return } } else { - transfers = make(map[uuid.UUID]uuid.UUID) + transfers = make(map[uuid.UUID]manifestEntry) } running := true @@ -145,14 +154,14 @@ func (m *manifestorState) process(decoder *gob.Decoder) { for running { select { case transferId := <-m.Channels.RequestGeneration: - manifestXferId, err := m.generateAndSendManifest(transferId) + entry, err := m.generateAndSendManifest(transferId) if err == nil { - transfers[transferId] = manifestXferId + transfers[transferId] = entry } m.Channels.Error <- err case transferId := <-m.Channels.RequestCancellation: - if manifestXferId, found := transfers[transferId]; found { - err := m.cancel(manifestXferId) + if entry, found := transfers[transferId]; found { + err := m.cancel(entry.ManifestTransferId) if err == nil { delete(transfers, transferId) } @@ -162,13 +171,14 @@ func (m *manifestorState) process(decoder *gob.Decoder) { } case <-pulse: // check the manifest transfers - for transferId, manifestXferId := range transfers { - completed, err := m.updateStatus(transferId, manifestXferId) + for transferId, entry := range transfers { + completed, err := m.updateStatus(transferId, entry) if err != nil { slog.Error(err.Error()) continue } if completed { + os.Remove(entry.Filename) delete(transfers, transferId) } } @@ -180,46 +190,54 @@ func (m *manifestorState) process(decoder *gob.Decoder) { clock.Unsubscribe() } -func (m *manifestorState) generateAndSendManifest(transferId uuid.UUID) (uuid.UUID, error) { +func (m *manifestorState) generateAndSendManifest(transferId uuid.UUID) (manifestEntry, error) { spec, err := store.GetSpecification(transferId) if err != nil { - return uuid.UUID{}, err + return manifestEntry{}, err } manifest, err := m.generateManifest(transferId, spec) if err != nil { - return uuid.UUID{}, err + return manifestEntry{}, err } - manifestFile := filepath.Join(config.Service.ManifestDirectory, fmt.Sprintf("manifest-%s.json", transferId.String())) - if err := manifest.SaveDescriptor(manifestFile); err != nil { - return uuid.UUID{}, fmt.Errorf("creating manifest file: %s", err.Error()) + filename := filepath.Join(config.Service.ManifestDirectory, fmt.Sprintf("manifest-%s.json", transferId.String())) + if err := manifest.SaveDescriptor(filename); err != nil { + return manifestEntry{}, fmt.Errorf("creating manifest file: %s", err.Error()) } // begin transferring the manifest source, err := endpoints.NewEndpoint(config.Service.Endpoint) if err != nil { - return uuid.UUID{}, err + return manifestEntry{}, err + } + destination, err := determineDestinationEndpoint(spec.Destination) + if err != nil { + return manifestEntry{}, err } - destination, err := destinationEndpoint(spec.Destination) + destinationFolder, err := determineDestinationFolder(transferId) if err != nil { - return uuid.UUID{}, err + return manifestEntry{}, err } manifestXferId, err := source.Transfer(destination, []FileTransfer{ { - SourcePath: manifestFile, - DestinationPath: filepath.Join(destinationFolder(spec.Destination), "manifest.json"), + SourcePath: filename, + DestinationPath: filepath.Join(destinationFolder, "manifest.json"), }, }) if err != nil { - return uuid.UUID{}, fmt.Errorf("transferring manifest file: %s", err.Error()) + return manifestEntry{}, fmt.Errorf("transferring manifest file: %s", err.Error()) } status, err := store.GetStatus(transferId) if err != nil { - return uuid.UUID{}, err + return manifestEntry{}, err } status.Code = TransferStatusFinalizing - return manifestXferId, store.SetStatus(transferId, status) + return manifestEntry{ + ManifestTransferId: manifestXferId, + Manifest: manifest, + Filename: filename, + }, store.SetStatus(transferId, status) } // generates a manifest for the transfer with the given ID and begins transferring it to its @@ -280,7 +298,7 @@ func (m *manifestorState) generateManifest(transferId uuid.UUID, spec Specificat // update the status of the manifest transfer with the given ID, returning true if the transfer has // completed (successfully or unsuccessfully), false otherwise -func (m *manifestorState) updateStatus(transferId, manifestXferId uuid.UUID) (bool, error) { +func (m *manifestorState) updateStatus(transferId uuid.UUID, entry manifestEntry) (bool, error) { oldStatus, err := store.GetStatus(transferId) if err != nil { return false, err @@ -291,7 +309,7 @@ func (m *manifestorState) updateStatus(transferId, manifestXferId uuid.UUID) (bo if err != nil { return false, err } - manifestStatus, err := source.Status(manifestXferId) + manifestStatus, err := source.Status(entry.ManifestTransferId) if err != nil { return false, err } @@ -305,6 +323,37 @@ func (m *manifestorState) updateStatus(transferId, manifestXferId uuid.UUID) (bo if err := store.SetStatus(transferId, newStatus); err != nil { return true, err } + + // write a transfer record to the journal + spec, err := store.GetSpecification(transferId) + if err != nil { + return true, err + } + size, err := store.GetPayloadSize(transferId) + if err != nil { + return true, err + } + var statusString string + if strings.Contains(newStatus.Message, "success") { + statusString = "succeeded" + } else { + statusString = "failed" + } + err = journal.RecordTransfer(journal.Record{ + Id: transferId, + Source: spec.Source, + Destination: spec.Destination, + Orcid: spec.User.Orcid, + StartTime: spec.TimeOfRequest, + StopTime: time.Now(), + Status: statusString, + PayloadSize: size, + NumFiles: len(spec.FileIds), + Manifest: entry.Manifest, + }) + if err != nil { + slog.Error(err.Error()) + } publish(Message{ Description: newStatus.Message, TransferId: transferId, diff --git a/transfers/mover.go b/transfers/mover.go index f9d74a33..dab52815 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -31,6 +31,7 @@ import ( "github.com/google/uuid" "github.com/kbase/dts/endpoints" + "github.com/kbase/dts/journal" ) //------- @@ -168,6 +169,28 @@ func (m *moverState) process(decoder *gob.Decoder) { if err != nil { slog.Error(err.Error()) } + } else { // failed -- write an entry to the journal + spec, err := store.GetSpecification(transferId) + if err == nil { + var size uint64 + size, err = store.GetPayloadSize(transferId) + if err == nil { + err = journal.RecordTransfer(journal.Record{ + Id: transferId, + Source: spec.Source, + Destination: spec.Destination, + Orcid: spec.User.Orcid, + StartTime: spec.TimeOfRequest, + StopTime: time.Now(), + Status: "failed", + PayloadSize: size, + NumFiles: len(spec.FileIds), + }) + } + } + if err != nil { + slog.Error(err.Error()) + } } delete(moveOperations, transferId) } @@ -204,11 +227,15 @@ func (m *moverState) moveFiles(transferId uuid.UUID) ([]moveOperation, error) { return nil, err } moves := make([]moveOperation, 0) + destinationFolder, err := determineDestinationFolder(transferId) + if err != nil { + return nil, err + } for source, descriptorsForSource := range descriptorsForEndpoint { files := make([]endpoints.FileTransfer, len(descriptorsForSource)) for i, descriptor := range descriptorsForSource { path := descriptor["path"].(string) - destinationPath := filepath.Join(destinationFolder(spec.Destination), path) + destinationPath := filepath.Join(destinationFolder, path) files[i] = endpoints.FileTransfer{ SourcePath: path, DestinationPath: destinationPath, @@ -219,7 +246,7 @@ func (m *moverState) moveFiles(transferId uuid.UUID) ([]moveOperation, error) { if err != nil { return nil, err } - destinationEp, err := destinationEndpoint(spec.Destination) + destinationEp, err := determineDestinationEndpoint(spec.Destination) if err != nil { return nil, err } diff --git a/transfers/stager.go b/transfers/stager.go index ccfb6c53..759dbd0b 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -30,6 +30,7 @@ import ( "github.com/google/uuid" "github.com/kbase/dts/databases" + "github.com/kbase/dts/journal" ) //-------- @@ -256,6 +257,30 @@ func (s *stagerState) updateStatus(transferId uuid.UUID, staging stagingEntry) ( TransferStatus: newStatus, Time: time.Now(), }) + if newStatus.Code == TransferStatusFailed { // write an entry to the journal + spec, err := store.GetSpecification(transferId) + if err == nil { + var size uint64 + size, err = store.GetPayloadSize(transferId) + if err == nil { + err = journal.RecordTransfer(journal.Record{ + Id: transferId, + Source: spec.Source, + Destination: spec.Destination, + Orcid: spec.User.Orcid, + StartTime: spec.TimeOfRequest, + StopTime: time.Now(), + Status: "failed", + PayloadSize: size, + NumFiles: len(spec.FileIds), + }) + } + } + if err != nil { + slog.Error(err.Error()) + } + + } } if newStatus.Code == TransferStatusActive { diff --git a/transfers/transfers.go b/transfers/transfers.go index a02d3a14..b80078b9 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -28,6 +28,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/google/uuid" @@ -152,6 +153,8 @@ type Specification struct { FileIds []string // the name of source database from which files are transferred (as specified in the config file) Source string + // the time at which the transfer is requested + TimeOfRequest time.Time // information about the user requesting the task User auth.User } @@ -181,6 +184,8 @@ func Create(spec Specification) (uuid.UUID, error) { } } + spec.TimeOfRequest = time.Now() + // create a new task and send it along for processing return dispatcher.CreateTransfer(spec) } @@ -319,7 +324,7 @@ func validateDirectory(dirType, dir string) error { // * store: maintains metadata records and status info for ongoing and completed transfers // resolves the given destination (name) string, accounting for custom transfers -func destinationEndpoint(destination string) (endpoints.Endpoint, error) { +func determineDestinationEndpoint(destination string) (endpoints.Endpoint, error) { // everything's been validated at this point, so no need to check for errors if strings.Contains(destination, ":") { // custom transfer spec customSpec, _ := endpoints.ParseCustomSpec(destination) @@ -332,11 +337,24 @@ func destinationEndpoint(destination string) (endpoints.Endpoint, error) { } // resolves the folder at the given destination in which transferred files are deposited -func destinationFolder(destination string) string { - if customSpec, err := endpoints.ParseCustomSpec(destination); err == nil { // custom transfer? - return customSpec.Path +func determineDestinationFolder(transferId uuid.UUID) (string, error) { + spec, err := store.GetSpecification(transferId) + if err != nil { + return "", err + } + dtsFolder := "dts-" + transferId.String() + if customSpec, err := endpoints.ParseCustomSpec(spec.Destination); err == nil { // custom transfer? + return filepath.Join(customSpec.Path, dtsFolder), nil + } + destDb, err := databases.NewDatabase(spec.Destination) + if err != nil { + return "", err + } + username, err := destDb.LocalUser(spec.User.Orcid) + if err != nil { + return "", err } - return "" + return filepath.Join(username, dtsFolder), nil } // given a set of Frictionless DataResource descriptors, returns a map mapping the name of each From a86719d84d53e10da12cc0de86e8572966c5f9f2 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Thu, 6 Nov 2025 14:56:28 -0800 Subject: [PATCH 27/28] Removed debugging statements. --- transfers/dispatcher.go | 5 ----- transfers/manifestor.go | 5 ----- transfers/mover.go | 5 ----- transfers/stager.go | 5 ----- transfers/store.go | 10 ---------- 5 files changed, 30 deletions(-) diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index bf056310..fb87b5ec 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -96,14 +96,12 @@ func (channels *dispatcherChannels) Close() { } func (d *dispatcherState) Start() error { - slog.Debug("dispatcher.Start()") d.Channels = newDispatcherChannels(config.Service.MaxConnections) go d.process() return <-d.Channels.Error } func (d *dispatcherState) Stop() error { - slog.Debug("dispatcher.Stop") d.Channels.Stop <- struct{}{} err := <-d.Channels.Error d.Channels.Close() @@ -111,7 +109,6 @@ func (d *dispatcherState) Stop() error { } func (d *dispatcherState) CreateTransfer(spec Specification) (uuid.UUID, error) { - slog.Debug("dispatcher.CreateTransfer") d.Channels.RequestTransfer <- spec select { case id := <-d.Channels.ReturnTransferId: @@ -122,7 +119,6 @@ func (d *dispatcherState) CreateTransfer(spec Specification) (uuid.UUID, error) } func (d *dispatcherState) GetTransferStatus(transferId uuid.UUID) (TransferStatus, error) { - slog.Debug("dispatcher.GetTransferStatus") d.Channels.RequestStatus <- transferId select { case status := <-d.Channels.ReturnStatus: @@ -133,7 +129,6 @@ func (d *dispatcherState) GetTransferStatus(transferId uuid.UUID) (TransferStatu } func (d *dispatcherState) CancelTransfer(transferId uuid.UUID) error { - slog.Debug("dispatcher.CancelTransfer") d.Channels.CancelTransfer <- transferId err := <-d.Channels.Error if err != nil { diff --git a/transfers/manifestor.go b/transfers/manifestor.go index c6c9da62..974cccaa 100644 --- a/transfers/manifestor.go +++ b/transfers/manifestor.go @@ -84,7 +84,6 @@ func (channels *manifestorChannels) Close() { } func (m *manifestorState) Start() error { - slog.Debug("manifestor.Start") m.Channels = newManifestorChannels() m.Endpoints = make(map[string]endpoints.Endpoint) go m.process(nil) @@ -92,7 +91,6 @@ func (m *manifestorState) Start() error { } func (m *manifestorState) Load(decoder *gob.Decoder) error { - slog.Debug("manifestor.Load") m.Channels = newManifestorChannels() m.Endpoints = make(map[string]endpoints.Endpoint) go m.process(decoder) @@ -101,7 +99,6 @@ func (m *manifestorState) Load(decoder *gob.Decoder) error { // stops the manifestor goroutine func (m *manifestorState) SaveAndStop(encoder *gob.Encoder) error { - slog.Debug("manifestor.Stop") m.Channels.SaveAndStop <- encoder err := <-m.Channels.Error m.Channels.Close() @@ -111,7 +108,6 @@ func (m *manifestorState) SaveAndStop(encoder *gob.Encoder) error { // starts generating a manifest for the given transfer, moving it subsequently to that transfer's // destination func (m *manifestorState) Generate(transferId uuid.UUID) error { - slog.Debug("manifestor.Generate") m.Channels.RequestGeneration <- transferId return <-m.Channels.Error } @@ -119,7 +115,6 @@ func (m *manifestorState) Generate(transferId uuid.UUID) error { // cancels the generation/transfer of a manifest // destination func (m *manifestorState) Cancel(transferId uuid.UUID) error { - slog.Debug("manifestor.Cancel") m.Channels.RequestCancellation <- transferId return <-m.Channels.Error } diff --git a/transfers/mover.go b/transfers/mover.go index dab52815..81d17e97 100644 --- a/transfers/mover.go +++ b/transfers/mover.go @@ -80,7 +80,6 @@ func (channels *moverChannels) Close() { // starts the mover func (m *moverState) Start() error { - slog.Debug("mover.Start") m.Channels = newMoverChannels() m.Endpoints = make(map[string]endpoints.Endpoint) go m.process(nil) @@ -89,7 +88,6 @@ func (m *moverState) Start() error { // loads the mover from saved data func (m *moverState) Load(decoder *gob.Decoder) error { - slog.Debug("mover.Start") m.Channels = newMoverChannels() m.Endpoints = make(map[string]endpoints.Endpoint) go m.process(decoder) @@ -98,7 +96,6 @@ func (m *moverState) Load(decoder *gob.Decoder) error { // stops the mover goroutine func (m *moverState) SaveAndStop(encoder *gob.Encoder) error { - slog.Debug("mover.Stop") m.Channels.SaveAndStop <- encoder err := <-m.Channels.Error m.Channels.Close() @@ -107,14 +104,12 @@ func (m *moverState) SaveAndStop(encoder *gob.Encoder) error { // starts moving files associated with the given transfer ID func (m *moverState) MoveFiles(transferId uuid.UUID) error { - slog.Debug("mover.MoveFiles") m.Channels.RequestMove <- transferId return <-m.Channels.Error } // cancels a file move operation func (m *moverState) Cancel(transferId uuid.UUID) error { - slog.Debug("mover.Cancel") m.Channels.RequestCancellation <- transferId return <-m.Channels.Error } diff --git a/transfers/stager.go b/transfers/stager.go index 759dbd0b..bab6f59b 100644 --- a/transfers/stager.go +++ b/transfers/stager.go @@ -78,7 +78,6 @@ func (channels *stagerChannels) Close() { // starts the stager func (s *stagerState) Start() error { - slog.Debug("stager.Start") s.Channels = newStagerChannels() go s.process(nil) return <-s.Channels.Error @@ -86,7 +85,6 @@ func (s *stagerState) Start() error { // loads the stager from saved data func (s *stagerState) Load(decoder *gob.Decoder) error { - slog.Debug("stager.Load") s.Channels = newStagerChannels() go s.process(decoder) return <-s.Channels.Error @@ -94,7 +92,6 @@ func (s *stagerState) Load(decoder *gob.Decoder) error { // stops the stager goroutine func (s *stagerState) SaveAndStop(encoder *gob.Encoder) error { - slog.Debug("stager.Stop") s.Channels.SaveAndStop <- encoder err := <-s.Channels.Error s.Channels.Close() @@ -103,14 +100,12 @@ func (s *stagerState) SaveAndStop(encoder *gob.Encoder) error { // requests that files be staged for the transfer with the given ID func (s *stagerState) StageFiles(id uuid.UUID) error { - slog.Debug("stager.StageFiles") s.Channels.RequestStaging <- id return <-s.Channels.Error } // cancels a file staging operation func (s *stagerState) Cancel(transferId uuid.UUID) error { - slog.Debug("stager.Cancel") s.Channels.RequestCancellation <- transferId return <-s.Channels.Error } diff --git a/transfers/store.go b/transfers/store.go index 56f0df01..d3666598 100644 --- a/transfers/store.go +++ b/transfers/store.go @@ -25,7 +25,6 @@ import ( "cmp" "encoding/gob" "fmt" - "log/slog" "slices" "time" @@ -127,21 +126,18 @@ type transferIdAndStatus struct { } func (s *storeState) Start() error { - slog.Debug("store.Start") s.Channels = newStoreChannels() go s.process(nil) return <-s.Channels.Error } func (s *storeState) Load(decoder *gob.Decoder) error { - slog.Debug("store.Start") s.Channels = newStoreChannels() go s.process(decoder) return <-s.Channels.Error } func (s *storeState) SaveAndStop(encoder *gob.Encoder) error { - slog.Debug("store.Stop") s.Channels.SaveAndStop <- encoder err := <-s.Channels.Error s.Channels.Close() @@ -151,7 +147,6 @@ func (s *storeState) SaveAndStop(encoder *gob.Encoder) error { // creates a new entry for a transfer within the store, populating it with relevant metadata and // returning a UUID, number of files, and/or error condition for the request func (s *storeState) NewTransfer(spec Specification) (uuid.UUID, error) { - slog.Debug("store.NewTransfer") s.Channels.RequestNewTransfer <- spec select { case id := <-s.Channels.ReturnNewTransfer: @@ -162,7 +157,6 @@ func (s *storeState) NewTransfer(spec Specification) (uuid.UUID, error) { } func (s *storeState) GetSpecification(transferId uuid.UUID) (Specification, error) { - slog.Debug(fmt.Sprintf("store.GetSpecification (%s)", transferId.String())) s.Channels.RequestSpec <- transferId select { case spec := <-s.Channels.ReturnSpec: @@ -173,7 +167,6 @@ func (s *storeState) GetSpecification(transferId uuid.UUID) (Specification, erro } func (s *storeState) GetDescriptors(transferId uuid.UUID) ([]map[string]any, error) { - slog.Debug(fmt.Sprintf("store.GetDescriptors (%s)", transferId.String())) s.Channels.RequestDescriptors <- transferId select { case descriptors := <-s.Channels.ReturnDescriptors: @@ -194,7 +187,6 @@ func (s *storeState) GetPayloadSize(transferId uuid.UUID) (uint64, error) { } func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) error { - slog.Debug(fmt.Sprintf("store.SetStatus (%s, %d)", transferId.String(), status.Code)) s.Channels.SetStatus <- transferIdAndStatus{ Id: transferId, Status: status, @@ -203,7 +195,6 @@ func (s *storeState) SetStatus(transferId uuid.UUID, status TransferStatus) erro } func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { - slog.Debug(fmt.Sprintf("store.GetStatus (%s)", transferId.String())) s.Channels.RequestStatus <- transferId select { case status := <-s.Channels.ReturnStatus: @@ -214,7 +205,6 @@ func (s *storeState) GetStatus(transferId uuid.UUID) (TransferStatus, error) { } func (s *storeState) Remove(transferId uuid.UUID) error { - slog.Debug(fmt.Sprintf("store.Remove (%s)", transferId.String())) s.Channels.RequestRemoval <- transferId return <-s.Channels.Error } From 4b0e2dde6f768892b6fd490319d9cc361b8435f1 Mon Sep 17 00:00:00 2001 From: "Jeffrey N. Johnson" Date: Fri, 7 Nov 2025 08:51:26 -0800 Subject: [PATCH 28/28] Eradicated old "task" language from transfers package, and removed old test code. --- transfers/dispatcher.go | 8 ++++---- transfers/errors.go | 4 ++-- transfers/transfers.go | 33 +++++++++++++++------------------ transfers/transfers_test.go | 10 ++-------- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/transfers/dispatcher.go b/transfers/dispatcher.go index fb87b5ec..9077df5b 100644 --- a/transfers/dispatcher.go +++ b/transfers/dispatcher.go @@ -62,15 +62,15 @@ type dispatcherState struct { type dispatcherChannels struct { RequestTransfer chan Specification // used by client to create a new transfer - ReturnTransferId chan uuid.UUID // returns task ID to client + ReturnTransferId chan uuid.UUID // returns transfer ID to client CancelTransfer chan uuid.UUID // used by client to cancel a transfer RequestStatus chan uuid.UUID // used by client to request transfer status - ReturnStatus chan TransferStatus // returns task status to client + ReturnStatus chan TransferStatus // returns transfer status to client Error chan error // internal -> client error propagation - Stop chan struct{} // used by client to stop task management + Stop chan struct{} // used by client to stop transfer management } func newDispatcherChannels(maxConnections int) dispatcherChannels { @@ -201,7 +201,7 @@ func (d *dispatcherState) start() error { return manifestor.Start() } - slog.Debug(fmt.Sprintf("found previous tasks in %s", saveFilename)) + slog.Debug(fmt.Sprintf("found previous transfers in %s", saveFilename)) defer saveFile.Close() decoder := gob.NewDecoder(saveFile) var databaseStates databases.DatabaseSaveStates diff --git a/transfers/errors.go b/transfers/errors.go index df086f8d..23a69619 100644 --- a/transfers/errors.go +++ b/transfers/errors.go @@ -29,14 +29,14 @@ import ( "github.com/kbase/dts/config" ) -// indicates that Start() has been called when tasks are being processed +// indicates that Start() has been called when transfers are being processed type AlreadyRunningError struct{} func (t AlreadyRunningError) Error() string { return "transfer orchestration is already running and cannot be started again" } -// indicates that Stop() has been called when tasks are not being processed +// indicates that Stop() has been called when transfers are not being processed type NotRunningError struct{} func (t NotRunningError) Error() string { diff --git a/transfers/transfers.go b/transfers/transfers.go index b80078b9..0cfb4972 100644 --- a/transfers/transfers.go +++ b/transfers/transfers.go @@ -114,7 +114,7 @@ func Start(conf config.Config) error { return nil } -// Stops processing tasks. Adding new tasks and requesting task statuses are +// Stops processing transfers. Adding new transfers and requesting transfer statuses are // disallowed in a stopped state. func Stop() error { var err error @@ -135,14 +135,14 @@ func Stop() error { return err } -// Returns true if tasks are currently being processed, false if not. +// Returns true if transfers are currently being processed, false if not. func Running() bool { return global.Running } -// this type holds a specification used to create a valid transfer task +// this type holds a specification used to create a valid transfer transfer type Specification struct { - // a Markdown description of the transfer task + // a Markdown description of the transfer transfer Description string // the name of destination database to which files are transferred (as specified in the config // file) OR a custom destination spec (::) @@ -155,49 +155,46 @@ type Specification struct { Source string // the time at which the transfer is requested TimeOfRequest time.Time - // information about the user requesting the task + // information about the user requesting the transfer User auth.User } -// Creates a new transfer task associated with the user with the specified Orcid -// ID to the manager's set, returning a UUID for the task. The task is defined -// by specifying the names of the source and destination databases and a set of -// file IDs associated with the source. +// Creates a new transfer associated with the user with the specified Orcid ID to the manager's set, +// returning a UUID for the transfer. The transfer is defined by specifying the names of the source +// and destination databases and a set of file IDs associated with the source. func Create(spec Specification) (uuid.UUID, error) { - var taskId uuid.UUID - // have we requested files to be transferred? if len(spec.FileIds) == 0 { - return taskId, &NoFilesRequestedError{} + return uuid.UUID{}, &NoFilesRequestedError{} } // verify the source and destination strings _, err := databases.NewDatabase(spec.Source) // source must refer to a database if err != nil { - return taskId, err + return uuid.UUID{}, err } // destination can be a database OR a custom location if _, err = databases.NewDatabase(spec.Destination); err != nil { if _, err = endpoints.ParseCustomSpec(spec.Destination); err != nil { - return taskId, err + return uuid.UUID{}, err } } spec.TimeOfRequest = time.Now() - // create a new task and send it along for processing + // create a new transfer and send it along for processing return dispatcher.CreateTransfer(spec) } -// Given a task UUID, returns its transfer status (or a non-nil error +// Given a transfer ID, returns its transfer status (or a non-nil error // indicating any issues encountered). func Status(transferId uuid.UUID) (TransferStatus, error) { return dispatcher.GetTransferStatus(transferId) } -// Requests that the task with the given UUID be canceled. Clients should check -// the status of the task separately. +// Requests that the transfer with the given UUID be canceled. Clients should check +// the status of the transfer separately. func Cancel(transferId uuid.UUID) error { return dispatcher.CancelTransfer(transferId) } diff --git a/transfers/transfers_test.go b/transfers/transfers_test.go index 6f341ac4..a12674dd 100644 --- a/transfers/transfers_test.go +++ b/transfers/transfers_test.go @@ -19,7 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// These tests must be run serially, since tasks are coordinated by a +// These tests must be run serially, since transfers are coordinated by a // single instance. package transfers @@ -43,12 +43,6 @@ import ( // this runner runs all tests for all the singletons in this package func TestRunner(t *testing.T) { - /* - dispatcherTests := DispatcherTests{Test: t} - print("dispatcher start/stop\n") - dispatcherTests.TestStartAndStop() - */ - transfers := TransferTests{Test: t} transfers.TestStartAndStop() transfers.TestCreate() @@ -78,7 +72,7 @@ func (t *TransferTests) TestCreate() { saveFilename := filepath.Join(conf.Service.DataDirectory, "dts.gob") os.Remove(saveFilename) - // subscribe to the transfer mechanism's feed and collect messages describing the task's journey + // subscribe to the transfer mechanism's feed and collect messages describing the transfer's journey // through the aether var messages []Message subscription := Subscribe(32)

lMwnaRjxJz; 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�k^8y(f-d4y2*Z(C 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