func eventsSocket(r *http.Request, w http.ResponseWriter) error { listener := eventListener{} typeStr := r.FormValue("type") if typeStr == "" { typeStr = "logging,operation" } c, err := shared.WebsocketUpgrader.Upgrade(w, r, nil) if err != nil { return err } listener.active = make(chan bool, 1) listener.connection = c listener.id = uuid.NewRandom().String() listener.messageTypes = strings.Split(typeStr, ",") eventsLock.Lock() eventListeners[listener.id] = &listener eventsLock.Unlock() shared.LogDebugf("New events listener: %s", listener.id) <-listener.active eventsLock.Lock() delete(eventListeners, listener.id) eventsLock.Unlock() listener.connection.Close() shared.LogDebugf("Disconnected events listener: %s", listener.id) return nil }
func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) { unixConn := conn.(*net.UnixConn) switch state { case http.StateNew: cred, err := getCred(unixConn) if err != nil { shared.LogDebugf("Error getting ucred for conn %s", err) } else { m.m[unixConn] = cred } case http.StateActive: return case http.StateIdle: return case http.StateHijacked: /* * The "Hijacked" state indicates that the connection has been * taken over from net/http. This is useful for things like * developing websocket libraries, who want to upgrade the * connection to a websocket one, and not use net/http any * more. Whatever the case, we want to forget about it since we * won't see it either. */ delete(m.m, unixConn) case http.StateClosed: delete(m.m, unixConn) default: shared.LogDebugf("Unknown state for connection %s", state) } }
func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error, error) { if op.class != operationClassWebsocket { return nil, fmt.Errorf("Only websocket operations can be connected") } if op.status != shared.Running { return nil, fmt.Errorf("Only running operations can be connected") } chanConnect := make(chan error, 1) op.lock.Lock() go func(op *operation, chanConnect chan error) { err := op.onConnect(op, r, w) if err != nil { chanConnect <- err shared.LogDebugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err) return } chanConnect <- nil shared.LogDebugf("Handled %s operation: %s", op.class.String(), op.id) }(op, chanConnect) op.lock.Unlock() shared.LogDebugf("Connected %s operation: %s", op.class.String(), op.id) return chanConnect, nil }
// RsyncSend sets up the sending half of an rsync, to recursively send the // directory pointed to by path over the websocket. func RsyncSend(path string, conn *websocket.Conn) error { cmd, dataSocket, stderr, err := rsyncSendSetup(path) if dataSocket != nil { defer dataSocket.Close() } if err != nil { return err } readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, dataSocket) output, err := ioutil.ReadAll(stderr) if err != nil { shared.LogDebugf("problem reading rsync stderr %s", err) } err = cmd.Wait() if err != nil { shared.LogDebugf("problem with rsync send of %s: %s: %s", path, err, string(output)) } <-readDone <-writeDone return err }
func containersPost(d *Daemon, r *http.Request) Response { shared.LogDebugf("Responding to container create") req := containerPostReq{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } if req.Name == "" { cs, err := dbContainersList(d.db, cTypeRegular) if err != nil { return InternalError(err) } i := 0 for { i++ req.Name = strings.ToLower(petname.Generate(2, "-")) if !shared.StringInSlice(req.Name, cs) { break } if i > 100 { return InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries")) } } shared.LogDebugf("No name provided, creating %s", req.Name) } if req.Devices == nil { req.Devices = shared.Devices{} } if req.Config == nil { req.Config = map[string]string{} } if strings.Contains(req.Name, shared.SnapshotDelimiter) { return BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter)) } switch req.Source.Type { case "image": return createFromImage(d, &req) case "none": return createFromNone(d, &req) case "migration": return createFromMigration(d, &req) case "copy": return createFromCopy(d, &req) default: return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type)) } }
func deviceEventListener(d *Daemon) { chNetlinkCPU, chNetlinkNetwork, chUSB, err := deviceNetlinkListener() if err != nil { shared.LogErrorf("scheduler: couldn't setup netlink listener") return } for { select { case e := <-chNetlinkCPU: if len(e) != 2 { shared.LogErrorf("Scheduler: received an invalid cpu hotplug event") continue } if !cgCpusetController { continue } shared.LogDebugf("Scheduler: cpu: %s is now %s: re-balancing", e[0], e[1]) deviceTaskBalance(d) case e := <-chNetlinkNetwork: if len(e) != 2 { shared.LogErrorf("Scheduler: received an invalid network hotplug event") continue } if !cgNetPrioController { continue } shared.LogDebugf("Scheduler: network: %s has been added: updating network priorities", e[0]) deviceNetworkPriority(d, e[0]) networkAutoAttach(d, e[0]) case e := <-chUSB: deviceUSBEvent(d, e) case e := <-deviceSchedRebalance: if len(e) != 3 { shared.LogErrorf("Scheduler: received an invalid rebalance event") continue } if !cgCpusetController { continue } shared.LogDebugf("Scheduler: %s %s %s: re-balancing", e[0], e[1], e[2]) deviceTaskBalance(d) } } }
// RsyncRecv sets up the receiving half of the websocket to rsync (the other // half set up by RsyncSend), putting the contents in the directory specified // by path. func RsyncRecv(path string, conn *websocket.Conn, writeWrapper func(io.WriteCloser) io.WriteCloser) error { cmd := exec.Command("rsync", "--server", "-vlogDtpre.iLsfx", "--numeric-ids", "--devices", "--partial", ".", path) stdin, err := cmd.StdinPipe() if err != nil { return err } stdout, err := cmd.StdoutPipe() if err != nil { return err } stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } writePipe := io.WriteCloser(stdin) if writeWrapper != nil { writePipe = writeWrapper(stdin) } readDone, writeDone := shared.WebsocketMirror(conn, writePipe, stdout) data, err2 := ioutil.ReadAll(stderr) if err2 != nil { shared.LogDebugf("error reading rsync stderr: %s", err2) return err2 } err = cmd.Wait() if err != nil { shared.LogDebugf("rsync recv error for path %s: %s: %s", path, err, string(data)) } <-readDone <-writeDone return err }
func (op *operation) Run() (chan error, error) { if op.status != shared.Pending { return nil, fmt.Errorf("Only pending operations can be started") } chanRun := make(chan error, 1) op.lock.Lock() op.status = shared.Running if op.onRun != nil { go func(op *operation, chanRun chan error) { err := op.onRun(op) if err != nil { op.lock.Lock() op.status = shared.Failure op.err = SmartError(err).String() op.lock.Unlock() op.done() chanRun <- err shared.LogDebugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err) _, md, _ := op.Render() eventSend("operation", md) return } op.lock.Lock() op.status = shared.Success op.lock.Unlock() op.done() chanRun <- nil op.lock.Lock() shared.LogDebugf("Success for %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) op.lock.Unlock() }(op, chanRun) } op.lock.Unlock() shared.LogDebugf("Started %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) return chanRun, nil }
func (c *execCmd) sendTermSize(control *websocket.Conn) error { width, height, err := termios.GetSize(int(syscall.Stdout)) if err != nil { return err } shared.LogDebugf("Window size is now: %dx%d", width, height) w, err := control.NextWriter(websocket.TextMessage) if err != nil { return err } msg := shared.ContainerExecControl{} msg.Command = "window-resize" msg.Args = make(map[string]string) msg.Args["width"] = strconv.Itoa(width) msg.Args["height"] = strconv.Itoa(height) buf, err := json.Marshal(msg) if err != nil { return err } _, err = w.Write(buf) w.Close() return err }
/* * filesystemDetect returns the filesystem on which * the passed-in path sits */ func filesystemDetect(path string) (string, error) { fs := syscall.Statfs_t{} err := syscall.Statfs(path, &fs) if err != nil { return "", err } switch fs.Type { case filesystemSuperMagicBtrfs: return "btrfs", nil case filesystemSuperMagicZfs: return "zfs", nil case filesystemSuperMagicTmpfs: return "tmpfs", nil case filesystemSuperMagicExt4: return "ext4", nil case filesystemSuperMagicXfs: return "xfs", nil case filesystemSuperMagicNfs: return "nfs", nil default: shared.LogDebugf("Unknown backing filesystem type: 0x%x", fs.Type) return string(fs.Type), nil } }
func (op *operation) UpdateMetadata(opMetadata interface{}) error { if op.status != shared.Pending && op.status != shared.Running { return fmt.Errorf("Only pending or running operations can be updated") } if op.readonly { return fmt.Errorf("Read-only operations can't be updated") } newMetadata, err := shared.ParseMetadata(opMetadata) if err != nil { return err } op.lock.Lock() op.updatedAt = time.Now() op.metadata = newMetadata op.lock.Unlock() shared.LogDebugf("Updated metadata for %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) return nil }
func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) { for i := 0; i < 100; i++ { result, err := db.Exec(q, args...) if err == nil { return result, nil } if !isDbLockedError(err) { shared.LogDebugf("DbExec: query %q error %q", q, err) return nil, err } time.Sleep(100 * time.Millisecond) } shared.LogDebugf("DbExec: query %q args %q, DB still locked", q, args) shared.PrintStack() return nil, fmt.Errorf("DB is locked") }
/* * . q is the database query * . inargs is an array of interfaces containing the query arguments * . outfmt is an array of interfaces containing the right types of output * arguments, i.e. * var arg1 string * var arg2 int * outfmt := {}interface{}{arg1, arg2} * * The result will be an array (one per output row) of arrays (one per output argument) * of interfaces, containing pointers to the actual output arguments. */ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{}) ([][]interface{}, error) { for i := 0; i < 100; i++ { result, err := doDbQueryScan(db, q, inargs, outfmt) if err == nil { return result, nil } if !isDbLockedError(err) { shared.LogDebugf("DbQuery: query %q error %q", q, err) return nil, err } time.Sleep(100 * time.Millisecond) } shared.LogDebugf("DbQueryscan: query %q inargs %q, DB still locked", q, inargs) shared.PrintStack() return nil, fmt.Errorf("DB is locked") }
func txCommit(tx *sql.Tx) error { for i := 0; i < 100; i++ { err := tx.Commit() if err == nil { return nil } if !isDbLockedError(err) { shared.LogDebugf("Txcommit: error %q", err) return err } time.Sleep(100 * time.Millisecond) } shared.LogDebugf("Txcommit: db still locked") shared.PrintStack() return fmt.Errorf("DB is locked") }
func dbBegin(db *sql.DB) (*sql.Tx, error) { for i := 0; i < 100; i++ { tx, err := db.Begin() if err == nil { return tx, nil } if !isDbLockedError(err) { shared.LogDebugf("DbBegin: error %q", err) return nil, err } time.Sleep(100 * time.Millisecond) } shared.LogDebugf("DbBegin: DB still locked") shared.PrintStack() return nil, fmt.Errorf("DB is locked") }
func memProfiler(memProfile string) { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGUSR1) for { sig := <-ch shared.LogDebugf("Received '%s signal', dumping memory.", sig) doMemDump(memProfile) } }
func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) { // TODO: figure out what the equivalent of signal.SIGWINCH is on // windows and use that; for now if you resize your terminal it just // won't work quite correctly. err := c.sendTermSize(control) if err != nil { shared.LogDebugf("error setting term size %s", err) } }
func doMemDump(memProfile string) { f, err := os.Create(memProfile) if err != nil { shared.LogDebugf("Error opening memory profile file '%s': %s", err) return } pprof.WriteHeapProfile(f) f.Close() }
func doDeleteImage(d *Daemon, fingerprint string) error { id, imgInfo, err := dbImageGet(d.db, fingerprint, false, false) if err != nil { return err } // get storage before deleting images/$fp because we need to // look at the path s, err := storageForImage(d, imgInfo) if err != nil { shared.LogError("error detecting image storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err}) } else { // Remove the image from storage backend if err = s.ImageDelete(imgInfo.Fingerprint); err != nil { shared.LogError("error deleting the image from storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err}) } } // Remove main image file fname := shared.VarPath("images", imgInfo.Fingerprint) if shared.PathExists(fname) { err = os.Remove(fname) if err != nil { shared.LogDebugf("Error deleting image file %s: %s", fname, err) } } // Remove the rootfs file fname = shared.VarPath("images", imgInfo.Fingerprint) + ".rootfs" if shared.PathExists(fname) { err = os.Remove(fname) if err != nil { shared.LogDebugf("Error deleting image file %s: %s", fname, err) } } // Remove the DB entry if err = dbImageDelete(d.db, id); err != nil { return err } return nil }
func containersGet(d *Daemon, r *http.Request) Response { for i := 0; i < 100; i++ { result, err := doContainersGet(d, d.isRecursionRequest(r)) if err == nil { return SyncResponse(true, result) } if !isDbLockedError(err) { shared.LogDebugf("DBERR: containersGet: error %q", err) return InternalError(err) } // 1 s may seem drastic, but we really don't want to thrash // perhaps we should use a random amount time.Sleep(100 * time.Millisecond) } shared.LogDebugf("DBERR: containersGet, db is locked") shared.PrintStack() return InternalError(fmt.Errorf("DB is locked")) }
func operationCreate(opClass operationClass, opResources map[string][]string, opMetadata interface{}, onRun func(*operation) error, onCancel func(*operation) error, onConnect func(*operation, *http.Request, http.ResponseWriter) error) (*operation, error) { // Main attributes op := operation{} op.id = uuid.NewRandom().String() op.class = opClass op.createdAt = time.Now() op.updatedAt = op.createdAt op.status = shared.Pending op.url = fmt.Sprintf("/%s/operations/%s", shared.APIVersion, op.id) op.resources = opResources op.chanDone = make(chan error) newMetadata, err := shared.ParseMetadata(opMetadata) if err != nil { return nil, err } op.metadata = newMetadata // Callback functions op.onRun = onRun op.onCancel = onCancel op.onConnect = onConnect // Sanity check if op.class != operationClassWebsocket && op.onConnect != nil { return nil, fmt.Errorf("Only websocket operations can have a Connect hook") } if op.class == operationClassWebsocket && op.onConnect == nil { return nil, fmt.Errorf("Websocket operations must have a Connect hook") } if op.class == operationClassToken && op.onRun != nil { return nil, fmt.Errorf("Token operations can't have a Run hook") } if op.class == operationClassToken && op.onCancel != nil { return nil, fmt.Errorf("Token operations can't have a Cancel hook") } operationsLock.Lock() operations[op.id] = &op operationsLock.Unlock() shared.LogDebugf("New %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) return &op, nil }
// Stop stops the shared daemon. func (d *Daemon) Stop() error { forceStop := false d.tomb.Kill(errStop) shared.LogInfof("Stopping REST API handler:") for _, socket := range []*Socket{d.TCPSocket, d.UnixSocket} { if socket == nil { continue } if socket.CloseOnExit { shared.LogInfo(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()}) socket.Socket.Close() } else { shared.LogInfo(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()}) forceStop = true } } if n, err := d.numRunningContainers(); err != nil || n == 0 { shared.LogInfof("Unmounting shmounts") syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH) shared.LogInfof("Done unmounting shmounts") } else { shared.LogDebugf("Not unmounting shmounts (containers are still running)") } shared.LogInfof("Closing the database") d.db.Close() shared.LogInfof("Stopping /dev/lxd handler") d.devlxd.Close() shared.LogInfof("Stopped /dev/lxd handler") shared.LogInfof("Saving simplestreams cache") imageSaveStreamCache() shared.LogInfof("Saved simplestreams cache") if d.MockMode || forceStop { return nil } err := d.tomb.Wait() if err == errStop { return nil } return err }
func rsyncWebsocket(path string, cmd *exec.Cmd, conn *websocket.Conn) error { stdin, err := cmd.StdinPipe() if err != nil { return err } stdout, err := cmd.StdoutPipe() if err != nil { return err } stderr, err := cmd.StderrPipe() if err != nil { return err } if err := cmd.Start(); err != nil { return err } readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout) data, err2 := ioutil.ReadAll(stderr) if err2 != nil { shared.LogDebugf("error reading rsync stderr: %s", err2) return err2 } err = cmd.Wait() if err != nil { shared.LogDebugf("rsync recv error for path %s: %s: %s", path, err, string(data)) } <-readDone <-writeDone return err }
func (p *patch) apply(d *Daemon) error { shared.LogDebugf("Applying patch: %s", p.name) err := p.run(p.name, d) if err != nil { return err } err = dbPatchesMarkApplied(d.db, p.name) if err != nil { return err } return nil }
func (c *migrationFields) controlChannel() <-chan MigrationControl { ch := make(chan MigrationControl) go func() { msg := MigrationControl{} err := c.recv(&msg) if err != nil { shared.LogDebugf("Got error reading migration control socket %s", err) close(ch) return } ch <- msg }() return ch }
func (u *dbUpdate) apply(currentVersion int, d *Daemon) error { // Get the current schema version shared.LogDebugf("Updating DB schema from %d to %d", currentVersion, u.version) err := u.run(currentVersion, u.version, d) if err != nil { return err } _, err = d.db.Exec("INSERT INTO schema (version, updated_at) VALUES (?, strftime(\"%s\"));", u.version) if err != nil { return err } return nil }
func dbContainerConfigInsert(tx *sql.Tx, id int, config map[string]string) error { str := "INSERT INTO containers_config (container_id, key, value) values (?, ?, ?)" stmt, err := tx.Prepare(str) if err != nil { return err } defer stmt.Close() for k, v := range config { _, err := stmt.Exec(id, k, v) if err != nil { shared.LogDebugf("Error adding configuration item %s = %s to container %d", k, v, id) return err } } return nil }
func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interface{}) error { for i := 0; i < 100; i++ { err := db.QueryRow(q, args...).Scan(outargs...) if err == nil { return nil } if isNoMatchError(err) { return err } if !isDbLockedError(err) { return err } time.Sleep(100 * time.Millisecond) } shared.LogDebugf("DbQueryRowScan: query %q args %q, DB still locked", q, args) shared.PrintStack() return fmt.Errorf("DB is locked") }
func (op *operation) UpdateResources(opResources map[string][]string) error { if op.status != shared.Pending && op.status != shared.Running { return fmt.Errorf("Only pending or running operations can be updated") } if op.readonly { return fmt.Errorf("Read-only operations can't be updated") } op.lock.Lock() op.updatedAt = time.Now() op.resources = opResources op.lock.Unlock() shared.LogDebugf("Updated resources for %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) return nil }
func dbUpdateFromV10(currentVersion int, version int, d *Daemon) error { if d.MockMode { // No need to move lxc to containers in mock runs, // dbUpdateFromV12 will then set the db version to 13 return nil } if shared.PathExists(shared.VarPath("lxc")) { err := os.Rename(shared.VarPath("lxc"), shared.VarPath("containers")) if err != nil { return err } shared.LogDebugf("Restarting all the containers following directory rename") containersShutdown(d) containersRestart(d) } return nil }