Merge lp:~mathieu-lonjaret/pyjuju/go-ssh into lp:pyjuju/go

Proposed by mpl
Status: Work in progress
Proposed branch: lp:~mathieu-lonjaret/pyjuju/go-ssh
Merge into: lp:pyjuju/go
Diff against target: 100 lines (+96/-0)
1 file modified
state/connect.go (+96/-0)
To merge this branch: bzr merge lp:~mathieu-lonjaret/pyjuju/go-ssh
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+91301@code.launchpad.net

Description of the change

ssh integration with state info

https://codereview.appspot.com/5620051/

To post a comment you must log in.
lp:~mathieu-lonjaret/pyjuju/go-ssh updated
50. By Gustavo Niemeyer

store: simplify storage of CharmEvent

This will also introduce bson marshaling of charm.URL and
port the store code to mgo tip (time.Time must be used
instead of bson.Timestamp).

R=fwereade
CC=
https://codereview.appspot.com/5606053

51. By Roger Peppe

environs/ec2: add code to create juju-specific cloudinit data.

When we create another provider, this code will be factored out,
but for the time being we'll keep it local to environs/ec2.

Also add a function to get ssh authorized keys.

R=fwereade, niemeyer
CC=
https://codereview.appspot.com/5610050

52. By mpl

ssh integration in state

Revision history for this message
mpl (mathieu-lonjaret) wrote :

Reviewers: mp+91301_code.launchpad.net,

Message:
Please take a look.

Description:

https://code.launchpad.net/~mathieu-lonjaret/juju/go-ssh/+merge/91301

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/5620051/

Affected files:
   A state/connect.go

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (3.7 KiB)

thanks for doing this.
needs a bit of work but not too much!

