-
Notifications
You must be signed in to change notification settings - Fork 0
/
daemon.go
399 lines (349 loc) · 10.2 KB
/
daemon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
package flex
import (
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"gopkg.in/lxc/go-lxc.v2"
"gopkg.in/tomb.v2"
"github.com/kr/pty"
)
// A Daemon can respond to requests from a flex client.
type Daemon struct {
tomb tomb.Tomb
config Config
unixl net.Listener
tcpl net.Listener
id_map *idmap
lxcpath string
mux *http.ServeMux
}
// varPath returns the provided path elements joined by a slash and
// appended to the end of $FLEX_DIR, which defaults to /var/lib/flex.
func varPath(path ...string) string {
varDir := os.Getenv("FLEX_DIR")
if varDir == "" {
varDir = "/var/lib/flex"
}
items := []string{varDir}
items = append(items, path...)
return filepath.Join(items...)
}
// StartDaemon starts the flex daemon with the provided configuration.
func StartDaemon(config *Config) (*Daemon, error) {
d := &Daemon{config: *config}
d.mux = http.NewServeMux()
d.mux.HandleFunc("/ping", d.servePing)
d.mux.HandleFunc("/list", d.serveList)
d.mux.HandleFunc("/create", d.serveCreate)
d.mux.HandleFunc("/attach", d.serveAttach)
var err error
d.id_map, err = newIdmap()
if err != nil {
return nil, err
}
Debugf("idmap is %d %d %d %d\n",
d.id_map.uidmin,
d.id_map.uidrange,
d.id_map.gidmin,
d.id_map.gidrange)
d.mux.HandleFunc("/start", buildByNameServe("start", func(c *lxc.Container) error { return c.Start() }, d))
d.mux.HandleFunc("/stop", buildByNameServe("stop", func(c *lxc.Container) error { return c.Stop() }, d))
d.mux.HandleFunc("/reboot", buildByNameServe("reboot", func(c *lxc.Container) error { return c.Reboot() }, d))
d.mux.HandleFunc("/destroy", buildByNameServe("destroy", func(c *lxc.Container) error { return c.Destroy() }, d))
d.lxcpath = varPath("lxc")
err = os.MkdirAll(varPath("/"), 0755)
if err != nil {
return nil, err
}
err = os.MkdirAll(d.lxcpath, 0755)
if err != nil {
return nil, err
}
unixAddr, err := net.ResolveUnixAddr("unix", varPath("unix.socket"))
if err != nil {
return nil, fmt.Errorf("cannot resolve unix socket address: %v", err)
}
unixl, err := net.ListenUnix("unix", unixAddr)
if err != nil {
return nil, fmt.Errorf("cannot listen on unix socket: %v", err)
}
d.unixl = unixl
if d.config.ListenAddr != "" {
// Watch out. Threre's a listener active which must be closed on errors.
tcpAddr, err := net.ResolveTCPAddr("tcp", d.config.ListenAddr)
if err != nil {
d.unixl.Close()
return nil, fmt.Errorf("cannot resolve unix socket address: %v", err)
}
tcpl, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
d.unixl.Close()
return nil, fmt.Errorf("cannot listen on unix socket: %v", err)
}
d.tcpl = tcpl
d.tomb.Go(func() error { return http.Serve(d.tcpl, d.mux) })
}
d.tomb.Go(func() error { return http.Serve(d.unixl, d.mux) })
return d, nil
}
var errStop = fmt.Errorf("requested stop")
// Stop stops the flex daemon.
func (d *Daemon) Stop() error {
d.tomb.Kill(errStop)
d.unixl.Close()
if d.tcpl != nil {
d.tcpl.Close()
}
err := d.tomb.Wait()
if err == errStop {
return nil
}
return err
}
// None of the daemon methods should print anything to stdout or stderr. If
// there's a local issue in the daemon that the admin should know about, it
// should be logged using either Logf or Debugf.
//
// Then, all of those issues that prevent the request from being served properly
// for any reason (bad parameters or any other local error) should be notified
// back to the client by writing an error json document to w, which in turn will
// be read by the client and returned via the API as an error result. These
// errors then surface via the CLI (cmd/flex/*) in os.Stderr.
//
// Together, these ideas ensure that we have a proper daemon, and a proper client,
// which can both be used independently and also embedded into other applications.
func (d *Daemon) servePing(w http.ResponseWriter, r *http.Request) {
remoteAddr := r.RemoteAddr
if remoteAddr == "@" {
remoteAddr = "unix socket"
}
Debugf("responding to ping from %s", remoteAddr)
w.Write([]byte("pong"))
}
// FIXME(niemeyer): These methods should be returning json to the client.
// They may be easily converted by replacing:
//
// fmt.Fprintf(w, "Port: %d", port)
//
// with:
//
// type jmap map[string]interface{}
// err := json.NewEncoder(w).Encode(jmap{"port": port})
//
// Common results may also be done with a struct. For example, for errors
// something like this might be convenient:
//
// type jerror struct {
// Error string `json:"error"`
// }
//
// It may then be used as:
//
// err := json.NewEncoder(w).Encode(jerror{"message"})
//
// I suggest establishing a few strong conventions early on for how an error
// document looks like, etc.
func (d *Daemon) serveList(w http.ResponseWriter, r *http.Request) {
Debugf("responding to list")
c := lxc.DefinedContainers(d.lxcpath)
for i := range c {
fmt.Fprintf(w, "%d: %s (%s)\n", i, c[i].Name(), c[i].State())
}
}
func (d *Daemon) serveAttach(w http.ResponseWriter, r *http.Request) {
Debugf("responding to attach")
name := r.FormValue("name")
if name == "" {
fmt.Fprintf(w, "failed parsing name")
return
}
command := r.FormValue("command")
if command == "" {
fmt.Fprintf(w, "failed parsing command")
return
}
secret := r.FormValue("secret")
if secret == "" {
fmt.Fprintf(w, "failed parsing secret")
return
}
addr := ":0"
// tcp6 doesn't seem to work with Dial("tcp", ) at the client
l, err := net.Listen("tcp4", addr)
if err != nil {
fmt.Fprintf(w, "failed listening")
return
}
fmt.Fprintf(w, "%s", l.Addr().String())
go func(l net.Listener, name string, command string, secret string) {
conn, err := l.Accept()
l.Close()
if err != nil {
Debugf(err.Error())
return
}
defer conn.Close()
// FIXME(niemeyer): This likely works okay because the kernel tends to
// be sane enough to not break down such a small amount of data into
// multiple operations. That said, if we were to make it work
// independent of the good will of the kernel and network layers, we'd
// have to take into account that Read might also return a single byte,
// for example, and then return more when it was next called. Or, it
// might return a password plus more data that the client delivered
// anticipating it would have a successful authentication.
//
// We could easily handle it using buffered io (bufio package), but that
// would spoil the use of conn directly below when binding it to
// the pty. So, given it's a trivial amount of data, I suggest calling
// a local helper function that will read byte by byte until it finds
// a predefined delimiter ('\n'?) and returns (data string, err error).
//
b := make([]byte, 100)
n, err := conn.Read(b)
if err != nil {
Debugf("bad read: %s", err.Error())
return
}
if n != len(secret) {
Debugf("read %d characters, secret is %d", n, len(secret))
return
}
if string(b[:n]) != secret {
Debugf("Wrong secret received from attach client")
return
}
Debugf("Attaching")
c, err := lxc.NewContainer(name, d.lxcpath)
if err != nil {
Debugf("%s", err.Error())
}
pty, tty, err := pty.Open()
if err != nil {
Debugf("Failed opening getting a tty: %q", err.Error())
return
}
defer pty.Close()
defer tty.Close()
/*
* The pty will be passed to the container's Attach. The two
* below goroutines will copy output from the socket to the
* pty.stdin, and from pty.std{out,err} to the socket
* If the RunCommand exits, we want ourselves (the gofunc) and
* the copy-goroutines to exit. If the connection closes, we
* also want to exit
*/
go func() {
io.Copy(pty, conn)
Debugf("conn->pty exiting")
return
}()
go func() {
io.Copy(conn, pty)
Debugf("pty->conn exiting")
return
}()
options := lxc.DefaultAttachOptions
options.StdinFd = tty.Fd()
options.StdoutFd = tty.Fd()
options.StderrFd = tty.Fd()
options.ClearEnv = true
_, err = c.RunCommand([]string{command}, options)
if err != nil {
return
}
Debugf("RunCommand exited, stopping console")
}(l, name, command, secret)
}
func (d *Daemon) serveCreate(w http.ResponseWriter, r *http.Request) {
Debugf("responding to create")
name := r.FormValue("name")
if name == "" {
fmt.Fprintf(w, "failed parsing name")
return
}
distro := r.FormValue("distro")
if distro == "" {
fmt.Fprintf(w, "failed parsing distro")
return
}
release := r.FormValue("release")
if release == "" {
fmt.Fprintf(w, "failed parsing release")
return
}
arch := r.FormValue("arch")
if arch == "" {
fmt.Fprintf(w, "failed parsing arch")
return
}
opts := lxc.TemplateOptions{
Template: "download",
Distro: distro,
Release: release,
Arch: arch,
}
c, err := lxc.NewContainer(name, d.lxcpath)
if err != nil {
return
}
/*
* Set the id mapping. This may not be how we want to do it, but it's a
* start. First, we remove any id_map lines in the config which might
* have come from ~/.config/lxc/default.conf. Then add id mapping based
* on Domain.id_map
*/
if d.id_map != nil {
Debugf("setting custom idmap")
err = c.SetConfigItem("lxc.id_map", "")
if err != nil {
fmt.Fprintf(w, "Failed to clear id mapping, continuing")
}
uidstr := fmt.Sprintf("u 0 %d %d\n", d.id_map.uidmin, d.id_map.uidrange)
Debugf("uidstr is %s\n", uidstr)
err = c.SetConfigItem("lxc.id_map", uidstr)
if err != nil {
fmt.Fprintf(w, "Failed to set uid mapping")
return
}
gidstr := fmt.Sprintf("g 0 %d %d\n", d.id_map.gidmin, d.id_map.gidrange)
err = c.SetConfigItem("lxc.id_map", gidstr)
if err != nil {
fmt.Fprintf(w, "Failed to set gid mapping")
return
}
c.SaveConfigFile("/tmp/c")
}
/*
* Actually create the container
*/
err = c.Create(opts)
if err != nil {
fmt.Fprintf(w, "fail!")
} else {
fmt.Fprintf(w, "success!")
}
}
type byname func(*lxc.Container) error
func buildByNameServe(function string, f byname, d *Daemon) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
Debugf("responding to %s", function)
name := r.FormValue("name")
if name == "" {
fmt.Fprintf(w, "failed parsing name")
return
}
c, err := lxc.NewContainer(name, d.lxcpath)
if err != nil {
fmt.Fprintf(w, "failed getting container")
return
}
err = f(c)
if err != nil {
fmt.Fprintf(w, "operation failed")
return
}
}
}