// Ensure a time duration can be parsed. func TestParseDuration(t *testing.T) { var tests = []struct { s string d time.Duration err string }{ {s: `3`, d: 3 * time.Microsecond}, {s: `1000`, d: 1000 * time.Microsecond}, {s: `10u`, d: 10 * time.Microsecond}, {s: `10ยต`, d: 10 * time.Microsecond}, {s: `15ms`, d: 15 * time.Millisecond}, {s: `100s`, d: 100 * time.Second}, {s: `2m`, d: 2 * time.Minute}, {s: `2h`, d: 2 * time.Hour}, {s: `2d`, d: 2 * 24 * time.Hour}, {s: `2w`, d: 2 * 7 * 24 * time.Hour}, {s: ``, err: "invalid duration"}, {s: `w`, err: "invalid duration"}, {s: `1.2w`, err: "invalid duration"}, {s: `10x`, err: "invalid duration"}, } for i, tt := range tests { d, err := influxql.ParseDuration(tt.s) if !reflect.DeepEqual(tt.err, errstring(err)) { t.Errorf("%d. %q: error mismatch:\n exp=%s\n got=%s\n\n", i, tt.s, tt.err, err) } else if tt.d != d { t.Errorf("%d. %q\n\nduration mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.d, d) } } }
// create a new number from a text string func newDur(p int, text string) (*DurationNode, error) { n := &DurationNode{ pos: pos(p), } d, err := influxql.ParseDuration(text) if err != nil { return nil, err } n.Dur = d return n, nil }
func (ts *Service) handleSave(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") newTask := &rawTask{ Name: name, SnapshotInterval: ts.snapshotInterval, } // Check for existing task raw, err := ts.LoadRaw(name) exists := err == nil if exists { newTask = raw } // Get task type ttStr := r.URL.Query().Get("type") switch ttStr { case "stream": newTask.Type = kapacitor.StreamTask case "batch": newTask.Type = kapacitor.BatchTask default: if !exists { if ttStr == "" { httpd.HttpError(w, fmt.Sprintf("no task with name %q exists cannot infer type.", name), true, http.StatusBadRequest) } else { httpd.HttpError(w, fmt.Sprintf("unknown type %q", ttStr), true, http.StatusBadRequest) } return } } // Get tick script tick, err := ioutil.ReadAll(r.Body) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } if len(tick) > 0 { newTask.TICKscript = string(tick) } else if !exists { httpd.HttpError(w, fmt.Sprintf("must provide TICKscript via POST data."), true, http.StatusBadRequest) return } // Get dbrps dbrpsStr := r.URL.Query().Get("dbrps") if dbrpsStr != "" { dbrps := make([]kapacitor.DBRP, 0) err = json.Unmarshal([]byte(dbrpsStr), &dbrps) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } newTask.DBRPs = dbrps } else if !exists { httpd.HttpError(w, fmt.Sprintf("must provide at least one database and retention policy."), true, http.StatusBadRequest) return } // Get snapshot interval snapshotIntervalStr := r.URL.Query().Get("snapshot") if snapshotIntervalStr != "" { snapshotInterval, err := influxql.ParseDuration(snapshotIntervalStr) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } newTask.SnapshotInterval = snapshotInterval } err = ts.Save(newTask) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } }
func (r *Service) handleRecord(w http.ResponseWriter, req *http.Request) { type doFunc func() error var doF doFunc started := make(chan struct{}) rid := uuid.NewV4() typ := req.URL.Query().Get("type") switch typ { case "stream": task := req.URL.Query().Get("name") if task == "" { httpd.HttpError(w, "no task specified", true, http.StatusBadRequest) return } t, err := r.TaskStore.Load(task) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } durStr := req.URL.Query().Get("duration") dur, err := influxql.ParseDuration(durStr) if err != nil { httpd.HttpError(w, "invalid duration string: "+err.Error(), true, http.StatusBadRequest) return } doF = func() error { err := r.doRecordStream(rid, dur, t.DBRPs, started) if err != nil { close(started) } return err } case "batch": var err error // Determine start time. var start time.Time startStr := req.URL.Query().Get("start") pastStr := req.URL.Query().Get("past") if startStr != "" && pastStr != "" { httpd.HttpError(w, "must not pass both 'start' and 'past' parameters", true, http.StatusBadRequest) return } now := time.Now() switch { case startStr != "": start, err = time.Parse(time.RFC3339, startStr) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } case pastStr != "": diff, err := influxql.ParseDuration(pastStr) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } start = now.Add(-1 * diff) } // Get stop time, if present stop := now stopStr := req.URL.Query().Get("stop") if stopStr != "" { stop, err = time.Parse(time.RFC3339, stopStr) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } } // Get task task := req.URL.Query().Get("name") if task == "" { httpd.HttpError(w, "no task specified", true, http.StatusBadRequest) return } t, err := r.TaskStore.Load(task) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } doF = func() error { close(started) return r.doRecordBatch(rid, t, start, stop) } case "query": query := req.URL.Query().Get("query") if query == "" { httpd.HttpError(w, "must pass query parameter", true, http.StatusBadRequest) return } typeStr := req.URL.Query().Get("ttype") var tt kapacitor.TaskType switch typeStr { case "stream": tt = kapacitor.StreamTask case "batch": tt = kapacitor.BatchTask default: httpd.HttpError(w, fmt.Sprintf("invalid type %q", typeStr), true, http.StatusBadRequest) return } doF = func() error { close(started) return r.doRecordQuery(rid, query, tt) } default: httpd.HttpError(w, "invalid recording type", true, http.StatusBadRequest) return } // Store recording in running recordings. errC := make(chan error, 1) func() { r.recordingsMu.Lock() defer r.recordingsMu.Unlock() r.runningRecordings[rid.String()] = errC }() // Spawn routine to perform actual recording. go func() { err := doF() if err != nil { // Always log an error since the user may not have requested the error. r.logger.Printf("E! recording %s failed: %v", rid.String(), err) } errC <- err // We have finished delete from running map r.recordingsMu.Lock() defer r.recordingsMu.Unlock() delete(r.runningRecordings, rid.String()) }() // Wait till the goroutine for doing the recording has actually started <-started // Respond with the recording ID type response struct { RecordingID string `json:"RecordingID"` } w.Write(httpd.MarshalJSON(response{rid.String()}, true)) }