Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions pkg/synchronization/endpoint/remote/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"errors"
"fmt"
"io"
"os"
"path/filepath"

"google.golang.org/protobuf/proto"

Expand Down Expand Up @@ -33,6 +35,31 @@ type endpointServer struct {
decoder *encoding.ProtobufDecoder
}

// ensureSynchronizationRootParentExists ensures that the parent directory chain
// for a synchronization root exists if the root itself doesn't already exist.
func ensureSynchronizationRootParentExists(root string) error {
// If the synchronization root already exists, then there's nothing to do.
if _, err := os.Lstat(root); err == nil {
return nil
} else if !os.IsNotExist(err) {
return fmt.Errorf("unable to query synchronization root: %w", err)
}

// Compute the parent directory. If the root is a filesystem root, then
// there's no higher-level directory hierarchy to create.
parent := filepath.Dir(root)
if parent == root {
return nil
}

// Create any missing parent directories using standard mkdir -p semantics.
if err := os.MkdirAll(parent, 0777); err != nil {
return fmt.Errorf("unable to create synchronization root parent directory hierarchy: %w", err)
}

return nil
}

// ServeEndpoint creates and serves a endpoint server on the specified stream.
// It enforces that the provided stream is closed by the time this function
// returns, regardless of failure. The provided stream must unblock read and
Expand Down Expand Up @@ -104,6 +131,18 @@ func ServeEndpoint(logger *logging.Logger, stream io.ReadWriteCloser) error {
request.Root = r
}

// For remote beta endpoints, ensure that the synchronization root's parent
// directory chain exists so that a missing directory root can be created by
// the normal transition logic.
if !request.Alpha {
if err := ensureSynchronizationRootParentExists(request.Root); err != nil {
err = fmt.Errorf("unable to prepare synchronization root parent directory hierarchy: %w", err)
encoder.Encode(&InitializeSynchronizationResponse{Error: err.Error()})
flusher.Flush()
return err
}
}

// Create the underlying endpoint. If it fails to create, then send a
// failure response and abort. If it succeeds, then defer its closure.
endpoint, err := local.NewEndpoint(
Expand Down
33 changes: 32 additions & 1 deletion pkg/synchronization/endpoint/remote/server_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
package remote

// TODO: Implement.
import (
"os"
"path/filepath"
"testing"
)

func TestEnsureSynchronizationRootParentExistsCreatesMissingParentsOnly(t *testing.T) {
root := filepath.Join(t.TempDir(), "missing", "parent", "root")

if err := ensureSynchronizationRootParentExists(root); err != nil {
t.Fatalf("unable to ensure parent hierarchy: %v", err)
}

if _, err := os.Stat(filepath.Dir(root)); err != nil {
t.Fatalf("parent directory hierarchy not created: %v", err)
}

if _, err := os.Stat(root); !os.IsNotExist(err) {
t.Fatalf("synchronization root should not be created directly, err: %v", err)
}
}

func TestEnsureSynchronizationRootParentExistsNoopIfRootExists(t *testing.T) {
root := filepath.Join(t.TempDir(), "root")
if err := os.Mkdir(root, 0700); err != nil {
t.Fatalf("unable to create test root: %v", err)
}

if err := ensureSynchronizationRootParentExists(root); err != nil {
t.Fatalf("unable to ensure parent hierarchy for existing root: %v", err)
}
}