Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote SSH connection support #1898

Merged
merged 10 commits into from
May 23, 2018
Merged

Remote SSH connection support #1898

merged 10 commits into from
May 23, 2018

Conversation

AWoloszyn
Copy link
Contributor

This is still somewhat in the experimental phase, it has no UI present yet, and can only be
enabled by command-line flags, but it adds functional remote SSH support.

This depends on both #1896 #1897 to be useful.

@@ -88,6 +90,10 @@ func withLibraryPlatformSuffix(lib string, os device.OSKind) string {
}
}

func GetLibraryName(lib LibraryType, abi *device.ABI) string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Doc strings everywhere

@@ -101,13 +105,31 @@ func run(ctx context.Context) error {
r.SetDeviceProperty(ctx, host, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr))
}

deviceScanDone, onDeviceScanDone := task.NewSignal()
numAsyncScans := 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More common to use a sync.WaitGroup:

wg := sync.WaitGroup{}
if *scanAndroidDevs {
	wg.Add(1)
	crash.Go(func() { monitorAndroidDevices(ctx, r, wg.Done) })
}

...

crash.Go(func(){
	wg.Wait()
	onDeviceScanDone(ctx)
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

func (l pkgLayout) Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) {
if hostOS(ctx) == abi.OS {
return l.root.Join(withExecutablePlatformSuffix("gapir", hostOS(ctx))), nil
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No else when the true branch always returns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

func (l pkgLayout) Library(ctx context.Context, lib LibraryType, abi *device.ABI) (file.Path, error) {
if hostOS(ctx) == abi.OS {
return l.root.Join("lib", withLibraryPlatformSuffix(libTypeToName[lib], hostOS(ctx))), nil
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

func (l pkgLayout) DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) {
if hostOS(ctx) == os {
return l.root.Join(withExecutablePlatformSuffix("device_info", os)), nil
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

func (l *ZipLayout) Gapir(ctx context.Context, abi *device.ABI) (*zip.File, error) {
if l.os == abi.OS {
return l.file(withExecutablePlatformSuffix("gapir", l.os))
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if err != nil {
return func() {}, err
return func(ctx context.Context) {}, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to call cleanup here and return nil, err.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
return setupJSON(lib, json, env)

err = setupJSON(ctx, lib, json, setup, tempdir, env)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I think it would be better to check err, and if not nil, call cleanup and return nil, err.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return setupJSON(lib, json, env)

err = setupJSON(ctx, lib, json, setup, tempdir, env)
return cleanup, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then this becomes return cleanup, nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

func findLibraryAndJSON(ctx context.Context, libType layout.LibraryType) (file.Path, file.Path, error) {
lib, err := layout.Library(ctx, libType)
func findLibraryAndJSON(ctx context.Context, rs DeviceReplaySetup, tempdir string, libType layout.LibraryType) (string, file.Path, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this is private, would be great to document the return values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

err = s.newHost(ctx, d, launchArgs)
} else if d, ok := d.(adb.Device); ok {
err = s.newADB(ctx, d, abi)
err = s.newHost(ctx, d, abi, launchArgs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, it's a shame with the device abstractions that host and remote can't share the same code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.
I think I might be able to clean this up a bit. I wanted to see how the gapit changes shook out before I tried unifying things like this. Since they will be similar in nature.

AWoloszyn added 5 commits May 23, 2018 09:48
This will print the device information on stdout, as either
json or a binary protobuf.
This allows an ssh configuration file to be created,
and used to detect a remote SSH device.
AWoloszyn added 2 commits May 23, 2018 11:06
We were writing both stdout and stderr to the same buffer
in a very thread-unsafe way.
Copy link
Contributor

@ben-clayton ben-clayton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few comments I've made that don't seem to have been addressed. Please can you mark those you've done with a comment so I know what's still being looked at vs got skipped?

@@ -142,6 +162,21 @@ func monitorAndroidDevices(ctx context.Context, r *bind.Registry, onDeviceScanDo
}
}

func getRemoteSSHDevices(ctx context.Context, r *bind.Registry, f io.Reader, scanDone func()) {
// Populate the registry with all the existing devices.
func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of this func?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function was originally longer, so it was for the defer(), but no longer needed.

@@ -88,6 +90,11 @@ func withLibraryPlatformSuffix(lib string, os device.OSKind) string {
}
}

// GetLibraryName returns the filename of the given Library.
func GetLibraryName(lib LibraryType, abi *device.ABI) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: drop Get unless it collides with something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// remoteProcess is the interface to a running process, as started by a Target.
type remoteProcess struct {
session *ssh.Session
wg sync.WaitGroup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gofmt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if err != nil {
return nil, err
}
go func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping

}
p := &remoteProcess{
session: session,
wg: sync.WaitGroup{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gofmt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if err != nil {
return
}
b.doTunnel(ctx, local, remotePort)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you be leaving this loop if doTunnel returns an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely yes thanks. If we can't connect once, its not likely the next attempt will work.

if err != nil {
return 0, err
}
crash.Go( func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to spin up (yet) another go-routine to monitor the cancellation of ctx.

crash.Go(func() {
	<-task.ShouldStop(ctx)
	listener.Close()
})

Alternatively:

type TimeoutErr interface { Timeout() bool }

for !task.Stopped(ctx) {
	listener.(*net.TCPListener).SetDeadline(time.Now().Add(time.Second))
	local, err := listener.Accept()
	if err != nil {
		if t, ok := err.(TimeoutErr); ok && t.Timeout() {
			continue
		}
		break // not a timeout
	}
	b.doTunnel(ctx, local, remotePort)
}

// UnmarshalJSON is used by json.Unmashall, this allows us to set
// up a default configuration, so that unknown parameters have
// sane defaults.
func (c *Configuration) UnmarshalJSON(data []byte) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my previous comments here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the reason is very much for default values.
I was hoping to make sane defaults, but it seemed less clean (to me at least) to have a bunch of

func (c Configuration) Port() int {
   if Port == 0 {
     return 22;
   }
}

For every parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, fair enough

WriteFile(ctx context.Context, contents io.Reader, mode string, destPath string) error
// ForwardPort forwards the remote port. It automatically selects an open
// local port.
ForwardPort(ctx context.Context, remoteport int) (int, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually a more convenient API than the Forward which was shared with ADB. I do wonder if we should have a common interface here (by adopting this new interface).

type deviceReplaySetup interface {
// MakeTempDir returns a path to a created temporary. The returned function
// can be called to clean up the temporary directory.
MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public methods in a private interface only really makes sense if the implementations are on public types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return
}
if err = b.doTunnel(ctx, local, remotePort); err != nil {
break
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Inconsistent mix of return and break

KnownHosts string
}

// UnmarshalJSON is used by json.Unmashall, this allows us to set
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Unmarshal

// ReadConfiguration reads a set of configurations from then
// given reader, and returns the configurations to the user.
func ReadConfiguration(r io.Reader) ([]Configuration, error) {
cfg := []Configuration{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cfgs := []Configuration{}
d := json.Decoder(r)
for d.More() {
	cfg := {
		User:       u.Username,
		Port:       22,
		Keyfile:    u.HomeDir + "/.ssh/id_rsa",
		KnownHosts: u.HomeDir + "/.ssh/known_hosts",
	}
	if err := d.Decode(&cfg); err != nil {
		return nil, err
	}
	cfgs = append(cfgs, cfg)
}
return cfgs, nil

Copy link
Contributor

@ben-clayton ben-clayton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thank you for all the changes!

@AWoloszyn
Copy link
Contributor Author

Thanks for the careful reviews!

@AWoloszyn AWoloszyn merged commit db72e9f into google:master May 23, 2018
@AWoloszyn AWoloszyn deleted the remote-ssh branch May 23, 2018 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants