feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -1,534 +0,0 @@
/*
Copyright 2020 KubeSphere Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2alpha1
import (
"context"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/emicklei/go-restful/v3"
"github.com/pkg/errors"
prommodel "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/template"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
const (
RuleLevelCluster RuleLevel = "cluster"
RuleLevelNamespace RuleLevel = "namespace"
AnnotationKeyRuleUpdateTime = "rule_update_time"
)
var (
ErrThanosRulerNotEnabled = errors.New("The request operation to custom alerting rule could not be done because thanos ruler is not enabled")
ErrAlertingRuleNotFound = errors.New("The alerting rule was not found")
ErrAlertingRuleAlreadyExists = errors.New("The alerting rule already exists")
ErrAlertingAPIV2NotEnabled = errors.New("The alerting v2 API is not enabled")
templateTestData = template.AlertTemplateData(map[string]string{}, map[string]string{}, "", 0)
templateTestTextPrefix = "{{$labels := .Labels}}{{$externalLabels := .ExternalLabels}}{{$value := .Value}}"
ruleNameMatcher = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
)
type RuleLevel string
type AlertingRule struct {
Id string `json:"id,omitempty" description:"rule id is only used by built-in alerting rules"`
Name string `json:"name,omitempty" description:"rule name should be unique in one namespace for custom alerting rules"`
Query string `json:"query,omitempty" description:"prometheus query expression, grammars of which may be referred to https://prometheus.io/docs/prometheus/latest/querying/basics/"`
Duration string `json:"duration,omitempty" description:"duration an alert transitions from Pending to Firing state, which must match ^([0-9]+)(y|w|d|h|m|s|ms)$"`
Labels map[string]string `json:"labels,omitempty" description:"extra labels to attach to the resulting alert sample vectors (the key string has to match [a-zA-Z_][a-zA-Z0-9_]*). eg: a typical label called severity, whose value may be info, warning, error, critical, is usually used to indicate the severity of an alert"`
Annotations map[string]string `json:"annotations,omitempty" description:"non-identifying key/value pairs. summary, message, description are the commonly used annotation names"`
}
type PostableAlertingRule struct {
AlertingRule `json:",omitempty"`
}
func (r *PostableAlertingRule) Validate() error {
errs := []error{}
if r.Name == "" {
errs = append(errs, errors.New("name can not be empty"))
} else {
if !ruleNameMatcher.MatchString(r.Name) {
errs = append(errs, errors.New("rule name must match regular expression ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"))
}
}
if r.Query == "" {
errs = append(errs, errors.New("query can not be empty"))
} else if _, err := parser.ParseExpr(r.Query); err != nil {
errs = append(errs, errors.Wrapf(err, "query is invalid: %s", r.Query))
}
if r.Duration != "" {
if _, err := prommodel.ParseDuration(r.Duration); err != nil {
errs = append(errs, errors.Wrapf(err, "duration is invalid: %s", r.Duration))
}
}
parseTest := func(text string) error {
tmpl := template.NewTemplateExpander(
context.TODO(),
templateTestTextPrefix+text,
"__alert_"+r.Name,
templateTestData,
prommodel.Time(timestamp.FromTime(time.Now())),
nil,
nil,
nil,
)
return tmpl.ParseTest()
}
if len(r.Labels) > 0 {
for name, v := range r.Labels {
if !prommodel.LabelName(name).IsValid() || strings.HasPrefix(name, "__") {
errs = append(errs, errors.Errorf(
"label name (%s) is not valid. The name must match [a-zA-Z_][a-zA-Z0-9_]* and has not the __ prefix (label names with this prefix are for internal use)", name))
}
if !prommodel.LabelValue(v).IsValid() {
errs = append(errs, errors.Errorf("invalid label value: %s", v))
}
if err := parseTest(v); err != nil {
errs = append(errs, errors.Errorf("invalid label value: %s", v))
}
}
}
if len(r.Annotations) > 0 {
for name, v := range r.Annotations {
if !prommodel.LabelName(name).IsValid() {
errs = append(errs, errors.Errorf("invalid annotation name: %s", v))
}
if err := parseTest(v); err != nil {
errs = append(errs, errors.Errorf("invalid annotation value: %s", v))
}
}
}
return utilerrors.NewAggregate(errs)
}
type GettableAlertingRule struct {
AlertingRule `json:",omitempty"`
State string `json:"state,omitempty" description:"state of a rule based on its alerts, one of firing, pending, inactive"`
Health string `json:"health,omitempty" description:"health state of a rule based on the last execution, one of ok, err, unknown"`
LastError string `json:"lastError,omitempty" description:"error for the last execution"`
EvaluationDurationSeconds float64 `json:"evaluationTime,omitempty" description:"taken seconds for evaluation of query expression"`
LastEvaluation *time.Time `json:"lastEvaluation,omitempty" description:"time for last evaluation of query expression"`
Alerts []*Alert `json:"alerts,omitempty" description:"alerts"`
}
type GettableAlertingRuleList struct {
Items []*GettableAlertingRule `json:"items"`
Total int `json:"total"`
}
type Alert struct {
ActiveAt *time.Time `json:"activeAt,omitempty" description:"time when alert is active"`
Annotations map[string]string `json:"annotations,omitempty" description:"annotations"`
Labels map[string]string `json:"labels,omitempty" description:"labels"`
State string `json:"state,omitempty" description:"state"`
Value string `json:"value,omitempty" description:"the value at the last evaluation of the query expression"`
RuleId string `json:"ruleId,omitempty" description:"rule id triggering the alert"`
RuleName string `json:"ruleName,omitempty" description:"rule name triggering the alert"`
}
type AlertList struct {
Items []*Alert `json:"items"`
Total int `json:"total"`
}
type AlertingRuleQueryParams struct {
NameContainFilter string
State string
Health string
LabelEqualFilters map[string]string
LabelContainFilters map[string]string
PageNum int
Limit int
SortField string
SortType string
}
func (q *AlertingRuleQueryParams) Filter(rules []*GettableAlertingRule) []*GettableAlertingRule {
var ret []*GettableAlertingRule
for _, rule := range rules {
if rule == nil {
continue
}
if q == nil || q.matches(rule) {
ret = append(ret, rule)
}
}
return ret
}
func (q *AlertingRuleQueryParams) matches(rule *GettableAlertingRule) bool {
if q.NameContainFilter != "" && !containsCaseInsensitive(rule.Name, q.NameContainFilter) {
return false
}
if q.State != "" && q.State != rule.State {
return false
}
if q.Health != "" && q.Health != rule.Health {
return false
}
if len(rule.Labels) == 0 {
return len(q.LabelEqualFilters) == 0 && len(q.LabelContainFilters) == 0
}
for k, v := range q.LabelEqualFilters {
if fv, ok := rule.Labels[k]; !ok || fv != v {
return false
}
}
for k, v := range q.LabelContainFilters {
if fv, ok := rule.Labels[k]; !ok || !containsCaseInsensitive(fv, v) {
return false
}
}
return true
}
// AlertingRuleIdCompare defines the default order for the alerting rules.
// For the alerting rule list, it guarantees a stable sort. For the custom alerting rules with possible same names
// and the builtin alerting rules with possible same ids, it guarantees the stability of get operations.
func AlertingRuleIdCompare(leftId, rightId string) bool {
// default to ascending order of id
return leftId <= rightId
}
func (q *AlertingRuleQueryParams) Sort(rules []*GettableAlertingRule) {
baseCompare := func(left, right *GettableAlertingRule) bool {
var leftUpdateTime, rightUpdateTime string
if len(left.Annotations) > 0 {
leftUpdateTime = left.Annotations[AnnotationKeyRuleUpdateTime]
}
if len(right.Annotations) > 0 {
rightUpdateTime = right.Annotations[AnnotationKeyRuleUpdateTime]
}
if leftUpdateTime != rightUpdateTime {
return leftUpdateTime > rightUpdateTime
}
return AlertingRuleIdCompare(left.Id, right.Id)
}
var compare = baseCompare
if q != nil {
reverse := q.SortType == "desc"
switch q.SortField {
case "name":
compare = func(left, right *GettableAlertingRule) bool {
if c := strings.Compare(left.Name, right.Name); c != 0 {
if reverse {
return c > 0
}
return c < 0
}
return baseCompare(left, right)
}
case "lastEvaluation":
compare = func(left, right *GettableAlertingRule) bool {
if left.LastEvaluation == nil {
if right.LastEvaluation != nil {
return false
}
} else {
if right.LastEvaluation == nil {
return true
} else if left.LastEvaluation.Equal(*right.LastEvaluation) {
if reverse {
return left.LastEvaluation.After(*right.LastEvaluation)
}
return left.LastEvaluation.Before(*right.LastEvaluation)
}
}
return baseCompare(left, right)
}
case "evaluationTime":
compare = func(left, right *GettableAlertingRule) bool {
if left.EvaluationDurationSeconds != right.EvaluationDurationSeconds {
if reverse {
return left.EvaluationDurationSeconds > right.EvaluationDurationSeconds
}
return left.EvaluationDurationSeconds < right.EvaluationDurationSeconds
}
return baseCompare(left, right)
}
}
}
sort.Slice(rules, func(i, j int) bool {
return compare(rules[i], rules[j])
})
}
func (q *AlertingRuleQueryParams) Sub(rules []*GettableAlertingRule) []*GettableAlertingRule {
start, stop := 0, 10
if q != nil {
start, stop = (q.PageNum-1)*q.Limit, q.PageNum*q.Limit
}
total := len(rules)
if start < total {
if stop > total {
stop = total
}
return rules[start:stop]
}
return nil
}
type AlertQueryParams struct {
State string
LabelEqualFilters map[string]string
LabelContainFilters map[string]string
PageNum int
Limit int
}
func (q *AlertQueryParams) Filter(alerts []*Alert) []*Alert {
var ret []*Alert
for _, alert := range alerts {
if alert == nil {
continue
}
if q == nil || q.matches(alert) {
ret = append(ret, alert)
}
}
return ret
}
func (q *AlertQueryParams) matches(alert *Alert) bool {
if q.State != "" && q.State != alert.State {
return false
}
if len(alert.Labels) == 0 {
return len(q.LabelEqualFilters) == 0 && len(q.LabelContainFilters) == 0
}
for k, v := range q.LabelEqualFilters {
if fv, ok := alert.Labels[k]; !ok || fv != v {
return false
}
}
for k, v := range q.LabelContainFilters {
if fv, ok := alert.Labels[k]; !ok || !containsCaseInsensitive(fv, v) {
return false
}
}
return true
}
func (q *AlertQueryParams) Sort(alerts []*Alert) {
compare := func(left, right *Alert) bool {
if left.ActiveAt == nil {
if right.ActiveAt != nil {
return false
}
} else {
if right.ActiveAt == nil {
return true
} else if !left.ActiveAt.Equal(*right.ActiveAt) {
return left.ActiveAt.After(*right.ActiveAt)
}
}
return prommodel.LabelsToSignature(left.Labels) <= prommodel.LabelsToSignature(right.Labels)
}
sort.Slice(alerts, func(i, j int) bool {
return compare(alerts[i], alerts[j])
})
}
func (q *AlertQueryParams) Sub(alerts []*Alert) []*Alert {
start, stop := 0, 10
if q != nil {
start, stop = (q.PageNum-1)*q.Limit, q.PageNum*q.Limit
}
total := len(alerts)
if start < total {
if stop > total {
stop = total
}
return alerts[start:stop]
}
return nil
}
func ParseAlertingRuleQueryParams(req *restful.Request) (*AlertingRuleQueryParams, error) {
var (
q = &AlertingRuleQueryParams{}
err error
)
q.NameContainFilter = req.QueryParameter("name")
q.State = req.QueryParameter("state")
q.Health = req.QueryParameter("health")
q.PageNum, err = strconv.Atoi(req.QueryParameter("page"))
if err != nil {
q.PageNum = 1
}
if q.PageNum <= 0 {
q.PageNum = 1
}
q.Limit, err = strconv.Atoi(req.QueryParameter("limit"))
if err != nil {
q.Limit = 10
err = nil
}
q.LabelEqualFilters, q.LabelContainFilters = parseLabelFilters(req)
q.SortField = req.QueryParameter("sort_field")
q.SortType = req.QueryParameter("sort_type")
return q, err
}
func ParseAlertQueryParams(req *restful.Request) (*AlertQueryParams, error) {
var (
q = &AlertQueryParams{}
err error
)
q.State = req.QueryParameter("state")
q.PageNum, err = strconv.Atoi(req.QueryParameter("page"))
if err != nil {
q.PageNum = 1
}
if q.PageNum <= 0 {
q.PageNum = 1
}
q.Limit, err = strconv.Atoi(req.QueryParameter("limit"))
if err != nil {
q.Limit = 10
err = nil
}
q.LabelEqualFilters, q.LabelContainFilters = parseLabelFilters(req)
return q, err
}
func parseLabelFilters(req *restful.Request) (map[string]string, map[string]string) {
var (
labelEqualFilters = make(map[string]string)
labelContainFilters = make(map[string]string)
labelFiltersString = req.QueryParameter("label_filters")
)
for _, filter := range strings.Split(labelFiltersString, ",") {
if i := strings.Index(filter, "="); i > 0 && len(filter) > i+1 {
labelEqualFilters[filter[:i]] = filter[i+1:]
} else if i := strings.Index(filter, "~"); i > 0 && len(filter) > i+1 {
labelContainFilters[filter[:i]] = filter[i+1:]
}
}
return labelEqualFilters, labelContainFilters
}
const (
ErrBadData ErrorType = "bad_data"
ErrDuplicateName ErrorType = "duplicate_name"
ErrNotFound ErrorType = "not_found"
ErrServer ErrorType = "server_error"
StatusSuccess Status = "success"
StatusError Status = "error"
ResultCreated Result = "created"
ResultUpdated Result = "updated"
ResultDeleted Result = "deleted"
)
type Status string
type ErrorType string
type Result string
type BulkResponse struct {
Errors bool `json:"errors" description:"If true, one or more operations in the bulk request don't complete successfully"`
Items []*BulkItemResponse `json:"items" description:"It contains the result of each operation in the bulk request"`
}
// MakeBulkResponse tidies the internal items and sets the errors
func (br *BulkResponse) MakeBulkResponse() *BulkResponse {
var (
items []*BulkItemResponse
itemMap = make(map[string]*BulkItemResponse)
)
for i, item := range br.Items {
if item.Status == StatusError {
br.Errors = true
}
pitem, ok := itemMap[item.RuleName]
if !ok || (pitem.Status == StatusSuccess || item.Status == StatusError) {
itemMap[item.RuleName] = br.Items[i]
}
}
for k := range itemMap {
item := itemMap[k]
if item.Error != nil {
item.ErrorStr = item.Error.Error()
}
items = append(items, itemMap[k])
}
br.Items = items
return br
}
type BulkItemResponse struct {
RuleName string `json:"ruleName,omitempty"`
Status Status `json:"status,omitempty" description:"It may be success or error"`
Result Result `json:"result,omitempty" description:"It may be created, updated or deleted, and only for successful operations"`
ErrorType ErrorType `json:"errorType,omitempty" description:"It may be bad_data, duplicate_name, not_found or server_error, and only for failed operations"`
Error error `json:"-"`
ErrorStr string `json:"error,omitempty" description:"It is only returned for failed operations"`
}
func NewBulkItemSuccessResponse(ruleName string, result Result) *BulkItemResponse {
return &BulkItemResponse{
RuleName: ruleName,
Status: StatusSuccess,
Result: result,
}
}
func NewBulkItemErrorServerResponse(ruleName string, err error) *BulkItemResponse {
return &BulkItemResponse{
RuleName: ruleName,
Status: StatusError,
ErrorType: ErrServer,
Error: err,
}
}
// containsCaseInsensitive reports whether substr is case-insensitive within s.
func containsCaseInsensitive(s, substr string) bool {
return strings.Contains(
strings.ToLower(s),
strings.ToLower(substr))
}

View File

@@ -1,384 +0,0 @@
package v2alpha1
import (
"errors"
"fmt"
"net/http"
"testing"
"time"
"github.com/emicklei/go-restful/v3"
"github.com/prometheus/prometheus/rules"
"github.com/stretchr/testify/assert"
)
var (
alertingRule = &AlertingRule{
Id: "ca7f09e76954e67c",
Name: "test-cpu-usage-high",
Query: `namespace:workload_cpu_usage:sum{namespace="test"} > 1`,
Duration: "1m",
Annotations: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
Labels: map[string]string{
"flags": "The flags is here",
"cpu_num": "The cpu_num is here",
},
}
postableAlertingRule = &PostableAlertingRule{
AlertingRule: *alertingRule,
}
gettableAlertingRule = &GettableAlertingRule{
AlertingRule: *alertingRule,
Health: string(rules.HealthGood),
State: rules.StateInactive.String(),
}
gettableAlertingRules = []*GettableAlertingRule{
{
AlertingRule: *alertingRule,
Health: string(rules.HealthGood),
State: rules.StateInactive.String(),
EvaluationDurationSeconds: 1,
LastEvaluation: &time.Time{},
},
{
AlertingRule: AlertingRule{
Id: "ca7f09e76954e688",
Name: "test-cpu-usage-high-2",
Query: `namespace:workload_cpu_usage:sum{namespace="test"} > 1`,
Duration: "1m",
Annotations: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
Labels: map[string]string{
"flags": "The flags is here",
"cpu_num": "The cpu_num is here",
},
},
State: rules.StateInactive.String(),
Health: string(rules.HealthGood),
EvaluationDurationSeconds: 0,
LastEvaluation: &time.Time{},
},
}
alertingRuleQueryParams = &AlertingRuleQueryParams{
NameContainFilter: "test-cpu-usage-high",
State: rules.StateInactive.String(),
Health: string(rules.HealthGood),
LabelEqualFilters: map[string]string{
"flags": "The flags is here",
"cpu_num": "The cpu_num is here",
},
LabelContainFilters: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
PageNum: 1,
Limit: 10,
SortField: "name",
SortType: "desc",
}
)
func TestPostableAlertingRule_Validate(t *testing.T) {
// validate AlertingRule name field
// Name is empty
postableAlertingRule.Name = ""
err := postableAlertingRule.Validate()
assert.Equal(t, "name can not be empty", err.Error())
// Name do not match regx
postableAlertingRule.Name = "TestCPUUsageHigh"
err = postableAlertingRule.Validate()
assert.Equal(t, "rule name must match regular expression ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", err.Error())
// validate AlertingRule Query field
postableAlertingRule.Name = "test-cpu-usage-high"
// Query is empty
postableAlertingRule.Query = ""
err = postableAlertingRule.Validate()
assert.Equal(t, "query can not be empty", err.Error())
postableAlertingRule.Query = `namespace:workload_cpu_usage:sum{namespace="test"} > 1`
// test no error
err = postableAlertingRule.Validate()
assert.Empty(t, err)
}
func TestAlertQueryParams_Filter(t *testing.T) {
// empty
gettableAlertingRules := []*GettableAlertingRule{}
queryParams := &AlertingRuleQueryParams{}
ret := queryParams.Filter(gettableAlertingRules)
assert.Empty(t, ret)
queryParams.NameContainFilter = "test-cpu-usage-high"
gettableAlertingRules = []*GettableAlertingRule{
gettableAlertingRule,
}
ret = queryParams.Filter(gettableAlertingRules)
assert.NotEmpty(t, ret)
}
func TestAlertQueryParams_match(t *testing.T) {
// empty
rule := &GettableAlertingRule{}
queryParams := &AlertingRuleQueryParams{}
ret := queryParams.matches(rule)
assert.True(t, ret)
// Not empty
// test false case
queryParams.NameContainFilter = "test-cpu"
ret = queryParams.matches(rule)
assert.False(t, ret)
//test true case
rule = gettableAlertingRule
queryParams.NameContainFilter = "test-cpu-usage-high"
ret = queryParams.matches(rule)
assert.True(t, ret)
}
func TestAlertingRuleIdCompare(t *testing.T) {
leftId := "test-id-1"
rightId := "test-id-2"
ret := AlertingRuleIdCompare(leftId, rightId)
assert.True(t, ret)
}
func TestAlertQueryParams_Sort(t *testing.T) {
queryParams := alertingRuleQueryParams
rules := []*GettableAlertingRule{
gettableAlertingRule,
gettableAlertingRule,
}
// sort by name
queryParams.Sort(rules)
// sort by lastEvaluation
queryParams.SortField = "lastEvaluation"
queryParams.Sort(rules)
// sort by evaluationTime
queryParams.SortField = "evaluationTime"
queryParams.Sort(rules)
}
func TestAlertQueryParams_Sub(t *testing.T) {
rules := gettableAlertingRules
queryParams := alertingRuleQueryParams
queryParams.Sub(rules)
}
var (
alertQueryParams = &AlertQueryParams{
State: rules.StateFiring.String(),
LabelEqualFilters: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
LabelContainFilters: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
PageNum: 1,
Limit: 10,
}
alert = &Alert{
RuleId: "ca7f09e76954e67c",
RuleName: "test-cpu-usage-high",
State: rules.StateFiring.String(),
Value: `namespace:workload_cpu_usage:sum{namespace="test"} > 1`,
Annotations: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
Labels: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
ActiveAt: &time.Time{},
}
alerts = []*Alert{
alert,
{
RuleId: "ca7f09e76954e699",
RuleName: "test-cpu-usage-high-2",
State: rules.StateFiring.String(),
Value: `namespace:workload_cpu_usage:sum{namespace="test"} > 1`,
Annotations: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
Labels: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
ActiveAt: &time.Time{},
},
}
)
func TestAlertingRuleQueryParams_Filter(t *testing.T) {
// empty
// Alert Array is empty
loacalAlerts := []*Alert{}
queryParam := &AlertQueryParams{}
ret := queryParam.Filter(loacalAlerts)
assert.Empty(t, ret)
// Alert Array has nil
loacalAlerts = []*Alert{nil}
ret = queryParam.Filter(loacalAlerts)
assert.Empty(t, ret)
// all pass
loacalAlerts = alerts
ret = queryParam.Filter(loacalAlerts)
assert.NotEmpty(t, ret)
}
func TestAlertingRuleQueryParams_matches(t *testing.T) {
loacalAlert := alert
queryParam := alertQueryParams
queryParam.LabelEqualFilters = map[string]string{
"flags": "The flags is here",
"cpu_num": "The cpu_num is here",
}
ret := queryParam.matches(loacalAlert)
assert.False(t, ret)
queryParam.LabelEqualFilters = map[string]string{
"alias": "The alias is here",
"description": "The description is here",
}
ret = queryParam.matches(loacalAlert)
assert.True(t, ret)
}
func TestAlertingRuleQueryParams_Sort(t *testing.T) {
loacalAlerts := alerts
queryParam := alertQueryParams
queryParam.Sort(loacalAlerts)
}
func TestAlertingRuleQueryParams_Sub(t *testing.T) {
loacalAlerts := alerts
queryParam := alertQueryParams
queryParam.Sub(loacalAlerts)
}
var (
succBulkItemResponse = BulkItemResponse{
RuleName: "test-cpu-usage-high",
Status: StatusSuccess,
Result: ResultCreated,
ErrorType: "",
Error: nil,
ErrorStr: "",
}
errBulkItemResponse = BulkItemResponse{
RuleName: "test-mem-usage-high",
Status: StatusError,
Result: ResultUpdated,
ErrorType: ErrBadData,
Error: errors.New(string(ErrBadData)),
ErrorStr: string(ErrBadData),
}
bulkResponse = BulkResponse{
Items: []*BulkItemResponse{
&succBulkItemResponse,
&errBulkItemResponse,
},
}
)
func TestParseAlertingRuleQueryParams(t *testing.T) {
queryParam := "name=test-cpu&state=firing&health=ok&sort_field=lastEvaluation&sort_type=desc&label_filters=name~test"
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost/alerting.kubesphere.io/v2alpha1/rules?%s", queryParam), nil)
if err != nil {
t.Fatal(err)
}
expected := AlertingRuleQueryParams{
NameContainFilter: "test-cpu",
State: "firing",
Health: "ok",
LabelEqualFilters: make(map[string]string),
LabelContainFilters: map[string]string{"name": "test"},
PageNum: 1,
Limit: 10,
SortField: "lastEvaluation",
SortType: "desc",
}
request := restful.NewRequest(req)
actual, err := ParseAlertingRuleQueryParams(request)
assert.NoError(t, err)
assert.Equal(t, &expected, actual)
}
func TestParseAlertQueryParams(t *testing.T) {
queryParam := "state=firing&label_filters=name~test"
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost/alerting.kubesphere.io/v2alpha1/alerts?%s", queryParam), nil)
if err != nil {
t.Fatal(err)
}
expected := AlertQueryParams{
State: "firing",
LabelEqualFilters: make(map[string]string),
LabelContainFilters: map[string]string{"name": "test"},
PageNum: 1,
Limit: 10,
}
request := restful.NewRequest(req)
actual, err := ParseAlertQueryParams(request)
assert.NoError(t, err)
assert.Equal(t, &expected, actual)
}
func TestBulkResponse_MakeBulkResponse(t *testing.T) {
br := bulkResponse.MakeBulkResponse()
assert.True(t, br.Errors)
}
func TestBulkResponse_NewBulkItemSuccessResponse(t *testing.T) {
ruleName := "test-cpu-usage-high"
result := ResultCreated
ret := NewBulkItemSuccessResponse(ruleName, result)
assert.Equal(t, ResultCreated, ret.Result)
}
func TestBulkResponse_NewBulkItemErrorServerResponse(t *testing.T) {
ruleName := "test-cpu-usage-high"
err := errors.New(string(ErrBadData))
ret := NewBulkItemErrorServerResponse(ruleName, err)
assert.Equal(t, errors.New(string(ErrBadData)), ret.Error)
}
func TestContainsCaseInsensitive(t *testing.T) {
left := "left"
right := "RIGHT"
ret := containsCaseInsensitive(left, right)
assert.False(t, ret)
}

View File

@@ -1,156 +0,0 @@
/*
Copyright 2020 KubeSphere Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2beta1
import (
"strings"
"time"
alertingv2beta1 "kubesphere.io/api/alerting/v2beta1"
)
const (
// for rulegroup/alert
FieldState = "state"
FieldBuiltin = "builtin"
// for rulegroup
FieldRuleGroupEvaluationTime = "evaluationTime"
FieldRuleGroupLastEvaluation = "lastEvalution"
// for alert
FieldAlertLabelFilters = "label_filters"
FieldAlertActiveAt = "activeAt"
FieldAlertLabelMatcher = "label_matcher"
)
var SortableFields = []string{
FieldRuleGroupEvaluationTime,
FieldRuleGroupLastEvaluation,
FieldAlertActiveAt,
}
var ComparableFields = []string{
FieldState,
FieldAlertLabelFilters,
}
type RuleGroup struct {
alertingv2beta1.RuleGroup `json:",inline"`
Status RuleGroupStatus `json:"status,omitempty"`
}
type ClusterRuleGroup struct {
alertingv2beta1.ClusterRuleGroup `json:",inline"`
Status RuleGroupStatus `json:"status,omitempty"`
}
type GlobalRuleGroup struct {
alertingv2beta1.GlobalRuleGroup `json:",inline"`
Status RuleGroupStatus `json:"status,omitempty"`
}
type RuleGroupStatus struct {
EvaluationTime *float64 `json:"evaluationTime,omitempty" description:"time spent on rule group evaluation in seconds"`
LastEvaluation *time.Time `json:"lastEvaluation,omitempty" description:"time of last evaluation"`
RulesStatus []RuleStatus `json:"rulesStatus,omitempty" description:"status of rules in one RuleGroup"`
RulesStats RulesStats `json:"rulesStats,omitempty" description:"statistics of rules in one RuleGroup"`
}
type RulesStats struct {
Inactive int `json:"inactive" description:"count of rules in the inactive state"`
Pending int `json:"pending" description:"count of rules in the pending state"`
Firing int `json:"firing" description:"count of rules in the firing state"`
Disabled int `json:"disabled" description:"count of disabled rules"`
}
type RuleStatus struct {
Expr string `json:"expr,omitempty" description:"expression evaluated, for global rules only"`
State string `json:"state,omitempty" description:"state of a rule, one of firing, pending, inactive or disabled depending on the rule and its alerts"`
Health string `json:"health,omitempty" description:"health state of a rule, one of ok, err, unknown depending on the last execution result"`
LastError string `json:"lastError,omitempty" description:"error of the last evaluation"`
EvaluationTime *float64 `json:"evaluationTime,omitempty" description:"time spent on the expression evaluation in seconds"`
LastEvaluation *time.Time `json:"lastEvaluation,omitempty" description:"time of last evaluation"`
ActiveAt *time.Time `json:"activeAt,omitempty" description:"time when this rule became active"`
Alerts []*Alert `json:"alerts,omitempty" description:"alerts"`
}
type Alert struct {
ActiveAt *time.Time `json:"activeAt,omitempty" description:"time when this alert became active"`
Annotations map[string]string `json:"annotations,omitempty" description:"annotations"`
Labels map[string]string `json:"labels,omitempty" description:"labels"`
State string `json:"state,omitempty" description:"state"`
Value string `json:"value,omitempty" description:"the value from the last expression evaluation"`
}
type LabelFilterOperator string
const (
LabelFilterOperatorEqual = "="
LabelFilterOperatorContain = "~"
)
type LabelFilter struct {
LabelName string
LabelValue string
Operator LabelFilterOperator
}
func (f *LabelFilter) Matches(labels map[string]string) bool {
v, ok := labels[f.LabelName]
if ok {
switch f.Operator {
case LabelFilterOperatorEqual:
return v == f.LabelValue
case LabelFilterOperatorContain:
return strings.Contains(v, f.LabelValue)
}
}
return false
}
type LabelFilters []LabelFilter
func (fs LabelFilters) Matches(labels map[string]string) bool {
for _, f := range fs {
if !f.Matches(labels) {
return false
}
}
return true
}
func ParseLabelFilters(filters string) LabelFilters {
var fs LabelFilters
for _, filter := range strings.Split(filters, ",") {
if i := strings.Index(filter, LabelFilterOperatorEqual); i > 0 {
fs = append(fs, LabelFilter{
Operator: LabelFilterOperatorEqual,
LabelName: filter[:i],
LabelValue: filter[i+1:],
})
} else if i := strings.Index(filter, LabelFilterOperatorContain); i > 0 {
fs = append(fs, LabelFilter{
Operator: LabelFilterOperatorContain,
LabelName: filter[:i],
LabelValue: filter[i+1:],
})
}
}
return fs
}

View File

@@ -1,112 +0,0 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"strconv"
"time"
"github.com/emicklei/go-restful/v3"
"kubesphere.io/kubesphere/pkg/simple/client/auditing"
)
type APIResponse struct {
Events *auditing.Events `json:"query,omitempty" description:"query results"`
Statistics *auditing.Statistics `json:"statistics,omitempty" description:"statistics results"`
Histogram *auditing.Histogram `json:"histogram,omitempty" description:"histogram results"`
}
type Query struct {
Operation string `json:"operation,omitempty"`
WorkspaceFilter string `json:"workspace_filter,omitempty"`
WorkspaceSearch string `json:"workspace_search,omitempty"`
ObjectRefNamespaceFilter string `json:"objectref_namespace_filter,omitempty"`
ObjectRefNamespaceSearch string `json:"objectref_namespace_search,omitempty"`
ObjectRefNameFilter string `json:"objectref_name_filter,omitempty"`
ObjectRefNameSearch string `json:"objectref_name_search,omitempty"`
LevelFilter string `json:"level_filter,omitempty"`
VerbFilter string `json:"verb_filter,omitempty"`
UserFilter string `json:"user_filter,omitempty"`
UserSearch string `json:"user_search,omitempty"`
GroupSearch string `json:"group_search,omitempty"`
SourceIpSearch string `json:"source_ip_search,omitempty"`
ObjectRefResourceFilter string `json:"objectref_resource_filter,omitempty"`
ObjectRefSubresourceFilter string `json:"objectref_subresource_filter,omitempty"`
ResponseCodeFilter string `json:"response_code_filter,omitempty"`
ResponseStatusFilter string `json:"response_status_filter,omitempty"`
StartTime time.Time `json:"start_time,omitempty"`
EndTime time.Time `json:"end_time,omitempty"`
Interval string `json:"interval,omitempty"`
Sort string `json:"sort,omitempty"`
From int64 `json:"from,omitempty"`
Size int64 `json:"size,omitempty"`
}
func ParseQueryParameter(req *restful.Request) (*Query, error) {
q := &Query{}
q.Operation = req.QueryParameter("operation")
q.WorkspaceFilter = req.QueryParameter("workspace_filter")
q.WorkspaceSearch = req.QueryParameter("workspace_search")
q.ObjectRefNamespaceFilter = req.QueryParameter("objectref_namespace_filter")
q.ObjectRefNamespaceSearch = req.QueryParameter("objectref_namespace_search")
q.ObjectRefNameFilter = req.QueryParameter("objectref_name_filter")
q.ObjectRefNameSearch = req.QueryParameter("objectref_name_search")
q.LevelFilter = req.QueryParameter("level_filter")
q.VerbFilter = req.QueryParameter("verb_filter")
q.SourceIpSearch = req.QueryParameter("source_ip_search")
q.UserFilter = req.QueryParameter("user_filter")
q.UserSearch = req.QueryParameter("user_search")
q.GroupSearch = req.QueryParameter("group_search")
q.ObjectRefResourceFilter = req.QueryParameter("objectref_resource_filter")
q.ObjectRefSubresourceFilter = req.QueryParameter("objectref_subresource_filter")
q.ResponseCodeFilter = req.QueryParameter("response_code_filter")
q.ResponseStatusFilter = req.QueryParameter("response_status_filter")
if tstr := req.QueryParameter("start_time"); tstr != "" {
sec, err := strconv.ParseInt(tstr, 10, 64)
if err != nil {
return nil, err
}
t := time.Unix(sec, 0)
q.StartTime = t
}
if tstr := req.QueryParameter("end_time"); tstr != "" {
sec, err := strconv.ParseInt(tstr, 10, 64)
if err != nil {
return nil, err
}
t := time.Unix(sec, 0)
q.EndTime = t
}
if q.Interval = req.QueryParameter("interval"); q.Interval == "" {
q.Interval = "15m"
}
q.From, _ = strconv.ParseInt(req.QueryParameter("from"), 10, 64)
size, err := strconv.ParseInt(req.QueryParameter("size"), 10, 64)
if err != nil {
size = 10
}
q.Size = size
if q.Sort = req.QueryParameter("sort"); q.Sort != "asc" {
q.Sort = "desc"
}
return q, nil
}

View File

@@ -1,46 +0,0 @@
package v1alpha1
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/emicklei/go-restful/v3"
"github.com/stretchr/testify/assert"
)
func TestParseQueryParameter(t *testing.T) {
queryParam := "operation=query&workspace_filter=my-ws,demo-ws&workspace_search=my,demo&objectref_namespace_filter=my-ns,my-test&objectref_namespace_search=my&objectref_name_filter=my-ref,demo-ref&objectref_name_search=ref&source_ip_search=192.168.&user_filter=user1&user_search=my,demo&group_search=my,demo&start_time=1136214245&end_time=1136214245&from=0&sort=desc"
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost/tenant.kubesphere.io/v2alpha1/auditing/events?%s", queryParam), nil)
if err != nil {
t.Fatal(err)
}
expected := Query{
Operation: "query",
WorkspaceFilter: "my-ws,demo-ws",
WorkspaceSearch: "my,demo",
ObjectRefNamespaceFilter: "my-ns,my-test",
ObjectRefNamespaceSearch: "my",
ObjectRefNameFilter: "my-ref,demo-ref",
ObjectRefNameSearch: "ref",
UserFilter: "user1",
UserSearch: "my,demo",
GroupSearch: "my,demo",
SourceIpSearch: "192.168.",
StartTime: time.Unix(1136214245, 0),
EndTime: time.Unix(1136214245, 0),
Interval: "15m",
Sort: "desc",
From: int64(0),
Size: int64(10),
}
request := restful.NewRequest(req)
actual, err := ParseQueryParameter(request)
assert.NoError(t, err)
assert.Equal(t, &expected, actual)
}

View File

@@ -1,18 +1,34 @@
// Copyright 2022 The KubeSphere Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package v1alpha1
type UpdateClusterRequest struct {
KubeConfig []byte `json:"kubeconfig"`
}
type CreateLabelRequest struct {
Key string `json:"key"`
Value string `json:"value"`
}
type UnbindClustersRequest struct {
Clusters []string `json:"clusters"`
}
type BindingClustersRequest struct {
Labels []string `json:"labels"`
Clusters []string `json:"clusters"`
}
type LabelValue struct {
Value string `json:"value"`
ID string `json:"id"`
}
type UpdateVisibilityRequest struct {
Op string `json:"op"`
Workspace string `json:"workspace"`
}

View File

@@ -1,36 +0,0 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha2
import "time"
type PageableDevOpsProject struct {
Items []*DevOpsProject `json:"items"`
TotalCount int `json:"total_count"`
}
type DevOpsProject struct {
ProjectId string `json:"project_id" db:"project_id" description:"ProjectId must be unique within a workspace, it is generated by kubesphere."`
Name string `json:"name" description:"DevOps Projects's Name"`
Description string `json:"description,omitempty" description:"DevOps Projects's Description, used to describe the DevOps Project"`
Creator string `json:"creator" description:"Creator's username"`
CreateTime time.Time `json:"create_time" description:"DevOps Project's Creation time"`
Status string `json:"status" description:"DevOps project's status. e.g. active"`
Visibility string `json:"visibility,omitempty" description:"Deprecated Field"`
Extra string `json:"extra,omitempty" description:"Internal Use"`
Workspace string `json:"workspace" description:"The workspace to which the devops project belongs"`
}

View File

@@ -1,102 +0,0 @@
/*
Copyright 2020 KubeSphere Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"strconv"
"time"
"github.com/emicklei/go-restful/v3"
"kubesphere.io/kubesphere/pkg/simple/client/events"
)
type APIResponse struct {
Events *events.Events `json:"query,omitempty" description:"query results"`
Statistics *events.Statistics `json:"statistics,omitempty" description:"statistics results"`
Histogram *events.Histogram `json:"histogram,omitempty" description:"histogram results"`
}
type Query struct {
Operation string `json:"operation,omitempty"`
WorkspaceFilter string `json:"workspace_filter,omitempty"`
WorkspaceSearch string `json:"workspace_search,omitempty"`
InvolvedObjectNamespaceFilter string `json:"involved_object_namespace_filter,omitempty"`
InvolvedObjectNamespaceSearch string `json:"involved_object_namespace_search,omitempty"`
InvolvedObjectNameFilter string `json:"involved_object_name_filter,omitempty"`
InvolvedObjectNameSearch string `json:"involved_object_name_search,omitempty"`
InvolvedObjectKindFilter string `json:"involved_object_kind_filter,omitempty"`
ReasonFilter string `json:"reason_filter,omitempty"`
ReasonSearch string `json:"reason_search,omitempty"`
MessageSearch string `json:"message_search,omitempty"`
TypeFilter string `json:"type_filter,omitempty"`
StartTime time.Time `json:"start_time,omitempty"`
EndTime time.Time `json:"end_time,omitempty"`
Interval string `json:"interval,omitempty"`
Sort string `json:"sort,omitempty"`
From int64 `json:"from,omitempty"`
Size int64 `json:"size,omitempty"`
}
func ParseQueryParameter(req *restful.Request) (*Query, error) {
q := &Query{}
q.Operation = req.QueryParameter("operation")
q.WorkspaceFilter = req.QueryParameter("workspace_filter")
q.WorkspaceSearch = req.QueryParameter("workspace_search")
q.InvolvedObjectNamespaceFilter = req.QueryParameter("involved_object_namespace_filter")
q.InvolvedObjectNamespaceSearch = req.QueryParameter("involved_object_namespace_search")
q.InvolvedObjectNameFilter = req.QueryParameter("involved_object_name_filter")
q.InvolvedObjectNameSearch = req.QueryParameter("involved_object_name_search")
q.InvolvedObjectKindFilter = req.QueryParameter("involved_object_kind_filter")
q.ReasonFilter = req.QueryParameter("reason_filter")
q.ReasonSearch = req.QueryParameter("reason_search")
q.MessageSearch = req.QueryParameter("message_search")
q.TypeFilter = req.QueryParameter("type_filter")
if tstr := req.QueryParameter("start_time"); tstr != "" {
sec, err := strconv.ParseInt(tstr, 10, 64)
if err != nil {
return nil, err
}
t := time.Unix(sec, 0)
q.StartTime = t
}
if tstr := req.QueryParameter("end_time"); tstr != "" {
sec, err := strconv.ParseInt(tstr, 10, 64)
if err != nil {
return nil, err
}
t := time.Unix(sec, 0)
q.EndTime = t
}
if q.Interval = req.QueryParameter("interval"); q.Interval == "" {
q.Interval = "15m"
}
q.From, _ = strconv.ParseInt(req.QueryParameter("from"), 10, 64)
size, err := strconv.ParseInt(req.QueryParameter("size"), 10, 64)
if err != nil {
size = 10
}
q.Size = size
if q.Sort = req.QueryParameter("sort"); q.Sort != "asc" {
q.Sort = "desc"
}
return q, nil
}

View File

@@ -1,47 +0,0 @@
package v1alpha1
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/emicklei/go-restful/v3"
"github.com/stretchr/testify/assert"
)
func TestParseQueryParameter(t *testing.T) {
queryParam := "operation=query&workspace_filter=my-ws,demo-ws&workspace_search=my,demo&involved_object_namespace_filter=my-ns,my-test&involved_object_namespace_search=my&involved_object_name_filter=my-involvedObject,demo-involvedObject&involved_object_name_search=involvedObject&involved_object_kind_filter=involvedObject.kind&reason_filter=reason.filter&reason_search=reason&message_search=message&type_filter=Normal&interval=15m&start_time=1136214245&end_time=1136214245&from=0&sort=desc"
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost/tenant.kubesphere.io/v2alpha1/events?%s", queryParam), nil)
if err != nil {
t.Fatal(err)
}
expected := Query{
Operation: "query",
WorkspaceFilter: "my-ws,demo-ws",
WorkspaceSearch: "my,demo",
InvolvedObjectNamespaceFilter: "my-ns,my-test",
InvolvedObjectNamespaceSearch: "my",
InvolvedObjectNameFilter: "my-involvedObject,demo-involvedObject",
InvolvedObjectNameSearch: "involvedObject",
InvolvedObjectKindFilter: "involvedObject.kind",
ReasonFilter: "reason.filter",
ReasonSearch: "reason",
MessageSearch: "message",
TypeFilter: "Normal",
StartTime: time.Unix(1136214245, 0),
EndTime: time.Unix(1136214245, 0),
Interval: "15m",
Sort: "desc",
From: int64(0),
Size: int64(10),
}
request := restful.NewRequest(req)
actual, err := ParseQueryParameter(request)
assert.NoError(t, err)
assert.Equal(t, &expected, actual)
}

View File

@@ -1,117 +0,0 @@
/*
Copyright 2020 KubeSphere Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha2
import (
"strconv"
"time"
"github.com/emicklei/go-restful/v3"
"kubesphere.io/kubesphere/pkg/simple/client/logging"
)
const (
OperationStatistics = "statistics"
OperationHistogram = "histogram"
OperationQuery = "query"
OperationExport = "export"
DefaultInterval = "15m"
DefaultSize = 10
OrderAscending = "asc"
OrderDescending = "desc"
)
type APIResponse struct {
Logs *logging.Logs `json:"query,omitempty" description:"query results"`
Statistics *logging.Statistics `json:"statistics,omitempty" description:"statistics results"`
Histogram *logging.Histogram `json:"histogram,omitempty" description:"histogram results"`
}
type Query struct {
Operation string
NamespaceFilter string
NamespaceSearch string
WorkloadFilter string
WorkloadSearch string
PodFilter string
PodSearch string
ContainerFilter string
ContainerSearch string
LogSearch string
StartTime time.Time
EndTime time.Time
Interval string
Sort string
From int64
Size int64
}
func ParseQueryParameter(req *restful.Request) (*Query, error) {
var q Query
q.Operation = req.QueryParameter("operation")
q.NamespaceFilter = req.QueryParameter("namespaces")
q.NamespaceSearch = req.QueryParameter("namespace_query")
q.WorkloadFilter = req.QueryParameter("workloads")
q.WorkloadSearch = req.QueryParameter("workload_query")
q.PodFilter = req.QueryParameter("pods")
q.PodSearch = req.QueryParameter("pod_query")
q.ContainerFilter = req.QueryParameter("containers")
q.ContainerSearch = req.QueryParameter("container_query")
q.LogSearch = req.QueryParameter("log_query")
if q.Operation == "" {
q.Operation = OperationQuery
}
if tstr := req.QueryParameter("start_time"); tstr != "" {
sec, err := strconv.ParseInt(tstr, 10, 64)
if err != nil {
return nil, err
}
q.StartTime = time.Unix(sec, 0)
}
if tstr := req.QueryParameter("end_time"); tstr != "" {
sec, err := strconv.ParseInt(tstr, 10, 64)
if err != nil {
return nil, err
}
q.EndTime = time.Unix(sec, 0)
}
switch q.Operation {
case OperationHistogram:
q.Interval = req.QueryParameter("interval")
if q.Interval == "" {
q.Interval = DefaultInterval
}
case OperationQuery:
q.From, _ = strconv.ParseInt(req.QueryParameter("from"), 10, 64)
size, err := strconv.ParseInt(req.QueryParameter("size"), 10, 64)
if err != nil {
size = DefaultSize
}
q.Size = size
q.Sort = req.QueryParameter("sort")
if q.Sort != OrderAscending {
q.Sort = OrderDescending
}
}
return &q, nil
}

View File

@@ -1,77 +0,0 @@
package v1alpha2
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/emicklei/go-restful/v3"
"github.com/stretchr/testify/assert"
)
func TestParseQueryParameter(t *testing.T) {
// default operation -- query
defaultParam := "namespaces=default,my-ns,my-test&namespace_query=default,my&workloads=my-wl,demo-wl&workload_query=wl&pods=my-po,demo-po&pod_query=po&containers=my-cont,demo-cont&container_query=cont&log_query=ERR&start_time=1136214245&end_time=1136214245&from=0&sort=desc"
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost/tenant.kubesphere.io/v2alpha1/logs?%s", defaultParam), nil)
if err != nil {
t.Fatal(err)
}
expected := Query{
Operation: OperationQuery,
NamespaceFilter: "default,my-ns,my-test",
NamespaceSearch: "default,my",
WorkloadFilter: "my-wl,demo-wl",
WorkloadSearch: "wl",
PodFilter: "my-po,demo-po",
PodSearch: "po",
ContainerFilter: "my-cont,demo-cont",
ContainerSearch: "cont",
LogSearch: "ERR",
StartTime: time.Unix(1136214245, 0),
EndTime: time.Unix(1136214245, 0),
Sort: OrderDescending,
From: int64(0),
Size: int64(10),
}
request := restful.NewRequest(req)
actual, err := ParseQueryParameter(request)
assert.NoError(t, err)
assert.Equal(t, &expected, actual)
// histogram operation
queryParamInterval := "operation=histogram&interval=15m&" + defaultParam
reqInterval, err := http.NewRequest("GET", fmt.Sprintf("http://localhost/tenant.kubesphere.io/v2alpha1/logs?%s", queryParamInterval), nil)
if err != nil {
t.Fatal(err)
}
expected = Query{
Operation: OperationHistogram,
NamespaceFilter: "default,my-ns,my-test",
NamespaceSearch: "default,my",
WorkloadFilter: "my-wl,demo-wl",
WorkloadSearch: "wl",
PodFilter: "my-po,demo-po",
PodSearch: "po",
ContainerFilter: "my-cont,demo-cont",
ContainerSearch: "cont",
LogSearch: "ERR",
StartTime: time.Unix(1136214245, 0),
EndTime: time.Unix(1136214245, 0),
Interval: DefaultInterval,
From: int64(0),
Size: int64(0),
}
requestInterval := restful.NewRequest(reqInterval)
actual, err = ParseQueryParameter(requestInterval)
assert.NoError(t, err)
assert.Equal(t, &expected, actual)
}

View File

@@ -1,105 +0,0 @@
// Copyright 2022 The KubeSphere Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1alpha1
import (
"time"
"github.com/emicklei/go-restful/v3"
"kubesphere.io/kubesphere/pkg/apiserver/query"
model "kubesphere.io/kubesphere/pkg/models/monitoring"
"kubesphere.io/kubesphere/pkg/simple/client/monitoring"
)
const (
DefaultStep = 10 * time.Minute
DefaultFilter = ".*"
DefaultOrder = model.OrderDescending
DefaultPage = 1
DefaultLimit = 5
ErrNoHit = "'end' or 'time' must be after the namespace creation time."
ErrParamConflict = "'time' and the combination of 'start' and 'end' are mutually exclusive."
ErrInvalidStartEnd = "'start' must be before 'end'."
ErrInvalidPage = "Invalid parameter 'page'."
ErrInvalidLimit = "Invalid parameter 'limit'."
ErrParameterNotfound = "Parmameter [%s] not found"
ErrResourceNotfound = "resource not found"
ErrScopeNotAllowed = "scope [%s] not allowed"
)
type Query struct {
Level monitoring.Level
Operation string
LabelSelector string
Time string
Start string
End string
Step string
Target string
Order string
Page string
Limit string
MetricFilter string
ResourceFilter string
NodeName string
WorkspaceName string
NamespaceName string
WorkloadKind string
WorkloadName string
PodName string
Applications string
Services string
StorageClassName string
PVCFilter string
Cluster string
}
func ParseQueryParameter(req *restful.Request) *Query {
var q Query
q.LabelSelector = req.QueryParameter(query.ParameterLabelSelector)
q.Level = monitoring.Level(monitoring.MeteringLevelMap[req.QueryParameter("level")])
q.Operation = req.QueryParameter("operation")
q.Time = req.QueryParameter("time")
q.Start = req.QueryParameter("start")
q.End = req.QueryParameter("end")
q.Step = req.QueryParameter("step")
q.Target = req.QueryParameter("sort_metric")
q.Order = req.QueryParameter("sort_type")
q.Page = req.QueryParameter("page")
q.Limit = req.QueryParameter("limit")
q.MetricFilter = req.QueryParameter("metrics_filter")
q.ResourceFilter = req.QueryParameter("resources_filter")
q.WorkspaceName = req.QueryParameter("workspace")
q.NamespaceName = req.QueryParameter("namespace")
if q.NamespaceName == "" {
q.NamespaceName = req.PathParameter("namespace")
}
q.NodeName = req.QueryParameter("node")
q.WorkloadKind = req.QueryParameter("kind")
q.WorkloadName = req.QueryParameter("workload")
q.PodName = req.QueryParameter("pod")
q.Applications = req.QueryParameter("applications")
q.Services = req.QueryParameter("services")
q.StorageClassName = req.QueryParameter("storageclass")
q.PVCFilter = req.QueryParameter("pvc_filter")
q.Cluster = req.QueryParameter("cluster")
return &q
}

View File

@@ -1,51 +0,0 @@
package v1alpha1
import (
"fmt"
"net/http"
"testing"
"github.com/emicklei/go-restful/v3"
"github.com/stretchr/testify/assert"
"kubesphere.io/kubesphere/pkg/simple/client/monitoring"
)
func TestParseQueryParameter(t *testing.T) {
queryParam := "level=LevelCluster&operation=query&time=1559347600&start=1559347200&end=1561939200&step=10m&sort_metric=meter_workspace_cpu_usage&sort_type=desc&page=1&limit=5&metrics_filter=meter_workspace_cpu_usage|meter_workspace_memory_usage&resources_filter=cpu&workspace=my-ws&namespace=my-ns&node=my-node&kind=deployment&workload=my-wl&pod=my-pod&applications=nignx&services=my-svc&storageclass=nfs&pvc_filter=my-pvc&cluster=my-cluster"
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost/tenant.kubesphere.io/v2alpha1/metering?%s", queryParam), nil)
if err != nil {
t.Fatal(err)
}
expected := Query{
Level: monitoring.Level(1),
Operation: "query",
LabelSelector: "",
Time: "1559347600",
Start: "1559347200",
End: "1561939200",
Step: "10m",
Target: "meter_workspace_cpu_usage",
Order: "desc",
Page: "1",
Limit: "5",
MetricFilter: "meter_workspace_cpu_usage|meter_workspace_memory_usage",
ResourceFilter: "cpu",
NodeName: "my-node",
WorkspaceName: "my-ws",
NamespaceName: "my-ns",
WorkloadKind: "deployment",
WorkloadName: "my-wl",
PodName: "my-pod",
Applications: "nignx",
Services: "my-svc",
StorageClassName: "nfs",
PVCFilter: "my-pvc",
Cluster: "my-cluster",
}
request := restful.NewRequest(req)
actual := ParseQueryParameter(request)
assert.Equal(t, &expected, actual)
}

View File

@@ -1,18 +1,7 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package v1alpha2

View File

@@ -1,29 +1,18 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package api
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime"
)
type ListResult struct {
Items []interface{} `json:"items"`
TotalItems int `json:"totalItems"`
Items []runtime.Object `json:"items"`
TotalItems int `json:"totalItems"`
}
type ResourceQuota struct {
@@ -65,114 +54,25 @@ type Workloads struct {
Items map[string]interface{} `json:"items,omitempty" description:"unhealthy workloads"`
}
type ClientType string
const (
ClientKubernetes ClientType = "Kubernetes"
ClientKubeSphere ClientType = "Kubesphere"
ClientIstio ClientType = "Istio"
ClientS2i ClientType = "S2i"
ClientApplication ClientType = "Application"
StatusOK = "ok"
)
var SupportedGroupVersionResources = map[ClientType][]schema.GroupVersionResource{
// all supported kubernetes api objects
ClientKubernetes: {
{Group: "", Version: "v1", Resource: "namespaces"},
{Group: "", Version: "v1", Resource: "nodes"},
{Group: "", Version: "v1", Resource: "resourcequotas"},
{Group: "", Version: "v1", Resource: "pods"},
{Group: "", Version: "v1", Resource: "services"},
{Group: "", Version: "v1", Resource: "persistentvolumeclaims"},
{Group: "", Version: "v1", Resource: "secrets"},
{Group: "", Version: "v1", Resource: "configmaps"},
{Group: "", Version: "v1", Resource: "serviceaccounts"},
{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "roles"},
{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebindings"},
{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterroles"},
{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrolebindings"},
{Group: "apps", Version: "v1", Resource: "deployments"},
{Group: "apps", Version: "v1", Resource: "daemonsets"},
{Group: "apps", Version: "v1", Resource: "replicasets"},
{Group: "apps", Version: "v1", Resource: "statefulsets"},
{Group: "apps", Version: "v1", Resource: "controllerrevisions"},
{Group: "storage.k8s.io", Version: "v1", Resource: "storageclasses"},
{Group: "batch", Version: "v1", Resource: "jobs"},
{Group: "batch", Version: "v1", Resource: "cronjobs"},
{Group: "networking.k8s.io", Version: "v1", Resource: "ingresses"},
{Group: "autoscaling", Version: "v2", Resource: "horizontalpodautoscalers"},
},
// all supported kubesphere api objects
ClientKubeSphere: {
{Group: "tenant.kubesphere.io", Version: "v1alpha1", Resource: "workspaces"},
{Group: "devops.kubesphere.io", Version: "v1alpha1", Resource: "s2ibinaries"},
{Group: "servicemesh.kubesphere.io", Version: "v1alpha2", Resource: "strategies"},
{Group: "servicemesh.kubesphere.io", Version: "v1alpha2", Resource: "servicepolicies"},
},
// all supported istio api objects
ClientIstio: {},
// all supported s2i api objects
// TODO: move s2i clientset into kubesphere
ClientS2i: {
{Group: "devops.kubesphere.io", Version: "v1alpha1", Resource: "s2ibuildertemplates"},
{Group: "devops.kubesphere.io", Version: "v1alpha1", Resource: "s2iruns"},
{Group: "devops.kubesphere.io", Version: "v1alpha1", Resource: "s2ibuilders"},
},
// kubernetes-sigs application api objects
ClientApplication: {
{Group: "app.k8s.io", Version: "v1beta1", Resource: "applications"},
},
}
// List of all resource kinds supported by the UI.
const (
ResourceKindConfigMap = "configmaps"
ResourceKindDaemonSet = "daemonsets"
ResourceKindDeployment = "deployments"
ResourceKindEvent = "events"
ResourceKindHorizontalPodAutoscaler = "horizontalpodautoscalers"
ResourceKindIngress = "ingresses"
ResourceKindJob = "jobs"
ResourceKindCronJob = "cronjobs"
ResourceKindLimitRange = "limitranges"
ResourceKindNamespace = "namespaces"
ResourceKindNode = "nodes"
ResourceKindPersistentVolumeClaim = "persistentvolumeclaims"
ResourceKindPersistentVolume = "persistentvolumes"
ResourceKindCustomResourceDefinition = "customresourcedefinitions"
ResourceKindPod = "pods"
ResourceKindReplicaSet = "replicasets"
ResourceKindResourceQuota = "resourcequota"
ResourceKindSecret = "secrets"
ResourceKindService = "services"
ResourceKindStatefulSet = "statefulsets"
ResourceKindStorageClass = "storageclasses"
ResourceKindClusterRole = "clusterroles"
ResourceKindClusterRoleBinding = "clusterrolebindings"
ResourceKindRole = "roles"
ResourceKindRoleBinding = "rolebindings"
ResourceKindWorkspace = "workspaces"
ResourceKindS2iBinary = "s2ibinaries"
ResourceKindStrategy = "strategy"
ResourceKindServicePolicy = "servicepolicies"
ResourceKindS2iBuilderTemplate = "s2ibuildertemplates"
ResourceKindeS2iRun = "s2iruns"
ResourceKindS2iBuilder = "s2ibuilders"
ResourceKindApplication = "applications"
WorkspaceNone = ""
ClusterNone = ""
ResourceKindDaemonSet = "daemonsets"
ResourceKindDeployment = "deployments"
ResourceKindJob = "jobs"
ResourceKindPersistentVolumeClaim = "persistentvolumeclaims"
ResourceKindStatefulSet = "statefulsets"
StatusOK = "ok"
WorkspaceNone = ""
ClusterNone = ""
TagNonResourceAPI = "NonResource APIs"
TagAuthentication = "Authentication"
TagMultiCluster = "Multi-cluster"
TagIdentityManagement = "Identity Management"
TagAccessManagement = "Access Management"
TagAdvancedOperations = "Advanced Operations"
TagTerminal = "Web Terminal"
TagNamespacedResources = "Namespaced Resources"
TagClusterResources = "Cluster Resources"
TagComponentStatus = "Component Status"
TagUserRelatedResources = "User Related Resources"
TagPlatformConfigurations = "Platform Configurations"
)

View File

@@ -1,18 +1,7 @@
/*
Copyright 2020 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package api