func (ts *Service) handleTemplate(w http.ResponseWriter, r *http.Request) { id, err := ts.templateIDFromPath(r.URL.Path) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } raw, err := ts.templates.Get(id) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } scriptFormat := r.URL.Query().Get("script-format") switch scriptFormat { case "": scriptFormat = "formatted" case "formatted", "raw": default: httpd.HttpError(w, fmt.Sprintf("invalid script-format parameter %q", scriptFormat), true, http.StatusBadRequest) return } t, err := ts.convertTemplate(raw, scriptFormat) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(httpd.MarshalJSON(t, true)) }
func (ts *Service) handleTask(w http.ResponseWriter, r *http.Request) { id, err := ts.taskIDFromPath(r.URL.Path) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } raw, err := ts.tasks.Get(id) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } scriptFormat := r.URL.Query().Get("script-format") switch scriptFormat { case "", "formatted": scriptFormat = "formatted" case "raw": default: httpd.HttpError(w, fmt.Sprintf("invalid script-format parameter %q", scriptFormat), true, http.StatusBadRequest) return } dotView := r.URL.Query().Get("dot-view") switch dotView { case "": dotView = "attributes" case "attributes": case "labels": default: httpd.HttpError(w, fmt.Sprintf("invalid dot-view parameter %q", dotView), true, http.StatusBadRequest) return } tmID := r.URL.Query().Get("replay-id") if tmID == "" { tmID = kapacitor.MainTaskMaster } tm := ts.TaskMasterLookup.Get(tmID) if tm == nil { httpd.HttpError(w, fmt.Sprintf("no running replay with ID: %s", tmID), true, http.StatusBadRequest) return } if tmID != kapacitor.MainTaskMaster && !tm.IsExecuting(raw.ID) { httpd.HttpError(w, fmt.Sprintf("replay %s is not for task: %s", tmID, raw.ID), true, http.StatusBadRequest) return } t, err := ts.convertTask(raw, scriptFormat, dotView, tm) if err != nil { httpd.HttpError(w, fmt.Sprintf("invalid task stored in db: %s", err.Error()), true, http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(httpd.MarshalJSON(t, true)) }
func (s *Service) handleRecordBatch(w http.ResponseWriter, req *http.Request) { var opt kclient.RecordBatchOptions dec := json.NewDecoder(req.Body) err := dec.Decode(&opt) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } if opt.ID == "" { opt.ID = uuid.NewV4().String() } if !validID.MatchString(opt.ID) { httpd.HttpError(w, fmt.Sprintf("recording ID must contain only letters, numbers, '-', '.' and '_'. %q", opt.ID), true, http.StatusBadRequest) return } if opt.Start.IsZero() { httpd.HttpError(w, "must provide start time", true, http.StatusBadRequest) return } if opt.Stop.IsZero() { opt.Stop = time.Now() } t, err := s.TaskStore.Load(opt.Task) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } dataUrl := s.dataURLFromID(opt.ID, batchEXT) recording := Recording{ ID: opt.ID, DataURL: dataUrl.String(), Type: BatchRecording, Date: time.Now(), Status: Running, } err = s.recordings.Create(recording) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } go func(recording Recording) { ds, _ := parseDataSourceURL(dataUrl.String()) err := s.doRecordBatch(ds, t, opt.Start, opt.Stop) s.updateRecordingResult(recording, ds, err) }(recording) w.WriteHeader(http.StatusCreated) w.Write(httpd.MarshalJSON(convertRecording(recording), true)) }
func (s *Service) handleRecordQuery(w http.ResponseWriter, req *http.Request) { var opt kclient.RecordQueryOptions dec := json.NewDecoder(req.Body) err := dec.Decode(&opt) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } if opt.ID == "" { opt.ID = uuid.NewV4().String() } if !validID.MatchString(opt.ID) { httpd.HttpError(w, fmt.Sprintf("recording ID must contain only letters, numbers, '-', '.' and '_'. %q", opt.ID), true, http.StatusBadRequest) return } if opt.Query == "" { httpd.HttpError(w, "must provide query", true, http.StatusBadRequest) return } var dataUrl url.URL var typ RecordingType switch opt.Type { case kclient.StreamTask: dataUrl = s.dataURLFromID(opt.ID, streamEXT) typ = StreamRecording case kclient.BatchTask: dataUrl = s.dataURLFromID(opt.ID, batchEXT) typ = BatchRecording } recording := Recording{ ID: opt.ID, DataURL: dataUrl.String(), Type: typ, Date: time.Now(), Status: Running, } err = s.recordings.Create(recording) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } go func(recording Recording) { ds, _ := parseDataSourceURL(dataUrl.String()) err := s.doRecordQuery(ds, opt.Query, typ, opt.Cluster) s.updateRecordingResult(recording, ds, err) }(recording) w.WriteHeader(http.StatusCreated) w.Write(httpd.MarshalJSON(convertRecording(recording), true)) }
func (ts *Service) handleTask(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") if name == "" { httpd.HttpError(w, "must pass task name", true, http.StatusBadRequest) return } labels := false labelsStr := r.URL.Query().Get("labels") if labelsStr != "" { var err error labels, err = strconv.ParseBool(labelsStr) if err != nil { httpd.HttpError(w, "invalid labels value:", true, http.StatusBadRequest) return } } raw, err := ts.LoadRaw(name) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } executing := ts.TaskMaster.IsExecuting(name) errMsg := raw.Error dot := "" task, err := ts.Load(name) if err == nil { if executing { dot = ts.TaskMaster.ExecutingDot(name, labels) } else { dot = string(task.Dot()) } } else { errMsg = err.Error() } info := TaskInfo{ Name: name, Type: raw.Type, DBRPs: raw.DBRPs, TICKscript: raw.TICKscript, Dot: dot, Enabled: ts.IsEnabled(name), Executing: executing, Error: errMsg, } w.Write(httpd.MarshalJSON(info, true)) }
func (r *Service) handleGetRecording(w http.ResponseWriter, req *http.Request) { rid := req.URL.Query().Get("id") // First check if its still running var errC <-chan error var running bool func() { r.recordingsMu.RLock() defer r.recordingsMu.RUnlock() errC, running = r.runningRecordings[rid] }() if running { // It is still running wait for it to finish err := <-errC if err != nil { info := RecordingInfo{ ID: rid, Error: err.Error(), } w.Write(httpd.MarshalJSON(info, true)) return } } // It already finished, return its info info, err := r.GetRecordings([]string{rid}) if err != nil { httpd.HttpError(w, "error finding recording: "+err.Error(), true, http.StatusInternalServerError) return } if len(info) != 1 { httpd.HttpError(w, "recording not found", true, http.StatusNotFound) return } w.Write(httpd.MarshalJSON(info[0], true)) }
func (s *Service) handleRecordStream(w http.ResponseWriter, r *http.Request) { var opt kclient.RecordStreamOptions dec := json.NewDecoder(r.Body) err := dec.Decode(&opt) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } if opt.ID == "" { opt.ID = uuid.NewV4().String() } if !validID.MatchString(opt.ID) { httpd.HttpError(w, fmt.Sprintf("recording ID must contain only letters, numbers, '-', '.' and '_'. %q", opt.ID), true, http.StatusBadRequest) return } t, err := s.TaskStore.Load(opt.Task) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } dataUrl := s.dataURLFromID(opt.ID, streamEXT) recording := Recording{ ID: opt.ID, DataURL: dataUrl.String(), Type: StreamRecording, Date: time.Now(), Status: Running, } err = s.recordings.Create(recording) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } // Spawn routine to perform actual recording. go func(recording Recording) { ds, _ := parseDataSourceURL(dataUrl.String()) err := s.doRecordStream(opt.ID, ds, opt.Stop, t.DBRPs, t.Measurements()) s.updateRecordingResult(recording, ds, err) }(recording) w.WriteHeader(http.StatusCreated) w.Write(httpd.MarshalJSON(convertRecording(recording), true)) }
func (s *Service) handleReplay(w http.ResponseWriter, req *http.Request) { id, err := s.replayIDFromPath(req.URL.Path) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } replay, err := s.replays.Get(id) if err != nil { httpd.HttpError(w, "could not find replay: "+err.Error(), true, http.StatusNotFound) return } if replay.Status == Running { w.WriteHeader(http.StatusAccepted) } else { w.WriteHeader(http.StatusOK) } w.Write(httpd.MarshalJSON(convertReplay(replay), true)) }
func (ts *Service) handleTasks(w http.ResponseWriter, r *http.Request) { tasksStr := r.URL.Query().Get("tasks") var tasks []string if tasksStr != "" { tasks = strings.Split(tasksStr, ",") } infos, err := ts.GetTaskInfo(tasks) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } type response struct { Tasks []taskInfo `json:"Tasks"` } w.Write(httpd.MarshalJSON(response{infos}, true)) }
func (s *Service) handleList(w http.ResponseWriter, req *http.Request) { ridsStr := req.URL.Query().Get("rids") var rids []string if ridsStr != "" { rids = strings.Split(ridsStr, ",") } infos, err := s.GetRecordings(rids) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusNotFound) return } type response struct { Recordings []RecordingInfo `json:"Recordings"` } w.Write(httpd.MarshalJSON(response{infos}, true)) }
func (s *Service) handleRecording(w http.ResponseWriter, r *http.Request) { rid, err := s.recordingIDFromPath(r.URL.Path) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } recording, err := s.recordings.Get(rid) if err != nil { httpd.HttpError(w, "error finding recording: "+err.Error(), true, http.StatusInternalServerError) return } if recording.Status == Running { w.WriteHeader(http.StatusAccepted) } else { w.WriteHeader(http.StatusOK) } w.Write(httpd.MarshalJSON(convertRecording(recording), true)) }
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)) }
func (ts *Service) handleUpdateTask(w http.ResponseWriter, r *http.Request) { id, err := ts.taskIDFromPath(r.URL.Path) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } task := client.UpdateTaskOptions{} dec := json.NewDecoder(r.Body) err = dec.Decode(&task) if err != nil { httpd.HttpError(w, "invalid JSON", true, http.StatusBadRequest) return } // Check for existing task original, err := ts.tasks.Get(id) if err != nil { httpd.HttpError(w, "task does not exist, cannot update", true, http.StatusNotFound) return } updated := original // Set ID if changing if task.ID != "" { updated.ID = task.ID } if task.TemplateID != "" || updated.TemplateID != "" { templateID := task.TemplateID if templateID == "" { templateID = updated.TemplateID } template, err := ts.templates.Get(templateID) if err != nil { httpd.HttpError(w, fmt.Sprintf("unknown template %s: err: %s", task.TemplateID, err), true, http.StatusBadRequest) return } if original.ID != updated.ID || original.TemplateID != updated.TemplateID { if original.TemplateID != "" { if err := ts.templates.DisassociateTask(original.TemplateID, original.ID); err != nil { httpd.HttpError(w, fmt.Sprintf("failed to disassociate task with template: %s", err), true, http.StatusBadRequest) return } } if err := ts.templates.AssociateTask(templateID, updated.ID); err != nil { httpd.HttpError(w, fmt.Sprintf("failed to associate task with template: %s", err), true, http.StatusBadRequest) return } } updated.Type = template.Type updated.TICKscript = template.TICKscript updated.TemplateID = templateID } else { // Only set type and script if not a templated task // Set task type switch task.Type { case client.StreamTask: updated.Type = StreamTask case client.BatchTask: updated.Type = BatchTask } // Set tick script if task.TICKscript != "" { updated.TICKscript = task.TICKscript } } // Set dbrps if len(task.DBRPs) > 0 { updated.DBRPs = make([]DBRP, len(task.DBRPs)) for i, dbrp := range task.DBRPs { updated.DBRPs[i] = DBRP{ Database: dbrp.Database, RetentionPolicy: dbrp.RetentionPolicy, } } } // Set status previousStatus := updated.Status switch task.Status { case client.Enabled: updated.Status = Enabled case client.Disabled: updated.Status = Disabled } statusChanged := previousStatus != updated.Status // Set vars if len(task.Vars) > 0 { updated.Vars, err = ts.convertToServiceVars(task.Vars) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } } // Validate task _, err = ts.newKapacitorTask(updated) if err != nil { httpd.HttpError(w, "invalid TICKscript: "+err.Error(), true, http.StatusBadRequest) return } now := time.Now() updated.Modified = now if statusChanged && updated.Status == Enabled { updated.LastEnabled = now } if original.ID != updated.ID { // Task ID changed delete and re-create. if err := ts.tasks.Create(updated); err != nil { httpd.HttpError(w, fmt.Sprintf("failed to create new task during ID change: %s", err.Error()), true, http.StatusInternalServerError) return } if err := ts.tasks.Delete(original.ID); err != nil { ts.logger.Printf("E! failed to delete old task definition during ID change: old ID: %s new ID: %s, %s", original.ID, updated.ID, err.Error()) } if original.Status == Enabled && updated.Status == Enabled { // Stop task and start it under new name ts.stopTask(original.ID) if err := ts.startTask(updated); err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } } } else { if err := ts.tasks.Replace(updated); err != nil { httpd.HttpError(w, fmt.Sprintf("failed to replace task definition: %s", err.Error()), true, http.StatusInternalServerError) return } } if statusChanged { // Enable/Disable task switch updated.Status { case Enabled: kapacitor.NumEnabledTasksVar.Add(1) err = ts.startTask(updated) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } case Disabled: kapacitor.NumEnabledTasksVar.Add(-1) ts.stopTask(original.ID) } } t, err := ts.convertTask(updated, "formatted", "attributes", ts.TaskMasterLookup.Main()) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(httpd.MarshalJSON(t, true)) }
func (ts *Service) handleCreateTask(w http.ResponseWriter, r *http.Request) { task := client.CreateTaskOptions{} dec := json.NewDecoder(r.Body) err := dec.Decode(&task) if err != nil { httpd.HttpError(w, "invalid JSON", true, http.StatusBadRequest) return } if task.ID == "" { task.ID = uuid.NewV4().String() } if !validTaskID.MatchString(task.ID) { httpd.HttpError(w, fmt.Sprintf("task ID must contain only letters, numbers, '-', '.' and '_'. %q", task.ID), true, http.StatusBadRequest) return } newTask := Task{ ID: task.ID, } // Check for existing task _, err = ts.tasks.Get(task.ID) if err == nil { httpd.HttpError(w, fmt.Sprintf("task %s already exists", task.ID), true, http.StatusBadRequest) return } // Check for template ID if task.TemplateID != "" { template, err := ts.templates.Get(task.TemplateID) if err != nil { httpd.HttpError(w, fmt.Sprintf("unknown template %s: err: %s", task.TemplateID, err), true, http.StatusBadRequest) return } newTask.Type = template.Type newTask.TICKscript = template.TICKscript newTask.TemplateID = task.TemplateID switch template.Type { case StreamTask: task.Type = client.StreamTask case BatchTask: task.Type = client.BatchTask } task.TICKscript = template.TICKscript if err := ts.templates.AssociateTask(task.TemplateID, newTask.ID); err != nil { httpd.HttpError(w, fmt.Sprintf("failed to associate task with template: %s", err), true, http.StatusBadRequest) return } } else { // Set task type switch task.Type { case client.StreamTask: newTask.Type = StreamTask case client.BatchTask: newTask.Type = BatchTask default: httpd.HttpError(w, fmt.Sprintf("unknown type %q", task.Type), true, http.StatusBadRequest) return } // Set tick script newTask.TICKscript = task.TICKscript if newTask.TICKscript == "" { httpd.HttpError(w, fmt.Sprintf("must provide TICKscript"), true, http.StatusBadRequest) return } } // Set dbrps newTask.DBRPs = make([]DBRP, len(task.DBRPs)) for i, dbrp := range task.DBRPs { newTask.DBRPs[i] = DBRP{ Database: dbrp.Database, RetentionPolicy: dbrp.RetentionPolicy, } } if len(newTask.DBRPs) == 0 { httpd.HttpError(w, fmt.Sprintf("must provide at least one database and retention policy."), true, http.StatusBadRequest) return } // Set status switch task.Status { case client.Enabled: newTask.Status = Enabled case client.Disabled: newTask.Status = Disabled default: newTask.Status = Disabled } // Set vars newTask.Vars, err = ts.convertToServiceVars(task.Vars) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } // Validate task _, err = ts.newKapacitorTask(newTask) if err != nil { httpd.HttpError(w, "invalid TICKscript: "+err.Error(), true, http.StatusBadRequest) return } now := time.Now() newTask.Created = now newTask.Modified = now if newTask.Status == Enabled { newTask.LastEnabled = now } // Save task err = ts.tasks.Create(newTask) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } // Count new task kapacitor.NumTasksVar.Add(1) if newTask.Status == Enabled { //Count new enabled task kapacitor.NumEnabledTasksVar.Add(1) // Start task err = ts.startTask(newTask) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } } // Return task info t, err := ts.convertTask(newTask, "formatted", "attributes", ts.TaskMasterLookup.Main()) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(httpd.MarshalJSON(t, true)) }
func (ts *Service) handleListTasks(w http.ResponseWriter, r *http.Request) { pattern := r.URL.Query().Get("pattern") fields := r.URL.Query()["fields"] if len(fields) == 0 { fields = allTaskFields } else { // Always return ID field fields = append(fields, "id", "link") } scriptFormat := r.URL.Query().Get("script-format") switch scriptFormat { case "": scriptFormat = "formatted" case "formatted": case "raw": default: httpd.HttpError(w, fmt.Sprintf("invalid script-format parameter %q", scriptFormat), true, http.StatusBadRequest) return } dotView := r.URL.Query().Get("dot-view") switch dotView { case "": dotView = "attributes" case "attributes": case "labels": default: httpd.HttpError(w, fmt.Sprintf("invalid dot-view parameter %q", dotView), true, http.StatusBadRequest) return } var err error offset := int64(0) offsetStr := r.URL.Query().Get("offset") if offsetStr != "" { offset, err = strconv.ParseInt(offsetStr, 10, 64) if err != nil { httpd.HttpError(w, fmt.Sprintf("invalid offset parameter %q must be an integer: %s", offsetStr, err), true, http.StatusBadRequest) } } limit := int64(100) limitStr := r.URL.Query().Get("limit") if limitStr != "" { limit, err = strconv.ParseInt(limitStr, 10, 64) if err != nil { httpd.HttpError(w, fmt.Sprintf("invalid limit parameter %q must be an integer: %s", limitStr, err), true, http.StatusBadRequest) } } rawTasks, err := ts.tasks.List(pattern, int(offset), int(limit)) tasks := make([]map[string]interface{}, len(rawTasks)) tm := ts.TaskMasterLookup.Main() for i, task := range rawTasks { tasks[i] = make(map[string]interface{}, len(fields)) executing := tm.IsExecuting(task.ID) for _, field := range fields { var value interface{} switch field { case "id": value = task.ID case "link": value = ts.taskLink(task.ID) case "type": switch task.Type { case StreamTask: value = client.StreamTask case BatchTask: value = client.BatchTask } case "dbrps": dbrps := make([]client.DBRP, len(task.DBRPs)) for i, dbrp := range task.DBRPs { dbrps[i] = client.DBRP{ Database: dbrp.Database, RetentionPolicy: dbrp.RetentionPolicy, } } value = dbrps case "script": value = task.TICKscript if scriptFormat == "formatted" { formatted, err := tick.Format(task.TICKscript) if err == nil { // Only format if it succeeded. // Otherwise a change in syntax may prevent task retrieval. value = formatted } } case "executing": value = executing case "dot": if executing { value = tm.ExecutingDot(task.ID, dotView == "labels") } else { kt, err := ts.newKapacitorTask(task) if err != nil { break } value = string(kt.Dot()) } case "stats": if executing { s, err := tm.ExecutionStats(task.ID) if err != nil { ts.logger.Printf("E! failed to retrieve stats for task %s: %v", task.ID, err) } else { value = client.ExecutionStats{ TaskStats: s.TaskStats, NodeStats: s.NodeStats, } } } case "error": value = task.Error case "status": switch task.Status { case Disabled: value = client.Disabled case Enabled: value = client.Enabled } case "created": value = task.Created case "modified": value = task.Modified case "last-enabled": value = task.LastEnabled default: httpd.HttpError(w, fmt.Sprintf("unsupported field %q", field), true, http.StatusBadRequest) return } tasks[i][field] = value } } type response struct { Tasks []map[string]interface{} `json:"tasks"` } w.Write(httpd.MarshalJSON(response{tasks}, true)) }
func (s *Service) handleListReplays(w http.ResponseWriter, r *http.Request) { pattern := r.URL.Query().Get("pattern") fields := r.URL.Query()["fields"] if len(fields) == 0 { fields = allReplayFields } else { // Always return ID field fields = append(fields, "id", "link") } var err error offset := int64(0) offsetStr := r.URL.Query().Get("offset") if offsetStr != "" { offset, err = strconv.ParseInt(offsetStr, 10, 64) if err != nil { httpd.HttpError(w, fmt.Sprintf("invalid offset parameter %q must be an integer: %s", offsetStr, err), true, http.StatusBadRequest) } } limit := int64(100) limitStr := r.URL.Query().Get("limit") if limitStr != "" { limit, err = strconv.ParseInt(limitStr, 10, 64) if err != nil { httpd.HttpError(w, fmt.Sprintf("invalid limit parameter %q must be an integer: %s", limitStr, err), true, http.StatusBadRequest) } } replays, err := s.replays.List(pattern, int(offset), int(limit)) rs := make([]map[string]interface{}, len(replays)) for i, replay := range replays { rs[i] = make(map[string]interface{}, len(fields)) for _, field := range fields { var value interface{} switch field { case "id": value = replay.ID case "link": value = replayLink(replay.ID) case "recording": value = replay.RecordingID case "task": value = replay.TaskID case "recording-time": value = replay.RecordingTime case "clock": switch replay.Clock { case Fast: value = kclient.Fast case Real: value = kclient.Real } case "date": value = replay.Date case "error": value = replay.Error case "status": switch replay.Status { case Failed: value = kclient.Failed case Running: value = kclient.Running case Finished: value = kclient.Finished } case "progress": value = replay.Progress default: httpd.HttpError(w, fmt.Sprintf("unsupported field %q", field), true, http.StatusBadRequest) return } rs[i][field] = value } } type response struct { Replays []map[string]interface{} `json:"replays"` } w.Write(httpd.MarshalJSON(response{Replays: rs}, true)) }
func (ts *Service) handleUpdateTemplate(w http.ResponseWriter, r *http.Request) { id, err := ts.templateIDFromPath(r.URL.Path) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } template := client.UpdateTemplateOptions{} dec := json.NewDecoder(r.Body) err = dec.Decode(&template) if err != nil { httpd.HttpError(w, "invalid JSON", true, http.StatusBadRequest) return } // Check for existing template original, err := ts.templates.Get(id) if err != nil { httpd.HttpError(w, "template does not exist, cannot update", true, http.StatusNotFound) return } updated := original // Set ID if template.ID != "" { updated.ID = template.ID } // Set template type switch template.Type { case client.StreamTask: updated.Type = StreamTask case client.BatchTask: updated.Type = BatchTask } // Set tick script if template.TICKscript != "" { updated.TICKscript = template.TICKscript } // Validate template _, err = ts.templateTask(updated) if err != nil { httpd.HttpError(w, "invalid TICKscript: "+err.Error(), true, http.StatusBadRequest) return } // Get associated tasks taskIds, err := ts.templates.ListAssociatedTasks(original.ID) if err != nil { httpd.HttpError(w, fmt.Sprintf("error getting associated tasks for template %s: %s", original.ID, err.Error()), true, http.StatusInternalServerError) return } // Save updated template now := time.Now() updated.Modified = now if original.ID != updated.ID { if err := ts.templates.Create(updated); err != nil { httpd.HttpError(w, fmt.Sprintf("failed to create new template for ID change: %s", err.Error()), true, http.StatusInternalServerError) return } if err := ts.templates.Delete(original.ID); err != nil { ts.logger.Printf("E! failed to delete old template during ID change, old ID: %s new ID: %s, %s", original.ID, updated.ID, err.Error()) } } else { if err := ts.templates.Replace(updated); err != nil { httpd.HttpError(w, fmt.Sprintf("failed to replace template definition: %s", err.Error()), true, http.StatusInternalServerError) return } } // Update all associated tasks err = ts.updateAllAssociatedTasks(original, updated, taskIds) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } // Return template definition t, err := ts.convertTemplate(updated, "formatted") if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(httpd.MarshalJSON(t, true)) }
func (ts *Service) handleCreateTemplate(w http.ResponseWriter, r *http.Request) { template := client.CreateTemplateOptions{} dec := json.NewDecoder(r.Body) err := dec.Decode(&template) if err != nil { httpd.HttpError(w, "invalid JSON", true, http.StatusBadRequest) return } if template.ID == "" { template.ID = uuid.NewV4().String() } if !validTemplateID.MatchString(template.ID) { httpd.HttpError(w, fmt.Sprintf("template ID must contain only letters, numbers, '-', '.' and '_'. %q", template.ID), true, http.StatusBadRequest) return } newTemplate := Template{ ID: template.ID, } // Check for existing template _, err = ts.templates.Get(template.ID) if err == nil { httpd.HttpError(w, fmt.Sprintf("template %s already exists", template.ID), true, http.StatusBadRequest) return } // Set template type switch template.Type { case client.StreamTask: newTemplate.Type = StreamTask case client.BatchTask: newTemplate.Type = BatchTask default: httpd.HttpError(w, fmt.Sprintf("unknown type %q", template.Type), true, http.StatusBadRequest) return } // Set tick script newTemplate.TICKscript = template.TICKscript if newTemplate.TICKscript == "" { httpd.HttpError(w, fmt.Sprintf("must provide TICKscript"), true, http.StatusBadRequest) return } // Validate template _, err = ts.templateTask(newTemplate) if err != nil { httpd.HttpError(w, "invalid TICKscript: "+err.Error(), true, http.StatusBadRequest) return } now := time.Now() newTemplate.Created = now newTemplate.Modified = now // Save template err = ts.templates.Create(newTemplate) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } // Return template definition t, err := ts.convertTemplate(newTemplate, "formatted") if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write(httpd.MarshalJSON(t, true)) }
func (s *Service) handleCreateReplay(w http.ResponseWriter, req *http.Request) { var opt kclient.CreateReplayOptions // Default clock to the Fast clock opt.Clock = kclient.Fast dec := json.NewDecoder(req.Body) err := dec.Decode(&opt) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } if opt.ID == "" { opt.ID = uuid.NewV4().String() } if !validID.MatchString(opt.ID) { httpd.HttpError(w, fmt.Sprintf("replay ID must contain only letters, numbers, '-', '.' and '_'. %q", opt.ID), true, http.StatusBadRequest) return } t, err := s.TaskStore.Load(opt.Task) if err != nil { httpd.HttpError(w, "task load: "+err.Error(), true, http.StatusNotFound) return } recording, err := s.recordings.Get(opt.Recording) if err != nil { httpd.HttpError(w, "recording not found: "+err.Error(), true, http.StatusNotFound) return } var clk clock.Clock var clockType Clock switch opt.Clock { case kclient.Real: clk = clock.Wall() clockType = Real case kclient.Fast: clk = clock.Fast() clockType = Fast default: httpd.HttpError(w, fmt.Sprintf("invalid clock type %v", opt.Clock), true, http.StatusBadRequest) return } // Successfully started replay replay := Replay{ ID: opt.ID, RecordingID: opt.Recording, TaskID: opt.Task, RecordingTime: opt.RecordingTime, Clock: clockType, Date: time.Now(), Status: Running, } s.replays.Create(replay) go func(replay Replay) { err := s.doReplayFromRecording(opt.ID, t, recording, clk, opt.RecordingTime) s.updateReplayResult(replay, err) }(replay) w.WriteHeader(http.StatusCreated) w.Write(httpd.MarshalJSON(convertReplay(replay), true)) }
func (s *Service) handleReplayBatch(w http.ResponseWriter, req *http.Request) { var opt kclient.ReplayBatchOptions // Default clock to the Fast clock opt.Clock = kclient.Fast dec := json.NewDecoder(req.Body) err := dec.Decode(&opt) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } if opt.ID == "" { opt.ID = uuid.NewV4().String() } if !validID.MatchString(opt.ID) { httpd.HttpError(w, fmt.Sprintf("replay ID must match %v %q", validID, opt.ID), true, http.StatusBadRequest) return } t, err := s.TaskStore.Load(opt.Task) if err != nil { httpd.HttpError(w, "task load: "+err.Error(), true, http.StatusNotFound) return } var clk clock.Clock var clockType Clock switch opt.Clock { case kclient.Real: clk = clock.Wall() clockType = Real case kclient.Fast: clk = clock.Fast() clockType = Fast default: httpd.HttpError(w, fmt.Sprintf("invalid clock type %v", opt.Clock), true, http.StatusBadRequest) return } if t.Type == kapacitor.StreamTask { httpd.HttpError(w, fmt.Sprintf("cannot replay batch against stream task: %s", opt.Task), true, http.StatusBadRequest) return } // Successfully started replay replay := Replay{ ID: opt.ID, TaskID: opt.Task, RecordingTime: opt.RecordingTime, Clock: clockType, Date: time.Now(), Status: Running, } err = s.replays.Create(replay) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } go func(replay Replay) { err := s.doLiveBatchReplay(opt.ID, t, clk, opt.RecordingTime, opt.Start, opt.Stop) s.updateReplayResult(replay, err) }(replay) w.WriteHeader(http.StatusCreated) w.Write(httpd.MarshalJSON(convertReplay(replay), true)) }
func (ts *Service) handleListTemplates(w http.ResponseWriter, r *http.Request) { pattern := r.URL.Query().Get("pattern") fields := r.URL.Query()["fields"] if len(fields) == 0 { fields = allTemplateFields } else { // Always return ID field fields = append(fields, "id", "link") } scriptFormat := r.URL.Query().Get("script-format") switch scriptFormat { case "": scriptFormat = "formatted" case "formatted": case "raw": default: httpd.HttpError(w, fmt.Sprintf("invalid script-format parameter %q", scriptFormat), true, http.StatusBadRequest) return } var err error offset := int64(0) offsetStr := r.URL.Query().Get("offset") if offsetStr != "" { offset, err = strconv.ParseInt(offsetStr, 10, 64) if err != nil { httpd.HttpError(w, fmt.Sprintf("invalid offset parameter %q must be an integer: %s", offsetStr, err), true, http.StatusBadRequest) } } limit := int64(100) limitStr := r.URL.Query().Get("limit") if limitStr != "" { limit, err = strconv.ParseInt(limitStr, 10, 64) if err != nil { httpd.HttpError(w, fmt.Sprintf("invalid limit parameter %q must be an integer: %s", limitStr, err), true, http.StatusBadRequest) } } rawTemplates, err := ts.templates.List(pattern, int(offset), int(limit)) templates := make([]map[string]interface{}, len(rawTemplates)) for i, template := range rawTemplates { templates[i] = make(map[string]interface{}, len(fields)) task, err := ts.templateTask(template) if err != nil { continue } for _, field := range fields { var value interface{} switch field { case "id": value = template.ID case "link": value = ts.templateLink(template.ID) case "type": switch template.Type { case StreamTask: value = client.StreamTask case BatchTask: value = client.BatchTask } case "script": value = template.TICKscript if scriptFormat == "formatted" { formatted, err := tick.Format(template.TICKscript) if err == nil { // Only format if it succeeded. // Otherwise a change in syntax may prevent template retrieval. value = formatted } } case "dot": value = string(task.Dot()) case "vars": vars, err := ts.convertToClientVarsFromTick(task.Vars()) if err != nil { ts.logger.Printf("E! failed to get vars for template %s: %s", template.ID, err) break } value = vars case "error": value = template.Error case "created": value = template.Created case "modified": value = template.Modified default: httpd.HttpError(w, fmt.Sprintf("unsupported field %q", field), true, http.StatusBadRequest) return } templates[i][field] = value } } type response struct { Templates []map[string]interface{} `json:"templates"` } w.Write(httpd.MarshalJSON(response{templates}, true)) }
func (r *Service) handleReplayQuery(w http.ResponseWriter, req *http.Request) { var opt kclient.ReplayQueryOptions dec := json.NewDecoder(req.Body) err := dec.Decode(&opt) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusBadRequest) return } if opt.ID == "" { opt.ID = uuid.NewV4().String() } if !validID.MatchString(opt.ID) { httpd.HttpError(w, fmt.Sprintf("recording ID must match %v %q", validID, opt.ID), true, http.StatusBadRequest) return } if opt.Query == "" { httpd.HttpError(w, "must provide query", true, http.StatusBadRequest) return } t, err := r.TaskStore.Load(opt.Task) if err != nil { httpd.HttpError(w, "task load: "+err.Error(), true, http.StatusNotFound) return } var clk clock.Clock var clockType Clock switch opt.Clock { case kclient.Real: clk = clock.Wall() clockType = Real case kclient.Fast: clk = clock.Fast() clockType = Fast default: httpd.HttpError(w, fmt.Sprintf("invalid clock type %v", opt.Clock), true, http.StatusBadRequest) return } replay := Replay{ ID: opt.ID, TaskID: opt.Task, RecordingTime: opt.RecordingTime, Clock: clockType, Date: time.Now(), Status: Running, } err = r.replays.Create(replay) if err != nil { httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError) return } go func(replay Replay) { err := r.doLiveQueryReplay(replay.ID, t, clk, opt.RecordingTime, opt.Query, opt.Cluster) r.updateReplayResult(replay, err) }(replay) w.WriteHeader(http.StatusCreated) w.Write(httpd.MarshalJSON(convertReplay(replay), true)) }