Esempio n. 1
0
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))
}
Esempio n. 2
0
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))
}
Esempio n. 3
0
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))
}
Esempio n. 4
0
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))
}
Esempio n. 5
0
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))
}
Esempio n. 6
0
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))
}
Esempio n. 7
0
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))
}
Esempio n. 8
0
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))
}
Esempio n. 9
0
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))
}
Esempio n. 10
0
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))
}
Esempio n. 11
0
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))
}
Esempio n. 12
0
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))
}
Esempio n. 13
0
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))
}
Esempio n. 14
0
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))
}
Esempio n. 15
0
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))
}
Esempio n. 16
0
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))
}
Esempio n. 17
0
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))
}
Esempio n. 18
0
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))
}
Esempio n. 19
0
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))
}
Esempio n. 20
0
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))
}
Esempio n. 21
0
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))
}
Esempio n. 22
0
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))
}