most importantly it needs some tests.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go
File state/connect.go (right):

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode13
state/connect.go:13: const proxy = "localhost:7890"
this can go (see below)

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode22
state/connect.go:22: SSHPassword string // we could probably stuff all
this in
rather than having a sentence going down that right hand comment, i
think an actual comment would be better.
// We only implement SSH user/password authentication for now.
// We could put these fields in an SSH-specific struct type.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode24
state/connect.go:24: // add whatever else is necessary for making the
connection.
delete this comment

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode28
state/connect.go:28: type myClientPassword struct {
i'm not keen on "my" - i don't think it adds useful info.

i think this might be better:

// clientPassword implements the ssh ClientPassword interface
// for a single SSH user.
type clientPassword struct {

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode40
state/connect.go:40: // takes the addr of the zk server to reach, and
returns a
s/takes/initTunnel takes/

although i'm not sure initTunnel is a great name.
how about dialSSH ?

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode53
state/connect.go:53: conn, err := client.Dial("tcp", addr)
return client.Dial("tcp", addr)

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode65
state/connect.go:65: func (info *Info) forwarder(c chan int) {
i think the caller should be able to see the error when the dial of the
ssh address fails.

i think it's a mistake that the proxy is on a fixed port - that means
that we can't have two of these going on at the same time.

how about something like this? you'd call it every time you wanted to
make a new connection. it would be possible to see if there's an
existing proxy for a given address and reuse if so, but i don't think
it's worth it for the time being.

(warning: i haven't even compiled the code below)

// newProxy returns a local proxy for the given address.
// It dials info.SSHAddr, arranges an ssh port forward
// from there to addr, listens on the proxy address
// and copies data between it and the ssh connection.
func (info *Info) newProxy(addr string) (proxy string, err error) {
     l, err := net.Listen("tcp", "localhost:0")
     if err != nil {
         return "", err
     }
     proxyAddr := l.Addr().String()

     zkServer, err := info.initTunnel(addr)
     if err != nil {
         return "", err
     }

     go func() {
        defer l.Close()
        defer zkServer.Close()

        zkClient, err := l.Accept()
        if err != nil {
            log.Printf("error accepting connection on %v: %v", l.Addr(),
err)
            return
        }
        defer zkClient.Close()
        done := make(chan bool)
        go forwarder(zkClient, zkServer, don...

Read more...

Revision history for this message
mpl (mathieu-lonjaret) wrote :
lp:~mathieu-lonjaret/pyjuju/go-ssh updated
53. By mpl

addresses rog comments

Revision history for this message
mpl (mathieu-lonjaret) wrote :
Download full text (4.5 KiB)

Tests will come with the next commit.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go
File state/connect.go (right):

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode13
state/connect.go:13: const proxy = "localhost:7890"
On 2012/02/06 09:36:39, rog wrote:
> this can go (see below)

Done.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode22
state/connect.go:22: SSHPassword string // we could probably stuff all
this in
On 2012/02/06 09:36:39, rog wrote:
> rather than having a sentence going down that right hand comment, i
think an
> actual comment would be better.
> // We only implement SSH user/password authentication for now.
> // We could put these fields in an SSH-specific struct type.

Done.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode24
state/connect.go:24: // add whatever else is necessary for making the
connection.
On 2012/02/06 09:36:39, rog wrote:
> delete this comment

Done.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode28
state/connect.go:28: type myClientPassword struct {
On 2012/02/06 09:36:39, rog wrote:
> i'm not keen on "my" - i don't think it adds useful info.

> i think this might be better:

> // clientPassword implements the ssh ClientPassword interface
> // for a single SSH user.
> type clientPassword struct {

Done.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode40
state/connect.go:40: // takes the addr of the zk server to reach, and
returns a
On 2012/02/06 09:36:39, rog wrote:
> s/takes/initTunnel takes/

> although i'm not sure initTunnel is a great name.
> how about dialSSH ?

as agreed on irc, sshDial. Done.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode53
state/connect.go:53: conn, err := client.Dial("tcp", addr)
On 2012/02/06 09:36:39, rog wrote:
> return client.Dial("tcp", addr)

Done.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode65
state/connect.go:65: func (info *Info) forwarder(c chan int) {
On 2012/02/06 09:36:39, rog wrote:
> i think the caller should be able to see the error when the dial of
the ssh
> address fails.

> i think it's a mistake that the proxy is on a fixed port - that means
that we
> can't have two of these going on at the same time.

> how about something like this? you'd call it every time you wanted to
make a new
> connection. it would be possible to see if there's an existing proxy
for a given
> address and reuse if so, but i don't think it's worth it for the time
being.

> (warning: i haven't even compiled the code below)

> // newProxy returns a local proxy for the given address.
> // It dials info.SSHAddr, arranges an ssh port forward
> // from there to addr, listens on the proxy address
> // and copies data between it and the ssh connection.
> func (info *Info) newProxy(addr string) (proxy string, err error) {
> l, err := net.Listen("tcp", "localhost:0")
> if err != nil {
> return "", err
> }
> proxyAddr := l.Addr().String()

> zkServer, err := info.initTunnel(addr)
> if err != nil {
> return "", err
> }

> go func() {
> ...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (3.7 KiB)

https://codereview.appspot.com/5620051/diff/2001/state/connect.go
File state/connect.go (right):

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode65
state/connect.go:65: func (info *Info) forwarder(c chan int) {
On 2012/02/06 22:29:09, mpl wrote:
> On 2012/02/06 09:36:39, rog wrote:
> > i think the caller should be able to see the error when the dial of
the ssh
> > address fails.
> >
> > i think it's a mistake that the proxy is on a fixed port - that
means that we
> > can't have two of these going on at the same time.
> >
> > how about something like this? you'd call it every time you wanted
to make a
> new
> > connection. it would be possible to see if there's an existing proxy
for a
> given
> > address and reuse if so, but i don't think it's worth it for the
time being.
> >
> > (warning: i haven't even compiled the code below)
> >
> >
> > // newProxy returns a local proxy for the given address.
> > // It dials info.SSHAddr, arranges an ssh port forward
> > // from there to addr, listens on the proxy address
> > // and copies data between it and the ssh connection.
> > func (info *Info) newProxy(addr string) (proxy string, err error) {
> > l, err := net.Listen("tcp", "localhost:0")
> > if err != nil {
> > return "", err
> > }
> > proxyAddr := l.Addr().String()
> >
> > zkServer, err := info.initTunnel(addr)
> > if err != nil {
> > return "", err
> > }
> >
> > go func() {
> > defer l.Close()
> > defer zkServer.Close()
> >
> > zkClient, err := l.Accept()
> > if err != nil {
> > log.Printf("error accepting connection on %v: %v",
l.Addr(), err)
> > return
> > }
> > defer zkClient.Close()
> > done := make(chan bool)
> > go forwarder(zkClient, zkServer, done)
> > go forwarder(zkServer, zkClient, done)
> > <-done
> > <-done
> > }()
> > return proxyAddr, err
> > }
> >
> > func forwarder(w io.Writer, r io.Reader, done chan bool){
> > _, err := io.Copy(w, r)
> > if err != nil {
> > log.Printf("network I/O error: %v", err)
> > }
> > done <- true
> > }

> Ok. but it looks like the proxy is only used for one Dial, since
there's no
> looping around Accept? It's fine by me, but I'm just making sure
that's what we
> want.

hmm, thinking about this, perhaps we do want a loop, as the zk client
might dial its address multiple times.
that means we also need some way of telling the proxy to go away when
the State is closed. (perhaps closing the state should just close the
listener, which should cause Accept to fail)

> Also, I was using a chan to make sure that the proxy was as ready as
possible to
> be accepting before letting zookeeper.Dial happen. Why is it better to
drop it?

after the Listen has been done, the proxy is ready to accept.

https://codereview.appspot.com/5620051/diff/2001/state/connect.go#newcode107
state/connect.go:107: func (info *Info) Open() (*State, error) {
On 2012/02/07 08:23:44, TheMue wrote:
> I'm still not happy with the idea of "opening an info to get a state".
I still
> prefer the more functional approach of "opening a state to...

Read more...

lp:~mathieu-lonjaret/pyjuju/go-ssh updated
54. By mpl

wip on rog comments

55. By mpl

wip

56. By mpl

ssh tunnel with call to binary, first draft, untested

Revision history for this message
mpl (mathieu-lonjaret) wrote :

Unmerged revisions

56. By mpl

ssh tunnel with call to binary, first draft, untested

55. By mpl

wip

54. By mpl

wip on rog comments

53. By mpl

addresses rog comments

52. By mpl

ssh integration in state

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'state/connect.go'
2--- state/connect.go 1970-01-01 00:00:00 +0000
3+++ state/connect.go 2012-02-13 14:18:21 +0000
4@@ -0,0 +1,96 @@
5+package state
6+
7+import (
8+ "errors"
9+ "log"
10+ "math/rand"
11+ "os"
12+ "os/exec"
13+ "strconv"
14+ "strings"
15+
16+ "launchpad.net/gozk/zookeeper"
17+)
18+
19+// Info represents information about a juju
20+// state server or servers.
21+type Info struct {
22+ UseSSH bool
23+ Addrs []string // List of IP address:port
24+ SSHUsername string // We only implement SSH user/password authentication for now.
25+ SSHPassword string // We could put these fields in an SSH-specific struct type.
26+ SSHAddr string
27+ SSHPortsRange [2]int
28+ // all fields should be public.
29+}
30+
31+// TODO(mpl): add the key authentication
32+func (info *Info) forwardPort(addr string) (string, error) {
33+ localPort := ""
34+ parts := strings.Split(addr, ":")
35+ if len(parts) != 2 {
36+ return localPort, errors.New("Wrong address format; was expecting \"host:port\"")
37+ }
38+ remoteHost := parts[0]
39+ remotePort := parts[1]
40+
41+ // get a random local port in the defined range
42+ if info.SSHPortsRange[1] < info.SSHPortsRange[0] {
43+ return localPort, errors.New("Invalid ports range")
44+ }
45+ randPort := rand.Intn(info.SSHPortsRange[1]-info.SSHPortsRange[0]) + info.SSHPortsRange[0]
46+ localPort = strconv.Itoa(randPort)
47+
48+ cmd, err := exec.LookPath("ssh")
49+ if err != nil {
50+ return localPort, err
51+ }
52+ args := []string{cmd,
53+ "-o", "PasswordAuthentication no",
54+ "-Llocalhost:" + localPort + ":localhost:" + remotePort,
55+ info.SSHUsername + "@" + remoteHost}
56+ fds := []*os.File{os.Stdin, os.Stdout, os.Stderr}
57+
58+ _, err = os.StartProcess(args[0], args, &os.ProcAttr{Dir: "/", Files: fds})
59+ if err != nil {
60+ return localPort, err
61+ }
62+ return localPort, nil
63+}
64+
65+// Open returns a new State representing the
66+// environment connected to using the given info.
67+func Open(info *Info) (*State, error) {
68+ var (
69+ zk *zookeeper.Conn
70+ session <-chan zookeeper.Event
71+ err error
72+ )
73+ if info.UseSSH {
74+ for _, addr := range info.Addrs {
75+ localPort, err := info.forwardPort(addr)
76+ if err != nil {
77+ log.Print(err)
78+ continue
79+ }
80+ proxyAddr := "localhost:" + localPort
81+ zk, session, err = zookeeper.Dial(proxyAddr, 5e9)
82+ if err != nil {
83+ log.Print(err)
84+ continue
85+ }
86+ break
87+ }
88+ } else {
89+ zk, session, err = zookeeper.Dial(strings.Join(info.Addrs, ","), 5e9)
90+ if err != nil {
91+ return nil, err
92+ }
93+ }
94+ // Wait for connection.
95+ event := <-session
96+ if event.State != zookeeper.STATE_CONNECTED {
97+ return nil, errors.New("Could not connect to zookeeper")
98+ }
99+ return &State{zk}, nil
100+}

Subscribers

People subscribed via source and target branches

to all changes: