未验证 提交 1f4d5cb6 编写于 作者: K KubeSphere CI Bot 提交者: GitHub

Merge pull request #3181 from junotx/custom-alerting

feature: custom alerting
......@@ -159,7 +159,8 @@ func run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{})
kubernetesClient.KubeSphere(),
kubernetesClient.Istio(),
kubernetesClient.Snapshot(),
kubernetesClient.ApiExtensions())
kubernetesClient.ApiExtensions(),
kubernetesClient.Prometheus())
mgrOptions := manager.Options{
CertDir: s.WebhookCertDir,
......
......@@ -28,6 +28,7 @@ import (
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme"
"kubesphere.io/kubesphere/pkg/informers"
genericoptions "kubesphere.io/kubesphere/pkg/server/options"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
auditingclient "kubesphere.io/kubesphere/pkg/simple/client/auditing/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
runtimecache "sigs.k8s.io/controller-runtime/pkg/cache"
......@@ -82,6 +83,7 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
s.MultiClusterOptions.AddFlags(fss.FlagSet("multicluster"), s.MultiClusterOptions)
s.EventsOptions.AddFlags(fss.FlagSet("events"), s.EventsOptions)
s.AuditingOptions.AddFlags(fss.FlagSet("auditing"), s.AuditingOptions)
s.AlertingOptions.AddFlags(fss.FlagSet("alerting"), s.AlertingOptions)
fs = fss.FlagSet("klog")
local := flag.NewFlagSet("klog", flag.ExitOnError)
......@@ -109,7 +111,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
apiServer.KubernetesClient = kubernetesClient
informerFactory := informers.NewInformerFactories(kubernetesClient.Kubernetes(), kubernetesClient.KubeSphere(),
kubernetesClient.Istio(), kubernetesClient.Snapshot(), kubernetesClient.ApiExtensions())
kubernetesClient.Istio(), kubernetesClient.Snapshot(), kubernetesClient.ApiExtensions(), kubernetesClient.Prometheus())
apiServer.InformerFactory = informerFactory
if s.MonitoringOptions == nil || len(s.MonitoringOptions.Endpoint) == 0 {
......@@ -199,6 +201,14 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
apiServer.OpenpitrixClient = opClient
}
if s.AlertingOptions != nil && (s.AlertingOptions.PrometheusEndpoint != "" || s.AlertingOptions.ThanosRulerEndpoint != "") {
alertingClient, err := alerting.NewRuleClient(s.AlertingOptions)
if err != nil {
return nil, fmt.Errorf("failed to init alerting client: %v", err)
}
apiServer.AlertingClient = alertingClient
}
server := &http.Server{
Addr: fmt.Sprintf(":%d", s.GenericServerRunOptions.InsecurePort),
}
......
......@@ -34,6 +34,7 @@ func (s *ServerRunOptions) Validate() []error {
errors = append(errors, s.AuthorizationOptions.Validate()...)
errors = append(errors, s.EventsOptions.Validate()...)
errors = append(errors, s.AuditingOptions.Validate()...)
errors = append(errors, s.AlertingOptions.Validate()...)
return errors
}
......@@ -8,10 +8,9 @@ go 1.13
require (
code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/PuerkitoBio/goquery v1.5.0
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496
github.com/aws/aws-sdk-go v1.30.12
github.com/aws/aws-sdk-go v1.33.12
github.com/beevik/etree v1.1.0
github.com/container-storage-interface/spec v1.2.0
github.com/containernetworking/cni v0.8.0
......@@ -26,6 +25,7 @@ require (
github.com/emicklei/go-restful-openapi v1.4.1
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/structs v1.1.0
github.com/ghodss/yaml v1.0.0
github.com/go-ldap/ldap v3.0.3+incompatible
github.com/go-logr/logr v0.1.0
github.com/go-logr/zapr v0.1.1 // indirect
......@@ -39,15 +39,13 @@ require (
github.com/golang/example v0.0.0-20170904185048-46695d81d1fa
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/google/go-cmp v0.4.0
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/go-cmp v0.5.0
github.com/google/uuid v1.1.1
github.com/gorilla/websocket v1.4.1
github.com/json-iterator/go v1.1.10
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/kubernetes-csi/external-snapshotter/client/v3 v3.0.0
github.com/kubesphere/sonargo v0.0.2
github.com/lib/pq v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.2.2
github.com/onsi/ginkgo v1.14.0
github.com/onsi/gomega v1.10.1
......@@ -58,9 +56,11 @@ require (
github.com/projectcalico/kube-controllers v3.8.8+incompatible
github.com/projectcalico/libcalico-go v1.7.2-0.20191104213956-8f81e1e344ce
github.com/prometheus-community/prom-label-proxy v0.2.0
github.com/prometheus-operator/prometheus-operator v0.42.2-0.20200928114327-fbd01683839a
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.42.1
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/common v0.10.0
github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1
github.com/prometheus/common v0.11.1
github.com/prometheus/prometheus v1.8.2-0.20200907175821-8219b442c864
github.com/sony/sonyflake v1.0.0
github.com/speps/go-hashids v2.0.0+incompatible
github.com/spf13/cobra v1.0.0
......@@ -68,15 +68,15 @@ require (
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.6.1
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20200422194213-44a606286825
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/grpc v1.29.0
google.golang.org/grpc v1.30.0
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect
gopkg.in/src-d/go-git.v4 v4.11.0
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
gotest.tools v2.2.0+incompatible
istio.io/api v0.0.0-20201113182140-d4b7e3fc2b44
istio.io/client-go v0.0.0-20201113183938-0734e976e785
......@@ -86,7 +86,7 @@ require (
k8s.io/apimachinery v0.19.0
k8s.io/apiserver v0.18.6
k8s.io/cli-runtime v0.18.6
k8s.io/client-go v0.19.0
k8s.io/client-go v12.0.0+incompatible
k8s.io/code-generator v0.19.0
k8s.io/component-base v0.18.6
k8s.io/klog v1.0.0
......@@ -494,42 +494,37 @@ replace (
github.com/rcrowley/go-metrics => github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a
github.com/retailnext/hllpp => github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52
github.com/robfig/cron => github.com/robfig/cron v1.2.0
github.com/rogpeppe/fastuuid => github.com/rogpeppe/fastuuid v1.2.0
github.com/rogpeppe/fastuuid => github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af
github.com/rogpeppe/go-charset => github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4
github.com/rogpeppe/go-internal => github.com/rogpeppe/go-internal v1.3.0
github.com/rs/cors => github.com/rs/cors v1.6.0
github.com/russross/blackfriday => github.com/russross/blackfriday v1.5.2
github.com/russross/blackfriday/v2 => github.com/russross/blackfriday/v2 v2.0.1
github.com/ryanuber/columnize => github.com/ryanuber/columnize v2.1.0+incompatible
github.com/samuel/go-zookeeper => github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da
github.com/satori/go.uuid => github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
github.com/satori/go.uuid => github.com/satori/go.uuid v1.2.0
github.com/sean-/seed => github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529
github.com/segmentio/kafka-go => github.com/segmentio/kafka-go v0.2.0
github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0
github.com/shirou/gopsutil => github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7
github.com/shirou/w32 => github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4
github.com/shurcooL/httpfs => github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/sanitized_anchor_name => github.com/shurcooL/sanitized_anchor_name v1.0.0
github.com/shurcooL/vfsgen => github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/sirupsen/logrus => github.com/sirupsen/logrus v1.4.2
github.com/smartystreets/assertions => github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d
github.com/smartystreets/goconvey => github.com/smartystreets/goconvey v1.6.4
github.com/soheilhy/cmux => github.com/soheilhy/cmux v0.1.4
github.com/sony/gobreaker => github.com/sony/gobreaker v0.4.1
github.com/sony/sonyflake => github.com/sony/sonyflake v1.0.0
github.com/sony/sonyflake => github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009
github.com/spaolacci/murmur3 => github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72
github.com/speps/go-hashids => github.com/speps/go-hashids v2.0.0+incompatible
github.com/spf13/afero => github.com/spf13/afero v1.2.2
github.com/spf13/cast => github.com/spf13/cast v1.3.0
github.com/spf13/cobra => github.com/spf13/cobra v1.0.0
github.com/spf13/cobra => github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman => github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/pflag => github.com/spf13/pflag v1.0.5
github.com/spf13/viper => github.com/spf13/viper v1.4.0
github.com/src-d/gcfg => github.com/src-d/gcfg v1.4.0
github.com/streadway/amqp => github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271
github.com/streadway/handy => github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a
github.com/stretchr/objx => github.com/stretchr/objx v0.2.0
github.com/stretchr/testify => github.com/stretchr/testify v1.6.1
github.com/stretchr/testify => github.com/stretchr/testify v1.4.0
github.com/tidwall/pretty => github.com/tidwall/pretty v1.0.0
github.com/tinylib/msgp => github.com/tinylib/msgp v1.1.0
github.com/tmc/grpc-websocket-proxy => github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5
......@@ -538,8 +533,7 @@ replace (
github.com/uber/jaeger-lib => github.com/uber/jaeger-lib v2.2.0+incompatible
github.com/ugorji/go => github.com/ugorji/go v1.1.4
github.com/ugorji/go/codec => github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0
github.com/urfave/cli => github.com/urfave/cli v1.22.1
github.com/vektah/gqlparser => github.com/vektah/gqlparser v1.1.2
github.com/urfave/cli => github.com/urfave/cli v1.20.0
github.com/willf/bitset => github.com/willf/bitset v1.1.3
github.com/xanzy/ssh-agent => github.com/xanzy/ssh-agent v0.2.1
github.com/xdg/scram => github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
......
此差异已折叠。
/*
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 (
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/emicklei/go-restful"
"github.com/pkg/errors"
prommodel "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/promql/parser"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
const (
RuleLevelCluster RuleLevel = "cluster"
RuleLevelNamespace RuleLevel = "namespace"
)
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")
ruleLabelNameMatcher = regexp.MustCompile(`[a-zA-Z_][a-zA-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"))
}
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))
}
}
if len(r.Labels) > 0 {
for name, _ := range r.Labels {
if !ruleLabelNameMatcher.MatchString(name) || 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))
}
}
}
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
Offset 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 != "" && !strings.Contains(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 || !strings.Contains(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) {
idCompare := func(left, right *GettableAlertingRule) bool {
return AlertingRuleIdCompare(left.Id, right.Id)
}
var compare = idCompare
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 idCompare(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 idCompare(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 idCompare(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.Offset, q.Offset+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
Offset 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 || !strings.Contains(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.Offset, q.Offset+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.Offset, _ = strconv.Atoi(req.QueryParameter("offset"))
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.Offset, _ = strconv.Atoi(req.QueryParameter("offset"))
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
}
......@@ -49,6 +49,7 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/informers"
alertingv1 "kubesphere.io/kubesphere/pkg/kapis/alerting/v1"
alertingv2alpha1 "kubesphere.io/kubesphere/pkg/kapis/alerting/v2alpha1"
clusterkapisv1alpha1 "kubesphere.io/kubesphere/pkg/kapis/cluster/v1alpha1"
configv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/config/v1alpha2"
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2"
......@@ -72,6 +73,7 @@ import (
"kubesphere.io/kubesphere/pkg/models/iam/im"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/loginrecord"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/user"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
"kubesphere.io/kubesphere/pkg/simple/client/auditing"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/devops"
......@@ -146,6 +148,8 @@ type APIServer struct {
AuditingClient auditing.Client
AlertingClient alerting.RuleClient
// controller-runtime cache
RuntimeCache runtimecache.Cache
}
......@@ -255,6 +259,8 @@ func (s *APIServer) installKubeSphereAPIs() {
s.InformerFactory.KubernetesSharedInformerFactory()))
urlruntime.Must(notificationv1.AddToContainer(s.container, s.Config.NotificationOptions.Endpoint))
urlruntime.Must(alertingv1.AddToContainer(s.container, s.Config.AlertingOptions.Endpoint))
urlruntime.Must(alertingv2alpha1.AddToContainer(s.container, s.InformerFactory,
s.KubernetesClient.Prometheus(), s.AlertingClient, s.Config.AlertingOptions))
urlruntime.Must(version.AddToContainer(s.container, s.KubernetesClient.Discovery()))
}
......@@ -524,6 +530,26 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
apiextensionsInformerFactory.Start(stopCh)
apiextensionsInformerFactory.WaitForCacheSync(stopCh)
if promFactory := s.InformerFactory.PrometheusSharedInformerFactory(); promFactory != nil {
prometheusGVRs := []schema.GroupVersionResource{
{Group: "monitoring.coreos.com", Version: "v1", Resource: "prometheuses"},
{Group: "monitoring.coreos.com", Version: "v1", Resource: "prometheusrules"},
{Group: "monitoring.coreos.com", Version: "v1", Resource: "thanosrulers"},
}
for _, gvr := range prometheusGVRs {
if isResourceExists(gvr) {
_, err = promFactory.ForResource(gvr)
if err != nil {
return err
}
} else {
klog.Warningf("resource %s not exists in the cluster", gvr)
}
}
promFactory.Start(stopCh)
promFactory.WaitForCacheSync(stopCh)
}
// controller runtime cache for resources
go s.RuntimeCache.Start(stopCh)
s.RuntimeCache.WaitForCacheSync(stopCh)
......
......@@ -56,7 +56,7 @@ func TestGetAuditLevel(t *testing.T) {
ksClient := fake.NewSimpleClientset()
k8sClient := fakek8s.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
a := auditing{
webhookLister: fakeInformerFactory.KubeSphereSharedInformerFactory().Auditing().V1alpha1().Webhooks().Lister(),
......@@ -85,7 +85,7 @@ func TestAuditing_Enabled(t *testing.T) {
ksClient := fake.NewSimpleClientset()
k8sClient := fakek8s.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
a := auditing{
webhookLister: fakeInformerFactory.KubeSphereSharedInformerFactory().Auditing().V1alpha1().Webhooks().Lister(),
......@@ -115,7 +115,7 @@ func TestAuditing_K8sAuditingEnabled(t *testing.T) {
ksClient := fake.NewSimpleClientset()
k8sClient := fakek8s.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
a := auditing{
webhookLister: fakeInformerFactory.KubeSphereSharedInformerFactory().Auditing().V1alpha1().Webhooks().Lister(),
......@@ -145,7 +145,7 @@ func TestAuditing_LogRequestObject(t *testing.T) {
ksClient := fake.NewSimpleClientset()
k8sClient := fakek8s.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
a := auditing{
webhookLister: fakeInformerFactory.KubeSphereSharedInformerFactory().Auditing().V1alpha1().Webhooks().Lister(),
......@@ -236,7 +236,7 @@ func TestAuditing_LogResponseObject(t *testing.T) {
ksClient := fake.NewSimpleClientset()
k8sClient := fakek8s.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
a := auditing{
webhookLister: fakeInformerFactory.KubeSphereSharedInformerFactory().Auditing().V1alpha1().Webhooks().Lister(),
......
......@@ -857,7 +857,7 @@ func newMockRBACAuthorizer(staticRoles *StaticRoles) (*RBACAuthorizer, error) {
ksClient := fakeks.NewSimpleClientset()
k8sClient := fakek8s.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
k8sInformerFactory := fakeInformerFactory.KubernetesSharedInformerFactory()
ksInformerFactory := fakeInformerFactory.KubeSphereSharedInformerFactory()
......
......@@ -247,7 +247,8 @@ func (conf *Config) stripEmptyOptions() {
conf.S3Options = nil
}
if conf.AlertingOptions != nil && conf.AlertingOptions.Endpoint == "" {
if conf.AlertingOptions != nil && conf.AlertingOptions.Endpoint == "" &&
conf.AlertingOptions.PrometheusEndpoint == "" && conf.AlertingOptions.ThanosRulerEndpoint == "" {
conf.AlertingOptions = nil
}
......
......@@ -119,6 +119,10 @@ func newTestConfig() (*Config, error) {
},
AlertingOptions: &alerting.Options{
Endpoint: "http://alerting-client-server.kubesphere-alerting-system.svc:9200/api",
PrometheusEndpoint: "http://prometheus-operated.kubesphere-monitoring-system.svc",
ThanosRulerEndpoint: "http://thanos-ruler-operated.kubesphere-monitoring-system.svc",
ThanosRuleResourceLabels: "thanosruler=thanos-ruler,role=thanos-alerting-rules",
},
NotificationOptions: &notification.Options{
Endpoint: "http://notification.kubesphere-alerting-system.svc:9200",
......
......@@ -104,6 +104,8 @@ const (
LogQueryTag = "Log Query"
EventsQueryTag = "Events Query"
AuditingQueryTag = "Auditing Query"
AlertingTag = "Alerting"
)
var (
......
......@@ -19,6 +19,8 @@ package informers
import (
snapshotclient "github.com/kubernetes-csi/external-snapshotter/client/v3/clientset/versioned"
snapshotinformer "github.com/kubernetes-csi/external-snapshotter/client/v3/informers/externalversions"
prominformers "github.com/prometheus-operator/prometheus-operator/pkg/client/informers/externalversions"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
istioclient "istio.io/client-go/pkg/clientset/versioned"
istioinformers "istio.io/client-go/pkg/informers/externalversions"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
......@@ -41,6 +43,7 @@ type InformerFactory interface {
IstioSharedInformerFactory() istioinformers.SharedInformerFactory
SnapshotSharedInformerFactory() snapshotinformer.SharedInformerFactory
ApiExtensionSharedInformerFactory() apiextensionsinformers.SharedInformerFactory
PrometheusSharedInformerFactory() prominformers.SharedInformerFactory
// Start shared informer factory one by one if they are not nil
Start(stopCh <-chan struct{})
......@@ -52,10 +55,12 @@ type informerFactories struct {
istioInformerFactory istioinformers.SharedInformerFactory
snapshotInformerFactory snapshotinformer.SharedInformerFactory
apiextensionsInformerFactory apiextensionsinformers.SharedInformerFactory
prometheusInformerFactory prominformers.SharedInformerFactory
}
func NewInformerFactories(client kubernetes.Interface, ksClient versioned.Interface, istioClient istioclient.Interface,
snapshotClient snapshotclient.Interface, apiextensionsClient apiextensionsclient.Interface) InformerFactory {
snapshotClient snapshotclient.Interface, apiextensionsClient apiextensionsclient.Interface,
prometheusClient promresourcesclient.Interface) InformerFactory {
factory := &informerFactories{}
if client != nil {
......@@ -78,6 +83,10 @@ func NewInformerFactories(client kubernetes.Interface, ksClient versioned.Interf
factory.apiextensionsInformerFactory = apiextensionsinformers.NewSharedInformerFactory(apiextensionsClient, defaultResync)
}
if prometheusClient != nil {
factory.prometheusInformerFactory = prominformers.NewSharedInformerFactory(prometheusClient, defaultResync)
}
return factory
}
......@@ -101,6 +110,10 @@ func (f *informerFactories) ApiExtensionSharedInformerFactory() apiextensionsinf
return f.apiextensionsInformerFactory
}
func (f *informerFactories) PrometheusSharedInformerFactory() prominformers.SharedInformerFactory {
return f.prometheusInformerFactory
}
func (f *informerFactories) Start(stopCh <-chan struct{}) {
if f.informerFactory != nil {
f.informerFactory.Start(stopCh)
......@@ -121,4 +134,8 @@ func (f *informerFactories) Start(stopCh <-chan struct{}) {
if f.apiextensionsInformerFactory != nil {
f.apiextensionsInformerFactory.Start(stopCh)
}
if f.prometheusInformerFactory != nil {
f.prometheusInformerFactory.Start(stopCh)
}
}
......@@ -17,19 +17,23 @@ limitations under the License.
package informers
import (
"time"
snapshotinformer "github.com/kubernetes-csi/external-snapshotter/client/v3/informers/externalversions"
prominformers "github.com/prometheus-operator/prometheus-operator/pkg/client/informers/externalversions"
promfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake"
istioinformers "istio.io/client-go/pkg/informers/externalversions"
apiextensionsinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
ksfake "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"time"
)
type nullInformerFactory struct {
fakeK8sInformerFactory informers.SharedInformerFactory
fakeKsInformerFactory ksinformers.SharedInformerFactory
fakePrometheusFactory prominformers.SharedInformerFactory
}
func NewNullInformerFactory() InformerFactory {
......@@ -39,9 +43,13 @@ func NewNullInformerFactory() InformerFactory {
fakeKsClient := ksfake.NewSimpleClientset()
fakeKsInformerFactory := ksinformers.NewSharedInformerFactory(fakeKsClient, time.Minute*10)
fakePrometheusClient := promfake.NewSimpleClientset()
fakePrometheusFactory := prominformers.NewSharedInformerFactory(fakePrometheusClient, time.Minute*10)
return &nullInformerFactory{
fakeK8sInformerFactory: fakeInformerFactory,
fakeKsInformerFactory: fakeKsInformerFactory,
fakePrometheusFactory: fakePrometheusFactory,
}
}
......@@ -65,5 +73,9 @@ func (n nullInformerFactory) ApiExtensionSharedInformerFactory() apiextensionsin
return nil
}
func (n *nullInformerFactory) PrometheusSharedInformerFactory() prominformers.SharedInformerFactory {
return n.fakePrometheusFactory
}
func (n nullInformerFactory) Start(stopCh <-chan struct{}) {
}
/*
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 (
"github.com/emicklei/go-restful"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
"k8s.io/klog"
ksapi "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
"kubesphere.io/kubesphere/pkg/informers"
alertingmodels "kubesphere.io/kubesphere/pkg/models/alerting"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
)
type handler struct {
operator alertingmodels.Operator
}
func newHandler(informers informers.InformerFactory,
promResourceClient promresourcesclient.Interface, ruleClient alerting.RuleClient,
option *alerting.Options) *handler {
return &handler{
operator: alertingmodels.NewOperator(
informers, promResourceClient, ruleClient, option),
}
}
func (h *handler) handleListCustomAlertingRules(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
query, err := v2alpha1.ParseAlertingRuleQueryParams(req)
if err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
rules, err := h.operator.ListCustomAlertingRules(req.Request.Context(), namespace, query)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrThanosRulerNotEnabled:
ksapi.HandleBadRequest(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
resp.WriteEntity(rules)
}
func (h *handler) handleListCustomRulesAlerts(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
query, err := v2alpha1.ParseAlertQueryParams(req)
if err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
alerts, err := h.operator.ListCustomRulesAlerts(req.Request.Context(), namespace, query)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrThanosRulerNotEnabled:
ksapi.HandleBadRequest(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
resp.WriteEntity(alerts)
}
func (h *handler) handleGetCustomAlertingRule(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
ruleName := req.PathParameter("rule_name")
rule, err := h.operator.GetCustomAlertingRule(req.Request.Context(), namespace, ruleName)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrThanosRulerNotEnabled:
ksapi.HandleBadRequest(resp, nil, err)
case err == v2alpha1.ErrAlertingRuleNotFound:
ksapi.HandleNotFound(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
if rule == nil {
ksapi.HandleNotFound(resp, nil, err)
return
}
resp.WriteEntity(rule)
}
func (h *handler) handleListCustomRuleAlerts(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
ruleName := req.PathParameter("rule_name")
alerts, err := h.operator.ListCustomRuleAlerts(req.Request.Context(), namespace, ruleName)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrThanosRulerNotEnabled:
ksapi.HandleBadRequest(resp, nil, err)
case err == v2alpha1.ErrAlertingRuleNotFound:
ksapi.HandleNotFound(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
resp.WriteEntity(alerts)
}
func (h *handler) handleCreateCustomAlertingRule(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
var rule v2alpha1.PostableAlertingRule
if err := req.ReadEntity(&rule); err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
if err := rule.Validate(); err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
err := h.operator.CreateCustomAlertingRule(req.Request.Context(), namespace, &rule)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrThanosRulerNotEnabled:
ksapi.HandleBadRequest(resp, nil, err)
case err == v2alpha1.ErrAlertingRuleAlreadyExists:
ksapi.HandleConflict(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
}
func (h *handler) handleUpdateCustomAlertingRule(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
ruleName := req.PathParameter("rule_name")
var rule v2alpha1.PostableAlertingRule
if err := req.ReadEntity(&rule); err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
if err := rule.Validate(); err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
err := h.operator.UpdateCustomAlertingRule(req.Request.Context(), namespace, ruleName, &rule)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrThanosRulerNotEnabled:
ksapi.HandleBadRequest(resp, nil, err)
case err == v2alpha1.ErrAlertingRuleNotFound:
ksapi.HandleNotFound(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
}
func (h *handler) handleDeleteCustomAlertingRule(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
name := req.PathParameter("rule_name")
err := h.operator.DeleteCustomAlertingRule(req.Request.Context(), namespace, name)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrThanosRulerNotEnabled:
ksapi.HandleBadRequest(resp, nil, err)
case err == v2alpha1.ErrAlertingRuleNotFound:
ksapi.HandleNotFound(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
}
func (h *handler) handleListBuiltinAlertingRules(req *restful.Request, resp *restful.Response) {
query, err := v2alpha1.ParseAlertingRuleQueryParams(req)
if err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
rules, err := h.operator.ListBuiltinAlertingRules(req.Request.Context(), query)
if err != nil {
klog.Error(err)
ksapi.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(rules)
}
func (h *handler) handleListBuiltinRulesAlerts(req *restful.Request, resp *restful.Response) {
query, err := v2alpha1.ParseAlertQueryParams(req)
if err != nil {
klog.Error(err)
ksapi.HandleBadRequest(resp, nil, err)
return
}
alerts, err := h.operator.ListBuiltinRulesAlerts(req.Request.Context(), query)
if err != nil {
klog.Error(err)
ksapi.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(alerts)
}
func (h *handler) handleGetBuiltinAlertingRule(req *restful.Request, resp *restful.Response) {
ruleId := req.PathParameter("rule_id")
rule, err := h.operator.GetBuiltinAlertingRule(req.Request.Context(), ruleId)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrAlertingRuleNotFound:
ksapi.HandleNotFound(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
if rule == nil {
ksapi.HandleNotFound(resp, nil, err)
return
}
resp.WriteEntity(rule)
}
func (h *handler) handleListBuiltinRuleAlerts(req *restful.Request, resp *restful.Response) {
ruleId := req.PathParameter("rule_id")
alerts, err := h.operator.ListBuiltinRuleAlerts(req.Request.Context(), ruleId)
if err != nil {
klog.Error(err)
switch {
case err == v2alpha1.ErrAlertingRuleNotFound:
ksapi.HandleNotFound(resp, nil, err)
default:
ksapi.HandleInternalError(resp, nil, err)
}
return
}
resp.WriteEntity(alerts)
}
/*
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 (
"net/http"
"github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
"k8s.io/apimachinery/pkg/runtime/schema"
ksapi "kubesphere.io/kubesphere/pkg/api"
alertingv2alpha1 "kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
)
const (
groupName = "alerting.kubesphere.io"
)
var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v2alpha1"}
func AddToContainer(container *restful.Container, informers informers.InformerFactory,
promResourceClient promresourcesclient.Interface, ruleClient alerting.RuleClient,
option *alerting.Options) error {
handler := newHandler(informers, promResourceClient, ruleClient, option)
ws := runtime.NewWebService(GroupVersion)
ws.Route(ws.GET("/rules").
To(handler.handleListCustomAlertingRules).
Doc("list the cluster-level custom alerting rules").
Param(ws.QueryParameter("name", "rule name")).
Param(ws.QueryParameter("state", "state of a rule based on its alerts, one of `firing`, `pending`, `inactive`")).
Param(ws.QueryParameter("health", "health state of a rule based on the last execution, one of `ok`, `err`, `unknown`")).
Param(ws.QueryParameter("label_filters", "label filters, concatenating multiple filters with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").DataFormat("key=%s,key~%s")).
Param(ws.QueryParameter("sort_field", "sort field, one of `name`, `lastEvaluation`, `evaluationTime`")).
Param(ws.QueryParameter("sort_type", "sort type, one of `asc`, `desc`")).
Param(ws.QueryParameter("offset", "offset of the result set").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("limit", "limit size of the result set").DataType("integer").DefaultValue("10")).
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.GettableAlertingRuleList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/alerts").
To(handler.handleListCustomRulesAlerts).
Doc("list the alerts of the cluster-level custom alerting rules").
Param(ws.QueryParameter("state", "state, one of `firing`, `pending`, `inactive`")).
Param(ws.QueryParameter("label_filters", "label filters, concatenating multiple filters with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").DataFormat("key=%s,key~%s")).
Param(ws.QueryParameter("offset", "offset of the result set").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("limit", "limit size of the result set").DataType("integer").DefaultValue("10")).
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.AlertList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/rules/{rule_name}").
To(handler.handleGetCustomAlertingRule).
Doc("get the cluster-level custom alerting rule with the specified name").
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.GettableAlertingRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/rules/{rule_name}/alerts").
To(handler.handleListCustomRuleAlerts).
Doc("list the alerts of the cluster-level custom alerting rule with the specified name").
Returns(http.StatusOK, ksapi.StatusOK, []alertingv2alpha1.Alert{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.POST("/rules").
To(handler.handleCreateCustomAlertingRule).
Doc("create a cluster-level custom alerting rule").
Reads(alertingv2alpha1.PostableAlertingRule{}).
Returns(http.StatusOK, ksapi.StatusOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.PUT("/rules/{rule_name}").
To(handler.handleUpdateCustomAlertingRule).
Doc("update the cluster-level custom alerting rule with the specified name").
Reads(alertingv2alpha1.PostableAlertingRule{}).
Returns(http.StatusOK, ksapi.StatusOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.DELETE("/rules/{rule_name}").
To(handler.handleDeleteCustomAlertingRule).
Doc("delete the cluster-level custom alerting rule with the specified name").
Returns(http.StatusOK, ksapi.StatusOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/namespaces/{namespace}/rules").
To(handler.handleListCustomAlertingRules).
Doc("list the custom alerting rules in the specified namespace").
Param(ws.QueryParameter("name", "rule name")).
Param(ws.QueryParameter("state", "state of a rule based on its alerts, one of `firing`, `pending`, `inactive`")).
Param(ws.QueryParameter("health", "health state of a rule based on the last execution, one of `ok`, `err`, `unknown`")).
Param(ws.QueryParameter("label_filters", "label filters, concatenating multiple filters with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").DataFormat("key=%s,key~%s")).
Param(ws.QueryParameter("sort_field", "sort field, one of `name`, `lastEvaluation`, `evaluationTime`")).
Param(ws.QueryParameter("sort_type", "sort type, one of `asc`, `desc`")).
Param(ws.QueryParameter("offset", "offset of the result set").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("limit", "limit size of the result set").DataType("integer").DefaultValue("10")).
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.GettableAlertingRuleList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/namespaces/{namespace}/alerts").
To(handler.handleListCustomRulesAlerts).
Doc("list the alerts of the custom alerting rules in the specified namespace.").
Param(ws.QueryParameter("state", "state, one of `firing`, `pending`, `inactive`")).
Param(ws.QueryParameter("label_filters", "label filters, concatenating multiple filters with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").DataFormat("key=%s,key~%s")).
Param(ws.QueryParameter("offset", "offset of the result set").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("limit", "limit size of the result set").DataType("integer").DefaultValue("10")).
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.AlertList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/namespaces/{namespace}/rules/{rule_name}").
To(handler.handleGetCustomAlertingRule).
Doc("get the custom alerting rule with the specified name in the specified namespace").
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.GettableAlertingRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/namespaces/{namespace}/rules/{rule_name}/alerts").
To(handler.handleListCustomRuleAlerts).
Doc("get the alerts of the custom alerting rule with the specified name in the specified namespace").
Returns(http.StatusOK, ksapi.StatusOK, []alertingv2alpha1.Alert{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.POST("/namespaces/{namespace}/rules").
To(handler.handleCreateCustomAlertingRule).
Doc("create a custom alerting rule in the specified namespace").
Reads(alertingv2alpha1.PostableAlertingRule{}).
Returns(http.StatusOK, ksapi.StatusOK, "").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.PUT("/namespaces/{namespace}/rules/{rule_name}").
To(handler.handleUpdateCustomAlertingRule).
Doc("update the custom alerting rule with the specified name in the specified namespace").
Reads(alertingv2alpha1.PostableAlertingRule{}).
Returns(http.StatusOK, ksapi.StatusOK, "").
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.DELETE("/namespaces/{namespace}/rules/{rule_name}").
To(handler.handleDeleteCustomAlertingRule).
Doc("delete the custom alerting rule with the specified rule name in the specified namespace").
Returns(http.StatusOK, ksapi.StatusOK, nil).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/builtin/rules").
To(handler.handleListBuiltinAlertingRules).
Doc("list the builtin(non-custom) alerting rules").
Param(ws.QueryParameter("name", "rule name")).
Param(ws.QueryParameter("state", "state of a rule based on its alerts, one of `firing`, `pending`, `inactive`")).
Param(ws.QueryParameter("health", "health state of a rule based on the last execution, one of `ok`, `err`, `unknown`")).
Param(ws.QueryParameter("label_filters", "label filters, concatenating multiple filters with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").DataFormat("key=%s,key~%s")).
Param(ws.QueryParameter("sort_field", "sort field, one of `name`, `lastEvaluation`, `evaluationTime`")).
Param(ws.QueryParameter("sort_type", "sort type, one of `asc`, `desc`")).
Param(ws.QueryParameter("offset", "offset of the result set").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("limit", "limit size of the result set").DataType("integer").DefaultValue("10")).
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.GettableAlertingRuleList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/builtin/alerts").
To(handler.handleListBuiltinRulesAlerts).
Doc("list the alerts of the builtin(non-custom) rules").
Param(ws.QueryParameter("state", "state, one of `firing`, `pending`, `inactive`")).
Param(ws.QueryParameter("label_filters", "label filters, concatenating multiple filters with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").DataFormat("key=%s,key~%s")).
Param(ws.QueryParameter("offset", "offset of the result set").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("limit", "limit size of the result set").DataType("integer").DefaultValue("10")).
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.AlertList{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/builtin/rules/{rule_id}").
To(handler.handleGetBuiltinAlertingRule).
Doc("get the builtin(non-custom) alerting rule with specified id").
Returns(http.StatusOK, ksapi.StatusOK, alertingv2alpha1.GettableAlertingRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
ws.Route(ws.GET("/builtin/rules/{rule_id}/alerts").
To(handler.handleListBuiltinRuleAlerts).
Doc("list the alerts of the builtin(non-custom) alerting rule with the specified id").
Returns(http.StatusOK, ksapi.StatusOK, []alertingv2alpha1.Alert{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag}))
container.Add(ws)
return nil
}
......@@ -125,7 +125,7 @@ func TestGeranteAgentDeployment(t *testing.T) {
k8sclient := k8sfake.NewSimpleClientset(service)
ksclient := fake.NewSimpleClientset(cluster)
informersFactory := informers.NewInformerFactories(k8sclient, ksclient, nil, nil, nil)
informersFactory := informers.NewInformerFactories(k8sclient, ksclient, nil, nil, nil, nil)
informersFactory.KubernetesSharedInformerFactory().Core().V1().Services().Informer().GetIndexer().Add(service)
informersFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters().Informer().GetIndexer().Add(cluster)
......@@ -233,7 +233,7 @@ func TestValidateKubeConfig(t *testing.T) {
k8sclient := k8sfake.NewSimpleClientset(service)
ksclient := fake.NewSimpleClientset(cluster)
informersFactory := informers.NewInformerFactories(k8sclient, ksclient, nil, nil, nil)
informersFactory := informers.NewInformerFactories(k8sclient, ksclient, nil, nil, nil, nil)
informersFactory.KubernetesSharedInformerFactory().Core().V1().Services().Informer().GetIndexer().Add(service)
informersFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters().Informer().GetIndexer().Add(cluster)
......
......@@ -216,7 +216,7 @@ func TestParseRequestParams(t *testing.T) {
for i, tt := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
client := fake.NewSimpleClientset(&tt.namespace)
fakeInformerFactory := informers.NewInformerFactories(client, nil, nil, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(client, nil, nil, nil, nil, nil)
handler := newHandler(client, nil, fakeInformerFactory, nil)
result, err := handler.makeQueryOptions(tt.params, tt.lvl)
......
......@@ -186,7 +186,7 @@ func prepare() (informers.InformerFactory, error) {
snapshotClient := fakesnapshot.NewSimpleClientset()
apiextensionsClient := fakeapiextensions.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, snapshotClient, apiextensionsClient)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, snapshotClient, apiextensionsClient, nil)
k8sInformerFactory := fakeInformerFactory.KubernetesSharedInformerFactory()
......
package alerting
import (
"context"
"sort"
"strings"
"github.com/pkg/errors"
promresourcesv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
prominformersv1 "github.com/prometheus-operator/prometheus-operator/pkg/client/informers/externalversions/monitoring/v1"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
coreinformersv1 "k8s.io/client-go/informers/core/v1"
"kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/alerting/rules"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
)
const (
rulerNamespace = constants.KubeSphereMonitoringNamespace
customRuleGroupDefault = "alerting.custom.defaults"
customRuleResourceLabelKeyLevel = "custom-alerting-rule-level"
)
var (
maxSecretSize = corev1.MaxSecretSize
maxConfigMapDataSize = int(float64(maxSecretSize) * 0.45)
)
// Operator contains all operations to alerting rules. The operations may involve manipulations of prometheusrule
// custom resources where the rules are persisted, and querying the rules state from prometheus endpoint and
// thanos ruler endpoint.
// For the following apis, if namespace is empty, do operations to alerting rules with cluster level,
// or do operations only to rules of the specified namespaces.
// All custom rules will be configured for thanos ruler, so the operations to custom alerting rule can not be done
// if thanos ruler is not enabled.
type Operator interface {
// ListCustomAlertingRules lists the custom alerting rules.
ListCustomAlertingRules(ctx context.Context, namespace string,
queryParams *v2alpha1.AlertingRuleQueryParams) (*v2alpha1.GettableAlertingRuleList, error)
// ListCustomRulesAlerts lists the alerts of the custom alerting rules.
ListCustomRulesAlerts(ctx context.Context, namespace string,
queryParams *v2alpha1.AlertQueryParams) (*v2alpha1.AlertList, error)
// GetCustomAlertingRule gets the custom alerting rule with the given name.
GetCustomAlertingRule(ctx context.Context, namespace, ruleName string) (*v2alpha1.GettableAlertingRule, error)
// ListCustomRuleAlerts lists the alerts of the custom alerting rule with the given name.
ListCustomRuleAlerts(ctx context.Context, namespace, ruleName string) ([]*v2alpha1.Alert, error)
// CreateCustomAlertingRule creates a custom alerting rule.
CreateCustomAlertingRule(ctx context.Context, namespace string, rule *v2alpha1.PostableAlertingRule) error
// UpdateCustomAlertingRule updates the custom alerting rule with the given name.
UpdateCustomAlertingRule(ctx context.Context, namespace, ruleName string, rule *v2alpha1.PostableAlertingRule) error
// DeleteCustomAlertingRule deletes the custom alerting rule with the given name.
DeleteCustomAlertingRule(ctx context.Context, namespace, ruleName string) error
// ListBuiltinAlertingRules lists the builtin(non-custom) alerting rules
ListBuiltinAlertingRules(ctx context.Context,
queryParams *v2alpha1.AlertingRuleQueryParams) (*v2alpha1.GettableAlertingRuleList, error)
// ListBuiltinRulesAlerts lists the alerts of the builtin(non-custom) alerting rules
ListBuiltinRulesAlerts(ctx context.Context,
queryParams *v2alpha1.AlertQueryParams) (*v2alpha1.AlertList, error)
// GetBuiltinAlertingRule gets the builtin(non-custom) alerting rule with the given id
GetBuiltinAlertingRule(ctx context.Context, ruleId string) (*v2alpha1.GettableAlertingRule, error)
// ListBuiltinRuleAlerts lists the alerts of the builtin(non-custom) alerting rule with the given id
ListBuiltinRuleAlerts(ctx context.Context, ruleId string) ([]*v2alpha1.Alert, error)
}
func NewOperator(informers informers.InformerFactory,
promResourceClient promresourcesclient.Interface, ruleClient alerting.RuleClient,
option *alerting.Options) Operator {
o := operator{
namespaceInformer: informers.KubernetesSharedInformerFactory().Core().V1().Namespaces(),
promResourceClient: promResourceClient,
prometheusInformer: informers.PrometheusSharedInformerFactory().Monitoring().V1().Prometheuses(),
thanosRulerInformer: informers.PrometheusSharedInformerFactory().Monitoring().V1().ThanosRulers(),
ruleResourceInformer: informers.PrometheusSharedInformerFactory().Monitoring().V1().PrometheusRules(),
ruleClient: ruleClient,
thanosRuleResourceLabels: make(map[string]string),
}
o.resourceRuleCache = rules.NewRuleCache(o.ruleResourceInformer)
if option != nil && len(option.ThanosRuleResourceLabels) != 0 {
lblStrings := strings.Split(option.ThanosRuleResourceLabels, ",")
for _, lblString := range lblStrings {
lbl := strings.Split(strings.TrimSpace(lblString), "=")
if len(lbl) == 2 {
o.thanosRuleResourceLabels[lbl[0]] = lbl[1]
}
}
}
return &o
}
type operator struct {
ruleClient alerting.RuleClient
promResourceClient promresourcesclient.Interface
prometheusInformer prominformersv1.PrometheusInformer
thanosRulerInformer prominformersv1.ThanosRulerInformer
ruleResourceInformer prominformersv1.PrometheusRuleInformer
namespaceInformer coreinformersv1.NamespaceInformer
resourceRuleCache *rules.RuleCache
thanosRuleResourceLabels map[string]string
}
func (o *operator) ListCustomAlertingRules(ctx context.Context, namespace string,
queryParams *v2alpha1.AlertingRuleQueryParams) (*v2alpha1.GettableAlertingRuleList, error) {
var level v2alpha1.RuleLevel
if namespace == "" {
namespace = rulerNamespace
level = v2alpha1.RuleLevelCluster
} else {
level = v2alpha1.RuleLevelNamespace
}
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return nil, err
}
alertingRules, err := o.listCustomAlertingRules(ctx, ruleNamespace, level)
if err != nil {
return nil, err
}
return pageAlertingRules(alertingRules, queryParams), nil
}
func (o *operator) ListCustomRulesAlerts(ctx context.Context, namespace string,
queryParams *v2alpha1.AlertQueryParams) (*v2alpha1.AlertList, error) {
var level v2alpha1.RuleLevel
if namespace == "" {
namespace = rulerNamespace
level = v2alpha1.RuleLevelCluster
} else {
level = v2alpha1.RuleLevelNamespace
}
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return nil, err
}
alertingRules, err := o.listCustomAlertingRules(ctx, ruleNamespace, level)
if err != nil {
return nil, err
}
return pageAlerts(alertingRules, queryParams), nil
}
func (o *operator) GetCustomAlertingRule(ctx context.Context, namespace, ruleName string) (
*v2alpha1.GettableAlertingRule, error) {
var level v2alpha1.RuleLevel
if namespace == "" {
namespace = rulerNamespace
level = v2alpha1.RuleLevelCluster
} else {
level = v2alpha1.RuleLevelNamespace
}
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return nil, err
}
return o.getCustomAlertingRule(ctx, ruleNamespace, ruleName, level)
}
func (o *operator) ListCustomRuleAlerts(ctx context.Context, namespace, ruleName string) (
[]*v2alpha1.Alert, error) {
rule, err := o.GetCustomAlertingRule(ctx, namespace, ruleName)
if err != nil {
return nil, err
}
if rule == nil {
return nil, v2alpha1.ErrAlertingRuleNotFound
}
return rule.Alerts, nil
}
func (o *operator) ListBuiltinAlertingRules(ctx context.Context,
queryParams *v2alpha1.AlertingRuleQueryParams) (*v2alpha1.GettableAlertingRuleList, error) {
alertingRules, err := o.listBuiltinAlertingRules(ctx)
if err != nil {
return nil, err
}
return pageAlertingRules(alertingRules, queryParams), nil
}
func (o *operator) ListBuiltinRulesAlerts(ctx context.Context,
queryParams *v2alpha1.AlertQueryParams) (*v2alpha1.AlertList, error) {
alertingRules, err := o.listBuiltinAlertingRules(ctx)
if err != nil {
return nil, err
}
return pageAlerts(alertingRules, queryParams), nil
}
func (o *operator) GetBuiltinAlertingRule(ctx context.Context, ruleId string) (
*v2alpha1.GettableAlertingRule, error) {
return o.getBuiltinAlertingRule(ctx, ruleId)
}
func (o *operator) ListBuiltinRuleAlerts(ctx context.Context, ruleId string) ([]*v2alpha1.Alert, error) {
rule, err := o.getBuiltinAlertingRule(ctx, ruleId)
if err != nil {
return nil, err
}
if rule == nil {
return nil, v2alpha1.ErrAlertingRuleNotFound
}
return rule.Alerts, nil
}
func (o *operator) ListClusterAlertingRules(ctx context.Context, customFlag string,
queryParams *v2alpha1.AlertingRuleQueryParams) (*v2alpha1.GettableAlertingRuleList, error) {
namespace := rulerNamespace
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return nil, err
}
alertingRules, err := o.listCustomAlertingRules(ctx, ruleNamespace, v2alpha1.RuleLevelCluster)
if err != nil {
return nil, err
}
return pageAlertingRules(alertingRules, queryParams), nil
}
func (o *operator) ListClusterRulesAlerts(ctx context.Context,
queryParams *v2alpha1.AlertQueryParams) (*v2alpha1.AlertList, error) {
namespace := rulerNamespace
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return nil, err
}
alertingRules, err := o.listCustomAlertingRules(ctx, ruleNamespace, v2alpha1.RuleLevelCluster)
if err != nil {
return nil, err
}
return pageAlerts(alertingRules, queryParams), nil
}
func (o *operator) listCustomAlertingRules(ctx context.Context, ruleNamespace *corev1.Namespace,
level v2alpha1.RuleLevel) ([]*v2alpha1.GettableAlertingRule, error) {
ruler, err := o.getThanosRuler()
if err != nil {
return nil, err
}
if ruler == nil {
return nil, v2alpha1.ErrThanosRulerNotEnabled
}
resourceRulesMap, err := o.resourceRuleCache.ListRules(ruler, ruleNamespace,
labels.SelectorFromSet(labels.Set{customRuleResourceLabelKeyLevel: string(level)}))
if err != nil {
return nil, err
}
ruleGroups, err := o.ruleClient.ThanosRules(ctx)
if err != nil {
return nil, err
}
return rules.GetAlertingRulesStatus(ruleNamespace.Name, &rules.ResourceRuleChunk{
ResourceRulesMap: resourceRulesMap,
Custom: true,
Level: level,
}, ruleGroups, ruler.ExternalLabels())
}
func (o *operator) getCustomAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
ruleName string, level v2alpha1.RuleLevel) (*v2alpha1.GettableAlertingRule, error) {
ruler, err := o.getThanosRuler()
if err != nil {
return nil, err
}
if ruler == nil {
return nil, v2alpha1.ErrThanosRulerNotEnabled
}
resourceRule, err := o.resourceRuleCache.GetRule(ruler, ruleNamespace,
labels.SelectorFromSet(labels.Set{customRuleResourceLabelKeyLevel: string(level)}), ruleName)
if err != nil {
return nil, err
}
if resourceRule == nil {
return nil, v2alpha1.ErrAlertingRuleNotFound
}
ruleGroups, err := o.ruleClient.ThanosRules(ctx)
if err != nil {
return nil, err
}
return rules.GetAlertingRuleStatus(ruleNamespace.Name, &rules.ResourceRule{
ResourceRuleItem: *resourceRule,
Custom: true,
Level: level,
}, ruleGroups, ruler.ExternalLabels())
}
func (o *operator) listBuiltinAlertingRules(ctx context.Context) (
[]*v2alpha1.GettableAlertingRule, error) {
ruler, err := o.getPrometheusRuler()
if err != nil {
return nil, err
}
ruleGroups, err := o.ruleClient.PrometheusRules(ctx)
if err != nil {
return nil, err
}
if ruler == nil {
// for out-cluster prometheus
return rules.ParseAlertingRules(ruleGroups, false, v2alpha1.RuleLevelCluster,
func(group, id string, rule *alerting.AlertingRule) bool {
return true
})
}
namespace := rulerNamespace
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return nil, err
}
resourceRulesMap, err := o.resourceRuleCache.ListRules(ruler, ruleNamespace, nil)
if err != nil {
return nil, err
}
return rules.GetAlertingRulesStatus(ruleNamespace.Name, &rules.ResourceRuleChunk{
ResourceRulesMap: resourceRulesMap,
Custom: false,
Level: v2alpha1.RuleLevelCluster,
}, ruleGroups, ruler.ExternalLabels())
}
func (o *operator) getBuiltinAlertingRule(ctx context.Context, ruleId string) (*v2alpha1.GettableAlertingRule, error) {
ruler, err := o.getPrometheusRuler()
if err != nil {
return nil, err
}
ruleGroups, err := o.ruleClient.PrometheusRules(ctx)
if err != nil {
return nil, err
}
if ruler == nil {
// for out-cluster prometheus
alertingRules, err := rules.ParseAlertingRules(ruleGroups, false, v2alpha1.RuleLevelCluster,
func(group, id string, rule *alerting.AlertingRule) bool {
return ruleId == id
})
if err != nil {
return nil, err
}
if len(alertingRules) == 0 {
return nil, v2alpha1.ErrAlertingRuleNotFound
}
sort.Slice(alertingRules, func(i, j int) bool {
return v2alpha1.AlertingRuleIdCompare(alertingRules[i].Id, alertingRules[j].Id)
})
return alertingRules[0], nil
}
namespace := rulerNamespace
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return nil, err
}
resourceRule, err := o.resourceRuleCache.GetRule(ruler, ruleNamespace, nil, ruleId)
if err != nil {
return nil, err
}
if resourceRule == nil {
return nil, v2alpha1.ErrAlertingRuleNotFound
}
return rules.GetAlertingRuleStatus(ruleNamespace.Name, &rules.ResourceRule{
ResourceRuleItem: *resourceRule,
Custom: false,
Level: v2alpha1.RuleLevelCluster,
}, ruleGroups, ruler.ExternalLabels())
}
func (o *operator) CreateCustomAlertingRule(ctx context.Context, namespace string,
rule *v2alpha1.PostableAlertingRule) error {
ruler, err := o.getThanosRuler()
if err != nil {
return err
}
if ruler == nil {
return v2alpha1.ErrThanosRulerNotEnabled
}
var (
level v2alpha1.RuleLevel
ruleResourceLabels = make(map[string]string)
)
for k, v := range o.thanosRuleResourceLabels {
ruleResourceLabels[k] = v
}
if namespace == "" {
namespace = rulerNamespace
level = v2alpha1.RuleLevelCluster
} else {
level = v2alpha1.RuleLevelNamespace
expr, err := rules.InjectExprNamespaceLabel(rule.Query, namespace)
if err != nil {
return err
}
rule.Query = expr
}
ruleResourceLabels[customRuleResourceLabelKeyLevel] = string(level)
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return err
}
extraRuleResourceSelector := labels.SelectorFromSet(labels.Set{customRuleResourceLabelKeyLevel: string(level)})
resourceRule, err := o.resourceRuleCache.GetRule(ruler, ruleNamespace, extraRuleResourceSelector, rule.Name)
if err != nil {
return err
}
if resourceRule != nil {
return v2alpha1.ErrAlertingRuleAlreadyExists
}
return ruler.AddAlertingRule(ctx, ruleNamespace, extraRuleResourceSelector,
customRuleGroupDefault, parseToPrometheusRule(rule), ruleResourceLabels)
}
func (o *operator) UpdateCustomAlertingRule(ctx context.Context, namespace, name string,
rule *v2alpha1.PostableAlertingRule) error {
rule.Name = name
ruler, err := o.getThanosRuler()
if err != nil {
return err
}
if ruler == nil {
return v2alpha1.ErrThanosRulerNotEnabled
}
var (
level v2alpha1.RuleLevel
ruleResourceLabels = make(map[string]string)
)
for k, v := range o.thanosRuleResourceLabels {
ruleResourceLabels[k] = v
}
if namespace == "" {
namespace = rulerNamespace
level = v2alpha1.RuleLevelCluster
} else {
level = v2alpha1.RuleLevelNamespace
expr, err := rules.InjectExprNamespaceLabel(rule.Query, namespace)
if err != nil {
return err
}
rule.Query = expr
}
ruleResourceLabels[customRuleResourceLabelKeyLevel] = string(level)
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return err
}
extraRuleResourceSelector := labels.SelectorFromSet(labels.Set{customRuleResourceLabelKeyLevel: string(level)})
resourceRule, err := o.resourceRuleCache.GetRule(ruler, ruleNamespace, extraRuleResourceSelector, rule.Name)
if err != nil {
return err
}
if resourceRule == nil {
return v2alpha1.ErrAlertingRuleNotFound
}
return ruler.UpdateAlertingRule(ctx, ruleNamespace, extraRuleResourceSelector,
resourceRule.Group, parseToPrometheusRule(rule), ruleResourceLabels)
}
func (o *operator) DeleteCustomAlertingRule(ctx context.Context, namespace, name string) error {
ruler, err := o.getThanosRuler()
if err != nil {
return err
}
if ruler == nil {
return v2alpha1.ErrThanosRulerNotEnabled
}
var (
level v2alpha1.RuleLevel
)
if namespace == "" {
namespace = rulerNamespace
level = v2alpha1.RuleLevelCluster
} else {
level = v2alpha1.RuleLevelNamespace
}
ruleNamespace, err := o.namespaceInformer.Lister().Get(namespace)
if err != nil {
return err
}
extraRuleResourceSelector := labels.SelectorFromSet(labels.Set{customRuleResourceLabelKeyLevel: string(level)})
resourceRule, err := o.resourceRuleCache.GetRule(ruler, ruleNamespace, extraRuleResourceSelector, name)
if err != nil {
return err
}
if resourceRule == nil {
return v2alpha1.ErrAlertingRuleNotFound
}
return ruler.DeleteAlertingRule(ctx, ruleNamespace, extraRuleResourceSelector, resourceRule.Group, name)
}
// getPrometheusRuler gets the cluster-in prometheus
func (o *operator) getPrometheusRuler() (rules.Ruler, error) {
prometheuses, err := o.prometheusInformer.Lister().Prometheuses(rulerNamespace).List(labels.Everything())
if err != nil {
return nil, errors.Wrap(err, "error listing prometheuses")
}
if len(prometheuses) > 1 {
// It is not supported to have multiple Prometheus instances in the monitoring namespace for now
return nil, errors.Errorf(
"there is more than one prometheus custom resource in %s", rulerNamespace)
}
if len(prometheuses) == 0 {
return nil, nil
}
return rules.NewPrometheusRuler(prometheuses[0], o.ruleResourceInformer, o.promResourceClient), nil
}
func (o *operator) getThanosRuler() (rules.Ruler, error) {
thanosrulers, err := o.thanosRulerInformer.Lister().ThanosRulers(rulerNamespace).List(labels.Everything())
if err != nil {
return nil, errors.Wrap(err, "error listing thanosrulers: ")
}
if len(thanosrulers) > 1 {
// It is not supported to have multiple thanosruler instances in the monitoring namespace for now
return nil, errors.Errorf(
"there is more than one thanosruler custom resource in %s", rulerNamespace)
}
if len(thanosrulers) == 0 {
// if there is no thanos ruler, custom rules will not be supported
return nil, nil
}
return rules.NewThanosRuler(thanosrulers[0], o.ruleResourceInformer, o.promResourceClient), nil
}
func parseToPrometheusRule(rule *v2alpha1.PostableAlertingRule) *promresourcesv1.Rule {
return &promresourcesv1.Rule{
Alert: rule.Name,
Expr: intstr.FromString(rule.Query),
For: rule.Duration,
Labels: rule.Labels,
Annotations: rule.Annotations,
}
}
func pageAlertingRules(alertingRules []*v2alpha1.GettableAlertingRule,
queryParams *v2alpha1.AlertingRuleQueryParams) *v2alpha1.GettableAlertingRuleList {
alertingRules = queryParams.Filter(alertingRules)
queryParams.Sort(alertingRules)
return &v2alpha1.GettableAlertingRuleList{
Total: len(alertingRules),
Items: queryParams.Sub(alertingRules),
}
}
func pageAlerts(alertingRules []*v2alpha1.GettableAlertingRule,
queryParams *v2alpha1.AlertQueryParams) *v2alpha1.AlertList {
var alerts []*v2alpha1.Alert
for _, rule := range alertingRules {
alerts = append(alerts, queryParams.Filter(rule.Alerts)...)
}
queryParams.Sort(alerts)
return &v2alpha1.AlertList{
Total: len(alerts),
Items: queryParams.Sub(alerts),
}
}
package rules
import (
"sort"
"sync"
promresourcesv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
prominformersv1 "github.com/prometheus-operator/prometheus-operator/pkg/client/informers/externalversions/monitoring/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
"kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
"kubesphere.io/kubesphere/pkg/server/errors"
)
// RuleCache caches all rules from the prometheusrule custom resources
type RuleCache struct {
lock sync.RWMutex
namespaces map[string]*namespaceRuleCache
}
func NewRuleCache(ruleResourceInformer prominformersv1.PrometheusRuleInformer) *RuleCache {
rc := RuleCache{
namespaces: make(map[string]*namespaceRuleCache),
}
ruleResourceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: rc.addCache,
UpdateFunc: func(oldObj, newObj interface{}) {
rc.addCache(newObj)
},
DeleteFunc: rc.deleteCache,
})
return &rc
}
func (c *RuleCache) addCache(referObj interface{}) {
pr, ok := referObj.(*promresourcesv1.PrometheusRule)
if !ok {
return
}
cr := parseRuleResource(pr)
c.lock.Lock()
defer c.lock.Unlock()
cn, ok := c.namespaces[pr.Namespace]
if !ok || cn == nil {
cn = &namespaceRuleCache{
namespace: pr.Namespace,
resources: make(map[string]*resourceRuleCache),
}
c.namespaces[pr.Namespace] = cn
}
cn.resources[pr.Name] = cr
}
func (c *RuleCache) deleteCache(referObj interface{}) {
pr, ok := referObj.(*promresourcesv1.PrometheusRule)
if !ok {
return
}
c.lock.Lock()
defer c.lock.Unlock()
cn, ok := c.namespaces[pr.Namespace]
if !ok {
return
}
delete(cn.resources, pr.Name)
if len(cn.resources) == 0 {
delete(c.namespaces, pr.Namespace)
}
}
func (c *RuleCache) getResourceRuleCaches(ruler Ruler, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector) (map[string]*resourceRuleCache, error) {
selected, err := ruleNamespaceSelected(ruler, ruleNamespace)
if err != nil {
return nil, err
}
if !selected {
return nil, nil
}
rSelector, err := ruler.RuleResourceSelector(extraRuleResourceSelector)
if err != nil {
return nil, err
}
var m = make(map[string]*resourceRuleCache)
c.lock.RLock()
defer c.lock.RUnlock()
cn, ok := c.namespaces[ruleNamespace.Name]
if ok && cn != nil {
for _, cr := range cn.resources {
if rSelector.Matches(labels.Set(cr.Labels)) {
m[cr.Name] = cr
}
}
}
return m, nil
}
func (c *RuleCache) GetRule(ruler Ruler, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector, idOrName string) (*ResourceRuleItem, error) {
caches, err := c.getResourceRuleCaches(ruler, ruleNamespace, extraRuleResourceSelector)
if err != nil {
return nil, err
}
if len(caches) == 0 {
return nil, nil
}
var rules []*ResourceRuleItem
switch ruler.(type) {
case *PrometheusRuler:
for rn, rc := range caches {
if rule, ok := rc.IdRules[idOrName]; ok {
rules = append(rules, &ResourceRuleItem{
Group: rule.Group,
Id: rule.Id,
Rule: rule.Rule.DeepCopy(),
ResourceName: rn,
})
}
}
case *ThanosRuler:
for rn, rc := range caches {
if nrules, ok := rc.NameRules[idOrName]; ok {
for _, nrule := range nrules {
rules = append(rules, &ResourceRuleItem{
Group: nrule.Group,
Id: nrule.Id,
Rule: nrule.Rule.DeepCopy(),
ResourceName: rn,
})
}
}
}
default:
return nil, errors.New("unsupported ruler type")
}
if l := len(rules); l == 0 {
return nil, nil
} else if l > 1 {
// guarantees the stability of the get operations.
sort.Slice(rules, func(i, j int) bool {
return v2alpha1.AlertingRuleIdCompare(rules[i].Id, rules[j].Id)
})
}
return rules[0], nil
}
func (c *RuleCache) ListRules(ruler Ruler, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector) (map[string]*ResourceRuleCollection, error) {
caches, err := c.getResourceRuleCaches(ruler, ruleNamespace, extraRuleResourceSelector)
if err != nil {
return nil, err
}
if len(caches) == 0 {
return nil, nil
}
ret := make(map[string]*ResourceRuleCollection)
for rn, rc := range caches {
rrs := &ResourceRuleCollection{
GroupSet: make(map[string]struct{}),
IdRules: make(map[string]*ResourceRuleItem),
NameRules: make(map[string][]*ResourceRuleItem),
}
for name, rules := range rc.NameRules {
for _, rule := range rules {
rrs.GroupSet[rule.Group] = struct{}{}
rr := &ResourceRuleItem{
Group: rule.Group,
Id: rule.Id,
Rule: rule.Rule.DeepCopy(),
ResourceName: rn,
}
rrs.IdRules[rr.Id] = rr
rrs.NameRules[name] = append(rrs.NameRules[name], rr)
}
}
if len(rrs.IdRules) > 0 {
ret[rn] = rrs
}
}
return ret, nil
}
type namespaceRuleCache struct {
namespace string
resources map[string]*resourceRuleCache
}
type resourceRuleCache struct {
Name string
Labels map[string]string
GroupSet map[string]struct{}
IdRules map[string]*cacheRule
NameRules map[string][]*cacheRule
}
type cacheRule struct {
Group string
Id string
Rule *promresourcesv1.Rule
}
func parseRuleResource(pr *promresourcesv1.PrometheusRule) *resourceRuleCache {
var (
groupSet = make(map[string]struct{})
idRules = make(map[string]*cacheRule)
nameRules = make(map[string][]*cacheRule)
)
for i := 0; i < len(pr.Spec.Groups); i++ {
g := pr.Spec.Groups[i]
for j := 0; j < len(g.Rules); j++ {
gr := g.Rules[j]
if gr.Alert == "" {
continue
}
groupSet[g.Name] = struct{}{}
cr := &cacheRule{
Group: g.Name,
Id: GenResourceRuleIdIgnoreFormat(g.Name, &gr),
Rule: &gr,
}
nameRules[cr.Rule.Alert] = append(nameRules[cr.Rule.Alert], cr)
idRules[cr.Id] = cr
}
}
return &resourceRuleCache{
Name: pr.Name,
Labels: pr.Labels,
GroupSet: groupSet,
IdRules: idRules,
NameRules: nameRules,
}
}
package rules
import (
promresourcesv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
)
type ResourceRuleCollection struct {
GroupSet map[string]struct{}
IdRules map[string]*ResourceRuleItem
NameRules map[string][]*ResourceRuleItem
}
type ResourceRuleItem struct {
ResourceName string
Group string
Id string
Rule *promresourcesv1.Rule
}
type ResourceRule struct {
Level v2alpha1.RuleLevel
Custom bool
ResourceRuleItem
}
type ResourceRuleChunk struct {
Level v2alpha1.RuleLevel
Custom bool
ResourceRulesMap map[string]*ResourceRuleCollection
}
package rules
import (
"context"
"fmt"
"sort"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
promresourcesv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
prominformersv1 "github.com/prometheus-operator/prometheus-operator/pkg/client/informers/externalversions/monitoring/v1"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
)
const (
customAlertingRuleResourcePrefix = "custom-alerting-rule-"
)
var (
maxSecretSize = corev1.MaxSecretSize
maxConfigMapDataSize = int(float64(maxSecretSize) * 0.45)
errOutOfConfigMapSize = errors.New("out of config map size")
)
type Ruler interface {
Namespace() string
RuleResourceNamespaceSelector() (labels.Selector, error)
RuleResourceSelector(extraRuleResourceSelector labels.Selector) (labels.Selector, error)
ExternalLabels() func() map[string]string
ListRuleResources(ruleNamespace *corev1.Namespace, extraRuleResourceSelector labels.Selector) (
[]*promresourcesv1.PrometheusRule, error)
AddAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace, extraRuleResourceSelector labels.Selector,
group string, rule *promresourcesv1.Rule, ruleResourceLabels map[string]string) error
UpdateAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace, extraRuleResourceSelector labels.Selector,
group string, rule *promresourcesv1.Rule, ruleResourceLabels map[string]string) error
DeleteAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace, extraRuleResourceSelector labels.Selector,
group string, name string) error
}
type ruleResource promresourcesv1.PrometheusRule
// deleteAlertingRule deletes the rules with the given name.
// If the rule is deleted, return true to indicate the resource should be updated.
func (r *ruleResource) deleteAlertingRule(name string) (bool, error) {
var (
nGroups []promresourcesv1.RuleGroup
ok bool
)
for _, g := range r.Spec.Groups {
var rules []promresourcesv1.Rule
for _, gr := range g.Rules {
if gr.Alert != "" && gr.Alert == name {
ok = true
continue
}
rules = append(rules, gr)
}
if len(rules) > 0 {
nGroups = append(nGroups, promresourcesv1.RuleGroup{
Name: g.Name,
Interval: g.Interval,
PartialResponseStrategy: g.PartialResponseStrategy,
Rules: rules,
})
}
}
if ok {
r.Spec.Groups = nGroups
}
return ok, nil
}
// updateAlertingRule updates the rule with the given group.
// If the rule is updated, return true to indicate the resource should be updated.
func (r *ruleResource) updateAlertingRule(groupName string, rule *promresourcesv1.Rule) (bool, error) {
var (
ok bool
pr = (promresourcesv1.PrometheusRule)(*r)
npr = pr.DeepCopy()
groupMap = make(map[string]*promresourcesv1.RuleGroup)
)
for _, g := range npr.Spec.Groups {
var rules []promresourcesv1.Rule
for i, gr := range g.Rules {
if gr.Alert != "" && gr.Alert == rule.Alert {
ok = true
continue
}
rules = append(rules, g.Rules[i])
}
if len(rules) > 0 {
groupMap[g.Name] = &promresourcesv1.RuleGroup{
Name: g.Name,
Interval: g.Interval,
PartialResponseStrategy: g.PartialResponseStrategy,
Rules: rules,
}
}
}
if ok {
if g, exist := groupMap[groupName]; exist {
g.Rules = append(g.Rules, *rule)
} else {
groupMap[groupName] = &promresourcesv1.RuleGroup{
Name: groupName,
Rules: []promresourcesv1.Rule{*rule},
}
}
var groups []promresourcesv1.RuleGroup
for _, g := range groupMap {
groups = append(groups, *g)
}
npr.Spec.Groups = groups
content, err := yaml.Marshal(npr.Spec)
if err != nil {
return false, errors.Wrap(err, "failed to unmarshal content")
}
if len(string(content)) < maxConfigMapDataSize { // check size limit
r.Spec.Groups = groups
return true, nil
}
return false, errOutOfConfigMapSize
}
return false, nil
}
func (r *ruleResource) addAlertingRule(group string, rule *promresourcesv1.Rule) (bool, error) {
var (
err error
pr = (promresourcesv1.PrometheusRule)(*r)
npr = pr.DeepCopy()
ok bool
)
for i := 0; i < len(npr.Spec.Groups); i++ {
if npr.Spec.Groups[i].Name == group {
npr.Spec.Groups[i].Rules = append(npr.Spec.Groups[i].Rules, *rule)
ok = true
break
}
}
if !ok { // add a group when there is no group with the specified group name
npr.Spec.Groups = append(npr.Spec.Groups, promresourcesv1.RuleGroup{
Name: group,
Rules: []promresourcesv1.Rule{*rule},
})
}
content, err := yaml.Marshal(npr.Spec)
if err != nil {
return false, errors.Wrap(err, "failed to unmarshal content")
}
if len(string(content)) < maxConfigMapDataSize { // check size limit
r.Spec.Groups = npr.Spec.Groups
return true, nil
} else {
return false, errOutOfConfigMapSize
}
}
func (r *ruleResource) commit(ctx context.Context, prometheusResourceClient promresourcesclient.Interface) error {
var pr = (promresourcesv1.PrometheusRule)(*r)
if len(pr.Spec.Groups) == 0 {
return prometheusResourceClient.MonitoringV1().PrometheusRules(r.Namespace).Delete(ctx, r.Name, metav1.DeleteOptions{})
}
newPr, err := prometheusResourceClient.MonitoringV1().PrometheusRules(r.Namespace).Update(ctx, &pr, metav1.UpdateOptions{})
if err != nil {
return err
}
newPr.DeepCopyInto(&pr)
return nil
}
type PrometheusRuler struct {
resource *promresourcesv1.Prometheus
informer prominformersv1.PrometheusRuleInformer
client promresourcesclient.Interface
}
func NewPrometheusRuler(resource *promresourcesv1.Prometheus, informer prominformersv1.PrometheusRuleInformer,
client promresourcesclient.Interface) Ruler {
return &PrometheusRuler{
resource: resource,
informer: informer,
client: client,
}
}
func (r *PrometheusRuler) Namespace() string {
return r.resource.Namespace
}
func (r *PrometheusRuler) RuleResourceNamespaceSelector() (labels.Selector, error) {
if r.resource.Spec.RuleNamespaceSelector == nil {
return nil, nil
}
return metav1.LabelSelectorAsSelector(r.resource.Spec.RuleNamespaceSelector)
}
func (r *PrometheusRuler) RuleResourceSelector(extraRuleResourceSelector labels.Selector) (labels.Selector, error) {
rSelector, err := metav1.LabelSelectorAsSelector(r.resource.Spec.RuleSelector)
if err != nil {
return nil, err
}
if extraRuleResourceSelector != nil {
if requirements, ok := extraRuleResourceSelector.Requirements(); ok {
rSelector = rSelector.Add(requirements...)
}
}
return rSelector, nil
}
func (r *PrometheusRuler) ExternalLabels() func() map[string]string {
// ignoring the external labels because rules gotten from prometheus endpoint do not include them
return nil
}
func (r *PrometheusRuler) ListRuleResources(ruleNamespace *corev1.Namespace, extraRuleResourceSelector labels.Selector) (
[]*promresourcesv1.PrometheusRule, error) {
selected, err := ruleNamespaceSelected(r, ruleNamespace)
if err != nil {
return nil, err
}
if !selected {
return nil, nil
}
rSelector, err := r.RuleResourceSelector(extraRuleResourceSelector)
if err != nil {
return nil, err
}
return r.informer.Lister().PrometheusRules(ruleNamespace.Name).List(rSelector)
}
func (r *PrometheusRuler) AddAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector,
group string, rule *promresourcesv1.Rule, ruleResourceLabels map[string]string) error {
return errors.New("not supported to add rules for prometheus")
}
func (r *PrometheusRuler) UpdateAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector,
group string, rule *promresourcesv1.Rule, ruleResourceLabels map[string]string) error {
return errors.New("not supported to update rules for prometheus")
}
func (r *PrometheusRuler) DeleteAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector,
group string, name string) error {
return errors.New("not supported to update rules for prometheus")
}
type ThanosRuler struct {
resource *promresourcesv1.ThanosRuler
informer prominformersv1.PrometheusRuleInformer
client promresourcesclient.Interface
}
func NewThanosRuler(resource *promresourcesv1.ThanosRuler, informer prominformersv1.PrometheusRuleInformer,
client promresourcesclient.Interface) Ruler {
return &ThanosRuler{
resource: resource,
informer: informer,
client: client,
}
}
func (r *ThanosRuler) Namespace() string {
return r.resource.Namespace
}
func (r *ThanosRuler) RuleResourceNamespaceSelector() (labels.Selector, error) {
if r.resource.Spec.RuleNamespaceSelector == nil {
return nil, nil
}
return metav1.LabelSelectorAsSelector(r.resource.Spec.RuleNamespaceSelector)
}
func (r *ThanosRuler) RuleResourceSelector(extraRuleSelector labels.Selector) (labels.Selector, error) {
rSelector, err := metav1.LabelSelectorAsSelector(r.resource.Spec.RuleSelector)
if err != nil {
return nil, err
}
if extraRuleSelector != nil {
if requirements, ok := extraRuleSelector.Requirements(); ok {
rSelector = rSelector.Add(requirements...)
}
}
return rSelector, nil
}
func (r *ThanosRuler) ExternalLabels() func() map[string]string {
// rules gotten from thanos ruler endpoint include the labels
lbls := make(map[string]string)
if ls := r.resource.Spec.Labels; ls != nil {
for k, v := range ls {
lbls[k] = v
}
}
return func() map[string]string {
return lbls
}
}
func (r *ThanosRuler) ListRuleResources(ruleNamespace *corev1.Namespace, extraRuleSelector labels.Selector) (
[]*promresourcesv1.PrometheusRule, error) {
selected, err := ruleNamespaceSelected(r, ruleNamespace)
if err != nil {
return nil, err
}
if !selected {
return nil, nil
}
rSelector, err := r.RuleResourceSelector(extraRuleSelector)
if err != nil {
return nil, err
}
return r.informer.Lister().PrometheusRules(ruleNamespace.Name).List(rSelector)
}
func (r *ThanosRuler) AddAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector,
group string, rule *promresourcesv1.Rule, ruleResourceLabels map[string]string) error {
prometheusRules, err := r.ListRuleResources(ruleNamespace, extraRuleResourceSelector)
if err != nil {
return err
}
return r.addAlertingRule(ctx, ruleNamespace, prometheusRules, nil, group, rule, ruleResourceLabels)
}
func (r *ThanosRuler) addAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
prometheusRules []*promresourcesv1.PrometheusRule, excludeRuleResources map[string]*ruleResource,
group string, rule *promresourcesv1.Rule, ruleResourceLabels map[string]string) error {
sort.Slice(prometheusRules, func(i, j int) bool {
return len(fmt.Sprint(prometheusRules[i])) <= len(fmt.Sprint(prometheusRules[j]))
})
for _, prometheusRule := range prometheusRules {
if len(excludeRuleResources) > 0 {
if _, ok := excludeRuleResources[prometheusRule.Name]; ok {
continue
}
}
resource := ruleResource(*prometheusRule)
if ok, err := resource.addAlertingRule(group, rule); err != nil {
if err == errOutOfConfigMapSize {
break
}
return err
} else if ok {
if err = resource.commit(ctx, r.client); err != nil {
return err
}
return nil
}
}
// create a new rule resource and add rule into it when all existing rule resources are full.
newPromRule := promresourcesv1.PrometheusRule{
ObjectMeta: metav1.ObjectMeta{
Namespace: ruleNamespace.Name,
GenerateName: customAlertingRuleResourcePrefix,
Labels: ruleResourceLabels,
},
Spec: promresourcesv1.PrometheusRuleSpec{
Groups: []promresourcesv1.RuleGroup{{
Name: group,
Rules: []promresourcesv1.Rule{*rule},
}},
},
}
if _, err := r.client.MonitoringV1().
PrometheusRules(ruleNamespace.Name).Create(ctx, &newPromRule, metav1.CreateOptions{}); err != nil {
return errors.Wrapf(err, "error creating a prometheusrule resource %s/%s",
newPromRule.Namespace, newPromRule.Name)
}
return nil
}
func (r *ThanosRuler) UpdateAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector,
group string, rule *promresourcesv1.Rule, ruleResourceLabels map[string]string) error {
prometheusRules, err := r.ListRuleResources(ruleNamespace, extraRuleResourceSelector)
if err != nil {
return err
}
var (
found bool
success bool
resourcesToDelRule = make(map[string]*ruleResource)
)
for _, prometheusRule := range prometheusRules {
resource := ruleResource(*prometheusRule)
if success { // If the update has been successful, delete the possible same rule in other resources
if ok, err := resource.deleteAlertingRule(rule.Alert); err != nil {
return err
} else if ok {
if err = resource.commit(ctx, r.client); err != nil {
return err
}
}
continue
}
if ok, err := resource.updateAlertingRule(group, rule); err != nil {
if err == errOutOfConfigMapSize {
// updating the rule in the resource will oversize the size limit,
// so delete it and then add the new rule to a new resource.
resourcesToDelRule[resource.Name] = &resource
found = true
} else {
return err
}
} else if ok {
if err = resource.commit(ctx, r.client); err != nil {
return err
}
found = true
success = true
}
}
if !found {
return v2alpha1.ErrAlertingRuleNotFound
}
if !success {
err := r.addAlertingRule(ctx, ruleNamespace, prometheusRules, resourcesToDelRule, group, rule, ruleResourceLabels)
if err != nil {
return err
}
}
for _, resource := range resourcesToDelRule {
if ok, err := resource.deleteAlertingRule(rule.Alert); err != nil {
return err
} else if ok {
if err = resource.commit(ctx, r.client); err != nil {
return err
}
}
}
return nil
}
func (r *ThanosRuler) DeleteAlertingRule(ctx context.Context, ruleNamespace *corev1.Namespace,
extraRuleResourceSelector labels.Selector, group string, name string) error {
prometheusRules, err := r.ListRuleResources(ruleNamespace, extraRuleResourceSelector)
if err != nil {
return err
}
var success bool
for _, prometheusRule := range prometheusRules {
resource := ruleResource(*prometheusRule)
if ok, err := resource.deleteAlertingRule(name); err != nil {
return err
} else if ok {
if err = resource.commit(ctx, r.client); err != nil {
return err
}
success = true
}
}
if !success {
return v2alpha1.ErrAlertingRuleNotFound
}
return nil
}
func ruleNamespaceSelected(r Ruler, ruleNamespace *corev1.Namespace) (bool, error) {
rnSelector, err := r.RuleResourceNamespaceSelector()
if err != nil {
return false, err
}
if rnSelector == nil { // refer to the comment of Prometheus.Spec.RuleResourceNamespaceSelector
if r.Namespace() != ruleNamespace.Name {
return false, nil
}
} else {
if !rnSelector.Matches(labels.Set(ruleNamespace.Labels)) {
return false, nil
}
}
return true, nil
}
package rules
import (
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
"path/filepath"
"sort"
"strings"
"time"
"github.com/pkg/errors"
"github.com/prometheus-community/prom-label-proxy/injectproxy"
promresourcesv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
prommodel "github.com/prometheus/common/model"
promlabels "github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/rules"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
)
const (
ErrGenRuleId = "error generating rule id"
LabelKeyInternalRuleGroup = "__rule_group__"
LabelKeyInternalRuleName = "__rule_name__"
LabelKeyInternalRuleQuery = "__rule_query__"
LabelKeyInternalRuleDuration = "__rule_duration__"
)
func FormatExpr(expr string) (string, error) {
parsedExpr, err := parser.ParseExpr(expr)
if err == nil {
return parsedExpr.String(), nil
}
return "", errors.Wrapf(err, "failed to parse expr: %s", expr)
}
// InjectExprNamespaceLabel injects an label, whose key is "namespace" and whose value is the given namespace,
// into the prometheus query expression, which will limit the query scope.
func InjectExprNamespaceLabel(expr, namespace string) (string, error) {
parsedExpr, err := parser.ParseExpr(expr)
if err != nil {
return "", err
}
if err = injectproxy.NewEnforcer(&promlabels.Matcher{
Type: promlabels.MatchEqual,
Name: "namespace",
Value: namespace,
}).EnforceNode(parsedExpr); err == nil {
return parsedExpr.String(), nil
}
return "", err
}
func FormatDuration(for_ string) (string, error) {
var duration prommodel.Duration
var err error
if for_ != "" {
duration, err = prommodel.ParseDuration(for_)
if err != nil {
return "", errors.Wrapf(err, "failed to parse Duration string(\"%s\") to time.Duration", for_)
}
}
return duration.String(), nil
}
func parseDurationSeconds(durationSeconds float64) string {
return prommodel.Duration(int64(durationSeconds * float64(time.Second))).String()
}
func GenResourceRuleIdIgnoreFormat(group string, rule *promresourcesv1.Rule) string {
query, err := FormatExpr(rule.Expr.String())
if err != nil {
klog.Warning(errors.Wrapf(err, "invalid alerting rule(%s)", rule.Alert))
query = rule.Expr.String()
}
duration, err := FormatDuration(rule.For)
if err != nil {
klog.Warning(errors.Wrapf(err, "invalid alerting rule(%s)", rule.Alert))
duration = rule.For
}
lbls := make(map[string]string)
for k, v := range rule.Labels {
lbls[k] = v
}
lbls[LabelKeyInternalRuleGroup] = group
lbls[LabelKeyInternalRuleName] = rule.Alert
lbls[LabelKeyInternalRuleQuery] = query
lbls[LabelKeyInternalRuleDuration] = duration
return prommodel.Fingerprint(prommodel.LabelsToSignature(lbls)).String()
}
func GenEndpointRuleId(group string, epRule *alerting.AlertingRule,
externalLabels func() map[string]string) (string, error) {
query, err := FormatExpr(epRule.Query)
if err != nil {
return "", err
}
duration := parseDurationSeconds(epRule.Duration)
var labelsMap map[string]string
if externalLabels == nil {
labelsMap = epRule.Labels
} else {
labelsMap = make(map[string]string)
extLabels := externalLabels()
for key, value := range epRule.Labels {
if v, ok := extLabels[key]; !(ok && value == v) {
labelsMap[key] = value
}
}
}
lbls := make(map[string]string)
for k, v := range labelsMap {
lbls[k] = v
}
lbls[LabelKeyInternalRuleGroup] = group
lbls[LabelKeyInternalRuleName] = epRule.Name
lbls[LabelKeyInternalRuleQuery] = query
lbls[LabelKeyInternalRuleDuration] = duration
return prommodel.Fingerprint(prommodel.LabelsToSignature(lbls)).String(), nil
}
// GetAlertingRulesStatus mix rules from prometheusrule custom resources and rules from endpoints.
// Use rules from prometheusrule custom resources as the main reference.
func GetAlertingRulesStatus(ruleNamespace string, ruleChunk *ResourceRuleChunk, epRuleGroups []*alerting.RuleGroup,
extLabels func() map[string]string) ([]*v2alpha1.GettableAlertingRule, error) {
var (
idEpRules = make(map[string]*alerting.AlertingRule)
nameIds = make(map[string][]string)
ret []*v2alpha1.GettableAlertingRule
)
for _, group := range epRuleGroups {
fileShort := strings.TrimSuffix(filepath.Base(group.File), filepath.Ext(group.File))
if !strings.HasPrefix(fileShort, ruleNamespace+"-") {
continue
}
resourceRules, ok := ruleChunk.ResourceRulesMap[strings.TrimPrefix(fileShort, ruleNamespace+"-")]
if !ok {
continue
}
if _, ok := resourceRules.GroupSet[group.Name]; !ok {
continue
}
for _, epRule := range group.Rules {
if eid, err := GenEndpointRuleId(group.Name, epRule, extLabels); err != nil {
return nil, errors.Wrap(err, ErrGenRuleId)
} else {
idEpRules[eid] = epRule
nameIds[epRule.Name] = append(nameIds[epRule.Name], eid)
}
}
}
if ruleChunk.Custom {
// guarantee the names of the custom alerting rules not to be repeated
var m = make(map[string][]*ResourceRuleItem)
for _, resourceRules := range ruleChunk.ResourceRulesMap {
for name, rrArr := range resourceRules.NameRules {
m[name] = append(m[name], rrArr...)
}
}
for _, rrArr := range m {
if l := len(rrArr); l > 0 {
if l > 1 {
sort.Slice(rrArr, func(i, j int) bool {
return v2alpha1.AlertingRuleIdCompare(rrArr[i].Id, rrArr[j].Id)
})
}
resRule := rrArr[0]
epRule := idEpRules[resRule.Id]
if r := getAlertingRuleStatus(resRule, epRule, ruleChunk.Custom, ruleChunk.Level); r != nil {
ret = append(ret, r)
}
}
}
} else {
// guarantee the ids of the builtin alerting rules not to be repeated
var m = make(map[string]*v2alpha1.GettableAlertingRule)
for _, resourceRules := range ruleChunk.ResourceRulesMap {
for id, rule := range resourceRules.IdRules {
if r := getAlertingRuleStatus(rule, idEpRules[id], ruleChunk.Custom, ruleChunk.Level); r != nil {
m[id] = r
}
}
}
for _, r := range m {
ret = append(ret, r)
}
}
return ret, nil
}
func GetAlertingRuleStatus(ruleNamespace string, rule *ResourceRule, epRuleGroups []*alerting.RuleGroup,
extLabels func() map[string]string) (*v2alpha1.GettableAlertingRule, error) {
if rule == nil || rule.Rule == nil {
return nil, nil
}
var epRules = make(map[string]*alerting.AlertingRule)
for _, group := range epRuleGroups {
fileShort := strings.TrimSuffix(filepath.Base(group.File), filepath.Ext(group.File))
if !strings.HasPrefix(fileShort, ruleNamespace+"-") {
continue
}
if strings.TrimPrefix(fileShort, ruleNamespace+"-") != rule.ResourceName {
continue
}
for _, epRule := range group.Rules {
if eid, err := GenEndpointRuleId(group.Name, epRule, extLabels); err != nil {
return nil, errors.Wrap(err, ErrGenRuleId)
} else {
if rule.Rule.Alert == epRule.Name {
epRules[eid] = epRule
}
}
}
}
var epRule *alerting.AlertingRule
if rule.Custom {
// guarantees the stability of the get operations.
var ids []string
for k, _ := range epRules {
ids = append(ids, k)
}
if l := len(ids); l > 0 {
if l > 1 {
sort.Slice(ids, func(i, j int) bool {
return v2alpha1.AlertingRuleIdCompare(ids[i], ids[j])
})
}
epRule = epRules[ids[0]]
}
} else {
epRule = epRules[rule.Id]
}
return getAlertingRuleStatus(&rule.ResourceRuleItem, epRule, rule.Custom, rule.Level), nil
}
func getAlertingRuleStatus(resRule *ResourceRuleItem, epRule *alerting.AlertingRule,
custom bool, level v2alpha1.RuleLevel) *v2alpha1.GettableAlertingRule {
if resRule == nil || resRule.Rule == nil {
return nil
}
rule := v2alpha1.GettableAlertingRule{
AlertingRule: v2alpha1.AlertingRule{
Id: resRule.Id,
Name: resRule.Rule.Alert,
Query: resRule.Rule.Expr.String(),
Duration: resRule.Rule.For,
Labels: resRule.Rule.Labels,
Annotations: resRule.Rule.Annotations,
},
State: stateInactiveString,
Health: string(rules.HealthUnknown),
}
if epRule != nil {
// The state information and alerts associated with the rule are from the rule from the endpoint.
if epRule.Health != "" {
rule.Health = epRule.Health
}
rule.LastError = epRule.LastError
rule.LastEvaluation = epRule.LastEvaluation
rule.EvaluationDurationSeconds = epRule.EvaluationTime
rState := strings.ToLower(epRule.State)
cliRuleStateEmpty := rState == ""
if !cliRuleStateEmpty {
rule.State = rState
}
for _, a := range epRule.Alerts {
aState := strings.ToLower(a.State)
if cliRuleStateEmpty {
// for the rules gotten from prometheus or thanos ruler with a lower version, they may not contain
// the state property, so compute the rule state by states of its alerts
if alertState(rState) < alertState(aState) {
rule.State = aState
}
}
rule.Alerts = append(rule.Alerts, &v2alpha1.Alert{
ActiveAt: a.ActiveAt,
Labels: a.Labels,
Annotations: a.Annotations,
State: aState,
Value: a.Value,
RuleId: rule.Id,
RuleName: rule.Name,
})
}
}
return &rule
}
func ParseAlertingRules(epRuleGroups []*alerting.RuleGroup, custom bool, level v2alpha1.RuleLevel,
filterFunc func(group, ruleId string, rule *alerting.AlertingRule) bool) ([]*v2alpha1.GettableAlertingRule, error) {
var ret []*v2alpha1.GettableAlertingRule
for _, g := range epRuleGroups {
for _, r := range g.Rules {
id, err := GenEndpointRuleId(g.Name, r, nil)
if err != nil {
return nil, err
}
if filterFunc(g.Name, id, r) {
rule := &v2alpha1.GettableAlertingRule{
AlertingRule: v2alpha1.AlertingRule{
Id: id,
Name: r.Name,
Query: r.Query,
Duration: parseDurationSeconds(r.Duration),
Labels: r.Labels,
Annotations: r.Annotations,
},
State: r.State,
Health: string(r.Health),
LastError: r.LastError,
LastEvaluation: r.LastEvaluation,
EvaluationDurationSeconds: r.EvaluationTime,
}
if rule.Health != "" {
rule.Health = string(rules.HealthUnknown)
}
ruleStateEmpty := rule.State == ""
rule.State = stateInactiveString
for _, a := range r.Alerts {
aState := strings.ToLower(a.State)
if ruleStateEmpty {
// for the rules gotten from prometheus or thanos ruler with a lower version, they may not contain
// the state property, so compute the rule state by states of its alerts
if alertState(rule.State) < alertState(aState) {
rule.State = aState
}
}
rule.Alerts = append(rule.Alerts, &v2alpha1.Alert{
ActiveAt: a.ActiveAt,
Labels: a.Labels,
Annotations: a.Annotations,
State: aState,
Value: a.Value,
RuleId: rule.Id,
RuleName: rule.Name,
})
}
ret = append(ret, rule)
}
}
}
return ret, nil
}
var (
statePendingString = rules.StatePending.String()
stateFiringString = rules.StateFiring.String()
stateInactiveString = rules.StateInactive.String()
)
func alertState(state string) rules.AlertState {
switch state {
case statePendingString:
return rules.StatePending
case stateFiringString:
return rules.StateFiring
case stateInactiveString:
return rules.StateInactive
}
return rules.StateInactive
}
package rules
import (
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
"testing"
"github.com/google/go-cmp/cmp"
promresourcesv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/prometheus/prometheus/rules"
"k8s.io/apimachinery/pkg/util/intstr"
"kubesphere.io/kubesphere/pkg/api/alerting/v2alpha1"
)
func TestGetAlertingRulesStatus(t *testing.T) {
var tests = []struct {
description string
ruleNamespace string
resourceRuleChunk *ResourceRuleChunk
ruleGroups []*alerting.RuleGroup
extLabels func() map[string]string
expected []*v2alpha1.GettableAlertingRule
}{{
description: "get alerting rules status",
ruleNamespace: "test",
resourceRuleChunk: &ResourceRuleChunk{
Level: v2alpha1.RuleLevelNamespace,
Custom: true,
ResourceRulesMap: map[string]*ResourceRuleCollection{
"custom-alerting-rule-jqbgn": &ResourceRuleCollection{
GroupSet: map[string]struct{}{"alerting.custom.defaults": struct{}{}},
NameRules: map[string][]*ResourceRuleItem{
"ca7f09e76954e67c": []*ResourceRuleItem{{
ResourceName: "custom-alerting-rule-jqbgn",
Group: "alerting.custom.defaults",
Id: "ca7f09e76954e67c",
Rule: &promresourcesv1.Rule{
Alert: "TestCPUUsageHigh",
Expr: intstr.FromString(`namespace:workload_cpu_usage:sum{namespace="test"} > 1`),
For: "1m",
Annotations: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
},
}},
},
},
},
},
ruleGroups: []*alerting.RuleGroup{{
Name: "alerting.custom.defaults",
File: "/etc/thanos/rules/thanos-ruler-thanos-ruler-rulefiles-0/test-custom-alerting-rule-jqbgn.yaml",
Rules: []*alerting.AlertingRule{{
Name: "TestCPUUsageHigh",
Query: `namespace:workload_cpu_usage:sum{namespace="test"} > 1`,
Duration: 60,
Health: string(rules.HealthGood),
State: stateInactiveString,
Annotations: map[string]string{
"alias": "The alias is here",
"description": "The description is here",
},
}},
}},
expected: []*v2alpha1.GettableAlertingRule{{
AlertingRule: v2alpha1.AlertingRule{
Id: "ca7f09e76954e67c",
Name: "TestCPUUsageHigh",
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",
},
},
Health: string(rules.HealthGood),
State: stateInactiveString,
}},
}}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
rules, err := GetAlertingRulesStatus(test.ruleNamespace, test.resourceRuleChunk, test.ruleGroups, test.extLabels)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(rules, test.expected); diff != "" {
t.Fatalf("%T differ (-got, +want): %s", test.expected, diff)
}
})
}
}
......@@ -213,7 +213,7 @@ func prepare() (informers.InformerFactory, error) {
k8sClient := fakek8s.NewSimpleClientset()
istioClient := fakeistio.NewSimpleClientset()
snapshotClient := fakesnapshot.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, snapshotClient, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, snapshotClient, nil, nil)
k8sInformerFactory := fakeInformerFactory.KubernetesSharedInformerFactory()
......
......@@ -108,7 +108,7 @@ func prepare() *ResourceGetter {
istioClient := fakeistio.NewSimpleClientset()
snapshotClient := fakesnapshot.NewSimpleClientset()
apiextensionsClient := fakeapiextensions.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, snapshotClient, apiextensionsClient)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, snapshotClient, apiextensionsClient, nil)
for _, namespace := range namespaces {
fakeInformerFactory.KubernetesSharedInformerFactory().Core().V1().
......
......@@ -491,7 +491,7 @@ func prepare() Interface {
ksClient := fakeks.NewSimpleClientset([]runtime.Object{testWorkspace, systemWorkspace}...)
k8sClient := fakek8s.NewSimpleClientset([]runtime.Object{testNamespace, kubesphereSystem}...)
istioClient := fakeistio.NewSimpleClientset()
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, nil, nil)
fakeInformerFactory := informers.NewInformerFactories(k8sClient, ksClient, istioClient, nil, nil, nil)
for _, workspace := range workspaces {
fakeInformerFactory.KubeSphereSharedInformerFactory().Tenant().V1alpha1().
......
......@@ -16,8 +16,21 @@ limitations under the License.
package alerting
import (
"fmt"
"strings"
"github.com/spf13/pflag"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
)
type Options struct {
Endpoint string `json:"endpoint" yaml:"endpoint"`
// The following options are for the alerting with v2alpha1 version or higher versions
PrometheusEndpoint string `json:"prometheusEndpoint" yaml:"prometheusEndpoint"`
ThanosRulerEndpoint string `json:"thanosRulerEndpoint" yaml:"thanosRulerEndpoint"`
ThanosRuleResourceLabels string `json:"thanosRuleResourceLabels" yaml:"thanosRuleResourceLabels"`
}
func NewAlertingOptions() *Options {
......@@ -26,13 +39,37 @@ func NewAlertingOptions() *Options {
}
}
func (s *Options) ApplyTo(options *Options) {
if options == nil {
options = s
return
}
func (o *Options) ApplyTo(options *Options) {
reflectutils.Override(options, o)
}
if s.Endpoint != "" {
options.Endpoint = s.Endpoint
func (o *Options) Validate() []error {
errs := []error{}
if len(o.ThanosRuleResourceLabels) > 0 {
lblStrings := strings.Split(o.ThanosRuleResourceLabels, ",")
for _, lblString := range lblStrings {
if len(lblString) > 0 {
lbl := strings.Split(lblString, "=")
if len(lbl) != 2 {
errs = append(errs, fmt.Errorf("invalid alerting-thanos-rule-resource-labels arg: %s", o.ThanosRuleResourceLabels))
break
}
}
}
}
return errs
}
func (o *Options) AddFlags(fs *pflag.FlagSet, c *Options) {
fs.StringVar(&o.Endpoint, "alerting-server-endpoint", c.Endpoint,
"alerting server endpoint for alerting v1.")
fs.StringVar(&o.PrometheusEndpoint, "alerting-prometheus-endpoint", c.PrometheusEndpoint,
"Prometheus service endpoint from which built-in alerting rules are fetched(alerting v2alpha1 or higher required)")
fs.StringVar(&o.ThanosRulerEndpoint, "alerting-thanos-ruler-endpoint", c.ThanosRulerEndpoint,
"Thanos ruler service endpoint from which custom alerting rules are fetched(alerting v2alpha1 or higher required)")
fs.StringVar(&o.ThanosRuleResourceLabels, "alerting-thanos-rule-resource-labels", c.ThanosRuleResourceLabels,
"Labels used by Thanos Ruler to select PrometheusRule custom resources. eg: thanosruler=thanos-ruler,role=custom-alerting-rules (alerting v2alpha1 or higher required)")
}
package alerting
import (
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"net/http"
)
import "github.com/prometheus/client_golang/api"
const (
apiPrefix = "/api/v1"
epRules = apiPrefix + "/rules"
statusAPIError = 422
statusSuccess status = "success"
statusError status = "error"
ErrBadData ErrorType = "bad_data"
ErrTimeout ErrorType = "timeout"
ErrCanceled ErrorType = "canceled"
ErrExec ErrorType = "execution"
ErrBadResponse ErrorType = "bad_response"
ErrServer ErrorType = "server_error"
ErrClient ErrorType = "client_error"
)
type status string
type ErrorType string
type Error struct {
Type ErrorType
Msg string
Detail string
}
func (e *Error) Error() string {
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
}
type response struct {
Status status `json:"status"`
Data json.RawMessage `json:"data,omitempty"`
ErrorType ErrorType `json:"errorType,omitempty"`
Error string `json:"error,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
type RuleClient interface {
PrometheusRules(ctx context.Context) ([]*RuleGroup, error)
ThanosRules(ctx context.Context) ([]*RuleGroup, error)
}
type ruleClient struct {
prometheus api.Client
thanosruler api.Client
}
func (c *ruleClient) PrometheusRules(ctx context.Context) ([]*RuleGroup, error) {
if c.prometheus != nil {
return c.rules(c.prometheus, ctx)
}
return nil, nil
}
func (c *ruleClient) ThanosRules(ctx context.Context) ([]*RuleGroup, error) {
if c.thanosruler != nil {
return c.rules(c.thanosruler, ctx)
}
return nil, nil
}
func (c *ruleClient) rules(client api.Client, ctx context.Context) ([]*RuleGroup, error) {
u := client.URL(epRules, nil)
q := u.Query()
q.Add("type", "alert")
u.RawQuery = q.Encode()
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, errors.Wrap(err, "error creating request: ")
}
_, body, _, err := c.do(client, ctx, req)
if err != nil {
return nil, errors.Wrap(err, "error doing request: ")
}
var result struct {
Groups []*RuleGroup
}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, errors.Wrap(err, "")
}
return result.Groups, nil
}
func (c *ruleClient) do(client api.Client, ctx context.Context, req *http.Request) (*http.Response, []byte, []string, error) {
resp, body, e := client.Do(ctx, req)
if e != nil {
return resp, body, nil, e
}
code := resp.StatusCode
if code/100 != 2 && !apiError(code) {
errorType, errorMsg := errorTypeAndMsgFor(resp)
return resp, body, nil, &Error{
Type: errorType,
Msg: errorMsg,
Detail: string(body),
}
}
var result response
if http.StatusNoContent != code {
if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
return resp, body, nil, &Error{
Type: ErrBadResponse,
Msg: jsonErr.Error(),
}
}
}
var err error
if apiError(code) && result.Status == "success" {
err = &Error{
Type: ErrBadResponse,
Msg: "inconsistent body for response code",
}
}
if result.Status == "error" {
err = &Error{
Type: result.ErrorType,
Msg: result.Error,
}
}
return resp, []byte(result.Data), result.Warnings, err
}
func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) {
switch resp.StatusCode / 100 {
case 4:
return ErrClient, fmt.Sprintf("client error: %d", resp.StatusCode)
case 5:
return ErrServer, fmt.Sprintf("server error: %d", resp.StatusCode)
}
return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode)
}
func apiError(code int) bool {
// These are the codes that rule server sends when it returns an error.
return code == statusAPIError || code == http.StatusBadRequest ||
code == http.StatusServiceUnavailable || code == http.StatusInternalServerError
}
func NewRuleClient(options *Options) (RuleClient, error) {
var (
c ruleClient
e error
)
if options.PrometheusEndpoint != "" {
c.prometheus, e = api.NewClient(api.Config{Address: options.PrometheusEndpoint})
}
if options.ThanosRulerEndpoint != "" {
c.thanosruler, e = api.NewClient(api.Config{Address: options.ThanosRulerEndpoint})
}
return &c, e
}
package alerting
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
func TestListRules(t *testing.T) {
var tests = []struct {
description string
fakeCode int
fakeResp string
expectError bool
}{{
description: "list alerting rules from prometheus endpoint",
expectError: false,
fakeCode: 200,
fakeResp: `
{
"status": "success",
"data": {
"groups": [
{
"name": "kubernetes-resources",
"file": "/etc/prometheus/rules/prometheus-k8s-rulefiles-0/kubesphere-monitoring-system-prometheus-k8s-rules.yaml",
"rules": [
{
"state": "firing",
"name": "KubeCPUOvercommit",
"query": "sum(namespace:kube_pod_container_resource_requests_cpu_cores:sum) / sum(kube_node_status_allocatable_cpu_cores) > (count(kube_node_status_allocatable_cpu_cores) - 1) / count(kube_node_status_allocatable_cpu_cores)",
"duration": 300,
"labels": {
"severity": "warning"
},
"annotations": {
"message": "Cluster has overcommitted CPU resource requests for Pods and cannot tolerate node failure.",
"runbook_url": "https://github.com/kubernetes-monitoring/kubernetes-mixin/tree/master/runbook.md#alert-name-kubecpuovercommit"
},
"alerts": [
{
"labels": {
"alertname": "KubeCPUOvercommit",
"severity": "warning"
},
"annotations": {
"message": "Cluster has overcommitted CPU resource requests for Pods and cannot tolerate node failure.",
"runbook_url": "https://github.com/ kubernetes-monitoring/kubernetes-mixin/tree/master/runbook.md#alert-name-kubecpuovercommit"
},
"state": "firing",
"activeAt": "2020-09-22T06:18:47.55260138Z",
"value": "4.405e-01"
}
],
"health": "ok",
"evaluationTime": 0.000894038,
"lastEvaluation": "2020-09-22T08:57:17.566233983Z",
"type": "alerting"
}
]
}
]
}
}
`,
}}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
mock := MockService(epRules, test.fakeCode, test.fakeResp)
defer mock.Close()
c, e := NewRuleClient(&Options{PrometheusEndpoint: mock.URL})
if e != nil {
t.Fatal(e)
}
rgs, e := c.PrometheusRules(context.TODO())
if test.expectError {
} else {
if e != nil {
t.Fatal(e)
} else if len(rgs) == 1 && len(rgs[0].Rules) == 1 {
} else {
t.Fatalf("expect %d group and %d rule but got %d group and %d rule", 1, 1, len(rgs), len(rgs[0].Rules))
}
}
})
}
}
func MockService(pattern string, fakeCode int, fakeResp string) *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc(pattern, func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(fakeCode)
res.Write([]byte(fakeResp))
})
return httptest.NewServer(mux)
}
package alerting
import (
"time"
)
type RuleGroup struct {
Name string `json:"name"`
File string `json:"file"`
Rules []*AlertingRule `json:"rules"`
Interval float64 `json:"interval"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation *time.Time `json:"lastEvaluation"`
}
type AlertingRule struct {
// State can be "pending", "firing", "inactive".
State string `json:"state"`
Name string `json:"name"`
Query string `json:"query"`
Duration float64 `json:"duration"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
Alerts []*Alert `json:"alerts"`
// Health can be "ok", "err", "unknown".
Health string `json:"health"`
LastError string `json:"lastError,omitempty"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation *time.Time `json:"lastEvaluation"`
// Type of an alertingRule is always "alerting".
Type string `json:"type"`
}
type Alert struct {
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
State string `json:"state"`
ActiveAt *time.Time `json:"activeAt,omitempty"`
Value string `json:"value"`
}
......@@ -18,6 +18,7 @@ package k8s
import (
snapshotclient "github.com/kubernetes-csi/external-snapshotter/client/v3/clientset/versioned"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
istioclient "istio.io/client-go/pkg/clientset/versioned"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/client-go/discovery"
......@@ -42,6 +43,8 @@ type FakeClient struct {
ApiExtensionClient apiextensionsclient.Interface
prometheusClient promresourcesclient.Interface
MasterURL string
KubeConfig *rest.Config
......@@ -50,7 +53,8 @@ type FakeClient struct {
func NewFakeClientSets(k8sClient kubernetes.Interface, discoveryClient *discovery.DiscoveryClient,
kubeSphereClient kubesphere.Interface,
istioClient istioclient.Interface, snapshotClient snapshotclient.Interface,
apiextensionsclient apiextensionsclient.Interface, masterURL string, kubeConfig *rest.Config) Client {
apiextensionsclient apiextensionsclient.Interface, prometheusClient promresourcesclient.Interface,
masterURL string, kubeConfig *rest.Config) Client {
return &FakeClient{
K8sClient: k8sClient,
DiscoveryClient: discoveryClient,
......@@ -58,6 +62,7 @@ func NewFakeClientSets(k8sClient kubernetes.Interface, discoveryClient *discover
IstioClient: istioClient,
SnapshotClient: snapshotClient,
ApiExtensionClient: apiextensionsclient,
prometheusClient: prometheusClient,
MasterURL: masterURL,
KubeConfig: kubeConfig,
}
......@@ -87,6 +92,10 @@ func (n *FakeClient) Discovery() discovery.DiscoveryInterface {
return n.DiscoveryClient
}
func (n *FakeClient) Prometheus() promresourcesclient.Interface {
return n.prometheusClient
}
func (n *FakeClient) Master() string {
return n.MasterURL
}
......
......@@ -17,7 +17,10 @@ limitations under the License.
package k8s
import (
"strings"
snapshotclient "github.com/kubernetes-csi/external-snapshotter/client/v3/clientset/versioned"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
istioclient "istio.io/client-go/pkg/clientset/versioned"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/client-go/discovery"
......@@ -25,7 +28,6 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"strings"
)
type Client interface {
......@@ -35,6 +37,7 @@ type Client interface {
Snapshot() snapshotclient.Interface
ApiExtensions() apiextensionsclient.Interface
Discovery() discovery.DiscoveryInterface
Prometheus() promresourcesclient.Interface
Master() string
Config() *rest.Config
}
......@@ -55,6 +58,8 @@ type kubernetesClient struct {
apiextensions apiextensionsclient.Interface
prometheus promresourcesclient.Interface
master string
config *rest.Config
......@@ -77,6 +82,7 @@ func NewKubernetesClientOrDie(options *KubernetesOptions) Client {
istio: istioclient.NewForConfigOrDie(config),
snapshot: snapshotclient.NewForConfigOrDie(config),
apiextensions: apiextensionsclient.NewForConfigOrDie(config),
prometheus: promresourcesclient.NewForConfigOrDie(config),
master: config.Host,
config: config,
}
......@@ -135,6 +141,11 @@ func NewKubernetesClient(options *KubernetesOptions) (Client, error) {
return nil, err
}
k.prometheus, err = promresourcesclient.NewForConfig(config)
if err != nil {
return nil, err
}
k.master = options.Master
k.config = config
......@@ -165,6 +176,10 @@ func (k *kubernetesClient) ApiExtensions() apiextensionsclient.Interface {
return k.apiextensions
}
func (k *kubernetesClient) Prometheus() promresourcesclient.Interface {
return k.prometheus
}
// master address used to generate kubeconfig for downloading
func (k *kubernetesClient) Master() string {
return k.master
......
......@@ -18,6 +18,7 @@ package k8s
import (
snapshotclient "github.com/kubernetes-csi/external-snapshotter/client/v3/clientset/versioned"
promresourcesclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
istio "istio.io/client-go/pkg/clientset/versioned"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/client-go/discovery"
......@@ -57,6 +58,10 @@ func (n nullClient) Discovery() discovery.DiscoveryInterface {
return nil
}
func (n *nullClient) Prometheus() promresourcesclient.Interface {
return nil
}
func (n nullClient) Master() string {
return ""
}
......
......@@ -37,6 +37,7 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
alertingv2alpha1 "kubesphere.io/kubesphere/pkg/kapis/alerting/v2alpha1"
clusterkapisv1alpha1 "kubesphere.io/kubesphere/pkg/kapis/cluster/v1alpha1"
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2"
devopsv1alpha3 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha3"
......@@ -128,6 +129,7 @@ func generateSwaggerJson() []byte {
urlruntime.Must(terminalv1alpha2.AddToContainer(container, clientsets.Kubernetes(), nil))
urlruntime.Must(metricsv1alpha2.AddToContainer(container))
urlruntime.Must(networkv1alpha2.AddToContainer(container, ""))
urlruntime.Must(alertingv2alpha1.AddToContainer(container, informerFactory, nil, nil, nil))
config := restfulspec.Config{
WebServices: container.RegisteredWebServices(),
......
*.out
*.5
*.6
*.8
*.swp
_obj
_test
testdata
Copyright (c) 2011, Evan Shaw <edsrzf@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mmap-go
=======
mmap-go is a portable mmap package for the [Go programming language](http://golang.org).
It has been tested on Linux (386, amd64), OS X, and Windows (386). It should also
work on other Unix-like platforms, but hasn't been tested with them. I'm interested
to hear about the results.
I haven't been able to add more features without adding significant complexity,
so mmap-go doesn't support mprotect, mincore, and maybe a few other things.
If you're running on a Unix-like platform and need some of these features,
I suggest Gustavo Niemeyer's [gommap](http://labix.org/gommap).
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines the common package interface and contains a little bit of
// factored out logic.
// Package mmap allows mapping files into memory. It tries to provide a simple, reasonably portable interface,
// but doesn't go out of its way to abstract away every little platform detail.
// This specifically means:
// * forked processes may or may not inherit mappings
// * a file's timestamp may or may not be updated by writes through mappings
// * specifying a size larger than the file's actual size can increase the file's size
// * If the mapped file is being modified by another process while your program's running, don't expect consistent results between platforms
package mmap
import (
"errors"
"os"
"reflect"
"unsafe"
)
const (
// RDONLY maps the memory read-only.
// Attempts to write to the MMap object will result in undefined behavior.
RDONLY = 0
// RDWR maps the memory as read-write. Writes to the MMap object will update the
// underlying file.
RDWR = 1 << iota
// COPY maps the memory as copy-on-write. Writes to the MMap object will affect
// memory, but the underlying file will remain unchanged.
COPY
// If EXEC is set, the mapped memory is marked as executable.
EXEC
)
const (
// If the ANON flag is set, the mapped memory will not be backed by a file.
ANON = 1 << iota
)
// MMap represents a file mapped into memory.
type MMap []byte
// Map maps an entire file into memory.
// If ANON is set in flags, f is ignored.
func Map(f *os.File, prot, flags int) (MMap, error) {
return MapRegion(f, -1, prot, flags, 0)
}
// MapRegion maps part of a file into memory.
// The offset parameter must be a multiple of the system's page size.
// If length < 0, the entire file will be mapped.
// If ANON is set in flags, f is ignored.
func MapRegion(f *os.File, length int, prot, flags int, offset int64) (MMap, error) {
if offset%int64(os.Getpagesize()) != 0 {
return nil, errors.New("offset parameter must be a multiple of the system's page size")
}
var fd uintptr
if flags&ANON == 0 {
fd = uintptr(f.Fd())
if length < 0 {
fi, err := f.Stat()
if err != nil {
return nil, err
}
length = int(fi.Size())
}
} else {
if length <= 0 {
return nil, errors.New("anonymous mapping requires non-zero length")
}
fd = ^uintptr(0)
}
return mmap(length, uintptr(prot), uintptr(flags), fd, offset)
}
func (m *MMap) header() *reflect.SliceHeader {
return (*reflect.SliceHeader)(unsafe.Pointer(m))
}
func (m *MMap) addrLen() (uintptr, uintptr) {
header := m.header()
return header.Data, uintptr(header.Len)
}
// Lock keeps the mapped region in physical memory, ensuring that it will not be
// swapped out.
func (m MMap) Lock() error {
return m.lock()
}
// Unlock reverses the effect of Lock, allowing the mapped region to potentially
// be swapped out.
// If m is already unlocked, aan error will result.
func (m MMap) Unlock() error {
return m.unlock()
}
// Flush synchronizes the mapping's contents to the file's contents on disk.
func (m MMap) Flush() error {
return m.flush()
}
// Unmap deletes the memory mapped region, flushes any remaining changes, and sets
// m to nil.
// Trying to read or write any remaining references to m after Unmap is called will
// result in undefined behavior.
// Unmap should only be called on the slice value that was originally returned from
// a call to Map. Calling Unmap on a derived slice may cause errors.
func (m *MMap) Unmap() error {
err := m.unmap()
*m = nil
return err
}
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd linux openbsd solaris netbsd
package mmap
import (
"golang.org/x/sys/unix"
)
func mmap(len int, inprot, inflags, fd uintptr, off int64) ([]byte, error) {
flags := unix.MAP_SHARED
prot := unix.PROT_READ
switch {
case inprot&COPY != 0:
prot |= unix.PROT_WRITE
flags = unix.MAP_PRIVATE
case inprot&RDWR != 0:
prot |= unix.PROT_WRITE
}
if inprot&EXEC != 0 {
prot |= unix.PROT_EXEC
}
if inflags&ANON != 0 {
flags |= unix.MAP_ANON
}
b, err := unix.Mmap(int(fd), off, len, prot, flags)
if err != nil {
return nil, err
}
return b, nil
}
func (m MMap) flush() error {
return unix.Msync([]byte(m), unix.MS_SYNC)
}
func (m MMap) lock() error {
return unix.Mlock([]byte(m))
}
func (m MMap) unlock() error {
return unix.Munlock([]byte(m))
}
func (m MMap) unmap() error {
return unix.Munmap([]byte(m))
}
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mmap
import (
"errors"
"os"
"sync"
"golang.org/x/sys/windows"
)
// mmap on Windows is a two-step process.
// First, we call CreateFileMapping to get a handle.
// Then, we call MapviewToFile to get an actual pointer into memory.
// Because we want to emulate a POSIX-style mmap, we don't want to expose
// the handle -- only the pointer. We also want to return only a byte slice,
// not a struct, so it's convenient to manipulate.
// We keep this map so that we can get back the original handle from the memory address.
type addrinfo struct {
file windows.Handle
mapview windows.Handle
}
var handleLock sync.Mutex
var handleMap = map[uintptr]*addrinfo{}
func mmap(len int, prot, flags, hfile uintptr, off int64) ([]byte, error) {
flProtect := uint32(windows.PAGE_READONLY)
dwDesiredAccess := uint32(windows.FILE_MAP_READ)
switch {
case prot&COPY != 0:
flProtect = windows.PAGE_WRITECOPY
dwDesiredAccess = windows.FILE_MAP_COPY
case prot&RDWR != 0:
flProtect = windows.PAGE_READWRITE
dwDesiredAccess = windows.FILE_MAP_WRITE
}
if prot&EXEC != 0 {
flProtect <<= 4
dwDesiredAccess |= windows.FILE_MAP_EXECUTE
}
// The maximum size is the area of the file, starting from 0,
// that we wish to allow to be mappable. It is the sum of
// the length the user requested, plus the offset where that length
// is starting from. This does not map the data into memory.
maxSizeHigh := uint32((off + int64(len)) >> 32)
maxSizeLow := uint32((off + int64(len)) & 0xFFFFFFFF)
// TODO: Do we need to set some security attributes? It might help portability.
h, errno := windows.CreateFileMapping(windows.Handle(hfile), nil, flProtect, maxSizeHigh, maxSizeLow, nil)
if h == 0 {
return nil, os.NewSyscallError("CreateFileMapping", errno)
}
// Actually map a view of the data into memory. The view's size
// is the length the user requested.
fileOffsetHigh := uint32(off >> 32)
fileOffsetLow := uint32(off & 0xFFFFFFFF)
addr, errno := windows.MapViewOfFile(h, dwDesiredAccess, fileOffsetHigh, fileOffsetLow, uintptr(len))
if addr == 0 {
return nil, os.NewSyscallError("MapViewOfFile", errno)
}
handleLock.Lock()
handleMap[addr] = &addrinfo{
file: windows.Handle(hfile),
mapview: h,
}
handleLock.Unlock()
m := MMap{}
dh := m.header()
dh.Data = addr
dh.Len = len
dh.Cap = dh.Len
return m, nil
}
func (m MMap) flush() error {
addr, len := m.addrLen()
errno := windows.FlushViewOfFile(addr, len)
if errno != nil {
return os.NewSyscallError("FlushViewOfFile", errno)
}
handleLock.Lock()
defer handleLock.Unlock()
handle, ok := handleMap[addr]
if !ok {
// should be impossible; we would've errored above
return errors.New("unknown base address")
}
errno = windows.FlushFileBuffers(handle.file)
return os.NewSyscallError("FlushFileBuffers", errno)
}
func (m MMap) lock() error {
addr, len := m.addrLen()
errno := windows.VirtualLock(addr, len)
return os.NewSyscallError("VirtualLock", errno)
}
func (m MMap) unlock() error {
addr, len := m.addrLen()
errno := windows.VirtualUnlock(addr, len)
return os.NewSyscallError("VirtualUnlock", errno)
}
func (m MMap) unmap() error {
err := m.flush()
if err != nil {
return err
}
addr := m.header().Data
// Lock the UnmapViewOfFile along with the handleMap deletion.
// As soon as we unmap the view, the OS is free to give the
// same addr to another new map. We don't want another goroutine
// to insert and remove the same addr into handleMap while
// we're trying to remove our old addr/handle pair.
handleLock.Lock()
defer handleLock.Unlock()
err = windows.UnmapViewOfFile(addr)
if err != nil {
return err
}
handle, ok := handleMap[addr]
if !ok {
// should be impossible; we would've errored above
return errors.New("unknown base address")
}
delete(handleMap, addr)
e := windows.CloseHandle(windows.Handle(handle.mapview))
return os.NewSyscallError("CloseHandle", e)
}
cmd/snappytool/snappytool
testdata/bench
# These explicitly listed benchmark data files are for an obsolete version of
# snappy_test.go.
testdata/alice29.txt
testdata/asyoulik.txt
testdata/fireworks.jpeg
testdata/geo.protodata
testdata/html
testdata/html_x_4
testdata/kppkn.gtb
testdata/lcet10.txt
testdata/paper-100k.pdf
testdata/plrabn12.txt
testdata/urls.10K
# This is the official list of Snappy-Go authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Damian Gryski <dgryski@gmail.com>
Google Inc.
Jan Mercl <0xjnml@gmail.com>
Rodolfo Carvalho <rhcarvalho@gmail.com>
Sebastien Binet <seb.binet@gmail.com>
# This is the official list of people who can contribute
# (and typically have contributed) code to the Snappy-Go repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
# Please keep the list sorted.
Damian Gryski <dgryski@gmail.com>
Jan Mercl <0xjnml@gmail.com>
Kai Backman <kaib@golang.org>
Marc-Antoine Ruel <maruel@chromium.org>
Nigel Tao <nigeltao@golang.org>
Rob Pike <r@golang.org>
Rodolfo Carvalho <rhcarvalho@gmail.com>
Russ Cox <rsc@golang.org>
Sebastien Binet <seb.binet@gmail.com>
Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The Snappy compression format in the Go programming language.
To download and install from source:
$ go get github.com/golang/snappy
Unless otherwise noted, the Snappy-Go source files are distributed
under the BSD-style license found in the LICENSE file.
Benchmarks.
The golang/snappy benchmarks include compressing (Z) and decompressing (U) ten
or so files, the same set used by the C++ Snappy code (github.com/google/snappy
and note the "google", not "golang"). On an "Intel(R) Core(TM) i7-3770 CPU @
3.40GHz", Go's GOARCH=amd64 numbers as of 2016-05-29:
"go test -test.bench=."
_UFlat0-8 2.19GB/s ± 0% html
_UFlat1-8 1.41GB/s ± 0% urls
_UFlat2-8 23.5GB/s ± 2% jpg
_UFlat3-8 1.91GB/s ± 0% jpg_200
_UFlat4-8 14.0GB/s ± 1% pdf
_UFlat5-8 1.97GB/s ± 0% html4
_UFlat6-8 814MB/s ± 0% txt1
_UFlat7-8 785MB/s ± 0% txt2
_UFlat8-8 857MB/s ± 0% txt3
_UFlat9-8 719MB/s ± 1% txt4
_UFlat10-8 2.84GB/s ± 0% pb
_UFlat11-8 1.05GB/s ± 0% gaviota
_ZFlat0-8 1.04GB/s ± 0% html
_ZFlat1-8 534MB/s ± 0% urls
_ZFlat2-8 15.7GB/s ± 1% jpg
_ZFlat3-8 740MB/s ± 3% jpg_200
_ZFlat4-8 9.20GB/s ± 1% pdf
_ZFlat5-8 991MB/s ± 0% html4
_ZFlat6-8 379MB/s ± 0% txt1
_ZFlat7-8 352MB/s ± 0% txt2
_ZFlat8-8 396MB/s ± 1% txt3
_ZFlat9-8 327MB/s ± 1% txt4
_ZFlat10-8 1.33GB/s ± 1% pb
_ZFlat11-8 605MB/s ± 1% gaviota
"go test -test.bench=. -tags=noasm"
_UFlat0-8 621MB/s ± 2% html
_UFlat1-8 494MB/s ± 1% urls
_UFlat2-8 23.2GB/s ± 1% jpg
_UFlat3-8 1.12GB/s ± 1% jpg_200
_UFlat4-8 4.35GB/s ± 1% pdf
_UFlat5-8 609MB/s ± 0% html4
_UFlat6-8 296MB/s ± 0% txt1
_UFlat7-8 288MB/s ± 0% txt2
_UFlat8-8 309MB/s ± 1% txt3
_UFlat9-8 280MB/s ± 1% txt4
_UFlat10-8 753MB/s ± 0% pb
_UFlat11-8 400MB/s ± 0% gaviota
_ZFlat0-8 409MB/s ± 1% html
_ZFlat1-8 250MB/s ± 1% urls
_ZFlat2-8 12.3GB/s ± 1% jpg
_ZFlat3-8 132MB/s ± 0% jpg_200
_ZFlat4-8 2.92GB/s ± 0% pdf
_ZFlat5-8 405MB/s ± 1% html4
_ZFlat6-8 179MB/s ± 1% txt1
_ZFlat7-8 170MB/s ± 1% txt2
_ZFlat8-8 189MB/s ± 1% txt3
_ZFlat9-8 164MB/s ± 1% txt4
_ZFlat10-8 479MB/s ± 1% pb
_ZFlat11-8 270MB/s ± 1% gaviota
For comparison (Go's encoded output is byte-for-byte identical to C++'s), here
are the numbers from C++ Snappy's
make CXXFLAGS="-O2 -DNDEBUG -g" clean snappy_unittest.log && cat snappy_unittest.log
BM_UFlat/0 2.4GB/s html
BM_UFlat/1 1.4GB/s urls
BM_UFlat/2 21.8GB/s jpg
BM_UFlat/3 1.5GB/s jpg_200
BM_UFlat/4 13.3GB/s pdf
BM_UFlat/5 2.1GB/s html4
BM_UFlat/6 1.0GB/s txt1
BM_UFlat/7 959.4MB/s txt2
BM_UFlat/8 1.0GB/s txt3
BM_UFlat/9 864.5MB/s txt4
BM_UFlat/10 2.9GB/s pb
BM_UFlat/11 1.2GB/s gaviota
BM_ZFlat/0 944.3MB/s html (22.31 %)
BM_ZFlat/1 501.6MB/s urls (47.78 %)
BM_ZFlat/2 14.3GB/s jpg (99.95 %)
BM_ZFlat/3 538.3MB/s jpg_200 (73.00 %)
BM_ZFlat/4 8.3GB/s pdf (83.30 %)
BM_ZFlat/5 903.5MB/s html4 (22.52 %)
BM_ZFlat/6 336.0MB/s txt1 (57.88 %)
BM_ZFlat/7 312.3MB/s txt2 (61.91 %)
BM_ZFlat/8 353.1MB/s txt3 (54.99 %)
BM_ZFlat/9 289.9MB/s txt4 (66.26 %)
BM_ZFlat/10 1.2GB/s pb (19.68 %)
BM_ZFlat/11 527.4MB/s gaviota (37.72 %)
此差异已折叠。
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
// +build gc
// +build !noasm
package snappy
// decode has the same semantics as in decode_other.go.
//
//go:noescape
func decode(dst, src []byte) int
此差异已折叠。
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !amd64 appengine !gc noasm
package snappy
// decode writes the decoding of src to dst. It assumes that the varint-encoded
// length of the decompressed bytes has already been read, and that len(dst)
// equals that length.
//
// It returns 0 on success or a decodeErrCodeXxx error code on failure.
func decode(dst, src []byte) int {
var d, s, offset, length int
for s < len(src) {
switch src[s] & 0x03 {
case tagLiteral:
x := uint32(src[s] >> 2)
switch {
case x < 60:
s++
case x == 60:
s += 2
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-1])
case x == 61:
s += 3
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-2]) | uint32(src[s-1])<<8
case x == 62:
s += 4
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
case x == 63:
s += 5
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
}
length = int(x) + 1
if length <= 0 {
return decodeErrCodeUnsupportedLiteralLength
}
if length > len(dst)-d || length > len(src)-s {
return decodeErrCodeCorrupt
}
copy(dst[d:], src[s:s+length])
d += length
s += length
continue
case tagCopy1:
s += 2
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
length = 4 + int(src[s-2])>>2&0x7
offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
case tagCopy2:
s += 3
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
length = 1 + int(src[s-3])>>2
offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
case tagCopy4:
s += 5
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
return decodeErrCodeCorrupt
}
length = 1 + int(src[s-5])>>2
offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
}
if offset <= 0 || d < offset || length > len(dst)-d {
return decodeErrCodeCorrupt
}
// Copy from an earlier sub-slice of dst to a later sub-slice. Unlike
// the built-in copy function, this byte-by-byte copy always runs
// forwards, even if the slices overlap. Conceptually, this is:
//
// d += forwardCopy(dst[d:d+length], dst[d-offset:])
for end := d + length; d != end; d++ {
dst[d] = dst[d-offset]
}
}
if d != len(dst) {
return decodeErrCodeCorrupt
}
return 0
}
此差异已折叠。
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
// +build gc
// +build !noasm
package snappy
// emitLiteral has the same semantics as in encode_other.go.
//
//go:noescape
func emitLiteral(dst, lit []byte) int
// emitCopy has the same semantics as in encode_other.go.
//
//go:noescape
func emitCopy(dst []byte, offset, length int) int
// extendMatch has the same semantics as in encode_other.go.
//
//go:noescape
func extendMatch(src []byte, i, j int) int
// encodeBlock has the same semantics as in encode_other.go.
//
//go:noescape
func encodeBlock(dst, src []byte) (d int)
此差异已折叠。
此差异已折叠。
module github.com/golang/snappy
此差异已折叠。
#### joe made this: http://goel.io/joe
#####=== Go ===#####
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册