Skip to content
This repository was archived by the owner on Aug 15, 2022. It is now read-only.
Merged
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
1 change: 1 addition & 0 deletions go/src/koding/klient/app/klient.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ func (k *Klient) RegisterMethods() {
k.kite.HandleFunc("machine.create", machinegroup.KiteHandlerCreate(k.machines))
k.kite.HandleFunc("machine.id", machinegroup.KiteHandlerID(k.machines))
k.kite.HandleFunc("machine.ssh", machinegroup.KiteHandlerSSH(k.machines))
k.kite.HandleFunc("machine.mount.head", machinegroup.KiteHandlerHeadMount(k.machines))

// Machine index handlers.
k.handleWithSub("machine.index.head", index.KiteHandlerHead())
Expand Down
10 changes: 8 additions & 2 deletions go/src/koding/klient/machine/client/clienttest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os/user"
"path/filepath"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -111,12 +112,17 @@ func (c *Client) SSHAddKeys(_ string, _ ...string) error {

// MountHeadIndex gets basic info about the index generated from local path.
func (c *Client) MountHeadIndex(path string) (string, int, int64, error) {
idx, err := c.MountGetIndex(path)
absPath, err := filepath.Abs(path)
if err != nil {
return "", 0, 0, err
}

return path, idx.Count(-1), idx.DiskSize(-1), nil
idx, err := c.MountGetIndex(absPath)
if err != nil {
return "", 0, 0, err
}

return absPath, idx.Count(-1), idx.DiskSize(-1), nil
}

// MountGetIndex creates an index from provided local path. Generated index is
Expand Down
25 changes: 25 additions & 0 deletions go/src/koding/klient/machine/machinegroup/kite.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,28 @@ func KiteHandlerSSH(g *Group) kite.HandlerFunc {
return res, nil
}
}

// KiteHandlerHeadMount creates a kite handler function that, when called,
// invokes machine group HeadMount method.
func KiteHandlerHeadMount(g *Group) kite.HandlerFunc {
return func(r *kite.Request) (interface{}, error) {
req := &HeadMountRequest{}

if r.Args != nil {
if err := r.Args.One().Unmarshal(req); err != nil {
return nil, err
}
}

res, err := g.HeadMount(req)
if err != nil {
// TODO(ppknap): create errors file similar to kloud/stack/errors.
return nil, &kite.Error{
Type: "machinesError",
Message: err.Error(),
}
}

return res, nil
}
}
92 changes: 92 additions & 0 deletions go/src/koding/klient/machine/machinegroup/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package machinegroup

import (
"errors"
"fmt"

"koding/klient/machine"
"koding/klient/machine/mount"
)

// HeadMountRequest defines machine group head mount request.
type HeadMountRequest struct {
// ID is a unique identifier for the remote machine.
ID machine.ID `json:"id"`

// Mount describes the mount to be headed.
Mount mount.Mount `json:"mount"`
}

// HeadMountResponse defines machine group head mount response.
type HeadMountResponse struct {
// ExistMountID is not empty when mount to a given remote folder already exists.
ExistMountID mount.ID `json:"existMountID,omitempty"`

// AbsRemotePath stores absolute representation of remote path.
AbsRemotePath string `json:"absRemotePath"`

// AllCount stores the number of all files handled by mount.
AllCount int `json:"allCount"`

// AllDiskSize stores the size of all files handled by mount.
AllDiskSize int64 `json:"allDiskSize"`
}

// HeadMount retrieves information about existing mount or prepares remote
// machine for mounting. It can tell in advance if remote directory exists and
// if it is possible to mount it. This function does not create any mount data.
func (g *Group) HeadMount(req *HeadMountRequest) (*HeadMountResponse, error) {
if req == nil {
return nil, errors.New("invalid nil request")
}

// Check if local path is not already mounted.
switch mountID, err := g.mount.Path(req.Mount.Path); err {
case nil:
return nil, fmt.Errorf("path %q is already used by mount: %s", req.Mount.Path, mountID)
case mount.ErrMountNotFound: // valid.
default:
return nil, err
}

c, err := g.client.Client(req.ID)
if err != nil {
return nil, err
}

absRemotePath, count, diskSize, err := c.MountHeadIndex(req.Mount.RemotePath)
if err != nil {
return nil, err
}

res := &HeadMountResponse{
ExistMountID: "",
AbsRemotePath: absRemotePath,
AllCount: count,
AllDiskSize: diskSize,
}

// Check if remote folder of provided machine is already mounted.
mountIDs, err := g.mount.RemotePath(absRemotePath)
if err != nil {
if err != mount.ErrMountNotFound {
g.log.Warning("Cannot obtain list of mounts to %s remote directory: %s", absRemotePath, err)
}
return res, nil
}

for _, mountID := range mountIDs {
id, err := g.mount.MachineID(mountID)
if err != nil {
continue
}

if id == req.ID {
res.ExistMountID = mountID
g.log.Warning("Remote machine %s mount to %s already exist: %s", id, absRemotePath, mountID)
break
}
}

return res, nil
}
73 changes: 73 additions & 0 deletions go/src/koding/klient/machine/machinegroup/mount_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package machinegroup

import (
"testing"
"time"

"koding/klient/machine"
"koding/klient/machine/client/clienttest"
"koding/klient/machine/index"
"koding/klient/machine/mount/mounttest"
)

func TestHeadMount(t *testing.T) {
var (
builder = clienttest.NewBuilder(nil)
id = machine.ID("serv")
)

wd, m, clean, err := mounttest.MountDirs("")
if err != nil {
t.Fatalf("want err = nil; got %v", err)
}
defer clean()

g, err := New(testOptions(wd, builder))
if err != nil {
t.Fatalf("want err = nil; got %v", err)
}
defer g.Close()

// Add connected remote machine.
createReq := &CreateRequest{
Addresses: map[machine.ID][]machine.Addr{
id: {clienttest.TurnOnAddr()},
},
}
if _, err := g.Create(createReq); err != nil {
t.Fatalf("want err = nil; got %v", err)
}
if err := builder.WaitForBuild(time.Second); err != nil {
t.Fatalf("want err = nil; got %v", err)
}

// Head testing mount.
headMountReq := &HeadMountRequest{
ID: id,
Mount: m,
}
headMountRes, err := g.HeadMount(headMountReq)
if err != nil {
t.Fatalf("want err = nil; got %v", err)
}

if headMountRes.ExistMountID != "" {
t.Errorf("want mount does not exist; got: %s", headMountRes.ExistMountID)
}
if headMountRes.AbsRemotePath != m.RemotePath {
t.Errorf("want remote path = %s; got %s", m.RemotePath, headMountRes.AbsRemotePath)
}

// Compare indexes.
idx, err := index.NewIndexFiles(m.RemotePath)
if err != nil {
t.Fatalf("want err = nil; got %v", err)
}

if resc, idxc := headMountRes.AllCount, idx.Count(-1); resc != idxc {
t.Errorf("want file count = %d; got %d", idxc, resc)
}
if resds, idxds := headMountRes.AllDiskSize, idx.DiskSize(-1); resds != idxds {
t.Errorf("want disk size = %d; got %d", idxds, resds)
}
}