// watchCommand is an helper method to send back the command outputs of // commands like Halt,Destroy or Up to the callback function passed in the // request. func (h *Handlers) watchCommand(r *kite.Request, filePath string, fn commandFunc) (interface{}, error) { var params struct { Success dnode.Function Failure dnode.Function Output dnode.Function Heartbeat dnode.Function } if r.Args == nil { return nil, errors.New("arguments are not passed") } if err := r.Args.One().Unmarshal(¶ms); err != nil { return nil, err } path := filepath.Join(h.opts.Home, "logs", filepath.Base(filePath), strings.ToLower(r.Method)+"-"+r.ID+".log") output, err := h.output(path) if err != nil { return nil, err } fail := func(err error) error { go retry(func() error { return params.Failure.Call(err.Error()) }) if e := output.Close(); e != nil { h.log().Warning("failure closing %q: %s", path, e) } return err } if !params.Failure.IsValid() { return nil, errors.New("invalid request: missing failure callback") } if !params.Success.IsValid() { return nil, fail(errors.New("invalid request: missing success callback")) } var verr error var fns OutputFuncs fns = append(fns, func(line string) { fmt.Fprintln(output, line) i := strings.Index(strings.ToLower(line), "error:") if i == -1 { return } msg := strings.TrimSpace(line[i+len("error:"):]) if msg != "" { msg = unquoter.Replace(msg) verr = multierror.Append(verr, errors.New(msg)) } }) if params.Output.IsValid() { h.log().Debug("sending output to %q for %q", r.Username, r.Method) fns = append(fns, func(line string) { h.log().Debug("%s: %s", r.Method, line) params.Output.Call(line) }) } w := &vagrantutil.Waiter{ OutputFunc: fns.Output, } out, err := fn() if err != nil { return nil, fail(err) } go func() { h.log().Debug("vagrant: waiting for output from %q...", r.Method) defer func() { if err := output.Close(); err != nil && !logrotate.IsNop(err) { h.log().Warning("failure closing %q: %s", path, err) } }() if params.Heartbeat.IsValid() { stop := make(chan struct{}) defer close(stop) go func() { t := time.NewTicker(5 * time.Second) defer t.Stop() for { select { case <-stop: h.log().Debug("stopping heartbeat for %q", filePath) return case <-t.C: h.log().Debug("sending heartbeat for %q", filePath) if err := params.Heartbeat.Call(); err != nil { h.log().Debug("heartbeat failure for %q: %s", filePath, err) } } } }() } err := w.Wait(out, nil) if err != nil { verr = multierror.Append(verr, err) h.log().Error("Klient %q error for %q: %s", r.Method, filePath, verr) fail(verr) return } h.log().Info("Klient %q success for %q", r.Method, filePath) retry(func() error { return params.Success.Call() }) }() return true, nil }
// process is a worker goroutine that serializes access // to up.rotate; the MetaStore used in default implementation // is not goroutine safe - trying to upload the same file // in two concurrent goroutines could corrupt BoltDB database. // // TODO(rjeczalik): To increase throughput process could spawn // multiple goroutines ensuring there's at most one concurrent // upload per unique key. func (up *Uploader) process() { watched := make(map[string]struct{}) prefix := up.cfg.Kite.Config.Id for { select { case <-up.close: return case req := <-up.req: // Upload file at the given interval, if the file // is requested to be watched. if req.Interval > 0 && req.File != "" { if req.Interval < 15*time.Minute { req.Interval = 15 * time.Minute } if _, ok := watched[req.File]; !ok { go func(file string) { t := time.NewTicker(req.Interval) defer t.Stop() for { select { case <-up.close: return case <-t.C: up.req <- &request{ UploadRequest: &UploadRequest{ File: file, }, } } } }(req.File) } } var r response if req.File != "" { r.url, r.err = up.rotate.UploadFile(prefix, req.File) } else { r.url, r.err = up.rotate.Upload(path.Clean(prefix+"/"+req.Key), bytes.NewReader(req.Content)) } if req.respC != nil { select { case req.respC <- &r: default: } } switch { case r.err == nil: up.log().Debug("%s: uploaded successfully", req.File) case logrotate.IsNop(r.err): up.log().Debug("%s: nothing to upload", req.File) case os.IsNotExist(r.err): up.log().Debug("%s: file does not exist", req.File) default: up.log().Debug("%s: failed to upload: %s", req.File, r.err) } } } }