diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index c5c437dd33..59091e05fe 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -2333,6 +2333,40 @@ func (r *ClickHouseReader) setColdStorage(ctx context.Context, tableName string, return nil } +func (r *ClickHouseReader) RemoveTTL(ctx context.Context, + params *model.RemoveTTLParams) (*model.RemoveTTLResponseItem, *model.ApiError) { + + var reqs []string + templateQuery := `ALTER TABLE %v REMOVE TTL` + tracesTables := []string{signozTraceDBName + "." + signozTraceTableName, signozTraceDBName + "." + signozDurationMVTable, signozTraceDBName + "." + signozSpansTable, signozTraceDBName + "." + signozErrorIndexTable} + metricsTables := []string{signozMetricDBName + "." + signozSampleName} + + switch params.Type { + case constants.TraceTTL: + for _, tableName := range tracesTables { + reqs = append(reqs, fmt.Sprintf(templateQuery, tableName)) + } + case constants.MetricsTTL: + for _, tableName := range metricsTables { + reqs = append(reqs, fmt.Sprintf(templateQuery, tableName)) + } + default: + for _, tableName := range append(append([]string{}, tracesTables...), metricsTables...) { + reqs = append(reqs, fmt.Sprintf(templateQuery, tableName)) + } + } + + zap.S().Debugf("Executing remove TTL requests: %s\n", reqs) + for _, req := range reqs { + if err := r.db.Exec(ctx, req); err != nil { + zap.S().Error(fmt.Errorf("error while removing ttl. Err=%v", err)) + return nil, &model.ApiError{Typ: model.ErrorExec, + Err: fmt.Errorf("error while removing ttl. Err=%v", err)} + } + } + return &model.RemoveTTLResponseItem{Message: "ttl has been successfully removed"}, nil +} + // GetDisks returns a list of disks {name, type} configured in clickhouse DB. func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *model.ApiError) { diskItems := []model.DiskItem{} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 9b2c752749..6148ce8531 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -310,6 +310,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/serviceMapDependencies", ViewAccess(aH.serviceMapDependencies)).Methods(http.MethodPost) router.HandleFunc("/api/v1/settings/ttl", AdminAccess(aH.setTTL)).Methods(http.MethodPost) router.HandleFunc("/api/v1/settings/ttl", ViewAccess(aH.getTTL)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/settings/ttl", AdminAccess(aH.removeTTL)).Methods(http.MethodDelete) router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet) @@ -1162,6 +1163,47 @@ func (aH *APIHandler) getTTL(w http.ResponseWriter, r *http.Request) { aH.writeJSON(w, r, result) } +func (aH *APIHandler) removeTTL(w http.ResponseWriter, r *http.Request) { + ttlParams, err := parseRemoveTTL(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + existingTTL, apiErr := (*aH.reader).GetTTL(context.Background(), &model.GetTTLParams{GetAllTTL: true}) + if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { + return + } + + if ttlParams.Type == constants.TraceTTL && existingTTL.TracesTime == -1 && + aH.handleError(w, fmt.Errorf("traces doesn't have any TTL set, cannot remove"), http.StatusBadRequest) { + return + } + + if ttlParams.Type == constants.MetricsTTL && existingTTL.MetricsTime == -1 && + aH.handleError(w, fmt.Errorf("metrics doesn't have any TTL set, cannot remove"), http.StatusBadRequest) { + return + } + + if ttlParams.RemoveAllTTL { + if existingTTL.TracesTime == -1 && existingTTL.MetricsTime != -1 { + ttlParams.Type = constants.MetricsTTL + ttlParams.RemoveAllTTL = false + } else if existingTTL.TracesTime != -1 && existingTTL.MetricsTime == -1 { + ttlParams.Type = constants.TraceTTL + ttlParams.RemoveAllTTL = false + } else if aH.handleError(w, fmt.Errorf("no TTL set, cannot remove"), http.StatusBadRequest) { + return + } + } + + result, apiErr := (*aH.reader).RemoveTTL(context.Background(), ttlParams) + if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { + return + } + + aH.writeJSON(w, r, result) +} + func (aH *APIHandler) getDisks(w http.ResponseWriter, r *http.Request) { result, apiErr := (*aH.reader).GetDisks(context.Background()) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { diff --git a/pkg/query-service/app/interface.go b/pkg/query-service/app/interface.go index 422f57ba41..ec372a8e41 100644 --- a/pkg/query-service/app/interface.go +++ b/pkg/query-service/app/interface.go @@ -50,6 +50,7 @@ type Reader interface { // Setter Interfaces SetTTL(ctx context.Context, ttlParams *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError) + RemoveTTL(ctx context.Context, ttlParams *model.RemoveTTLParams) (*model.RemoveTTLResponseItem, *model.ApiError) GetMetricAutocompleteMetricNames(ctx context.Context, matchText string) (*[]string, *model.ApiError) GetMetricAutocompleteTagKey(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError) diff --git a/pkg/query-service/app/parser.go b/pkg/query-service/app/parser.go index 1f189a5541..c40178851d 100644 --- a/pkg/query-service/app/parser.go +++ b/pkg/query-service/app/parser.go @@ -545,7 +545,7 @@ func parseTTLParams(r *http.Request) (*model.TTLParams, error) { // Validate the TTL duration. durationParsed, err := time.ParseDuration(delDuration) - if err != nil { + if err != nil || durationParsed.Seconds() <= 0 { return nil, fmt.Errorf("Not a valid TTL duration %v", delDuration) } @@ -554,7 +554,7 @@ func parseTTLParams(r *http.Request) (*model.TTLParams, error) { // If some cold storage is provided, validate the cold storage move TTL. if len(coldStorage) > 0 { toColdParsed, err = time.ParseDuration(toColdDuration) - if err != nil { + if err != nil || toColdParsed.Seconds() <= 0 { return nil, fmt.Errorf("Not a valid toCold TTL duration %v", toColdDuration) } if toColdParsed.Seconds() != 0 && toColdParsed.Seconds() >= durationParsed.Seconds() { @@ -587,6 +587,23 @@ func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) { return &model.GetTTLParams{Type: typeTTL, GetAllTTL: getAllTTL}, nil } +func parseRemoveTTL(r *http.Request) (*model.RemoveTTLParams, error) { + + typeTTL := r.URL.Query().Get("type") + removeAllTTL := false + + if len(typeTTL) == 0 { + removeAllTTL = true + } else { + // Validate the type parameter + if typeTTL != constants.TraceTTL && typeTTL != constants.MetricsTTL { + return nil, fmt.Errorf("type param should be , got %v", typeTTL) + } + } + + return &model.RemoveTTLParams{Type: typeTTL, RemoveAllTTL: removeAllTTL}, nil +} + func parseUserRequest(r *http.Request) (*model.User, error) { var req model.User if err := json.NewDecoder(r.Body).Decode(&req); err != nil { diff --git a/pkg/query-service/model/queryParams.go b/pkg/query-service/model/queryParams.go index e93989de94..4ab9f40770 100644 --- a/pkg/query-service/model/queryParams.go +++ b/pkg/query-service/model/queryParams.go @@ -206,3 +206,8 @@ type GetErrorParams struct { ErrorID string ServiceName string } + +type RemoveTTLParams struct { + Type string + RemoveAllTTL bool +} diff --git a/pkg/query-service/model/response.go b/pkg/query-service/model/response.go index 959e2b23d4..1e4c739abf 100644 --- a/pkg/query-service/model/response.go +++ b/pkg/query-service/model/response.go @@ -246,6 +246,10 @@ type SetTTLResponseItem struct { Message string `json:"message"` } +type RemoveTTLResponseItem struct { + Message string `json:"message"` +} + type DiskItem struct { Name string `json:"name,omitempty" ch:"name"` Type string `json:"type,omitempty" ch:"type"`