Beispiel #1
0
func (s *Service) handleTest(w http.ResponseWriter, r *http.Request) {
	name := s.nameFromPath(r.URL.Path)
	if name == "" {
		httpd.HttpError(w, "must provide service name", true, http.StatusBadRequest)
		return
	}

	test, ok := s.testers[name]
	if !ok {
		httpd.HttpError(w, fmt.Sprintf("service %q not found", name), true, http.StatusNotFound)
		return
	}

	options := test.TestOptions()
	if options != nil {
		if err := json.NewDecoder(r.Body).Decode(options); err != nil {
			httpd.HttpError(w, fmt.Sprint("failed to decode JSON body:", err), true, http.StatusBadRequest)
			return
		}
	}

	result := ServiceTestResult{}
	err := test.Test(options)
	if err != nil {
		result.Message = err.Error()
	} else {
		result.Success = true
	}
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(result)
}
Beispiel #2
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))
}
Beispiel #3
0
func (s *Service) handleDeleteRecording(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 == ErrNoRecordingExists {
		w.WriteHeader(http.StatusNoContent)
		return
	}
	if err != nil {
		httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError)
		return
	}
	err = s.recordings.Delete(rid)
	if err != nil {
		httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError)
		return
	}
	ds, err := parseDataSourceURL(recording.DataURL)
	if err != nil {
		httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError)
		return
	}

	err = ds.Remove()
	if err != nil {
		httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}
Beispiel #4
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))
}
Beispiel #5
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))
}
Beispiel #6
0
func (ts *Service) handleDeleteTemplate(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
	}
	err = ts.templates.Delete(id)
	if err != nil {
		httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}
