func TestMountCommandRemoteCache(t *testing.T) { Convey("Given a process error is returned", t, func() { fakeKlient := &testutil.FakeKlient{ ReturnRemoteCacheErr: util.KiteErrorf(kiteerrortypes.ProcessError, "Err msg"), } var b bytes.Buffer c := MountCommand{ Stdout: &b, Klient: fakeKlient, } Convey("It should print RemoteProcessFailed", func() { err := c.callRemoteCache(req.Cache{}, func(*dnode.Partial) {}) So(err, ShouldNotBeNil) So(b.String(), ShouldContainSubstring, "A requested process on the remote") }) }) Convey("Given a non-process error is returned", t, func() { fakeKlient := &testutil.FakeKlient{ ReturnRemoteCacheErr: errors.New("Err msg"), } var b bytes.Buffer c := MountCommand{ Stdout: &b, Klient: fakeKlient, } Convey("It should not print RemoteProcessFailed", func() { err := c.callRemoteCache(req.Cache{}, func(*dnode.Partial) {}) So(err, ShouldNotBeNil) So(b.String(), ShouldNotContainSubstring, "A requested process on the remote") }) }) }
// Dial dials the internal dialer. func (m *Machine) Dial() (err error) { // set the resulting dial based on the success of the Dial method. // Note that repeated calls to Dial creates a new XHR transport, so failing // dial on an existing sets a new local client transport session. // In otherwords, a failed dial will result in a not-connected sessuin. Due to // this, we track the state of the dialed by result, regardless of original state. defer func() { m.hasDialed = err == nil }() if m.Transport == nil { m.Log.Error("Dial was attempted with a nil Transport") return util.KiteErrorf( kiteerrortypes.MachineNotValidYet, "Machine.Transport is nil", ) } // Log the failure here, because this logger has machine context. if err := m.Transport.Dial(); err != nil { m.Log.Error("Dialer returned error. err:%s", err) return util.NewKiteError(kiteerrortypes.DialingFailed, err) } return nil }
// MachineStatus dials the given machine name, pings it, and returns ok or not. // Custom type errors for any problems encountered. func (s *Status) MachineStatus(name string) error { machine, err := s.MachineGetter.GetMachine(name) if err != nil { return err } // Try and ping it directly via http. This lets us know if the machine is // reachable, without dealing with any kite issues. if _, err := s.HTTPClient.Get(fmt.Sprintf("http://%s:56789/kite", machine.IP)); err != nil { return util.KiteErrorf( kiteerrortypes.MachineUnreachable, "Machine unreachable. host:%s, err:%s", machine.IP, err, ) } if err := machine.DialOnce(); err != nil { return s.handleKiteErr(err) } // We have to use klient.info here, because we're trying to catch the // TokenIsNotValidYet error. kite.ping does not use auth, and does not show // that error as a result. klient.info does use auth, and doesn't reset klient // usage. So it seems like an okay method for this. // // In the future, we should probably add auth'd ping to klient, to avoid side // effects. if _, err := machine.Tell("klient.info"); err != nil { return s.handleKiteErr(err) } return nil }
// CheckValid checks if this Machine is missing any required fields. Fields can be // missing because we store all machines, online or offline, but Kontrol doesn't // return any information about offline machines. We don't have Kites, for offline // machines. Because of this, a Machine may exist, but not be usable. // // Eg, you could attempt to mount an offline machine - if it hasn't connected // to kontrol since klient restarted, it won't be valid. // // This is a common check, and should be performed before using a machine. func (m *Machine) CheckValid() error { if m.Transport == nil { return util.KiteErrorf( kiteerrortypes.MachineNotValidYet, "Machine.Transport is nil", ) } if m.KiteTracker == nil { return util.KiteErrorf( kiteerrortypes.MachineNotValidYet, "Machine.KiteTracker is nil", ) } if m.Log == nil { return util.KiteErrorf(kiteerrortypes.MachineNotValidYet, "Machine.Log is nil.") } return nil }
// TellWithTimeout uses the Kite protocol (with a dnode response) to communicate // with this machine. func (m *Machine) TellWithTimeout(method string, timeout time.Duration, args ...interface{}) (*dnode.Partial, error) { if m.Transport == nil { m.Log.Error("TellWithTimeout was attempted with a nil Transport") return nil, util.KiteErrorf( kiteerrortypes.MachineNotValidYet, "Machine.Transport is nil", ) } if err := m.DialOnce(); err != nil { return nil, err } return m.Transport.TellWithTimeout(method, timeout, args...) }
// IsConfigured checks the Mounter fields to ensure (as best as it can) that // there are no missing required fields, such as mock fields and etc. func (m *Mounter) IsConfigured() error { if m.Options.Name == "" { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing Name") } if m.Options.LocalPath == "" { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing LocalPath") } if m.IP == "" { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing IP") } if m.Options.PrefetchAll && m.Options.CachePath == "" { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Using PrefetchAll but missing CachePath") } if m.Machine == nil { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing Machine") } if m.KiteTracker == nil { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing KiteTracker") } if m.Transport == nil { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing Transport") } if m.PathUnmounter == nil { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing Unmounter") } if m.Log == nil { return util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing Log") } return nil }
// Publish method takes arbitrary event data, and passes it to // any functions which have subscribed via `client.Subscribe`. The // only required value is a single `eventName` value. Only listeners // of the given eventName will be called back with the data. // // Examples: // // { // "eventName": "fullscreen" // } // // { // "eventName": "openFiles", // "files": ["file1.txt", "file2.txt"] // } // // The only response is an error, if any. func (c *PubSub) Publish(r *kite.Request) (interface{}, error) { // Parse the eventName from the incoming data. Note that this method // accepts any data beyond eventName, so that this method is as generic // as possible. var params PublishRequest if r.Args == nil { return nil, errors.New("client.Publish: Arguments are not passed") } // The raw response that we'll be publishing to the Client. We're // sending the Raw response because, as seen in the params struct, // we don't know the data format being passed to client.Publish. resp := r.Args.One() err := r.Args.One().Unmarshal(¶ms) if err != nil || params.EventName == "" { c.Log.Info("client.Publish: Unknown param format %q\n", resp) return nil, errors.New("client.Publish: eventName is required") } c.subMu.Lock() defer c.subMu.Unlock() subs, ok := c.Subscriptions[params.EventName] if !ok { return nil, util.KiteErrorf(kiteerrortypes.NoSubscribers, "client.Publish: No client.Subscribers found for %q", params.EventName) } // This condition should never occur - Subscription() should remove // all of the subs manually. If it doesn't, something wrong occured // during the removal attempt. if len(subs) == 0 { c.Log.Info("client.Publish: The event %q was found empty, when it should have been removed\n", params.EventName) return nil, fmt.Errorf("client.Publish: No client.Subscribers found for %q", params.EventName) } c.Log.Info("client.Publish: Publishing data for event %q\n", params.EventName) for _, sub := range subs { sub.Call(resp) } return nil, nil }
func (m *Mounter) Mount() (*Mount, error) { if err := m.IsConfigured(); err != nil { m.Log.Error("Mounter improperly configured. err:%s", err) return nil, err } // Mount() requires a MountAdder if m.MountAdder == nil { return nil, util.KiteErrorf(kiteerrortypes.MissingArgument, "Missing MountAdder") } var syncOpts rsync.SyncIntervalOpts if m.Intervaler != nil { syncOpts = m.Intervaler.SyncIntervalOpts() } else { m.Log.Warning("Unable to locate Intervaler") } mount := &Mount{ MountFolder: m.Options, MountName: m.Options.Name, IP: m.IP, SyncIntervalOpts: syncOpts, EventSub: m.EventSub, } if m.Options.OneWaySyncMount { mount.Type = SyncMount } else { mount.Type = FuseMount } mount.Log = MountLogger(mount, m.Log) if err := m.MountExisting(mount); err != nil { return nil, err } if err := m.MountAdder.AddMount(mount); err != nil { return nil, err } return mount, nil }
// // If left too large, the Kernel (in OSX, for example) will fail fs ops by printing // "Socket is not connected" to the user. So if that message is seen, this value // likely needs to be lowered. fuseTellTimeout = 55 * time.Second // Messages displayed to the user about the machines status. autoRemountFailed = "Error auto-mounting. Please unmount & mount again." autoRemounting = "Remounting after extended disconnect. Please wait..." ) var ( // ErrRemotePathDoesNotExist is returned when the remote path does not exist, // or is not a dir. ErrRemotePathDoesNotExist = util.KiteErrorf( kiteerrortypes.RemotePathDoesNotExist, "The RemotePath either does not exist, or is not a dir", ) ) // MounterTransport is the transport that the Mounter uses to communicate with // the remote machine. type MounterTransport interface { Tell(string, ...interface{}) (*dnode.Partial, error) TellWithTimeout(string, time.Duration, ...interface{}) (*dnode.Partial, error) } // Mounter is responsible for actually mounting fuse mounts from Klient. type Mounter struct { Log logging.Logger // The options for this Mounter, such as LocalFolder, etc.
func TestSSHCommand(t *testing.T) { Convey("", t, func() { tempSSHDir, err := ioutil.TempDir("", "") So(err, ShouldBeNil) defer os.Remove(tempSSHDir) teller := newFakeTransport() s := ssh.SSHCommand{ SSHKey: &ssh.SSHKey{ Log: testutil.DiscardLogger, KeyPath: tempSSHDir, KeyName: "key", // Create a klient, with the fake transport to satisfy the Teller interface. Klient: &klient.Klient{ Teller: teller, }, }, } Convey("Given PrepareForSSH is called", func() { Convey("When it returns a dialing error", func() { kiteErr := util.KiteErrorf( kiteerrortypes.DialingFailed, "Failed to dial.", ) teller.TripErrors["remote.sshKeysAdd"] = kiteErr teller.TripResponses["remote.currentUsername"] = &dnode.Partial{ Raw: []byte(`"foo"`), } Convey("It should return ErrRemoteDialingFailed", func() { So(s.PrepareForSSH("foo"), ShouldEqual, kiteErr) }) }) }) Convey("It should return public key path", func() { key := s.PublicKeyPath() So(key, ShouldEqual, path.Join(tempSSHDir, "key.pub")) }) Convey("It should return private key path", func() { key := s.PrivateKeyPath() So(key, ShouldEqual, path.Join(tempSSHDir, "key")) }) Convey("It should return error if invalid key exists", func() { err := ioutil.WriteFile(s.PrivateKeyPath(), []byte("a"), 0700) So(err, ShouldBeNil) err = ioutil.WriteFile(s.PublicKeyPath(), []byte("a"), 0700) So(err, ShouldBeNil) err = s.PrepareForSSH("name") So(err, ShouldNotBeNil) So(os.IsExist(err), ShouldBeFalse) }) Convey("It should create ssh folder if it doesn't exist", func() { teller.TripResponses["remote.currentUsername"] = &dnode.Partial{ Raw: []byte(`"foo"`), } So(s.PrepareForSSH("name"), ShouldBeNil) _, err := os.Stat(s.KeyPath) So(err, ShouldBeNil) }) Convey("It generates and saves key to remote if key doesn't exist", func() { teller.TripResponses["remote.currentUsername"] = &dnode.Partial{ Raw: []byte(`"foo"`), } err := s.PrepareForSSH("name") So(err, ShouldBeNil) firstContents, err := ioutil.ReadFile(s.PublicKeyPath()) So(err, ShouldBeNil) publicExists := s.PublicKeyExists() So(publicExists, ShouldBeTrue) privateExists := s.PrivateKeyExists() So(privateExists, ShouldBeTrue) Convey("It returns key if it exists", func() { err := s.PrepareForSSH("name") So(err, ShouldBeNil) secondContents, err := ioutil.ReadFile(s.PublicKeyPath()) So(err, ShouldBeNil) So(string(firstContents), ShouldEqual, string(secondContents)) }) }) }) }
import ( "errors" "fmt" "sync" "koding/klient/kiteerrortypes" "koding/klient/util" "github.com/koding/kite" "github.com/koding/kite/dnode" ) var ( // ErrSubNotFound is returned from Unsubscribe if the given sub id cannot be found. ErrSubNotFound = util.KiteErrorf( kiteerrortypes.SubNotFound, "The given subscription id cannot be found.", ) ) // SubscribeResponse is the response type of the `client.Subscribe` method. type SubscribeResponse struct { ID int `json:"id"` } // SubscribeRequest is the request type for the `client.Subscribe` method. type SubscribeRequest struct { EventName string `json:"eventName"` OnPublish dnode.Function `json:"onPublish"` } // UnsubscribeRequest is the request type for the `client.Unsubscribe` method.
// The machine is remounting // // TODO: Move this type to a mount specific status, once we support multiple // mounts. MachineRemounting ) const ( // The duration between IsConnected() checks performed by WaitUntilOnline() waitUntilOnlinePause = 5 * time.Second ) var ( // Returned by various methods if the requested machine cannot be found. ErrMachineNotFound error = util.KiteErrorf( kiteerrortypes.MachineNotFound, "Machine not found", ) // Returned by various methods if the requested action is locked. ErrMachineActionIsLocked error = util.KiteErrorf( kiteerrortypes.MachineActionIsLocked, "Machine action is locked", ) ) // Transport is a Kite compatible interface for Machines. type Transport interface { Dial() error Tell(string, ...interface{}) (*dnode.Partial, error) TellWithTimeout(string, time.Duration, ...interface{}) (*dnode.Partial, error) }
import ( "koding/fuseklient" "koding/klient/kiteerrortypes" "koding/klient/remote/kitepinger" "koding/klient/remote/req" "koding/klient/remote/rsync" "koding/klient/util" "github.com/koding/logging" ) var ( // Returned by various methods if the requested mount cannot be found. ErrMountNotFound error = util.KiteErrorf( kiteerrortypes.MountNotFound, "Mount not found", ) ) type MountType int const ( UnknownMount MountType = iota FuseMount SyncMount ) // Mount stores information about mounted folders, and is both with // to various Remote.* kite methods as well as being saved in // klient's storage. type Mount struct {
// The key used to store machines in the database, allowing machine / machinemeta // to persist between Klient restarts. machinesStorageKey = "machines" ) // 15 names to be used to identify machines. var sourceMachineNames = []string{ "apple", "orange", "banana", "grape", "coconut", "peach", "mango", "date", "kiwi", "lemon", "squash", "jackfruit", "raisin", "tomato", "quince", } var ( // ErrMachineAlreadyAdded is returned if the given machine instance is already // added to this Machines struct. ErrMachineAlreadyAdded = util.KiteErrorf( kiteerrortypes.MachineAlreadyAdded, "Machine already added.", ) // ErrMachineDuplicate is returned if the given machine has unique fields // matching the given machine, such as kite url or name, meaning that the // machines could get confused in future usage. ErrMachineDuplicate error = util.KiteErrorf( kiteerrortypes.MachineDuplicate, "Machine has unique fields that match another machine.", ) ) // Machines is responsible for storing and querying the *Machine(s) type Machines struct { Log logging.Logger // The internal list of mounts