Beispiel #7
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))
}
Beispiel #8
0
func (s *Service) handleGetConfig(w http.ResponseWriter, r *http.Request) {
	if !s.enabled {
		httpd.HttpError(w, "config override service is not enabled", true, http.StatusForbidden)
		return
	}
	section, element, hasSection, hasElement := sectionAndElementFromPath(r.URL.Path, configBasePath)
	config, err := s.getConfig(section)
	if err != nil {
		httpd.HttpError(w, fmt.Sprint("failed to resolve current config:", err), true, http.StatusInternalServerError)
		return
	}
	if hasSection && section == "" {
		httpd.HttpError(w, "section not specified, do you have an extra trailing '/'?", true, http.StatusBadRequest)
		return
	}
	if !hasSection {
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(config)
	} else if section != "" {
		sec, ok := config.Sections[section]
		if !ok {
			httpd.HttpError(w, fmt.Sprint("unknown section: ", section), true, http.StatusNotFound)
			return
		}
		if hasElement {
			var elementEntry client.ConfigElement
			// Find specified element
			elementKey := s.elementKeys[section]
			found := false
			for _, e := range sec.Elements {
				if (element == "" && elementKey == "") || e.Options[elementKey] == element {
					elementEntry = e
					found = true
					break
				}
			}
			if found {
				w.WriteHeader(http.StatusOK)
				json.NewEncoder(w).Encode(elementEntry)
			} else {
				httpd.HttpError(w, fmt.Sprintf("unknown section/element: %s/%s", section, element), true, http.StatusNotFound)
				return
			}
		} else {
			w.WriteHeader(http.StatusOK)
			json.NewEncoder(w).Encode(sec)
		}
	}
}
Beispiel #9
0
func (s *Service) handleListTests(w http.ResponseWriter, r *http.Request) {
	tests := ServiceTests{
		Link: serviceTestsLink,
	}
	pattern := r.URL.Query().Get("pattern")
	if pattern == "" {
		pattern = "*"
	}
	for name, test := range s.testers {
		if ok, err := filepath.Match(pattern, name); err != nil {
			httpd.HttpError(w, fmt.Sprintf("bad pattern: %v", err), true, http.StatusBadRequest)
			return
		} else if ok {
			options := test.TestOptions()
			tests.Services = append(tests.Services, ServiceTest{
				Link:    s.serviceTestLink(name),
				Name:    name,
				Options: options,
			})
		}
	}
	sort.Sort(tests.Services)

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(tests)
}
Beispiel #10
0
// Refresh the subscriptions linking for all clusters.
func (s *Service) handleSubscriptions(w http.ResponseWriter, r *http.Request) {
	err := s.LinkSubscriptions()
	if err != nil {
		httpd.HttpError(w, fmt.Sprintf("failed to link subscriptions: %s", err.Error()), true, http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}
Beispiel #11
0
func (ts *Service) handleDisable(w http.ResponseWriter, r *http.Request) {
	name := r.URL.Query().Get("name")
	err := ts.Disable(name)
	if err != nil {
		httpd.HttpError(w, err.Error(), true, http.StatusInternalServerError)
		return
	}
}
Beispiel #12
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))
}
Beispiel #13
0
func (h *HTTPOutNode) runOut([]byte) error {

	hndl := func(w http.ResponseWriter, req *http.Request) {
		h.mu.RLock()
		defer h.mu.RUnlock()

		if b, err := json.Marshal(h.result); err != nil {
			httpd.HttpError(
				w,
				err.Error(),
				true,
				http.StatusInternalServerError,
			)
		} else {
			w.Write(b)
		}
	}

	p := path.Join("/task", h.et.Task.Name, h.c.Endpoint)

	r := []httpd.Route{{
		Name:        h.Name(),
		Method:      "GET",
		Pattern:     p,
		HandlerFunc: hndl,
	}}

	h.endpoint = h.et.tm.HTTPDService.URL() + p
	func() {
		h.mu.Lock()
		defer h.mu.Unlock()
		h.routes = r
	}()

	err := h.et.tm.HTTPDService.AddRoutes(r)
	if err != nil {
		return err
	}

	switch h.Wants() {
	case pipeline.StreamEdge:
		for p, ok := h.ins[0].NextPoint(); ok; p, ok = h.ins[0].NextPoint() {
			h.timer.Start()
			row := models.PointToRow(p)
			h.updateResultWithRow(p.Group, row)
			h.timer.Stop()
		}
	case pipeline.BatchEdge:
		for b, ok := h.ins[0].NextBatch(); ok; b, ok = h.ins[0].NextBatch() {
			h.timer.Start()
			row := models.BatchToRow(b)
			h.updateResultWithRow(b.Group, row)
			h.timer.Stop()
		}
	}
	return nil
}
Beispiel #14
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))
}
Beispiel #15
0
func (s *Service) handleDeleteReplay(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
	}
	//TODO: Cancel running replays
	s.replays.Delete(id)
	w.WriteHeader(http.StatusNoContent)
}
Beispiel #16
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))
}
Beispiel #17
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))
}
Beispiel #18
0
func (s *Service) handleTestOptions(w http.ResponseWriter, r *http.Request) {
	name := s.nameFromPath(r.URL.Path)
	if name == "" {
		httpd.HttpError(w, "must provide service name", true, http.StatusBadRequest)
		return
	}

	test, ok := s.testers[name]
	if !ok {
		httpd.HttpError(w, fmt.Sprintf("service %q not found", name), true, http.StatusNotFound)
		return
	}

	options := test.TestOptions()
	serviceTest := ServiceTest{
		Link:    s.serviceTestLink(name),
		Name:    name,
		Options: options,
	}
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(serviceTest)
}
Beispiel #19
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))
}
Beispiel #20
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))
}
Beispiel #21
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))
}
Beispiel #22
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))
}
Beispiel #23
0
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
	}
}
Beispiel #24
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))
}
Beispiel #25
0
func (r *Service) handleReplay(w http.ResponseWriter, req *http.Request) {
	name := req.URL.Query().Get("name")
	id := req.URL.Query().Get("id")
	clockTyp := req.URL.Query().Get("clock")
	recTimeStr := req.URL.Query().Get("rec-time")
	var recTime bool
	if recTimeStr != "" {
		var err error
		recTime, err = strconv.ParseBool(recTimeStr)
		if err != nil {
			httpd.HttpError(w, err.Error(), true, http.StatusBadRequest)
			return
		}
	}

	t, err := r.TaskStore.Load(name)
	if err != nil {
		httpd.HttpError(w, "task load: "+err.Error(), true, http.StatusNotFound)
		return
	}

	var clk clock.Clock
	switch clockTyp {
	case "", "wall":
		clk = clock.Wall()
	case "fast":
		clk = clock.Fast()
	}

	// Create new isolated task master
	tm := r.TaskMaster.New()
	tm.Open()
	defer tm.Close()
	et, err := tm.StartTask(t)
	if err != nil {
		httpd.HttpError(w, "task start: "+err.Error(), true, http.StatusBadRequest)
		return
	}

	replay := kapacitor.NewReplay(clk)
	var replayC <-chan error
	switch t.Type {
	case kapacitor.StreamTask:
		f, err := r.FindStreamRecording(id)
		if err != nil {
			httpd.HttpError(w, "replay find: "+err.Error(), true, http.StatusNotFound)
			return
		}
		stream, err := tm.Stream(id)
		if err != nil {
			httpd.HttpError(w, "stream start: "+err.Error(), true, http.StatusInternalServerError)
			return
		}
		replayC = replay.ReplayStream(f, stream, recTime, precision)
	case kapacitor.BatchTask:
		fs, err := r.FindBatchRecording(id)
		if err != nil {
			httpd.HttpError(w, "replay find: "+err.Error(), true, http.StatusNotFound)
			return
		}
		batches := tm.BatchCollectors(name)
		replayC = replay.ReplayBatch(fs, batches, recTime)
	}

	// Check for error on replay
	err = <-replayC
	if err != nil {
		httpd.HttpError(w, "replay: "+err.Error(), true, http.StatusInternalServerError)
		return
	}

	// Drain tm so the task can finish
	tm.Drain()

	// Check for error on task
	err = et.Err()
	if err != nil {
		httpd.HttpError(w, "task run: "+err.Error(), true, http.StatusInternalServerError)
		return
	}

	// Call close explicity to check for error
	err = tm.Close()
	if err != nil {
		httpd.HttpError(w, "closing: "+err.Error(), true, http.StatusInternalServerError)
		return
	}
}
Beispiel #26
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))
}
Beispiel #27
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))
}
Beispiel #28
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))
}
Beispiel #29
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))
}
Beispiel #30
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))
}