diff --git a/.github/workflows/managed.yml b/.github/workflows/managed.yml index 0c32da72382..acbf4fac508 100644 --- a/.github/workflows/managed.yml +++ b/.github/workflows/managed.yml @@ -61,7 +61,6 @@ jobs: -v ./:/root/go/src/github.com/percona/pmm \ -v ~/go-mod-cache:/home/pmm/.cache/go/mod \ -v ~/go-build-cache:/home/pmm/.cache/go-build \ - -v ./managed/data/advisors/:/usr/local/percona/advisors/ \ -v ./managed/data/checks/:/usr/local/percona/checks/ \ -v ./managed/data/alerting-templates/:/usr/local/percona/alerting-templates/ \ -v ./.devcontainer/Makefile:/root/go/src/github.com/percona/pmm/Makefile:ro \ diff --git a/.github/workflows/percona-intelligence.yml b/.github/workflows/percona-intelligence.yml index 4fb677b2def..e0fd0e84680 100644 --- a/.github/workflows/percona-intelligence.yml +++ b/.github/workflows/percona-intelligence.yml @@ -4,7 +4,6 @@ on: pull_request: paths: - 'managed/data/alerting-templates/**' - - 'managed/data/advisors/**' - 'managed/data/checks/**' permissions: diff --git a/.gitignore b/.gitignore index 026903dd185..f6dbd7d585c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ settings.json # Temporary (OS, build, config, etc) .DS_Store /tmp/ - .env .netrc .modules @@ -18,7 +17,6 @@ packer.log ci.yml Makefile.local - # PMM specific bin /dev/clickhouse-backups/ diff --git a/AGENTS.md b/AGENTS.md index dc1ddc79cc0..140b663611e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -351,6 +351,7 @@ Core components and per-area guides: see [Component Guides](#component-guides) a - Import grouping: stdlib, then external (`github.com/percona`, third-party), then internal (this repo) - Use `any` instead of `interface{}` - Use modern slice helpers (`slices.Contains`), range loops +- Use `new(expr)` instead of `pointer.To(expr)` when a pointer to a value is needed - Don't use named return values - Don't inline comments (`code // comment`); put comments on separate lines - Don't add obvious/redundant comments; only comment non-obvious intent diff --git a/Makefile.include b/Makefile.include index 14e6cc39620..26860a3cf23 100644 --- a/Makefile.include +++ b/Makefile.include @@ -59,16 +59,25 @@ check-all: check-license check ## Run linter and license checks check-new: bin/golangci-lint run -c=.golangci.yml --new-from-rev=origin/main --new -FILES = $(shell find . -type f -name '*.go') +# Files passed to the Go formatters: the FILES override if given, the whole tree otherwise. +FILES ?= +GO_FILES = $(if $(strip $(FILES)),$(FILES),$(shell find . -type f -name '*.go')) -format: ## Format source code +# Go files changed on the current branch (committed, staged or unstaged), plus untracked ones. +CHANGED_GO_FILES = $(sort $(shell git diff --name-only --diff-filter=d $$(git merge-base main HEAD) -- '*.go'; git ls-files --others --exclude-standard -- '*.go')) + +format: ## Format source code; pass FILES="a.go b.go" to format only those files make -C api format - go tool gofumpt -l -w $(FILES) - go tool goimports -local github.com/percona/pmm -l -w $(FILES) - go tool gci write --section Standard --section Default --section "Prefix(github.com/percona/pmm)" $(FILES) + go tool gofumpt -l -w $(GO_FILES) + go tool goimports -local github.com/percona/pmm -l -w $(GO_FILES) + go tool gci write --section Standard --section Default --section "Prefix(github.com/percona/pmm)" $(GO_FILES) + +format-changed: ## Format the Go files changed on this branch (much faster than `make format`) + @files="$(CHANGED_GO_FILES)"; \ + if [ -z "$$files" ]; then echo "No changed Go files."; else $(MAKE) format FILES="$$files"; fi format-fast: ## Format only (without running goimports and gci) - @go tool gofumpt -l -w $(FILES) + @go tool gofumpt -l -w $(GO_FILES) serve: ## Serve API documentation with nginx nginx -p . -c api/nginx/nginx.conf diff --git a/agent/docker-compose.yml b/agent/docker-compose.yml index ed92c986127..965d783ebb3 100644 --- a/agent/docker-compose.yml +++ b/agent/docker-compose.yml @@ -8,8 +8,6 @@ services: - "127.0.0.1:443:8443" environment: - PMM_DEBUG=1 - # for local development - - PMM_DEV_ADVISOR_CHECKS_FILE=/srv/checks/custom-checks.yml volumes: - ./testdata/checks:/srv/checks diff --git a/api-tests/docker-compose.yml b/api-tests/docker-compose.yml index 181a50f4d59..54a877d6441 100644 --- a/api-tests/docker-compose.yml +++ b/api-tests/docker-compose.yml @@ -7,8 +7,6 @@ services: - 127.0.0.1:443:8443 environment: - PMM_DEBUG=1 - # for local development - # - PMM_DEV_ADVISOR_CHECKS_FILE=/srv/checks/custom-checks.yml volumes: - ./testdata/checks:/srv/checks diff --git a/api-tests/server/advisors_test.go b/api-tests/server/advisors_test.go index 1541faea03f..04e9958f68d 100644 --- a/api-tests/server/advisors_test.go +++ b/api-tests/server/advisors_test.go @@ -50,30 +50,6 @@ func TestStartChecks(t *testing.T) { }) } -func TestGetAdvisorCheckResults(t *testing.T) { - t.Run("with disabled Advisors", func(t *testing.T) { - toggleAdvisorChecks(t, false) - t.Cleanup(func() { RestoreSettingsDefaults(t) }) - - results, err := advisorClient.Default.AdvisorService.GetFailedChecks(nil) - pmmapitests.AssertAPIErrorf(t, err, 400, codes.FailedPrecondition, `advisor checks are disabled.`) - assert.Nil(t, results) - }) - - t.Run("with enabled Advisors", func(t *testing.T) { - toggleAdvisorChecks(t, true) - t.Cleanup(func() { RestoreSettingsDefaults(t) }) - - resp, err := advisorClient.Default.AdvisorService.StartAdvisorChecks(nil) - require.NoError(t, err) - assert.NotNil(t, resp) - - results, err := advisorClient.Default.AdvisorService.GetFailedChecks(nil) - require.NoError(t, err) - assert.NotNil(t, results) - }) -} - func TestListAdvisorChecks(t *testing.T) { toggleAdvisorChecks(t, true) t.Cleanup(func() { RestoreSettingsDefaults(t) }) @@ -98,11 +74,8 @@ func TestListAdvisors(t *testing.T) { assert.NotNil(t, resp) assert.NotEmpty(t, resp.Payload.Advisors) for _, a := range resp.Payload.Advisors { - assert.NotEmpty(t, a.Name, "%+v", a) - assert.NotEmpty(t, a.Summary, "%+v", a) - assert.NotEmpty(t, a.Description, "%+v", a) assert.NotEmpty(t, a.Category, "%+v", a) - assert.NotEmpty(t, a.Comment, "%+v", a) + assert.NotEmpty(t, a.Subcategory, "%+v", a) assert.NotEmpty(t, a.Checks, "%+v", a) for _, c := range a.Checks { diff --git a/api/advisors/v1/advisors.pb.go b/api/advisors/v1/advisors.pb.go index 3e4995415ad..3454c51324e 100644 --- a/api/advisors/v1/advisors.pb.go +++ b/api/advisors/v1/advisors.pb.go @@ -16,6 +16,7 @@ import ( _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" v1 "github.com/percona/pmm/api/management/v1" ) @@ -80,306 +81,194 @@ func (AdvisorCheckInterval) EnumDescriptor() ([]byte, []int) { return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{0} } -type AdvisorCheckFamily int32 +type AdvisorCheckTechnology int32 const ( - AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_UNSPECIFIED AdvisorCheckFamily = 0 - AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_MYSQL AdvisorCheckFamily = 1 - AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_POSTGRESQL AdvisorCheckFamily = 2 - AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_MONGODB AdvisorCheckFamily = 3 + AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED AdvisorCheckTechnology = 0 + AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MYSQL AdvisorCheckTechnology = 1 + AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL AdvisorCheckTechnology = 2 + AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MONGODB AdvisorCheckTechnology = 3 ) -// Enum value maps for AdvisorCheckFamily. +// Enum value maps for AdvisorCheckTechnology. var ( - AdvisorCheckFamily_name = map[int32]string{ - 0: "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - 1: "ADVISOR_CHECK_FAMILY_MYSQL", - 2: "ADVISOR_CHECK_FAMILY_POSTGRESQL", - 3: "ADVISOR_CHECK_FAMILY_MONGODB", - } - AdvisorCheckFamily_value = map[string]int32{ - "ADVISOR_CHECK_FAMILY_UNSPECIFIED": 0, - "ADVISOR_CHECK_FAMILY_MYSQL": 1, - "ADVISOR_CHECK_FAMILY_POSTGRESQL": 2, - "ADVISOR_CHECK_FAMILY_MONGODB": 3, + AdvisorCheckTechnology_name = map[int32]string{ + 0: "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + 1: "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + 2: "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + 3: "ADVISOR_CHECK_TECHNOLOGY_MONGODB", + } + AdvisorCheckTechnology_value = map[string]int32{ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED": 0, + "ADVISOR_CHECK_TECHNOLOGY_MYSQL": 1, + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL": 2, + "ADVISOR_CHECK_TECHNOLOGY_MONGODB": 3, } ) -func (x AdvisorCheckFamily) Enum() *AdvisorCheckFamily { - p := new(AdvisorCheckFamily) +func (x AdvisorCheckTechnology) Enum() *AdvisorCheckTechnology { + p := new(AdvisorCheckTechnology) *p = x return p } -func (x AdvisorCheckFamily) String() string { +func (x AdvisorCheckTechnology) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (AdvisorCheckFamily) Descriptor() protoreflect.EnumDescriptor { +func (AdvisorCheckTechnology) Descriptor() protoreflect.EnumDescriptor { return file_advisors_v1_advisors_proto_enumTypes[1].Descriptor() } -func (AdvisorCheckFamily) Type() protoreflect.EnumType { +func (AdvisorCheckTechnology) Type() protoreflect.EnumType { return &file_advisors_v1_advisors_proto_enumTypes[1] } -func (x AdvisorCheckFamily) Number() protoreflect.EnumNumber { +func (x AdvisorCheckTechnology) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } -// Deprecated: Use AdvisorCheckFamily.Descriptor instead. -func (AdvisorCheckFamily) EnumDescriptor() ([]byte, []int) { +// Deprecated: Use AdvisorCheckTechnology.Descriptor instead. +func (AdvisorCheckTechnology) EnumDescriptor() ([]byte, []int) { return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{1} } -// AdvisorCheckResult represents the check result returned from pmm-managed after running the check. -type AdvisorCheckResult struct { - state protoimpl.MessageState `protogen:"open.v1"` - Summary string `protobuf:"bytes,1,opt,name=summary,proto3" json:"summary,omitempty"` - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - Severity v1.Severity `protobuf:"varint,3,opt,name=severity,proto3,enum=management.v1.Severity" json:"severity,omitempty"` - Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // URL containing information on how to resolve an issue detected by an Advisor check. - ReadMoreUrl string `protobuf:"bytes,5,opt,name=read_more_url,json=readMoreUrl,proto3" json:"read_more_url,omitempty"` - // Name of the monitored service on which the check ran. - ServiceName string `protobuf:"bytes,6,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AdvisorCheckResult) Reset() { - *x = AdvisorCheckResult{} - mi := &file_advisors_v1_advisors_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AdvisorCheckResult) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AdvisorCheckResult) ProtoMessage() {} - -func (x *AdvisorCheckResult) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AdvisorCheckResult.ProtoReflect.Descriptor instead. -func (*AdvisorCheckResult) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{0} -} - -func (x *AdvisorCheckResult) GetSummary() string { - if x != nil { - return x.Summary - } - return "" -} - -func (x *AdvisorCheckResult) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (x *AdvisorCheckResult) GetSeverity() v1.Severity { - if x != nil { - return x.Severity - } - return v1.Severity(0) -} +// AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service. +type AdvisorCheckResultStatus int32 -func (x *AdvisorCheckResult) GetLabels() map[string]string { - if x != nil { - return x.Labels - } - return nil -} +const ( + AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED AdvisorCheckResultStatus = 0 + // The check ran and found no issue. + AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_OK AdvisorCheckResultStatus = 1 + // The check ran and detected an issue. + AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_FAILED AdvisorCheckResultStatus = 2 + // The check could not be executed. + AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_ERROR AdvisorCheckResultStatus = 3 +) -func (x *AdvisorCheckResult) GetReadMoreUrl() string { - if x != nil { - return x.ReadMoreUrl +// Enum value maps for AdvisorCheckResultStatus. +var ( + AdvisorCheckResultStatus_name = map[int32]string{ + 0: "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + 1: "ADVISOR_CHECK_RESULT_STATUS_OK", + 2: "ADVISOR_CHECK_RESULT_STATUS_FAILED", + 3: "ADVISOR_CHECK_RESULT_STATUS_ERROR", + } + AdvisorCheckResultStatus_value = map[string]int32{ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED": 0, + "ADVISOR_CHECK_RESULT_STATUS_OK": 1, + "ADVISOR_CHECK_RESULT_STATUS_FAILED": 2, + "ADVISOR_CHECK_RESULT_STATUS_ERROR": 3, } - return "" -} +) -func (x *AdvisorCheckResult) GetServiceName() string { - if x != nil { - return x.ServiceName - } - return "" +func (x AdvisorCheckResultStatus) Enum() *AdvisorCheckResultStatus { + p := new(AdvisorCheckResultStatus) + *p = x + return p } -// CheckResultSummary is a summary of check results. -type CheckResultSummary struct { - state protoimpl.MessageState `protogen:"open.v1"` - ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` - // Number of failed checks for this service with severity level "EMERGENCY". - EmergencyCount uint32 `protobuf:"varint,3,opt,name=emergency_count,json=emergencyCount,proto3" json:"emergency_count,omitempty"` - // Number of failed checks for this service with severity level "ALERT". - AlertCount uint32 `protobuf:"varint,4,opt,name=alert_count,json=alertCount,proto3" json:"alert_count,omitempty"` - // Number of failed checks for this service with severity level "CRITICAL". - CriticalCount uint32 `protobuf:"varint,5,opt,name=critical_count,json=criticalCount,proto3" json:"critical_count,omitempty"` - // Number of failed checks for this service with severity level "ERROR". - ErrorCount uint32 `protobuf:"varint,6,opt,name=error_count,json=errorCount,proto3" json:"error_count,omitempty"` - // Number of failed checks for this service with severity level "WARNING". - WarningCount uint32 `protobuf:"varint,7,opt,name=warning_count,json=warningCount,proto3" json:"warning_count,omitempty"` - // Number of failed checks for this service with severity level "NOTICE". - NoticeCount uint32 `protobuf:"varint,8,opt,name=notice_count,json=noticeCount,proto3" json:"notice_count,omitempty"` - // Number of failed checks for this service with severity level "INFO". - InfoCount uint32 `protobuf:"varint,9,opt,name=info_count,json=infoCount,proto3" json:"info_count,omitempty"` - // Number of failed checks for this service with severity level "DEBUG". - DebugCount uint32 `protobuf:"varint,10,opt,name=debug_count,json=debugCount,proto3" json:"debug_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x AdvisorCheckResultStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (x *CheckResultSummary) Reset() { - *x = CheckResultSummary{} - mi := &file_advisors_v1_advisors_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (AdvisorCheckResultStatus) Descriptor() protoreflect.EnumDescriptor { + return file_advisors_v1_advisors_proto_enumTypes[2].Descriptor() } -func (x *CheckResultSummary) String() string { - return protoimpl.X.MessageStringOf(x) +func (AdvisorCheckResultStatus) Type() protoreflect.EnumType { + return &file_advisors_v1_advisors_proto_enumTypes[2] } -func (*CheckResultSummary) ProtoMessage() {} - -func (x *CheckResultSummary) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) +func (x AdvisorCheckResultStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } -// Deprecated: Use CheckResultSummary.ProtoReflect.Descriptor instead. -func (*CheckResultSummary) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{1} +// Deprecated: Use AdvisorCheckResultStatus.Descriptor instead. +func (AdvisorCheckResultStatus) EnumDescriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{2} } -func (x *CheckResultSummary) GetServiceName() string { - if x != nil { - return x.ServiceName - } - return "" -} +// AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run. +type AdvisorCheckTriggeredBy int32 -func (x *CheckResultSummary) GetServiceId() string { - if x != nil { - return x.ServiceId - } - return "" -} +const ( + AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED AdvisorCheckTriggeredBy = 0 + // The run was started by a user via the API or UI. + AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_USER AdvisorCheckTriggeredBy = 1 + // The run was started by the built-in scheduler. + AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER AdvisorCheckTriggeredBy = 2 +) -func (x *CheckResultSummary) GetEmergencyCount() uint32 { - if x != nil { - return x.EmergencyCount +// Enum value maps for AdvisorCheckTriggeredBy. +var ( + AdvisorCheckTriggeredBy_name = map[int32]string{ + 0: "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + 1: "ADVISOR_CHECK_TRIGGERED_BY_USER", + 2: "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER", } - return 0 -} - -func (x *CheckResultSummary) GetAlertCount() uint32 { - if x != nil { - return x.AlertCount + AdvisorCheckTriggeredBy_value = map[string]int32{ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED": 0, + "ADVISOR_CHECK_TRIGGERED_BY_USER": 1, + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER": 2, } - return 0 -} +) -func (x *CheckResultSummary) GetCriticalCount() uint32 { - if x != nil { - return x.CriticalCount - } - return 0 +func (x AdvisorCheckTriggeredBy) Enum() *AdvisorCheckTriggeredBy { + p := new(AdvisorCheckTriggeredBy) + *p = x + return p } -func (x *CheckResultSummary) GetErrorCount() uint32 { - if x != nil { - return x.ErrorCount - } - return 0 +func (x AdvisorCheckTriggeredBy) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (x *CheckResultSummary) GetWarningCount() uint32 { - if x != nil { - return x.WarningCount - } - return 0 +func (AdvisorCheckTriggeredBy) Descriptor() protoreflect.EnumDescriptor { + return file_advisors_v1_advisors_proto_enumTypes[3].Descriptor() } -func (x *CheckResultSummary) GetNoticeCount() uint32 { - if x != nil { - return x.NoticeCount - } - return 0 +func (AdvisorCheckTriggeredBy) Type() protoreflect.EnumType { + return &file_advisors_v1_advisors_proto_enumTypes[3] } -func (x *CheckResultSummary) GetInfoCount() uint32 { - if x != nil { - return x.InfoCount - } - return 0 +func (x AdvisorCheckTriggeredBy) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } -func (x *CheckResultSummary) GetDebugCount() uint32 { - if x != nil { - return x.DebugCount - } - return 0 +// Deprecated: Use AdvisorCheckTriggeredBy.Descriptor instead. +func (AdvisorCheckTriggeredBy) EnumDescriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{3} } -// CheckResult represents the check results for a given service. -type CheckResult struct { - state protoimpl.MessageState `protogen:"open.v1"` - Summary string `protobuf:"bytes,1,opt,name=summary,proto3" json:"summary,omitempty"` - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - Severity v1.Severity `protobuf:"varint,3,opt,name=severity,proto3,enum=management.v1.Severity" json:"severity,omitempty"` - Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // URL containing information on how to resolve an issue detected by an Advisor check. - ReadMoreUrl string `protobuf:"bytes,5,opt,name=read_more_url,json=readMoreUrl,proto3" json:"read_more_url,omitempty"` - // Name of the monitored service on which the check ran. - ServiceName string `protobuf:"bytes,6,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - // ID of the monitored service on which the check ran. - ServiceId string `protobuf:"bytes,7,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` - // Name of the check that failed - CheckName string `protobuf:"bytes,8,opt,name=check_name,json=checkName,proto3" json:"check_name,omitempty"` - // Silence status of the check result - Silenced bool `protobuf:"varint,10,opt,name=silenced,proto3" json:"silenced,omitempty"` +// AdvisorCheckQuery is a single data-collection query of an advisor check. +type AdvisorCheckQuery struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"` + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `protobuf:"bytes,3,rep,name=parameters,proto3" json:"parameters,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CheckResult) Reset() { - *x = CheckResult{} - mi := &file_advisors_v1_advisors_proto_msgTypes[2] +func (x *AdvisorCheckQuery) Reset() { + *x = AdvisorCheckQuery{} + mi := &file_advisors_v1_advisors_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CheckResult) String() string { +func (x *AdvisorCheckQuery) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CheckResult) ProtoMessage() {} +func (*AdvisorCheckQuery) ProtoMessage() {} -func (x *CheckResult) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[2] +func (x *AdvisorCheckQuery) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -390,74 +279,32 @@ func (x *CheckResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CheckResult.ProtoReflect.Descriptor instead. -func (*CheckResult) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{2} +// Deprecated: Use AdvisorCheckQuery.ProtoReflect.Descriptor instead. +func (*AdvisorCheckQuery) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{0} } -func (x *CheckResult) GetSummary() string { +func (x *AdvisorCheckQuery) GetType() string { if x != nil { - return x.Summary + return x.Type } return "" } -func (x *CheckResult) GetDescription() string { +func (x *AdvisorCheckQuery) GetQuery() string { if x != nil { - return x.Description + return x.Query } return "" } -func (x *CheckResult) GetSeverity() v1.Severity { - if x != nil { - return x.Severity - } - return v1.Severity(0) -} - -func (x *CheckResult) GetLabels() map[string]string { +func (x *AdvisorCheckQuery) GetParameters() map[string]string { if x != nil { - return x.Labels + return x.Parameters } return nil } -func (x *CheckResult) GetReadMoreUrl() string { - if x != nil { - return x.ReadMoreUrl - } - return "" -} - -func (x *CheckResult) GetServiceName() string { - if x != nil { - return x.ServiceName - } - return "" -} - -func (x *CheckResult) GetServiceId() string { - if x != nil { - return x.ServiceId - } - return "" -} - -func (x *CheckResult) GetCheckName() string { - if x != nil { - return x.CheckName - } - return "" -} - -func (x *CheckResult) GetSilenced() bool { - if x != nil { - return x.Silenced - } - return false -} - // AdvisorCheck contains check name and status. type AdvisorCheck struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -471,15 +318,27 @@ type AdvisorCheck struct { Summary string `protobuf:"bytes,4,opt,name=summary,proto3" json:"summary,omitempty"` // Check execution interval. Interval AdvisorCheckInterval `protobuf:"varint,5,opt,name=interval,proto3,enum=advisors.v1.AdvisorCheckInterval" json:"interval,omitempty"` - // DB family. - Family AdvisorCheckFamily `protobuf:"varint,6,opt,name=family,proto3,enum=advisors.v1.AdvisorCheckFamily" json:"family,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // DB technology. + Technology AdvisorCheckTechnology `protobuf:"varint,6,opt,name=technology,proto3,enum=advisors.v1.AdvisorCheckTechnology" json:"technology,omitempty"` + // Category (top-level grouping). + Category string `protobuf:"bytes,7,opt,name=category,proto3" json:"category,omitempty"` + // Subcategory (second-level grouping within a category). + Subcategory string `protobuf:"bytes,8,opt,name=subcategory,proto3" json:"subcategory,omitempty"` + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `protobuf:"varint,9,opt,name=user_defined,json=userDefined,proto3" json:"user_defined,omitempty"` + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*AdvisorCheckQuery `protobuf:"bytes,10,rep,name=queries,proto3" json:"queries,omitempty"` + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `protobuf:"bytes,11,opt,name=script,proto3" json:"script,omitempty"` + // IDs of services for which this check is disabled. + DisabledServiceIds []string `protobuf:"bytes,12,rep,name=disabled_service_ids,json=disabledServiceIds,proto3" json:"disabled_service_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AdvisorCheck) Reset() { *x = AdvisorCheck{} - mi := &file_advisors_v1_advisors_proto_msgTypes[3] + mi := &file_advisors_v1_advisors_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -491,7 +350,7 @@ func (x *AdvisorCheck) String() string { func (*AdvisorCheck) ProtoMessage() {} func (x *AdvisorCheck) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[3] + mi := &file_advisors_v1_advisors_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -504,7 +363,7 @@ func (x *AdvisorCheck) ProtoReflect() protoreflect.Message { // Deprecated: Use AdvisorCheck.ProtoReflect.Descriptor instead. func (*AdvisorCheck) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{3} + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{1} } func (x *AdvisorCheck) GetName() string { @@ -542,34 +401,86 @@ func (x *AdvisorCheck) GetInterval() AdvisorCheckInterval { return AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_UNSPECIFIED } -func (x *AdvisorCheck) GetFamily() AdvisorCheckFamily { +func (x *AdvisorCheck) GetTechnology() AdvisorCheckTechnology { + if x != nil { + return x.Technology + } + return AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED +} + +func (x *AdvisorCheck) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *AdvisorCheck) GetSubcategory() string { + if x != nil { + return x.Subcategory + } + return "" +} + +func (x *AdvisorCheck) GetUserDefined() bool { + if x != nil { + return x.UserDefined + } + return false +} + +func (x *AdvisorCheck) GetQueries() []*AdvisorCheckQuery { + if x != nil { + return x.Queries + } + return nil +} + +func (x *AdvisorCheck) GetScript() string { + if x != nil { + return x.Script + } + return "" +} + +func (x *AdvisorCheck) GetDisabledServiceIds() []string { if x != nil { - return x.Family + return x.DisabledServiceIds } - return AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_UNSPECIFIED + return nil } type Advisor struct { state protoimpl.MessageState `protogen:"open.v1"` - // Machine-readable name (ID) that is used in expression. + // Deprecated: no longer populated; an advisor is identified by its category/subcategory pair. + // + // Deprecated: Marked as deprecated in advisors/v1/advisors.proto. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // Long human-readable description. + // Deprecated: advisor descriptions were removed. + // + // Deprecated: Marked as deprecated in advisors/v1/advisors.proto. Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - // Short human-readable summary. + // Deprecated: use subcategory instead. + // + // Deprecated: Marked as deprecated in advisors/v1/advisors.proto. Summary string `protobuf:"bytes,3,opt,name=summary,proto3" json:"summary,omitempty"` - // Comment. + // Deprecated: no longer populated. + // + // Deprecated: Marked as deprecated in advisors/v1/advisors.proto. Comment string `protobuf:"bytes,4,opt,name=comment,proto3" json:"comment,omitempty"` - // Category. + // Category (top-level grouping). Category string `protobuf:"bytes,5,opt,name=category,proto3" json:"category,omitempty"` + // Subcategory (second-level grouping within a category). + Subcategory string `protobuf:"bytes,6,opt,name=subcategory,proto3" json:"subcategory,omitempty"` // Advisor checks. - Checks []*AdvisorCheck `protobuf:"bytes,6,rep,name=checks,proto3" json:"checks,omitempty"` + Checks []*AdvisorCheck `protobuf:"bytes,7,rep,name=checks,proto3" json:"checks,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Advisor) Reset() { *x = Advisor{} - mi := &file_advisors_v1_advisors_proto_msgTypes[4] + mi := &file_advisors_v1_advisors_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -581,7 +492,7 @@ func (x *Advisor) String() string { func (*Advisor) ProtoMessage() {} func (x *Advisor) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[4] + mi := &file_advisors_v1_advisors_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -594,9 +505,10 @@ func (x *Advisor) ProtoReflect() protoreflect.Message { // Deprecated: Use Advisor.ProtoReflect.Descriptor instead. func (*Advisor) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{4} + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{2} } +// Deprecated: Marked as deprecated in advisors/v1/advisors.proto. func (x *Advisor) GetName() string { if x != nil { return x.Name @@ -604,6 +516,7 @@ func (x *Advisor) GetName() string { return "" } +// Deprecated: Marked as deprecated in advisors/v1/advisors.proto. func (x *Advisor) GetDescription() string { if x != nil { return x.Description @@ -611,6 +524,7 @@ func (x *Advisor) GetDescription() string { return "" } +// Deprecated: Marked as deprecated in advisors/v1/advisors.proto. func (x *Advisor) GetSummary() string { if x != nil { return x.Summary @@ -618,6 +532,7 @@ func (x *Advisor) GetSummary() string { return "" } +// Deprecated: Marked as deprecated in advisors/v1/advisors.proto. func (x *Advisor) GetComment() string { if x != nil { return x.Comment @@ -632,6 +547,13 @@ func (x *Advisor) GetCategory() string { return "" } +func (x *Advisor) GetSubcategory() string { + if x != nil { + return x.Subcategory + } + return "" +} + func (x *Advisor) GetChecks() []*AdvisorCheck { if x != nil { return x.Checks @@ -646,14 +568,18 @@ type ChangeAdvisorCheckParams struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Enable *bool `protobuf:"varint,2,opt,name=enable,proto3,oneof" json:"enable,omitempty"` // check execution interval. - Interval AdvisorCheckInterval `protobuf:"varint,4,opt,name=interval,proto3,enum=advisors.v1.AdvisorCheckInterval" json:"interval,omitempty"` + Interval AdvisorCheckInterval `protobuf:"varint,4,opt,name=interval,proto3,enum=advisors.v1.AdvisorCheckInterval" json:"interval,omitempty"` + // IDs of services to apply the enable/disable to. When set, enable/disable + // affects only the given services instead of the whole check; interval + // changes are not allowed in the same params entry. + ServiceIds []string `protobuf:"bytes,5,rep,name=service_ids,json=serviceIds,proto3" json:"service_ids,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ChangeAdvisorCheckParams) Reset() { *x = ChangeAdvisorCheckParams{} - mi := &file_advisors_v1_advisors_proto_msgTypes[5] + mi := &file_advisors_v1_advisors_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -665,7 +591,7 @@ func (x *ChangeAdvisorCheckParams) String() string { func (*ChangeAdvisorCheckParams) ProtoMessage() {} func (x *ChangeAdvisorCheckParams) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[5] + mi := &file_advisors_v1_advisors_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -678,7 +604,7 @@ func (x *ChangeAdvisorCheckParams) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangeAdvisorCheckParams.ProtoReflect.Descriptor instead. func (*ChangeAdvisorCheckParams) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{5} + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{3} } func (x *ChangeAdvisorCheckParams) GetName() string { @@ -702,17 +628,27 @@ func (x *ChangeAdvisorCheckParams) GetInterval() AdvisorCheckInterval { return AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_UNSPECIFIED } +func (x *ChangeAdvisorCheckParams) GetServiceIds() []string { + if x != nil { + return x.ServiceIds + } + return nil +} + type StartAdvisorChecksRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Names of the checks that should be started. - Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` + Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` + // IDs of the services to run the checks against. When empty, the checks run + // against every monitored service of a matching technology. + ServiceIds []string `protobuf:"bytes,2,rep,name=service_ids,json=serviceIds,proto3" json:"service_ids,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *StartAdvisorChecksRequest) Reset() { *x = StartAdvisorChecksRequest{} - mi := &file_advisors_v1_advisors_proto_msgTypes[6] + mi := &file_advisors_v1_advisors_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -724,7 +660,7 @@ func (x *StartAdvisorChecksRequest) String() string { func (*StartAdvisorChecksRequest) ProtoMessage() {} func (x *StartAdvisorChecksRequest) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[6] + mi := &file_advisors_v1_advisors_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -737,7 +673,7 @@ func (x *StartAdvisorChecksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartAdvisorChecksRequest.ProtoReflect.Descriptor instead. func (*StartAdvisorChecksRequest) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{6} + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{4} } func (x *StartAdvisorChecksRequest) GetNames() []string { @@ -747,15 +683,24 @@ func (x *StartAdvisorChecksRequest) GetNames() []string { return nil } +func (x *StartAdvisorChecksRequest) GetServiceIds() []string { + if x != nil { + return x.ServiceIds + } + return nil +} + type StartAdvisorChecksResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState `protogen:"open.v1"` + // ID assigned to this run; all check results produced by it share this run_id. + RunId string `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *StartAdvisorChecksResponse) Reset() { *x = StartAdvisorChecksResponse{} - mi := &file_advisors_v1_advisors_proto_msgTypes[7] + mi := &file_advisors_v1_advisors_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -767,7 +712,7 @@ func (x *StartAdvisorChecksResponse) String() string { func (*StartAdvisorChecksResponse) ProtoMessage() {} func (x *StartAdvisorChecksResponse) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[7] + mi := &file_advisors_v1_advisors_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -780,7 +725,14 @@ func (x *StartAdvisorChecksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartAdvisorChecksResponse.ProtoReflect.Descriptor instead. func (*StartAdvisorChecksResponse) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{7} + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{5} +} + +func (x *StartAdvisorChecksResponse) GetRunId() string { + if x != nil { + return x.RunId + } + return "" } type ListAdvisorChecksRequest struct { @@ -791,19 +743,1471 @@ type ListAdvisorChecksRequest struct { func (x *ListAdvisorChecksRequest) Reset() { *x = ListAdvisorChecksRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAdvisorChecksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAdvisorChecksRequest) ProtoMessage() {} + +func (x *ListAdvisorChecksRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAdvisorChecksRequest.ProtoReflect.Descriptor instead. +func (*ListAdvisorChecksRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{6} +} + +type ListAdvisorChecksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Checks []*AdvisorCheck `protobuf:"bytes,1,rep,name=checks,proto3" json:"checks,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAdvisorChecksResponse) Reset() { + *x = ListAdvisorChecksResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAdvisorChecksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAdvisorChecksResponse) ProtoMessage() {} + +func (x *ListAdvisorChecksResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAdvisorChecksResponse.ProtoReflect.Descriptor instead. +func (*ListAdvisorChecksResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{7} +} + +func (x *ListAdvisorChecksResponse) GetChecks() []*AdvisorCheck { + if x != nil { + return x.Checks + } + return nil +} + +type GetAdvisorCheckRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Machine-readable name (ID) of the check. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAdvisorCheckRequest) Reset() { + *x = GetAdvisorCheckRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAdvisorCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAdvisorCheckRequest) ProtoMessage() {} + +func (x *GetAdvisorCheckRequest) ProtoReflect() protoreflect.Message { mi := &file_advisors_v1_advisors_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAdvisorCheckRequest.ProtoReflect.Descriptor instead. +func (*GetAdvisorCheckRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{8} +} + +func (x *GetAdvisorCheckRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetAdvisorCheckResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Check *AdvisorCheck `protobuf:"bytes,1,opt,name=check,proto3" json:"check,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAdvisorCheckResponse) Reset() { + *x = GetAdvisorCheckResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAdvisorCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAdvisorCheckResponse) ProtoMessage() {} + +func (x *GetAdvisorCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAdvisorCheckResponse.ProtoReflect.Descriptor instead. +func (*GetAdvisorCheckResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{9} +} + +func (x *GetAdvisorCheckResponse) GetCheck() *AdvisorCheck { + if x != nil { + return x.Check + } + return nil +} + +type CreateAdvisorCheckRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The check to create. Its name must be unique across all checks. + Check *AdvisorCheck `protobuf:"bytes,1,opt,name=check,proto3" json:"check,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAdvisorCheckRequest) Reset() { + *x = CreateAdvisorCheckRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAdvisorCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAdvisorCheckRequest) ProtoMessage() {} + +func (x *CreateAdvisorCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAdvisorCheckRequest.ProtoReflect.Descriptor instead. +func (*CreateAdvisorCheckRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{10} +} + +func (x *CreateAdvisorCheckRequest) GetCheck() *AdvisorCheck { + if x != nil { + return x.Check + } + return nil +} + +type CreateAdvisorCheckResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Check *AdvisorCheck `protobuf:"bytes,1,opt,name=check,proto3" json:"check,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAdvisorCheckResponse) Reset() { + *x = CreateAdvisorCheckResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAdvisorCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAdvisorCheckResponse) ProtoMessage() {} + +func (x *CreateAdvisorCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAdvisorCheckResponse.ProtoReflect.Descriptor instead. +func (*CreateAdvisorCheckResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{11} +} + +func (x *CreateAdvisorCheckResponse) GetCheck() *AdvisorCheck { + if x != nil { + return x.Check + } + return nil +} + +type UpdateAdvisorCheckRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Machine-readable name (ID) of the check to update. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The updated check definition. A check cannot be renamed: leave its name + // empty or set it to the name above, otherwise the request is rejected. + Check *AdvisorCheck `protobuf:"bytes,2,opt,name=check,proto3" json:"check,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateAdvisorCheckRequest) Reset() { + *x = UpdateAdvisorCheckRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAdvisorCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAdvisorCheckRequest) ProtoMessage() {} + +func (x *UpdateAdvisorCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAdvisorCheckRequest.ProtoReflect.Descriptor instead. +func (*UpdateAdvisorCheckRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{12} +} + +func (x *UpdateAdvisorCheckRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateAdvisorCheckRequest) GetCheck() *AdvisorCheck { + if x != nil { + return x.Check + } + return nil +} + +type UpdateAdvisorCheckResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Check *AdvisorCheck `protobuf:"bytes,1,opt,name=check,proto3" json:"check,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateAdvisorCheckResponse) Reset() { + *x = UpdateAdvisorCheckResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAdvisorCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAdvisorCheckResponse) ProtoMessage() {} + +func (x *UpdateAdvisorCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAdvisorCheckResponse.ProtoReflect.Descriptor instead. +func (*UpdateAdvisorCheckResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{13} +} + +func (x *UpdateAdvisorCheckResponse) GetCheck() *AdvisorCheck { + if x != nil { + return x.Check + } + return nil +} + +type DeleteAdvisorCheckRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Machine-readable name (ID) of the check to delete. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAdvisorCheckRequest) Reset() { + *x = DeleteAdvisorCheckRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAdvisorCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAdvisorCheckRequest) ProtoMessage() {} + +func (x *DeleteAdvisorCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAdvisorCheckRequest.ProtoReflect.Descriptor instead. +func (*DeleteAdvisorCheckRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{14} +} + +func (x *DeleteAdvisorCheckRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteAdvisorCheckResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAdvisorCheckResponse) Reset() { + *x = DeleteAdvisorCheckResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAdvisorCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAdvisorCheckResponse) ProtoMessage() {} + +func (x *DeleteAdvisorCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAdvisorCheckResponse.ProtoReflect.Descriptor instead. +func (*DeleteAdvisorCheckResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{15} +} + +type TestAdvisorCheckRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Check definition to execute; it is not saved. + Check *AdvisorCheck `protobuf:"bytes,1,opt,name=check,proto3" json:"check,omitempty"` + // ID of the service to run the check against. + ServiceId string `protobuf:"bytes,2,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestAdvisorCheckRequest) Reset() { + *x = TestAdvisorCheckRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestAdvisorCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestAdvisorCheckRequest) ProtoMessage() {} + +func (x *TestAdvisorCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestAdvisorCheckRequest.ProtoReflect.Descriptor instead. +func (*TestAdvisorCheckRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{16} +} + +func (x *TestAdvisorCheckRequest) GetCheck() *AdvisorCheck { + if x != nil { + return x.Check + } + return nil +} + +func (x *TestAdvisorCheckRequest) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +// TestAdvisorCheckResult is a single finding produced by a test (dry-run) check execution. +type TestAdvisorCheckResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + Summary string `protobuf:"bytes,1,opt,name=summary,proto3" json:"summary,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Severity v1.Severity `protobuf:"varint,3,opt,name=severity,proto3,enum=management.v1.Severity" json:"severity,omitempty"` + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // URL containing information on how to resolve an issue detected by the check. + ReadMoreUrl string `protobuf:"bytes,5,opt,name=read_more_url,json=readMoreUrl,proto3" json:"read_more_url,omitempty"` + // Name of the monitored service on which the check ran. + ServiceName string `protobuf:"bytes,6,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // ID of the monitored service on which the check ran. + ServiceId string `protobuf:"bytes,7,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + // Name of the tested check. + CheckName string `protobuf:"bytes,8,opt,name=check_name,json=checkName,proto3" json:"check_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestAdvisorCheckResult) Reset() { + *x = TestAdvisorCheckResult{} + mi := &file_advisors_v1_advisors_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestAdvisorCheckResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestAdvisorCheckResult) ProtoMessage() {} + +func (x *TestAdvisorCheckResult) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestAdvisorCheckResult.ProtoReflect.Descriptor instead. +func (*TestAdvisorCheckResult) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{17} +} + +func (x *TestAdvisorCheckResult) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + +func (x *TestAdvisorCheckResult) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *TestAdvisorCheckResult) GetSeverity() v1.Severity { + if x != nil { + return x.Severity + } + return v1.Severity(0) +} + +func (x *TestAdvisorCheckResult) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *TestAdvisorCheckResult) GetReadMoreUrl() string { + if x != nil { + return x.ReadMoreUrl + } + return "" +} + +func (x *TestAdvisorCheckResult) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *TestAdvisorCheckResult) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +func (x *TestAdvisorCheckResult) GetCheckName() string { + if x != nil { + return x.CheckName + } + return "" +} + +type TestAdvisorCheckResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Findings produced by the check script; empty means the check passed. + Results []*TestAdvisorCheckResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + // Output produced by the script's print() calls, for debugging. + ScriptOutput string `protobuf:"bytes,2,opt,name=script_output,json=scriptOutput,proto3" json:"script_output,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestAdvisorCheckResponse) Reset() { + *x = TestAdvisorCheckResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestAdvisorCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestAdvisorCheckResponse) ProtoMessage() {} + +func (x *TestAdvisorCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestAdvisorCheckResponse.ProtoReflect.Descriptor instead. +func (*TestAdvisorCheckResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{18} +} + +func (x *TestAdvisorCheckResponse) GetResults() []*TestAdvisorCheckResult { + if x != nil { + return x.Results + } + return nil +} + +func (x *TestAdvisorCheckResponse) GetScriptOutput() string { + if x != nil { + return x.ScriptOutput + } + return "" +} + +type ListAdvisorCheckTestTargetsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Technology of the check to be tested; determines the eligible service type. + Technology AdvisorCheckTechnology `protobuf:"varint,1,opt,name=technology,proto3,enum=advisors.v1.AdvisorCheckTechnology" json:"technology,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAdvisorCheckTestTargetsRequest) Reset() { + *x = ListAdvisorCheckTestTargetsRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAdvisorCheckTestTargetsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAdvisorCheckTestTargetsRequest) ProtoMessage() {} + +func (x *ListAdvisorCheckTestTargetsRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAdvisorCheckTestTargetsRequest.ProtoReflect.Descriptor instead. +func (*ListAdvisorCheckTestTargetsRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{19} +} + +func (x *ListAdvisorCheckTestTargetsRequest) GetTechnology() AdvisorCheckTechnology { + if x != nil { + return x.Technology + } + return AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED +} + +// AdvisorCheckTestTarget is a service an advisor check can be tested against. +type AdvisorCheckTestTarget struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ID of the eligible service. + ServiceId string `protobuf:"bytes,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + // Name of the eligible service. + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdvisorCheckTestTarget) Reset() { + *x = AdvisorCheckTestTarget{} + mi := &file_advisors_v1_advisors_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdvisorCheckTestTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdvisorCheckTestTarget) ProtoMessage() {} + +func (x *AdvisorCheckTestTarget) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdvisorCheckTestTarget.ProtoReflect.Descriptor instead. +func (*AdvisorCheckTestTarget) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{20} +} + +func (x *AdvisorCheckTestTarget) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +func (x *AdvisorCheckTestTarget) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +type ListAdvisorCheckTestTargetsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Services a check of the requested technology can be tested against. + Targets []*AdvisorCheckTestTarget `protobuf:"bytes,1,rep,name=targets,proto3" json:"targets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAdvisorCheckTestTargetsResponse) Reset() { + *x = ListAdvisorCheckTestTargetsResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAdvisorCheckTestTargetsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAdvisorCheckTestTargetsResponse) ProtoMessage() {} + +func (x *ListAdvisorCheckTestTargetsResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAdvisorCheckTestTargetsResponse.ProtoReflect.Descriptor instead. +func (*ListAdvisorCheckTestTargetsResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{21} +} + +func (x *ListAdvisorCheckTestTargetsResponse) GetTargets() []*AdvisorCheckTestTarget { + if x != nil { + return x.Targets + } + return nil +} + +type ListAdvisorsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAdvisorsRequest) Reset() { + *x = ListAdvisorsRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAdvisorsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAdvisorsRequest) ProtoMessage() {} + +func (x *ListAdvisorsRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAdvisorsRequest.ProtoReflect.Descriptor instead. +func (*ListAdvisorsRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{22} +} + +type ListAdvisorsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Advisors []*Advisor `protobuf:"bytes,1,rep,name=advisors,proto3" json:"advisors,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAdvisorsResponse) Reset() { + *x = ListAdvisorsResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAdvisorsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAdvisorsResponse) ProtoMessage() {} + +func (x *ListAdvisorsResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAdvisorsResponse.ProtoReflect.Descriptor instead. +func (*ListAdvisorsResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{23} +} + +func (x *ListAdvisorsResponse) GetAdvisors() []*Advisor { + if x != nil { + return x.Advisors + } + return nil +} + +type ChangeAdvisorChecksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Params []*ChangeAdvisorCheckParams `protobuf:"bytes,1,rep,name=params,proto3" json:"params,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChangeAdvisorChecksRequest) Reset() { + *x = ChangeAdvisorChecksRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChangeAdvisorChecksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeAdvisorChecksRequest) ProtoMessage() {} + +func (x *ChangeAdvisorChecksRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeAdvisorChecksRequest.ProtoReflect.Descriptor instead. +func (*ChangeAdvisorChecksRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{24} +} + +func (x *ChangeAdvisorChecksRequest) GetParams() []*ChangeAdvisorCheckParams { + if x != nil { + return x.Params + } + return nil +} + +type ChangeAdvisorChecksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChangeAdvisorChecksResponse) Reset() { + *x = ChangeAdvisorChecksResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChangeAdvisorChecksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeAdvisorChecksResponse) ProtoMessage() {} + +func (x *ChangeAdvisorChecksResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeAdvisorChecksResponse.ProtoReflect.Descriptor instead. +func (*ChangeAdvisorChecksResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{25} +} + +// Insight represents a single persisted Advisor check run against a service. +type Insight struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique identifier of the history record. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // ID of the run this result belongs to; all results produced by one execution share it. + RunId string `protobuf:"bytes,2,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` + // Name of the check that ran. + CheckName string `protobuf:"bytes,3,opt,name=check_name,json=checkName,proto3" json:"check_name,omitempty"` + // Category the check belongs to (top-level grouping). + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` + // Subcategory the check belongs to (second-level grouping within a category). + Subcategory string `protobuf:"bytes,5,opt,name=subcategory,proto3" json:"subcategory,omitempty"` + // Check execution interval. + Interval AdvisorCheckInterval `protobuf:"varint,6,opt,name=interval,proto3,enum=advisors.v1.AdvisorCheckInterval" json:"interval,omitempty"` + // ID of the monitored service on which the check ran. + ServiceId string `protobuf:"bytes,7,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + // Name of the monitored service on which the check ran. + ServiceName string `protobuf:"bytes,8,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Type of the monitored service on which the check ran. + ServiceType string `protobuf:"bytes,9,opt,name=service_type,json=serviceType,proto3" json:"service_type,omitempty"` + // ID of the node the service runs on. + NodeId string `protobuf:"bytes,10,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + // Name of the node the service runs on. + NodeName string `protobuf:"bytes,11,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"` + // Environment of the monitored service on which the check ran. + Environment string `protobuf:"bytes,12,opt,name=environment,proto3" json:"environment,omitempty"` + // Cluster of the monitored service on which the check ran. + Cluster string `protobuf:"bytes,13,opt,name=cluster,proto3" json:"cluster,omitempty"` + // Replication set of the monitored service on which the check ran. + ReplicationSet string `protobuf:"bytes,14,opt,name=replication_set,json=replicationSet,proto3" json:"replication_set,omitempty"` + // Outcome of the check run. + Status AdvisorCheckResultStatus `protobuf:"varint,15,opt,name=status,proto3,enum=advisors.v1.AdvisorCheckResultStatus" json:"status,omitempty"` + // Short human-readable summary of the result. + Summary string `protobuf:"bytes,16,opt,name=summary,proto3" json:"summary,omitempty"` + // Long human-readable description of the result. + Description string `protobuf:"bytes,17,opt,name=description,proto3" json:"description,omitempty"` + // URL containing information on how to resolve a detected issue. + ReadMoreUrl string `protobuf:"bytes,18,opt,name=read_more_url,json=readMoreUrl,proto3" json:"read_more_url,omitempty"` + // Output returned by the check run (finding details or execution error). + Outcome string `protobuf:"bytes,19,opt,name=outcome,proto3" json:"outcome,omitempty"` + // Severity of the result. + Severity v1.Severity `protobuf:"varint,20,opt,name=severity,proto3,enum=management.v1.Severity" json:"severity,omitempty"` + // Result labels. + Labels map[string]string `protobuf:"bytes,21,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // Time when the check ran. + CheckedAt *timestamppb.Timestamp `protobuf:"bytes,22,opt,name=checked_at,json=checkedAt,proto3" json:"checked_at,omitempty"` + // Whether the result has been marked as read. + IsRead bool `protobuf:"varint,23,opt,name=is_read,json=isRead,proto3" json:"is_read,omitempty"` + // The actor that initiated the run. + TriggeredBy AdvisorCheckTriggeredBy `protobuf:"varint,24,opt,name=triggered_by,json=triggeredBy,proto3,enum=advisors.v1.AdvisorCheckTriggeredBy" json:"triggered_by,omitempty"` + // Cloud region of the node the service runs on, empty when not applicable. + Region string `protobuf:"bytes,25,opt,name=region,proto3" json:"region,omitempty"` + // Cloud availability zone of the node the service runs on, empty when not applicable. + Az string `protobuf:"bytes,26,opt,name=az,proto3" json:"az,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Insight) Reset() { + *x = Insight{} + mi := &file_advisors_v1_advisors_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Insight) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Insight) ProtoMessage() {} + +func (x *Insight) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Insight.ProtoReflect.Descriptor instead. +func (*Insight) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{26} +} + +func (x *Insight) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Insight) GetRunId() string { + if x != nil { + return x.RunId + } + return "" +} + +func (x *Insight) GetCheckName() string { + if x != nil { + return x.CheckName + } + return "" +} + +func (x *Insight) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *Insight) GetSubcategory() string { + if x != nil { + return x.Subcategory + } + return "" +} + +func (x *Insight) GetInterval() AdvisorCheckInterval { + if x != nil { + return x.Interval + } + return AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_UNSPECIFIED +} + +func (x *Insight) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +func (x *Insight) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *Insight) GetServiceType() string { + if x != nil { + return x.ServiceType + } + return "" +} + +func (x *Insight) GetNodeId() string { + if x != nil { + return x.NodeId + } + return "" +} + +func (x *Insight) GetNodeName() string { + if x != nil { + return x.NodeName + } + return "" +} + +func (x *Insight) GetEnvironment() string { + if x != nil { + return x.Environment + } + return "" +} + +func (x *Insight) GetCluster() string { + if x != nil { + return x.Cluster + } + return "" +} + +func (x *Insight) GetReplicationSet() string { + if x != nil { + return x.ReplicationSet + } + return "" +} + +func (x *Insight) GetStatus() AdvisorCheckResultStatus { + if x != nil { + return x.Status + } + return AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED +} + +func (x *Insight) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + +func (x *Insight) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Insight) GetReadMoreUrl() string { + if x != nil { + return x.ReadMoreUrl + } + return "" +} + +func (x *Insight) GetOutcome() string { + if x != nil { + return x.Outcome + } + return "" +} + +func (x *Insight) GetSeverity() v1.Severity { + if x != nil { + return x.Severity + } + return v1.Severity(0) +} + +func (x *Insight) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *Insight) GetCheckedAt() *timestamppb.Timestamp { + if x != nil { + return x.CheckedAt + } + return nil +} + +func (x *Insight) GetIsRead() bool { + if x != nil { + return x.IsRead + } + return false +} + +func (x *Insight) GetTriggeredBy() AdvisorCheckTriggeredBy { + if x != nil { + return x.TriggeredBy + } + return AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED +} + +func (x *Insight) GetRegion() string { + if x != nil { + return x.Region + } + return "" +} + +func (x *Insight) GetAz() string { + if x != nil { + return x.Az + } + return "" +} + +type ListInsightsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Maximum number of results per page. + PageSize *int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3,oneof" json:"page_size,omitempty"` + // Index of the requested page, starts from 0. + PageIndex *int32 `protobuf:"varint,2,opt,name=page_index,json=pageIndex,proto3,oneof" json:"page_index,omitempty"` + // Filter by service ID. + ServiceId string `protobuf:"bytes,3,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + // Filter by outcome. + Status *AdvisorCheckResultStatus `protobuf:"varint,4,opt,name=status,proto3,enum=advisors.v1.AdvisorCheckResultStatus,oneof" json:"status,omitempty"` + // Filter by read state. + IsRead *bool `protobuf:"varint,5,opt,name=is_read,json=isRead,proto3,oneof" json:"is_read,omitempty"` + // Return only results recorded at or after this time. + From *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=from,proto3" json:"from,omitempty"` + // Return only results recorded at or before this time. + To *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=to,proto3" json:"to,omitempty"` + // Filter by service name (partial, case-insensitive match). + ServiceName string `protobuf:"bytes,8,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Filter by node name (partial, case-insensitive match). + NodeName string `protobuf:"bytes,9,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"` + // Filter by advisor category. + Category string `protobuf:"bytes,10,opt,name=category,proto3" json:"category,omitempty"` + // Filter by check name. + CheckName string `protobuf:"bytes,11,opt,name=check_name,json=checkName,proto3" json:"check_name,omitempty"` + // Filter by severity. + Severity *v1.Severity `protobuf:"varint,12,opt,name=severity,proto3,enum=management.v1.Severity,oneof" json:"severity,omitempty"` + // Filter by run ID. + RunId string `protobuf:"bytes,13,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` + // Filter by the actor that initiated the run. + TriggeredBy *AdvisorCheckTriggeredBy `protobuf:"varint,14,opt,name=triggered_by,json=triggeredBy,proto3,enum=advisors.v1.AdvisorCheckTriggeredBy,oneof" json:"triggered_by,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInsightsRequest) Reset() { + *x = ListInsightsRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListInsightsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListInsightsRequest) ProtoMessage() {} + +func (x *ListInsightsRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListInsightsRequest.ProtoReflect.Descriptor instead. +func (*ListInsightsRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{27} +} + +func (x *ListInsightsRequest) GetPageSize() int32 { + if x != nil && x.PageSize != nil { + return *x.PageSize + } + return 0 +} + +func (x *ListInsightsRequest) GetPageIndex() int32 { + if x != nil && x.PageIndex != nil { + return *x.PageIndex + } + return 0 +} + +func (x *ListInsightsRequest) GetServiceId() string { + if x != nil { + return x.ServiceId + } + return "" +} + +func (x *ListInsightsRequest) GetStatus() AdvisorCheckResultStatus { + if x != nil && x.Status != nil { + return *x.Status + } + return AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED +} + +func (x *ListInsightsRequest) GetIsRead() bool { + if x != nil && x.IsRead != nil { + return *x.IsRead + } + return false +} + +func (x *ListInsightsRequest) GetFrom() *timestamppb.Timestamp { + if x != nil { + return x.From + } + return nil +} + +func (x *ListInsightsRequest) GetTo() *timestamppb.Timestamp { + if x != nil { + return x.To + } + return nil +} + +func (x *ListInsightsRequest) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *ListInsightsRequest) GetNodeName() string { + if x != nil { + return x.NodeName + } + return "" +} + +func (x *ListInsightsRequest) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *ListInsightsRequest) GetCheckName() string { + if x != nil { + return x.CheckName + } + return "" +} + +func (x *ListInsightsRequest) GetSeverity() v1.Severity { + if x != nil && x.Severity != nil { + return *x.Severity + } + return v1.Severity(0) +} + +func (x *ListInsightsRequest) GetRunId() string { + if x != nil { + return x.RunId + } + return "" +} + +func (x *ListInsightsRequest) GetTriggeredBy() AdvisorCheckTriggeredBy { + if x != nil && x.TriggeredBy != nil { + return *x.TriggeredBy + } + return AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED +} + +type ListInsightsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Total number of results. + TotalItems int32 `protobuf:"varint,1,opt,name=total_items,json=totalItems,proto3" json:"total_items,omitempty"` + // Total number of pages. + TotalPages int32 `protobuf:"varint,2,opt,name=total_pages,json=totalPages,proto3" json:"total_pages,omitempty"` + // Insight records. + Results []*Insight `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInsightsResponse) Reset() { + *x = ListInsightsResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListInsightsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListInsightsResponse) ProtoMessage() {} + +func (x *ListInsightsResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListInsightsResponse.ProtoReflect.Descriptor instead. +func (*ListInsightsResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{28} +} + +func (x *ListInsightsResponse) GetTotalItems() int32 { + if x != nil { + return x.TotalItems + } + return 0 +} + +func (x *ListInsightsResponse) GetTotalPages() int32 { + if x != nil { + return x.TotalPages + } + return 0 +} + +func (x *ListInsightsResponse) GetResults() []*Insight { + if x != nil { + return x.Results + } + return nil +} + +type ListInsightsFilterValuesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInsightsFilterValuesRequest) Reset() { + *x = ListInsightsFilterValuesRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListAdvisorChecksRequest) String() string { +func (x *ListInsightsFilterValuesRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListAdvisorChecksRequest) ProtoMessage() {} +func (*ListInsightsFilterValuesRequest) ProtoMessage() {} -func (x *ListAdvisorChecksRequest) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[8] +func (x *ListInsightsFilterValuesRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -814,33 +2218,36 @@ func (x *ListAdvisorChecksRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListAdvisorChecksRequest.ProtoReflect.Descriptor instead. -func (*ListAdvisorChecksRequest) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{8} +// Deprecated: Use ListInsightsFilterValuesRequest.ProtoReflect.Descriptor instead. +func (*ListInsightsFilterValuesRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{29} } -type ListAdvisorChecksResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Checks []*AdvisorCheck `protobuf:"bytes,1,rep,name=checks,proto3" json:"checks,omitempty"` +type ListInsightsFilterValuesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Distinct service names present in the check results history, sorted alphabetically. + ServiceNames []string `protobuf:"bytes,1,rep,name=service_names,json=serviceNames,proto3" json:"service_names,omitempty"` + // Distinct node names present in the check results history, sorted alphabetically. + NodeNames []string `protobuf:"bytes,2,rep,name=node_names,json=nodeNames,proto3" json:"node_names,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListAdvisorChecksResponse) Reset() { - *x = ListAdvisorChecksResponse{} - mi := &file_advisors_v1_advisors_proto_msgTypes[9] +func (x *ListInsightsFilterValuesResponse) Reset() { + *x = ListInsightsFilterValuesResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListAdvisorChecksResponse) String() string { +func (x *ListInsightsFilterValuesResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListAdvisorChecksResponse) ProtoMessage() {} +func (*ListInsightsFilterValuesResponse) ProtoMessage() {} -func (x *ListAdvisorChecksResponse) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[9] +func (x *ListInsightsFilterValuesResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -851,39 +2258,63 @@ func (x *ListAdvisorChecksResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListAdvisorChecksResponse.ProtoReflect.Descriptor instead. -func (*ListAdvisorChecksResponse) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{9} +// Deprecated: Use ListInsightsFilterValuesResponse.ProtoReflect.Descriptor instead. +func (*ListInsightsFilterValuesResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{30} } -func (x *ListAdvisorChecksResponse) GetChecks() []*AdvisorCheck { +func (x *ListInsightsFilterValuesResponse) GetServiceNames() []string { if x != nil { - return x.Checks + return x.ServiceNames } return nil } -type ListAdvisorsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` +func (x *ListInsightsFilterValuesResponse) GetNodeNames() []string { + if x != nil { + return x.NodeNames + } + return nil +} + +// InsightsFilters select Advisor insights by attribute; all present fields must match. +type InsightsFilters struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Filter by check name. + CheckName string `protobuf:"bytes,1,opt,name=check_name,json=checkName,proto3" json:"check_name,omitempty"` + // Filter by service name (partial, case-insensitive match). + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Filter by node name (partial, case-insensitive match). + NodeName string `protobuf:"bytes,3,opt,name=node_name,json=nodeName,proto3" json:"node_name,omitempty"` + // Filter by advisor category. + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` + // Filter by severity. + Severity *v1.Severity `protobuf:"varint,5,opt,name=severity,proto3,enum=management.v1.Severity,oneof" json:"severity,omitempty"` + // Filter by outcome. + Status *AdvisorCheckResultStatus `protobuf:"varint,6,opt,name=status,proto3,enum=advisors.v1.AdvisorCheckResultStatus,oneof" json:"status,omitempty"` + // Filter by read state. + IsRead *bool `protobuf:"varint,7,opt,name=is_read,json=isRead,proto3,oneof" json:"is_read,omitempty"` + // Filter by run ID. + RunId string `protobuf:"bytes,8,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListAdvisorsRequest) Reset() { - *x = ListAdvisorsRequest{} - mi := &file_advisors_v1_advisors_proto_msgTypes[10] +func (x *InsightsFilters) Reset() { + *x = InsightsFilters{} + mi := &file_advisors_v1_advisors_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListAdvisorsRequest) String() string { +func (x *InsightsFilters) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListAdvisorsRequest) ProtoMessage() {} +func (*InsightsFilters) ProtoMessage() {} -func (x *ListAdvisorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[10] +func (x *InsightsFilters) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -894,77 +2325,95 @@ func (x *ListAdvisorsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListAdvisorsRequest.ProtoReflect.Descriptor instead. -func (*ListAdvisorsRequest) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{10} +// Deprecated: Use InsightsFilters.ProtoReflect.Descriptor instead. +func (*InsightsFilters) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{31} } -type ListAdvisorsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Advisors []*Advisor `protobuf:"bytes,1,rep,name=advisors,proto3" json:"advisors,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +func (x *InsightsFilters) GetCheckName() string { + if x != nil { + return x.CheckName + } + return "" } -func (x *ListAdvisorsResponse) Reset() { - *x = ListAdvisorsResponse{} - mi := &file_advisors_v1_advisors_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x *InsightsFilters) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" } -func (x *ListAdvisorsResponse) String() string { - return protoimpl.X.MessageStringOf(x) +func (x *InsightsFilters) GetNodeName() string { + if x != nil { + return x.NodeName + } + return "" } -func (*ListAdvisorsResponse) ProtoMessage() {} - -func (x *ListAdvisorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[11] +func (x *InsightsFilters) GetCategory() string { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.Category } - return mi.MessageOf(x) + return "" } -// Deprecated: Use ListAdvisorsResponse.ProtoReflect.Descriptor instead. -func (*ListAdvisorsResponse) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{11} +func (x *InsightsFilters) GetSeverity() v1.Severity { + if x != nil && x.Severity != nil { + return *x.Severity + } + return v1.Severity(0) } -func (x *ListAdvisorsResponse) GetAdvisors() []*Advisor { +func (x *InsightsFilters) GetStatus() AdvisorCheckResultStatus { + if x != nil && x.Status != nil { + return *x.Status + } + return AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED +} + +func (x *InsightsFilters) GetIsRead() bool { + if x != nil && x.IsRead != nil { + return *x.IsRead + } + return false +} + +func (x *InsightsFilters) GetRunId() string { if x != nil { - return x.Advisors + return x.RunId } - return nil + return "" } -type ChangeAdvisorChecksRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Params []*ChangeAdvisorCheckParams `protobuf:"bytes,1,rep,name=params,proto3" json:"params,omitempty"` +type MarkInsightsReadRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // IDs of the insights to update. Takes precedence over filters. + Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"` + // Read state to set on the records. + IsRead bool `protobuf:"varint,2,opt,name=is_read,json=isRead,proto3" json:"is_read,omitempty"` + // When set and ids is empty, all insights matching these filters are updated + // (an empty filter set matches every record). Either ids or filters must be provided. + Filters *InsightsFilters `protobuf:"bytes,3,opt,name=filters,proto3" json:"filters,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ChangeAdvisorChecksRequest) Reset() { - *x = ChangeAdvisorChecksRequest{} - mi := &file_advisors_v1_advisors_proto_msgTypes[12] +func (x *MarkInsightsReadRequest) Reset() { + *x = MarkInsightsReadRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ChangeAdvisorChecksRequest) String() string { +func (x *MarkInsightsReadRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ChangeAdvisorChecksRequest) ProtoMessage() {} +func (*MarkInsightsReadRequest) ProtoMessage() {} -func (x *ChangeAdvisorChecksRequest) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[12] +func (x *MarkInsightsReadRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -975,39 +2424,53 @@ func (x *ChangeAdvisorChecksRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ChangeAdvisorChecksRequest.ProtoReflect.Descriptor instead. -func (*ChangeAdvisorChecksRequest) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{12} +// Deprecated: Use MarkInsightsReadRequest.ProtoReflect.Descriptor instead. +func (*MarkInsightsReadRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{32} } -func (x *ChangeAdvisorChecksRequest) GetParams() []*ChangeAdvisorCheckParams { +func (x *MarkInsightsReadRequest) GetIds() []string { if x != nil { - return x.Params + return x.Ids } return nil } -type ChangeAdvisorChecksResponse struct { +func (x *MarkInsightsReadRequest) GetIsRead() bool { + if x != nil { + return x.IsRead + } + return false +} + +func (x *MarkInsightsReadRequest) GetFilters() *InsightsFilters { + if x != nil { + return x.Filters + } + return nil +} + +type MarkInsightsReadResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ChangeAdvisorChecksResponse) Reset() { - *x = ChangeAdvisorChecksResponse{} - mi := &file_advisors_v1_advisors_proto_msgTypes[13] +func (x *MarkInsightsReadResponse) Reset() { + *x = MarkInsightsReadResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ChangeAdvisorChecksResponse) String() string { +func (x *MarkInsightsReadResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ChangeAdvisorChecksResponse) ProtoMessage() {} +func (*MarkInsightsReadResponse) ProtoMessage() {} -func (x *ChangeAdvisorChecksResponse) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[13] +func (x *MarkInsightsReadResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1018,32 +2481,53 @@ func (x *ChangeAdvisorChecksResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ChangeAdvisorChecksResponse.ProtoReflect.Descriptor instead. -func (*ChangeAdvisorChecksResponse) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{13} -} - -type ListFailedServicesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Deprecated: Use MarkInsightsReadResponse.ProtoReflect.Descriptor instead. +func (*MarkInsightsReadResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{33} } -func (x *ListFailedServicesRequest) Reset() { - *x = ListFailedServicesRequest{} - mi := &file_advisors_v1_advisors_proto_msgTypes[14] +// AdvisorRun is a single execution of Advisor checks. Its totals are recorded on +// completion, so they stay accurate after the run's insights have been pruned. +type AdvisorRun struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ID shared by every insight the run produced. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The actor that initiated the run. + TriggeredBy AdvisorCheckTriggeredBy `protobuf:"varint,2,opt,name=triggered_by,json=triggeredBy,proto3,enum=advisors.v1.AdvisorCheckTriggeredBy" json:"triggered_by,omitempty"` + // When the run began. + StartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` + // When the run completed; unset while it is still running. + FinishedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=finished_at,json=finishedAt,proto3" json:"finished_at,omitempty"` + // Number of distinct checks the run executed. + ChecksCount int32 `protobuf:"varint,5,opt,name=checks_count,json=checksCount,proto3" json:"checks_count,omitempty"` + // Number of distinct services the run covered. + ServicesCount int32 `protobuf:"varint,6,opt,name=services_count,json=servicesCount,proto3" json:"services_count,omitempty"` + // Number of findings, i.e. checks that detected an issue. + FindingsCount int32 `protobuf:"varint,7,opt,name=findings_count,json=findingsCount,proto3" json:"findings_count,omitempty"` + // Number of checks that could not be executed at all. + ErrorsCount int32 `protobuf:"varint,8,opt,name=errors_count,json=errorsCount,proto3" json:"errors_count,omitempty"` + // Number of findings per severity, most severe first. A repeated field rather + // than a map so severity stays a typed enum instead of a free-form key. + SeverityCounts []*SeverityCount `protobuf:"bytes,9,rep,name=severity_counts,json=severityCounts,proto3" json:"severity_counts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdvisorRun) Reset() { + *x = AdvisorRun{} + mi := &file_advisors_v1_advisors_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListFailedServicesRequest) String() string { +func (x *AdvisorRun) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListFailedServicesRequest) ProtoMessage() {} +func (*AdvisorRun) ProtoMessage() {} -func (x *ListFailedServicesRequest) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[14] +func (x *AdvisorRun) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1054,33 +2538,98 @@ func (x *ListFailedServicesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListFailedServicesRequest.ProtoReflect.Descriptor instead. -func (*ListFailedServicesRequest) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{14} +// Deprecated: Use AdvisorRun.ProtoReflect.Descriptor instead. +func (*AdvisorRun) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{34} +} + +func (x *AdvisorRun) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AdvisorRun) GetTriggeredBy() AdvisorCheckTriggeredBy { + if x != nil { + return x.TriggeredBy + } + return AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED +} + +func (x *AdvisorRun) GetStartedAt() *timestamppb.Timestamp { + if x != nil { + return x.StartedAt + } + return nil +} + +func (x *AdvisorRun) GetFinishedAt() *timestamppb.Timestamp { + if x != nil { + return x.FinishedAt + } + return nil +} + +func (x *AdvisorRun) GetChecksCount() int32 { + if x != nil { + return x.ChecksCount + } + return 0 +} + +func (x *AdvisorRun) GetServicesCount() int32 { + if x != nil { + return x.ServicesCount + } + return 0 +} + +func (x *AdvisorRun) GetFindingsCount() int32 { + if x != nil { + return x.FindingsCount + } + return 0 +} + +func (x *AdvisorRun) GetErrorsCount() int32 { + if x != nil { + return x.ErrorsCount + } + return 0 +} + +func (x *AdvisorRun) GetSeverityCounts() []*SeverityCount { + if x != nil { + return x.SeverityCounts + } + return nil } -type ListFailedServicesResponse struct { +// SeverityCount is the number of findings a run produced at a single severity. +type SeverityCount struct { state protoimpl.MessageState `protogen:"open.v1"` - Result []*CheckResultSummary `protobuf:"bytes,1,rep,name=result,proto3" json:"result,omitempty"` + Severity v1.Severity `protobuf:"varint,1,opt,name=severity,proto3,enum=management.v1.Severity" json:"severity,omitempty"` + Count int32 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListFailedServicesResponse) Reset() { - *x = ListFailedServicesResponse{} - mi := &file_advisors_v1_advisors_proto_msgTypes[15] +func (x *SeverityCount) Reset() { + *x = SeverityCount{} + mi := &file_advisors_v1_advisors_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListFailedServicesResponse) String() string { +func (x *SeverityCount) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListFailedServicesResponse) ProtoMessage() {} +func (*SeverityCount) ProtoMessage() {} -func (x *ListFailedServicesResponse) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[15] +func (x *SeverityCount) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1091,45 +2640,56 @@ func (x *ListFailedServicesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListFailedServicesResponse.ProtoReflect.Descriptor instead. -func (*ListFailedServicesResponse) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{15} +// Deprecated: Use SeverityCount.ProtoReflect.Descriptor instead. +func (*SeverityCount) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{35} } -func (x *ListFailedServicesResponse) GetResult() []*CheckResultSummary { +func (x *SeverityCount) GetSeverity() v1.Severity { if x != nil { - return x.Result + return x.Severity } - return nil + return v1.Severity(0) +} + +func (x *SeverityCount) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 } -type GetFailedChecksRequest struct { +type ListRunsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Maximum number of results per page. PageSize *int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3,oneof" json:"page_size,omitempty"` // Index of the requested page, starts from 0. PageIndex *int32 `protobuf:"varint,2,opt,name=page_index,json=pageIndex,proto3,oneof" json:"page_index,omitempty"` - // Service ID. - ServiceId string `protobuf:"bytes,3,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + // Filter by the actor that initiated the run. + TriggeredBy *AdvisorCheckTriggeredBy `protobuf:"varint,3,opt,name=triggered_by,json=triggeredBy,proto3,enum=advisors.v1.AdvisorCheckTriggeredBy,oneof" json:"triggered_by,omitempty"` + // Return only runs started at or after this time. + From *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"` + // Return only runs started at or before this time. + To *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetFailedChecksRequest) Reset() { - *x = GetFailedChecksRequest{} - mi := &file_advisors_v1_advisors_proto_msgTypes[16] +func (x *ListRunsRequest) Reset() { + *x = ListRunsRequest{} + mi := &file_advisors_v1_advisors_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetFailedChecksRequest) String() string { +func (x *ListRunsRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetFailedChecksRequest) ProtoMessage() {} +func (*ListRunsRequest) ProtoMessage() {} -func (x *GetFailedChecksRequest) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[16] +func (x *ListRunsRequest) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1140,59 +2700,73 @@ func (x *GetFailedChecksRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetFailedChecksRequest.ProtoReflect.Descriptor instead. -func (*GetFailedChecksRequest) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{16} +// Deprecated: Use ListRunsRequest.ProtoReflect.Descriptor instead. +func (*ListRunsRequest) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{36} } -func (x *GetFailedChecksRequest) GetPageSize() int32 { +func (x *ListRunsRequest) GetPageSize() int32 { if x != nil && x.PageSize != nil { return *x.PageSize } return 0 } -func (x *GetFailedChecksRequest) GetPageIndex() int32 { +func (x *ListRunsRequest) GetPageIndex() int32 { if x != nil && x.PageIndex != nil { return *x.PageIndex } return 0 } -func (x *GetFailedChecksRequest) GetServiceId() string { +func (x *ListRunsRequest) GetTriggeredBy() AdvisorCheckTriggeredBy { + if x != nil && x.TriggeredBy != nil { + return *x.TriggeredBy + } + return AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED +} + +func (x *ListRunsRequest) GetFrom() *timestamppb.Timestamp { if x != nil { - return x.ServiceId + return x.From } - return "" + return nil +} + +func (x *ListRunsRequest) GetTo() *timestamppb.Timestamp { + if x != nil { + return x.To + } + return nil } -type GetFailedChecksResponse struct { +type ListRunsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Total number of results. TotalItems int32 `protobuf:"varint,1,opt,name=total_items,json=totalItems,proto3" json:"total_items,omitempty"` // Total number of pages. TotalPages int32 `protobuf:"varint,2,opt,name=total_pages,json=totalPages,proto3" json:"total_pages,omitempty"` - // Check results - Results []*CheckResult `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"` + // Runs, most recently started first. + Results []*AdvisorRun `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetFailedChecksResponse) Reset() { - *x = GetFailedChecksResponse{} - mi := &file_advisors_v1_advisors_proto_msgTypes[17] +func (x *ListRunsResponse) Reset() { + *x = ListRunsResponse{} + mi := &file_advisors_v1_advisors_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetFailedChecksResponse) String() string { +func (x *ListRunsResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetFailedChecksResponse) ProtoMessage() {} +func (*ListRunsResponse) ProtoMessage() {} -func (x *GetFailedChecksResponse) ProtoReflect() protoreflect.Message { - mi := &file_advisors_v1_advisors_proto_msgTypes[17] +func (x *ListRunsResponse) ProtoReflect() protoreflect.Message { + mi := &file_advisors_v1_advisors_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1203,26 +2777,26 @@ func (x *GetFailedChecksResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetFailedChecksResponse.ProtoReflect.Descriptor instead. -func (*GetFailedChecksResponse) Descriptor() ([]byte, []int) { - return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{17} +// Deprecated: Use ListRunsResponse.ProtoReflect.Descriptor instead. +func (*ListRunsResponse) Descriptor() ([]byte, []int) { + return file_advisors_v1_advisors_proto_rawDescGZIP(), []int{37} } -func (x *GetFailedChecksResponse) GetTotalItems() int32 { +func (x *ListRunsResponse) GetTotalItems() int32 { if x != nil { return x.TotalItems } return 0 } -func (x *GetFailedChecksResponse) GetTotalPages() int32 { +func (x *ListRunsResponse) GetTotalPages() int32 { if x != nil { return x.TotalPages } return 0 } -func (x *GetFailedChecksResponse) GetResults() []*CheckResult { +func (x *ListRunsResponse) GetResults() []*AdvisorRun { if x != nil { return x.Results } @@ -1233,117 +2807,267 @@ var File_advisors_v1_advisors_proto protoreflect.FileDescriptor const file_advisors_v1_advisors_proto_rawDesc = "" + "\n" + - "\x1aadvisors/v1/advisors.proto\x12\vadvisors.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1cmanagement/v1/severity.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\x1a\x17validate/validate.proto\"\xcc\x02\n" + - "\x12AdvisorCheckResult\x12\x18\n" + - "\asummary\x18\x01 \x01(\tR\asummary\x12 \n" + - "\vdescription\x18\x02 \x01(\tR\vdescription\x123\n" + - "\bseverity\x18\x03 \x01(\x0e2\x17.management.v1.SeverityR\bseverity\x12C\n" + - "\x06labels\x18\x04 \x03(\v2+.advisors.v1.AdvisorCheckResult.LabelsEntryR\x06labels\x12\"\n" + - "\rread_more_url\x18\x05 \x01(\tR\vreadMoreUrl\x12!\n" + - "\fservice_name\x18\x06 \x01(\tR\vserviceName\x1a9\n" + - "\vLabelsEntry\x12\x10\n" + + "\x1aadvisors/v1/advisors.proto\x12\vadvisors.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1cmanagement/v1/severity.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\x1a\x17validate/validate.proto\"\xcc\x01\n" + + "\x11AdvisorCheckQuery\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x14\n" + + "\x05query\x18\x02 \x01(\tR\x05query\x12N\n" + + "\n" + + "parameters\x18\x03 \x03(\v2..advisors.v1.AdvisorCheckQuery.ParametersEntryR\n" + + "parameters\x1a=\n" + + "\x0fParametersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf0\x02\n" + - "\x12CheckResultSummary\x12!\n" + - "\fservice_name\x18\x01 \x01(\tR\vserviceName\x12\x1d\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x85\x04\n" + + "\fAdvisorCheck\x126\n" + + "\x04name\x18\x01 \x01(\tB\"\xfaB\x1fr\x1d\x18\x80\x012\x18^[a-zA-Z_][a-zA-Z0-9_]*$R\x04name\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x18\n" + + "\asummary\x18\x04 \x01(\tR\asummary\x12=\n" + + "\binterval\x18\x05 \x01(\x0e2!.advisors.v1.AdvisorCheckIntervalR\binterval\x12C\n" + "\n" + - "service_id\x18\x02 \x01(\tR\tserviceId\x12'\n" + - "\x0femergency_count\x18\x03 \x01(\rR\x0eemergencyCount\x12\x1f\n" + - "\valert_count\x18\x04 \x01(\rR\n" + - "alertCount\x12%\n" + - "\x0ecritical_count\x18\x05 \x01(\rR\rcriticalCount\x12\x1f\n" + - "\verror_count\x18\x06 \x01(\rR\n" + - "errorCount\x12#\n" + - "\rwarning_count\x18\a \x01(\rR\fwarningCount\x12!\n" + - "\fnotice_count\x18\b \x01(\rR\vnoticeCount\x12\x1d\n" + + "technology\x18\x06 \x01(\x0e2#.advisors.v1.AdvisorCheckTechnologyR\n" + + "technology\x12\x1a\n" + + "\bcategory\x18\a \x01(\tR\bcategory\x12 \n" + + "\vsubcategory\x18\b \x01(\tR\vsubcategory\x12!\n" + + "\fuser_defined\x18\t \x01(\bR\vuserDefined\x128\n" + + "\aqueries\x18\n" + + " \x03(\v2\x1e.advisors.v1.AdvisorCheckQueryR\aqueries\x12\x16\n" + + "\x06script\x18\v \x01(\tR\x06script\x120\n" + + "\x14disabled_service_ids\x18\f \x03(\tR\x12disabledServiceIds\"\xf4\x01\n" + + "\aAdvisor\x12\x16\n" + + "\x04name\x18\x01 \x01(\tB\x02\x18\x01R\x04name\x12$\n" + + "\vdescription\x18\x02 \x01(\tB\x02\x18\x01R\vdescription\x12\x1c\n" + + "\asummary\x18\x03 \x01(\tB\x02\x18\x01R\asummary\x12\x1c\n" + + "\acomment\x18\x04 \x01(\tB\x02\x18\x01R\acomment\x12\x1a\n" + + "\bcategory\x18\x05 \x01(\tR\bcategory\x12 \n" + + "\vsubcategory\x18\x06 \x01(\tR\vsubcategory\x121\n" + + "\x06checks\x18\a \x03(\v2\x19.advisors.v1.AdvisorCheckR\x06checks\"\xb6\x01\n" + + "\x18ChangeAdvisorCheckParams\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" + + "\x06enable\x18\x02 \x01(\bH\x00R\x06enable\x88\x01\x01\x12=\n" + + "\binterval\x18\x04 \x01(\x0e2!.advisors.v1.AdvisorCheckIntervalR\binterval\x12\x1f\n" + + "\vservice_ids\x18\x05 \x03(\tR\n" + + "serviceIdsB\t\n" + + "\a_enable\"R\n" + + "\x19StartAdvisorChecksRequest\x12\x14\n" + + "\x05names\x18\x01 \x03(\tR\x05names\x12\x1f\n" + + "\vservice_ids\x18\x02 \x03(\tR\n" + + "serviceIds\"3\n" + + "\x1aStartAdvisorChecksResponse\x12\x15\n" + + "\x06run_id\x18\x01 \x01(\tR\x05runId\"\x1a\n" + + "\x18ListAdvisorChecksRequest\"N\n" + + "\x19ListAdvisorChecksResponse\x121\n" + + "\x06checks\x18\x01 \x03(\v2\x19.advisors.v1.AdvisorCheckR\x06checks\",\n" + + "\x16GetAdvisorCheckRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"J\n" + + "\x17GetAdvisorCheckResponse\x12/\n" + + "\x05check\x18\x01 \x01(\v2\x19.advisors.v1.AdvisorCheckR\x05check\"L\n" + + "\x19CreateAdvisorCheckRequest\x12/\n" + + "\x05check\x18\x01 \x01(\v2\x19.advisors.v1.AdvisorCheckR\x05check\"M\n" + + "\x1aCreateAdvisorCheckResponse\x12/\n" + + "\x05check\x18\x01 \x01(\v2\x19.advisors.v1.AdvisorCheckR\x05check\"`\n" + + "\x19UpdateAdvisorCheckRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12/\n" + + "\x05check\x18\x02 \x01(\v2\x19.advisors.v1.AdvisorCheckR\x05check\"M\n" + + "\x1aUpdateAdvisorCheckResponse\x12/\n" + + "\x05check\x18\x01 \x01(\v2\x19.advisors.v1.AdvisorCheckR\x05check\"/\n" + + "\x19DeleteAdvisorCheckRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\x1c\n" + + "\x1aDeleteAdvisorCheckResponse\"r\n" + + "\x17TestAdvisorCheckRequest\x12/\n" + + "\x05check\x18\x01 \x01(\v2\x19.advisors.v1.AdvisorCheckR\x05check\x12&\n" + "\n" + - "info_count\x18\t \x01(\rR\tinfoCount\x12\x1f\n" + - "\vdebug_count\x18\n" + - " \x01(\rR\n" + - "debugCount\"\x98\x03\n" + - "\vCheckResult\x12\x18\n" + + "service_id\x18\x02 \x01(\tB\a\xfaB\x04r\x02\x10\x01R\tserviceId\"\x92\x03\n" + + "\x16TestAdvisorCheckResult\x12\x18\n" + "\asummary\x18\x01 \x01(\tR\asummary\x12 \n" + "\vdescription\x18\x02 \x01(\tR\vdescription\x123\n" + - "\bseverity\x18\x03 \x01(\x0e2\x17.management.v1.SeverityR\bseverity\x12<\n" + - "\x06labels\x18\x04 \x03(\v2$.advisors.v1.CheckResult.LabelsEntryR\x06labels\x12\"\n" + + "\bseverity\x18\x03 \x01(\x0e2\x17.management.v1.SeverityR\bseverity\x12G\n" + + "\x06labels\x18\x04 \x03(\v2/.advisors.v1.TestAdvisorCheckResult.LabelsEntryR\x06labels\x12\"\n" + "\rread_more_url\x18\x05 \x01(\tR\vreadMoreUrl\x12!\n" + "\fservice_name\x18\x06 \x01(\tR\vserviceName\x12\x1d\n" + "\n" + "service_id\x18\a \x01(\tR\tserviceId\x12\x1d\n" + "\n" + - "check_name\x18\b \x01(\tR\tcheckName\x12\x1a\n" + - "\bsilenced\x18\n" + - " \x01(\bR\bsilenced\x1a9\n" + + "check_name\x18\b \x01(\tR\tcheckName\x1a9\n" + "\vLabelsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf0\x01\n" + - "\fAdvisorCheck\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + - "\aenabled\x18\x02 \x01(\bR\aenabled\x12 \n" + - "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x18\n" + - "\asummary\x18\x04 \x01(\tR\asummary\x12=\n" + - "\binterval\x18\x05 \x01(\x0e2!.advisors.v1.AdvisorCheckIntervalR\binterval\x127\n" + - "\x06family\x18\x06 \x01(\x0e2\x1f.advisors.v1.AdvisorCheckFamilyR\x06family\"\xc2\x01\n" + - "\aAdvisor\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + - "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x18\n" + - "\asummary\x18\x03 \x01(\tR\asummary\x12\x18\n" + - "\acomment\x18\x04 \x01(\tR\acomment\x12\x1a\n" + - "\bcategory\x18\x05 \x01(\tR\bcategory\x121\n" + - "\x06checks\x18\x06 \x03(\v2\x19.advisors.v1.AdvisorCheckR\x06checks\"\x95\x01\n" + - "\x18ChangeAdvisorCheckParams\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" + - "\x06enable\x18\x02 \x01(\bH\x00R\x06enable\x88\x01\x01\x12=\n" + - "\binterval\x18\x04 \x01(\x0e2!.advisors.v1.AdvisorCheckIntervalR\bintervalB\t\n" + - "\a_enable\"1\n" + - "\x19StartAdvisorChecksRequest\x12\x14\n" + - "\x05names\x18\x01 \x03(\tR\x05names\"\x1c\n" + - "\x1aStartAdvisorChecksResponse\"\x1a\n" + - "\x18ListAdvisorChecksRequest\"N\n" + - "\x19ListAdvisorChecksResponse\x121\n" + - "\x06checks\x18\x01 \x03(\v2\x19.advisors.v1.AdvisorCheckR\x06checks\"\x15\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"~\n" + + "\x18TestAdvisorCheckResponse\x12=\n" + + "\aresults\x18\x01 \x03(\v2#.advisors.v1.TestAdvisorCheckResultR\aresults\x12#\n" + + "\rscript_output\x18\x02 \x01(\tR\fscriptOutput\"i\n" + + "\"ListAdvisorCheckTestTargetsRequest\x12C\n" + + "\n" + + "technology\x18\x01 \x01(\x0e2#.advisors.v1.AdvisorCheckTechnologyR\n" + + "technology\"Z\n" + + "\x16AdvisorCheckTestTarget\x12\x1d\n" + + "\n" + + "service_id\x18\x01 \x01(\tR\tserviceId\x12!\n" + + "\fservice_name\x18\x02 \x01(\tR\vserviceName\"d\n" + + "#ListAdvisorCheckTestTargetsResponse\x12=\n" + + "\atargets\x18\x01 \x03(\v2#.advisors.v1.AdvisorCheckTestTargetR\atargets\"\x15\n" + "\x13ListAdvisorsRequest\"H\n" + "\x14ListAdvisorsResponse\x120\n" + "\badvisors\x18\x01 \x03(\v2\x14.advisors.v1.AdvisorR\badvisors\"[\n" + "\x1aChangeAdvisorChecksRequest\x12=\n" + "\x06params\x18\x01 \x03(\v2%.advisors.v1.ChangeAdvisorCheckParamsR\x06params\"\x1d\n" + - "\x1bChangeAdvisorChecksResponse\"\x1b\n" + - "\x19ListFailedServicesRequest\"U\n" + - "\x1aListFailedServicesResponse\x127\n" + - "\x06result\x18\x01 \x03(\v2\x1f.advisors.v1.CheckResultSummaryR\x06result\"\xac\x01\n" + - "\x16GetFailedChecksRequest\x12)\n" + + "\x1bChangeAdvisorChecksResponse\"\xf4\a\n" + + "\aInsight\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x15\n" + + "\x06run_id\x18\x02 \x01(\tR\x05runId\x12\x1d\n" + + "\n" + + "check_name\x18\x03 \x01(\tR\tcheckName\x12\x1a\n" + + "\bcategory\x18\x04 \x01(\tR\bcategory\x12 \n" + + "\vsubcategory\x18\x05 \x01(\tR\vsubcategory\x12=\n" + + "\binterval\x18\x06 \x01(\x0e2!.advisors.v1.AdvisorCheckIntervalR\binterval\x12\x1d\n" + + "\n" + + "service_id\x18\a \x01(\tR\tserviceId\x12!\n" + + "\fservice_name\x18\b \x01(\tR\vserviceName\x12!\n" + + "\fservice_type\x18\t \x01(\tR\vserviceType\x12\x17\n" + + "\anode_id\x18\n" + + " \x01(\tR\x06nodeId\x12\x1b\n" + + "\tnode_name\x18\v \x01(\tR\bnodeName\x12 \n" + + "\venvironment\x18\f \x01(\tR\venvironment\x12\x18\n" + + "\acluster\x18\r \x01(\tR\acluster\x12'\n" + + "\x0freplication_set\x18\x0e \x01(\tR\x0ereplicationSet\x12=\n" + + "\x06status\x18\x0f \x01(\x0e2%.advisors.v1.AdvisorCheckResultStatusR\x06status\x12\x18\n" + + "\asummary\x18\x10 \x01(\tR\asummary\x12 \n" + + "\vdescription\x18\x11 \x01(\tR\vdescription\x12\"\n" + + "\rread_more_url\x18\x12 \x01(\tR\vreadMoreUrl\x12\x18\n" + + "\aoutcome\x18\x13 \x01(\tR\aoutcome\x123\n" + + "\bseverity\x18\x14 \x01(\x0e2\x17.management.v1.SeverityR\bseverity\x128\n" + + "\x06labels\x18\x15 \x03(\v2 .advisors.v1.Insight.LabelsEntryR\x06labels\x129\n" + + "\n" + + "checked_at\x18\x16 \x01(\v2\x1a.google.protobuf.TimestampR\tcheckedAt\x12\x17\n" + + "\ais_read\x18\x17 \x01(\bR\x06isRead\x12G\n" + + "\ftriggered_by\x18\x18 \x01(\x0e2$.advisors.v1.AdvisorCheckTriggeredByR\vtriggeredBy\x12\x16\n" + + "\x06region\x18\x19 \x01(\tR\x06region\x12\x0e\n" + + "\x02az\x18\x1a \x01(\tR\x02az\x1a9\n" + + "\vLabelsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb6\x05\n" + + "\x13ListInsightsRequest\x12)\n" + "\tpage_size\x18\x01 \x01(\x05B\a\xfaB\x04\x1a\x02(\x01H\x00R\bpageSize\x88\x01\x01\x12+\n" + "\n" + "page_index\x18\x02 \x01(\x05B\a\xfaB\x04\x1a\x02(\x00H\x01R\tpageIndex\x88\x01\x01\x12\x1d\n" + "\n" + - "service_id\x18\x03 \x01(\tR\tserviceIdB\f\n" + + "service_id\x18\x03 \x01(\tR\tserviceId\x12B\n" + + "\x06status\x18\x04 \x01(\x0e2%.advisors.v1.AdvisorCheckResultStatusH\x02R\x06status\x88\x01\x01\x12\x1c\n" + + "\ais_read\x18\x05 \x01(\bH\x03R\x06isRead\x88\x01\x01\x12.\n" + + "\x04from\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\x04from\x12*\n" + + "\x02to\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\x02to\x12!\n" + + "\fservice_name\x18\b \x01(\tR\vserviceName\x12\x1b\n" + + "\tnode_name\x18\t \x01(\tR\bnodeName\x12\x1a\n" + + "\bcategory\x18\n" + + " \x01(\tR\bcategory\x12\x1d\n" + + "\n" + + "check_name\x18\v \x01(\tR\tcheckName\x128\n" + + "\bseverity\x18\f \x01(\x0e2\x17.management.v1.SeverityH\x04R\bseverity\x88\x01\x01\x12\x15\n" + + "\x06run_id\x18\r \x01(\tR\x05runId\x12L\n" + + "\ftriggered_by\x18\x0e \x01(\x0e2$.advisors.v1.AdvisorCheckTriggeredByH\x05R\vtriggeredBy\x88\x01\x01B\f\n" + + "\n" + + "_page_sizeB\r\n" + + "\v_page_indexB\t\n" + + "\a_statusB\n" + + "\n" + + "\b_is_readB\v\n" + + "\t_severityB\x0f\n" + + "\r_triggered_by\"\x88\x01\n" + + "\x14ListInsightsResponse\x12\x1f\n" + + "\vtotal_items\x18\x01 \x01(\x05R\n" + + "totalItems\x12\x1f\n" + + "\vtotal_pages\x18\x02 \x01(\x05R\n" + + "totalPages\x12.\n" + + "\aresults\x18\x03 \x03(\v2\x14.advisors.v1.InsightR\aresults\"!\n" + + "\x1fListInsightsFilterValuesRequest\"f\n" + + " ListInsightsFilterValuesResponse\x12#\n" + + "\rservice_names\x18\x01 \x03(\tR\fserviceNames\x12\x1d\n" + + "\n" + + "node_names\x18\x02 \x03(\tR\tnodeNames\"\xe3\x02\n" + + "\x0fInsightsFilters\x12\x1d\n" + + "\n" + + "check_name\x18\x01 \x01(\tR\tcheckName\x12!\n" + + "\fservice_name\x18\x02 \x01(\tR\vserviceName\x12\x1b\n" + + "\tnode_name\x18\x03 \x01(\tR\bnodeName\x12\x1a\n" + + "\bcategory\x18\x04 \x01(\tR\bcategory\x128\n" + + "\bseverity\x18\x05 \x01(\x0e2\x17.management.v1.SeverityH\x00R\bseverity\x88\x01\x01\x12B\n" + + "\x06status\x18\x06 \x01(\x0e2%.advisors.v1.AdvisorCheckResultStatusH\x01R\x06status\x88\x01\x01\x12\x1c\n" + + "\ais_read\x18\a \x01(\bH\x02R\x06isRead\x88\x01\x01\x12\x15\n" + + "\x06run_id\x18\b \x01(\tR\x05runIdB\v\n" + + "\t_severityB\t\n" + + "\a_statusB\n" + + "\n" + + "\b_is_read\"|\n" + + "\x17MarkInsightsReadRequest\x12\x10\n" + + "\x03ids\x18\x01 \x03(\tR\x03ids\x12\x17\n" + + "\ais_read\x18\x02 \x01(\bR\x06isRead\x126\n" + + "\afilters\x18\x03 \x01(\v2\x1c.advisors.v1.InsightsFiltersR\afilters\"\x1a\n" + + "\x18MarkInsightsReadResponse\"\xb6\x03\n" + + "\n" + + "AdvisorRun\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12G\n" + + "\ftriggered_by\x18\x02 \x01(\x0e2$.advisors.v1.AdvisorCheckTriggeredByR\vtriggeredBy\x129\n" + + "\n" + + "started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x12;\n" + + "\vfinished_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "finishedAt\x12!\n" + + "\fchecks_count\x18\x05 \x01(\x05R\vchecksCount\x12%\n" + + "\x0eservices_count\x18\x06 \x01(\x05R\rservicesCount\x12%\n" + + "\x0efindings_count\x18\a \x01(\x05R\rfindingsCount\x12!\n" + + "\ferrors_count\x18\b \x01(\x05R\verrorsCount\x12C\n" + + "\x0fseverity_counts\x18\t \x03(\v2\x1a.advisors.v1.SeverityCountR\x0eseverityCounts\"Z\n" + + "\rSeverityCount\x123\n" + + "\bseverity\x18\x01 \x01(\x0e2\x17.management.v1.SeverityR\bseverity\x12\x14\n" + + "\x05count\x18\x02 \x01(\x05R\x05count\"\xc1\x02\n" + + "\x0fListRunsRequest\x12)\n" + + "\tpage_size\x18\x01 \x01(\x05B\a\xfaB\x04\x1a\x02(\x01H\x00R\bpageSize\x88\x01\x01\x12+\n" + + "\n" + + "page_index\x18\x02 \x01(\x05B\a\xfaB\x04\x1a\x02(\x00H\x01R\tpageIndex\x88\x01\x01\x12L\n" + + "\ftriggered_by\x18\x03 \x01(\x0e2$.advisors.v1.AdvisorCheckTriggeredByH\x02R\vtriggeredBy\x88\x01\x01\x12.\n" + + "\x04from\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x04from\x12*\n" + + "\x02to\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x02toB\f\n" + "\n" + "_page_sizeB\r\n" + - "\v_page_index\"\x8f\x01\n" + - "\x17GetFailedChecksResponse\x12\x1f\n" + + "\v_page_indexB\x0f\n" + + "\r_triggered_by\"\x87\x01\n" + + "\x10ListRunsResponse\x12\x1f\n" + "\vtotal_items\x18\x01 \x01(\x05R\n" + "totalItems\x12\x1f\n" + "\vtotal_pages\x18\x02 \x01(\x05R\n" + - "totalPages\x122\n" + - "\aresults\x18\x03 \x03(\v2\x18.advisors.v1.CheckResultR\aresults*\xa9\x01\n" + + "totalPages\x121\n" + + "\aresults\x18\x03 \x03(\v2\x17.advisors.v1.AdvisorRunR\aresults*\xa9\x01\n" + "\x14AdvisorCheckInterval\x12&\n" + "\"ADVISOR_CHECK_INTERVAL_UNSPECIFIED\x10\x00\x12#\n" + "\x1fADVISOR_CHECK_INTERVAL_STANDARD\x10\x01\x12#\n" + "\x1fADVISOR_CHECK_INTERVAL_FREQUENT\x10\x02\x12\x1f\n" + - "\x1bADVISOR_CHECK_INTERVAL_RARE\x10\x03*\xa1\x01\n" + - "\x12AdvisorCheckFamily\x12$\n" + - " ADVISOR_CHECK_FAMILY_UNSPECIFIED\x10\x00\x12\x1e\n" + - "\x1aADVISOR_CHECK_FAMILY_MYSQL\x10\x01\x12#\n" + - "\x1fADVISOR_CHECK_FAMILY_POSTGRESQL\x10\x02\x12 \n" + - "\x1cADVISOR_CHECK_FAMILY_MONGODB\x10\x032\xee\n" + - "\n" + - "\x0eAdvisorService\x12\xf3\x01\n" + - "\x12ListFailedServices\x12&.advisors.v1.ListFailedServicesRequest\x1a'.advisors.v1.ListFailedServicesResponse\"\x8b\x01\x92Ae\x12\x14List Failed Services\x1aMReturns a list of services with failed checks and a summary of check results.\x82\xd3\xe4\x93\x02\x1d\x12\x1b/v1/advisors/failedServices\x12\xd5\x01\n" + - "\x0fGetFailedChecks\x12#.advisors.v1.GetFailedChecksRequest\x1a$.advisors.v1.GetFailedChecksResponse\"w\x92AR\x12\x19Get Failed Advisor Checks\x1a5Returns the latest check results for a given service.\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/advisors/checks/failed\x12\xb0\x02\n" + + "\x1bADVISOR_CHECK_INTERVAL_RARE\x10\x03*\xb5\x01\n" + + "\x16AdvisorCheckTechnology\x12(\n" + + "$ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED\x10\x00\x12\"\n" + + "\x1eADVISOR_CHECK_TECHNOLOGY_MYSQL\x10\x01\x12'\n" + + "#ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL\x10\x02\x12$\n" + + " ADVISOR_CHECK_TECHNOLOGY_MONGODB\x10\x03*\xba\x01\n" + + "\x18AdvisorCheckResultStatus\x12+\n" + + "'ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED\x10\x00\x12\"\n" + + "\x1eADVISOR_CHECK_RESULT_STATUS_OK\x10\x01\x12&\n" + + "\"ADVISOR_CHECK_RESULT_STATUS_FAILED\x10\x02\x12%\n" + + "!ADVISOR_CHECK_RESULT_STATUS_ERROR\x10\x03*\x94\x01\n" + + "\x17AdvisorCheckTriggeredBy\x12*\n" + + "&ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED\x10\x00\x12#\n" + + "\x1fADVISOR_CHECK_TRIGGERED_BY_USER\x10\x01\x12(\n" + + "$ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER\x10\x022\x84\x1c\n" + + "\x0eAdvisorService\x12\xcb\x01\n" + + "\bListRuns\x12\x1c.advisors.v1.ListRunsRequest\x1a\x1d.advisors.v1.ListRunsResponse\"\x81\x01\x92Ae\x12\x11List Advisor Runs\x1aPReturns the chronological history of Advisor check executions with their totals.\x82\xd3\xe4\x93\x02\x13\x12\x11/v1/advisors/runs\x12\xe1\x01\n" + + "\fListInsights\x12 .advisors.v1.ListInsightsRequest\x1a!.advisors.v1.ListInsightsResponse\"\x8b\x01\x92Ak\x12\x15List Advisor Insights\x1aRReturns the history of Advisor check results (insights), including their outcomes.\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/advisors/insights\x12\xbc\x02\n" + + "\x18ListInsightsFilterValues\x12,.advisors.v1.ListInsightsFilterValuesRequest\x1a-.advisors.v1.ListInsightsFilterValuesResponse\"\xc2\x01\x92A\x94\x01\x12#List Advisor Insights Filter Values\x1amReturns the distinct service and node names present in the Advisor insights, for populating filter dropdowns.\x82\xd3\xe4\x93\x02$\x12\"/v1/advisors/insights:filterValues\x12\x8c\x02\n" + + "\x10MarkInsightsRead\x12$.advisors.v1.MarkInsightsReadRequest\x1a%.advisors.v1.MarkInsightsReadResponse\"\xaa\x01\x92A~\x12\x1aMark Advisor Insights Read\x1a`Sets the read state on the specified Advisor insights. Set is_read to false to mark them unread.\x82\xd3\xe4\x93\x02#:\x01*\"\x1e/v1/advisors/insights:markRead\x12\xb0\x02\n" + "\x12StartAdvisorChecks\x12&.advisors.v1.StartAdvisorChecksRequest\x1a'.advisors.v1.StartAdvisorChecksResponse\"\xc8\x01\x92A\xa0\x01\x12\x14Start Advisor Checks\x1a\x87\x01Executes Advisor checks and returns when all checks are executed. All available checks will be started if check names aren't specified.\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/v1/advisors/checks:start\x12\xc3\x01\n" + "\x11ListAdvisorChecks\x12%.advisors.v1.ListAdvisorChecksRequest\x1a&.advisors.v1.ListAdvisorChecksResponse\"_\x92AA\x12\x13List Advisor Checks\x1a*List advisor checks available to the user.\x82\xd3\xe4\x93\x02\x15\x12\x13/v1/advisors/checks\x12\xa1\x01\n" + "\fListAdvisors\x12 .advisors.v1.ListAdvisorsRequest\x1a!.advisors.v1.ListAdvisorsResponse\"L\x92A5\x12\rList Advisors\x1a$List advisors available to the user.\x82\xd3\xe4\x93\x02\x0e\x12\f/v1/advisors\x12\xf0\x01\n" + - "\x13ChangeAdvisorChecks\x12'.advisors.v1.ChangeAdvisorChecksRequest\x1a(.advisors.v1.ChangeAdvisorChecksResponse\"\x85\x01\x92AX\x12\x15Change Advisor Checks\x1a?Enables/disables advisor checks or changes their exec interval.\x82\xd3\xe4\x93\x02$:\x01*\"\x1f/v1/advisors/checks:batchChangeB\xa0\x01\n" + + "\x13ChangeAdvisorChecks\x12'.advisors.v1.ChangeAdvisorChecksRequest\x1a(.advisors.v1.ChangeAdvisorChecksResponse\"\x85\x01\x92AX\x12\x15Change Advisor Checks\x1a?Enables/disables advisor checks or changes their exec interval.\x82\xd3\xe4\x93\x02$:\x01*\"\x1f/v1/advisors/checks:batchChange\x12\xe2\x01\n" + + "\x0fGetAdvisorCheck\x12#.advisors.v1.GetAdvisorCheckRequest\x1a$.advisors.v1.GetAdvisorCheckResponse\"\x83\x01\x92A^\x12\x11Get Advisor Check\x1aIReturns a single advisor check by name, including its queries and script.\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/advisors/checks/{name}\x12\xca\x01\n" + + "\x12CreateAdvisorCheck\x12&.advisors.v1.CreateAdvisorCheckRequest\x1a'.advisors.v1.CreateAdvisorCheckResponse\"c\x92AB\x12\x14Create Advisor Check\x1a*Creates a new user-authored advisor check.\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/advisors/checks\x12\xf1\x02\n" + + "\x12UpdateAdvisorCheck\x12&.advisors.v1.UpdateAdvisorCheckRequest\x1a'.advisors.v1.UpdateAdvisorCheckResponse\"\x89\x02\x92A\xe0\x01\x12\x14Update Advisor Check\x1a\xc7\x01Updates an existing user-authored advisor check. Percona-shipped checks cannot be modified. A check cannot be renamed: the name in the request body must either be empty or match the name in the path.\x82\xd3\xe4\x93\x02\x1f:\x01*\x1a\x1a/v1/advisors/checks/{name}\x12\x9e\x02\n" + + "\x10TestAdvisorCheck\x12$.advisors.v1.TestAdvisorCheckRequest\x1a%.advisors.v1.TestAdvisorCheckResponse\"\xbc\x01\x92A\x95\x01\x12\x12Test Advisor Check\x1a\x7fExecutes an advisor check definition against a single service without saving the check; results are returned and not persisted.\x82\xd3\xe4\x93\x02\x1d:\x01*\"\x18/v1/advisors/checks:test\x12\xa2\x02\n" + + "\x1bListAdvisorCheckTestTargets\x12/.advisors.v1.ListAdvisorCheckTestTargetsRequest\x1a0.advisors.v1.ListAdvisorCheckTestTargetsResponse\"\x9f\x01\x92Au\x12\x1fList Advisor Check Test Targets\x1aRLists the services an advisor check of the given technology can be tested against.\x82\xd3\xe4\x93\x02!\x12\x1f/v1/advisors/checks:testTargets\x12\xf5\x01\n" + + "\x12DeleteAdvisorCheck\x12&.advisors.v1.DeleteAdvisorCheckRequest\x1a'.advisors.v1.DeleteAdvisorCheckResponse\"\x8d\x01\x92Ah\x12\x14Delete Advisor Check\x1aPDeletes a user-authored advisor check. Percona-shipped checks cannot be deleted.\x82\xd3\xe4\x93\x02\x1c*\x1a/v1/advisors/checks/{name}B\xa0\x01\n" + "\x0fcom.advisors.v1B\rAdvisorsProtoP\x01Z1github.com/percona/pmm/api/advisors/v1;advisorsv1\xa2\x02\x03AXX\xaa\x02\vAdvisors.V1\xca\x02\vAdvisors\\V1\xe2\x02\x17Advisors\\V1\\GPBMetadata\xea\x02\fAdvisors::V1b\x06proto3" var ( @@ -1359,66 +3083,137 @@ func file_advisors_v1_advisors_proto_rawDescGZIP() []byte { } var ( - file_advisors_v1_advisors_proto_enumTypes = make([]protoimpl.EnumInfo, 2) - file_advisors_v1_advisors_proto_msgTypes = make([]protoimpl.MessageInfo, 20) + file_advisors_v1_advisors_proto_enumTypes = make([]protoimpl.EnumInfo, 4) + file_advisors_v1_advisors_proto_msgTypes = make([]protoimpl.MessageInfo, 41) file_advisors_v1_advisors_proto_goTypes = []any{ - AdvisorCheckInterval(0), // 0: advisors.v1.AdvisorCheckInterval - AdvisorCheckFamily(0), // 1: advisors.v1.AdvisorCheckFamily - (*AdvisorCheckResult)(nil), // 2: advisors.v1.AdvisorCheckResult - (*CheckResultSummary)(nil), // 3: advisors.v1.CheckResultSummary - (*CheckResult)(nil), // 4: advisors.v1.CheckResult - (*AdvisorCheck)(nil), // 5: advisors.v1.AdvisorCheck - (*Advisor)(nil), // 6: advisors.v1.Advisor - (*ChangeAdvisorCheckParams)(nil), // 7: advisors.v1.ChangeAdvisorCheckParams - (*StartAdvisorChecksRequest)(nil), // 8: advisors.v1.StartAdvisorChecksRequest - (*StartAdvisorChecksResponse)(nil), // 9: advisors.v1.StartAdvisorChecksResponse - (*ListAdvisorChecksRequest)(nil), // 10: advisors.v1.ListAdvisorChecksRequest - (*ListAdvisorChecksResponse)(nil), // 11: advisors.v1.ListAdvisorChecksResponse - (*ListAdvisorsRequest)(nil), // 12: advisors.v1.ListAdvisorsRequest - (*ListAdvisorsResponse)(nil), // 13: advisors.v1.ListAdvisorsResponse - (*ChangeAdvisorChecksRequest)(nil), // 14: advisors.v1.ChangeAdvisorChecksRequest - (*ChangeAdvisorChecksResponse)(nil), // 15: advisors.v1.ChangeAdvisorChecksResponse - (*ListFailedServicesRequest)(nil), // 16: advisors.v1.ListFailedServicesRequest - (*ListFailedServicesResponse)(nil), // 17: advisors.v1.ListFailedServicesResponse - (*GetFailedChecksRequest)(nil), // 18: advisors.v1.GetFailedChecksRequest - (*GetFailedChecksResponse)(nil), // 19: advisors.v1.GetFailedChecksResponse - nil, // 20: advisors.v1.AdvisorCheckResult.LabelsEntry - nil, // 21: advisors.v1.CheckResult.LabelsEntry - v1.Severity(0), // 22: management.v1.Severity + AdvisorCheckInterval(0), // 0: advisors.v1.AdvisorCheckInterval + AdvisorCheckTechnology(0), // 1: advisors.v1.AdvisorCheckTechnology + AdvisorCheckResultStatus(0), // 2: advisors.v1.AdvisorCheckResultStatus + AdvisorCheckTriggeredBy(0), // 3: advisors.v1.AdvisorCheckTriggeredBy + (*AdvisorCheckQuery)(nil), // 4: advisors.v1.AdvisorCheckQuery + (*AdvisorCheck)(nil), // 5: advisors.v1.AdvisorCheck + (*Advisor)(nil), // 6: advisors.v1.Advisor + (*ChangeAdvisorCheckParams)(nil), // 7: advisors.v1.ChangeAdvisorCheckParams + (*StartAdvisorChecksRequest)(nil), // 8: advisors.v1.StartAdvisorChecksRequest + (*StartAdvisorChecksResponse)(nil), // 9: advisors.v1.StartAdvisorChecksResponse + (*ListAdvisorChecksRequest)(nil), // 10: advisors.v1.ListAdvisorChecksRequest + (*ListAdvisorChecksResponse)(nil), // 11: advisors.v1.ListAdvisorChecksResponse + (*GetAdvisorCheckRequest)(nil), // 12: advisors.v1.GetAdvisorCheckRequest + (*GetAdvisorCheckResponse)(nil), // 13: advisors.v1.GetAdvisorCheckResponse + (*CreateAdvisorCheckRequest)(nil), // 14: advisors.v1.CreateAdvisorCheckRequest + (*CreateAdvisorCheckResponse)(nil), // 15: advisors.v1.CreateAdvisorCheckResponse + (*UpdateAdvisorCheckRequest)(nil), // 16: advisors.v1.UpdateAdvisorCheckRequest + (*UpdateAdvisorCheckResponse)(nil), // 17: advisors.v1.UpdateAdvisorCheckResponse + (*DeleteAdvisorCheckRequest)(nil), // 18: advisors.v1.DeleteAdvisorCheckRequest + (*DeleteAdvisorCheckResponse)(nil), // 19: advisors.v1.DeleteAdvisorCheckResponse + (*TestAdvisorCheckRequest)(nil), // 20: advisors.v1.TestAdvisorCheckRequest + (*TestAdvisorCheckResult)(nil), // 21: advisors.v1.TestAdvisorCheckResult + (*TestAdvisorCheckResponse)(nil), // 22: advisors.v1.TestAdvisorCheckResponse + (*ListAdvisorCheckTestTargetsRequest)(nil), // 23: advisors.v1.ListAdvisorCheckTestTargetsRequest + (*AdvisorCheckTestTarget)(nil), // 24: advisors.v1.AdvisorCheckTestTarget + (*ListAdvisorCheckTestTargetsResponse)(nil), // 25: advisors.v1.ListAdvisorCheckTestTargetsResponse + (*ListAdvisorsRequest)(nil), // 26: advisors.v1.ListAdvisorsRequest + (*ListAdvisorsResponse)(nil), // 27: advisors.v1.ListAdvisorsResponse + (*ChangeAdvisorChecksRequest)(nil), // 28: advisors.v1.ChangeAdvisorChecksRequest + (*ChangeAdvisorChecksResponse)(nil), // 29: advisors.v1.ChangeAdvisorChecksResponse + (*Insight)(nil), // 30: advisors.v1.Insight + (*ListInsightsRequest)(nil), // 31: advisors.v1.ListInsightsRequest + (*ListInsightsResponse)(nil), // 32: advisors.v1.ListInsightsResponse + (*ListInsightsFilterValuesRequest)(nil), // 33: advisors.v1.ListInsightsFilterValuesRequest + (*ListInsightsFilterValuesResponse)(nil), // 34: advisors.v1.ListInsightsFilterValuesResponse + (*InsightsFilters)(nil), // 35: advisors.v1.InsightsFilters + (*MarkInsightsReadRequest)(nil), // 36: advisors.v1.MarkInsightsReadRequest + (*MarkInsightsReadResponse)(nil), // 37: advisors.v1.MarkInsightsReadResponse + (*AdvisorRun)(nil), // 38: advisors.v1.AdvisorRun + (*SeverityCount)(nil), // 39: advisors.v1.SeverityCount + (*ListRunsRequest)(nil), // 40: advisors.v1.ListRunsRequest + (*ListRunsResponse)(nil), // 41: advisors.v1.ListRunsResponse + nil, // 42: advisors.v1.AdvisorCheckQuery.ParametersEntry + nil, // 43: advisors.v1.TestAdvisorCheckResult.LabelsEntry + nil, // 44: advisors.v1.Insight.LabelsEntry + v1.Severity(0), // 45: management.v1.Severity + (*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp } ) var file_advisors_v1_advisors_proto_depIdxs = []int32{ - 22, // 0: advisors.v1.AdvisorCheckResult.severity:type_name -> management.v1.Severity - 20, // 1: advisors.v1.AdvisorCheckResult.labels:type_name -> advisors.v1.AdvisorCheckResult.LabelsEntry - 22, // 2: advisors.v1.CheckResult.severity:type_name -> management.v1.Severity - 21, // 3: advisors.v1.CheckResult.labels:type_name -> advisors.v1.CheckResult.LabelsEntry - 0, // 4: advisors.v1.AdvisorCheck.interval:type_name -> advisors.v1.AdvisorCheckInterval - 1, // 5: advisors.v1.AdvisorCheck.family:type_name -> advisors.v1.AdvisorCheckFamily - 5, // 6: advisors.v1.Advisor.checks:type_name -> advisors.v1.AdvisorCheck - 0, // 7: advisors.v1.ChangeAdvisorCheckParams.interval:type_name -> advisors.v1.AdvisorCheckInterval - 5, // 8: advisors.v1.ListAdvisorChecksResponse.checks:type_name -> advisors.v1.AdvisorCheck - 6, // 9: advisors.v1.ListAdvisorsResponse.advisors:type_name -> advisors.v1.Advisor - 7, // 10: advisors.v1.ChangeAdvisorChecksRequest.params:type_name -> advisors.v1.ChangeAdvisorCheckParams - 3, // 11: advisors.v1.ListFailedServicesResponse.result:type_name -> advisors.v1.CheckResultSummary - 4, // 12: advisors.v1.GetFailedChecksResponse.results:type_name -> advisors.v1.CheckResult - 16, // 13: advisors.v1.AdvisorService.ListFailedServices:input_type -> advisors.v1.ListFailedServicesRequest - 18, // 14: advisors.v1.AdvisorService.GetFailedChecks:input_type -> advisors.v1.GetFailedChecksRequest - 8, // 15: advisors.v1.AdvisorService.StartAdvisorChecks:input_type -> advisors.v1.StartAdvisorChecksRequest - 10, // 16: advisors.v1.AdvisorService.ListAdvisorChecks:input_type -> advisors.v1.ListAdvisorChecksRequest - 12, // 17: advisors.v1.AdvisorService.ListAdvisors:input_type -> advisors.v1.ListAdvisorsRequest - 14, // 18: advisors.v1.AdvisorService.ChangeAdvisorChecks:input_type -> advisors.v1.ChangeAdvisorChecksRequest - 17, // 19: advisors.v1.AdvisorService.ListFailedServices:output_type -> advisors.v1.ListFailedServicesResponse - 19, // 20: advisors.v1.AdvisorService.GetFailedChecks:output_type -> advisors.v1.GetFailedChecksResponse - 9, // 21: advisors.v1.AdvisorService.StartAdvisorChecks:output_type -> advisors.v1.StartAdvisorChecksResponse - 11, // 22: advisors.v1.AdvisorService.ListAdvisorChecks:output_type -> advisors.v1.ListAdvisorChecksResponse - 13, // 23: advisors.v1.AdvisorService.ListAdvisors:output_type -> advisors.v1.ListAdvisorsResponse - 15, // 24: advisors.v1.AdvisorService.ChangeAdvisorChecks:output_type -> advisors.v1.ChangeAdvisorChecksResponse - 19, // [19:25] is the sub-list for method output_type - 13, // [13:19] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 42, // 0: advisors.v1.AdvisorCheckQuery.parameters:type_name -> advisors.v1.AdvisorCheckQuery.ParametersEntry + 0, // 1: advisors.v1.AdvisorCheck.interval:type_name -> advisors.v1.AdvisorCheckInterval + 1, // 2: advisors.v1.AdvisorCheck.technology:type_name -> advisors.v1.AdvisorCheckTechnology + 4, // 3: advisors.v1.AdvisorCheck.queries:type_name -> advisors.v1.AdvisorCheckQuery + 5, // 4: advisors.v1.Advisor.checks:type_name -> advisors.v1.AdvisorCheck + 0, // 5: advisors.v1.ChangeAdvisorCheckParams.interval:type_name -> advisors.v1.AdvisorCheckInterval + 5, // 6: advisors.v1.ListAdvisorChecksResponse.checks:type_name -> advisors.v1.AdvisorCheck + 5, // 7: advisors.v1.GetAdvisorCheckResponse.check:type_name -> advisors.v1.AdvisorCheck + 5, // 8: advisors.v1.CreateAdvisorCheckRequest.check:type_name -> advisors.v1.AdvisorCheck + 5, // 9: advisors.v1.CreateAdvisorCheckResponse.check:type_name -> advisors.v1.AdvisorCheck + 5, // 10: advisors.v1.UpdateAdvisorCheckRequest.check:type_name -> advisors.v1.AdvisorCheck + 5, // 11: advisors.v1.UpdateAdvisorCheckResponse.check:type_name -> advisors.v1.AdvisorCheck + 5, // 12: advisors.v1.TestAdvisorCheckRequest.check:type_name -> advisors.v1.AdvisorCheck + 45, // 13: advisors.v1.TestAdvisorCheckResult.severity:type_name -> management.v1.Severity + 43, // 14: advisors.v1.TestAdvisorCheckResult.labels:type_name -> advisors.v1.TestAdvisorCheckResult.LabelsEntry + 21, // 15: advisors.v1.TestAdvisorCheckResponse.results:type_name -> advisors.v1.TestAdvisorCheckResult + 1, // 16: advisors.v1.ListAdvisorCheckTestTargetsRequest.technology:type_name -> advisors.v1.AdvisorCheckTechnology + 24, // 17: advisors.v1.ListAdvisorCheckTestTargetsResponse.targets:type_name -> advisors.v1.AdvisorCheckTestTarget + 6, // 18: advisors.v1.ListAdvisorsResponse.advisors:type_name -> advisors.v1.Advisor + 7, // 19: advisors.v1.ChangeAdvisorChecksRequest.params:type_name -> advisors.v1.ChangeAdvisorCheckParams + 0, // 20: advisors.v1.Insight.interval:type_name -> advisors.v1.AdvisorCheckInterval + 2, // 21: advisors.v1.Insight.status:type_name -> advisors.v1.AdvisorCheckResultStatus + 45, // 22: advisors.v1.Insight.severity:type_name -> management.v1.Severity + 44, // 23: advisors.v1.Insight.labels:type_name -> advisors.v1.Insight.LabelsEntry + 46, // 24: advisors.v1.Insight.checked_at:type_name -> google.protobuf.Timestamp + 3, // 25: advisors.v1.Insight.triggered_by:type_name -> advisors.v1.AdvisorCheckTriggeredBy + 2, // 26: advisors.v1.ListInsightsRequest.status:type_name -> advisors.v1.AdvisorCheckResultStatus + 46, // 27: advisors.v1.ListInsightsRequest.from:type_name -> google.protobuf.Timestamp + 46, // 28: advisors.v1.ListInsightsRequest.to:type_name -> google.protobuf.Timestamp + 45, // 29: advisors.v1.ListInsightsRequest.severity:type_name -> management.v1.Severity + 3, // 30: advisors.v1.ListInsightsRequest.triggered_by:type_name -> advisors.v1.AdvisorCheckTriggeredBy + 30, // 31: advisors.v1.ListInsightsResponse.results:type_name -> advisors.v1.Insight + 45, // 32: advisors.v1.InsightsFilters.severity:type_name -> management.v1.Severity + 2, // 33: advisors.v1.InsightsFilters.status:type_name -> advisors.v1.AdvisorCheckResultStatus + 35, // 34: advisors.v1.MarkInsightsReadRequest.filters:type_name -> advisors.v1.InsightsFilters + 3, // 35: advisors.v1.AdvisorRun.triggered_by:type_name -> advisors.v1.AdvisorCheckTriggeredBy + 46, // 36: advisors.v1.AdvisorRun.started_at:type_name -> google.protobuf.Timestamp + 46, // 37: advisors.v1.AdvisorRun.finished_at:type_name -> google.protobuf.Timestamp + 39, // 38: advisors.v1.AdvisorRun.severity_counts:type_name -> advisors.v1.SeverityCount + 45, // 39: advisors.v1.SeverityCount.severity:type_name -> management.v1.Severity + 3, // 40: advisors.v1.ListRunsRequest.triggered_by:type_name -> advisors.v1.AdvisorCheckTriggeredBy + 46, // 41: advisors.v1.ListRunsRequest.from:type_name -> google.protobuf.Timestamp + 46, // 42: advisors.v1.ListRunsRequest.to:type_name -> google.protobuf.Timestamp + 38, // 43: advisors.v1.ListRunsResponse.results:type_name -> advisors.v1.AdvisorRun + 40, // 44: advisors.v1.AdvisorService.ListRuns:input_type -> advisors.v1.ListRunsRequest + 31, // 45: advisors.v1.AdvisorService.ListInsights:input_type -> advisors.v1.ListInsightsRequest + 33, // 46: advisors.v1.AdvisorService.ListInsightsFilterValues:input_type -> advisors.v1.ListInsightsFilterValuesRequest + 36, // 47: advisors.v1.AdvisorService.MarkInsightsRead:input_type -> advisors.v1.MarkInsightsReadRequest + 8, // 48: advisors.v1.AdvisorService.StartAdvisorChecks:input_type -> advisors.v1.StartAdvisorChecksRequest + 10, // 49: advisors.v1.AdvisorService.ListAdvisorChecks:input_type -> advisors.v1.ListAdvisorChecksRequest + 26, // 50: advisors.v1.AdvisorService.ListAdvisors:input_type -> advisors.v1.ListAdvisorsRequest + 28, // 51: advisors.v1.AdvisorService.ChangeAdvisorChecks:input_type -> advisors.v1.ChangeAdvisorChecksRequest + 12, // 52: advisors.v1.AdvisorService.GetAdvisorCheck:input_type -> advisors.v1.GetAdvisorCheckRequest + 14, // 53: advisors.v1.AdvisorService.CreateAdvisorCheck:input_type -> advisors.v1.CreateAdvisorCheckRequest + 16, // 54: advisors.v1.AdvisorService.UpdateAdvisorCheck:input_type -> advisors.v1.UpdateAdvisorCheckRequest + 20, // 55: advisors.v1.AdvisorService.TestAdvisorCheck:input_type -> advisors.v1.TestAdvisorCheckRequest + 23, // 56: advisors.v1.AdvisorService.ListAdvisorCheckTestTargets:input_type -> advisors.v1.ListAdvisorCheckTestTargetsRequest + 18, // 57: advisors.v1.AdvisorService.DeleteAdvisorCheck:input_type -> advisors.v1.DeleteAdvisorCheckRequest + 41, // 58: advisors.v1.AdvisorService.ListRuns:output_type -> advisors.v1.ListRunsResponse + 32, // 59: advisors.v1.AdvisorService.ListInsights:output_type -> advisors.v1.ListInsightsResponse + 34, // 60: advisors.v1.AdvisorService.ListInsightsFilterValues:output_type -> advisors.v1.ListInsightsFilterValuesResponse + 37, // 61: advisors.v1.AdvisorService.MarkInsightsRead:output_type -> advisors.v1.MarkInsightsReadResponse + 9, // 62: advisors.v1.AdvisorService.StartAdvisorChecks:output_type -> advisors.v1.StartAdvisorChecksResponse + 11, // 63: advisors.v1.AdvisorService.ListAdvisorChecks:output_type -> advisors.v1.ListAdvisorChecksResponse + 27, // 64: advisors.v1.AdvisorService.ListAdvisors:output_type -> advisors.v1.ListAdvisorsResponse + 29, // 65: advisors.v1.AdvisorService.ChangeAdvisorChecks:output_type -> advisors.v1.ChangeAdvisorChecksResponse + 13, // 66: advisors.v1.AdvisorService.GetAdvisorCheck:output_type -> advisors.v1.GetAdvisorCheckResponse + 15, // 67: advisors.v1.AdvisorService.CreateAdvisorCheck:output_type -> advisors.v1.CreateAdvisorCheckResponse + 17, // 68: advisors.v1.AdvisorService.UpdateAdvisorCheck:output_type -> advisors.v1.UpdateAdvisorCheckResponse + 22, // 69: advisors.v1.AdvisorService.TestAdvisorCheck:output_type -> advisors.v1.TestAdvisorCheckResponse + 25, // 70: advisors.v1.AdvisorService.ListAdvisorCheckTestTargets:output_type -> advisors.v1.ListAdvisorCheckTestTargetsResponse + 19, // 71: advisors.v1.AdvisorService.DeleteAdvisorCheck:output_type -> advisors.v1.DeleteAdvisorCheckResponse + 58, // [58:72] is the sub-list for method output_type + 44, // [44:58] is the sub-list for method input_type + 44, // [44:44] is the sub-list for extension type_name + 44, // [44:44] is the sub-list for extension extendee + 0, // [0:44] is the sub-list for field type_name } func init() { file_advisors_v1_advisors_proto_init() } @@ -1426,15 +3221,17 @@ func file_advisors_v1_advisors_proto_init() { if File_advisors_v1_advisors_proto != nil { return } - file_advisors_v1_advisors_proto_msgTypes[5].OneofWrappers = []any{} - file_advisors_v1_advisors_proto_msgTypes[16].OneofWrappers = []any{} + file_advisors_v1_advisors_proto_msgTypes[3].OneofWrappers = []any{} + file_advisors_v1_advisors_proto_msgTypes[27].OneofWrappers = []any{} + file_advisors_v1_advisors_proto_msgTypes[31].OneofWrappers = []any{} + file_advisors_v1_advisors_proto_msgTypes[36].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_advisors_v1_advisors_proto_rawDesc), len(file_advisors_v1_advisors_proto_rawDesc)), - NumEnums: 2, - NumMessages: 20, + NumEnums: 4, + NumMessages: 41, NumExtensions: 0, NumServices: 1, }, diff --git a/api/advisors/v1/advisors.pb.gw.go b/api/advisors/v1/advisors.pb.gw.go index 3897f4315fd..8413a03755f 100644 --- a/api/advisors/v1/advisors.pb.gw.go +++ b/api/advisors/v1/advisors.pb.gw.go @@ -35,32 +35,46 @@ var ( _ = metadata.Join ) -func request_AdvisorService_ListFailedServices_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +var filter_AdvisorService_ListRuns_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_AdvisorService_ListRuns_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq ListFailedServicesRequest + protoReq ListRunsRequest metadata runtime.ServerMetadata ) if req.Body != nil { _, _ = io.Copy(io.Discard, req.Body) } - msg, err := client.ListFailedServices(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_ListRuns_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ListRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_AdvisorService_ListFailedServices_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_AdvisorService_ListRuns_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq ListFailedServicesRequest + protoReq ListRunsRequest metadata runtime.ServerMetadata ) - msg, err := server.ListFailedServices(ctx, &protoReq) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_ListRuns_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListRuns(ctx, &protoReq) return msg, metadata, err } -var filter_AdvisorService_GetFailedChecks_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +var filter_AdvisorService_ListInsights_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} -func request_AdvisorService_GetFailedChecks_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func request_AdvisorService_ListInsights_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq GetFailedChecksRequest + protoReq ListInsightsRequest metadata runtime.ServerMetadata ) if req.Body != nil { @@ -69,25 +83,73 @@ func request_AdvisorService_GetFailedChecks_0(ctx context.Context, marshaler run if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_GetFailedChecks_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_ListInsights_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.GetFailedChecks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ListInsights(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_AdvisorService_GetFailedChecks_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { +func local_request_AdvisorService_ListInsights_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( - protoReq GetFailedChecksRequest + protoReq ListInsightsRequest metadata runtime.ServerMetadata ) if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_GetFailedChecks_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_ListInsights_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListInsights(ctx, &protoReq) + return msg, metadata, err +} + +func request_AdvisorService_ListInsightsFilterValues_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListInsightsFilterValuesRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ListInsightsFilterValues(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_ListInsightsFilterValues_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListInsightsFilterValuesRequest + metadata runtime.ServerMetadata + ) + msg, err := server.ListInsightsFilterValues(ctx, &protoReq) + return msg, metadata, err +} + +func request_AdvisorService_MarkInsightsRead_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq MarkInsightsReadRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.MarkInsightsRead(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_MarkInsightsRead_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq MarkInsightsReadRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.GetFailedChecks(ctx, &protoReq) + msg, err := server.MarkInsightsRead(ctx, &protoReq) return msg, metadata, err } @@ -187,51 +249,303 @@ func local_request_AdvisorService_ChangeAdvisorChecks_0(ctx context.Context, mar return msg, metadata, err } +func request_AdvisorService_GetAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetAdvisorCheckRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := client.GetAdvisorCheck(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_GetAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetAdvisorCheckRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := server.GetAdvisorCheck(ctx, &protoReq) + return msg, metadata, err +} + +func request_AdvisorService_CreateAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateAdvisorCheckRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.CreateAdvisorCheck(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_CreateAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateAdvisorCheckRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CreateAdvisorCheck(ctx, &protoReq) + return msg, metadata, err +} + +func request_AdvisorService_UpdateAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateAdvisorCheckRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := client.UpdateAdvisorCheck(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_UpdateAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateAdvisorCheckRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := server.UpdateAdvisorCheck(ctx, &protoReq) + return msg, metadata, err +} + +func request_AdvisorService_TestAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq TestAdvisorCheckRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.TestAdvisorCheck(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_TestAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq TestAdvisorCheckRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.TestAdvisorCheck(ctx, &protoReq) + return msg, metadata, err +} + +var filter_AdvisorService_ListAdvisorCheckTestTargets_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_AdvisorService_ListAdvisorCheckTestTargets_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListAdvisorCheckTestTargetsRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_ListAdvisorCheckTestTargets_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ListAdvisorCheckTestTargets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_ListAdvisorCheckTestTargets_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListAdvisorCheckTestTargetsRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AdvisorService_ListAdvisorCheckTestTargets_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListAdvisorCheckTestTargets(ctx, &protoReq) + return msg, metadata, err +} + +func request_AdvisorService_DeleteAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, client AdvisorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteAdvisorCheckRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := client.DeleteAdvisorCheck(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AdvisorService_DeleteAdvisorCheck_0(ctx context.Context, marshaler runtime.Marshaler, server AdvisorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteAdvisorCheckRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + protoReq.Name, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + msg, err := server.DeleteAdvisorCheck(ctx, &protoReq) + return msg, metadata, err +} + // RegisterAdvisorServiceHandlerServer registers the http handlers for service AdvisorService to "mux". // UnaryRPC :call AdvisorServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAdvisorServiceHandlerFromEndpoint instead. // GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. func RegisterAdvisorServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AdvisorServiceServer) error { - mux.Handle(http.MethodGet, pattern_AdvisorService_ListFailedServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_AdvisorService_ListRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListRuns", runtime.WithHTTPPathPattern("/v1/advisors/runs")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_ListRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_ListRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_AdvisorService_ListInsights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListInsights", runtime.WithHTTPPathPattern("/v1/advisors/insights")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_ListInsights_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_ListInsights_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_AdvisorService_ListInsightsFilterValues_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListFailedServices", runtime.WithHTTPPathPattern("/v1/advisors/failedServices")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListInsightsFilterValues", runtime.WithHTTPPathPattern("/v1/advisors/insights:filterValues")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_AdvisorService_ListFailedServices_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_AdvisorService_ListInsightsFilterValues_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AdvisorService_ListFailedServices_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AdvisorService_ListInsightsFilterValues_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_AdvisorService_GetFailedChecks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodPost, pattern_AdvisorService_MarkInsightsRead_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/GetFailedChecks", runtime.WithHTTPPathPattern("/v1/advisors/checks/failed")) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/MarkInsightsRead", runtime.WithHTTPPathPattern("/v1/advisors/insights:markRead")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_AdvisorService_GetFailedChecks_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_AdvisorService_MarkInsightsRead_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AdvisorService_GetFailedChecks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AdvisorService_MarkInsightsRead_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) mux.Handle(http.MethodPost, pattern_AdvisorService_StartAdvisorChecks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) @@ -313,6 +627,126 @@ func RegisterAdvisorServiceHandlerServer(ctx context.Context, mux *runtime.Serve } forward_AdvisorService_ChangeAdvisorChecks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_AdvisorService_GetAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/GetAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_GetAdvisorCheck_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_GetAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AdvisorService_CreateAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/CreateAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_CreateAdvisorCheck_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_CreateAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_AdvisorService_UpdateAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/UpdateAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_UpdateAdvisorCheck_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_UpdateAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AdvisorService_TestAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/TestAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks:test")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_TestAdvisorCheck_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_TestAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_AdvisorService_ListAdvisorCheckTestTargets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListAdvisorCheckTestTargets", runtime.WithHTTPPathPattern("/v1/advisors/checks:testTargets")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_ListAdvisorCheckTestTargets_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_ListAdvisorCheckTestTargets_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_AdvisorService_DeleteAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/advisors.v1.AdvisorService/DeleteAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AdvisorService_DeleteAdvisorCheck_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_DeleteAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -353,39 +787,73 @@ func RegisterAdvisorServiceHandler(ctx context.Context, mux *runtime.ServeMux, c // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in // "AdvisorServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. func RegisterAdvisorServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AdvisorServiceClient) error { - mux.Handle(http.MethodGet, pattern_AdvisorService_ListFailedServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_AdvisorService_ListRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListFailedServices", runtime.WithHTTPPathPattern("/v1/advisors/failedServices")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListRuns", runtime.WithHTTPPathPattern("/v1/advisors/runs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_AdvisorService_ListFailedServices_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_AdvisorService_ListRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AdvisorService_ListFailedServices_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AdvisorService_ListRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle(http.MethodGet, pattern_AdvisorService_GetFailedChecks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle(http.MethodGet, pattern_AdvisorService_ListInsights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/GetFailedChecks", runtime.WithHTTPPathPattern("/v1/advisors/checks/failed")) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListInsights", runtime.WithHTTPPathPattern("/v1/advisors/insights")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_AdvisorService_GetFailedChecks_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_AdvisorService_ListInsights_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_AdvisorService_GetFailedChecks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_AdvisorService_ListInsights_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_AdvisorService_ListInsightsFilterValues_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListInsightsFilterValues", runtime.WithHTTPPathPattern("/v1/advisors/insights:filterValues")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_ListInsightsFilterValues_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_ListInsightsFilterValues_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AdvisorService_MarkInsightsRead_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/MarkInsightsRead", runtime.WithHTTPPathPattern("/v1/advisors/insights:markRead")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_MarkInsightsRead_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_MarkInsightsRead_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) mux.Handle(http.MethodPost, pattern_AdvisorService_StartAdvisorChecks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) @@ -455,23 +923,141 @@ func RegisterAdvisorServiceHandlerClient(ctx context.Context, mux *runtime.Serve } forward_AdvisorService_ChangeAdvisorChecks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_AdvisorService_GetAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/GetAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_GetAdvisorCheck_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_GetAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AdvisorService_CreateAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/CreateAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_CreateAdvisorCheck_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_CreateAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_AdvisorService_UpdateAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/UpdateAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_UpdateAdvisorCheck_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_UpdateAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AdvisorService_TestAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/TestAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks:test")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_TestAdvisorCheck_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_TestAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_AdvisorService_ListAdvisorCheckTestTargets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/ListAdvisorCheckTestTargets", runtime.WithHTTPPathPattern("/v1/advisors/checks:testTargets")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_ListAdvisorCheckTestTargets_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_ListAdvisorCheckTestTargets_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_AdvisorService_DeleteAdvisorCheck_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/advisors.v1.AdvisorService/DeleteAdvisorCheck", runtime.WithHTTPPathPattern("/v1/advisors/checks/{name}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AdvisorService_DeleteAdvisorCheck_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AdvisorService_DeleteAdvisorCheck_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } var ( - pattern_AdvisorService_ListFailedServices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "failedServices"}, "")) - pattern_AdvisorService_GetFailedChecks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "advisors", "checks", "failed"}, "")) - pattern_AdvisorService_StartAdvisorChecks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "start")) - pattern_AdvisorService_ListAdvisorChecks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "")) - pattern_AdvisorService_ListAdvisors_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "advisors"}, "")) - pattern_AdvisorService_ChangeAdvisorChecks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "batchChange")) + pattern_AdvisorService_ListRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "runs"}, "")) + pattern_AdvisorService_ListInsights_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "insights"}, "")) + pattern_AdvisorService_ListInsightsFilterValues_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "insights"}, "filterValues")) + pattern_AdvisorService_MarkInsightsRead_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "insights"}, "markRead")) + pattern_AdvisorService_StartAdvisorChecks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "start")) + pattern_AdvisorService_ListAdvisorChecks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "")) + pattern_AdvisorService_ListAdvisors_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "advisors"}, "")) + pattern_AdvisorService_ChangeAdvisorChecks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "batchChange")) + pattern_AdvisorService_GetAdvisorCheck_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "advisors", "checks", "name"}, "")) + pattern_AdvisorService_CreateAdvisorCheck_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "")) + pattern_AdvisorService_UpdateAdvisorCheck_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "advisors", "checks", "name"}, "")) + pattern_AdvisorService_TestAdvisorCheck_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "test")) + pattern_AdvisorService_ListAdvisorCheckTestTargets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "advisors", "checks"}, "testTargets")) + pattern_AdvisorService_DeleteAdvisorCheck_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "advisors", "checks", "name"}, "")) ) var ( - forward_AdvisorService_ListFailedServices_0 = runtime.ForwardResponseMessage - forward_AdvisorService_GetFailedChecks_0 = runtime.ForwardResponseMessage - forward_AdvisorService_StartAdvisorChecks_0 = runtime.ForwardResponseMessage - forward_AdvisorService_ListAdvisorChecks_0 = runtime.ForwardResponseMessage - forward_AdvisorService_ListAdvisors_0 = runtime.ForwardResponseMessage - forward_AdvisorService_ChangeAdvisorChecks_0 = runtime.ForwardResponseMessage + forward_AdvisorService_ListRuns_0 = runtime.ForwardResponseMessage + forward_AdvisorService_ListInsights_0 = runtime.ForwardResponseMessage + forward_AdvisorService_ListInsightsFilterValues_0 = runtime.ForwardResponseMessage + forward_AdvisorService_MarkInsightsRead_0 = runtime.ForwardResponseMessage + forward_AdvisorService_StartAdvisorChecks_0 = runtime.ForwardResponseMessage + forward_AdvisorService_ListAdvisorChecks_0 = runtime.ForwardResponseMessage + forward_AdvisorService_ListAdvisors_0 = runtime.ForwardResponseMessage + forward_AdvisorService_ChangeAdvisorChecks_0 = runtime.ForwardResponseMessage + forward_AdvisorService_GetAdvisorCheck_0 = runtime.ForwardResponseMessage + forward_AdvisorService_CreateAdvisorCheck_0 = runtime.ForwardResponseMessage + forward_AdvisorService_UpdateAdvisorCheck_0 = runtime.ForwardResponseMessage + forward_AdvisorService_TestAdvisorCheck_0 = runtime.ForwardResponseMessage + forward_AdvisorService_ListAdvisorCheckTestTargets_0 = runtime.ForwardResponseMessage + forward_AdvisorService_DeleteAdvisorCheck_0 = runtime.ForwardResponseMessage ) diff --git a/api/advisors/v1/advisors.pb.validate.go b/api/advisors/v1/advisors.pb.validate.go index 74c3d47a85b..bfd4f4428db 100644 --- a/api/advisors/v1/advisors.pb.validate.go +++ b/api/advisors/v1/advisors.pb.validate.go @@ -39,177 +39,48 @@ var ( _ = managementv1.Severity(0) ) -// Validate checks the field values on AdvisorCheckResult with the rules -// defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *AdvisorCheckResult) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on AdvisorCheckResult with the rules -// defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// AdvisorCheckResultMultiError, or nil if none found. -func (m *AdvisorCheckResult) ValidateAll() error { - return m.validate(true) -} - -func (m *AdvisorCheckResult) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - // no validation rules for Summary - - // no validation rules for Description - - // no validation rules for Severity - - // no validation rules for Labels - - // no validation rules for ReadMoreUrl - - // no validation rules for ServiceName - - if len(errors) > 0 { - return AdvisorCheckResultMultiError(errors) - } - - return nil -} - -// AdvisorCheckResultMultiError is an error wrapping multiple validation errors -// returned by AdvisorCheckResult.ValidateAll() if the designated constraints -// aren't met. -type AdvisorCheckResultMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m AdvisorCheckResultMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m AdvisorCheckResultMultiError) AllErrors() []error { return m } - -// AdvisorCheckResultValidationError is the validation error returned by -// AdvisorCheckResult.Validate if the designated constraints aren't met. -type AdvisorCheckResultValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e AdvisorCheckResultValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e AdvisorCheckResultValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e AdvisorCheckResultValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e AdvisorCheckResultValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e AdvisorCheckResultValidationError) ErrorName() string { - return "AdvisorCheckResultValidationError" -} - -// Error satisfies the builtin error interface -func (e AdvisorCheckResultValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sAdvisorCheckResult.%s: %s%s", - key, - e.field, - e.reason, - cause, - ) -} - -var _ error = AdvisorCheckResultValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = AdvisorCheckResultValidationError{} - -// Validate checks the field values on CheckResultSummary with the rules -// defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *CheckResultSummary) Validate() error { +// Validate checks the field values on AdvisorCheckQuery with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *AdvisorCheckQuery) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on CheckResultSummary with the rules +// ValidateAll checks the field values on AdvisorCheckQuery with the rules // defined in the proto definition for this message. If any rules are // violated, the result is a list of violation errors wrapped in -// CheckResultSummaryMultiError, or nil if none found. -func (m *CheckResultSummary) ValidateAll() error { +// AdvisorCheckQueryMultiError, or nil if none found. +func (m *AdvisorCheckQuery) ValidateAll() error { return m.validate(true) } -func (m *CheckResultSummary) validate(all bool) error { +func (m *AdvisorCheckQuery) validate(all bool) error { if m == nil { return nil } var errors []error - // no validation rules for ServiceName - - // no validation rules for ServiceId - - // no validation rules for EmergencyCount - - // no validation rules for AlertCount - - // no validation rules for CriticalCount - - // no validation rules for ErrorCount + // no validation rules for Type - // no validation rules for WarningCount + // no validation rules for Query - // no validation rules for NoticeCount - - // no validation rules for InfoCount - - // no validation rules for DebugCount + // no validation rules for Parameters if len(errors) > 0 { - return CheckResultSummaryMultiError(errors) + return AdvisorCheckQueryMultiError(errors) } return nil } -// CheckResultSummaryMultiError is an error wrapping multiple validation errors -// returned by CheckResultSummary.ValidateAll() if the designated constraints +// AdvisorCheckQueryMultiError is an error wrapping multiple validation errors +// returned by AdvisorCheckQuery.ValidateAll() if the designated constraints // aren't met. -type CheckResultSummaryMultiError []error +type AdvisorCheckQueryMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m CheckResultSummaryMultiError) Error() string { +func (m AdvisorCheckQueryMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -218,11 +89,11 @@ func (m CheckResultSummaryMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m CheckResultSummaryMultiError) AllErrors() []error { return m } +func (m AdvisorCheckQueryMultiError) AllErrors() []error { return m } -// CheckResultSummaryValidationError is the validation error returned by -// CheckResultSummary.Validate if the designated constraints aren't met. -type CheckResultSummaryValidationError struct { +// AdvisorCheckQueryValidationError is the validation error returned by +// AdvisorCheckQuery.Validate if the designated constraints aren't met. +type AdvisorCheckQueryValidationError struct { field string reason string cause error @@ -230,142 +101,24 @@ type CheckResultSummaryValidationError struct { } // Field function returns field value. -func (e CheckResultSummaryValidationError) Field() string { return e.field } +func (e AdvisorCheckQueryValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e CheckResultSummaryValidationError) Reason() string { return e.reason } +func (e AdvisorCheckQueryValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e CheckResultSummaryValidationError) Cause() error { return e.cause } +func (e AdvisorCheckQueryValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e CheckResultSummaryValidationError) Key() bool { return e.key } +func (e AdvisorCheckQueryValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e CheckResultSummaryValidationError) ErrorName() string { - return "CheckResultSummaryValidationError" -} - -// Error satisfies the builtin error interface -func (e CheckResultSummaryValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sCheckResultSummary.%s: %s%s", - key, - e.field, - e.reason, - cause, - ) -} - -var _ error = CheckResultSummaryValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = CheckResultSummaryValidationError{} - -// Validate checks the field values on CheckResult with the rules defined in -// the proto definition for this message. If any rules are violated, the first -// error encountered is returned, or nil if there are no violations. -func (m *CheckResult) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on CheckResult with the rules defined in -// the proto definition for this message. If any rules are violated, the -// result is a list of violation errors wrapped in CheckResultMultiError, or -// nil if none found. -func (m *CheckResult) ValidateAll() error { - return m.validate(true) -} - -func (m *CheckResult) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - // no validation rules for Summary - - // no validation rules for Description - - // no validation rules for Severity - - // no validation rules for Labels - - // no validation rules for ReadMoreUrl - - // no validation rules for ServiceName - - // no validation rules for ServiceId - - // no validation rules for CheckName - - // no validation rules for Silenced - - if len(errors) > 0 { - return CheckResultMultiError(errors) - } - - return nil -} - -// CheckResultMultiError is an error wrapping multiple validation errors -// returned by CheckResult.ValidateAll() if the designated constraints aren't met. -type CheckResultMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m CheckResultMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m CheckResultMultiError) AllErrors() []error { return m } - -// CheckResultValidationError is the validation error returned by -// CheckResult.Validate if the designated constraints aren't met. -type CheckResultValidationError struct { - field string - reason string - cause error - key bool +func (e AdvisorCheckQueryValidationError) ErrorName() string { + return "AdvisorCheckQueryValidationError" } -// Field function returns field value. -func (e CheckResultValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e CheckResultValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e CheckResultValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e CheckResultValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e CheckResultValidationError) ErrorName() string { return "CheckResultValidationError" } - // Error satisfies the builtin error interface -func (e CheckResultValidationError) Error() string { +func (e AdvisorCheckQueryValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -377,7 +130,7 @@ func (e CheckResultValidationError) Error() string { } return fmt.Sprintf( - "invalid %sCheckResult.%s: %s%s", + "invalid %sAdvisorCheckQuery.%s: %s%s", key, e.field, e.reason, @@ -385,7 +138,7 @@ func (e CheckResultValidationError) Error() string { ) } -var _ error = CheckResultValidationError{} +var _ error = AdvisorCheckQueryValidationError{} var _ interface { Field() string @@ -393,7 +146,7 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = CheckResultValidationError{} +} = AdvisorCheckQueryValidationError{} // Validate checks the field values on AdvisorCheck with the rules defined in // the proto definition for this message. If any rules are violated, the first @@ -417,7 +170,27 @@ func (m *AdvisorCheck) validate(all bool) error { var errors []error - // no validation rules for Name + if utf8.RuneCountInString(m.GetName()) > 128 { + err := AdvisorCheckValidationError{ + field: "Name", + reason: "value length must be at most 128 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if !_AdvisorCheck_Name_Pattern.MatchString(m.GetName()) { + err := AdvisorCheckValidationError{ + field: "Name", + reason: "value does not match regex pattern \"^[a-zA-Z_][a-zA-Z0-9_]*$\"", + } + if !all { + return err + } + errors = append(errors, err) + } // no validation rules for Enabled @@ -427,7 +200,49 @@ func (m *AdvisorCheck) validate(all bool) error { // no validation rules for Interval - // no validation rules for Family + // no validation rules for Technology + + // no validation rules for Category + + // no validation rules for Subcategory + + // no validation rules for UserDefined + + for idx, item := range m.GetQueries() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, AdvisorCheckValidationError{ + field: fmt.Sprintf("Queries[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, AdvisorCheckValidationError{ + field: fmt.Sprintf("Queries[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return AdvisorCheckValidationError{ + field: fmt.Sprintf("Queries[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + // no validation rules for Script if len(errors) > 0 { return AdvisorCheckMultiError(errors) @@ -507,6 +322,8 @@ var _ interface { ErrorName() string } = AdvisorCheckValidationError{} +var _AdvisorCheck_Name_Pattern = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") + // Validate checks the field values on Advisor with the rules defined in the // proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. @@ -538,6 +355,8 @@ func (m *Advisor) validate(all bool) error { // no validation rules for Category + // no validation rules for Subcategory + for idx, item := range m.GetChecks() { _, _ = idx, item @@ -886,6 +705,8 @@ func (m *StartAdvisorChecksResponse) validate(all bool) error { var errors []error + // no validation rules for RunId + if len(errors) > 0 { return StartAdvisorChecksResponseMultiError(errors) } @@ -1207,42 +1028,44 @@ var _ interface { ErrorName() string } = ListAdvisorChecksResponseValidationError{} -// Validate checks the field values on ListAdvisorsRequest with the rules +// Validate checks the field values on GetAdvisorCheckRequest with the rules // defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. -func (m *ListAdvisorsRequest) Validate() error { +func (m *GetAdvisorCheckRequest) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on ListAdvisorsRequest with the rules +// ValidateAll checks the field values on GetAdvisorCheckRequest with the rules // defined in the proto definition for this message. If any rules are // violated, the result is a list of violation errors wrapped in -// ListAdvisorsRequestMultiError, or nil if none found. -func (m *ListAdvisorsRequest) ValidateAll() error { +// GetAdvisorCheckRequestMultiError, or nil if none found. +func (m *GetAdvisorCheckRequest) ValidateAll() error { return m.validate(true) } -func (m *ListAdvisorsRequest) validate(all bool) error { +func (m *GetAdvisorCheckRequest) validate(all bool) error { if m == nil { return nil } var errors []error + // no validation rules for Name + if len(errors) > 0 { - return ListAdvisorsRequestMultiError(errors) + return GetAdvisorCheckRequestMultiError(errors) } return nil } -// ListAdvisorsRequestMultiError is an error wrapping multiple validation -// errors returned by ListAdvisorsRequest.ValidateAll() if the designated +// GetAdvisorCheckRequestMultiError is an error wrapping multiple validation +// errors returned by GetAdvisorCheckRequest.ValidateAll() if the designated // constraints aren't met. -type ListAdvisorsRequestMultiError []error +type GetAdvisorCheckRequestMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m ListAdvisorsRequestMultiError) Error() string { +func (m GetAdvisorCheckRequestMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -1251,11 +1074,11 @@ func (m ListAdvisorsRequestMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m ListAdvisorsRequestMultiError) AllErrors() []error { return m } +func (m GetAdvisorCheckRequestMultiError) AllErrors() []error { return m } -// ListAdvisorsRequestValidationError is the validation error returned by -// ListAdvisorsRequest.Validate if the designated constraints aren't met. -type ListAdvisorsRequestValidationError struct { +// GetAdvisorCheckRequestValidationError is the validation error returned by +// GetAdvisorCheckRequest.Validate if the designated constraints aren't met. +type GetAdvisorCheckRequestValidationError struct { field string reason string cause error @@ -1263,24 +1086,24 @@ type ListAdvisorsRequestValidationError struct { } // Field function returns field value. -func (e ListAdvisorsRequestValidationError) Field() string { return e.field } +func (e GetAdvisorCheckRequestValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e ListAdvisorsRequestValidationError) Reason() string { return e.reason } +func (e GetAdvisorCheckRequestValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e ListAdvisorsRequestValidationError) Cause() error { return e.cause } +func (e GetAdvisorCheckRequestValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e ListAdvisorsRequestValidationError) Key() bool { return e.key } +func (e GetAdvisorCheckRequestValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e ListAdvisorsRequestValidationError) ErrorName() string { - return "ListAdvisorsRequestValidationError" +func (e GetAdvisorCheckRequestValidationError) ErrorName() string { + return "GetAdvisorCheckRequestValidationError" } // Error satisfies the builtin error interface -func (e ListAdvisorsRequestValidationError) Error() string { +func (e GetAdvisorCheckRequestValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -1292,7 +1115,7 @@ func (e ListAdvisorsRequestValidationError) Error() string { } return fmt.Sprintf( - "invalid %sListAdvisorsRequest.%s: %s%s", + "invalid %sGetAdvisorCheckRequest.%s: %s%s", key, e.field, e.reason, @@ -1300,7 +1123,7 @@ func (e ListAdvisorsRequestValidationError) Error() string { ) } -var _ error = ListAdvisorsRequestValidationError{} +var _ error = GetAdvisorCheckRequestValidationError{} var _ interface { Field() string @@ -1308,78 +1131,2917 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = ListAdvisorsRequestValidationError{} +} = GetAdvisorCheckRequestValidationError{} -// Validate checks the field values on ListAdvisorsResponse with the rules +// Validate checks the field values on GetAdvisorCheckResponse with the rules // defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. -func (m *ListAdvisorsResponse) Validate() error { +func (m *GetAdvisorCheckResponse) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on ListAdvisorsResponse with the rules +// ValidateAll checks the field values on GetAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// GetAdvisorCheckResponseMultiError, or nil if none found. +func (m *GetAdvisorCheckResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *GetAdvisorCheckResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if all { + switch v := interface{}(m.GetCheck()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, GetAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, GetAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCheck()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return GetAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return GetAdvisorCheckResponseMultiError(errors) + } + + return nil +} + +// GetAdvisorCheckResponseMultiError is an error wrapping multiple validation +// errors returned by GetAdvisorCheckResponse.ValidateAll() if the designated +// constraints aren't met. +type GetAdvisorCheckResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m GetAdvisorCheckResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m GetAdvisorCheckResponseMultiError) AllErrors() []error { return m } + +// GetAdvisorCheckResponseValidationError is the validation error returned by +// GetAdvisorCheckResponse.Validate if the designated constraints aren't met. +type GetAdvisorCheckResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e GetAdvisorCheckResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e GetAdvisorCheckResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e GetAdvisorCheckResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e GetAdvisorCheckResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e GetAdvisorCheckResponseValidationError) ErrorName() string { + return "GetAdvisorCheckResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e GetAdvisorCheckResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sGetAdvisorCheckResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = GetAdvisorCheckResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = GetAdvisorCheckResponseValidationError{} + +// Validate checks the field values on CreateAdvisorCheckRequest with the rules // defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *CreateAdvisorCheckRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on CreateAdvisorCheckRequest with the +// rules defined in the proto definition for this message. If any rules are // violated, the result is a list of violation errors wrapped in -// ListAdvisorsResponseMultiError, or nil if none found. -func (m *ListAdvisorsResponse) ValidateAll() error { +// CreateAdvisorCheckRequestMultiError, or nil if none found. +func (m *CreateAdvisorCheckRequest) ValidateAll() error { return m.validate(true) } -func (m *ListAdvisorsResponse) validate(all bool) error { +func (m *CreateAdvisorCheckRequest) validate(all bool) error { if m == nil { return nil } var errors []error - for idx, item := range m.GetAdvisors() { - _, _ = idx, item + if all { + switch v := interface{}(m.GetCheck()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, CreateAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, CreateAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCheck()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return CreateAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + } + } + } - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, ListAdvisorsResponseValidationError{ - field: fmt.Sprintf("Advisors[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, ListAdvisorsResponseValidationError{ - field: fmt.Sprintf("Advisors[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } + if len(errors) > 0 { + return CreateAdvisorCheckRequestMultiError(errors) + } + + return nil +} + +// CreateAdvisorCheckRequestMultiError is an error wrapping multiple validation +// errors returned by CreateAdvisorCheckRequest.ValidateAll() if the +// designated constraints aren't met. +type CreateAdvisorCheckRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m CreateAdvisorCheckRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m CreateAdvisorCheckRequestMultiError) AllErrors() []error { return m } + +// CreateAdvisorCheckRequestValidationError is the validation error returned by +// CreateAdvisorCheckRequest.Validate if the designated constraints aren't met. +type CreateAdvisorCheckRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e CreateAdvisorCheckRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e CreateAdvisorCheckRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e CreateAdvisorCheckRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e CreateAdvisorCheckRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e CreateAdvisorCheckRequestValidationError) ErrorName() string { + return "CreateAdvisorCheckRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e CreateAdvisorCheckRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCreateAdvisorCheckRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = CreateAdvisorCheckRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = CreateAdvisorCheckRequestValidationError{} + +// Validate checks the field values on CreateAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *CreateAdvisorCheckResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on CreateAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// CreateAdvisorCheckResponseMultiError, or nil if none found. +func (m *CreateAdvisorCheckResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *CreateAdvisorCheckResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if all { + switch v := interface{}(m.GetCheck()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, CreateAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + case interface{ Validate() error }: if err := v.Validate(); err != nil { - return ListAdvisorsResponseValidationError{ - field: fmt.Sprintf("Advisors[%v]", idx), + errors = append(errors, CreateAdvisorCheckResponseValidationError{ + field: "Check", reason: "embedded message failed validation", cause: err, - } + }) } } + } else if v, ok := interface{}(m.GetCheck()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return CreateAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return CreateAdvisorCheckResponseMultiError(errors) + } + + return nil +} + +// CreateAdvisorCheckResponseMultiError is an error wrapping multiple +// validation errors returned by CreateAdvisorCheckResponse.ValidateAll() if +// the designated constraints aren't met. +type CreateAdvisorCheckResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m CreateAdvisorCheckResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m CreateAdvisorCheckResponseMultiError) AllErrors() []error { return m } + +// CreateAdvisorCheckResponseValidationError is the validation error returned +// by CreateAdvisorCheckResponse.Validate if the designated constraints aren't met. +type CreateAdvisorCheckResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e CreateAdvisorCheckResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e CreateAdvisorCheckResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e CreateAdvisorCheckResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e CreateAdvisorCheckResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e CreateAdvisorCheckResponseValidationError) ErrorName() string { + return "CreateAdvisorCheckResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e CreateAdvisorCheckResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCreateAdvisorCheckResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = CreateAdvisorCheckResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = CreateAdvisorCheckResponseValidationError{} + +// Validate checks the field values on UpdateAdvisorCheckRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *UpdateAdvisorCheckRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on UpdateAdvisorCheckRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// UpdateAdvisorCheckRequestMultiError, or nil if none found. +func (m *UpdateAdvisorCheckRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *UpdateAdvisorCheckRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Name + + if all { + switch v := interface{}(m.GetCheck()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, UpdateAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, UpdateAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCheck()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return UpdateAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return UpdateAdvisorCheckRequestMultiError(errors) + } + + return nil +} + +// UpdateAdvisorCheckRequestMultiError is an error wrapping multiple validation +// errors returned by UpdateAdvisorCheckRequest.ValidateAll() if the +// designated constraints aren't met. +type UpdateAdvisorCheckRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m UpdateAdvisorCheckRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m UpdateAdvisorCheckRequestMultiError) AllErrors() []error { return m } + +// UpdateAdvisorCheckRequestValidationError is the validation error returned by +// UpdateAdvisorCheckRequest.Validate if the designated constraints aren't met. +type UpdateAdvisorCheckRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e UpdateAdvisorCheckRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e UpdateAdvisorCheckRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e UpdateAdvisorCheckRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e UpdateAdvisorCheckRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e UpdateAdvisorCheckRequestValidationError) ErrorName() string { + return "UpdateAdvisorCheckRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e UpdateAdvisorCheckRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sUpdateAdvisorCheckRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = UpdateAdvisorCheckRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = UpdateAdvisorCheckRequestValidationError{} + +// Validate checks the field values on UpdateAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *UpdateAdvisorCheckResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on UpdateAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// UpdateAdvisorCheckResponseMultiError, or nil if none found. +func (m *UpdateAdvisorCheckResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *UpdateAdvisorCheckResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if all { + switch v := interface{}(m.GetCheck()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, UpdateAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, UpdateAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCheck()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return UpdateAdvisorCheckResponseValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if len(errors) > 0 { + return UpdateAdvisorCheckResponseMultiError(errors) + } + + return nil +} + +// UpdateAdvisorCheckResponseMultiError is an error wrapping multiple +// validation errors returned by UpdateAdvisorCheckResponse.ValidateAll() if +// the designated constraints aren't met. +type UpdateAdvisorCheckResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m UpdateAdvisorCheckResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m UpdateAdvisorCheckResponseMultiError) AllErrors() []error { return m } + +// UpdateAdvisorCheckResponseValidationError is the validation error returned +// by UpdateAdvisorCheckResponse.Validate if the designated constraints aren't met. +type UpdateAdvisorCheckResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e UpdateAdvisorCheckResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e UpdateAdvisorCheckResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e UpdateAdvisorCheckResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e UpdateAdvisorCheckResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e UpdateAdvisorCheckResponseValidationError) ErrorName() string { + return "UpdateAdvisorCheckResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e UpdateAdvisorCheckResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sUpdateAdvisorCheckResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = UpdateAdvisorCheckResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = UpdateAdvisorCheckResponseValidationError{} + +// Validate checks the field values on DeleteAdvisorCheckRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *DeleteAdvisorCheckRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on DeleteAdvisorCheckRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// DeleteAdvisorCheckRequestMultiError, or nil if none found. +func (m *DeleteAdvisorCheckRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *DeleteAdvisorCheckRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Name + + if len(errors) > 0 { + return DeleteAdvisorCheckRequestMultiError(errors) + } + + return nil +} + +// DeleteAdvisorCheckRequestMultiError is an error wrapping multiple validation +// errors returned by DeleteAdvisorCheckRequest.ValidateAll() if the +// designated constraints aren't met. +type DeleteAdvisorCheckRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m DeleteAdvisorCheckRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m DeleteAdvisorCheckRequestMultiError) AllErrors() []error { return m } + +// DeleteAdvisorCheckRequestValidationError is the validation error returned by +// DeleteAdvisorCheckRequest.Validate if the designated constraints aren't met. +type DeleteAdvisorCheckRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e DeleteAdvisorCheckRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e DeleteAdvisorCheckRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e DeleteAdvisorCheckRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e DeleteAdvisorCheckRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e DeleteAdvisorCheckRequestValidationError) ErrorName() string { + return "DeleteAdvisorCheckRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e DeleteAdvisorCheckRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sDeleteAdvisorCheckRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = DeleteAdvisorCheckRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = DeleteAdvisorCheckRequestValidationError{} + +// Validate checks the field values on DeleteAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *DeleteAdvisorCheckResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on DeleteAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// DeleteAdvisorCheckResponseMultiError, or nil if none found. +func (m *DeleteAdvisorCheckResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *DeleteAdvisorCheckResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if len(errors) > 0 { + return DeleteAdvisorCheckResponseMultiError(errors) + } + + return nil +} + +// DeleteAdvisorCheckResponseMultiError is an error wrapping multiple +// validation errors returned by DeleteAdvisorCheckResponse.ValidateAll() if +// the designated constraints aren't met. +type DeleteAdvisorCheckResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m DeleteAdvisorCheckResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m DeleteAdvisorCheckResponseMultiError) AllErrors() []error { return m } + +// DeleteAdvisorCheckResponseValidationError is the validation error returned +// by DeleteAdvisorCheckResponse.Validate if the designated constraints aren't met. +type DeleteAdvisorCheckResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e DeleteAdvisorCheckResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e DeleteAdvisorCheckResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e DeleteAdvisorCheckResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e DeleteAdvisorCheckResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e DeleteAdvisorCheckResponseValidationError) ErrorName() string { + return "DeleteAdvisorCheckResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e DeleteAdvisorCheckResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sDeleteAdvisorCheckResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = DeleteAdvisorCheckResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = DeleteAdvisorCheckResponseValidationError{} + +// Validate checks the field values on TestAdvisorCheckRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *TestAdvisorCheckRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on TestAdvisorCheckRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// TestAdvisorCheckRequestMultiError, or nil if none found. +func (m *TestAdvisorCheckRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *TestAdvisorCheckRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if all { + switch v := interface{}(m.GetCheck()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, TestAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, TestAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCheck()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return TestAdvisorCheckRequestValidationError{ + field: "Check", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if utf8.RuneCountInString(m.GetServiceId()) < 1 { + err := TestAdvisorCheckRequestValidationError{ + field: "ServiceId", + reason: "value length must be at least 1 runes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return TestAdvisorCheckRequestMultiError(errors) + } + + return nil +} + +// TestAdvisorCheckRequestMultiError is an error wrapping multiple validation +// errors returned by TestAdvisorCheckRequest.ValidateAll() if the designated +// constraints aren't met. +type TestAdvisorCheckRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m TestAdvisorCheckRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m TestAdvisorCheckRequestMultiError) AllErrors() []error { return m } + +// TestAdvisorCheckRequestValidationError is the validation error returned by +// TestAdvisorCheckRequest.Validate if the designated constraints aren't met. +type TestAdvisorCheckRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e TestAdvisorCheckRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e TestAdvisorCheckRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e TestAdvisorCheckRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e TestAdvisorCheckRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e TestAdvisorCheckRequestValidationError) ErrorName() string { + return "TestAdvisorCheckRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e TestAdvisorCheckRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sTestAdvisorCheckRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = TestAdvisorCheckRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = TestAdvisorCheckRequestValidationError{} + +// Validate checks the field values on TestAdvisorCheckResult with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *TestAdvisorCheckResult) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on TestAdvisorCheckResult with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// TestAdvisorCheckResultMultiError, or nil if none found. +func (m *TestAdvisorCheckResult) ValidateAll() error { + return m.validate(true) +} + +func (m *TestAdvisorCheckResult) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Summary + + // no validation rules for Description + + // no validation rules for Severity + + // no validation rules for Labels + + // no validation rules for ReadMoreUrl + + // no validation rules for ServiceName + + // no validation rules for ServiceId + + // no validation rules for CheckName + + if len(errors) > 0 { + return TestAdvisorCheckResultMultiError(errors) + } + + return nil +} + +// TestAdvisorCheckResultMultiError is an error wrapping multiple validation +// errors returned by TestAdvisorCheckResult.ValidateAll() if the designated +// constraints aren't met. +type TestAdvisorCheckResultMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m TestAdvisorCheckResultMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m TestAdvisorCheckResultMultiError) AllErrors() []error { return m } + +// TestAdvisorCheckResultValidationError is the validation error returned by +// TestAdvisorCheckResult.Validate if the designated constraints aren't met. +type TestAdvisorCheckResultValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e TestAdvisorCheckResultValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e TestAdvisorCheckResultValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e TestAdvisorCheckResultValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e TestAdvisorCheckResultValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e TestAdvisorCheckResultValidationError) ErrorName() string { + return "TestAdvisorCheckResultValidationError" +} + +// Error satisfies the builtin error interface +func (e TestAdvisorCheckResultValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sTestAdvisorCheckResult.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = TestAdvisorCheckResultValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = TestAdvisorCheckResultValidationError{} + +// Validate checks the field values on TestAdvisorCheckResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *TestAdvisorCheckResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on TestAdvisorCheckResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// TestAdvisorCheckResponseMultiError, or nil if none found. +func (m *TestAdvisorCheckResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *TestAdvisorCheckResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + for idx, item := range m.GetResults() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, TestAdvisorCheckResponseValidationError{ + field: fmt.Sprintf("Results[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, TestAdvisorCheckResponseValidationError{ + field: fmt.Sprintf("Results[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return TestAdvisorCheckResponseValidationError{ + field: fmt.Sprintf("Results[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + // no validation rules for ScriptOutput + + if len(errors) > 0 { + return TestAdvisorCheckResponseMultiError(errors) + } + + return nil +} + +// TestAdvisorCheckResponseMultiError is an error wrapping multiple validation +// errors returned by TestAdvisorCheckResponse.ValidateAll() if the designated +// constraints aren't met. +type TestAdvisorCheckResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m TestAdvisorCheckResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m TestAdvisorCheckResponseMultiError) AllErrors() []error { return m } + +// TestAdvisorCheckResponseValidationError is the validation error returned by +// TestAdvisorCheckResponse.Validate if the designated constraints aren't met. +type TestAdvisorCheckResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e TestAdvisorCheckResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e TestAdvisorCheckResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e TestAdvisorCheckResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e TestAdvisorCheckResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e TestAdvisorCheckResponseValidationError) ErrorName() string { + return "TestAdvisorCheckResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e TestAdvisorCheckResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sTestAdvisorCheckResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = TestAdvisorCheckResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = TestAdvisorCheckResponseValidationError{} + +// Validate checks the field values on ListAdvisorCheckTestTargetsRequest with +// the rules defined in the proto definition for this message. If any rules +// are violated, the first error encountered is returned, or nil if there are +// no violations. +func (m *ListAdvisorCheckTestTargetsRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListAdvisorCheckTestTargetsRequest +// with the rules defined in the proto definition for this message. If any +// rules are violated, the result is a list of violation errors wrapped in +// ListAdvisorCheckTestTargetsRequestMultiError, or nil if none found. +func (m *ListAdvisorCheckTestTargetsRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *ListAdvisorCheckTestTargetsRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Technology + + if len(errors) > 0 { + return ListAdvisorCheckTestTargetsRequestMultiError(errors) + } + + return nil +} + +// ListAdvisorCheckTestTargetsRequestMultiError is an error wrapping multiple +// validation errors returned by +// ListAdvisorCheckTestTargetsRequest.ValidateAll() if the designated +// constraints aren't met. +type ListAdvisorCheckTestTargetsRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListAdvisorCheckTestTargetsRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListAdvisorCheckTestTargetsRequestMultiError) AllErrors() []error { return m } + +// ListAdvisorCheckTestTargetsRequestValidationError is the validation error +// returned by ListAdvisorCheckTestTargetsRequest.Validate if the designated +// constraints aren't met. +type ListAdvisorCheckTestTargetsRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListAdvisorCheckTestTargetsRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListAdvisorCheckTestTargetsRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListAdvisorCheckTestTargetsRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListAdvisorCheckTestTargetsRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListAdvisorCheckTestTargetsRequestValidationError) ErrorName() string { + return "ListAdvisorCheckTestTargetsRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e ListAdvisorCheckTestTargetsRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListAdvisorCheckTestTargetsRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListAdvisorCheckTestTargetsRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListAdvisorCheckTestTargetsRequestValidationError{} + +// Validate checks the field values on AdvisorCheckTestTarget with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *AdvisorCheckTestTarget) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on AdvisorCheckTestTarget with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// AdvisorCheckTestTargetMultiError, or nil if none found. +func (m *AdvisorCheckTestTarget) ValidateAll() error { + return m.validate(true) +} + +func (m *AdvisorCheckTestTarget) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for ServiceId + + // no validation rules for ServiceName + + if len(errors) > 0 { + return AdvisorCheckTestTargetMultiError(errors) + } + + return nil +} + +// AdvisorCheckTestTargetMultiError is an error wrapping multiple validation +// errors returned by AdvisorCheckTestTarget.ValidateAll() if the designated +// constraints aren't met. +type AdvisorCheckTestTargetMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m AdvisorCheckTestTargetMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m AdvisorCheckTestTargetMultiError) AllErrors() []error { return m } + +// AdvisorCheckTestTargetValidationError is the validation error returned by +// AdvisorCheckTestTarget.Validate if the designated constraints aren't met. +type AdvisorCheckTestTargetValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e AdvisorCheckTestTargetValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e AdvisorCheckTestTargetValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e AdvisorCheckTestTargetValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e AdvisorCheckTestTargetValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e AdvisorCheckTestTargetValidationError) ErrorName() string { + return "AdvisorCheckTestTargetValidationError" +} + +// Error satisfies the builtin error interface +func (e AdvisorCheckTestTargetValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sAdvisorCheckTestTarget.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = AdvisorCheckTestTargetValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = AdvisorCheckTestTargetValidationError{} + +// Validate checks the field values on ListAdvisorCheckTestTargetsResponse with +// the rules defined in the proto definition for this message. If any rules +// are violated, the first error encountered is returned, or nil if there are +// no violations. +func (m *ListAdvisorCheckTestTargetsResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListAdvisorCheckTestTargetsResponse +// with the rules defined in the proto definition for this message. If any +// rules are violated, the result is a list of violation errors wrapped in +// ListAdvisorCheckTestTargetsResponseMultiError, or nil if none found. +func (m *ListAdvisorCheckTestTargetsResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *ListAdvisorCheckTestTargetsResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + for idx, item := range m.GetTargets() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListAdvisorCheckTestTargetsResponseValidationError{ + field: fmt.Sprintf("Targets[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListAdvisorCheckTestTargetsResponseValidationError{ + field: fmt.Sprintf("Targets[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListAdvisorCheckTestTargetsResponseValidationError{ + field: fmt.Sprintf("Targets[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ListAdvisorCheckTestTargetsResponseMultiError(errors) + } + + return nil +} + +// ListAdvisorCheckTestTargetsResponseMultiError is an error wrapping multiple +// validation errors returned by +// ListAdvisorCheckTestTargetsResponse.ValidateAll() if the designated +// constraints aren't met. +type ListAdvisorCheckTestTargetsResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListAdvisorCheckTestTargetsResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListAdvisorCheckTestTargetsResponseMultiError) AllErrors() []error { return m } + +// ListAdvisorCheckTestTargetsResponseValidationError is the validation error +// returned by ListAdvisorCheckTestTargetsResponse.Validate if the designated +// constraints aren't met. +type ListAdvisorCheckTestTargetsResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListAdvisorCheckTestTargetsResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListAdvisorCheckTestTargetsResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListAdvisorCheckTestTargetsResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListAdvisorCheckTestTargetsResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListAdvisorCheckTestTargetsResponseValidationError) ErrorName() string { + return "ListAdvisorCheckTestTargetsResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e ListAdvisorCheckTestTargetsResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListAdvisorCheckTestTargetsResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListAdvisorCheckTestTargetsResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListAdvisorCheckTestTargetsResponseValidationError{} + +// Validate checks the field values on ListAdvisorsRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ListAdvisorsRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListAdvisorsRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ListAdvisorsRequestMultiError, or nil if none found. +func (m *ListAdvisorsRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *ListAdvisorsRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if len(errors) > 0 { + return ListAdvisorsRequestMultiError(errors) + } + + return nil +} + +// ListAdvisorsRequestMultiError is an error wrapping multiple validation +// errors returned by ListAdvisorsRequest.ValidateAll() if the designated +// constraints aren't met. +type ListAdvisorsRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListAdvisorsRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListAdvisorsRequestMultiError) AllErrors() []error { return m } + +// ListAdvisorsRequestValidationError is the validation error returned by +// ListAdvisorsRequest.Validate if the designated constraints aren't met. +type ListAdvisorsRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListAdvisorsRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListAdvisorsRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListAdvisorsRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListAdvisorsRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListAdvisorsRequestValidationError) ErrorName() string { + return "ListAdvisorsRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e ListAdvisorsRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListAdvisorsRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListAdvisorsRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListAdvisorsRequestValidationError{} + +// Validate checks the field values on ListAdvisorsResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ListAdvisorsResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListAdvisorsResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ListAdvisorsResponseMultiError, or nil if none found. +func (m *ListAdvisorsResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *ListAdvisorsResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + for idx, item := range m.GetAdvisors() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListAdvisorsResponseValidationError{ + field: fmt.Sprintf("Advisors[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListAdvisorsResponseValidationError{ + field: fmt.Sprintf("Advisors[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListAdvisorsResponseValidationError{ + field: fmt.Sprintf("Advisors[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ListAdvisorsResponseMultiError(errors) + } + + return nil +} + +// ListAdvisorsResponseMultiError is an error wrapping multiple validation +// errors returned by ListAdvisorsResponse.ValidateAll() if the designated +// constraints aren't met. +type ListAdvisorsResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListAdvisorsResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListAdvisorsResponseMultiError) AllErrors() []error { return m } + +// ListAdvisorsResponseValidationError is the validation error returned by +// ListAdvisorsResponse.Validate if the designated constraints aren't met. +type ListAdvisorsResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListAdvisorsResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListAdvisorsResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListAdvisorsResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListAdvisorsResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListAdvisorsResponseValidationError) ErrorName() string { + return "ListAdvisorsResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e ListAdvisorsResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListAdvisorsResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListAdvisorsResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListAdvisorsResponseValidationError{} + +// Validate checks the field values on ChangeAdvisorChecksRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ChangeAdvisorChecksRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ChangeAdvisorChecksRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ChangeAdvisorChecksRequestMultiError, or nil if none found. +func (m *ChangeAdvisorChecksRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *ChangeAdvisorChecksRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + for idx, item := range m.GetParams() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ChangeAdvisorChecksRequestValidationError{ + field: fmt.Sprintf("Params[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ChangeAdvisorChecksRequestValidationError{ + field: fmt.Sprintf("Params[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ChangeAdvisorChecksRequestValidationError{ + field: fmt.Sprintf("Params[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ChangeAdvisorChecksRequestMultiError(errors) + } + + return nil +} + +// ChangeAdvisorChecksRequestMultiError is an error wrapping multiple +// validation errors returned by ChangeAdvisorChecksRequest.ValidateAll() if +// the designated constraints aren't met. +type ChangeAdvisorChecksRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ChangeAdvisorChecksRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ChangeAdvisorChecksRequestMultiError) AllErrors() []error { return m } + +// ChangeAdvisorChecksRequestValidationError is the validation error returned +// by ChangeAdvisorChecksRequest.Validate if the designated constraints aren't met. +type ChangeAdvisorChecksRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ChangeAdvisorChecksRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ChangeAdvisorChecksRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ChangeAdvisorChecksRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ChangeAdvisorChecksRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ChangeAdvisorChecksRequestValidationError) ErrorName() string { + return "ChangeAdvisorChecksRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e ChangeAdvisorChecksRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sChangeAdvisorChecksRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ChangeAdvisorChecksRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ChangeAdvisorChecksRequestValidationError{} + +// Validate checks the field values on ChangeAdvisorChecksResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ChangeAdvisorChecksResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ChangeAdvisorChecksResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ChangeAdvisorChecksResponseMultiError, or nil if none found. +func (m *ChangeAdvisorChecksResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *ChangeAdvisorChecksResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if len(errors) > 0 { + return ChangeAdvisorChecksResponseMultiError(errors) + } + + return nil +} + +// ChangeAdvisorChecksResponseMultiError is an error wrapping multiple +// validation errors returned by ChangeAdvisorChecksResponse.ValidateAll() if +// the designated constraints aren't met. +type ChangeAdvisorChecksResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ChangeAdvisorChecksResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ChangeAdvisorChecksResponseMultiError) AllErrors() []error { return m } + +// ChangeAdvisorChecksResponseValidationError is the validation error returned +// by ChangeAdvisorChecksResponse.Validate if the designated constraints +// aren't met. +type ChangeAdvisorChecksResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ChangeAdvisorChecksResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ChangeAdvisorChecksResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ChangeAdvisorChecksResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ChangeAdvisorChecksResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ChangeAdvisorChecksResponseValidationError) ErrorName() string { + return "ChangeAdvisorChecksResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e ChangeAdvisorChecksResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sChangeAdvisorChecksResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ChangeAdvisorChecksResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ChangeAdvisorChecksResponseValidationError{} + +// Validate checks the field values on Insight with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *Insight) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on Insight with the rules defined in the +// proto definition for this message. If any rules are violated, the result is +// a list of violation errors wrapped in InsightMultiError, or nil if none found. +func (m *Insight) ValidateAll() error { + return m.validate(true) +} + +func (m *Insight) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Id + + // no validation rules for RunId + + // no validation rules for CheckName + + // no validation rules for Category + + // no validation rules for Subcategory + + // no validation rules for Interval + + // no validation rules for ServiceId + + // no validation rules for ServiceName + + // no validation rules for ServiceType + + // no validation rules for NodeId + + // no validation rules for NodeName + + // no validation rules for Environment + + // no validation rules for Cluster + + // no validation rules for ReplicationSet + + // no validation rules for Status + + // no validation rules for Summary + + // no validation rules for Description + + // no validation rules for ReadMoreUrl + + // no validation rules for Outcome + + // no validation rules for Severity + + // no validation rules for Labels + + if all { + switch v := interface{}(m.GetCheckedAt()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, InsightValidationError{ + field: "CheckedAt", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, InsightValidationError{ + field: "CheckedAt", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetCheckedAt()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return InsightValidationError{ + field: "CheckedAt", + reason: "embedded message failed validation", + cause: err, + } + } + } + + // no validation rules for IsRead + + // no validation rules for TriggeredBy + + // no validation rules for Region + + // no validation rules for Az + + if len(errors) > 0 { + return InsightMultiError(errors) + } + + return nil +} + +// InsightMultiError is an error wrapping multiple validation errors returned +// by Insight.ValidateAll() if the designated constraints aren't met. +type InsightMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m InsightMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m InsightMultiError) AllErrors() []error { return m } + +// InsightValidationError is the validation error returned by Insight.Validate +// if the designated constraints aren't met. +type InsightValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e InsightValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e InsightValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e InsightValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e InsightValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e InsightValidationError) ErrorName() string { return "InsightValidationError" } + +// Error satisfies the builtin error interface +func (e InsightValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sInsight.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = InsightValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = InsightValidationError{} + +// Validate checks the field values on ListInsightsRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ListInsightsRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListInsightsRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ListInsightsRequestMultiError, or nil if none found. +func (m *ListInsightsRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *ListInsightsRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for ServiceId + + if all { + switch v := interface{}(m.GetFrom()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListInsightsRequestValidationError{ + field: "From", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListInsightsRequestValidationError{ + field: "From", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetFrom()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListInsightsRequestValidationError{ + field: "From", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetTo()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListInsightsRequestValidationError{ + field: "To", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListInsightsRequestValidationError{ + field: "To", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetTo()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListInsightsRequestValidationError{ + field: "To", + reason: "embedded message failed validation", + cause: err, + } + } + } + + // no validation rules for ServiceName + + // no validation rules for NodeName + + // no validation rules for Category + + // no validation rules for CheckName + + // no validation rules for RunId + + if m.PageSize != nil { + if m.GetPageSize() < 1 { + err := ListInsightsRequestValidationError{ + field: "PageSize", + reason: "value must be greater than or equal to 1", + } + if !all { + return err + } + errors = append(errors, err) + } + } + + if m.PageIndex != nil { + if m.GetPageIndex() < 0 { + err := ListInsightsRequestValidationError{ + field: "PageIndex", + reason: "value must be greater than or equal to 0", + } + if !all { + return err + } + errors = append(errors, err) + } + } + + if m.Status != nil { + // no validation rules for Status + } + + if m.IsRead != nil { + // no validation rules for IsRead + } + + if m.Severity != nil { + // no validation rules for Severity + } + + if m.TriggeredBy != nil { + // no validation rules for TriggeredBy + } + + if len(errors) > 0 { + return ListInsightsRequestMultiError(errors) + } + + return nil +} + +// ListInsightsRequestMultiError is an error wrapping multiple validation +// errors returned by ListInsightsRequest.ValidateAll() if the designated +// constraints aren't met. +type ListInsightsRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListInsightsRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListInsightsRequestMultiError) AllErrors() []error { return m } + +// ListInsightsRequestValidationError is the validation error returned by +// ListInsightsRequest.Validate if the designated constraints aren't met. +type ListInsightsRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListInsightsRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListInsightsRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListInsightsRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListInsightsRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListInsightsRequestValidationError) ErrorName() string { + return "ListInsightsRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e ListInsightsRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListInsightsRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListInsightsRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListInsightsRequestValidationError{} + +// Validate checks the field values on ListInsightsResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ListInsightsResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListInsightsResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ListInsightsResponseMultiError, or nil if none found. +func (m *ListInsightsResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *ListInsightsResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for TotalItems + + // no validation rules for TotalPages + + for idx, item := range m.GetResults() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListInsightsResponseValidationError{ + field: fmt.Sprintf("Results[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListInsightsResponseValidationError{ + field: fmt.Sprintf("Results[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListInsightsResponseValidationError{ + field: fmt.Sprintf("Results[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ListInsightsResponseMultiError(errors) + } + + return nil +} + +// ListInsightsResponseMultiError is an error wrapping multiple validation +// errors returned by ListInsightsResponse.ValidateAll() if the designated +// constraints aren't met. +type ListInsightsResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListInsightsResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListInsightsResponseMultiError) AllErrors() []error { return m } + +// ListInsightsResponseValidationError is the validation error returned by +// ListInsightsResponse.Validate if the designated constraints aren't met. +type ListInsightsResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListInsightsResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListInsightsResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListInsightsResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListInsightsResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListInsightsResponseValidationError) ErrorName() string { + return "ListInsightsResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e ListInsightsResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListInsightsResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListInsightsResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListInsightsResponseValidationError{} + +// Validate checks the field values on ListInsightsFilterValuesRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *ListInsightsFilterValuesRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListInsightsFilterValuesRequest with +// the rules defined in the proto definition for this message. If any rules +// are violated, the result is a list of violation errors wrapped in +// ListInsightsFilterValuesRequestMultiError, or nil if none found. +func (m *ListInsightsFilterValuesRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *ListInsightsFilterValuesRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if len(errors) > 0 { + return ListInsightsFilterValuesRequestMultiError(errors) + } + + return nil +} + +// ListInsightsFilterValuesRequestMultiError is an error wrapping multiple +// validation errors returned by ListInsightsFilterValuesRequest.ValidateAll() +// if the designated constraints aren't met. +type ListInsightsFilterValuesRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListInsightsFilterValuesRequestMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListInsightsFilterValuesRequestMultiError) AllErrors() []error { return m } + +// ListInsightsFilterValuesRequestValidationError is the validation error +// returned by ListInsightsFilterValuesRequest.Validate if the designated +// constraints aren't met. +type ListInsightsFilterValuesRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListInsightsFilterValuesRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListInsightsFilterValuesRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListInsightsFilterValuesRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListInsightsFilterValuesRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListInsightsFilterValuesRequestValidationError) ErrorName() string { + return "ListInsightsFilterValuesRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e ListInsightsFilterValuesRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListInsightsFilterValuesRequest.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListInsightsFilterValuesRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListInsightsFilterValuesRequestValidationError{} + +// Validate checks the field values on ListInsightsFilterValuesResponse with +// the rules defined in the proto definition for this message. If any rules +// are violated, the first error encountered is returned, or nil if there are +// no violations. +func (m *ListInsightsFilterValuesResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ListInsightsFilterValuesResponse with +// the rules defined in the proto definition for this message. If any rules +// are violated, the result is a list of violation errors wrapped in +// ListInsightsFilterValuesResponseMultiError, or nil if none found. +func (m *ListInsightsFilterValuesResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *ListInsightsFilterValuesResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if len(errors) > 0 { + return ListInsightsFilterValuesResponseMultiError(errors) + } + + return nil +} + +// ListInsightsFilterValuesResponseMultiError is an error wrapping multiple +// validation errors returned by +// ListInsightsFilterValuesResponse.ValidateAll() if the designated +// constraints aren't met. +type ListInsightsFilterValuesResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ListInsightsFilterValuesResponseMultiError) Error() string { + msgs := make([]string, 0, len(m)) + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ListInsightsFilterValuesResponseMultiError) AllErrors() []error { return m } + +// ListInsightsFilterValuesResponseValidationError is the validation error +// returned by ListInsightsFilterValuesResponse.Validate if the designated +// constraints aren't met. +type ListInsightsFilterValuesResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ListInsightsFilterValuesResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ListInsightsFilterValuesResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ListInsightsFilterValuesResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ListInsightsFilterValuesResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ListInsightsFilterValuesResponseValidationError) ErrorName() string { + return "ListInsightsFilterValuesResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e ListInsightsFilterValuesResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sListInsightsFilterValuesResponse.%s: %s%s", + key, + e.field, + e.reason, + cause, + ) +} + +var _ error = ListInsightsFilterValuesResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ListInsightsFilterValuesResponseValidationError{} + +// Validate checks the field values on InsightsFilters with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *InsightsFilters) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on InsightsFilters with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// InsightsFiltersMultiError, or nil if none found. +func (m *InsightsFilters) ValidateAll() error { + return m.validate(true) +} + +func (m *InsightsFilters) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for CheckName + + // no validation rules for ServiceName + + // no validation rules for NodeName + + // no validation rules for Category + + // no validation rules for RunId + + if m.Severity != nil { + // no validation rules for Severity + } + if m.Status != nil { + // no validation rules for Status + } + + if m.IsRead != nil { + // no validation rules for IsRead } if len(errors) > 0 { - return ListAdvisorsResponseMultiError(errors) + return InsightsFiltersMultiError(errors) } return nil } -// ListAdvisorsResponseMultiError is an error wrapping multiple validation -// errors returned by ListAdvisorsResponse.ValidateAll() if the designated -// constraints aren't met. -type ListAdvisorsResponseMultiError []error +// InsightsFiltersMultiError is an error wrapping multiple validation errors +// returned by InsightsFilters.ValidateAll() if the designated constraints +// aren't met. +type InsightsFiltersMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m ListAdvisorsResponseMultiError) Error() string { +func (m InsightsFiltersMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -1388,11 +4050,11 @@ func (m ListAdvisorsResponseMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m ListAdvisorsResponseMultiError) AllErrors() []error { return m } +func (m InsightsFiltersMultiError) AllErrors() []error { return m } -// ListAdvisorsResponseValidationError is the validation error returned by -// ListAdvisorsResponse.Validate if the designated constraints aren't met. -type ListAdvisorsResponseValidationError struct { +// InsightsFiltersValidationError is the validation error returned by +// InsightsFilters.Validate if the designated constraints aren't met. +type InsightsFiltersValidationError struct { field string reason string cause error @@ -1400,24 +4062,22 @@ type ListAdvisorsResponseValidationError struct { } // Field function returns field value. -func (e ListAdvisorsResponseValidationError) Field() string { return e.field } +func (e InsightsFiltersValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e ListAdvisorsResponseValidationError) Reason() string { return e.reason } +func (e InsightsFiltersValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e ListAdvisorsResponseValidationError) Cause() error { return e.cause } +func (e InsightsFiltersValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e ListAdvisorsResponseValidationError) Key() bool { return e.key } +func (e InsightsFiltersValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e ListAdvisorsResponseValidationError) ErrorName() string { - return "ListAdvisorsResponseValidationError" -} +func (e InsightsFiltersValidationError) ErrorName() string { return "InsightsFiltersValidationError" } // Error satisfies the builtin error interface -func (e ListAdvisorsResponseValidationError) Error() string { +func (e InsightsFiltersValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -1429,7 +4089,7 @@ func (e ListAdvisorsResponseValidationError) Error() string { } return fmt.Sprintf( - "invalid %sListAdvisorsResponse.%s: %s%s", + "invalid %sInsightsFilters.%s: %s%s", key, e.field, e.reason, @@ -1437,7 +4097,7 @@ func (e ListAdvisorsResponseValidationError) Error() string { ) } -var _ error = ListAdvisorsResponseValidationError{} +var _ error = InsightsFiltersValidationError{} var _ interface { Field() string @@ -1445,78 +4105,75 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = ListAdvisorsResponseValidationError{} +} = InsightsFiltersValidationError{} -// Validate checks the field values on ChangeAdvisorChecksRequest with the -// rules defined in the proto definition for this message. If any rules are +// Validate checks the field values on MarkInsightsReadRequest with the rules +// defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. -func (m *ChangeAdvisorChecksRequest) Validate() error { +func (m *MarkInsightsReadRequest) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on ChangeAdvisorChecksRequest with the +// ValidateAll checks the field values on MarkInsightsReadRequest with the // rules defined in the proto definition for this message. If any rules are // violated, the result is a list of violation errors wrapped in -// ChangeAdvisorChecksRequestMultiError, or nil if none found. -func (m *ChangeAdvisorChecksRequest) ValidateAll() error { +// MarkInsightsReadRequestMultiError, or nil if none found. +func (m *MarkInsightsReadRequest) ValidateAll() error { return m.validate(true) } -func (m *ChangeAdvisorChecksRequest) validate(all bool) error { +func (m *MarkInsightsReadRequest) validate(all bool) error { if m == nil { return nil } var errors []error - for idx, item := range m.GetParams() { - _, _ = idx, item + // no validation rules for IsRead - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, ChangeAdvisorChecksRequestValidationError{ - field: fmt.Sprintf("Params[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, ChangeAdvisorChecksRequestValidationError{ - field: fmt.Sprintf("Params[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } + if all { + switch v := interface{}(m.GetFilters()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, MarkInsightsReadRequestValidationError{ + field: "Filters", + reason: "embedded message failed validation", + cause: err, + }) } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + case interface{ Validate() error }: if err := v.Validate(); err != nil { - return ChangeAdvisorChecksRequestValidationError{ - field: fmt.Sprintf("Params[%v]", idx), + errors = append(errors, MarkInsightsReadRequestValidationError{ + field: "Filters", reason: "embedded message failed validation", cause: err, - } + }) + } + } + } else if v, ok := interface{}(m.GetFilters()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return MarkInsightsReadRequestValidationError{ + field: "Filters", + reason: "embedded message failed validation", + cause: err, } } - } if len(errors) > 0 { - return ChangeAdvisorChecksRequestMultiError(errors) + return MarkInsightsReadRequestMultiError(errors) } return nil } -// ChangeAdvisorChecksRequestMultiError is an error wrapping multiple -// validation errors returned by ChangeAdvisorChecksRequest.ValidateAll() if -// the designated constraints aren't met. -type ChangeAdvisorChecksRequestMultiError []error +// MarkInsightsReadRequestMultiError is an error wrapping multiple validation +// errors returned by MarkInsightsReadRequest.ValidateAll() if the designated +// constraints aren't met. +type MarkInsightsReadRequestMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m ChangeAdvisorChecksRequestMultiError) Error() string { +func (m MarkInsightsReadRequestMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -1525,11 +4182,11 @@ func (m ChangeAdvisorChecksRequestMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m ChangeAdvisorChecksRequestMultiError) AllErrors() []error { return m } +func (m MarkInsightsReadRequestMultiError) AllErrors() []error { return m } -// ChangeAdvisorChecksRequestValidationError is the validation error returned -// by ChangeAdvisorChecksRequest.Validate if the designated constraints aren't met. -type ChangeAdvisorChecksRequestValidationError struct { +// MarkInsightsReadRequestValidationError is the validation error returned by +// MarkInsightsReadRequest.Validate if the designated constraints aren't met. +type MarkInsightsReadRequestValidationError struct { field string reason string cause error @@ -1537,24 +4194,24 @@ type ChangeAdvisorChecksRequestValidationError struct { } // Field function returns field value. -func (e ChangeAdvisorChecksRequestValidationError) Field() string { return e.field } +func (e MarkInsightsReadRequestValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e ChangeAdvisorChecksRequestValidationError) Reason() string { return e.reason } +func (e MarkInsightsReadRequestValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e ChangeAdvisorChecksRequestValidationError) Cause() error { return e.cause } +func (e MarkInsightsReadRequestValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e ChangeAdvisorChecksRequestValidationError) Key() bool { return e.key } +func (e MarkInsightsReadRequestValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e ChangeAdvisorChecksRequestValidationError) ErrorName() string { - return "ChangeAdvisorChecksRequestValidationError" +func (e MarkInsightsReadRequestValidationError) ErrorName() string { + return "MarkInsightsReadRequestValidationError" } // Error satisfies the builtin error interface -func (e ChangeAdvisorChecksRequestValidationError) Error() string { +func (e MarkInsightsReadRequestValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -1566,7 +4223,7 @@ func (e ChangeAdvisorChecksRequestValidationError) Error() string { } return fmt.Sprintf( - "invalid %sChangeAdvisorChecksRequest.%s: %s%s", + "invalid %sMarkInsightsReadRequest.%s: %s%s", key, e.field, e.reason, @@ -1574,7 +4231,7 @@ func (e ChangeAdvisorChecksRequestValidationError) Error() string { ) } -var _ error = ChangeAdvisorChecksRequestValidationError{} +var _ error = MarkInsightsReadRequestValidationError{} var _ interface { Field() string @@ -1582,24 +4239,24 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = ChangeAdvisorChecksRequestValidationError{} +} = MarkInsightsReadRequestValidationError{} -// Validate checks the field values on ChangeAdvisorChecksResponse with the -// rules defined in the proto definition for this message. If any rules are +// Validate checks the field values on MarkInsightsReadResponse with the rules +// defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. -func (m *ChangeAdvisorChecksResponse) Validate() error { +func (m *MarkInsightsReadResponse) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on ChangeAdvisorChecksResponse with the +// ValidateAll checks the field values on MarkInsightsReadResponse with the // rules defined in the proto definition for this message. If any rules are // violated, the result is a list of violation errors wrapped in -// ChangeAdvisorChecksResponseMultiError, or nil if none found. -func (m *ChangeAdvisorChecksResponse) ValidateAll() error { +// MarkInsightsReadResponseMultiError, or nil if none found. +func (m *MarkInsightsReadResponse) ValidateAll() error { return m.validate(true) } -func (m *ChangeAdvisorChecksResponse) validate(all bool) error { +func (m *MarkInsightsReadResponse) validate(all bool) error { if m == nil { return nil } @@ -1607,19 +4264,19 @@ func (m *ChangeAdvisorChecksResponse) validate(all bool) error { var errors []error if len(errors) > 0 { - return ChangeAdvisorChecksResponseMultiError(errors) + return MarkInsightsReadResponseMultiError(errors) } return nil } -// ChangeAdvisorChecksResponseMultiError is an error wrapping multiple -// validation errors returned by ChangeAdvisorChecksResponse.ValidateAll() if -// the designated constraints aren't met. -type ChangeAdvisorChecksResponseMultiError []error +// MarkInsightsReadResponseMultiError is an error wrapping multiple validation +// errors returned by MarkInsightsReadResponse.ValidateAll() if the designated +// constraints aren't met. +type MarkInsightsReadResponseMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m ChangeAdvisorChecksResponseMultiError) Error() string { +func (m MarkInsightsReadResponseMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -1628,12 +4285,11 @@ func (m ChangeAdvisorChecksResponseMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m ChangeAdvisorChecksResponseMultiError) AllErrors() []error { return m } +func (m MarkInsightsReadResponseMultiError) AllErrors() []error { return m } -// ChangeAdvisorChecksResponseValidationError is the validation error returned -// by ChangeAdvisorChecksResponse.Validate if the designated constraints -// aren't met. -type ChangeAdvisorChecksResponseValidationError struct { +// MarkInsightsReadResponseValidationError is the validation error returned by +// MarkInsightsReadResponse.Validate if the designated constraints aren't met. +type MarkInsightsReadResponseValidationError struct { field string reason string cause error @@ -1641,24 +4297,24 @@ type ChangeAdvisorChecksResponseValidationError struct { } // Field function returns field value. -func (e ChangeAdvisorChecksResponseValidationError) Field() string { return e.field } +func (e MarkInsightsReadResponseValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e ChangeAdvisorChecksResponseValidationError) Reason() string { return e.reason } +func (e MarkInsightsReadResponseValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e ChangeAdvisorChecksResponseValidationError) Cause() error { return e.cause } +func (e MarkInsightsReadResponseValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e ChangeAdvisorChecksResponseValidationError) Key() bool { return e.key } +func (e MarkInsightsReadResponseValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e ChangeAdvisorChecksResponseValidationError) ErrorName() string { - return "ChangeAdvisorChecksResponseValidationError" +func (e MarkInsightsReadResponseValidationError) ErrorName() string { + return "MarkInsightsReadResponseValidationError" } // Error satisfies the builtin error interface -func (e ChangeAdvisorChecksResponseValidationError) Error() string { +func (e MarkInsightsReadResponseValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -1670,7 +4326,7 @@ func (e ChangeAdvisorChecksResponseValidationError) Error() string { } return fmt.Sprintf( - "invalid %sChangeAdvisorChecksResponse.%s: %s%s", + "invalid %sMarkInsightsReadResponse.%s: %s%s", key, e.field, e.reason, @@ -1678,7 +4334,7 @@ func (e ChangeAdvisorChecksResponseValidationError) Error() string { ) } -var _ error = ChangeAdvisorChecksResponseValidationError{} +var _ error = MarkInsightsReadResponseValidationError{} var _ interface { Field() string @@ -1686,44 +4342,147 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = ChangeAdvisorChecksResponseValidationError{} +} = MarkInsightsReadResponseValidationError{} -// Validate checks the field values on ListFailedServicesRequest with the rules -// defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *ListFailedServicesRequest) Validate() error { +// Validate checks the field values on AdvisorRun with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *AdvisorRun) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on ListFailedServicesRequest with the -// rules defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// ListFailedServicesRequestMultiError, or nil if none found. -func (m *ListFailedServicesRequest) ValidateAll() error { +// ValidateAll checks the field values on AdvisorRun with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in AdvisorRunMultiError, or +// nil if none found. +func (m *AdvisorRun) ValidateAll() error { return m.validate(true) } -func (m *ListFailedServicesRequest) validate(all bool) error { +func (m *AdvisorRun) validate(all bool) error { if m == nil { return nil } var errors []error + // no validation rules for Id + + // no validation rules for TriggeredBy + + if all { + switch v := interface{}(m.GetStartedAt()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, AdvisorRunValidationError{ + field: "StartedAt", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, AdvisorRunValidationError{ + field: "StartedAt", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetStartedAt()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return AdvisorRunValidationError{ + field: "StartedAt", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetFinishedAt()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, AdvisorRunValidationError{ + field: "FinishedAt", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, AdvisorRunValidationError{ + field: "FinishedAt", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetFinishedAt()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return AdvisorRunValidationError{ + field: "FinishedAt", + reason: "embedded message failed validation", + cause: err, + } + } + } + + // no validation rules for ChecksCount + + // no validation rules for ServicesCount + + // no validation rules for FindingsCount + + // no validation rules for ErrorsCount + + for idx, item := range m.GetSeverityCounts() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, AdvisorRunValidationError{ + field: fmt.Sprintf("SeverityCounts[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, AdvisorRunValidationError{ + field: fmt.Sprintf("SeverityCounts[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return AdvisorRunValidationError{ + field: fmt.Sprintf("SeverityCounts[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + if len(errors) > 0 { - return ListFailedServicesRequestMultiError(errors) + return AdvisorRunMultiError(errors) } return nil } -// ListFailedServicesRequestMultiError is an error wrapping multiple validation -// errors returned by ListFailedServicesRequest.ValidateAll() if the -// designated constraints aren't met. -type ListFailedServicesRequestMultiError []error +// AdvisorRunMultiError is an error wrapping multiple validation errors +// returned by AdvisorRun.ValidateAll() if the designated constraints aren't met. +type AdvisorRunMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m ListFailedServicesRequestMultiError) Error() string { +func (m AdvisorRunMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -1732,11 +4491,11 @@ func (m ListFailedServicesRequestMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m ListFailedServicesRequestMultiError) AllErrors() []error { return m } +func (m AdvisorRunMultiError) AllErrors() []error { return m } -// ListFailedServicesRequestValidationError is the validation error returned by -// ListFailedServicesRequest.Validate if the designated constraints aren't met. -type ListFailedServicesRequestValidationError struct { +// AdvisorRunValidationError is the validation error returned by +// AdvisorRun.Validate if the designated constraints aren't met. +type AdvisorRunValidationError struct { field string reason string cause error @@ -1744,24 +4503,22 @@ type ListFailedServicesRequestValidationError struct { } // Field function returns field value. -func (e ListFailedServicesRequestValidationError) Field() string { return e.field } +func (e AdvisorRunValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e ListFailedServicesRequestValidationError) Reason() string { return e.reason } +func (e AdvisorRunValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e ListFailedServicesRequestValidationError) Cause() error { return e.cause } +func (e AdvisorRunValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e ListFailedServicesRequestValidationError) Key() bool { return e.key } +func (e AdvisorRunValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e ListFailedServicesRequestValidationError) ErrorName() string { - return "ListFailedServicesRequestValidationError" -} +func (e AdvisorRunValidationError) ErrorName() string { return "AdvisorRunValidationError" } // Error satisfies the builtin error interface -func (e ListFailedServicesRequestValidationError) Error() string { +func (e AdvisorRunValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -1773,7 +4530,7 @@ func (e ListFailedServicesRequestValidationError) Error() string { } return fmt.Sprintf( - "invalid %sListFailedServicesRequest.%s: %s%s", + "invalid %sAdvisorRun.%s: %s%s", key, e.field, e.reason, @@ -1781,7 +4538,7 @@ func (e ListFailedServicesRequestValidationError) Error() string { ) } -var _ error = ListFailedServicesRequestValidationError{} +var _ error = AdvisorRunValidationError{} var _ interface { Field() string @@ -1789,78 +4546,48 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = ListFailedServicesRequestValidationError{} +} = AdvisorRunValidationError{} -// Validate checks the field values on ListFailedServicesResponse with the -// rules defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *ListFailedServicesResponse) Validate() error { +// Validate checks the field values on SeverityCount with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *SeverityCount) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on ListFailedServicesResponse with the -// rules defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// ListFailedServicesResponseMultiError, or nil if none found. -func (m *ListFailedServicesResponse) ValidateAll() error { +// ValidateAll checks the field values on SeverityCount with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in SeverityCountMultiError, or +// nil if none found. +func (m *SeverityCount) ValidateAll() error { return m.validate(true) } -func (m *ListFailedServicesResponse) validate(all bool) error { +func (m *SeverityCount) validate(all bool) error { if m == nil { return nil } var errors []error - for idx, item := range m.GetResult() { - _, _ = idx, item - - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, ListFailedServicesResponseValidationError{ - field: fmt.Sprintf("Result[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, ListFailedServicesResponseValidationError{ - field: fmt.Sprintf("Result[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return ListFailedServicesResponseValidationError{ - field: fmt.Sprintf("Result[%v]", idx), - reason: "embedded message failed validation", - cause: err, - } - } - } + // no validation rules for Severity - } + // no validation rules for Count if len(errors) > 0 { - return ListFailedServicesResponseMultiError(errors) + return SeverityCountMultiError(errors) } return nil } -// ListFailedServicesResponseMultiError is an error wrapping multiple -// validation errors returned by ListFailedServicesResponse.ValidateAll() if -// the designated constraints aren't met. -type ListFailedServicesResponseMultiError []error +// SeverityCountMultiError is an error wrapping multiple validation errors +// returned by SeverityCount.ValidateAll() if the designated constraints +// aren't met. +type SeverityCountMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m ListFailedServicesResponseMultiError) Error() string { +func (m SeverityCountMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -1869,11 +4596,11 @@ func (m ListFailedServicesResponseMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m ListFailedServicesResponseMultiError) AllErrors() []error { return m } +func (m SeverityCountMultiError) AllErrors() []error { return m } -// ListFailedServicesResponseValidationError is the validation error returned -// by ListFailedServicesResponse.Validate if the designated constraints aren't met. -type ListFailedServicesResponseValidationError struct { +// SeverityCountValidationError is the validation error returned by +// SeverityCount.Validate if the designated constraints aren't met. +type SeverityCountValidationError struct { field string reason string cause error @@ -1881,24 +4608,22 @@ type ListFailedServicesResponseValidationError struct { } // Field function returns field value. -func (e ListFailedServicesResponseValidationError) Field() string { return e.field } +func (e SeverityCountValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e ListFailedServicesResponseValidationError) Reason() string { return e.reason } +func (e SeverityCountValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e ListFailedServicesResponseValidationError) Cause() error { return e.cause } +func (e SeverityCountValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e ListFailedServicesResponseValidationError) Key() bool { return e.key } +func (e SeverityCountValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e ListFailedServicesResponseValidationError) ErrorName() string { - return "ListFailedServicesResponseValidationError" -} +func (e SeverityCountValidationError) ErrorName() string { return "SeverityCountValidationError" } // Error satisfies the builtin error interface -func (e ListFailedServicesResponseValidationError) Error() string { +func (e SeverityCountValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -1910,7 +4635,7 @@ func (e ListFailedServicesResponseValidationError) Error() string { } return fmt.Sprintf( - "invalid %sListFailedServicesResponse.%s: %s%s", + "invalid %sSeverityCount.%s: %s%s", key, e.field, e.reason, @@ -1918,7 +4643,7 @@ func (e ListFailedServicesResponseValidationError) Error() string { ) } -var _ error = ListFailedServicesResponseValidationError{} +var _ error = SeverityCountValidationError{} var _ interface { Field() string @@ -1926,35 +4651,91 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = ListFailedServicesResponseValidationError{} +} = SeverityCountValidationError{} -// Validate checks the field values on GetFailedChecksRequest with the rules -// defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *GetFailedChecksRequest) Validate() error { +// Validate checks the field values on ListRunsRequest with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *ListRunsRequest) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on GetFailedChecksRequest with the rules +// ValidateAll checks the field values on ListRunsRequest with the rules // defined in the proto definition for this message. If any rules are // violated, the result is a list of violation errors wrapped in -// GetFailedChecksRequestMultiError, or nil if none found. -func (m *GetFailedChecksRequest) ValidateAll() error { +// ListRunsRequestMultiError, or nil if none found. +func (m *ListRunsRequest) ValidateAll() error { return m.validate(true) } -func (m *GetFailedChecksRequest) validate(all bool) error { +func (m *ListRunsRequest) validate(all bool) error { if m == nil { return nil } var errors []error - // no validation rules for ServiceId + if all { + switch v := interface{}(m.GetFrom()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListRunsRequestValidationError{ + field: "From", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListRunsRequestValidationError{ + field: "From", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetFrom()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListRunsRequestValidationError{ + field: "From", + reason: "embedded message failed validation", + cause: err, + } + } + } + + if all { + switch v := interface{}(m.GetTo()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ListRunsRequestValidationError{ + field: "To", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ListRunsRequestValidationError{ + field: "To", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetTo()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ListRunsRequestValidationError{ + field: "To", + reason: "embedded message failed validation", + cause: err, + } + } + } if m.PageSize != nil { if m.GetPageSize() < 1 { - err := GetFailedChecksRequestValidationError{ + err := ListRunsRequestValidationError{ field: "PageSize", reason: "value must be greater than or equal to 1", } @@ -1967,7 +4748,7 @@ func (m *GetFailedChecksRequest) validate(all bool) error { if m.PageIndex != nil { if m.GetPageIndex() < 0 { - err := GetFailedChecksRequestValidationError{ + err := ListRunsRequestValidationError{ field: "PageIndex", reason: "value must be greater than or equal to 0", } @@ -1978,20 +4759,24 @@ func (m *GetFailedChecksRequest) validate(all bool) error { } } + if m.TriggeredBy != nil { + // no validation rules for TriggeredBy + } + if len(errors) > 0 { - return GetFailedChecksRequestMultiError(errors) + return ListRunsRequestMultiError(errors) } return nil } -// GetFailedChecksRequestMultiError is an error wrapping multiple validation -// errors returned by GetFailedChecksRequest.ValidateAll() if the designated -// constraints aren't met. -type GetFailedChecksRequestMultiError []error +// ListRunsRequestMultiError is an error wrapping multiple validation errors +// returned by ListRunsRequest.ValidateAll() if the designated constraints +// aren't met. +type ListRunsRequestMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m GetFailedChecksRequestMultiError) Error() string { +func (m ListRunsRequestMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -2000,11 +4785,11 @@ func (m GetFailedChecksRequestMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m GetFailedChecksRequestMultiError) AllErrors() []error { return m } +func (m ListRunsRequestMultiError) AllErrors() []error { return m } -// GetFailedChecksRequestValidationError is the validation error returned by -// GetFailedChecksRequest.Validate if the designated constraints aren't met. -type GetFailedChecksRequestValidationError struct { +// ListRunsRequestValidationError is the validation error returned by +// ListRunsRequest.Validate if the designated constraints aren't met. +type ListRunsRequestValidationError struct { field string reason string cause error @@ -2012,24 +4797,22 @@ type GetFailedChecksRequestValidationError struct { } // Field function returns field value. -func (e GetFailedChecksRequestValidationError) Field() string { return e.field } +func (e ListRunsRequestValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e GetFailedChecksRequestValidationError) Reason() string { return e.reason } +func (e ListRunsRequestValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e GetFailedChecksRequestValidationError) Cause() error { return e.cause } +func (e ListRunsRequestValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e GetFailedChecksRequestValidationError) Key() bool { return e.key } +func (e ListRunsRequestValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e GetFailedChecksRequestValidationError) ErrorName() string { - return "GetFailedChecksRequestValidationError" -} +func (e ListRunsRequestValidationError) ErrorName() string { return "ListRunsRequestValidationError" } // Error satisfies the builtin error interface -func (e GetFailedChecksRequestValidationError) Error() string { +func (e ListRunsRequestValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -2041,7 +4824,7 @@ func (e GetFailedChecksRequestValidationError) Error() string { } return fmt.Sprintf( - "invalid %sGetFailedChecksRequest.%s: %s%s", + "invalid %sListRunsRequest.%s: %s%s", key, e.field, e.reason, @@ -2049,7 +4832,7 @@ func (e GetFailedChecksRequestValidationError) Error() string { ) } -var _ error = GetFailedChecksRequestValidationError{} +var _ error = ListRunsRequestValidationError{} var _ interface { Field() string @@ -2057,24 +4840,24 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = GetFailedChecksRequestValidationError{} +} = ListRunsRequestValidationError{} -// Validate checks the field values on GetFailedChecksResponse with the rules -// defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *GetFailedChecksResponse) Validate() error { +// Validate checks the field values on ListRunsResponse with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *ListRunsResponse) Validate() error { return m.validate(false) } -// ValidateAll checks the field values on GetFailedChecksResponse with the -// rules defined in the proto definition for this message. If any rules are +// ValidateAll checks the field values on ListRunsResponse with the rules +// defined in the proto definition for this message. If any rules are // violated, the result is a list of violation errors wrapped in -// GetFailedChecksResponseMultiError, or nil if none found. -func (m *GetFailedChecksResponse) ValidateAll() error { +// ListRunsResponseMultiError, or nil if none found. +func (m *ListRunsResponse) ValidateAll() error { return m.validate(true) } -func (m *GetFailedChecksResponse) validate(all bool) error { +func (m *ListRunsResponse) validate(all bool) error { if m == nil { return nil } @@ -2092,7 +4875,7 @@ func (m *GetFailedChecksResponse) validate(all bool) error { switch v := interface{}(item).(type) { case interface{ ValidateAll() error }: if err := v.ValidateAll(); err != nil { - errors = append(errors, GetFailedChecksResponseValidationError{ + errors = append(errors, ListRunsResponseValidationError{ field: fmt.Sprintf("Results[%v]", idx), reason: "embedded message failed validation", cause: err, @@ -2100,7 +4883,7 @@ func (m *GetFailedChecksResponse) validate(all bool) error { } case interface{ Validate() error }: if err := v.Validate(); err != nil { - errors = append(errors, GetFailedChecksResponseValidationError{ + errors = append(errors, ListRunsResponseValidationError{ field: fmt.Sprintf("Results[%v]", idx), reason: "embedded message failed validation", cause: err, @@ -2109,7 +4892,7 @@ func (m *GetFailedChecksResponse) validate(all bool) error { } } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { if err := v.Validate(); err != nil { - return GetFailedChecksResponseValidationError{ + return ListRunsResponseValidationError{ field: fmt.Sprintf("Results[%v]", idx), reason: "embedded message failed validation", cause: err, @@ -2120,19 +4903,19 @@ func (m *GetFailedChecksResponse) validate(all bool) error { } if len(errors) > 0 { - return GetFailedChecksResponseMultiError(errors) + return ListRunsResponseMultiError(errors) } return nil } -// GetFailedChecksResponseMultiError is an error wrapping multiple validation -// errors returned by GetFailedChecksResponse.ValidateAll() if the designated -// constraints aren't met. -type GetFailedChecksResponseMultiError []error +// ListRunsResponseMultiError is an error wrapping multiple validation errors +// returned by ListRunsResponse.ValidateAll() if the designated constraints +// aren't met. +type ListRunsResponseMultiError []error // Error returns a concatenation of all the error messages it wraps. -func (m GetFailedChecksResponseMultiError) Error() string { +func (m ListRunsResponseMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) @@ -2141,11 +4924,11 @@ func (m GetFailedChecksResponseMultiError) Error() string { } // AllErrors returns a list of validation violation errors. -func (m GetFailedChecksResponseMultiError) AllErrors() []error { return m } +func (m ListRunsResponseMultiError) AllErrors() []error { return m } -// GetFailedChecksResponseValidationError is the validation error returned by -// GetFailedChecksResponse.Validate if the designated constraints aren't met. -type GetFailedChecksResponseValidationError struct { +// ListRunsResponseValidationError is the validation error returned by +// ListRunsResponse.Validate if the designated constraints aren't met. +type ListRunsResponseValidationError struct { field string reason string cause error @@ -2153,24 +4936,22 @@ type GetFailedChecksResponseValidationError struct { } // Field function returns field value. -func (e GetFailedChecksResponseValidationError) Field() string { return e.field } +func (e ListRunsResponseValidationError) Field() string { return e.field } // Reason function returns reason value. -func (e GetFailedChecksResponseValidationError) Reason() string { return e.reason } +func (e ListRunsResponseValidationError) Reason() string { return e.reason } // Cause function returns cause value. -func (e GetFailedChecksResponseValidationError) Cause() error { return e.cause } +func (e ListRunsResponseValidationError) Cause() error { return e.cause } // Key function returns key value. -func (e GetFailedChecksResponseValidationError) Key() bool { return e.key } +func (e ListRunsResponseValidationError) Key() bool { return e.key } // ErrorName returns error name. -func (e GetFailedChecksResponseValidationError) ErrorName() string { - return "GetFailedChecksResponseValidationError" -} +func (e ListRunsResponseValidationError) ErrorName() string { return "ListRunsResponseValidationError" } // Error satisfies the builtin error interface -func (e GetFailedChecksResponseValidationError) Error() string { +func (e ListRunsResponseValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) @@ -2182,7 +4963,7 @@ func (e GetFailedChecksResponseValidationError) Error() string { } return fmt.Sprintf( - "invalid %sGetFailedChecksResponse.%s: %s%s", + "invalid %sListRunsResponse.%s: %s%s", key, e.field, e.reason, @@ -2190,7 +4971,7 @@ func (e GetFailedChecksResponseValidationError) Error() string { ) } -var _ error = GetFailedChecksResponseValidationError{} +var _ error = ListRunsResponseValidationError{} var _ interface { Field() string @@ -2198,4 +4979,4 @@ var _ interface { Key() bool Cause() error ErrorName() string -} = GetFailedChecksResponseValidationError{} +} = ListRunsResponseValidationError{} diff --git a/api/advisors/v1/advisors.proto b/api/advisors/v1/advisors.proto index 4475eafb59e..c1f2bb259b7 100644 --- a/api/advisors/v1/advisors.proto +++ b/api/advisors/v1/advisors.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package advisors.v1; import "google/api/annotations.proto"; +import "google/protobuf/timestamp.proto"; import "management/v1/severity.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; import "validate/validate.proto"; @@ -15,69 +16,50 @@ enum AdvisorCheckInterval { ADVISOR_CHECK_INTERVAL_RARE = 3; } -enum AdvisorCheckFamily { - ADVISOR_CHECK_FAMILY_UNSPECIFIED = 0; - ADVISOR_CHECK_FAMILY_MYSQL = 1; - ADVISOR_CHECK_FAMILY_POSTGRESQL = 2; - ADVISOR_CHECK_FAMILY_MONGODB = 3; +enum AdvisorCheckTechnology { + ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED = 0; + ADVISOR_CHECK_TECHNOLOGY_MYSQL = 1; + ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL = 2; + ADVISOR_CHECK_TECHNOLOGY_MONGODB = 3; } -// AdvisorCheckResult represents the check result returned from pmm-managed after running the check. -message AdvisorCheckResult { - string summary = 1; - string description = 2; - management.v1.Severity severity = 3; - map labels = 4; - // URL containing information on how to resolve an issue detected by an Advisor check. - string read_more_url = 5; - // Name of the monitored service on which the check ran. - string service_name = 6; +// AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service. +enum AdvisorCheckResultStatus { + ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED = 0; + // The check ran and found no issue. + ADVISOR_CHECK_RESULT_STATUS_OK = 1; + // The check ran and detected an issue. + ADVISOR_CHECK_RESULT_STATUS_FAILED = 2; + // The check could not be executed. + ADVISOR_CHECK_RESULT_STATUS_ERROR = 3; } -// CheckResultSummary is a summary of check results. -message CheckResultSummary { - string service_name = 1; - string service_id = 2; - // Number of failed checks for this service with severity level "EMERGENCY". - uint32 emergency_count = 3; - // Number of failed checks for this service with severity level "ALERT". - uint32 alert_count = 4; - // Number of failed checks for this service with severity level "CRITICAL". - uint32 critical_count = 5; - // Number of failed checks for this service with severity level "ERROR". - uint32 error_count = 6; - // Number of failed checks for this service with severity level "WARNING". - uint32 warning_count = 7; - // Number of failed checks for this service with severity level "NOTICE". - uint32 notice_count = 8; - // Number of failed checks for this service with severity level "INFO". - uint32 info_count = 9; - // Number of failed checks for this service with severity level "DEBUG". - uint32 debug_count = 10; -} - -// CheckResult represents the check results for a given service. -message CheckResult { - string summary = 1; - string description = 2; - management.v1.Severity severity = 3; - map labels = 4; - // URL containing information on how to resolve an issue detected by an Advisor check. - string read_more_url = 5; - // Name of the monitored service on which the check ran. - string service_name = 6; - // ID of the monitored service on which the check ran. - string service_id = 7; - // Name of the check that failed - string check_name = 8; - // Silence status of the check result - bool silenced = 10; +// AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run. +enum AdvisorCheckTriggeredBy { + ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED = 0; + // The run was started by a user via the API or UI. + ADVISOR_CHECK_TRIGGERED_BY_USER = 1; + // The run was started by the built-in scheduler. + ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER = 2; +} + +// AdvisorCheckQuery is a single data-collection query of an advisor check. +message AdvisorCheckQuery { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + string type = 1; + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + string query = 2; + // Optional query parameters (e.g. range/step for metrics range queries). + map parameters = 3; } // AdvisorCheck contains check name and status. message AdvisorCheck { // Machine-readable name (ID) that is used in expression. - string name = 1; + string name = 1 [(validate.rules).string = { + pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$" + max_len: 128 + }]; // True if that check is enabled. bool enabled = 2; // Long human-readable description. @@ -86,23 +68,37 @@ message AdvisorCheck { string summary = 4; // Check execution interval. AdvisorCheckInterval interval = 5; - // DB family. - AdvisorCheckFamily family = 6; + // DB technology. + AdvisorCheckTechnology technology = 6; + // Category (top-level grouping). + string category = 7; + // Subcategory (second-level grouping within a category). + string subcategory = 8; + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + bool user_defined = 9; + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + repeated AdvisorCheckQuery queries = 10; + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + string script = 11; + // IDs of services for which this check is disabled. + repeated string disabled_service_ids = 12; } message Advisor { - // Machine-readable name (ID) that is used in expression. - string name = 1; - // Long human-readable description. - string description = 2; - // Short human-readable summary. - string summary = 3; - // Comment. - string comment = 4; - // Category. + // Deprecated: no longer populated; an advisor is identified by its category/subcategory pair. + string name = 1 [deprecated = true]; + // Deprecated: advisor descriptions were removed. + string description = 2 [deprecated = true]; + // Deprecated: use subcategory instead. + string summary = 3 [deprecated = true]; + // Deprecated: no longer populated. + string comment = 4 [deprecated = true]; + // Category (top-level grouping). string category = 5; + // Subcategory (second-level grouping within a category). + string subcategory = 6; // Advisor checks. - repeated AdvisorCheck checks = 6; + repeated AdvisorCheck checks = 7; } // ChangeAdvisorCheckParams specifies a single check parameters. @@ -112,14 +108,24 @@ message ChangeAdvisorCheckParams { optional bool enable = 2; // check execution interval. AdvisorCheckInterval interval = 4; + // IDs of services to apply the enable/disable to. When set, enable/disable + // affects only the given services instead of the whole check; interval + // changes are not allowed in the same params entry. + repeated string service_ids = 5; } message StartAdvisorChecksRequest { // Names of the checks that should be started. repeated string names = 1; + // IDs of the services to run the checks against. When empty, the checks run + // against every monitored service of a matching technology. + repeated string service_ids = 2; } -message StartAdvisorChecksResponse {} +message StartAdvisorChecksResponse { + // ID assigned to this run; all check results produced by it share this run_id. + string run_id = 1; +} message ListAdvisorChecksRequest {} @@ -127,6 +133,91 @@ message ListAdvisorChecksResponse { repeated AdvisorCheck checks = 1; } +message GetAdvisorCheckRequest { + // Machine-readable name (ID) of the check. + string name = 1; +} + +message GetAdvisorCheckResponse { + AdvisorCheck check = 1; +} + +message CreateAdvisorCheckRequest { + // The check to create. Its name must be unique across all checks. + AdvisorCheck check = 1; +} + +message CreateAdvisorCheckResponse { + AdvisorCheck check = 1; +} + +message UpdateAdvisorCheckRequest { + // Machine-readable name (ID) of the check to update. + string name = 1; + // The updated check definition. A check cannot be renamed: leave its name + // empty or set it to the name above, otherwise the request is rejected. + AdvisorCheck check = 2; +} + +message UpdateAdvisorCheckResponse { + AdvisorCheck check = 1; +} + +message DeleteAdvisorCheckRequest { + // Machine-readable name (ID) of the check to delete. + string name = 1; +} + +message DeleteAdvisorCheckResponse {} + +message TestAdvisorCheckRequest { + // Check definition to execute; it is not saved. + AdvisorCheck check = 1; + // ID of the service to run the check against. + string service_id = 2 [(validate.rules).string.min_len = 1]; +} + +// TestAdvisorCheckResult is a single finding produced by a test (dry-run) check execution. +message TestAdvisorCheckResult { + string summary = 1; + string description = 2; + management.v1.Severity severity = 3; + map labels = 4; + // URL containing information on how to resolve an issue detected by the check. + string read_more_url = 5; + // Name of the monitored service on which the check ran. + string service_name = 6; + // ID of the monitored service on which the check ran. + string service_id = 7; + // Name of the tested check. + string check_name = 8; +} + +message TestAdvisorCheckResponse { + // Findings produced by the check script; empty means the check passed. + repeated TestAdvisorCheckResult results = 1; + // Output produced by the script's print() calls, for debugging. + string script_output = 2; +} + +message ListAdvisorCheckTestTargetsRequest { + // Technology of the check to be tested; determines the eligible service type. + AdvisorCheckTechnology technology = 1; +} + +// AdvisorCheckTestTarget is a service an advisor check can be tested against. +message AdvisorCheckTestTarget { + // ID of the eligible service. + string service_id = 1; + // Name of the eligible service. + string service_name = 2; +} + +message ListAdvisorCheckTestTargetsResponse { + // Services a check of the requested technology can be tested against. + repeated AdvisorCheckTestTarget targets = 1; +} + message ListAdvisorsRequest {} message ListAdvisorsResponse { @@ -139,46 +230,230 @@ message ChangeAdvisorChecksRequest { message ChangeAdvisorChecksResponse {} -message ListFailedServicesRequest {} - -message ListFailedServicesResponse { - repeated CheckResultSummary result = 1; +// Insight represents a single persisted Advisor check run against a service. +message Insight { + // Unique identifier of the history record. + string id = 1; + // ID of the run this result belongs to; all results produced by one execution share it. + string run_id = 2; + // Name of the check that ran. + string check_name = 3; + // Category the check belongs to (top-level grouping). + string category = 4; + // Subcategory the check belongs to (second-level grouping within a category). + string subcategory = 5; + // Check execution interval. + AdvisorCheckInterval interval = 6; + // ID of the monitored service on which the check ran. + string service_id = 7; + // Name of the monitored service on which the check ran. + string service_name = 8; + // Type of the monitored service on which the check ran. + string service_type = 9; + // ID of the node the service runs on. + string node_id = 10; + // Name of the node the service runs on. + string node_name = 11; + // Environment of the monitored service on which the check ran. + string environment = 12; + // Cluster of the monitored service on which the check ran. + string cluster = 13; + // Replication set of the monitored service on which the check ran. + string replication_set = 14; + // Outcome of the check run. + AdvisorCheckResultStatus status = 15; + // Short human-readable summary of the result. + string summary = 16; + // Long human-readable description of the result. + string description = 17; + // URL containing information on how to resolve a detected issue. + string read_more_url = 18; + // Output returned by the check run (finding details or execution error). + string outcome = 19; + // Severity of the result. + management.v1.Severity severity = 20; + // Result labels. + map labels = 21; + // Time when the check ran. + google.protobuf.Timestamp checked_at = 22; + // Whether the result has been marked as read. + bool is_read = 23; + // The actor that initiated the run. + AdvisorCheckTriggeredBy triggered_by = 24; + // Cloud region of the node the service runs on, empty when not applicable. + string region = 25; + // Cloud availability zone of the node the service runs on, empty when not applicable. + string az = 26; } -message GetFailedChecksRequest { +message ListInsightsRequest { // Maximum number of results per page. optional int32 page_size = 1 [(validate.rules).int32.gte = 1]; // Index of the requested page, starts from 0. optional int32 page_index = 2 [(validate.rules).int32.gte = 0]; - // Service ID. + // Filter by service ID. string service_id = 3; + // Filter by outcome. + optional AdvisorCheckResultStatus status = 4; + // Filter by read state. + optional bool is_read = 5; + // Return only results recorded at or after this time. + google.protobuf.Timestamp from = 6; + // Return only results recorded at or before this time. + google.protobuf.Timestamp to = 7; + // Filter by service name (partial, case-insensitive match). + string service_name = 8; + // Filter by node name (partial, case-insensitive match). + string node_name = 9; + // Filter by advisor category. + string category = 10; + // Filter by check name. + string check_name = 11; + // Filter by severity. + optional management.v1.Severity severity = 12; + // Filter by run ID. + string run_id = 13; + // Filter by the actor that initiated the run. + optional AdvisorCheckTriggeredBy triggered_by = 14; } -message GetFailedChecksResponse { +message ListInsightsResponse { // Total number of results. int32 total_items = 1; // Total number of pages. int32 total_pages = 2; - // Check results - repeated CheckResult results = 3; + // Insight records. + repeated Insight results = 3; +} + +message ListInsightsFilterValuesRequest {} + +message ListInsightsFilterValuesResponse { + // Distinct service names present in the check results history, sorted alphabetically. + repeated string service_names = 1; + // Distinct node names present in the check results history, sorted alphabetically. + repeated string node_names = 2; +} + +// InsightsFilters select Advisor insights by attribute; all present fields must match. +message InsightsFilters { + // Filter by check name. + string check_name = 1; + // Filter by service name (partial, case-insensitive match). + string service_name = 2; + // Filter by node name (partial, case-insensitive match). + string node_name = 3; + // Filter by advisor category. + string category = 4; + // Filter by severity. + optional management.v1.Severity severity = 5; + // Filter by outcome. + optional AdvisorCheckResultStatus status = 6; + // Filter by read state. + optional bool is_read = 7; + // Filter by run ID. + string run_id = 8; +} + +message MarkInsightsReadRequest { + // IDs of the insights to update. Takes precedence over filters. + repeated string ids = 1; + // Read state to set on the records. + bool is_read = 2; + // When set and ids is empty, all insights matching these filters are updated + // (an empty filter set matches every record). Either ids or filters must be provided. + InsightsFilters filters = 3; +} + +message MarkInsightsReadResponse {} + +// AdvisorRun is a single execution of Advisor checks. Its totals are recorded on +// completion, so they stay accurate after the run's insights have been pruned. +message AdvisorRun { + // ID shared by every insight the run produced. + string id = 1; + // The actor that initiated the run. + AdvisorCheckTriggeredBy triggered_by = 2; + // When the run began. + google.protobuf.Timestamp started_at = 3; + // When the run completed; unset while it is still running. + google.protobuf.Timestamp finished_at = 4; + // Number of distinct checks the run executed. + int32 checks_count = 5; + // Number of distinct services the run covered. + int32 services_count = 6; + // Number of findings, i.e. checks that detected an issue. + int32 findings_count = 7; + // Number of checks that could not be executed at all. + int32 errors_count = 8; + // Number of findings per severity, most severe first. A repeated field rather + // than a map so severity stays a typed enum instead of a free-form key. + repeated SeverityCount severity_counts = 9; +} + +// SeverityCount is the number of findings a run produced at a single severity. +message SeverityCount { + management.v1.Severity severity = 1; + int32 count = 2; +} + +message ListRunsRequest { + // Maximum number of results per page. + optional int32 page_size = 1 [(validate.rules).int32.gte = 1]; + // Index of the requested page, starts from 0. + optional int32 page_index = 2 [(validate.rules).int32.gte = 0]; + // Filter by the actor that initiated the run. + optional AdvisorCheckTriggeredBy triggered_by = 3; + // Return only runs started at or after this time. + google.protobuf.Timestamp from = 4; + // Return only runs started at or before this time. + google.protobuf.Timestamp to = 5; +} + +message ListRunsResponse { + // Total number of results. + int32 total_items = 1; + // Total number of pages. + int32 total_pages = 2; + // Runs, most recently started first. + repeated AdvisorRun results = 3; } // AdvisorService service provides public Management API methods for Advisor Service. service AdvisorService { - // ListFailedServices returns a list of services with failed checks. - rpc ListFailedServices(ListFailedServicesRequest) returns (ListFailedServicesResponse) { - option (google.api.http) = {get: "/v1/advisors/failedServices"}; + // ListRuns returns the history of Advisor check executions, most recent first. + rpc ListRuns(ListRunsRequest) returns (ListRunsResponse) { + option (google.api.http) = {get: "/v1/advisors/runs"}; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "List Failed Services" - description: "Returns a list of services with failed checks and a summary of check results." + summary: "List Advisor Runs" + description: "Returns the chronological history of Advisor check executions with their totals." }; } - // GetFailedChecks returns the checks result for a given service. - rpc GetFailedChecks(GetFailedChecksRequest) returns (GetFailedChecksResponse) { - option (google.api.http) = {get: "/v1/advisors/checks/failed"}; + // ListInsights returns the history of Advisor check results (insights). + rpc ListInsights(ListInsightsRequest) returns (ListInsightsResponse) { + option (google.api.http) = {get: "/v1/advisors/insights"}; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "Get Failed Advisor Checks" - description: "Returns the latest check results for a given service." + summary: "List Advisor Insights" + description: "Returns the history of Advisor check results (insights), including their outcomes." + }; + } + // ListInsightsFilterValues returns the distinct values usable as insights filters. + rpc ListInsightsFilterValues(ListInsightsFilterValuesRequest) returns (ListInsightsFilterValuesResponse) { + option (google.api.http) = {get: "/v1/advisors/insights:filterValues"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "List Advisor Insights Filter Values" + description: "Returns the distinct service and node names present in the Advisor insights, for populating filter dropdowns." + }; + } + // MarkInsightsRead sets the read state on Advisor insights. + rpc MarkInsightsRead(MarkInsightsReadRequest) returns (MarkInsightsReadResponse) { + option (google.api.http) = { + post: "/v1/advisors/insights:markRead" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Mark Advisor Insights Read" + description: "Sets the read state on the specified Advisor insights. Set is_read to false to mark them unread." }; } // StartAdvisorChecks executes Advisor checks and returns when all checks are executed. @@ -219,4 +494,61 @@ service AdvisorService { description: "Enables/disables advisor checks or changes their exec interval." }; } + // GetAdvisorCheck returns a single advisor check by name, including its queries and script. + rpc GetAdvisorCheck(GetAdvisorCheckRequest) returns (GetAdvisorCheckResponse) { + option (google.api.http) = {get: "/v1/advisors/checks/{name}"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Get Advisor Check" + description: "Returns a single advisor check by name, including its queries and script." + }; + } + // CreateAdvisorCheck creates a new user-authored advisor check. + rpc CreateAdvisorCheck(CreateAdvisorCheckRequest) returns (CreateAdvisorCheckResponse) { + option (google.api.http) = { + post: "/v1/advisors/checks" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Create Advisor Check" + description: "Creates a new user-authored advisor check." + }; + } + // UpdateAdvisorCheck updates an existing user-authored advisor check. + rpc UpdateAdvisorCheck(UpdateAdvisorCheckRequest) returns (UpdateAdvisorCheckResponse) { + option (google.api.http) = { + put: "/v1/advisors/checks/{name}" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Update Advisor Check" + description: "Updates an existing user-authored advisor check. Percona-shipped checks cannot be modified. A check cannot be renamed: the name in the request body must either be empty or match the name in the path." + }; + } + // TestAdvisorCheck executes an advisor check definition without saving it. + rpc TestAdvisorCheck(TestAdvisorCheckRequest) returns (TestAdvisorCheckResponse) { + option (google.api.http) = { + post: "/v1/advisors/checks:test" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Test Advisor Check" + description: "Executes an advisor check definition against a single service without saving the check; results are returned and not persisted." + }; + } + // ListAdvisorCheckTestTargets returns the services an advisor check of the given technology can be tested against. + rpc ListAdvisorCheckTestTargets(ListAdvisorCheckTestTargetsRequest) returns (ListAdvisorCheckTestTargetsResponse) { + option (google.api.http) = {get: "/v1/advisors/checks:testTargets"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "List Advisor Check Test Targets" + description: "Lists the services an advisor check of the given technology can be tested against." + }; + } + // DeleteAdvisorCheck deletes a user-authored advisor check. + rpc DeleteAdvisorCheck(DeleteAdvisorCheckRequest) returns (DeleteAdvisorCheckResponse) { + option (google.api.http) = {delete: "/v1/advisors/checks/{name}"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Delete Advisor Check" + description: "Deletes a user-authored advisor check. Percona-shipped checks cannot be deleted." + }; + } } diff --git a/api/advisors/v1/advisors_grpc.pb.go b/api/advisors/v1/advisors_grpc.pb.go index 968dd072fc8..292a3759517 100644 --- a/api/advisors/v1/advisors_grpc.pb.go +++ b/api/advisors/v1/advisors_grpc.pb.go @@ -20,12 +20,20 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - AdvisorService_ListFailedServices_FullMethodName = "/advisors.v1.AdvisorService/ListFailedServices" - AdvisorService_GetFailedChecks_FullMethodName = "/advisors.v1.AdvisorService/GetFailedChecks" - AdvisorService_StartAdvisorChecks_FullMethodName = "/advisors.v1.AdvisorService/StartAdvisorChecks" - AdvisorService_ListAdvisorChecks_FullMethodName = "/advisors.v1.AdvisorService/ListAdvisorChecks" - AdvisorService_ListAdvisors_FullMethodName = "/advisors.v1.AdvisorService/ListAdvisors" - AdvisorService_ChangeAdvisorChecks_FullMethodName = "/advisors.v1.AdvisorService/ChangeAdvisorChecks" + AdvisorService_ListRuns_FullMethodName = "/advisors.v1.AdvisorService/ListRuns" + AdvisorService_ListInsights_FullMethodName = "/advisors.v1.AdvisorService/ListInsights" + AdvisorService_ListInsightsFilterValues_FullMethodName = "/advisors.v1.AdvisorService/ListInsightsFilterValues" + AdvisorService_MarkInsightsRead_FullMethodName = "/advisors.v1.AdvisorService/MarkInsightsRead" + AdvisorService_StartAdvisorChecks_FullMethodName = "/advisors.v1.AdvisorService/StartAdvisorChecks" + AdvisorService_ListAdvisorChecks_FullMethodName = "/advisors.v1.AdvisorService/ListAdvisorChecks" + AdvisorService_ListAdvisors_FullMethodName = "/advisors.v1.AdvisorService/ListAdvisors" + AdvisorService_ChangeAdvisorChecks_FullMethodName = "/advisors.v1.AdvisorService/ChangeAdvisorChecks" + AdvisorService_GetAdvisorCheck_FullMethodName = "/advisors.v1.AdvisorService/GetAdvisorCheck" + AdvisorService_CreateAdvisorCheck_FullMethodName = "/advisors.v1.AdvisorService/CreateAdvisorCheck" + AdvisorService_UpdateAdvisorCheck_FullMethodName = "/advisors.v1.AdvisorService/UpdateAdvisorCheck" + AdvisorService_TestAdvisorCheck_FullMethodName = "/advisors.v1.AdvisorService/TestAdvisorCheck" + AdvisorService_ListAdvisorCheckTestTargets_FullMethodName = "/advisors.v1.AdvisorService/ListAdvisorCheckTestTargets" + AdvisorService_DeleteAdvisorCheck_FullMethodName = "/advisors.v1.AdvisorService/DeleteAdvisorCheck" ) // AdvisorServiceClient is the client API for AdvisorService service. @@ -34,10 +42,14 @@ const ( // // AdvisorService service provides public Management API methods for Advisor Service. type AdvisorServiceClient interface { - // ListFailedServices returns a list of services with failed checks. - ListFailedServices(ctx context.Context, in *ListFailedServicesRequest, opts ...grpc.CallOption) (*ListFailedServicesResponse, error) - // GetFailedChecks returns the checks result for a given service. - GetFailedChecks(ctx context.Context, in *GetFailedChecksRequest, opts ...grpc.CallOption) (*GetFailedChecksResponse, error) + // ListRuns returns the history of Advisor check executions, most recent first. + ListRuns(ctx context.Context, in *ListRunsRequest, opts ...grpc.CallOption) (*ListRunsResponse, error) + // ListInsights returns the history of Advisor check results (insights). + ListInsights(ctx context.Context, in *ListInsightsRequest, opts ...grpc.CallOption) (*ListInsightsResponse, error) + // ListInsightsFilterValues returns the distinct values usable as insights filters. + ListInsightsFilterValues(ctx context.Context, in *ListInsightsFilterValuesRequest, opts ...grpc.CallOption) (*ListInsightsFilterValuesResponse, error) + // MarkInsightsRead sets the read state on Advisor insights. + MarkInsightsRead(ctx context.Context, in *MarkInsightsReadRequest, opts ...grpc.CallOption) (*MarkInsightsReadResponse, error) // StartAdvisorChecks executes Advisor checks and returns when all checks are executed. StartAdvisorChecks(ctx context.Context, in *StartAdvisorChecksRequest, opts ...grpc.CallOption) (*StartAdvisorChecksResponse, error) // ListAdvisorChecks returns a list of advisor checks available to the user.. @@ -46,6 +58,18 @@ type AdvisorServiceClient interface { ListAdvisors(ctx context.Context, in *ListAdvisorsRequest, opts ...grpc.CallOption) (*ListAdvisorsResponse, error) // ChangeAdvisorChecks enables/disables Advisor checks or changes their exec interval. ChangeAdvisorChecks(ctx context.Context, in *ChangeAdvisorChecksRequest, opts ...grpc.CallOption) (*ChangeAdvisorChecksResponse, error) + // GetAdvisorCheck returns a single advisor check by name, including its queries and script. + GetAdvisorCheck(ctx context.Context, in *GetAdvisorCheckRequest, opts ...grpc.CallOption) (*GetAdvisorCheckResponse, error) + // CreateAdvisorCheck creates a new user-authored advisor check. + CreateAdvisorCheck(ctx context.Context, in *CreateAdvisorCheckRequest, opts ...grpc.CallOption) (*CreateAdvisorCheckResponse, error) + // UpdateAdvisorCheck updates an existing user-authored advisor check. + UpdateAdvisorCheck(ctx context.Context, in *UpdateAdvisorCheckRequest, opts ...grpc.CallOption) (*UpdateAdvisorCheckResponse, error) + // TestAdvisorCheck executes an advisor check definition without saving it. + TestAdvisorCheck(ctx context.Context, in *TestAdvisorCheckRequest, opts ...grpc.CallOption) (*TestAdvisorCheckResponse, error) + // ListAdvisorCheckTestTargets returns the services an advisor check of the given technology can be tested against. + ListAdvisorCheckTestTargets(ctx context.Context, in *ListAdvisorCheckTestTargetsRequest, opts ...grpc.CallOption) (*ListAdvisorCheckTestTargetsResponse, error) + // DeleteAdvisorCheck deletes a user-authored advisor check. + DeleteAdvisorCheck(ctx context.Context, in *DeleteAdvisorCheckRequest, opts ...grpc.CallOption) (*DeleteAdvisorCheckResponse, error) } type advisorServiceClient struct { @@ -56,20 +80,40 @@ func NewAdvisorServiceClient(cc grpc.ClientConnInterface) AdvisorServiceClient { return &advisorServiceClient{cc} } -func (c *advisorServiceClient) ListFailedServices(ctx context.Context, in *ListFailedServicesRequest, opts ...grpc.CallOption) (*ListFailedServicesResponse, error) { +func (c *advisorServiceClient) ListRuns(ctx context.Context, in *ListRunsRequest, opts ...grpc.CallOption) (*ListRunsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ListFailedServicesResponse) - err := c.cc.Invoke(ctx, AdvisorService_ListFailedServices_FullMethodName, in, out, cOpts...) + out := new(ListRunsResponse) + err := c.cc.Invoke(ctx, AdvisorService_ListRuns_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *advisorServiceClient) GetFailedChecks(ctx context.Context, in *GetFailedChecksRequest, opts ...grpc.CallOption) (*GetFailedChecksResponse, error) { +func (c *advisorServiceClient) ListInsights(ctx context.Context, in *ListInsightsRequest, opts ...grpc.CallOption) (*ListInsightsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetFailedChecksResponse) - err := c.cc.Invoke(ctx, AdvisorService_GetFailedChecks_FullMethodName, in, out, cOpts...) + out := new(ListInsightsResponse) + err := c.cc.Invoke(ctx, AdvisorService_ListInsights_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *advisorServiceClient) ListInsightsFilterValues(ctx context.Context, in *ListInsightsFilterValuesRequest, opts ...grpc.CallOption) (*ListInsightsFilterValuesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListInsightsFilterValuesResponse) + err := c.cc.Invoke(ctx, AdvisorService_ListInsightsFilterValues_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *advisorServiceClient) MarkInsightsRead(ctx context.Context, in *MarkInsightsReadRequest, opts ...grpc.CallOption) (*MarkInsightsReadResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MarkInsightsReadResponse) + err := c.cc.Invoke(ctx, AdvisorService_MarkInsightsRead_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -116,16 +160,80 @@ func (c *advisorServiceClient) ChangeAdvisorChecks(ctx context.Context, in *Chan return out, nil } +func (c *advisorServiceClient) GetAdvisorCheck(ctx context.Context, in *GetAdvisorCheckRequest, opts ...grpc.CallOption) (*GetAdvisorCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAdvisorCheckResponse) + err := c.cc.Invoke(ctx, AdvisorService_GetAdvisorCheck_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *advisorServiceClient) CreateAdvisorCheck(ctx context.Context, in *CreateAdvisorCheckRequest, opts ...grpc.CallOption) (*CreateAdvisorCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateAdvisorCheckResponse) + err := c.cc.Invoke(ctx, AdvisorService_CreateAdvisorCheck_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *advisorServiceClient) UpdateAdvisorCheck(ctx context.Context, in *UpdateAdvisorCheckRequest, opts ...grpc.CallOption) (*UpdateAdvisorCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateAdvisorCheckResponse) + err := c.cc.Invoke(ctx, AdvisorService_UpdateAdvisorCheck_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *advisorServiceClient) TestAdvisorCheck(ctx context.Context, in *TestAdvisorCheckRequest, opts ...grpc.CallOption) (*TestAdvisorCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TestAdvisorCheckResponse) + err := c.cc.Invoke(ctx, AdvisorService_TestAdvisorCheck_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *advisorServiceClient) ListAdvisorCheckTestTargets(ctx context.Context, in *ListAdvisorCheckTestTargetsRequest, opts ...grpc.CallOption) (*ListAdvisorCheckTestTargetsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListAdvisorCheckTestTargetsResponse) + err := c.cc.Invoke(ctx, AdvisorService_ListAdvisorCheckTestTargets_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *advisorServiceClient) DeleteAdvisorCheck(ctx context.Context, in *DeleteAdvisorCheckRequest, opts ...grpc.CallOption) (*DeleteAdvisorCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteAdvisorCheckResponse) + err := c.cc.Invoke(ctx, AdvisorService_DeleteAdvisorCheck_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // AdvisorServiceServer is the server API for AdvisorService service. // All implementations must embed UnimplementedAdvisorServiceServer // for forward compatibility. // // AdvisorService service provides public Management API methods for Advisor Service. type AdvisorServiceServer interface { - // ListFailedServices returns a list of services with failed checks. - ListFailedServices(context.Context, *ListFailedServicesRequest) (*ListFailedServicesResponse, error) - // GetFailedChecks returns the checks result for a given service. - GetFailedChecks(context.Context, *GetFailedChecksRequest) (*GetFailedChecksResponse, error) + // ListRuns returns the history of Advisor check executions, most recent first. + ListRuns(context.Context, *ListRunsRequest) (*ListRunsResponse, error) + // ListInsights returns the history of Advisor check results (insights). + ListInsights(context.Context, *ListInsightsRequest) (*ListInsightsResponse, error) + // ListInsightsFilterValues returns the distinct values usable as insights filters. + ListInsightsFilterValues(context.Context, *ListInsightsFilterValuesRequest) (*ListInsightsFilterValuesResponse, error) + // MarkInsightsRead sets the read state on Advisor insights. + MarkInsightsRead(context.Context, *MarkInsightsReadRequest) (*MarkInsightsReadResponse, error) // StartAdvisorChecks executes Advisor checks and returns when all checks are executed. StartAdvisorChecks(context.Context, *StartAdvisorChecksRequest) (*StartAdvisorChecksResponse, error) // ListAdvisorChecks returns a list of advisor checks available to the user.. @@ -134,6 +242,18 @@ type AdvisorServiceServer interface { ListAdvisors(context.Context, *ListAdvisorsRequest) (*ListAdvisorsResponse, error) // ChangeAdvisorChecks enables/disables Advisor checks or changes their exec interval. ChangeAdvisorChecks(context.Context, *ChangeAdvisorChecksRequest) (*ChangeAdvisorChecksResponse, error) + // GetAdvisorCheck returns a single advisor check by name, including its queries and script. + GetAdvisorCheck(context.Context, *GetAdvisorCheckRequest) (*GetAdvisorCheckResponse, error) + // CreateAdvisorCheck creates a new user-authored advisor check. + CreateAdvisorCheck(context.Context, *CreateAdvisorCheckRequest) (*CreateAdvisorCheckResponse, error) + // UpdateAdvisorCheck updates an existing user-authored advisor check. + UpdateAdvisorCheck(context.Context, *UpdateAdvisorCheckRequest) (*UpdateAdvisorCheckResponse, error) + // TestAdvisorCheck executes an advisor check definition without saving it. + TestAdvisorCheck(context.Context, *TestAdvisorCheckRequest) (*TestAdvisorCheckResponse, error) + // ListAdvisorCheckTestTargets returns the services an advisor check of the given technology can be tested against. + ListAdvisorCheckTestTargets(context.Context, *ListAdvisorCheckTestTargetsRequest) (*ListAdvisorCheckTestTargetsResponse, error) + // DeleteAdvisorCheck deletes a user-authored advisor check. + DeleteAdvisorCheck(context.Context, *DeleteAdvisorCheckRequest) (*DeleteAdvisorCheckResponse, error) mustEmbedUnimplementedAdvisorServiceServer() } @@ -144,12 +264,20 @@ type AdvisorServiceServer interface { // pointer dereference when methods are called. type UnimplementedAdvisorServiceServer struct{} -func (UnimplementedAdvisorServiceServer) ListFailedServices(context.Context, *ListFailedServicesRequest) (*ListFailedServicesResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListFailedServices not implemented") +func (UnimplementedAdvisorServiceServer) ListRuns(context.Context, *ListRunsRequest) (*ListRunsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListRuns not implemented") +} + +func (UnimplementedAdvisorServiceServer) ListInsights(context.Context, *ListInsightsRequest) (*ListInsightsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListInsights not implemented") +} + +func (UnimplementedAdvisorServiceServer) ListInsightsFilterValues(context.Context, *ListInsightsFilterValuesRequest) (*ListInsightsFilterValuesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListInsightsFilterValues not implemented") } -func (UnimplementedAdvisorServiceServer) GetFailedChecks(context.Context, *GetFailedChecksRequest) (*GetFailedChecksResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetFailedChecks not implemented") +func (UnimplementedAdvisorServiceServer) MarkInsightsRead(context.Context, *MarkInsightsReadRequest) (*MarkInsightsReadResponse, error) { + return nil, status.Error(codes.Unimplemented, "method MarkInsightsRead not implemented") } func (UnimplementedAdvisorServiceServer) StartAdvisorChecks(context.Context, *StartAdvisorChecksRequest) (*StartAdvisorChecksResponse, error) { @@ -167,6 +295,30 @@ func (UnimplementedAdvisorServiceServer) ListAdvisors(context.Context, *ListAdvi func (UnimplementedAdvisorServiceServer) ChangeAdvisorChecks(context.Context, *ChangeAdvisorChecksRequest) (*ChangeAdvisorChecksResponse, error) { return nil, status.Error(codes.Unimplemented, "method ChangeAdvisorChecks not implemented") } + +func (UnimplementedAdvisorServiceServer) GetAdvisorCheck(context.Context, *GetAdvisorCheckRequest) (*GetAdvisorCheckResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetAdvisorCheck not implemented") +} + +func (UnimplementedAdvisorServiceServer) CreateAdvisorCheck(context.Context, *CreateAdvisorCheckRequest) (*CreateAdvisorCheckResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateAdvisorCheck not implemented") +} + +func (UnimplementedAdvisorServiceServer) UpdateAdvisorCheck(context.Context, *UpdateAdvisorCheckRequest) (*UpdateAdvisorCheckResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateAdvisorCheck not implemented") +} + +func (UnimplementedAdvisorServiceServer) TestAdvisorCheck(context.Context, *TestAdvisorCheckRequest) (*TestAdvisorCheckResponse, error) { + return nil, status.Error(codes.Unimplemented, "method TestAdvisorCheck not implemented") +} + +func (UnimplementedAdvisorServiceServer) ListAdvisorCheckTestTargets(context.Context, *ListAdvisorCheckTestTargetsRequest) (*ListAdvisorCheckTestTargetsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListAdvisorCheckTestTargets not implemented") +} + +func (UnimplementedAdvisorServiceServer) DeleteAdvisorCheck(context.Context, *DeleteAdvisorCheckRequest) (*DeleteAdvisorCheckResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteAdvisorCheck not implemented") +} func (UnimplementedAdvisorServiceServer) mustEmbedUnimplementedAdvisorServiceServer() {} func (UnimplementedAdvisorServiceServer) testEmbeddedByValue() {} @@ -188,38 +340,74 @@ func RegisterAdvisorServiceServer(s grpc.ServiceRegistrar, srv AdvisorServiceSer s.RegisterService(&AdvisorService_ServiceDesc, srv) } -func _AdvisorService_ListFailedServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListFailedServicesRequest) +func _AdvisorService_ListRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRunsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).ListRuns(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_ListRuns_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).ListRuns(ctx, req.(*ListRunsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdvisorService_ListInsights_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListInsightsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).ListInsights(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_ListInsights_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).ListInsights(ctx, req.(*ListInsightsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdvisorService_ListInsightsFilterValues_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListInsightsFilterValuesRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(AdvisorServiceServer).ListFailedServices(ctx, in) + return srv.(AdvisorServiceServer).ListInsightsFilterValues(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: AdvisorService_ListFailedServices_FullMethodName, + FullMethod: AdvisorService_ListInsightsFilterValues_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AdvisorServiceServer).ListFailedServices(ctx, req.(*ListFailedServicesRequest)) + return srv.(AdvisorServiceServer).ListInsightsFilterValues(ctx, req.(*ListInsightsFilterValuesRequest)) } return interceptor(ctx, in, info, handler) } -func _AdvisorService_GetFailedChecks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetFailedChecksRequest) +func _AdvisorService_MarkInsightsRead_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MarkInsightsReadRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(AdvisorServiceServer).GetFailedChecks(ctx, in) + return srv.(AdvisorServiceServer).MarkInsightsRead(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: AdvisorService_GetFailedChecks_FullMethodName, + FullMethod: AdvisorService_MarkInsightsRead_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AdvisorServiceServer).GetFailedChecks(ctx, req.(*GetFailedChecksRequest)) + return srv.(AdvisorServiceServer).MarkInsightsRead(ctx, req.(*MarkInsightsReadRequest)) } return interceptor(ctx, in, info, handler) } @@ -296,6 +484,114 @@ func _AdvisorService_ChangeAdvisorChecks_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _AdvisorService_GetAdvisorCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAdvisorCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).GetAdvisorCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_GetAdvisorCheck_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).GetAdvisorCheck(ctx, req.(*GetAdvisorCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdvisorService_CreateAdvisorCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateAdvisorCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).CreateAdvisorCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_CreateAdvisorCheck_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).CreateAdvisorCheck(ctx, req.(*CreateAdvisorCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdvisorService_UpdateAdvisorCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateAdvisorCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).UpdateAdvisorCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_UpdateAdvisorCheck_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).UpdateAdvisorCheck(ctx, req.(*UpdateAdvisorCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdvisorService_TestAdvisorCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TestAdvisorCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).TestAdvisorCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_TestAdvisorCheck_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).TestAdvisorCheck(ctx, req.(*TestAdvisorCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdvisorService_ListAdvisorCheckTestTargets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAdvisorCheckTestTargetsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).ListAdvisorCheckTestTargets(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_ListAdvisorCheckTestTargets_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).ListAdvisorCheckTestTargets(ctx, req.(*ListAdvisorCheckTestTargetsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdvisorService_DeleteAdvisorCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAdvisorCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdvisorServiceServer).DeleteAdvisorCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdvisorService_DeleteAdvisorCheck_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdvisorServiceServer).DeleteAdvisorCheck(ctx, req.(*DeleteAdvisorCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + // AdvisorService_ServiceDesc is the grpc.ServiceDesc for AdvisorService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -304,12 +600,20 @@ var AdvisorService_ServiceDesc = grpc.ServiceDesc{ HandlerType: (*AdvisorServiceServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "ListFailedServices", - Handler: _AdvisorService_ListFailedServices_Handler, + MethodName: "ListRuns", + Handler: _AdvisorService_ListRuns_Handler, + }, + { + MethodName: "ListInsights", + Handler: _AdvisorService_ListInsights_Handler, + }, + { + MethodName: "ListInsightsFilterValues", + Handler: _AdvisorService_ListInsightsFilterValues_Handler, }, { - MethodName: "GetFailedChecks", - Handler: _AdvisorService_GetFailedChecks_Handler, + MethodName: "MarkInsightsRead", + Handler: _AdvisorService_MarkInsightsRead_Handler, }, { MethodName: "StartAdvisorChecks", @@ -327,6 +631,30 @@ var AdvisorService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ChangeAdvisorChecks", Handler: _AdvisorService_ChangeAdvisorChecks_Handler, }, + { + MethodName: "GetAdvisorCheck", + Handler: _AdvisorService_GetAdvisorCheck_Handler, + }, + { + MethodName: "CreateAdvisorCheck", + Handler: _AdvisorService_CreateAdvisorCheck_Handler, + }, + { + MethodName: "UpdateAdvisorCheck", + Handler: _AdvisorService_UpdateAdvisorCheck_Handler, + }, + { + MethodName: "TestAdvisorCheck", + Handler: _AdvisorService_TestAdvisorCheck_Handler, + }, + { + MethodName: "ListAdvisorCheckTestTargets", + Handler: _AdvisorService_ListAdvisorCheckTestTargets_Handler, + }, + { + MethodName: "DeleteAdvisorCheck", + Handler: _AdvisorService_DeleteAdvisorCheck_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "advisors/v1/advisors.proto", diff --git a/api/advisors/v1/json/client/advisor_service/advisor_service_client.go b/api/advisors/v1/json/client/advisor_service/advisor_service_client.go index 6f48af3f1e6..381be219a77 100644 --- a/api/advisors/v1/json/client/advisor_service/advisor_service_client.go +++ b/api/advisors/v1/json/client/advisor_service/advisor_service_client.go @@ -53,16 +53,32 @@ type ClientOption func(*runtime.ClientOperation) type ClientService interface { ChangeAdvisorChecks(params *ChangeAdvisorChecksParams, opts ...ClientOption) (*ChangeAdvisorChecksOK, error) - GetFailedChecks(params *GetFailedChecksParams, opts ...ClientOption) (*GetFailedChecksOK, error) + CreateAdvisorCheck(params *CreateAdvisorCheckParams, opts ...ClientOption) (*CreateAdvisorCheckOK, error) + + DeleteAdvisorCheck(params *DeleteAdvisorCheckParams, opts ...ClientOption) (*DeleteAdvisorCheckOK, error) + + GetAdvisorCheck(params *GetAdvisorCheckParams, opts ...ClientOption) (*GetAdvisorCheckOK, error) + + ListAdvisorCheckTestTargets(params *ListAdvisorCheckTestTargetsParams, opts ...ClientOption) (*ListAdvisorCheckTestTargetsOK, error) ListAdvisorChecks(params *ListAdvisorChecksParams, opts ...ClientOption) (*ListAdvisorChecksOK, error) ListAdvisors(params *ListAdvisorsParams, opts ...ClientOption) (*ListAdvisorsOK, error) - ListFailedServices(params *ListFailedServicesParams, opts ...ClientOption) (*ListFailedServicesOK, error) + ListInsights(params *ListInsightsParams, opts ...ClientOption) (*ListInsightsOK, error) + + ListInsightsFilterValues(params *ListInsightsFilterValuesParams, opts ...ClientOption) (*ListInsightsFilterValuesOK, error) + + ListRuns(params *ListRunsParams, opts ...ClientOption) (*ListRunsOK, error) + + MarkInsightsRead(params *MarkInsightsReadParams, opts ...ClientOption) (*MarkInsightsReadOK, error) StartAdvisorChecks(params *StartAdvisorChecksParams, opts ...ClientOption) (*StartAdvisorChecksOK, error) + TestAdvisorCheck(params *TestAdvisorCheckParams, opts ...ClientOption) (*TestAdvisorCheckOK, error) + + UpdateAdvisorCheck(params *UpdateAdvisorCheckParams, opts ...ClientOption) (*UpdateAdvisorCheckOK, error) + SetTransport(transport runtime.ClientTransport) } @@ -111,24 +127,156 @@ func (a *Client) ChangeAdvisorChecks(params *ChangeAdvisorChecksParams, opts ... } /* -GetFailedChecks gets failed advisor checks +CreateAdvisorCheck creates advisor check + +Creates a new user-authored advisor check. +*/ +func (a *Client) CreateAdvisorCheck(params *CreateAdvisorCheckParams, opts ...ClientOption) (*CreateAdvisorCheckOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewCreateAdvisorCheckParams() + } + op := &runtime.ClientOperation{ + ID: "CreateAdvisorCheck", + Method: "POST", + PathPattern: "/v1/advisors/checks", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &CreateAdvisorCheckReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*CreateAdvisorCheckOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*CreateAdvisorCheckDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +DeleteAdvisorCheck deletes advisor check + +Deletes a user-authored advisor check. Percona-shipped checks cannot be deleted. +*/ +func (a *Client) DeleteAdvisorCheck(params *DeleteAdvisorCheckParams, opts ...ClientOption) (*DeleteAdvisorCheckOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewDeleteAdvisorCheckParams() + } + op := &runtime.ClientOperation{ + ID: "DeleteAdvisorCheck", + Method: "DELETE", + PathPattern: "/v1/advisors/checks/{name}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &DeleteAdvisorCheckReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*DeleteAdvisorCheckOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*DeleteAdvisorCheckDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +GetAdvisorCheck gets advisor check + +Returns a single advisor check by name, including its queries and script. +*/ +func (a *Client) GetAdvisorCheck(params *GetAdvisorCheckParams, opts ...ClientOption) (*GetAdvisorCheckOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewGetAdvisorCheckParams() + } + op := &runtime.ClientOperation{ + ID: "GetAdvisorCheck", + Method: "GET", + PathPattern: "/v1/advisors/checks/{name}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &GetAdvisorCheckReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*GetAdvisorCheckOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*GetAdvisorCheckDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +ListAdvisorCheckTestTargets lists advisor check test targets -Returns the latest check results for a given service. +Lists the services an advisor check of the given technology can be tested against. */ -func (a *Client) GetFailedChecks(params *GetFailedChecksParams, opts ...ClientOption) (*GetFailedChecksOK, error) { +func (a *Client) ListAdvisorCheckTestTargets(params *ListAdvisorCheckTestTargetsParams, opts ...ClientOption) (*ListAdvisorCheckTestTargetsOK, error) { // NOTE: parameters are not validated before sending if params == nil { - params = NewGetFailedChecksParams() + params = NewListAdvisorCheckTestTargetsParams() } op := &runtime.ClientOperation{ - ID: "GetFailedChecks", + ID: "ListAdvisorCheckTestTargets", Method: "GET", - PathPattern: "/v1/advisors/checks/failed", + PathPattern: "/v1/advisors/checks:testTargets", ProducesMediaTypes: []string{"application/json"}, ConsumesMediaTypes: []string{"application/json"}, Schemes: []string{"http", "https"}, Params: params, - Reader: &GetFailedChecksReader{formats: a.formats}, + Reader: &ListAdvisorCheckTestTargetsReader{formats: a.formats}, Context: params.Context, Client: params.HTTPClient, } @@ -141,7 +289,7 @@ func (a *Client) GetFailedChecks(params *GetFailedChecksParams, opts ...ClientOp } // only one success response has to be checked - success, ok := result.(*GetFailedChecksOK) + success, ok := result.(*ListAdvisorCheckTestTargetsOK) if ok { return success, nil } @@ -149,7 +297,7 @@ func (a *Client) GetFailedChecks(params *GetFailedChecksParams, opts ...ClientOp // unexpected success response. // // a default response is provided: fill this and return an error - unexpectedSuccess := result.(*GetFailedChecksDefault) + unexpectedSuccess := result.(*ListAdvisorCheckTestTargetsDefault) return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } @@ -243,24 +391,24 @@ func (a *Client) ListAdvisors(params *ListAdvisorsParams, opts ...ClientOption) } /* -ListFailedServices lists failed services +ListInsights lists advisor insights -Returns a list of services with failed checks and a summary of check results. +Returns the history of Advisor check results (insights), including their outcomes. */ -func (a *Client) ListFailedServices(params *ListFailedServicesParams, opts ...ClientOption) (*ListFailedServicesOK, error) { +func (a *Client) ListInsights(params *ListInsightsParams, opts ...ClientOption) (*ListInsightsOK, error) { // NOTE: parameters are not validated before sending if params == nil { - params = NewListFailedServicesParams() + params = NewListInsightsParams() } op := &runtime.ClientOperation{ - ID: "ListFailedServices", + ID: "ListInsights", Method: "GET", - PathPattern: "/v1/advisors/failedServices", + PathPattern: "/v1/advisors/insights", ProducesMediaTypes: []string{"application/json"}, ConsumesMediaTypes: []string{"application/json"}, Schemes: []string{"http", "https"}, Params: params, - Reader: &ListFailedServicesReader{formats: a.formats}, + Reader: &ListInsightsReader{formats: a.formats}, Context: params.Context, Client: params.HTTPClient, } @@ -273,7 +421,7 @@ func (a *Client) ListFailedServices(params *ListFailedServicesParams, opts ...Cl } // only one success response has to be checked - success, ok := result.(*ListFailedServicesOK) + success, ok := result.(*ListInsightsOK) if ok { return success, nil } @@ -281,7 +429,139 @@ func (a *Client) ListFailedServices(params *ListFailedServicesParams, opts ...Cl // unexpected success response. // // a default response is provided: fill this and return an error - unexpectedSuccess := result.(*ListFailedServicesDefault) + unexpectedSuccess := result.(*ListInsightsDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +ListInsightsFilterValues lists advisor insights filter values + +Returns the distinct service and node names present in the Advisor insights, for populating filter dropdowns. +*/ +func (a *Client) ListInsightsFilterValues(params *ListInsightsFilterValuesParams, opts ...ClientOption) (*ListInsightsFilterValuesOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewListInsightsFilterValuesParams() + } + op := &runtime.ClientOperation{ + ID: "ListInsightsFilterValues", + Method: "GET", + PathPattern: "/v1/advisors/insights:filterValues", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &ListInsightsFilterValuesReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*ListInsightsFilterValuesOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*ListInsightsFilterValuesDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +ListRuns lists advisor runs + +Returns the chronological history of Advisor check executions with their totals. +*/ +func (a *Client) ListRuns(params *ListRunsParams, opts ...ClientOption) (*ListRunsOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewListRunsParams() + } + op := &runtime.ClientOperation{ + ID: "ListRuns", + Method: "GET", + PathPattern: "/v1/advisors/runs", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &ListRunsReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*ListRunsOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*ListRunsDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +MarkInsightsRead marks advisor insights read + +Sets the read state on the specified Advisor insights. Set is_read to false to mark them unread. +*/ +func (a *Client) MarkInsightsRead(params *MarkInsightsReadParams, opts ...ClientOption) (*MarkInsightsReadOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewMarkInsightsReadParams() + } + op := &runtime.ClientOperation{ + ID: "MarkInsightsRead", + Method: "POST", + PathPattern: "/v1/advisors/insights:markRead", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &MarkInsightsReadReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*MarkInsightsReadOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*MarkInsightsReadDefault) return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } @@ -330,6 +610,94 @@ func (a *Client) StartAdvisorChecks(params *StartAdvisorChecksParams, opts ...Cl return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +TestAdvisorCheck tests advisor check + +Executes an advisor check definition against a single service without saving the check; results are returned and not persisted. +*/ +func (a *Client) TestAdvisorCheck(params *TestAdvisorCheckParams, opts ...ClientOption) (*TestAdvisorCheckOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewTestAdvisorCheckParams() + } + op := &runtime.ClientOperation{ + ID: "TestAdvisorCheck", + Method: "POST", + PathPattern: "/v1/advisors/checks:test", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &TestAdvisorCheckReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*TestAdvisorCheckOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*TestAdvisorCheckDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +UpdateAdvisorCheck updates advisor check + +Updates an existing user-authored advisor check. Percona-shipped checks cannot be modified. A check cannot be renamed: the name in the request body must either be empty or match the name in the path. +*/ +func (a *Client) UpdateAdvisorCheck(params *UpdateAdvisorCheckParams, opts ...ClientOption) (*UpdateAdvisorCheckOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewUpdateAdvisorCheckParams() + } + op := &runtime.ClientOperation{ + ID: "UpdateAdvisorCheck", + Method: "PUT", + PathPattern: "/v1/advisors/checks/{name}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &UpdateAdvisorCheckReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*UpdateAdvisorCheckOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*UpdateAdvisorCheckDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + // SetTransport changes the transport on the client func (a *Client) SetTransport(transport runtime.ClientTransport) { a.transport = transport diff --git a/api/advisors/v1/json/client/advisor_service/change_advisor_checks_responses.go b/api/advisors/v1/json/client/advisor_service/change_advisor_checks_responses.go index 483cfd954d8..cb110cb002b 100644 --- a/api/advisors/v1/json/client/advisor_service/change_advisor_checks_responses.go +++ b/api/advisors/v1/json/client/advisor_service/change_advisor_checks_responses.go @@ -536,6 +536,11 @@ type ChangeAdvisorChecksParamsBodyParamsItems0 struct { // AdvisorCheckInterval represents possible execution interval values for checks. // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] Interval *string `json:"interval,omitempty"` + + // IDs of services to apply the enable/disable to. When set, enable/disable + // affects only the given services instead of the whole check; interval + // changes are not allowed in the same params entry. + ServiceIds []string `json:"service_ids"` } // Validate validates this change advisor checks params body params items0 diff --git a/api/advisors/v1/json/client/advisor_service/create_advisor_check_parameters.go b/api/advisors/v1/json/client/advisor_service/create_advisor_check_parameters.go new file mode 100644 index 00000000000..633f6fd6fdd --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/create_advisor_check_parameters.go @@ -0,0 +1,141 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewCreateAdvisorCheckParams creates a new CreateAdvisorCheckParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewCreateAdvisorCheckParams() *CreateAdvisorCheckParams { + return &CreateAdvisorCheckParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewCreateAdvisorCheckParamsWithTimeout creates a new CreateAdvisorCheckParams object +// with the ability to set a timeout on a request. +func NewCreateAdvisorCheckParamsWithTimeout(timeout time.Duration) *CreateAdvisorCheckParams { + return &CreateAdvisorCheckParams{ + timeout: timeout, + } +} + +// NewCreateAdvisorCheckParamsWithContext creates a new CreateAdvisorCheckParams object +// with the ability to set a context for a request. +func NewCreateAdvisorCheckParamsWithContext(ctx context.Context) *CreateAdvisorCheckParams { + return &CreateAdvisorCheckParams{ + Context: ctx, + } +} + +// NewCreateAdvisorCheckParamsWithHTTPClient creates a new CreateAdvisorCheckParams object +// with the ability to set a custom HTTPClient for a request. +func NewCreateAdvisorCheckParamsWithHTTPClient(client *http.Client) *CreateAdvisorCheckParams { + return &CreateAdvisorCheckParams{ + HTTPClient: client, + } +} + +/* +CreateAdvisorCheckParams contains all the parameters to send to the API endpoint + + for the create advisor check operation. + + Typically these are written to a http.Request. +*/ +type CreateAdvisorCheckParams struct { + // Body. + Body CreateAdvisorCheckBody + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the create advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *CreateAdvisorCheckParams) WithDefaults() *CreateAdvisorCheckParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the create advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *CreateAdvisorCheckParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the create advisor check params +func (o *CreateAdvisorCheckParams) WithTimeout(timeout time.Duration) *CreateAdvisorCheckParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the create advisor check params +func (o *CreateAdvisorCheckParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the create advisor check params +func (o *CreateAdvisorCheckParams) WithContext(ctx context.Context) *CreateAdvisorCheckParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the create advisor check params +func (o *CreateAdvisorCheckParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the create advisor check params +func (o *CreateAdvisorCheckParams) WithHTTPClient(client *http.Client) *CreateAdvisorCheckParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the create advisor check params +func (o *CreateAdvisorCheckParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the create advisor check params +func (o *CreateAdvisorCheckParams) WithBody(body CreateAdvisorCheckBody) *CreateAdvisorCheckParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the create advisor check params +func (o *CreateAdvisorCheckParams) SetBody(body CreateAdvisorCheckBody) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *CreateAdvisorCheckParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/create_advisor_check_responses.go b/api/advisors/v1/json/client/advisor_service/create_advisor_check_responses.go new file mode 100644 index 00000000000..57729424c2b --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/create_advisor_check_responses.go @@ -0,0 +1,1204 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// CreateAdvisorCheckReader is a Reader for the CreateAdvisorCheck structure. +type CreateAdvisorCheckReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *CreateAdvisorCheckReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewCreateAdvisorCheckOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewCreateAdvisorCheckDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewCreateAdvisorCheckOK creates a CreateAdvisorCheckOK with default headers values +func NewCreateAdvisorCheckOK() *CreateAdvisorCheckOK { + return &CreateAdvisorCheckOK{} +} + +/* +CreateAdvisorCheckOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type CreateAdvisorCheckOK struct { + Payload *CreateAdvisorCheckOKBody +} + +// IsSuccess returns true when this create advisor check Ok response has a 2xx status code +func (o *CreateAdvisorCheckOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this create advisor check Ok response has a 3xx status code +func (o *CreateAdvisorCheckOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this create advisor check Ok response has a 4xx status code +func (o *CreateAdvisorCheckOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this create advisor check Ok response has a 5xx status code +func (o *CreateAdvisorCheckOK) IsServerError() bool { + return false +} + +// IsCode returns true when this create advisor check Ok response a status code equal to that given +func (o *CreateAdvisorCheckOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the create advisor check Ok response +func (o *CreateAdvisorCheckOK) Code() int { + return 200 +} + +func (o *CreateAdvisorCheckOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks][%d] createAdvisorCheckOk %s", 200, payload) +} + +func (o *CreateAdvisorCheckOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks][%d] createAdvisorCheckOk %s", 200, payload) +} + +func (o *CreateAdvisorCheckOK) GetPayload() *CreateAdvisorCheckOKBody { + return o.Payload +} + +func (o *CreateAdvisorCheckOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(CreateAdvisorCheckOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewCreateAdvisorCheckDefault creates a CreateAdvisorCheckDefault with default headers values +func NewCreateAdvisorCheckDefault(code int) *CreateAdvisorCheckDefault { + return &CreateAdvisorCheckDefault{ + _statusCode: code, + } +} + +/* +CreateAdvisorCheckDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type CreateAdvisorCheckDefault struct { + _statusCode int + + Payload *CreateAdvisorCheckDefaultBody +} + +// IsSuccess returns true when this create advisor check default response has a 2xx status code +func (o *CreateAdvisorCheckDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this create advisor check default response has a 3xx status code +func (o *CreateAdvisorCheckDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this create advisor check default response has a 4xx status code +func (o *CreateAdvisorCheckDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this create advisor check default response has a 5xx status code +func (o *CreateAdvisorCheckDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this create advisor check default response a status code equal to that given +func (o *CreateAdvisorCheckDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the create advisor check default response +func (o *CreateAdvisorCheckDefault) Code() int { + return o._statusCode +} + +func (o *CreateAdvisorCheckDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks][%d] CreateAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *CreateAdvisorCheckDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks][%d] CreateAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *CreateAdvisorCheckDefault) GetPayload() *CreateAdvisorCheckDefaultBody { + return o.Payload +} + +func (o *CreateAdvisorCheckDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(CreateAdvisorCheckDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +CreateAdvisorCheckBody create advisor check body +swagger:model CreateAdvisorCheckBody +*/ +type CreateAdvisorCheckBody struct { + // check + Check *CreateAdvisorCheckParamsBodyCheck `json:"check,omitempty"` +} + +// Validate validates this create advisor check body +func (o *CreateAdvisorCheckBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateCheck(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckBody) validateCheck(formats strfmt.Registry) error { + if swag.IsZero(o.Check) { // not required + return nil + } + + if o.Check != nil { + if err := o.Check.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check") + } + + return err + } + } + + return nil +} + +// ContextValidate validate this create advisor check body based on the context it is used +func (o *CreateAdvisorCheckBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateCheck(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckBody) contextValidateCheck(ctx context.Context, formats strfmt.Registry) error { + if o.Check != nil { + + if swag.IsZero(o.Check) { // not required + return nil + } + + if err := o.Check.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check") + } + + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckBody) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +CreateAdvisorCheckDefaultBody create advisor check default body +swagger:model CreateAdvisorCheckDefaultBody +*/ +type CreateAdvisorCheckDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*CreateAdvisorCheckDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this create advisor check default body +func (o *CreateAdvisorCheckDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("CreateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("CreateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this create advisor check default body based on the context it is used +func (o *CreateAdvisorCheckDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("CreateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("CreateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckDefaultBody) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +CreateAdvisorCheckDefaultBodyDetailsItems0 create advisor check default body details items0 +swagger:model CreateAdvisorCheckDefaultBodyDetailsItems0 +*/ +type CreateAdvisorCheckDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // create advisor check default body details items0 + CreateAdvisorCheckDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *CreateAdvisorCheckDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv CreateAdvisorCheckDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.CreateAdvisorCheckDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o CreateAdvisorCheckDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.CreateAdvisorCheckDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.CreateAdvisorCheckDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this create advisor check default body details items0 +func (o *CreateAdvisorCheckDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this create advisor check default body details items0 based on context it is used +func (o *CreateAdvisorCheckDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +CreateAdvisorCheckOKBody create advisor check OK body +swagger:model CreateAdvisorCheckOKBody +*/ +type CreateAdvisorCheckOKBody struct { + // check + Check *CreateAdvisorCheckOKBodyCheck `json:"check,omitempty"` +} + +// Validate validates this create advisor check OK body +func (o *CreateAdvisorCheckOKBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateCheck(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckOKBody) validateCheck(formats strfmt.Registry) error { + if swag.IsZero(o.Check) { // not required + return nil + } + + if o.Check != nil { + if err := o.Check.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("createAdvisorCheckOk" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("createAdvisorCheckOk" + "." + "check") + } + + return err + } + } + + return nil +} + +// ContextValidate validate this create advisor check OK body based on the context it is used +func (o *CreateAdvisorCheckOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateCheck(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckOKBody) contextValidateCheck(ctx context.Context, formats strfmt.Registry) error { + if o.Check != nil { + + if swag.IsZero(o.Check) { // not required + return nil + } + + if err := o.Check.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("createAdvisorCheckOk" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("createAdvisorCheckOk" + "." + "check") + } + + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckOKBody) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +CreateAdvisorCheckOKBodyCheck AdvisorCheck contains check name and status. +swagger:model CreateAdvisorCheckOKBodyCheck +*/ +type CreateAdvisorCheckOKBodyCheck struct { + // Machine-readable name (ID) that is used in expression. + Name string `json:"name,omitempty"` + + // True if that check is enabled. + Enabled bool `json:"enabled,omitempty"` + + // Long human-readable description. + Description string `json:"description,omitempty"` + + // Short human-readable summary. + Summary string `json:"summary,omitempty"` + + // AdvisorCheckInterval represents possible execution interval values for checks. + // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] + Interval *string `json:"interval,omitempty"` + + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*CreateAdvisorCheckOKBodyCheckQueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` +} + +// Validate validates this create advisor check OK body check +func (o *CreateAdvisorCheckOKBodyCheck) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateInterval(formats); err != nil { + res = append(res, err) + } + + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var createAdvisorCheckOkBodyCheckTypeIntervalPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + createAdvisorCheckOkBodyCheckTypeIntervalPropEnum = append(createAdvisorCheckOkBodyCheckTypeIntervalPropEnum, v) + } +} + +const ( + + // CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED captures enum value "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED string = "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + + // CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALSTANDARD captures enum value "ADVISOR_CHECK_INTERVAL_STANDARD" + CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALSTANDARD string = "ADVISOR_CHECK_INTERVAL_STANDARD" + + // CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALFREQUENT captures enum value "ADVISOR_CHECK_INTERVAL_FREQUENT" + CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALFREQUENT string = "ADVISOR_CHECK_INTERVAL_FREQUENT" + + // CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALRARE captures enum value "ADVISOR_CHECK_INTERVAL_RARE" + CreateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALRARE string = "ADVISOR_CHECK_INTERVAL_RARE" +) + +// prop value enum +func (o *CreateAdvisorCheckOKBodyCheck) validateIntervalEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, createAdvisorCheckOkBodyCheckTypeIntervalPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *CreateAdvisorCheckOKBodyCheck) validateInterval(formats strfmt.Registry) error { + if swag.IsZero(o.Interval) { // not required + return nil + } + + // value enum + if err := o.validateIntervalEnum("createAdvisorCheckOk"+"."+"check"+"."+"interval", "body", *o.Interval); err != nil { + return err + } + + return nil +} + +var createAdvisorCheckOkBodyCheckTypeTechnologyPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + createAdvisorCheckOkBodyCheckTypeTechnologyPropEnum = append(createAdvisorCheckOkBodyCheckTypeTechnologyPropEnum, v) + } +} + +const ( + + // CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + + // CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + + // CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + + // CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + CreateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" +) + +// prop value enum +func (o *CreateAdvisorCheckOKBodyCheck) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, createAdvisorCheckOkBodyCheckTypeTechnologyPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *CreateAdvisorCheckOKBodyCheck) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required + return nil + } + + // value enum + if err := o.validateTechnologyEnum("createAdvisorCheckOk"+"."+"check"+"."+"technology", "body", *o.Technology); err != nil { + return err + } + + return nil +} + +func (o *CreateAdvisorCheckOKBodyCheck) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("createAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("createAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this create advisor check OK body check based on the context it is used +func (o *CreateAdvisorCheckOKBodyCheck) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckOKBodyCheck) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("createAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("createAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckOKBodyCheck) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckOKBodyCheck) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckOKBodyCheck + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +CreateAdvisorCheckOKBodyCheckQueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model CreateAdvisorCheckOKBodyCheckQueriesItems0 +*/ +type CreateAdvisorCheckOKBodyCheckQueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this create advisor check OK body check queries items0 +func (o *CreateAdvisorCheckOKBodyCheckQueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this create advisor check OK body check queries items0 based on context it is used +func (o *CreateAdvisorCheckOKBodyCheckQueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckOKBodyCheckQueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckOKBodyCheckQueriesItems0) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckOKBodyCheckQueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +CreateAdvisorCheckParamsBodyCheck AdvisorCheck contains check name and status. +swagger:model CreateAdvisorCheckParamsBodyCheck +*/ +type CreateAdvisorCheckParamsBodyCheck struct { + // Machine-readable name (ID) that is used in expression. + Name string `json:"name,omitempty"` + + // True if that check is enabled. + Enabled bool `json:"enabled,omitempty"` + + // Long human-readable description. + Description string `json:"description,omitempty"` + + // Short human-readable summary. + Summary string `json:"summary,omitempty"` + + // AdvisorCheckInterval represents possible execution interval values for checks. + // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] + Interval *string `json:"interval,omitempty"` + + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*CreateAdvisorCheckParamsBodyCheckQueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` +} + +// Validate validates this create advisor check params body check +func (o *CreateAdvisorCheckParamsBodyCheck) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateInterval(formats); err != nil { + res = append(res, err) + } + + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var createAdvisorCheckParamsBodyCheckTypeIntervalPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + createAdvisorCheckParamsBodyCheckTypeIntervalPropEnum = append(createAdvisorCheckParamsBodyCheckTypeIntervalPropEnum, v) + } +} + +const ( + + // CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED captures enum value "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED string = "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + + // CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALSTANDARD captures enum value "ADVISOR_CHECK_INTERVAL_STANDARD" + CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALSTANDARD string = "ADVISOR_CHECK_INTERVAL_STANDARD" + + // CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALFREQUENT captures enum value "ADVISOR_CHECK_INTERVAL_FREQUENT" + CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALFREQUENT string = "ADVISOR_CHECK_INTERVAL_FREQUENT" + + // CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALRARE captures enum value "ADVISOR_CHECK_INTERVAL_RARE" + CreateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALRARE string = "ADVISOR_CHECK_INTERVAL_RARE" +) + +// prop value enum +func (o *CreateAdvisorCheckParamsBodyCheck) validateIntervalEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, createAdvisorCheckParamsBodyCheckTypeIntervalPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *CreateAdvisorCheckParamsBodyCheck) validateInterval(formats strfmt.Registry) error { + if swag.IsZero(o.Interval) { // not required + return nil + } + + // value enum + if err := o.validateIntervalEnum("body"+"."+"check"+"."+"interval", "body", *o.Interval); err != nil { + return err + } + + return nil +} + +var createAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + createAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum = append(createAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum, v) + } +} + +const ( + + // CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + + // CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + + // CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + + // CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + CreateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" +) + +// prop value enum +func (o *CreateAdvisorCheckParamsBodyCheck) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, createAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *CreateAdvisorCheckParamsBodyCheck) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required + return nil + } + + // value enum + if err := o.validateTechnologyEnum("body"+"."+"check"+"."+"technology", "body", *o.Technology); err != nil { + return err + } + + return nil +} + +func (o *CreateAdvisorCheckParamsBodyCheck) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this create advisor check params body check based on the context it is used +func (o *CreateAdvisorCheckParamsBodyCheck) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *CreateAdvisorCheckParamsBodyCheck) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckParamsBodyCheck) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckParamsBodyCheck) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckParamsBodyCheck + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +CreateAdvisorCheckParamsBodyCheckQueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model CreateAdvisorCheckParamsBodyCheckQueriesItems0 +*/ +type CreateAdvisorCheckParamsBodyCheckQueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this create advisor check params body check queries items0 +func (o *CreateAdvisorCheckParamsBodyCheckQueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this create advisor check params body check queries items0 based on context it is used +func (o *CreateAdvisorCheckParamsBodyCheckQueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *CreateAdvisorCheckParamsBodyCheckQueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *CreateAdvisorCheckParamsBodyCheckQueriesItems0) UnmarshalBinary(b []byte) error { + var res CreateAdvisorCheckParamsBodyCheckQueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/delete_advisor_check_parameters.go b/api/advisors/v1/json/client/advisor_service/delete_advisor_check_parameters.go new file mode 100644 index 00000000000..9e4e3f8ee2e --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/delete_advisor_check_parameters.go @@ -0,0 +1,146 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewDeleteAdvisorCheckParams creates a new DeleteAdvisorCheckParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewDeleteAdvisorCheckParams() *DeleteAdvisorCheckParams { + return &DeleteAdvisorCheckParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewDeleteAdvisorCheckParamsWithTimeout creates a new DeleteAdvisorCheckParams object +// with the ability to set a timeout on a request. +func NewDeleteAdvisorCheckParamsWithTimeout(timeout time.Duration) *DeleteAdvisorCheckParams { + return &DeleteAdvisorCheckParams{ + timeout: timeout, + } +} + +// NewDeleteAdvisorCheckParamsWithContext creates a new DeleteAdvisorCheckParams object +// with the ability to set a context for a request. +func NewDeleteAdvisorCheckParamsWithContext(ctx context.Context) *DeleteAdvisorCheckParams { + return &DeleteAdvisorCheckParams{ + Context: ctx, + } +} + +// NewDeleteAdvisorCheckParamsWithHTTPClient creates a new DeleteAdvisorCheckParams object +// with the ability to set a custom HTTPClient for a request. +func NewDeleteAdvisorCheckParamsWithHTTPClient(client *http.Client) *DeleteAdvisorCheckParams { + return &DeleteAdvisorCheckParams{ + HTTPClient: client, + } +} + +/* +DeleteAdvisorCheckParams contains all the parameters to send to the API endpoint + + for the delete advisor check operation. + + Typically these are written to a http.Request. +*/ +type DeleteAdvisorCheckParams struct { + /* Name. + + Machine-readable name (ID) of the check to delete. + */ + Name string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the delete advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *DeleteAdvisorCheckParams) WithDefaults() *DeleteAdvisorCheckParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the delete advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *DeleteAdvisorCheckParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the delete advisor check params +func (o *DeleteAdvisorCheckParams) WithTimeout(timeout time.Duration) *DeleteAdvisorCheckParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the delete advisor check params +func (o *DeleteAdvisorCheckParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the delete advisor check params +func (o *DeleteAdvisorCheckParams) WithContext(ctx context.Context) *DeleteAdvisorCheckParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the delete advisor check params +func (o *DeleteAdvisorCheckParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the delete advisor check params +func (o *DeleteAdvisorCheckParams) WithHTTPClient(client *http.Client) *DeleteAdvisorCheckParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the delete advisor check params +func (o *DeleteAdvisorCheckParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithName adds the name to the delete advisor check params +func (o *DeleteAdvisorCheckParams) WithName(name string) *DeleteAdvisorCheckParams { + o.SetName(name) + return o +} + +// SetName adds the name to the delete advisor check params +func (o *DeleteAdvisorCheckParams) SetName(name string) { + o.Name = name +} + +// WriteToRequest writes these params to a swagger request +func (o *DeleteAdvisorCheckParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // path param name + if err := r.SetPathParam("name", o.Name); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/delete_advisor_check_responses.go b/api/advisors/v1/json/client/advisor_service/delete_advisor_check_responses.go new file mode 100644 index 00000000000..801ff8196d8 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/delete_advisor_check_responses.go @@ -0,0 +1,411 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// DeleteAdvisorCheckReader is a Reader for the DeleteAdvisorCheck structure. +type DeleteAdvisorCheckReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *DeleteAdvisorCheckReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewDeleteAdvisorCheckOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewDeleteAdvisorCheckDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewDeleteAdvisorCheckOK creates a DeleteAdvisorCheckOK with default headers values +func NewDeleteAdvisorCheckOK() *DeleteAdvisorCheckOK { + return &DeleteAdvisorCheckOK{} +} + +/* +DeleteAdvisorCheckOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type DeleteAdvisorCheckOK struct { + Payload any +} + +// IsSuccess returns true when this delete advisor check Ok response has a 2xx status code +func (o *DeleteAdvisorCheckOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this delete advisor check Ok response has a 3xx status code +func (o *DeleteAdvisorCheckOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this delete advisor check Ok response has a 4xx status code +func (o *DeleteAdvisorCheckOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this delete advisor check Ok response has a 5xx status code +func (o *DeleteAdvisorCheckOK) IsServerError() bool { + return false +} + +// IsCode returns true when this delete advisor check Ok response a status code equal to that given +func (o *DeleteAdvisorCheckOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the delete advisor check Ok response +func (o *DeleteAdvisorCheckOK) Code() int { + return 200 +} + +func (o *DeleteAdvisorCheckOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[DELETE /v1/advisors/checks/{name}][%d] deleteAdvisorCheckOk %s", 200, payload) +} + +func (o *DeleteAdvisorCheckOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[DELETE /v1/advisors/checks/{name}][%d] deleteAdvisorCheckOk %s", 200, payload) +} + +func (o *DeleteAdvisorCheckOK) GetPayload() any { + return o.Payload +} + +func (o *DeleteAdvisorCheckOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewDeleteAdvisorCheckDefault creates a DeleteAdvisorCheckDefault with default headers values +func NewDeleteAdvisorCheckDefault(code int) *DeleteAdvisorCheckDefault { + return &DeleteAdvisorCheckDefault{ + _statusCode: code, + } +} + +/* +DeleteAdvisorCheckDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type DeleteAdvisorCheckDefault struct { + _statusCode int + + Payload *DeleteAdvisorCheckDefaultBody +} + +// IsSuccess returns true when this delete advisor check default response has a 2xx status code +func (o *DeleteAdvisorCheckDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this delete advisor check default response has a 3xx status code +func (o *DeleteAdvisorCheckDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this delete advisor check default response has a 4xx status code +func (o *DeleteAdvisorCheckDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this delete advisor check default response has a 5xx status code +func (o *DeleteAdvisorCheckDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this delete advisor check default response a status code equal to that given +func (o *DeleteAdvisorCheckDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the delete advisor check default response +func (o *DeleteAdvisorCheckDefault) Code() int { + return o._statusCode +} + +func (o *DeleteAdvisorCheckDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[DELETE /v1/advisors/checks/{name}][%d] DeleteAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *DeleteAdvisorCheckDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[DELETE /v1/advisors/checks/{name}][%d] DeleteAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *DeleteAdvisorCheckDefault) GetPayload() *DeleteAdvisorCheckDefaultBody { + return o.Payload +} + +func (o *DeleteAdvisorCheckDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(DeleteAdvisorCheckDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +DeleteAdvisorCheckDefaultBody delete advisor check default body +swagger:model DeleteAdvisorCheckDefaultBody +*/ +type DeleteAdvisorCheckDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*DeleteAdvisorCheckDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this delete advisor check default body +func (o *DeleteAdvisorCheckDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *DeleteAdvisorCheckDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("DeleteAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("DeleteAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this delete advisor check default body based on the context it is used +func (o *DeleteAdvisorCheckDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *DeleteAdvisorCheckDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("DeleteAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("DeleteAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *DeleteAdvisorCheckDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *DeleteAdvisorCheckDefaultBody) UnmarshalBinary(b []byte) error { + var res DeleteAdvisorCheckDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +DeleteAdvisorCheckDefaultBodyDetailsItems0 delete advisor check default body details items0 +swagger:model DeleteAdvisorCheckDefaultBodyDetailsItems0 +*/ +type DeleteAdvisorCheckDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // delete advisor check default body details items0 + DeleteAdvisorCheckDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *DeleteAdvisorCheckDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv DeleteAdvisorCheckDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.DeleteAdvisorCheckDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o DeleteAdvisorCheckDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.DeleteAdvisorCheckDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.DeleteAdvisorCheckDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this delete advisor check default body details items0 +func (o *DeleteAdvisorCheckDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this delete advisor check default body details items0 based on context it is used +func (o *DeleteAdvisorCheckDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *DeleteAdvisorCheckDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *DeleteAdvisorCheckDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res DeleteAdvisorCheckDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/get_advisor_check_parameters.go b/api/advisors/v1/json/client/advisor_service/get_advisor_check_parameters.go new file mode 100644 index 00000000000..598a527add9 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/get_advisor_check_parameters.go @@ -0,0 +1,146 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetAdvisorCheckParams creates a new GetAdvisorCheckParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetAdvisorCheckParams() *GetAdvisorCheckParams { + return &GetAdvisorCheckParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetAdvisorCheckParamsWithTimeout creates a new GetAdvisorCheckParams object +// with the ability to set a timeout on a request. +func NewGetAdvisorCheckParamsWithTimeout(timeout time.Duration) *GetAdvisorCheckParams { + return &GetAdvisorCheckParams{ + timeout: timeout, + } +} + +// NewGetAdvisorCheckParamsWithContext creates a new GetAdvisorCheckParams object +// with the ability to set a context for a request. +func NewGetAdvisorCheckParamsWithContext(ctx context.Context) *GetAdvisorCheckParams { + return &GetAdvisorCheckParams{ + Context: ctx, + } +} + +// NewGetAdvisorCheckParamsWithHTTPClient creates a new GetAdvisorCheckParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetAdvisorCheckParamsWithHTTPClient(client *http.Client) *GetAdvisorCheckParams { + return &GetAdvisorCheckParams{ + HTTPClient: client, + } +} + +/* +GetAdvisorCheckParams contains all the parameters to send to the API endpoint + + for the get advisor check operation. + + Typically these are written to a http.Request. +*/ +type GetAdvisorCheckParams struct { + /* Name. + + Machine-readable name (ID) of the check. + */ + Name string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetAdvisorCheckParams) WithDefaults() *GetAdvisorCheckParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetAdvisorCheckParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get advisor check params +func (o *GetAdvisorCheckParams) WithTimeout(timeout time.Duration) *GetAdvisorCheckParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get advisor check params +func (o *GetAdvisorCheckParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get advisor check params +func (o *GetAdvisorCheckParams) WithContext(ctx context.Context) *GetAdvisorCheckParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get advisor check params +func (o *GetAdvisorCheckParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get advisor check params +func (o *GetAdvisorCheckParams) WithHTTPClient(client *http.Client) *GetAdvisorCheckParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get advisor check params +func (o *GetAdvisorCheckParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithName adds the name to the get advisor check params +func (o *GetAdvisorCheckParams) WithName(name string) *GetAdvisorCheckParams { + o.SetName(name) + return o +} + +// SetName adds the name to the get advisor check params +func (o *GetAdvisorCheckParams) SetName(name string) { + o.Name = name +} + +// WriteToRequest writes these params to a swagger request +func (o *GetAdvisorCheckParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // path param name + if err := r.SetPathParam("name", o.Name); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/get_advisor_check_responses.go b/api/advisors/v1/json/client/advisor_service/get_advisor_check_responses.go new file mode 100644 index 00000000000..203685c3af1 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/get_advisor_check_responses.go @@ -0,0 +1,809 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// GetAdvisorCheckReader is a Reader for the GetAdvisorCheck structure. +type GetAdvisorCheckReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetAdvisorCheckReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewGetAdvisorCheckOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewGetAdvisorCheckDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewGetAdvisorCheckOK creates a GetAdvisorCheckOK with default headers values +func NewGetAdvisorCheckOK() *GetAdvisorCheckOK { + return &GetAdvisorCheckOK{} +} + +/* +GetAdvisorCheckOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type GetAdvisorCheckOK struct { + Payload *GetAdvisorCheckOKBody +} + +// IsSuccess returns true when this get advisor check Ok response has a 2xx status code +func (o *GetAdvisorCheckOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get advisor check Ok response has a 3xx status code +func (o *GetAdvisorCheckOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get advisor check Ok response has a 4xx status code +func (o *GetAdvisorCheckOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get advisor check Ok response has a 5xx status code +func (o *GetAdvisorCheckOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get advisor check Ok response a status code equal to that given +func (o *GetAdvisorCheckOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the get advisor check Ok response +func (o *GetAdvisorCheckOK) Code() int { + return 200 +} + +func (o *GetAdvisorCheckOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks/{name}][%d] getAdvisorCheckOk %s", 200, payload) +} + +func (o *GetAdvisorCheckOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks/{name}][%d] getAdvisorCheckOk %s", 200, payload) +} + +func (o *GetAdvisorCheckOK) GetPayload() *GetAdvisorCheckOKBody { + return o.Payload +} + +func (o *GetAdvisorCheckOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(GetAdvisorCheckOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewGetAdvisorCheckDefault creates a GetAdvisorCheckDefault with default headers values +func NewGetAdvisorCheckDefault(code int) *GetAdvisorCheckDefault { + return &GetAdvisorCheckDefault{ + _statusCode: code, + } +} + +/* +GetAdvisorCheckDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type GetAdvisorCheckDefault struct { + _statusCode int + + Payload *GetAdvisorCheckDefaultBody +} + +// IsSuccess returns true when this get advisor check default response has a 2xx status code +func (o *GetAdvisorCheckDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this get advisor check default response has a 3xx status code +func (o *GetAdvisorCheckDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this get advisor check default response has a 4xx status code +func (o *GetAdvisorCheckDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this get advisor check default response has a 5xx status code +func (o *GetAdvisorCheckDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this get advisor check default response a status code equal to that given +func (o *GetAdvisorCheckDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the get advisor check default response +func (o *GetAdvisorCheckDefault) Code() int { + return o._statusCode +} + +func (o *GetAdvisorCheckDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks/{name}][%d] GetAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *GetAdvisorCheckDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks/{name}][%d] GetAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *GetAdvisorCheckDefault) GetPayload() *GetAdvisorCheckDefaultBody { + return o.Payload +} + +func (o *GetAdvisorCheckDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(GetAdvisorCheckDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +GetAdvisorCheckDefaultBody get advisor check default body +swagger:model GetAdvisorCheckDefaultBody +*/ +type GetAdvisorCheckDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*GetAdvisorCheckDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this get advisor check default body +func (o *GetAdvisorCheckDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *GetAdvisorCheckDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("GetAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("GetAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this get advisor check default body based on the context it is used +func (o *GetAdvisorCheckDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *GetAdvisorCheckDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("GetAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("GetAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *GetAdvisorCheckDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GetAdvisorCheckDefaultBody) UnmarshalBinary(b []byte) error { + var res GetAdvisorCheckDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +GetAdvisorCheckDefaultBodyDetailsItems0 get advisor check default body details items0 +swagger:model GetAdvisorCheckDefaultBodyDetailsItems0 +*/ +type GetAdvisorCheckDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // get advisor check default body details items0 + GetAdvisorCheckDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *GetAdvisorCheckDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv GetAdvisorCheckDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.GetAdvisorCheckDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o GetAdvisorCheckDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.GetAdvisorCheckDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.GetAdvisorCheckDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this get advisor check default body details items0 +func (o *GetAdvisorCheckDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this get advisor check default body details items0 based on context it is used +func (o *GetAdvisorCheckDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *GetAdvisorCheckDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GetAdvisorCheckDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res GetAdvisorCheckDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +GetAdvisorCheckOKBody get advisor check OK body +swagger:model GetAdvisorCheckOKBody +*/ +type GetAdvisorCheckOKBody struct { + // check + Check *GetAdvisorCheckOKBodyCheck `json:"check,omitempty"` +} + +// Validate validates this get advisor check OK body +func (o *GetAdvisorCheckOKBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateCheck(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *GetAdvisorCheckOKBody) validateCheck(formats strfmt.Registry) error { + if swag.IsZero(o.Check) { // not required + return nil + } + + if o.Check != nil { + if err := o.Check.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("getAdvisorCheckOk" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("getAdvisorCheckOk" + "." + "check") + } + + return err + } + } + + return nil +} + +// ContextValidate validate this get advisor check OK body based on the context it is used +func (o *GetAdvisorCheckOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateCheck(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *GetAdvisorCheckOKBody) contextValidateCheck(ctx context.Context, formats strfmt.Registry) error { + if o.Check != nil { + + if swag.IsZero(o.Check) { // not required + return nil + } + + if err := o.Check.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("getAdvisorCheckOk" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("getAdvisorCheckOk" + "." + "check") + } + + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *GetAdvisorCheckOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GetAdvisorCheckOKBody) UnmarshalBinary(b []byte) error { + var res GetAdvisorCheckOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +GetAdvisorCheckOKBodyCheck AdvisorCheck contains check name and status. +swagger:model GetAdvisorCheckOKBodyCheck +*/ +type GetAdvisorCheckOKBodyCheck struct { + // Machine-readable name (ID) that is used in expression. + Name string `json:"name,omitempty"` + + // True if that check is enabled. + Enabled bool `json:"enabled,omitempty"` + + // Long human-readable description. + Description string `json:"description,omitempty"` + + // Short human-readable summary. + Summary string `json:"summary,omitempty"` + + // AdvisorCheckInterval represents possible execution interval values for checks. + // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] + Interval *string `json:"interval,omitempty"` + + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*GetAdvisorCheckOKBodyCheckQueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` +} + +// Validate validates this get advisor check OK body check +func (o *GetAdvisorCheckOKBodyCheck) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateInterval(formats); err != nil { + res = append(res, err) + } + + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var getAdvisorCheckOkBodyCheckTypeIntervalPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + getAdvisorCheckOkBodyCheckTypeIntervalPropEnum = append(getAdvisorCheckOkBodyCheckTypeIntervalPropEnum, v) + } +} + +const ( + + // GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED captures enum value "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED string = "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + + // GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALSTANDARD captures enum value "ADVISOR_CHECK_INTERVAL_STANDARD" + GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALSTANDARD string = "ADVISOR_CHECK_INTERVAL_STANDARD" + + // GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALFREQUENT captures enum value "ADVISOR_CHECK_INTERVAL_FREQUENT" + GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALFREQUENT string = "ADVISOR_CHECK_INTERVAL_FREQUENT" + + // GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALRARE captures enum value "ADVISOR_CHECK_INTERVAL_RARE" + GetAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALRARE string = "ADVISOR_CHECK_INTERVAL_RARE" +) + +// prop value enum +func (o *GetAdvisorCheckOKBodyCheck) validateIntervalEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, getAdvisorCheckOkBodyCheckTypeIntervalPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *GetAdvisorCheckOKBodyCheck) validateInterval(formats strfmt.Registry) error { + if swag.IsZero(o.Interval) { // not required + return nil + } + + // value enum + if err := o.validateIntervalEnum("getAdvisorCheckOk"+"."+"check"+"."+"interval", "body", *o.Interval); err != nil { + return err + } + + return nil +} + +var getAdvisorCheckOkBodyCheckTypeTechnologyPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + getAdvisorCheckOkBodyCheckTypeTechnologyPropEnum = append(getAdvisorCheckOkBodyCheckTypeTechnologyPropEnum, v) + } +} + +const ( + + // GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + + // GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + + // GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + + // GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + GetAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" +) + +// prop value enum +func (o *GetAdvisorCheckOKBodyCheck) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, getAdvisorCheckOkBodyCheckTypeTechnologyPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *GetAdvisorCheckOKBodyCheck) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required + return nil + } + + // value enum + if err := o.validateTechnologyEnum("getAdvisorCheckOk"+"."+"check"+"."+"technology", "body", *o.Technology); err != nil { + return err + } + + return nil +} + +func (o *GetAdvisorCheckOKBodyCheck) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("getAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("getAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this get advisor check OK body check based on the context it is used +func (o *GetAdvisorCheckOKBodyCheck) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *GetAdvisorCheckOKBodyCheck) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("getAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("getAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *GetAdvisorCheckOKBodyCheck) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GetAdvisorCheckOKBodyCheck) UnmarshalBinary(b []byte) error { + var res GetAdvisorCheckOKBodyCheck + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +GetAdvisorCheckOKBodyCheckQueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model GetAdvisorCheckOKBodyCheckQueriesItems0 +*/ +type GetAdvisorCheckOKBodyCheckQueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this get advisor check OK body check queries items0 +func (o *GetAdvisorCheckOKBodyCheckQueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this get advisor check OK body check queries items0 based on context it is used +func (o *GetAdvisorCheckOKBodyCheckQueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *GetAdvisorCheckOKBodyCheckQueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GetAdvisorCheckOKBodyCheckQueriesItems0) UnmarshalBinary(b []byte) error { + var res GetAdvisorCheckOKBodyCheckQueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/get_failed_checks_parameters.go b/api/advisors/v1/json/client/advisor_service/get_failed_checks_parameters.go deleted file mode 100644 index b533967fc6a..00000000000 --- a/api/advisors/v1/json/client/advisor_service/get_failed_checks_parameters.go +++ /dev/null @@ -1,228 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package advisor_service - -import ( - "context" - "net/http" - "time" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - cr "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" -) - -// NewGetFailedChecksParams creates a new GetFailedChecksParams object, -// with the default timeout for this client. -// -// Default values are not hydrated, since defaults are normally applied by the API server side. -// -// To enforce default values in parameter, use SetDefaults or WithDefaults. -func NewGetFailedChecksParams() *GetFailedChecksParams { - return &GetFailedChecksParams{ - timeout: cr.DefaultTimeout, - } -} - -// NewGetFailedChecksParamsWithTimeout creates a new GetFailedChecksParams object -// with the ability to set a timeout on a request. -func NewGetFailedChecksParamsWithTimeout(timeout time.Duration) *GetFailedChecksParams { - return &GetFailedChecksParams{ - timeout: timeout, - } -} - -// NewGetFailedChecksParamsWithContext creates a new GetFailedChecksParams object -// with the ability to set a context for a request. -func NewGetFailedChecksParamsWithContext(ctx context.Context) *GetFailedChecksParams { - return &GetFailedChecksParams{ - Context: ctx, - } -} - -// NewGetFailedChecksParamsWithHTTPClient creates a new GetFailedChecksParams object -// with the ability to set a custom HTTPClient for a request. -func NewGetFailedChecksParamsWithHTTPClient(client *http.Client) *GetFailedChecksParams { - return &GetFailedChecksParams{ - HTTPClient: client, - } -} - -/* -GetFailedChecksParams contains all the parameters to send to the API endpoint - - for the get failed checks operation. - - Typically these are written to a http.Request. -*/ -type GetFailedChecksParams struct { - /* PageIndex. - - Index of the requested page, starts from 0. - - Format: int32 - */ - PageIndex *int32 - - /* PageSize. - - Maximum number of results per page. - - Format: int32 - */ - PageSize *int32 - - /* ServiceID. - - Service ID. - */ - ServiceID *string - - timeout time.Duration - Context context.Context - HTTPClient *http.Client -} - -// WithDefaults hydrates default values in the get failed checks params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *GetFailedChecksParams) WithDefaults() *GetFailedChecksParams { - o.SetDefaults() - return o -} - -// SetDefaults hydrates default values in the get failed checks params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *GetFailedChecksParams) SetDefaults() { - // no default values defined for this parameter -} - -// WithTimeout adds the timeout to the get failed checks params -func (o *GetFailedChecksParams) WithTimeout(timeout time.Duration) *GetFailedChecksParams { - o.SetTimeout(timeout) - return o -} - -// SetTimeout adds the timeout to the get failed checks params -func (o *GetFailedChecksParams) SetTimeout(timeout time.Duration) { - o.timeout = timeout -} - -// WithContext adds the context to the get failed checks params -func (o *GetFailedChecksParams) WithContext(ctx context.Context) *GetFailedChecksParams { - o.SetContext(ctx) - return o -} - -// SetContext adds the context to the get failed checks params -func (o *GetFailedChecksParams) SetContext(ctx context.Context) { - o.Context = ctx -} - -// WithHTTPClient adds the HTTPClient to the get failed checks params -func (o *GetFailedChecksParams) WithHTTPClient(client *http.Client) *GetFailedChecksParams { - o.SetHTTPClient(client) - return o -} - -// SetHTTPClient adds the HTTPClient to the get failed checks params -func (o *GetFailedChecksParams) SetHTTPClient(client *http.Client) { - o.HTTPClient = client -} - -// WithPageIndex adds the pageIndex to the get failed checks params -func (o *GetFailedChecksParams) WithPageIndex(pageIndex *int32) *GetFailedChecksParams { - o.SetPageIndex(pageIndex) - return o -} - -// SetPageIndex adds the pageIndex to the get failed checks params -func (o *GetFailedChecksParams) SetPageIndex(pageIndex *int32) { - o.PageIndex = pageIndex -} - -// WithPageSize adds the pageSize to the get failed checks params -func (o *GetFailedChecksParams) WithPageSize(pageSize *int32) *GetFailedChecksParams { - o.SetPageSize(pageSize) - return o -} - -// SetPageSize adds the pageSize to the get failed checks params -func (o *GetFailedChecksParams) SetPageSize(pageSize *int32) { - o.PageSize = pageSize -} - -// WithServiceID adds the serviceID to the get failed checks params -func (o *GetFailedChecksParams) WithServiceID(serviceID *string) *GetFailedChecksParams { - o.SetServiceID(serviceID) - return o -} - -// SetServiceID adds the serviceId to the get failed checks params -func (o *GetFailedChecksParams) SetServiceID(serviceID *string) { - o.ServiceID = serviceID -} - -// WriteToRequest writes these params to a swagger request -func (o *GetFailedChecksParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { - if err := r.SetTimeout(o.timeout); err != nil { - return err - } - var res []error - - if o.PageIndex != nil { - - // query param page_index - var qrPageIndex int32 - - if o.PageIndex != nil { - qrPageIndex = *o.PageIndex - } - qPageIndex := swag.FormatInt32(qrPageIndex) - if qPageIndex != "" { - if err := r.SetQueryParam("page_index", qPageIndex); err != nil { - return err - } - } - } - - if o.PageSize != nil { - - // query param page_size - var qrPageSize int32 - - if o.PageSize != nil { - qrPageSize = *o.PageSize - } - qPageSize := swag.FormatInt32(qrPageSize) - if qPageSize != "" { - if err := r.SetQueryParam("page_size", qPageSize); err != nil { - return err - } - } - } - - if o.ServiceID != nil { - - // query param service_id - var qrServiceID string - - if o.ServiceID != nil { - qrServiceID = *o.ServiceID - } - qServiceID := qrServiceID - if qServiceID != "" { - if err := r.SetQueryParam("service_id", qServiceID); err != nil { - return err - } - } - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/api/advisors/v1/json/client/advisor_service/get_failed_checks_responses.go b/api/advisors/v1/json/client/advisor_service/get_failed_checks_responses.go deleted file mode 100644 index 083afeceffd..00000000000 --- a/api/advisors/v1/json/client/advisor_service/get_failed_checks_responses.go +++ /dev/null @@ -1,665 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package advisor_service - -import ( - "context" - "encoding/json" - stderrors "errors" - "fmt" - "io" - "strconv" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// GetFailedChecksReader is a Reader for the GetFailedChecks structure. -type GetFailedChecksReader struct { - formats strfmt.Registry -} - -// ReadResponse reads a server response into the received o. -func (o *GetFailedChecksReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { - switch response.Code() { - case 200: - result := NewGetFailedChecksOK() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return result, nil - default: - result := NewGetFailedChecksDefault(response.Code()) - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - if response.Code()/100 == 2 { - return result, nil - } - return nil, result - } -} - -// NewGetFailedChecksOK creates a GetFailedChecksOK with default headers values -func NewGetFailedChecksOK() *GetFailedChecksOK { - return &GetFailedChecksOK{} -} - -/* -GetFailedChecksOK describes a response with status code 200, with default header values. - -A successful response. -*/ -type GetFailedChecksOK struct { - Payload *GetFailedChecksOKBody -} - -// IsSuccess returns true when this get failed checks Ok response has a 2xx status code -func (o *GetFailedChecksOK) IsSuccess() bool { - return true -} - -// IsRedirect returns true when this get failed checks Ok response has a 3xx status code -func (o *GetFailedChecksOK) IsRedirect() bool { - return false -} - -// IsClientError returns true when this get failed checks Ok response has a 4xx status code -func (o *GetFailedChecksOK) IsClientError() bool { - return false -} - -// IsServerError returns true when this get failed checks Ok response has a 5xx status code -func (o *GetFailedChecksOK) IsServerError() bool { - return false -} - -// IsCode returns true when this get failed checks Ok response a status code equal to that given -func (o *GetFailedChecksOK) IsCode(code int) bool { - return code == 200 -} - -// Code gets the status code for the get failed checks Ok response -func (o *GetFailedChecksOK) Code() int { - return 200 -} - -func (o *GetFailedChecksOK) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/checks/failed][%d] getFailedChecksOk %s", 200, payload) -} - -func (o *GetFailedChecksOK) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/checks/failed][%d] getFailedChecksOk %s", 200, payload) -} - -func (o *GetFailedChecksOK) GetPayload() *GetFailedChecksOKBody { - return o.Payload -} - -func (o *GetFailedChecksOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - o.Payload = new(GetFailedChecksOKBody) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -// NewGetFailedChecksDefault creates a GetFailedChecksDefault with default headers values -func NewGetFailedChecksDefault(code int) *GetFailedChecksDefault { - return &GetFailedChecksDefault{ - _statusCode: code, - } -} - -/* -GetFailedChecksDefault describes a response with status code -1, with default header values. - -An unexpected error response. -*/ -type GetFailedChecksDefault struct { - _statusCode int - - Payload *GetFailedChecksDefaultBody -} - -// IsSuccess returns true when this get failed checks default response has a 2xx status code -func (o *GetFailedChecksDefault) IsSuccess() bool { - return o._statusCode/100 == 2 -} - -// IsRedirect returns true when this get failed checks default response has a 3xx status code -func (o *GetFailedChecksDefault) IsRedirect() bool { - return o._statusCode/100 == 3 -} - -// IsClientError returns true when this get failed checks default response has a 4xx status code -func (o *GetFailedChecksDefault) IsClientError() bool { - return o._statusCode/100 == 4 -} - -// IsServerError returns true when this get failed checks default response has a 5xx status code -func (o *GetFailedChecksDefault) IsServerError() bool { - return o._statusCode/100 == 5 -} - -// IsCode returns true when this get failed checks default response a status code equal to that given -func (o *GetFailedChecksDefault) IsCode(code int) bool { - return o._statusCode == code -} - -// Code gets the status code for the get failed checks default response -func (o *GetFailedChecksDefault) Code() int { - return o._statusCode -} - -func (o *GetFailedChecksDefault) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/checks/failed][%d] GetFailedChecks default %s", o._statusCode, payload) -} - -func (o *GetFailedChecksDefault) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/checks/failed][%d] GetFailedChecks default %s", o._statusCode, payload) -} - -func (o *GetFailedChecksDefault) GetPayload() *GetFailedChecksDefaultBody { - return o.Payload -} - -func (o *GetFailedChecksDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - o.Payload = new(GetFailedChecksDefaultBody) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -/* -GetFailedChecksDefaultBody get failed checks default body -swagger:model GetFailedChecksDefaultBody -*/ -type GetFailedChecksDefaultBody struct { - // code - Code int32 `json:"code,omitempty"` - - // message - Message string `json:"message,omitempty"` - - // details - Details []*GetFailedChecksDefaultBodyDetailsItems0 `json:"details"` -} - -// Validate validates this get failed checks default body -func (o *GetFailedChecksDefaultBody) Validate(formats strfmt.Registry) error { - var res []error - - if err := o.validateDetails(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *GetFailedChecksDefaultBody) validateDetails(formats strfmt.Registry) error { - if swag.IsZero(o.Details) { // not required - return nil - } - - for i := 0; i < len(o.Details); i++ { - if swag.IsZero(o.Details[i]) { // not required - continue - } - - if o.Details[i] != nil { - if err := o.Details[i].Validate(formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("GetFailedChecks default" + "." + "details" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("GetFailedChecks default" + "." + "details" + "." + strconv.Itoa(i)) - } - - return err - } - } - - } - - return nil -} - -// ContextValidate validate this get failed checks default body based on the context it is used -func (o *GetFailedChecksDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := o.contextValidateDetails(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *GetFailedChecksDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { - for i := 0; i < len(o.Details); i++ { - if o.Details[i] != nil { - - if swag.IsZero(o.Details[i]) { // not required - return nil - } - - if err := o.Details[i].ContextValidate(ctx, formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("GetFailedChecks default" + "." + "details" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("GetFailedChecks default" + "." + "details" + "." + strconv.Itoa(i)) - } - - return err - } - } - } - - return nil -} - -// MarshalBinary interface implementation -func (o *GetFailedChecksDefaultBody) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *GetFailedChecksDefaultBody) UnmarshalBinary(b []byte) error { - var res GetFailedChecksDefaultBody - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} - -/* -GetFailedChecksDefaultBodyDetailsItems0 get failed checks default body details items0 -swagger:model GetFailedChecksDefaultBodyDetailsItems0 -*/ -type GetFailedChecksDefaultBodyDetailsItems0 struct { - // at type - AtType string `json:"@type,omitempty"` - - // get failed checks default body details items0 - GetFailedChecksDefaultBodyDetailsItems0 map[string]any `json:"-"` -} - -// UnmarshalJSON unmarshals this object with additional properties from JSON -func (o *GetFailedChecksDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { - // stage 1, bind the properties - var stage1 struct { - // at type - AtType string `json:"@type,omitempty"` - } - if err := json.Unmarshal(data, &stage1); err != nil { - return err - } - var rcv GetFailedChecksDefaultBodyDetailsItems0 - - rcv.AtType = stage1.AtType - *o = rcv - - // stage 2, remove properties and add to map - stage2 := make(map[string]json.RawMessage) - if err := json.Unmarshal(data, &stage2); err != nil { - return err - } - - delete(stage2, "@type") - // stage 3, add additional properties values - if len(stage2) > 0 { - result := make(map[string]any) - for k, v := range stage2 { - var toadd any - if err := json.Unmarshal(v, &toadd); err != nil { - return err - } - result[k] = toadd - } - o.GetFailedChecksDefaultBodyDetailsItems0 = result - } - - return nil -} - -// MarshalJSON marshals this object with additional properties into a JSON object -func (o GetFailedChecksDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { - var stage1 struct { - // at type - AtType string `json:"@type,omitempty"` - } - - stage1.AtType = o.AtType - - // make JSON object for known properties - props, err := json.Marshal(stage1) - if err != nil { - return nil, err - } - - if len(o.GetFailedChecksDefaultBodyDetailsItems0) == 0 { // no additional properties - return props, nil - } - - // make JSON object for the additional properties - additional, err := json.Marshal(o.GetFailedChecksDefaultBodyDetailsItems0) - if err != nil { - return nil, err - } - - if len(props) < 3 { // "{}": only additional properties - return additional, nil - } - - // concatenate the 2 objects - return swag.ConcatJSON(props, additional), nil -} - -// Validate validates this get failed checks default body details items0 -func (o *GetFailedChecksDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { - return nil -} - -// ContextValidate validates this get failed checks default body details items0 based on context it is used -func (o *GetFailedChecksDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (o *GetFailedChecksDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *GetFailedChecksDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { - var res GetFailedChecksDefaultBodyDetailsItems0 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} - -/* -GetFailedChecksOKBody get failed checks OK body -swagger:model GetFailedChecksOKBody -*/ -type GetFailedChecksOKBody struct { - // Total number of results. - TotalItems int32 `json:"total_items,omitempty"` - - // Total number of pages. - TotalPages int32 `json:"total_pages,omitempty"` - - // Check results - Results []*GetFailedChecksOKBodyResultsItems0 `json:"results"` -} - -// Validate validates this get failed checks OK body -func (o *GetFailedChecksOKBody) Validate(formats strfmt.Registry) error { - var res []error - - if err := o.validateResults(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *GetFailedChecksOKBody) validateResults(formats strfmt.Registry) error { - if swag.IsZero(o.Results) { // not required - return nil - } - - for i := 0; i < len(o.Results); i++ { - if swag.IsZero(o.Results[i]) { // not required - continue - } - - if o.Results[i] != nil { - if err := o.Results[i].Validate(formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("getFailedChecksOk" + "." + "results" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("getFailedChecksOk" + "." + "results" + "." + strconv.Itoa(i)) - } - - return err - } - } - - } - - return nil -} - -// ContextValidate validate this get failed checks OK body based on the context it is used -func (o *GetFailedChecksOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := o.contextValidateResults(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *GetFailedChecksOKBody) contextValidateResults(ctx context.Context, formats strfmt.Registry) error { - for i := 0; i < len(o.Results); i++ { - if o.Results[i] != nil { - - if swag.IsZero(o.Results[i]) { // not required - return nil - } - - if err := o.Results[i].ContextValidate(ctx, formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("getFailedChecksOk" + "." + "results" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("getFailedChecksOk" + "." + "results" + "." + strconv.Itoa(i)) - } - - return err - } - } - } - - return nil -} - -// MarshalBinary interface implementation -func (o *GetFailedChecksOKBody) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *GetFailedChecksOKBody) UnmarshalBinary(b []byte) error { - var res GetFailedChecksOKBody - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} - -/* -GetFailedChecksOKBodyResultsItems0 CheckResult represents the check results for a given service. -swagger:model GetFailedChecksOKBodyResultsItems0 -*/ -type GetFailedChecksOKBodyResultsItems0 struct { - // summary - Summary string `json:"summary,omitempty"` - - // description - Description string `json:"description,omitempty"` - - // Severity represents severity level of the check result or alert. - // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] - Severity *string `json:"severity,omitempty"` - - // labels - Labels map[string]string `json:"labels,omitempty"` - - // URL containing information on how to resolve an issue detected by an Advisor check. - ReadMoreURL string `json:"read_more_url,omitempty"` - - // Name of the monitored service on which the check ran. - ServiceName string `json:"service_name,omitempty"` - - // ID of the monitored service on which the check ran. - ServiceID string `json:"service_id,omitempty"` - - // Name of the check that failed - CheckName string `json:"check_name,omitempty"` - - // Silence status of the check result - Silenced bool `json:"silenced,omitempty"` -} - -// Validate validates this get failed checks OK body results items0 -func (o *GetFailedChecksOKBodyResultsItems0) Validate(formats strfmt.Registry) error { - var res []error - - if err := o.validateSeverity(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -var getFailedChecksOkBodyResultsItems0TypeSeverityPropEnum []any - -func init() { - var res []string - if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { - panic(err) - } - for _, v := range res { - getFailedChecksOkBodyResultsItems0TypeSeverityPropEnum = append(getFailedChecksOkBodyResultsItems0TypeSeverityPropEnum, v) - } -} - -const ( - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYALERT captures enum value "SEVERITY_ALERT" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYALERT string = "SEVERITY_ALERT" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYCRITICAL string = "SEVERITY_CRITICAL" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYERROR captures enum value "SEVERITY_ERROR" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYERROR string = "SEVERITY_ERROR" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYWARNING captures enum value "SEVERITY_WARNING" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYWARNING string = "SEVERITY_WARNING" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYNOTICE captures enum value "SEVERITY_NOTICE" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYNOTICE string = "SEVERITY_NOTICE" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYINFO captures enum value "SEVERITY_INFO" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYINFO string = "SEVERITY_INFO" - - // GetFailedChecksOKBodyResultsItems0SeveritySEVERITYDEBUG captures enum value "SEVERITY_DEBUG" - GetFailedChecksOKBodyResultsItems0SeveritySEVERITYDEBUG string = "SEVERITY_DEBUG" -) - -// prop value enum -func (o *GetFailedChecksOKBodyResultsItems0) validateSeverityEnum(path, location string, value string) error { - if err := validate.EnumCase(path, location, value, getFailedChecksOkBodyResultsItems0TypeSeverityPropEnum, true); err != nil { - return err - } - return nil -} - -func (o *GetFailedChecksOKBodyResultsItems0) validateSeverity(formats strfmt.Registry) error { - if swag.IsZero(o.Severity) { // not required - return nil - } - - // value enum - if err := o.validateSeverityEnum("severity", "body", *o.Severity); err != nil { - return err - } - - return nil -} - -// ContextValidate validates this get failed checks OK body results items0 based on context it is used -func (o *GetFailedChecksOKBodyResultsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (o *GetFailedChecksOKBodyResultsItems0) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *GetFailedChecksOKBodyResultsItems0) UnmarshalBinary(b []byte) error { - var res GetFailedChecksOKBodyResultsItems0 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} diff --git a/api/advisors/v1/json/client/advisor_service/list_advisor_check_test_targets_parameters.go b/api/advisors/v1/json/client/advisor_service/list_advisor_check_test_targets_parameters.go new file mode 100644 index 00000000000..9695072a148 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_advisor_check_test_targets_parameters.go @@ -0,0 +1,168 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewListAdvisorCheckTestTargetsParams creates a new ListAdvisorCheckTestTargetsParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewListAdvisorCheckTestTargetsParams() *ListAdvisorCheckTestTargetsParams { + return &ListAdvisorCheckTestTargetsParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewListAdvisorCheckTestTargetsParamsWithTimeout creates a new ListAdvisorCheckTestTargetsParams object +// with the ability to set a timeout on a request. +func NewListAdvisorCheckTestTargetsParamsWithTimeout(timeout time.Duration) *ListAdvisorCheckTestTargetsParams { + return &ListAdvisorCheckTestTargetsParams{ + timeout: timeout, + } +} + +// NewListAdvisorCheckTestTargetsParamsWithContext creates a new ListAdvisorCheckTestTargetsParams object +// with the ability to set a context for a request. +func NewListAdvisorCheckTestTargetsParamsWithContext(ctx context.Context) *ListAdvisorCheckTestTargetsParams { + return &ListAdvisorCheckTestTargetsParams{ + Context: ctx, + } +} + +// NewListAdvisorCheckTestTargetsParamsWithHTTPClient creates a new ListAdvisorCheckTestTargetsParams object +// with the ability to set a custom HTTPClient for a request. +func NewListAdvisorCheckTestTargetsParamsWithHTTPClient(client *http.Client) *ListAdvisorCheckTestTargetsParams { + return &ListAdvisorCheckTestTargetsParams{ + HTTPClient: client, + } +} + +/* +ListAdvisorCheckTestTargetsParams contains all the parameters to send to the API endpoint + + for the list advisor check test targets operation. + + Typically these are written to a http.Request. +*/ +type ListAdvisorCheckTestTargetsParams struct { + /* Technology. + + Technology of the check to be tested; determines the eligible service type. + + Default: "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + */ + Technology *string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the list advisor check test targets params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListAdvisorCheckTestTargetsParams) WithDefaults() *ListAdvisorCheckTestTargetsParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the list advisor check test targets params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListAdvisorCheckTestTargetsParams) SetDefaults() { + technologyDefault := string("ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED") + + val := ListAdvisorCheckTestTargetsParams{ + Technology: &technologyDefault, + } + + val.timeout = o.timeout + val.Context = o.Context + val.HTTPClient = o.HTTPClient + *o = val +} + +// WithTimeout adds the timeout to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) WithTimeout(timeout time.Duration) *ListAdvisorCheckTestTargetsParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) WithContext(ctx context.Context) *ListAdvisorCheckTestTargetsParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) WithHTTPClient(client *http.Client) *ListAdvisorCheckTestTargetsParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithTechnology adds the technology to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) WithTechnology(technology *string) *ListAdvisorCheckTestTargetsParams { + o.SetTechnology(technology) + return o +} + +// SetTechnology adds the technology to the list advisor check test targets params +func (o *ListAdvisorCheckTestTargetsParams) SetTechnology(technology *string) { + o.Technology = technology +} + +// WriteToRequest writes these params to a swagger request +func (o *ListAdvisorCheckTestTargetsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if o.Technology != nil { + + // query param technology + var qrTechnology string + + if o.Technology != nil { + qrTechnology = *o.Technology + } + qTechnology := qrTechnology + if qTechnology != "" { + if err := r.SetQueryParam("technology", qTechnology); err != nil { + return err + } + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_advisor_check_test_targets_responses.go b/api/advisors/v1/json/client/advisor_service/list_advisor_check_test_targets_responses.go new file mode 100644 index 00000000000..b1d9c8b55a5 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_advisor_check_test_targets_responses.go @@ -0,0 +1,564 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ListAdvisorCheckTestTargetsReader is a Reader for the ListAdvisorCheckTestTargets structure. +type ListAdvisorCheckTestTargetsReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ListAdvisorCheckTestTargetsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewListAdvisorCheckTestTargetsOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewListAdvisorCheckTestTargetsDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewListAdvisorCheckTestTargetsOK creates a ListAdvisorCheckTestTargetsOK with default headers values +func NewListAdvisorCheckTestTargetsOK() *ListAdvisorCheckTestTargetsOK { + return &ListAdvisorCheckTestTargetsOK{} +} + +/* +ListAdvisorCheckTestTargetsOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type ListAdvisorCheckTestTargetsOK struct { + Payload *ListAdvisorCheckTestTargetsOKBody +} + +// IsSuccess returns true when this list advisor check test targets Ok response has a 2xx status code +func (o *ListAdvisorCheckTestTargetsOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this list advisor check test targets Ok response has a 3xx status code +func (o *ListAdvisorCheckTestTargetsOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this list advisor check test targets Ok response has a 4xx status code +func (o *ListAdvisorCheckTestTargetsOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this list advisor check test targets Ok response has a 5xx status code +func (o *ListAdvisorCheckTestTargetsOK) IsServerError() bool { + return false +} + +// IsCode returns true when this list advisor check test targets Ok response a status code equal to that given +func (o *ListAdvisorCheckTestTargetsOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the list advisor check test targets Ok response +func (o *ListAdvisorCheckTestTargetsOK) Code() int { + return 200 +} + +func (o *ListAdvisorCheckTestTargetsOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks:testTargets][%d] listAdvisorCheckTestTargetsOk %s", 200, payload) +} + +func (o *ListAdvisorCheckTestTargetsOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks:testTargets][%d] listAdvisorCheckTestTargetsOk %s", 200, payload) +} + +func (o *ListAdvisorCheckTestTargetsOK) GetPayload() *ListAdvisorCheckTestTargetsOKBody { + return o.Payload +} + +func (o *ListAdvisorCheckTestTargetsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListAdvisorCheckTestTargetsOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewListAdvisorCheckTestTargetsDefault creates a ListAdvisorCheckTestTargetsDefault with default headers values +func NewListAdvisorCheckTestTargetsDefault(code int) *ListAdvisorCheckTestTargetsDefault { + return &ListAdvisorCheckTestTargetsDefault{ + _statusCode: code, + } +} + +/* +ListAdvisorCheckTestTargetsDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type ListAdvisorCheckTestTargetsDefault struct { + _statusCode int + + Payload *ListAdvisorCheckTestTargetsDefaultBody +} + +// IsSuccess returns true when this list advisor check test targets default response has a 2xx status code +func (o *ListAdvisorCheckTestTargetsDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this list advisor check test targets default response has a 3xx status code +func (o *ListAdvisorCheckTestTargetsDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this list advisor check test targets default response has a 4xx status code +func (o *ListAdvisorCheckTestTargetsDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this list advisor check test targets default response has a 5xx status code +func (o *ListAdvisorCheckTestTargetsDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this list advisor check test targets default response a status code equal to that given +func (o *ListAdvisorCheckTestTargetsDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the list advisor check test targets default response +func (o *ListAdvisorCheckTestTargetsDefault) Code() int { + return o._statusCode +} + +func (o *ListAdvisorCheckTestTargetsDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks:testTargets][%d] ListAdvisorCheckTestTargets default %s", o._statusCode, payload) +} + +func (o *ListAdvisorCheckTestTargetsDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/checks:testTargets][%d] ListAdvisorCheckTestTargets default %s", o._statusCode, payload) +} + +func (o *ListAdvisorCheckTestTargetsDefault) GetPayload() *ListAdvisorCheckTestTargetsDefaultBody { + return o.Payload +} + +func (o *ListAdvisorCheckTestTargetsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListAdvisorCheckTestTargetsDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +ListAdvisorCheckTestTargetsDefaultBody list advisor check test targets default body +swagger:model ListAdvisorCheckTestTargetsDefaultBody +*/ +type ListAdvisorCheckTestTargetsDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this list advisor check test targets default body +func (o *ListAdvisorCheckTestTargetsDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListAdvisorCheckTestTargetsDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListAdvisorCheckTestTargets default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListAdvisorCheckTestTargets default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list advisor check test targets default body based on the context it is used +func (o *ListAdvisorCheckTestTargetsDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListAdvisorCheckTestTargetsDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListAdvisorCheckTestTargets default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListAdvisorCheckTestTargets default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsDefaultBody) UnmarshalBinary(b []byte) error { + var res ListAdvisorCheckTestTargetsDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 list advisor check test targets default body details items0 +swagger:model ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 +*/ +type ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // list advisor check test targets default body details items0 + ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this list advisor check test targets default body details items0 +func (o *ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list advisor check test targets default body details items0 based on context it is used +func (o *ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res ListAdvisorCheckTestTargetsDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListAdvisorCheckTestTargetsOKBody list advisor check test targets OK body +swagger:model ListAdvisorCheckTestTargetsOKBody +*/ +type ListAdvisorCheckTestTargetsOKBody struct { + // Services a check of the requested technology can be tested against. + Targets []*ListAdvisorCheckTestTargetsOKBodyTargetsItems0 `json:"targets"` +} + +// Validate validates this list advisor check test targets OK body +func (o *ListAdvisorCheckTestTargetsOKBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateTargets(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListAdvisorCheckTestTargetsOKBody) validateTargets(formats strfmt.Registry) error { + if swag.IsZero(o.Targets) { // not required + return nil + } + + for i := 0; i < len(o.Targets); i++ { + if swag.IsZero(o.Targets[i]) { // not required + continue + } + + if o.Targets[i] != nil { + if err := o.Targets[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("listAdvisorCheckTestTargetsOk" + "." + "targets" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("listAdvisorCheckTestTargetsOk" + "." + "targets" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list advisor check test targets OK body based on the context it is used +func (o *ListAdvisorCheckTestTargetsOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateTargets(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListAdvisorCheckTestTargetsOKBody) contextValidateTargets(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Targets); i++ { + if o.Targets[i] != nil { + + if swag.IsZero(o.Targets[i]) { // not required + return nil + } + + if err := o.Targets[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("listAdvisorCheckTestTargetsOk" + "." + "targets" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("listAdvisorCheckTestTargetsOk" + "." + "targets" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsOKBody) UnmarshalBinary(b []byte) error { + var res ListAdvisorCheckTestTargetsOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListAdvisorCheckTestTargetsOKBodyTargetsItems0 AdvisorCheckTestTarget is a service an advisor check can be tested against. +swagger:model ListAdvisorCheckTestTargetsOKBodyTargetsItems0 +*/ +type ListAdvisorCheckTestTargetsOKBodyTargetsItems0 struct { + // ID of the eligible service. + ServiceID string `json:"service_id,omitempty"` + + // Name of the eligible service. + ServiceName string `json:"service_name,omitempty"` +} + +// Validate validates this list advisor check test targets OK body targets items0 +func (o *ListAdvisorCheckTestTargetsOKBodyTargetsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list advisor check test targets OK body targets items0 based on context it is used +func (o *ListAdvisorCheckTestTargetsOKBodyTargetsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsOKBodyTargetsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListAdvisorCheckTestTargetsOKBodyTargetsItems0) UnmarshalBinary(b []byte) error { + var res ListAdvisorCheckTestTargetsOKBodyTargetsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_advisor_checks_responses.go b/api/advisors/v1/json/client/advisor_service/list_advisor_checks_responses.go index bb55fdb00b7..637ca6c46a8 100644 --- a/api/advisors/v1/json/client/advisor_service/list_advisor_checks_responses.go +++ b/api/advisors/v1/json/client/advisor_service/list_advisor_checks_responses.go @@ -545,9 +545,27 @@ type ListAdvisorChecksOKBodyChecksItems0 struct { // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] Interval *string `json:"interval,omitempty"` - // family - // Enum: ["ADVISOR_CHECK_FAMILY_UNSPECIFIED","ADVISOR_CHECK_FAMILY_MYSQL","ADVISOR_CHECK_FAMILY_POSTGRESQL","ADVISOR_CHECK_FAMILY_MONGODB"] - Family *string `json:"family,omitempty"` + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*ListAdvisorChecksOKBodyChecksItems0QueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` } // Validate validates this list advisor checks OK body checks items0 @@ -558,7 +576,11 @@ func (o *ListAdvisorChecksOKBodyChecksItems0) Validate(formats strfmt.Registry) res = append(res, err) } - if err := o.validateFamily(formats); err != nil { + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { res = append(res, err) } @@ -616,56 +638,121 @@ func (o *ListAdvisorChecksOKBodyChecksItems0) validateInterval(formats strfmt.Re return nil } -var listAdvisorChecksOkBodyChecksItems0TypeFamilyPropEnum []any +var listAdvisorChecksOkBodyChecksItems0TypeTechnologyPropEnum []any func init() { var res []string - if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_FAMILY_UNSPECIFIED","ADVISOR_CHECK_FAMILY_MYSQL","ADVISOR_CHECK_FAMILY_POSTGRESQL","ADVISOR_CHECK_FAMILY_MONGODB"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { panic(err) } for _, v := range res { - listAdvisorChecksOkBodyChecksItems0TypeFamilyPropEnum = append(listAdvisorChecksOkBodyChecksItems0TypeFamilyPropEnum, v) + listAdvisorChecksOkBodyChecksItems0TypeTechnologyPropEnum = append(listAdvisorChecksOkBodyChecksItems0TypeTechnologyPropEnum, v) } } const ( - // ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYUNSPECIFIED captures enum value "ADVISOR_CHECK_FAMILY_UNSPECIFIED" - ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYUNSPECIFIED string = "ADVISOR_CHECK_FAMILY_UNSPECIFIED" + // ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" - // ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYMYSQL captures enum value "ADVISOR_CHECK_FAMILY_MYSQL" - ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYMYSQL string = "ADVISOR_CHECK_FAMILY_MYSQL" + // ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" - // ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYPOSTGRESQL captures enum value "ADVISOR_CHECK_FAMILY_POSTGRESQL" - ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYPOSTGRESQL string = "ADVISOR_CHECK_FAMILY_POSTGRESQL" + // ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" - // ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYMONGODB captures enum value "ADVISOR_CHECK_FAMILY_MONGODB" - ListAdvisorChecksOKBodyChecksItems0FamilyADVISORCHECKFAMILYMONGODB string = "ADVISOR_CHECK_FAMILY_MONGODB" + // ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ListAdvisorChecksOKBodyChecksItems0TechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ) // prop value enum -func (o *ListAdvisorChecksOKBodyChecksItems0) validateFamilyEnum(path, location string, value string) error { - if err := validate.EnumCase(path, location, value, listAdvisorChecksOkBodyChecksItems0TypeFamilyPropEnum, true); err != nil { +func (o *ListAdvisorChecksOKBodyChecksItems0) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listAdvisorChecksOkBodyChecksItems0TypeTechnologyPropEnum, true); err != nil { return err } return nil } -func (o *ListAdvisorChecksOKBodyChecksItems0) validateFamily(formats strfmt.Registry) error { - if swag.IsZero(o.Family) { // not required +func (o *ListAdvisorChecksOKBodyChecksItems0) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required return nil } // value enum - if err := o.validateFamilyEnum("family", "body", *o.Family); err != nil { + if err := o.validateTechnologyEnum("technology", "body", *o.Technology); err != nil { return err } return nil } -// ContextValidate validates this list advisor checks OK body checks items0 based on context it is used +func (o *ListAdvisorChecksOKBodyChecksItems0) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list advisor checks OK body checks items0 based on the context it is used func (o *ListAdvisorChecksOKBodyChecksItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListAdvisorChecksOKBodyChecksItems0) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + return nil } @@ -686,3 +773,46 @@ func (o *ListAdvisorChecksOKBodyChecksItems0) UnmarshalBinary(b []byte) error { *o = res return nil } + +/* +ListAdvisorChecksOKBodyChecksItems0QueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model ListAdvisorChecksOKBodyChecksItems0QueriesItems0 +*/ +type ListAdvisorChecksOKBodyChecksItems0QueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this list advisor checks OK body checks items0 queries items0 +func (o *ListAdvisorChecksOKBodyChecksItems0QueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list advisor checks OK body checks items0 queries items0 based on context it is used +func (o *ListAdvisorChecksOKBodyChecksItems0QueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListAdvisorChecksOKBodyChecksItems0QueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListAdvisorChecksOKBodyChecksItems0QueriesItems0) UnmarshalBinary(b []byte) error { + var res ListAdvisorChecksOKBodyChecksItems0QueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_advisors_responses.go b/api/advisors/v1/json/client/advisor_service/list_advisors_responses.go index 63df558b915..554fb2377e6 100644 --- a/api/advisors/v1/json/client/advisor_service/list_advisors_responses.go +++ b/api/advisors/v1/json/client/advisor_service/list_advisors_responses.go @@ -529,21 +529,24 @@ ListAdvisorsOKBodyAdvisorsItems0 list advisors OK body advisors items0 swagger:model ListAdvisorsOKBodyAdvisorsItems0 */ type ListAdvisorsOKBodyAdvisorsItems0 struct { - // Machine-readable name (ID) that is used in expression. + // Deprecated: no longer populated; an advisor is identified by its category/subcategory pair. Name string `json:"name,omitempty"` - // Long human-readable description. + // Deprecated: advisor descriptions were removed. Description string `json:"description,omitempty"` - // Short human-readable summary. + // Deprecated: use subcategory instead. Summary string `json:"summary,omitempty"` - // Comment. + // Deprecated: no longer populated. Comment string `json:"comment,omitempty"` - // Category. + // Category (top-level grouping). Category string `json:"category,omitempty"` + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + // Advisor checks. Checks []*ListAdvisorsOKBodyAdvisorsItems0ChecksItems0 `json:"checks"` } @@ -671,9 +674,27 @@ type ListAdvisorsOKBodyAdvisorsItems0ChecksItems0 struct { // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] Interval *string `json:"interval,omitempty"` - // family - // Enum: ["ADVISOR_CHECK_FAMILY_UNSPECIFIED","ADVISOR_CHECK_FAMILY_MYSQL","ADVISOR_CHECK_FAMILY_POSTGRESQL","ADVISOR_CHECK_FAMILY_MONGODB"] - Family *string `json:"family,omitempty"` + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` } // Validate validates this list advisors OK body advisors items0 checks items0 @@ -684,7 +705,11 @@ func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) Validate(formats strfmt.R res = append(res, err) } - if err := o.validateFamily(formats); err != nil { + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { res = append(res, err) } @@ -742,56 +767,121 @@ func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) validateInterval(formats return nil } -var listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeFamilyPropEnum []any +var listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeTechnologyPropEnum []any func init() { var res []string - if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_FAMILY_UNSPECIFIED","ADVISOR_CHECK_FAMILY_MYSQL","ADVISOR_CHECK_FAMILY_POSTGRESQL","ADVISOR_CHECK_FAMILY_MONGODB"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { panic(err) } for _, v := range res { - listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeFamilyPropEnum = append(listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeFamilyPropEnum, v) + listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeTechnologyPropEnum = append(listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeTechnologyPropEnum, v) } } const ( - // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYUNSPECIFIED captures enum value "ADVISOR_CHECK_FAMILY_UNSPECIFIED" - ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYUNSPECIFIED string = "ADVISOR_CHECK_FAMILY_UNSPECIFIED" + // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" - // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYMYSQL captures enum value "ADVISOR_CHECK_FAMILY_MYSQL" - ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYMYSQL string = "ADVISOR_CHECK_FAMILY_MYSQL" + // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" - // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYPOSTGRESQL captures enum value "ADVISOR_CHECK_FAMILY_POSTGRESQL" - ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYPOSTGRESQL string = "ADVISOR_CHECK_FAMILY_POSTGRESQL" + // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" - // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYMONGODB captures enum value "ADVISOR_CHECK_FAMILY_MONGODB" - ListAdvisorsOKBodyAdvisorsItems0ChecksItems0FamilyADVISORCHECKFAMILYMONGODB string = "ADVISOR_CHECK_FAMILY_MONGODB" + // ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ListAdvisorsOKBodyAdvisorsItems0ChecksItems0TechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ) // prop value enum -func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) validateFamilyEnum(path, location string, value string) error { - if err := validate.EnumCase(path, location, value, listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeFamilyPropEnum, true); err != nil { +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listAdvisorsOkBodyAdvisorsItems0ChecksItems0TypeTechnologyPropEnum, true); err != nil { return err } return nil } -func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) validateFamily(formats strfmt.Registry) error { - if swag.IsZero(o.Family) { // not required +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required return nil } // value enum - if err := o.validateFamilyEnum("family", "body", *o.Family); err != nil { + if err := o.validateTechnologyEnum("technology", "body", *o.Technology); err != nil { return err } return nil } -// ContextValidate validates this list advisors OK body advisors items0 checks items0 based on context it is used +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list advisors OK body advisors items0 checks items0 based on the context it is used func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + return nil } @@ -812,3 +902,46 @@ func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0) UnmarshalBinary(b []byte) *o = res return nil } + +/* +ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0 +*/ +type ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this list advisors OK body advisors items0 checks items0 queries items0 +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list advisors OK body advisors items0 checks items0 queries items0 based on context it is used +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0) UnmarshalBinary(b []byte) error { + var res ListAdvisorsOKBodyAdvisorsItems0ChecksItems0QueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_failed_services_parameters.go b/api/advisors/v1/json/client/advisor_service/list_failed_services_parameters.go deleted file mode 100644 index f45e27320db..00000000000 --- a/api/advisors/v1/json/client/advisor_service/list_failed_services_parameters.go +++ /dev/null @@ -1,124 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package advisor_service - -import ( - "context" - "net/http" - "time" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - cr "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" -) - -// NewListFailedServicesParams creates a new ListFailedServicesParams object, -// with the default timeout for this client. -// -// Default values are not hydrated, since defaults are normally applied by the API server side. -// -// To enforce default values in parameter, use SetDefaults or WithDefaults. -func NewListFailedServicesParams() *ListFailedServicesParams { - return &ListFailedServicesParams{ - timeout: cr.DefaultTimeout, - } -} - -// NewListFailedServicesParamsWithTimeout creates a new ListFailedServicesParams object -// with the ability to set a timeout on a request. -func NewListFailedServicesParamsWithTimeout(timeout time.Duration) *ListFailedServicesParams { - return &ListFailedServicesParams{ - timeout: timeout, - } -} - -// NewListFailedServicesParamsWithContext creates a new ListFailedServicesParams object -// with the ability to set a context for a request. -func NewListFailedServicesParamsWithContext(ctx context.Context) *ListFailedServicesParams { - return &ListFailedServicesParams{ - Context: ctx, - } -} - -// NewListFailedServicesParamsWithHTTPClient creates a new ListFailedServicesParams object -// with the ability to set a custom HTTPClient for a request. -func NewListFailedServicesParamsWithHTTPClient(client *http.Client) *ListFailedServicesParams { - return &ListFailedServicesParams{ - HTTPClient: client, - } -} - -/* -ListFailedServicesParams contains all the parameters to send to the API endpoint - - for the list failed services operation. - - Typically these are written to a http.Request. -*/ -type ListFailedServicesParams struct { - timeout time.Duration - Context context.Context - HTTPClient *http.Client -} - -// WithDefaults hydrates default values in the list failed services params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *ListFailedServicesParams) WithDefaults() *ListFailedServicesParams { - o.SetDefaults() - return o -} - -// SetDefaults hydrates default values in the list failed services params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *ListFailedServicesParams) SetDefaults() { - // no default values defined for this parameter -} - -// WithTimeout adds the timeout to the list failed services params -func (o *ListFailedServicesParams) WithTimeout(timeout time.Duration) *ListFailedServicesParams { - o.SetTimeout(timeout) - return o -} - -// SetTimeout adds the timeout to the list failed services params -func (o *ListFailedServicesParams) SetTimeout(timeout time.Duration) { - o.timeout = timeout -} - -// WithContext adds the context to the list failed services params -func (o *ListFailedServicesParams) WithContext(ctx context.Context) *ListFailedServicesParams { - o.SetContext(ctx) - return o -} - -// SetContext adds the context to the list failed services params -func (o *ListFailedServicesParams) SetContext(ctx context.Context) { - o.Context = ctx -} - -// WithHTTPClient adds the HTTPClient to the list failed services params -func (o *ListFailedServicesParams) WithHTTPClient(client *http.Client) *ListFailedServicesParams { - o.SetHTTPClient(client) - return o -} - -// SetHTTPClient adds the HTTPClient to the list failed services params -func (o *ListFailedServicesParams) SetHTTPClient(client *http.Client) { - o.HTTPClient = client -} - -// WriteToRequest writes these params to a swagger request -func (o *ListFailedServicesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { - if err := r.SetTimeout(o.timeout); err != nil { - return err - } - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/api/advisors/v1/json/client/advisor_service/list_failed_services_responses.go b/api/advisors/v1/json/client/advisor_service/list_failed_services_responses.go deleted file mode 100644 index 4005d072b7a..00000000000 --- a/api/advisors/v1/json/client/advisor_service/list_failed_services_responses.go +++ /dev/null @@ -1,588 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package advisor_service - -import ( - "context" - "encoding/json" - stderrors "errors" - "fmt" - "io" - "strconv" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" -) - -// ListFailedServicesReader is a Reader for the ListFailedServices structure. -type ListFailedServicesReader struct { - formats strfmt.Registry -} - -// ReadResponse reads a server response into the received o. -func (o *ListFailedServicesReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { - switch response.Code() { - case 200: - result := NewListFailedServicesOK() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return result, nil - default: - result := NewListFailedServicesDefault(response.Code()) - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - if response.Code()/100 == 2 { - return result, nil - } - return nil, result - } -} - -// NewListFailedServicesOK creates a ListFailedServicesOK with default headers values -func NewListFailedServicesOK() *ListFailedServicesOK { - return &ListFailedServicesOK{} -} - -/* -ListFailedServicesOK describes a response with status code 200, with default header values. - -A successful response. -*/ -type ListFailedServicesOK struct { - Payload *ListFailedServicesOKBody -} - -// IsSuccess returns true when this list failed services Ok response has a 2xx status code -func (o *ListFailedServicesOK) IsSuccess() bool { - return true -} - -// IsRedirect returns true when this list failed services Ok response has a 3xx status code -func (o *ListFailedServicesOK) IsRedirect() bool { - return false -} - -// IsClientError returns true when this list failed services Ok response has a 4xx status code -func (o *ListFailedServicesOK) IsClientError() bool { - return false -} - -// IsServerError returns true when this list failed services Ok response has a 5xx status code -func (o *ListFailedServicesOK) IsServerError() bool { - return false -} - -// IsCode returns true when this list failed services Ok response a status code equal to that given -func (o *ListFailedServicesOK) IsCode(code int) bool { - return code == 200 -} - -// Code gets the status code for the list failed services Ok response -func (o *ListFailedServicesOK) Code() int { - return 200 -} - -func (o *ListFailedServicesOK) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/failedServices][%d] listFailedServicesOk %s", 200, payload) -} - -func (o *ListFailedServicesOK) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/failedServices][%d] listFailedServicesOk %s", 200, payload) -} - -func (o *ListFailedServicesOK) GetPayload() *ListFailedServicesOKBody { - return o.Payload -} - -func (o *ListFailedServicesOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - o.Payload = new(ListFailedServicesOKBody) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -// NewListFailedServicesDefault creates a ListFailedServicesDefault with default headers values -func NewListFailedServicesDefault(code int) *ListFailedServicesDefault { - return &ListFailedServicesDefault{ - _statusCode: code, - } -} - -/* -ListFailedServicesDefault describes a response with status code -1, with default header values. - -An unexpected error response. -*/ -type ListFailedServicesDefault struct { - _statusCode int - - Payload *ListFailedServicesDefaultBody -} - -// IsSuccess returns true when this list failed services default response has a 2xx status code -func (o *ListFailedServicesDefault) IsSuccess() bool { - return o._statusCode/100 == 2 -} - -// IsRedirect returns true when this list failed services default response has a 3xx status code -func (o *ListFailedServicesDefault) IsRedirect() bool { - return o._statusCode/100 == 3 -} - -// IsClientError returns true when this list failed services default response has a 4xx status code -func (o *ListFailedServicesDefault) IsClientError() bool { - return o._statusCode/100 == 4 -} - -// IsServerError returns true when this list failed services default response has a 5xx status code -func (o *ListFailedServicesDefault) IsServerError() bool { - return o._statusCode/100 == 5 -} - -// IsCode returns true when this list failed services default response a status code equal to that given -func (o *ListFailedServicesDefault) IsCode(code int) bool { - return o._statusCode == code -} - -// Code gets the status code for the list failed services default response -func (o *ListFailedServicesDefault) Code() int { - return o._statusCode -} - -func (o *ListFailedServicesDefault) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/failedServices][%d] ListFailedServices default %s", o._statusCode, payload) -} - -func (o *ListFailedServicesDefault) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[GET /v1/advisors/failedServices][%d] ListFailedServices default %s", o._statusCode, payload) -} - -func (o *ListFailedServicesDefault) GetPayload() *ListFailedServicesDefaultBody { - return o.Payload -} - -func (o *ListFailedServicesDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - o.Payload = new(ListFailedServicesDefaultBody) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { - return err - } - - return nil -} - -/* -ListFailedServicesDefaultBody list failed services default body -swagger:model ListFailedServicesDefaultBody -*/ -type ListFailedServicesDefaultBody struct { - // code - Code int32 `json:"code,omitempty"` - - // message - Message string `json:"message,omitempty"` - - // details - Details []*ListFailedServicesDefaultBodyDetailsItems0 `json:"details"` -} - -// Validate validates this list failed services default body -func (o *ListFailedServicesDefaultBody) Validate(formats strfmt.Registry) error { - var res []error - - if err := o.validateDetails(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *ListFailedServicesDefaultBody) validateDetails(formats strfmt.Registry) error { - if swag.IsZero(o.Details) { // not required - return nil - } - - for i := 0; i < len(o.Details); i++ { - if swag.IsZero(o.Details[i]) { // not required - continue - } - - if o.Details[i] != nil { - if err := o.Details[i].Validate(formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("ListFailedServices default" + "." + "details" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("ListFailedServices default" + "." + "details" + "." + strconv.Itoa(i)) - } - - return err - } - } - - } - - return nil -} - -// ContextValidate validate this list failed services default body based on the context it is used -func (o *ListFailedServicesDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := o.contextValidateDetails(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *ListFailedServicesDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { - for i := 0; i < len(o.Details); i++ { - if o.Details[i] != nil { - - if swag.IsZero(o.Details[i]) { // not required - return nil - } - - if err := o.Details[i].ContextValidate(ctx, formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("ListFailedServices default" + "." + "details" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("ListFailedServices default" + "." + "details" + "." + strconv.Itoa(i)) - } - - return err - } - } - } - - return nil -} - -// MarshalBinary interface implementation -func (o *ListFailedServicesDefaultBody) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *ListFailedServicesDefaultBody) UnmarshalBinary(b []byte) error { - var res ListFailedServicesDefaultBody - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} - -/* -ListFailedServicesDefaultBodyDetailsItems0 list failed services default body details items0 -swagger:model ListFailedServicesDefaultBodyDetailsItems0 -*/ -type ListFailedServicesDefaultBodyDetailsItems0 struct { - // at type - AtType string `json:"@type,omitempty"` - - // list failed services default body details items0 - ListFailedServicesDefaultBodyDetailsItems0 map[string]any `json:"-"` -} - -// UnmarshalJSON unmarshals this object with additional properties from JSON -func (o *ListFailedServicesDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { - // stage 1, bind the properties - var stage1 struct { - // at type - AtType string `json:"@type,omitempty"` - } - if err := json.Unmarshal(data, &stage1); err != nil { - return err - } - var rcv ListFailedServicesDefaultBodyDetailsItems0 - - rcv.AtType = stage1.AtType - *o = rcv - - // stage 2, remove properties and add to map - stage2 := make(map[string]json.RawMessage) - if err := json.Unmarshal(data, &stage2); err != nil { - return err - } - - delete(stage2, "@type") - // stage 3, add additional properties values - if len(stage2) > 0 { - result := make(map[string]any) - for k, v := range stage2 { - var toadd any - if err := json.Unmarshal(v, &toadd); err != nil { - return err - } - result[k] = toadd - } - o.ListFailedServicesDefaultBodyDetailsItems0 = result - } - - return nil -} - -// MarshalJSON marshals this object with additional properties into a JSON object -func (o ListFailedServicesDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { - var stage1 struct { - // at type - AtType string `json:"@type,omitempty"` - } - - stage1.AtType = o.AtType - - // make JSON object for known properties - props, err := json.Marshal(stage1) - if err != nil { - return nil, err - } - - if len(o.ListFailedServicesDefaultBodyDetailsItems0) == 0 { // no additional properties - return props, nil - } - - // make JSON object for the additional properties - additional, err := json.Marshal(o.ListFailedServicesDefaultBodyDetailsItems0) - if err != nil { - return nil, err - } - - if len(props) < 3 { // "{}": only additional properties - return additional, nil - } - - // concatenate the 2 objects - return swag.ConcatJSON(props, additional), nil -} - -// Validate validates this list failed services default body details items0 -func (o *ListFailedServicesDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { - return nil -} - -// ContextValidate validates this list failed services default body details items0 based on context it is used -func (o *ListFailedServicesDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (o *ListFailedServicesDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *ListFailedServicesDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { - var res ListFailedServicesDefaultBodyDetailsItems0 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} - -/* -ListFailedServicesOKBody list failed services OK body -swagger:model ListFailedServicesOKBody -*/ -type ListFailedServicesOKBody struct { - // result - Result []*ListFailedServicesOKBodyResultItems0 `json:"result"` -} - -// Validate validates this list failed services OK body -func (o *ListFailedServicesOKBody) Validate(formats strfmt.Registry) error { - var res []error - - if err := o.validateResult(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *ListFailedServicesOKBody) validateResult(formats strfmt.Registry) error { - if swag.IsZero(o.Result) { // not required - return nil - } - - for i := 0; i < len(o.Result); i++ { - if swag.IsZero(o.Result[i]) { // not required - continue - } - - if o.Result[i] != nil { - if err := o.Result[i].Validate(formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("listFailedServicesOk" + "." + "result" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("listFailedServicesOk" + "." + "result" + "." + strconv.Itoa(i)) - } - - return err - } - } - - } - - return nil -} - -// ContextValidate validate this list failed services OK body based on the context it is used -func (o *ListFailedServicesOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := o.contextValidateResult(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (o *ListFailedServicesOKBody) contextValidateResult(ctx context.Context, formats strfmt.Registry) error { - for i := 0; i < len(o.Result); i++ { - if o.Result[i] != nil { - - if swag.IsZero(o.Result[i]) { // not required - return nil - } - - if err := o.Result[i].ContextValidate(ctx, formats); err != nil { - ve := new(errors.Validation) - if stderrors.As(err, &ve) { - return ve.ValidateName("listFailedServicesOk" + "." + "result" + "." + strconv.Itoa(i)) - } - ce := new(errors.CompositeError) - if stderrors.As(err, &ce) { - return ce.ValidateName("listFailedServicesOk" + "." + "result" + "." + strconv.Itoa(i)) - } - - return err - } - } - } - - return nil -} - -// MarshalBinary interface implementation -func (o *ListFailedServicesOKBody) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *ListFailedServicesOKBody) UnmarshalBinary(b []byte) error { - var res ListFailedServicesOKBody - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} - -/* -ListFailedServicesOKBodyResultItems0 CheckResultSummary is a summary of check results. -swagger:model ListFailedServicesOKBodyResultItems0 -*/ -type ListFailedServicesOKBodyResultItems0 struct { - // service name - ServiceName string `json:"service_name,omitempty"` - - // service id - ServiceID string `json:"service_id,omitempty"` - - // Number of failed checks for this service with severity level "EMERGENCY". - EmergencyCount int64 `json:"emergency_count,omitempty"` - - // Number of failed checks for this service with severity level "ALERT". - AlertCount int64 `json:"alert_count,omitempty"` - - // Number of failed checks for this service with severity level "CRITICAL". - CriticalCount int64 `json:"critical_count,omitempty"` - - // Number of failed checks for this service with severity level "ERROR". - ErrorCount int64 `json:"error_count,omitempty"` - - // Number of failed checks for this service with severity level "WARNING". - WarningCount int64 `json:"warning_count,omitempty"` - - // Number of failed checks for this service with severity level "NOTICE". - NoticeCount int64 `json:"notice_count,omitempty"` - - // Number of failed checks for this service with severity level "INFO". - InfoCount int64 `json:"info_count,omitempty"` - - // Number of failed checks for this service with severity level "DEBUG". - DebugCount int64 `json:"debug_count,omitempty"` -} - -// Validate validates this list failed services OK body result items0 -func (o *ListFailedServicesOKBodyResultItems0) Validate(formats strfmt.Registry) error { - return nil -} - -// ContextValidate validates this list failed services OK body result items0 based on context it is used -func (o *ListFailedServicesOKBodyResultItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (o *ListFailedServicesOKBodyResultItems0) MarshalBinary() ([]byte, error) { - if o == nil { - return nil, nil - } - return swag.WriteJSON(o) -} - -// UnmarshalBinary interface implementation -func (o *ListFailedServicesOKBodyResultItems0) UnmarshalBinary(b []byte) error { - var res ListFailedServicesOKBodyResultItems0 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *o = res - return nil -} diff --git a/api/advisors/v1/json/client/advisor_service/list_insights_filter_values_parameters.go b/api/advisors/v1/json/client/advisor_service/list_insights_filter_values_parameters.go new file mode 100644 index 00000000000..d8725dcabd2 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_insights_filter_values_parameters.go @@ -0,0 +1,124 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewListInsightsFilterValuesParams creates a new ListInsightsFilterValuesParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewListInsightsFilterValuesParams() *ListInsightsFilterValuesParams { + return &ListInsightsFilterValuesParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewListInsightsFilterValuesParamsWithTimeout creates a new ListInsightsFilterValuesParams object +// with the ability to set a timeout on a request. +func NewListInsightsFilterValuesParamsWithTimeout(timeout time.Duration) *ListInsightsFilterValuesParams { + return &ListInsightsFilterValuesParams{ + timeout: timeout, + } +} + +// NewListInsightsFilterValuesParamsWithContext creates a new ListInsightsFilterValuesParams object +// with the ability to set a context for a request. +func NewListInsightsFilterValuesParamsWithContext(ctx context.Context) *ListInsightsFilterValuesParams { + return &ListInsightsFilterValuesParams{ + Context: ctx, + } +} + +// NewListInsightsFilterValuesParamsWithHTTPClient creates a new ListInsightsFilterValuesParams object +// with the ability to set a custom HTTPClient for a request. +func NewListInsightsFilterValuesParamsWithHTTPClient(client *http.Client) *ListInsightsFilterValuesParams { + return &ListInsightsFilterValuesParams{ + HTTPClient: client, + } +} + +/* +ListInsightsFilterValuesParams contains all the parameters to send to the API endpoint + + for the list insights filter values operation. + + Typically these are written to a http.Request. +*/ +type ListInsightsFilterValuesParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the list insights filter values params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListInsightsFilterValuesParams) WithDefaults() *ListInsightsFilterValuesParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the list insights filter values params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListInsightsFilterValuesParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the list insights filter values params +func (o *ListInsightsFilterValuesParams) WithTimeout(timeout time.Duration) *ListInsightsFilterValuesParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the list insights filter values params +func (o *ListInsightsFilterValuesParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the list insights filter values params +func (o *ListInsightsFilterValuesParams) WithContext(ctx context.Context) *ListInsightsFilterValuesParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the list insights filter values params +func (o *ListInsightsFilterValuesParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the list insights filter values params +func (o *ListInsightsFilterValuesParams) WithHTTPClient(client *http.Client) *ListInsightsFilterValuesParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the list insights filter values params +func (o *ListInsightsFilterValuesParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *ListInsightsFilterValuesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_insights_filter_values_responses.go b/api/advisors/v1/json/client/advisor_service/list_insights_filter_values_responses.go new file mode 100644 index 00000000000..3ca2b452f9c --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_insights_filter_values_responses.go @@ -0,0 +1,453 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ListInsightsFilterValuesReader is a Reader for the ListInsightsFilterValues structure. +type ListInsightsFilterValuesReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ListInsightsFilterValuesReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewListInsightsFilterValuesOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewListInsightsFilterValuesDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewListInsightsFilterValuesOK creates a ListInsightsFilterValuesOK with default headers values +func NewListInsightsFilterValuesOK() *ListInsightsFilterValuesOK { + return &ListInsightsFilterValuesOK{} +} + +/* +ListInsightsFilterValuesOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type ListInsightsFilterValuesOK struct { + Payload *ListInsightsFilterValuesOKBody +} + +// IsSuccess returns true when this list insights filter values Ok response has a 2xx status code +func (o *ListInsightsFilterValuesOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this list insights filter values Ok response has a 3xx status code +func (o *ListInsightsFilterValuesOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this list insights filter values Ok response has a 4xx status code +func (o *ListInsightsFilterValuesOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this list insights filter values Ok response has a 5xx status code +func (o *ListInsightsFilterValuesOK) IsServerError() bool { + return false +} + +// IsCode returns true when this list insights filter values Ok response a status code equal to that given +func (o *ListInsightsFilterValuesOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the list insights filter values Ok response +func (o *ListInsightsFilterValuesOK) Code() int { + return 200 +} + +func (o *ListInsightsFilterValuesOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights:filterValues][%d] listInsightsFilterValuesOk %s", 200, payload) +} + +func (o *ListInsightsFilterValuesOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights:filterValues][%d] listInsightsFilterValuesOk %s", 200, payload) +} + +func (o *ListInsightsFilterValuesOK) GetPayload() *ListInsightsFilterValuesOKBody { + return o.Payload +} + +func (o *ListInsightsFilterValuesOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListInsightsFilterValuesOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewListInsightsFilterValuesDefault creates a ListInsightsFilterValuesDefault with default headers values +func NewListInsightsFilterValuesDefault(code int) *ListInsightsFilterValuesDefault { + return &ListInsightsFilterValuesDefault{ + _statusCode: code, + } +} + +/* +ListInsightsFilterValuesDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type ListInsightsFilterValuesDefault struct { + _statusCode int + + Payload *ListInsightsFilterValuesDefaultBody +} + +// IsSuccess returns true when this list insights filter values default response has a 2xx status code +func (o *ListInsightsFilterValuesDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this list insights filter values default response has a 3xx status code +func (o *ListInsightsFilterValuesDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this list insights filter values default response has a 4xx status code +func (o *ListInsightsFilterValuesDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this list insights filter values default response has a 5xx status code +func (o *ListInsightsFilterValuesDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this list insights filter values default response a status code equal to that given +func (o *ListInsightsFilterValuesDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the list insights filter values default response +func (o *ListInsightsFilterValuesDefault) Code() int { + return o._statusCode +} + +func (o *ListInsightsFilterValuesDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights:filterValues][%d] ListInsightsFilterValues default %s", o._statusCode, payload) +} + +func (o *ListInsightsFilterValuesDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights:filterValues][%d] ListInsightsFilterValues default %s", o._statusCode, payload) +} + +func (o *ListInsightsFilterValuesDefault) GetPayload() *ListInsightsFilterValuesDefaultBody { + return o.Payload +} + +func (o *ListInsightsFilterValuesDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListInsightsFilterValuesDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +ListInsightsFilterValuesDefaultBody list insights filter values default body +swagger:model ListInsightsFilterValuesDefaultBody +*/ +type ListInsightsFilterValuesDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*ListInsightsFilterValuesDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this list insights filter values default body +func (o *ListInsightsFilterValuesDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListInsightsFilterValuesDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListInsightsFilterValues default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListInsightsFilterValues default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list insights filter values default body based on the context it is used +func (o *ListInsightsFilterValuesDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListInsightsFilterValuesDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListInsightsFilterValues default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListInsightsFilterValues default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListInsightsFilterValuesDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListInsightsFilterValuesDefaultBody) UnmarshalBinary(b []byte) error { + var res ListInsightsFilterValuesDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListInsightsFilterValuesDefaultBodyDetailsItems0 list insights filter values default body details items0 +swagger:model ListInsightsFilterValuesDefaultBodyDetailsItems0 +*/ +type ListInsightsFilterValuesDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // list insights filter values default body details items0 + ListInsightsFilterValuesDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *ListInsightsFilterValuesDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv ListInsightsFilterValuesDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.ListInsightsFilterValuesDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o ListInsightsFilterValuesDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.ListInsightsFilterValuesDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.ListInsightsFilterValuesDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this list insights filter values default body details items0 +func (o *ListInsightsFilterValuesDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list insights filter values default body details items0 based on context it is used +func (o *ListInsightsFilterValuesDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListInsightsFilterValuesDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListInsightsFilterValuesDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res ListInsightsFilterValuesDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListInsightsFilterValuesOKBody list insights filter values OK body +swagger:model ListInsightsFilterValuesOKBody +*/ +type ListInsightsFilterValuesOKBody struct { + // Distinct service names present in the check results history, sorted alphabetically. + ServiceNames []string `json:"service_names"` + + // Distinct node names present in the check results history, sorted alphabetically. + NodeNames []string `json:"node_names"` +} + +// Validate validates this list insights filter values OK body +func (o *ListInsightsFilterValuesOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list insights filter values OK body based on context it is used +func (o *ListInsightsFilterValuesOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListInsightsFilterValuesOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListInsightsFilterValuesOKBody) UnmarshalBinary(b []byte) error { + var res ListInsightsFilterValuesOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_insights_parameters.go b/api/advisors/v1/json/client/advisor_service/list_insights_parameters.go new file mode 100644 index 00000000000..38d5848591a --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_insights_parameters.go @@ -0,0 +1,625 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewListInsightsParams creates a new ListInsightsParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewListInsightsParams() *ListInsightsParams { + return &ListInsightsParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewListInsightsParamsWithTimeout creates a new ListInsightsParams object +// with the ability to set a timeout on a request. +func NewListInsightsParamsWithTimeout(timeout time.Duration) *ListInsightsParams { + return &ListInsightsParams{ + timeout: timeout, + } +} + +// NewListInsightsParamsWithContext creates a new ListInsightsParams object +// with the ability to set a context for a request. +func NewListInsightsParamsWithContext(ctx context.Context) *ListInsightsParams { + return &ListInsightsParams{ + Context: ctx, + } +} + +// NewListInsightsParamsWithHTTPClient creates a new ListInsightsParams object +// with the ability to set a custom HTTPClient for a request. +func NewListInsightsParamsWithHTTPClient(client *http.Client) *ListInsightsParams { + return &ListInsightsParams{ + HTTPClient: client, + } +} + +/* +ListInsightsParams contains all the parameters to send to the API endpoint + + for the list insights operation. + + Typically these are written to a http.Request. +*/ +type ListInsightsParams struct { + /* Category. + + Filter by advisor category. + */ + Category *string + + /* CheckName. + + Filter by check name. + */ + CheckName *string + + /* From. + + Return only results recorded at or after this time. + + Format: date-time + */ + From *strfmt.DateTime + + /* IsRead. + + Filter by read state. + */ + IsRead *bool + + /* NodeName. + + Filter by node name (partial, case-insensitive match). + */ + NodeName *string + + /* PageIndex. + + Index of the requested page, starts from 0. + + Format: int32 + */ + PageIndex *int32 + + /* PageSize. + + Maximum number of results per page. + + Format: int32 + */ + PageSize *int32 + + /* RunID. + + Filter by run ID. + */ + RunID *string + + /* ServiceID. + + Filter by service ID. + */ + ServiceID *string + + /* ServiceName. + + Filter by service name (partial, case-insensitive match). + */ + ServiceName *string + + /* Severity. + + Filter by severity. + + Default: "SEVERITY_UNSPECIFIED" + */ + Severity *string + + /* Status. + + Filter by outcome. + + - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue. + - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue. + - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed. + + Default: "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED" + */ + Status *string + + /* To. + + Return only results recorded at or before this time. + + Format: date-time + */ + To *strfmt.DateTime + + /* TriggeredBy. + + Filter by the actor that initiated the run. + + - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI. + - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler. + + Default: "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED" + */ + TriggeredBy *string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the list insights params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListInsightsParams) WithDefaults() *ListInsightsParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the list insights params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListInsightsParams) SetDefaults() { + var ( + severityDefault = string("SEVERITY_UNSPECIFIED") + + statusDefault = string("ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED") + + triggeredByDefault = string("ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED") + ) + + val := ListInsightsParams{ + Severity: &severityDefault, + Status: &statusDefault, + TriggeredBy: &triggeredByDefault, + } + + val.timeout = o.timeout + val.Context = o.Context + val.HTTPClient = o.HTTPClient + *o = val +} + +// WithTimeout adds the timeout to the list insights params +func (o *ListInsightsParams) WithTimeout(timeout time.Duration) *ListInsightsParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the list insights params +func (o *ListInsightsParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the list insights params +func (o *ListInsightsParams) WithContext(ctx context.Context) *ListInsightsParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the list insights params +func (o *ListInsightsParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the list insights params +func (o *ListInsightsParams) WithHTTPClient(client *http.Client) *ListInsightsParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the list insights params +func (o *ListInsightsParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithCategory adds the category to the list insights params +func (o *ListInsightsParams) WithCategory(category *string) *ListInsightsParams { + o.SetCategory(category) + return o +} + +// SetCategory adds the category to the list insights params +func (o *ListInsightsParams) SetCategory(category *string) { + o.Category = category +} + +// WithCheckName adds the checkName to the list insights params +func (o *ListInsightsParams) WithCheckName(checkName *string) *ListInsightsParams { + o.SetCheckName(checkName) + return o +} + +// SetCheckName adds the checkName to the list insights params +func (o *ListInsightsParams) SetCheckName(checkName *string) { + o.CheckName = checkName +} + +// WithFrom adds the from to the list insights params +func (o *ListInsightsParams) WithFrom(from *strfmt.DateTime) *ListInsightsParams { + o.SetFrom(from) + return o +} + +// SetFrom adds the from to the list insights params +func (o *ListInsightsParams) SetFrom(from *strfmt.DateTime) { + o.From = from +} + +// WithIsRead adds the isRead to the list insights params +func (o *ListInsightsParams) WithIsRead(isRead *bool) *ListInsightsParams { + o.SetIsRead(isRead) + return o +} + +// SetIsRead adds the isRead to the list insights params +func (o *ListInsightsParams) SetIsRead(isRead *bool) { + o.IsRead = isRead +} + +// WithNodeName adds the nodeName to the list insights params +func (o *ListInsightsParams) WithNodeName(nodeName *string) *ListInsightsParams { + o.SetNodeName(nodeName) + return o +} + +// SetNodeName adds the nodeName to the list insights params +func (o *ListInsightsParams) SetNodeName(nodeName *string) { + o.NodeName = nodeName +} + +// WithPageIndex adds the pageIndex to the list insights params +func (o *ListInsightsParams) WithPageIndex(pageIndex *int32) *ListInsightsParams { + o.SetPageIndex(pageIndex) + return o +} + +// SetPageIndex adds the pageIndex to the list insights params +func (o *ListInsightsParams) SetPageIndex(pageIndex *int32) { + o.PageIndex = pageIndex +} + +// WithPageSize adds the pageSize to the list insights params +func (o *ListInsightsParams) WithPageSize(pageSize *int32) *ListInsightsParams { + o.SetPageSize(pageSize) + return o +} + +// SetPageSize adds the pageSize to the list insights params +func (o *ListInsightsParams) SetPageSize(pageSize *int32) { + o.PageSize = pageSize +} + +// WithRunID adds the runID to the list insights params +func (o *ListInsightsParams) WithRunID(runID *string) *ListInsightsParams { + o.SetRunID(runID) + return o +} + +// SetRunID adds the runId to the list insights params +func (o *ListInsightsParams) SetRunID(runID *string) { + o.RunID = runID +} + +// WithServiceID adds the serviceID to the list insights params +func (o *ListInsightsParams) WithServiceID(serviceID *string) *ListInsightsParams { + o.SetServiceID(serviceID) + return o +} + +// SetServiceID adds the serviceId to the list insights params +func (o *ListInsightsParams) SetServiceID(serviceID *string) { + o.ServiceID = serviceID +} + +// WithServiceName adds the serviceName to the list insights params +func (o *ListInsightsParams) WithServiceName(serviceName *string) *ListInsightsParams { + o.SetServiceName(serviceName) + return o +} + +// SetServiceName adds the serviceName to the list insights params +func (o *ListInsightsParams) SetServiceName(serviceName *string) { + o.ServiceName = serviceName +} + +// WithSeverity adds the severity to the list insights params +func (o *ListInsightsParams) WithSeverity(severity *string) *ListInsightsParams { + o.SetSeverity(severity) + return o +} + +// SetSeverity adds the severity to the list insights params +func (o *ListInsightsParams) SetSeverity(severity *string) { + o.Severity = severity +} + +// WithStatus adds the status to the list insights params +func (o *ListInsightsParams) WithStatus(status *string) *ListInsightsParams { + o.SetStatus(status) + return o +} + +// SetStatus adds the status to the list insights params +func (o *ListInsightsParams) SetStatus(status *string) { + o.Status = status +} + +// WithTo adds the to to the list insights params +func (o *ListInsightsParams) WithTo(to *strfmt.DateTime) *ListInsightsParams { + o.SetTo(to) + return o +} + +// SetTo adds the to to the list insights params +func (o *ListInsightsParams) SetTo(to *strfmt.DateTime) { + o.To = to +} + +// WithTriggeredBy adds the triggeredBy to the list insights params +func (o *ListInsightsParams) WithTriggeredBy(triggeredBy *string) *ListInsightsParams { + o.SetTriggeredBy(triggeredBy) + return o +} + +// SetTriggeredBy adds the triggeredBy to the list insights params +func (o *ListInsightsParams) SetTriggeredBy(triggeredBy *string) { + o.TriggeredBy = triggeredBy +} + +// WriteToRequest writes these params to a swagger request +func (o *ListInsightsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if o.Category != nil { + + // query param category + var qrCategory string + + if o.Category != nil { + qrCategory = *o.Category + } + qCategory := qrCategory + if qCategory != "" { + if err := r.SetQueryParam("category", qCategory); err != nil { + return err + } + } + } + + if o.CheckName != nil { + + // query param check_name + var qrCheckName string + + if o.CheckName != nil { + qrCheckName = *o.CheckName + } + qCheckName := qrCheckName + if qCheckName != "" { + if err := r.SetQueryParam("check_name", qCheckName); err != nil { + return err + } + } + } + + if o.From != nil { + + // query param from + var qrFrom strfmt.DateTime + + if o.From != nil { + qrFrom = *o.From + } + qFrom := qrFrom.String() + if qFrom != "" { + if err := r.SetQueryParam("from", qFrom); err != nil { + return err + } + } + } + + if o.IsRead != nil { + + // query param is_read + var qrIsRead bool + + if o.IsRead != nil { + qrIsRead = *o.IsRead + } + qIsRead := swag.FormatBool(qrIsRead) + if qIsRead != "" { + if err := r.SetQueryParam("is_read", qIsRead); err != nil { + return err + } + } + } + + if o.NodeName != nil { + + // query param node_name + var qrNodeName string + + if o.NodeName != nil { + qrNodeName = *o.NodeName + } + qNodeName := qrNodeName + if qNodeName != "" { + if err := r.SetQueryParam("node_name", qNodeName); err != nil { + return err + } + } + } + + if o.PageIndex != nil { + + // query param page_index + var qrPageIndex int32 + + if o.PageIndex != nil { + qrPageIndex = *o.PageIndex + } + qPageIndex := swag.FormatInt32(qrPageIndex) + if qPageIndex != "" { + if err := r.SetQueryParam("page_index", qPageIndex); err != nil { + return err + } + } + } + + if o.PageSize != nil { + + // query param page_size + var qrPageSize int32 + + if o.PageSize != nil { + qrPageSize = *o.PageSize + } + qPageSize := swag.FormatInt32(qrPageSize) + if qPageSize != "" { + if err := r.SetQueryParam("page_size", qPageSize); err != nil { + return err + } + } + } + + if o.RunID != nil { + + // query param run_id + var qrRunID string + + if o.RunID != nil { + qrRunID = *o.RunID + } + qRunID := qrRunID + if qRunID != "" { + if err := r.SetQueryParam("run_id", qRunID); err != nil { + return err + } + } + } + + if o.ServiceID != nil { + + // query param service_id + var qrServiceID string + + if o.ServiceID != nil { + qrServiceID = *o.ServiceID + } + qServiceID := qrServiceID + if qServiceID != "" { + if err := r.SetQueryParam("service_id", qServiceID); err != nil { + return err + } + } + } + + if o.ServiceName != nil { + + // query param service_name + var qrServiceName string + + if o.ServiceName != nil { + qrServiceName = *o.ServiceName + } + qServiceName := qrServiceName + if qServiceName != "" { + if err := r.SetQueryParam("service_name", qServiceName); err != nil { + return err + } + } + } + + if o.Severity != nil { + + // query param severity + var qrSeverity string + + if o.Severity != nil { + qrSeverity = *o.Severity + } + qSeverity := qrSeverity + if qSeverity != "" { + if err := r.SetQueryParam("severity", qSeverity); err != nil { + return err + } + } + } + + if o.Status != nil { + + // query param status + var qrStatus string + + if o.Status != nil { + qrStatus = *o.Status + } + qStatus := qrStatus + if qStatus != "" { + if err := r.SetQueryParam("status", qStatus); err != nil { + return err + } + } + } + + if o.To != nil { + + // query param to + var qrTo strfmt.DateTime + + if o.To != nil { + qrTo = *o.To + } + qTo := qrTo.String() + if qTo != "" { + if err := r.SetQueryParam("to", qTo); err != nil { + return err + } + } + } + + if o.TriggeredBy != nil { + + // query param triggered_by + var qrTriggeredBy string + + if o.TriggeredBy != nil { + qrTriggeredBy = *o.TriggeredBy + } + qTriggeredBy := qrTriggeredBy + if qTriggeredBy != "" { + if err := r.SetQueryParam("triggered_by", qTriggeredBy); err != nil { + return err + } + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_insights_responses.go b/api/advisors/v1/json/client/advisor_service/list_insights_responses.go new file mode 100644 index 00000000000..5711d4a30e2 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_insights_responses.go @@ -0,0 +1,896 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ListInsightsReader is a Reader for the ListInsights structure. +type ListInsightsReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ListInsightsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewListInsightsOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewListInsightsDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewListInsightsOK creates a ListInsightsOK with default headers values +func NewListInsightsOK() *ListInsightsOK { + return &ListInsightsOK{} +} + +/* +ListInsightsOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type ListInsightsOK struct { + Payload *ListInsightsOKBody +} + +// IsSuccess returns true when this list insights Ok response has a 2xx status code +func (o *ListInsightsOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this list insights Ok response has a 3xx status code +func (o *ListInsightsOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this list insights Ok response has a 4xx status code +func (o *ListInsightsOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this list insights Ok response has a 5xx status code +func (o *ListInsightsOK) IsServerError() bool { + return false +} + +// IsCode returns true when this list insights Ok response a status code equal to that given +func (o *ListInsightsOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the list insights Ok response +func (o *ListInsightsOK) Code() int { + return 200 +} + +func (o *ListInsightsOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights][%d] listInsightsOk %s", 200, payload) +} + +func (o *ListInsightsOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights][%d] listInsightsOk %s", 200, payload) +} + +func (o *ListInsightsOK) GetPayload() *ListInsightsOKBody { + return o.Payload +} + +func (o *ListInsightsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListInsightsOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewListInsightsDefault creates a ListInsightsDefault with default headers values +func NewListInsightsDefault(code int) *ListInsightsDefault { + return &ListInsightsDefault{ + _statusCode: code, + } +} + +/* +ListInsightsDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type ListInsightsDefault struct { + _statusCode int + + Payload *ListInsightsDefaultBody +} + +// IsSuccess returns true when this list insights default response has a 2xx status code +func (o *ListInsightsDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this list insights default response has a 3xx status code +func (o *ListInsightsDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this list insights default response has a 4xx status code +func (o *ListInsightsDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this list insights default response has a 5xx status code +func (o *ListInsightsDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this list insights default response a status code equal to that given +func (o *ListInsightsDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the list insights default response +func (o *ListInsightsDefault) Code() int { + return o._statusCode +} + +func (o *ListInsightsDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights][%d] ListInsights default %s", o._statusCode, payload) +} + +func (o *ListInsightsDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/insights][%d] ListInsights default %s", o._statusCode, payload) +} + +func (o *ListInsightsDefault) GetPayload() *ListInsightsDefaultBody { + return o.Payload +} + +func (o *ListInsightsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListInsightsDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +ListInsightsDefaultBody list insights default body +swagger:model ListInsightsDefaultBody +*/ +type ListInsightsDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*ListInsightsDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this list insights default body +func (o *ListInsightsDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListInsightsDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListInsights default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListInsights default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list insights default body based on the context it is used +func (o *ListInsightsDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListInsightsDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListInsights default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListInsights default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListInsightsDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListInsightsDefaultBody) UnmarshalBinary(b []byte) error { + var res ListInsightsDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListInsightsDefaultBodyDetailsItems0 list insights default body details items0 +swagger:model ListInsightsDefaultBodyDetailsItems0 +*/ +type ListInsightsDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // list insights default body details items0 + ListInsightsDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *ListInsightsDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv ListInsightsDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.ListInsightsDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o ListInsightsDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.ListInsightsDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.ListInsightsDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this list insights default body details items0 +func (o *ListInsightsDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list insights default body details items0 based on context it is used +func (o *ListInsightsDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListInsightsDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListInsightsDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res ListInsightsDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListInsightsOKBody list insights OK body +swagger:model ListInsightsOKBody +*/ +type ListInsightsOKBody struct { + // Total number of results. + TotalItems int32 `json:"total_items,omitempty"` + + // Total number of pages. + TotalPages int32 `json:"total_pages,omitempty"` + + // Insight records. + Results []*ListInsightsOKBodyResultsItems0 `json:"results"` +} + +// Validate validates this list insights OK body +func (o *ListInsightsOKBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateResults(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListInsightsOKBody) validateResults(formats strfmt.Registry) error { + if swag.IsZero(o.Results) { // not required + return nil + } + + for i := 0; i < len(o.Results); i++ { + if swag.IsZero(o.Results[i]) { // not required + continue + } + + if o.Results[i] != nil { + if err := o.Results[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("listInsightsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("listInsightsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list insights OK body based on the context it is used +func (o *ListInsightsOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateResults(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListInsightsOKBody) contextValidateResults(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Results); i++ { + if o.Results[i] != nil { + + if swag.IsZero(o.Results[i]) { // not required + return nil + } + + if err := o.Results[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("listInsightsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("listInsightsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListInsightsOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListInsightsOKBody) UnmarshalBinary(b []byte) error { + var res ListInsightsOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListInsightsOKBodyResultsItems0 Insight represents a single persisted Advisor check run against a service. +swagger:model ListInsightsOKBodyResultsItems0 +*/ +type ListInsightsOKBodyResultsItems0 struct { + // Unique identifier of the history record. + ID string `json:"id,omitempty"` + + // ID of the run this result belongs to; all results produced by one execution share it. + RunID string `json:"run_id,omitempty"` + + // Name of the check that ran. + CheckName string `json:"check_name,omitempty"` + + // Category the check belongs to (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory the check belongs to (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // AdvisorCheckInterval represents possible execution interval values for checks. + // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] + Interval *string `json:"interval,omitempty"` + + // ID of the monitored service on which the check ran. + ServiceID string `json:"service_id,omitempty"` + + // Name of the monitored service on which the check ran. + ServiceName string `json:"service_name,omitempty"` + + // Type of the monitored service on which the check ran. + ServiceType string `json:"service_type,omitempty"` + + // ID of the node the service runs on. + NodeID string `json:"node_id,omitempty"` + + // Name of the node the service runs on. + NodeName string `json:"node_name,omitempty"` + + // Environment of the monitored service on which the check ran. + Environment string `json:"environment,omitempty"` + + // Cluster of the monitored service on which the check ran. + Cluster string `json:"cluster,omitempty"` + + // Replication set of the monitored service on which the check ran. + ReplicationSet string `json:"replication_set,omitempty"` + + // AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service. + // + // - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue. + // - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue. + // - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed. + // Enum: ["ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED","ADVISOR_CHECK_RESULT_STATUS_OK","ADVISOR_CHECK_RESULT_STATUS_FAILED","ADVISOR_CHECK_RESULT_STATUS_ERROR"] + Status *string `json:"status,omitempty"` + + // Short human-readable summary of the result. + Summary string `json:"summary,omitempty"` + + // Long human-readable description of the result. + Description string `json:"description,omitempty"` + + // URL containing information on how to resolve a detected issue. + ReadMoreURL string `json:"read_more_url,omitempty"` + + // Output returned by the check run (finding details or execution error). + Outcome string `json:"outcome,omitempty"` + + // Severity represents severity level of the check result or alert. + // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] + Severity *string `json:"severity,omitempty"` + + // Result labels. + Labels map[string]string `json:"labels,omitempty"` + + // Time when the check ran. + // Format: date-time + CheckedAt strfmt.DateTime `json:"checked_at,omitempty"` + + // Whether the result has been marked as read. + IsRead bool `json:"is_read,omitempty"` + + // AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run. + // + // - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI. + // - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler. + // Enum: ["ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED","ADVISOR_CHECK_TRIGGERED_BY_USER","ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER"] + TriggeredBy *string `json:"triggered_by,omitempty"` + + // Cloud region of the node the service runs on, empty when not applicable. + Region string `json:"region,omitempty"` + + // Cloud availability zone of the node the service runs on, empty when not applicable. + Az string `json:"az,omitempty"` +} + +// Validate validates this list insights OK body results items0 +func (o *ListInsightsOKBodyResultsItems0) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateInterval(formats); err != nil { + res = append(res, err) + } + + if err := o.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := o.validateSeverity(formats); err != nil { + res = append(res, err) + } + + if err := o.validateCheckedAt(formats); err != nil { + res = append(res, err) + } + + if err := o.validateTriggeredBy(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var listInsightsOkBodyResultsItems0TypeIntervalPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + listInsightsOkBodyResultsItems0TypeIntervalPropEnum = append(listInsightsOkBodyResultsItems0TypeIntervalPropEnum, v) + } +} + +const ( + + // ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALUNSPECIFIED captures enum value "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALUNSPECIFIED string = "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + + // ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALSTANDARD captures enum value "ADVISOR_CHECK_INTERVAL_STANDARD" + ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALSTANDARD string = "ADVISOR_CHECK_INTERVAL_STANDARD" + + // ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALFREQUENT captures enum value "ADVISOR_CHECK_INTERVAL_FREQUENT" + ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALFREQUENT string = "ADVISOR_CHECK_INTERVAL_FREQUENT" + + // ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALRARE captures enum value "ADVISOR_CHECK_INTERVAL_RARE" + ListInsightsOKBodyResultsItems0IntervalADVISORCHECKINTERVALRARE string = "ADVISOR_CHECK_INTERVAL_RARE" +) + +// prop value enum +func (o *ListInsightsOKBodyResultsItems0) validateIntervalEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listInsightsOkBodyResultsItems0TypeIntervalPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ListInsightsOKBodyResultsItems0) validateInterval(formats strfmt.Registry) error { + if swag.IsZero(o.Interval) { // not required + return nil + } + + // value enum + if err := o.validateIntervalEnum("interval", "body", *o.Interval); err != nil { + return err + } + + return nil +} + +var listInsightsOkBodyResultsItems0TypeStatusPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED","ADVISOR_CHECK_RESULT_STATUS_OK","ADVISOR_CHECK_RESULT_STATUS_FAILED","ADVISOR_CHECK_RESULT_STATUS_ERROR"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + listInsightsOkBodyResultsItems0TypeStatusPropEnum = append(listInsightsOkBodyResultsItems0TypeStatusPropEnum, v) + } +} + +const ( + + // ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSUNSPECIFIED captures enum value "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED" + ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSUNSPECIFIED string = "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED" + + // ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSOK captures enum value "ADVISOR_CHECK_RESULT_STATUS_OK" + ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSOK string = "ADVISOR_CHECK_RESULT_STATUS_OK" + + // ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSFAILED captures enum value "ADVISOR_CHECK_RESULT_STATUS_FAILED" + ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSFAILED string = "ADVISOR_CHECK_RESULT_STATUS_FAILED" + + // ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSERROR captures enum value "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ListInsightsOKBodyResultsItems0StatusADVISORCHECKRESULTSTATUSERROR string = "ADVISOR_CHECK_RESULT_STATUS_ERROR" +) + +// prop value enum +func (o *ListInsightsOKBodyResultsItems0) validateStatusEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listInsightsOkBodyResultsItems0TypeStatusPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ListInsightsOKBodyResultsItems0) validateStatus(formats strfmt.Registry) error { + if swag.IsZero(o.Status) { // not required + return nil + } + + // value enum + if err := o.validateStatusEnum("status", "body", *o.Status); err != nil { + return err + } + + return nil +} + +var listInsightsOkBodyResultsItems0TypeSeverityPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + listInsightsOkBodyResultsItems0TypeSeverityPropEnum = append(listInsightsOkBodyResultsItems0TypeSeverityPropEnum, v) + } +} + +const ( + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" + ListInsightsOKBodyResultsItems0SeveritySEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" + ListInsightsOKBodyResultsItems0SeveritySEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYALERT captures enum value "SEVERITY_ALERT" + ListInsightsOKBodyResultsItems0SeveritySEVERITYALERT string = "SEVERITY_ALERT" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" + ListInsightsOKBodyResultsItems0SeveritySEVERITYCRITICAL string = "SEVERITY_CRITICAL" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYERROR captures enum value "SEVERITY_ERROR" + ListInsightsOKBodyResultsItems0SeveritySEVERITYERROR string = "SEVERITY_ERROR" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYWARNING captures enum value "SEVERITY_WARNING" + ListInsightsOKBodyResultsItems0SeveritySEVERITYWARNING string = "SEVERITY_WARNING" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYNOTICE captures enum value "SEVERITY_NOTICE" + ListInsightsOKBodyResultsItems0SeveritySEVERITYNOTICE string = "SEVERITY_NOTICE" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYINFO captures enum value "SEVERITY_INFO" + ListInsightsOKBodyResultsItems0SeveritySEVERITYINFO string = "SEVERITY_INFO" + + // ListInsightsOKBodyResultsItems0SeveritySEVERITYDEBUG captures enum value "SEVERITY_DEBUG" + ListInsightsOKBodyResultsItems0SeveritySEVERITYDEBUG string = "SEVERITY_DEBUG" +) + +// prop value enum +func (o *ListInsightsOKBodyResultsItems0) validateSeverityEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listInsightsOkBodyResultsItems0TypeSeverityPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ListInsightsOKBodyResultsItems0) validateSeverity(formats strfmt.Registry) error { + if swag.IsZero(o.Severity) { // not required + return nil + } + + // value enum + if err := o.validateSeverityEnum("severity", "body", *o.Severity); err != nil { + return err + } + + return nil +} + +func (o *ListInsightsOKBodyResultsItems0) validateCheckedAt(formats strfmt.Registry) error { + if swag.IsZero(o.CheckedAt) { // not required + return nil + } + + if err := validate.FormatOf("checked_at", "body", "date-time", o.CheckedAt.String(), formats); err != nil { + return err + } + + return nil +} + +var listInsightsOkBodyResultsItems0TypeTriggeredByPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED","ADVISOR_CHECK_TRIGGERED_BY_USER","ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + listInsightsOkBodyResultsItems0TypeTriggeredByPropEnum = append(listInsightsOkBodyResultsItems0TypeTriggeredByPropEnum, v) + } +} + +const ( + + // ListInsightsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUNSPECIFIED captures enum value "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED" + ListInsightsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUNSPECIFIED string = "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED" + + // ListInsightsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUSER captures enum value "ADVISOR_CHECK_TRIGGERED_BY_USER" + ListInsightsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUSER string = "ADVISOR_CHECK_TRIGGERED_BY_USER" + + // ListInsightsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYSCHEDULER captures enum value "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ListInsightsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYSCHEDULER string = "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" +) + +// prop value enum +func (o *ListInsightsOKBodyResultsItems0) validateTriggeredByEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listInsightsOkBodyResultsItems0TypeTriggeredByPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ListInsightsOKBodyResultsItems0) validateTriggeredBy(formats strfmt.Registry) error { + if swag.IsZero(o.TriggeredBy) { // not required + return nil + } + + // value enum + if err := o.validateTriggeredByEnum("triggered_by", "body", *o.TriggeredBy); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this list insights OK body results items0 based on context it is used +func (o *ListInsightsOKBodyResultsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListInsightsOKBodyResultsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListInsightsOKBodyResultsItems0) UnmarshalBinary(b []byte) error { + var res ListInsightsOKBodyResultsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_runs_parameters.go b/api/advisors/v1/json/client/advisor_service/list_runs_parameters.go new file mode 100644 index 00000000000..39ddaeabf01 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_runs_parameters.go @@ -0,0 +1,312 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewListRunsParams creates a new ListRunsParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewListRunsParams() *ListRunsParams { + return &ListRunsParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewListRunsParamsWithTimeout creates a new ListRunsParams object +// with the ability to set a timeout on a request. +func NewListRunsParamsWithTimeout(timeout time.Duration) *ListRunsParams { + return &ListRunsParams{ + timeout: timeout, + } +} + +// NewListRunsParamsWithContext creates a new ListRunsParams object +// with the ability to set a context for a request. +func NewListRunsParamsWithContext(ctx context.Context) *ListRunsParams { + return &ListRunsParams{ + Context: ctx, + } +} + +// NewListRunsParamsWithHTTPClient creates a new ListRunsParams object +// with the ability to set a custom HTTPClient for a request. +func NewListRunsParamsWithHTTPClient(client *http.Client) *ListRunsParams { + return &ListRunsParams{ + HTTPClient: client, + } +} + +/* +ListRunsParams contains all the parameters to send to the API endpoint + + for the list runs operation. + + Typically these are written to a http.Request. +*/ +type ListRunsParams struct { + /* From. + + Return only runs started at or after this time. + + Format: date-time + */ + From *strfmt.DateTime + + /* PageIndex. + + Index of the requested page, starts from 0. + + Format: int32 + */ + PageIndex *int32 + + /* PageSize. + + Maximum number of results per page. + + Format: int32 + */ + PageSize *int32 + + /* To. + + Return only runs started at or before this time. + + Format: date-time + */ + To *strfmt.DateTime + + /* TriggeredBy. + + Filter by the actor that initiated the run. + + - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI. + - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler. + + Default: "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED" + */ + TriggeredBy *string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the list runs params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListRunsParams) WithDefaults() *ListRunsParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the list runs params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ListRunsParams) SetDefaults() { + triggeredByDefault := string("ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED") + + val := ListRunsParams{ + TriggeredBy: &triggeredByDefault, + } + + val.timeout = o.timeout + val.Context = o.Context + val.HTTPClient = o.HTTPClient + *o = val +} + +// WithTimeout adds the timeout to the list runs params +func (o *ListRunsParams) WithTimeout(timeout time.Duration) *ListRunsParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the list runs params +func (o *ListRunsParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the list runs params +func (o *ListRunsParams) WithContext(ctx context.Context) *ListRunsParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the list runs params +func (o *ListRunsParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the list runs params +func (o *ListRunsParams) WithHTTPClient(client *http.Client) *ListRunsParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the list runs params +func (o *ListRunsParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithFrom adds the from to the list runs params +func (o *ListRunsParams) WithFrom(from *strfmt.DateTime) *ListRunsParams { + o.SetFrom(from) + return o +} + +// SetFrom adds the from to the list runs params +func (o *ListRunsParams) SetFrom(from *strfmt.DateTime) { + o.From = from +} + +// WithPageIndex adds the pageIndex to the list runs params +func (o *ListRunsParams) WithPageIndex(pageIndex *int32) *ListRunsParams { + o.SetPageIndex(pageIndex) + return o +} + +// SetPageIndex adds the pageIndex to the list runs params +func (o *ListRunsParams) SetPageIndex(pageIndex *int32) { + o.PageIndex = pageIndex +} + +// WithPageSize adds the pageSize to the list runs params +func (o *ListRunsParams) WithPageSize(pageSize *int32) *ListRunsParams { + o.SetPageSize(pageSize) + return o +} + +// SetPageSize adds the pageSize to the list runs params +func (o *ListRunsParams) SetPageSize(pageSize *int32) { + o.PageSize = pageSize +} + +// WithTo adds the to to the list runs params +func (o *ListRunsParams) WithTo(to *strfmt.DateTime) *ListRunsParams { + o.SetTo(to) + return o +} + +// SetTo adds the to to the list runs params +func (o *ListRunsParams) SetTo(to *strfmt.DateTime) { + o.To = to +} + +// WithTriggeredBy adds the triggeredBy to the list runs params +func (o *ListRunsParams) WithTriggeredBy(triggeredBy *string) *ListRunsParams { + o.SetTriggeredBy(triggeredBy) + return o +} + +// SetTriggeredBy adds the triggeredBy to the list runs params +func (o *ListRunsParams) SetTriggeredBy(triggeredBy *string) { + o.TriggeredBy = triggeredBy +} + +// WriteToRequest writes these params to a swagger request +func (o *ListRunsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if o.From != nil { + + // query param from + var qrFrom strfmt.DateTime + + if o.From != nil { + qrFrom = *o.From + } + qFrom := qrFrom.String() + if qFrom != "" { + if err := r.SetQueryParam("from", qFrom); err != nil { + return err + } + } + } + + if o.PageIndex != nil { + + // query param page_index + var qrPageIndex int32 + + if o.PageIndex != nil { + qrPageIndex = *o.PageIndex + } + qPageIndex := swag.FormatInt32(qrPageIndex) + if qPageIndex != "" { + if err := r.SetQueryParam("page_index", qPageIndex); err != nil { + return err + } + } + } + + if o.PageSize != nil { + + // query param page_size + var qrPageSize int32 + + if o.PageSize != nil { + qrPageSize = *o.PageSize + } + qPageSize := swag.FormatInt32(qrPageSize) + if qPageSize != "" { + if err := r.SetQueryParam("page_size", qPageSize); err != nil { + return err + } + } + } + + if o.To != nil { + + // query param to + var qrTo strfmt.DateTime + + if o.To != nil { + qrTo = *o.To + } + qTo := qrTo.String() + if qTo != "" { + if err := r.SetQueryParam("to", qTo); err != nil { + return err + } + } + } + + if o.TriggeredBy != nil { + + // query param triggered_by + var qrTriggeredBy string + + if o.TriggeredBy != nil { + qrTriggeredBy = *o.TriggeredBy + } + qTriggeredBy := qrTriggeredBy + if qTriggeredBy != "" { + if err := r.SetQueryParam("triggered_by", qTriggeredBy); err != nil { + return err + } + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/list_runs_responses.go b/api/advisors/v1/json/client/advisor_service/list_runs_responses.go new file mode 100644 index 00000000000..f1c05a92296 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/list_runs_responses.go @@ -0,0 +1,868 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ListRunsReader is a Reader for the ListRuns structure. +type ListRunsReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ListRunsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewListRunsOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewListRunsDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewListRunsOK creates a ListRunsOK with default headers values +func NewListRunsOK() *ListRunsOK { + return &ListRunsOK{} +} + +/* +ListRunsOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type ListRunsOK struct { + Payload *ListRunsOKBody +} + +// IsSuccess returns true when this list runs Ok response has a 2xx status code +func (o *ListRunsOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this list runs Ok response has a 3xx status code +func (o *ListRunsOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this list runs Ok response has a 4xx status code +func (o *ListRunsOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this list runs Ok response has a 5xx status code +func (o *ListRunsOK) IsServerError() bool { + return false +} + +// IsCode returns true when this list runs Ok response a status code equal to that given +func (o *ListRunsOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the list runs Ok response +func (o *ListRunsOK) Code() int { + return 200 +} + +func (o *ListRunsOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/runs][%d] listRunsOk %s", 200, payload) +} + +func (o *ListRunsOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/runs][%d] listRunsOk %s", 200, payload) +} + +func (o *ListRunsOK) GetPayload() *ListRunsOKBody { + return o.Payload +} + +func (o *ListRunsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListRunsOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewListRunsDefault creates a ListRunsDefault with default headers values +func NewListRunsDefault(code int) *ListRunsDefault { + return &ListRunsDefault{ + _statusCode: code, + } +} + +/* +ListRunsDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type ListRunsDefault struct { + _statusCode int + + Payload *ListRunsDefaultBody +} + +// IsSuccess returns true when this list runs default response has a 2xx status code +func (o *ListRunsDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this list runs default response has a 3xx status code +func (o *ListRunsDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this list runs default response has a 4xx status code +func (o *ListRunsDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this list runs default response has a 5xx status code +func (o *ListRunsDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this list runs default response a status code equal to that given +func (o *ListRunsDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the list runs default response +func (o *ListRunsDefault) Code() int { + return o._statusCode +} + +func (o *ListRunsDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/runs][%d] ListRuns default %s", o._statusCode, payload) +} + +func (o *ListRunsDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[GET /v1/advisors/runs][%d] ListRuns default %s", o._statusCode, payload) +} + +func (o *ListRunsDefault) GetPayload() *ListRunsDefaultBody { + return o.Payload +} + +func (o *ListRunsDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(ListRunsDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +ListRunsDefaultBody list runs default body +swagger:model ListRunsDefaultBody +*/ +type ListRunsDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*ListRunsDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this list runs default body +func (o *ListRunsDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListRunsDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListRuns default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListRuns default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list runs default body based on the context it is used +func (o *ListRunsDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListRunsDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("ListRuns default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("ListRuns default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListRunsDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListRunsDefaultBody) UnmarshalBinary(b []byte) error { + var res ListRunsDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListRunsDefaultBodyDetailsItems0 list runs default body details items0 +swagger:model ListRunsDefaultBodyDetailsItems0 +*/ +type ListRunsDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // list runs default body details items0 + ListRunsDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *ListRunsDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv ListRunsDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.ListRunsDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o ListRunsDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.ListRunsDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.ListRunsDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this list runs default body details items0 +func (o *ListRunsDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this list runs default body details items0 based on context it is used +func (o *ListRunsDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListRunsDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListRunsDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res ListRunsDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListRunsOKBody list runs OK body +swagger:model ListRunsOKBody +*/ +type ListRunsOKBody struct { + // Total number of results. + TotalItems int32 `json:"total_items,omitempty"` + + // Total number of pages. + TotalPages int32 `json:"total_pages,omitempty"` + + // Runs, most recently started first. + Results []*ListRunsOKBodyResultsItems0 `json:"results"` +} + +// Validate validates this list runs OK body +func (o *ListRunsOKBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateResults(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListRunsOKBody) validateResults(formats strfmt.Registry) error { + if swag.IsZero(o.Results) { // not required + return nil + } + + for i := 0; i < len(o.Results); i++ { + if swag.IsZero(o.Results[i]) { // not required + continue + } + + if o.Results[i] != nil { + if err := o.Results[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("listRunsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("listRunsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list runs OK body based on the context it is used +func (o *ListRunsOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateResults(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListRunsOKBody) contextValidateResults(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Results); i++ { + if o.Results[i] != nil { + + if swag.IsZero(o.Results[i]) { // not required + return nil + } + + if err := o.Results[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("listRunsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("listRunsOk" + "." + "results" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListRunsOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListRunsOKBody) UnmarshalBinary(b []byte) error { + var res ListRunsOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListRunsOKBodyResultsItems0 AdvisorRun is a single execution of Advisor checks. Its totals are recorded on +// completion, so they stay accurate after the run's insights have been pruned. +swagger:model ListRunsOKBodyResultsItems0 +*/ +type ListRunsOKBodyResultsItems0 struct { + // ID shared by every insight the run produced. + ID string `json:"id,omitempty"` + + // AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run. + // + // - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI. + // - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler. + // Enum: ["ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED","ADVISOR_CHECK_TRIGGERED_BY_USER","ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER"] + TriggeredBy *string `json:"triggered_by,omitempty"` + + // When the run began. + // Format: date-time + StartedAt strfmt.DateTime `json:"started_at,omitempty"` + + // When the run completed; unset while it is still running. + // Format: date-time + FinishedAt strfmt.DateTime `json:"finished_at,omitempty"` + + // Number of distinct checks the run executed. + ChecksCount int32 `json:"checks_count,omitempty"` + + // Number of distinct services the run covered. + ServicesCount int32 `json:"services_count,omitempty"` + + // Number of findings, i.e. checks that detected an issue. + FindingsCount int32 `json:"findings_count,omitempty"` + + // Number of checks that could not be executed at all. + ErrorsCount int32 `json:"errors_count,omitempty"` + + // Number of findings per severity, most severe first. A repeated field rather + // than a map so severity stays a typed enum instead of a free-form key. + SeverityCounts []*ListRunsOKBodyResultsItems0SeverityCountsItems0 `json:"severity_counts"` +} + +// Validate validates this list runs OK body results items0 +func (o *ListRunsOKBodyResultsItems0) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateTriggeredBy(formats); err != nil { + res = append(res, err) + } + + if err := o.validateStartedAt(formats); err != nil { + res = append(res, err) + } + + if err := o.validateFinishedAt(formats); err != nil { + res = append(res, err) + } + + if err := o.validateSeverityCounts(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var listRunsOkBodyResultsItems0TypeTriggeredByPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED","ADVISOR_CHECK_TRIGGERED_BY_USER","ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + listRunsOkBodyResultsItems0TypeTriggeredByPropEnum = append(listRunsOkBodyResultsItems0TypeTriggeredByPropEnum, v) + } +} + +const ( + + // ListRunsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUNSPECIFIED captures enum value "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED" + ListRunsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUNSPECIFIED string = "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED" + + // ListRunsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUSER captures enum value "ADVISOR_CHECK_TRIGGERED_BY_USER" + ListRunsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYUSER string = "ADVISOR_CHECK_TRIGGERED_BY_USER" + + // ListRunsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYSCHEDULER captures enum value "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ListRunsOKBodyResultsItems0TriggeredByADVISORCHECKTRIGGEREDBYSCHEDULER string = "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" +) + +// prop value enum +func (o *ListRunsOKBodyResultsItems0) validateTriggeredByEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listRunsOkBodyResultsItems0TypeTriggeredByPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ListRunsOKBodyResultsItems0) validateTriggeredBy(formats strfmt.Registry) error { + if swag.IsZero(o.TriggeredBy) { // not required + return nil + } + + // value enum + if err := o.validateTriggeredByEnum("triggered_by", "body", *o.TriggeredBy); err != nil { + return err + } + + return nil +} + +func (o *ListRunsOKBodyResultsItems0) validateStartedAt(formats strfmt.Registry) error { + if swag.IsZero(o.StartedAt) { // not required + return nil + } + + if err := validate.FormatOf("started_at", "body", "date-time", o.StartedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (o *ListRunsOKBodyResultsItems0) validateFinishedAt(formats strfmt.Registry) error { + if swag.IsZero(o.FinishedAt) { // not required + return nil + } + + if err := validate.FormatOf("finished_at", "body", "date-time", o.FinishedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (o *ListRunsOKBodyResultsItems0) validateSeverityCounts(formats strfmt.Registry) error { + if swag.IsZero(o.SeverityCounts) { // not required + return nil + } + + for i := 0; i < len(o.SeverityCounts); i++ { + if swag.IsZero(o.SeverityCounts[i]) { // not required + continue + } + + if o.SeverityCounts[i] != nil { + if err := o.SeverityCounts[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("severity_counts" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("severity_counts" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this list runs OK body results items0 based on the context it is used +func (o *ListRunsOKBodyResultsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateSeverityCounts(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *ListRunsOKBodyResultsItems0) contextValidateSeverityCounts(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.SeverityCounts); i++ { + if o.SeverityCounts[i] != nil { + + if swag.IsZero(o.SeverityCounts[i]) { // not required + return nil + } + + if err := o.SeverityCounts[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("severity_counts" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("severity_counts" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *ListRunsOKBodyResultsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListRunsOKBodyResultsItems0) UnmarshalBinary(b []byte) error { + var res ListRunsOKBodyResultsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +ListRunsOKBodyResultsItems0SeverityCountsItems0 SeverityCount is the number of findings a run produced at a single severity. +swagger:model ListRunsOKBodyResultsItems0SeverityCountsItems0 +*/ +type ListRunsOKBodyResultsItems0SeverityCountsItems0 struct { + // Severity represents severity level of the check result or alert. + // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] + Severity *string `json:"severity,omitempty"` + + // count + Count int32 `json:"count,omitempty"` +} + +// Validate validates this list runs OK body results items0 severity counts items0 +func (o *ListRunsOKBodyResultsItems0SeverityCountsItems0) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateSeverity(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var listRunsOkBodyResultsItems0SeverityCountsItems0TypeSeverityPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + listRunsOkBodyResultsItems0SeverityCountsItems0TypeSeverityPropEnum = append(listRunsOkBodyResultsItems0SeverityCountsItems0TypeSeverityPropEnum, v) + } +} + +const ( + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYALERT captures enum value "SEVERITY_ALERT" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYALERT string = "SEVERITY_ALERT" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYCRITICAL string = "SEVERITY_CRITICAL" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYERROR captures enum value "SEVERITY_ERROR" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYERROR string = "SEVERITY_ERROR" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYWARNING captures enum value "SEVERITY_WARNING" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYWARNING string = "SEVERITY_WARNING" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYNOTICE captures enum value "SEVERITY_NOTICE" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYNOTICE string = "SEVERITY_NOTICE" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYINFO captures enum value "SEVERITY_INFO" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYINFO string = "SEVERITY_INFO" + + // ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYDEBUG captures enum value "SEVERITY_DEBUG" + ListRunsOKBodyResultsItems0SeverityCountsItems0SeveritySEVERITYDEBUG string = "SEVERITY_DEBUG" +) + +// prop value enum +func (o *ListRunsOKBodyResultsItems0SeverityCountsItems0) validateSeverityEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, listRunsOkBodyResultsItems0SeverityCountsItems0TypeSeverityPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ListRunsOKBodyResultsItems0SeverityCountsItems0) validateSeverity(formats strfmt.Registry) error { + if swag.IsZero(o.Severity) { // not required + return nil + } + + // value enum + if err := o.validateSeverityEnum("severity", "body", *o.Severity); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this list runs OK body results items0 severity counts items0 based on context it is used +func (o *ListRunsOKBodyResultsItems0SeverityCountsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ListRunsOKBodyResultsItems0SeverityCountsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ListRunsOKBodyResultsItems0SeverityCountsItems0) UnmarshalBinary(b []byte) error { + var res ListRunsOKBodyResultsItems0SeverityCountsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/mark_insights_read_parameters.go b/api/advisors/v1/json/client/advisor_service/mark_insights_read_parameters.go new file mode 100644 index 00000000000..2abb0e9c46a --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/mark_insights_read_parameters.go @@ -0,0 +1,141 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewMarkInsightsReadParams creates a new MarkInsightsReadParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewMarkInsightsReadParams() *MarkInsightsReadParams { + return &MarkInsightsReadParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewMarkInsightsReadParamsWithTimeout creates a new MarkInsightsReadParams object +// with the ability to set a timeout on a request. +func NewMarkInsightsReadParamsWithTimeout(timeout time.Duration) *MarkInsightsReadParams { + return &MarkInsightsReadParams{ + timeout: timeout, + } +} + +// NewMarkInsightsReadParamsWithContext creates a new MarkInsightsReadParams object +// with the ability to set a context for a request. +func NewMarkInsightsReadParamsWithContext(ctx context.Context) *MarkInsightsReadParams { + return &MarkInsightsReadParams{ + Context: ctx, + } +} + +// NewMarkInsightsReadParamsWithHTTPClient creates a new MarkInsightsReadParams object +// with the ability to set a custom HTTPClient for a request. +func NewMarkInsightsReadParamsWithHTTPClient(client *http.Client) *MarkInsightsReadParams { + return &MarkInsightsReadParams{ + HTTPClient: client, + } +} + +/* +MarkInsightsReadParams contains all the parameters to send to the API endpoint + + for the mark insights read operation. + + Typically these are written to a http.Request. +*/ +type MarkInsightsReadParams struct { + // Body. + Body MarkInsightsReadBody + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the mark insights read params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *MarkInsightsReadParams) WithDefaults() *MarkInsightsReadParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the mark insights read params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *MarkInsightsReadParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the mark insights read params +func (o *MarkInsightsReadParams) WithTimeout(timeout time.Duration) *MarkInsightsReadParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the mark insights read params +func (o *MarkInsightsReadParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the mark insights read params +func (o *MarkInsightsReadParams) WithContext(ctx context.Context) *MarkInsightsReadParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the mark insights read params +func (o *MarkInsightsReadParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the mark insights read params +func (o *MarkInsightsReadParams) WithHTTPClient(client *http.Client) *MarkInsightsReadParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the mark insights read params +func (o *MarkInsightsReadParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the mark insights read params +func (o *MarkInsightsReadParams) WithBody(body MarkInsightsReadBody) *MarkInsightsReadParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the mark insights read params +func (o *MarkInsightsReadParams) SetBody(body MarkInsightsReadBody) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *MarkInsightsReadParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/mark_insights_read_responses.go b/api/advisors/v1/json/client/advisor_service/mark_insights_read_responses.go new file mode 100644 index 00000000000..2057d681f7e --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/mark_insights_read_responses.go @@ -0,0 +1,708 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MarkInsightsReadReader is a Reader for the MarkInsightsRead structure. +type MarkInsightsReadReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *MarkInsightsReadReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewMarkInsightsReadOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewMarkInsightsReadDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewMarkInsightsReadOK creates a MarkInsightsReadOK with default headers values +func NewMarkInsightsReadOK() *MarkInsightsReadOK { + return &MarkInsightsReadOK{} +} + +/* +MarkInsightsReadOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type MarkInsightsReadOK struct { + Payload any +} + +// IsSuccess returns true when this mark insights read Ok response has a 2xx status code +func (o *MarkInsightsReadOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this mark insights read Ok response has a 3xx status code +func (o *MarkInsightsReadOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this mark insights read Ok response has a 4xx status code +func (o *MarkInsightsReadOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this mark insights read Ok response has a 5xx status code +func (o *MarkInsightsReadOK) IsServerError() bool { + return false +} + +// IsCode returns true when this mark insights read Ok response a status code equal to that given +func (o *MarkInsightsReadOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the mark insights read Ok response +func (o *MarkInsightsReadOK) Code() int { + return 200 +} + +func (o *MarkInsightsReadOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/insights:markRead][%d] markInsightsReadOk %s", 200, payload) +} + +func (o *MarkInsightsReadOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/insights:markRead][%d] markInsightsReadOk %s", 200, payload) +} + +func (o *MarkInsightsReadOK) GetPayload() any { + return o.Payload +} + +func (o *MarkInsightsReadOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewMarkInsightsReadDefault creates a MarkInsightsReadDefault with default headers values +func NewMarkInsightsReadDefault(code int) *MarkInsightsReadDefault { + return &MarkInsightsReadDefault{ + _statusCode: code, + } +} + +/* +MarkInsightsReadDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type MarkInsightsReadDefault struct { + _statusCode int + + Payload *MarkInsightsReadDefaultBody +} + +// IsSuccess returns true when this mark insights read default response has a 2xx status code +func (o *MarkInsightsReadDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this mark insights read default response has a 3xx status code +func (o *MarkInsightsReadDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this mark insights read default response has a 4xx status code +func (o *MarkInsightsReadDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this mark insights read default response has a 5xx status code +func (o *MarkInsightsReadDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this mark insights read default response a status code equal to that given +func (o *MarkInsightsReadDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the mark insights read default response +func (o *MarkInsightsReadDefault) Code() int { + return o._statusCode +} + +func (o *MarkInsightsReadDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/insights:markRead][%d] MarkInsightsRead default %s", o._statusCode, payload) +} + +func (o *MarkInsightsReadDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/insights:markRead][%d] MarkInsightsRead default %s", o._statusCode, payload) +} + +func (o *MarkInsightsReadDefault) GetPayload() *MarkInsightsReadDefaultBody { + return o.Payload +} + +func (o *MarkInsightsReadDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(MarkInsightsReadDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +MarkInsightsReadBody mark insights read body +swagger:model MarkInsightsReadBody +*/ +type MarkInsightsReadBody struct { + // IDs of the insights to update. Takes precedence over filters. + Ids []string `json:"ids"` + + // Read state to set on the records. + IsRead bool `json:"is_read,omitempty"` + + // filters + Filters *MarkInsightsReadParamsBodyFilters `json:"filters,omitempty"` +} + +// Validate validates this mark insights read body +func (o *MarkInsightsReadBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateFilters(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *MarkInsightsReadBody) validateFilters(formats strfmt.Registry) error { + if swag.IsZero(o.Filters) { // not required + return nil + } + + if o.Filters != nil { + if err := o.Filters.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "filters") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "filters") + } + + return err + } + } + + return nil +} + +// ContextValidate validate this mark insights read body based on the context it is used +func (o *MarkInsightsReadBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateFilters(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *MarkInsightsReadBody) contextValidateFilters(ctx context.Context, formats strfmt.Registry) error { + if o.Filters != nil { + + if swag.IsZero(o.Filters) { // not required + return nil + } + + if err := o.Filters.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "filters") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "filters") + } + + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *MarkInsightsReadBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *MarkInsightsReadBody) UnmarshalBinary(b []byte) error { + var res MarkInsightsReadBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +MarkInsightsReadDefaultBody mark insights read default body +swagger:model MarkInsightsReadDefaultBody +*/ +type MarkInsightsReadDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*MarkInsightsReadDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this mark insights read default body +func (o *MarkInsightsReadDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *MarkInsightsReadDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("MarkInsightsRead default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("MarkInsightsRead default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this mark insights read default body based on the context it is used +func (o *MarkInsightsReadDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *MarkInsightsReadDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("MarkInsightsRead default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("MarkInsightsRead default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *MarkInsightsReadDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *MarkInsightsReadDefaultBody) UnmarshalBinary(b []byte) error { + var res MarkInsightsReadDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +MarkInsightsReadDefaultBodyDetailsItems0 mark insights read default body details items0 +swagger:model MarkInsightsReadDefaultBodyDetailsItems0 +*/ +type MarkInsightsReadDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // mark insights read default body details items0 + MarkInsightsReadDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *MarkInsightsReadDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv MarkInsightsReadDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.MarkInsightsReadDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o MarkInsightsReadDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.MarkInsightsReadDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.MarkInsightsReadDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this mark insights read default body details items0 +func (o *MarkInsightsReadDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this mark insights read default body details items0 based on context it is used +func (o *MarkInsightsReadDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *MarkInsightsReadDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *MarkInsightsReadDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res MarkInsightsReadDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +MarkInsightsReadParamsBodyFilters InsightsFilters select Advisor insights by attribute; all present fields must match. +swagger:model MarkInsightsReadParamsBodyFilters +*/ +type MarkInsightsReadParamsBodyFilters struct { + // Filter by check name. + CheckName string `json:"check_name,omitempty"` + + // Filter by service name (partial, case-insensitive match). + ServiceName string `json:"service_name,omitempty"` + + // Filter by node name (partial, case-insensitive match). + NodeName string `json:"node_name,omitempty"` + + // Filter by advisor category. + Category string `json:"category,omitempty"` + + // Severity represents severity level of the check result or alert. + // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] + Severity *string `json:"severity,omitempty"` + + // AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service. + // + // - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue. + // - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue. + // - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed. + // Enum: ["ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED","ADVISOR_CHECK_RESULT_STATUS_OK","ADVISOR_CHECK_RESULT_STATUS_FAILED","ADVISOR_CHECK_RESULT_STATUS_ERROR"] + Status *string `json:"status,omitempty"` + + // Filter by read state. + IsRead *bool `json:"is_read,omitempty"` + + // Filter by run ID. + RunID string `json:"run_id,omitempty"` +} + +// Validate validates this mark insights read params body filters +func (o *MarkInsightsReadParamsBodyFilters) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateSeverity(formats); err != nil { + res = append(res, err) + } + + if err := o.validateStatus(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var markInsightsReadParamsBodyFiltersTypeSeverityPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + markInsightsReadParamsBodyFiltersTypeSeverityPropEnum = append(markInsightsReadParamsBodyFiltersTypeSeverityPropEnum, v) + } +} + +const ( + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYALERT captures enum value "SEVERITY_ALERT" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYALERT string = "SEVERITY_ALERT" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYCRITICAL string = "SEVERITY_CRITICAL" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYERROR captures enum value "SEVERITY_ERROR" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYERROR string = "SEVERITY_ERROR" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYWARNING captures enum value "SEVERITY_WARNING" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYWARNING string = "SEVERITY_WARNING" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYNOTICE captures enum value "SEVERITY_NOTICE" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYNOTICE string = "SEVERITY_NOTICE" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYINFO captures enum value "SEVERITY_INFO" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYINFO string = "SEVERITY_INFO" + + // MarkInsightsReadParamsBodyFiltersSeveritySEVERITYDEBUG captures enum value "SEVERITY_DEBUG" + MarkInsightsReadParamsBodyFiltersSeveritySEVERITYDEBUG string = "SEVERITY_DEBUG" +) + +// prop value enum +func (o *MarkInsightsReadParamsBodyFilters) validateSeverityEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, markInsightsReadParamsBodyFiltersTypeSeverityPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *MarkInsightsReadParamsBodyFilters) validateSeverity(formats strfmt.Registry) error { + if swag.IsZero(o.Severity) { // not required + return nil + } + + // value enum + if err := o.validateSeverityEnum("body"+"."+"filters"+"."+"severity", "body", *o.Severity); err != nil { + return err + } + + return nil +} + +var markInsightsReadParamsBodyFiltersTypeStatusPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED","ADVISOR_CHECK_RESULT_STATUS_OK","ADVISOR_CHECK_RESULT_STATUS_FAILED","ADVISOR_CHECK_RESULT_STATUS_ERROR"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + markInsightsReadParamsBodyFiltersTypeStatusPropEnum = append(markInsightsReadParamsBodyFiltersTypeStatusPropEnum, v) + } +} + +const ( + + // MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSUNSPECIFIED captures enum value "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED" + MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSUNSPECIFIED string = "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED" + + // MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSOK captures enum value "ADVISOR_CHECK_RESULT_STATUS_OK" + MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSOK string = "ADVISOR_CHECK_RESULT_STATUS_OK" + + // MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSFAILED captures enum value "ADVISOR_CHECK_RESULT_STATUS_FAILED" + MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSFAILED string = "ADVISOR_CHECK_RESULT_STATUS_FAILED" + + // MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSERROR captures enum value "ADVISOR_CHECK_RESULT_STATUS_ERROR" + MarkInsightsReadParamsBodyFiltersStatusADVISORCHECKRESULTSTATUSERROR string = "ADVISOR_CHECK_RESULT_STATUS_ERROR" +) + +// prop value enum +func (o *MarkInsightsReadParamsBodyFilters) validateStatusEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, markInsightsReadParamsBodyFiltersTypeStatusPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *MarkInsightsReadParamsBodyFilters) validateStatus(formats strfmt.Registry) error { + if swag.IsZero(o.Status) { // not required + return nil + } + + // value enum + if err := o.validateStatusEnum("body"+"."+"filters"+"."+"status", "body", *o.Status); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this mark insights read params body filters based on context it is used +func (o *MarkInsightsReadParamsBodyFilters) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *MarkInsightsReadParamsBodyFilters) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *MarkInsightsReadParamsBodyFilters) UnmarshalBinary(b []byte) error { + var res MarkInsightsReadParamsBodyFilters + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/start_advisor_checks_responses.go b/api/advisors/v1/json/client/advisor_service/start_advisor_checks_responses.go index 8477c9cc6ee..db751157f3d 100644 --- a/api/advisors/v1/json/client/advisor_service/start_advisor_checks_responses.go +++ b/api/advisors/v1/json/client/advisor_service/start_advisor_checks_responses.go @@ -53,7 +53,7 @@ StartAdvisorChecksOK describes a response with status code 200, with default hea A successful response. */ type StartAdvisorChecksOK struct { - Payload any + Payload *StartAdvisorChecksOKBody } // IsSuccess returns true when this start advisor checks Ok response has a 2xx status code @@ -96,13 +96,15 @@ func (o *StartAdvisorChecksOK) String() string { return fmt.Sprintf("[POST /v1/advisors/checks:start][%d] startAdvisorChecksOk %s", 200, payload) } -func (o *StartAdvisorChecksOK) GetPayload() any { +func (o *StartAdvisorChecksOK) GetPayload() *StartAdvisorChecksOKBody { return o.Payload } func (o *StartAdvisorChecksOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(StartAdvisorChecksOKBody) + // response payload - if err := consumer.Consume(response.Body(), &o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { return err } @@ -189,6 +191,10 @@ swagger:model StartAdvisorChecksBody type StartAdvisorChecksBody struct { // Names of the checks that should be started. Names []string `json:"names"` + + // IDs of the services to run the checks against. When empty, the checks run + // against every monitored service of a matching technology. + ServiceIds []string `json:"service_ids"` } // Validate validates this start advisor checks body @@ -446,3 +452,40 @@ func (o *StartAdvisorChecksDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) e *o = res return nil } + +/* +StartAdvisorChecksOKBody start advisor checks OK body +swagger:model StartAdvisorChecksOKBody +*/ +type StartAdvisorChecksOKBody struct { + // ID assigned to this run; all check results produced by it share this run_id. + RunID string `json:"run_id,omitempty"` +} + +// Validate validates this start advisor checks OK body +func (o *StartAdvisorChecksOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this start advisor checks OK body based on context it is used +func (o *StartAdvisorChecksOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *StartAdvisorChecksOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *StartAdvisorChecksOKBody) UnmarshalBinary(b []byte) error { + var res StartAdvisorChecksOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/test_advisor_check_parameters.go b/api/advisors/v1/json/client/advisor_service/test_advisor_check_parameters.go new file mode 100644 index 00000000000..ab7e2f786c6 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/test_advisor_check_parameters.go @@ -0,0 +1,141 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewTestAdvisorCheckParams creates a new TestAdvisorCheckParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewTestAdvisorCheckParams() *TestAdvisorCheckParams { + return &TestAdvisorCheckParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewTestAdvisorCheckParamsWithTimeout creates a new TestAdvisorCheckParams object +// with the ability to set a timeout on a request. +func NewTestAdvisorCheckParamsWithTimeout(timeout time.Duration) *TestAdvisorCheckParams { + return &TestAdvisorCheckParams{ + timeout: timeout, + } +} + +// NewTestAdvisorCheckParamsWithContext creates a new TestAdvisorCheckParams object +// with the ability to set a context for a request. +func NewTestAdvisorCheckParamsWithContext(ctx context.Context) *TestAdvisorCheckParams { + return &TestAdvisorCheckParams{ + Context: ctx, + } +} + +// NewTestAdvisorCheckParamsWithHTTPClient creates a new TestAdvisorCheckParams object +// with the ability to set a custom HTTPClient for a request. +func NewTestAdvisorCheckParamsWithHTTPClient(client *http.Client) *TestAdvisorCheckParams { + return &TestAdvisorCheckParams{ + HTTPClient: client, + } +} + +/* +TestAdvisorCheckParams contains all the parameters to send to the API endpoint + + for the test advisor check operation. + + Typically these are written to a http.Request. +*/ +type TestAdvisorCheckParams struct { + // Body. + Body TestAdvisorCheckBody + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the test advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *TestAdvisorCheckParams) WithDefaults() *TestAdvisorCheckParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the test advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *TestAdvisorCheckParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the test advisor check params +func (o *TestAdvisorCheckParams) WithTimeout(timeout time.Duration) *TestAdvisorCheckParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the test advisor check params +func (o *TestAdvisorCheckParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the test advisor check params +func (o *TestAdvisorCheckParams) WithContext(ctx context.Context) *TestAdvisorCheckParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the test advisor check params +func (o *TestAdvisorCheckParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the test advisor check params +func (o *TestAdvisorCheckParams) WithHTTPClient(client *http.Client) *TestAdvisorCheckParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the test advisor check params +func (o *TestAdvisorCheckParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the test advisor check params +func (o *TestAdvisorCheckParams) WithBody(body TestAdvisorCheckBody) *TestAdvisorCheckParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the test advisor check params +func (o *TestAdvisorCheckParams) SetBody(body TestAdvisorCheckBody) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *TestAdvisorCheckParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/test_advisor_check_responses.go b/api/advisors/v1/json/client/advisor_service/test_advisor_check_responses.go new file mode 100644 index 00000000000..cafd582b970 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/test_advisor_check_responses.go @@ -0,0 +1,1057 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// TestAdvisorCheckReader is a Reader for the TestAdvisorCheck structure. +type TestAdvisorCheckReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *TestAdvisorCheckReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewTestAdvisorCheckOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewTestAdvisorCheckDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewTestAdvisorCheckOK creates a TestAdvisorCheckOK with default headers values +func NewTestAdvisorCheckOK() *TestAdvisorCheckOK { + return &TestAdvisorCheckOK{} +} + +/* +TestAdvisorCheckOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type TestAdvisorCheckOK struct { + Payload *TestAdvisorCheckOKBody +} + +// IsSuccess returns true when this test advisor check Ok response has a 2xx status code +func (o *TestAdvisorCheckOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this test advisor check Ok response has a 3xx status code +func (o *TestAdvisorCheckOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this test advisor check Ok response has a 4xx status code +func (o *TestAdvisorCheckOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this test advisor check Ok response has a 5xx status code +func (o *TestAdvisorCheckOK) IsServerError() bool { + return false +} + +// IsCode returns true when this test advisor check Ok response a status code equal to that given +func (o *TestAdvisorCheckOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the test advisor check Ok response +func (o *TestAdvisorCheckOK) Code() int { + return 200 +} + +func (o *TestAdvisorCheckOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks:test][%d] testAdvisorCheckOk %s", 200, payload) +} + +func (o *TestAdvisorCheckOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks:test][%d] testAdvisorCheckOk %s", 200, payload) +} + +func (o *TestAdvisorCheckOK) GetPayload() *TestAdvisorCheckOKBody { + return o.Payload +} + +func (o *TestAdvisorCheckOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(TestAdvisorCheckOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewTestAdvisorCheckDefault creates a TestAdvisorCheckDefault with default headers values +func NewTestAdvisorCheckDefault(code int) *TestAdvisorCheckDefault { + return &TestAdvisorCheckDefault{ + _statusCode: code, + } +} + +/* +TestAdvisorCheckDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type TestAdvisorCheckDefault struct { + _statusCode int + + Payload *TestAdvisorCheckDefaultBody +} + +// IsSuccess returns true when this test advisor check default response has a 2xx status code +func (o *TestAdvisorCheckDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this test advisor check default response has a 3xx status code +func (o *TestAdvisorCheckDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this test advisor check default response has a 4xx status code +func (o *TestAdvisorCheckDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this test advisor check default response has a 5xx status code +func (o *TestAdvisorCheckDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this test advisor check default response a status code equal to that given +func (o *TestAdvisorCheckDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the test advisor check default response +func (o *TestAdvisorCheckDefault) Code() int { + return o._statusCode +} + +func (o *TestAdvisorCheckDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks:test][%d] TestAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *TestAdvisorCheckDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/advisors/checks:test][%d] TestAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *TestAdvisorCheckDefault) GetPayload() *TestAdvisorCheckDefaultBody { + return o.Payload +} + +func (o *TestAdvisorCheckDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(TestAdvisorCheckDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +TestAdvisorCheckBody test advisor check body +swagger:model TestAdvisorCheckBody +*/ +type TestAdvisorCheckBody struct { + // ID of the service to run the check against. + ServiceID string `json:"service_id,omitempty"` + + // check + Check *TestAdvisorCheckParamsBodyCheck `json:"check,omitempty"` +} + +// Validate validates this test advisor check body +func (o *TestAdvisorCheckBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateCheck(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TestAdvisorCheckBody) validateCheck(formats strfmt.Registry) error { + if swag.IsZero(o.Check) { // not required + return nil + } + + if o.Check != nil { + if err := o.Check.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check") + } + + return err + } + } + + return nil +} + +// ContextValidate validate this test advisor check body based on the context it is used +func (o *TestAdvisorCheckBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateCheck(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TestAdvisorCheckBody) contextValidateCheck(ctx context.Context, formats strfmt.Registry) error { + if o.Check != nil { + + if swag.IsZero(o.Check) { // not required + return nil + } + + if err := o.Check.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check") + } + + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *TestAdvisorCheckBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *TestAdvisorCheckBody) UnmarshalBinary(b []byte) error { + var res TestAdvisorCheckBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +TestAdvisorCheckDefaultBody test advisor check default body +swagger:model TestAdvisorCheckDefaultBody +*/ +type TestAdvisorCheckDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*TestAdvisorCheckDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this test advisor check default body +func (o *TestAdvisorCheckDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TestAdvisorCheckDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("TestAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("TestAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this test advisor check default body based on the context it is used +func (o *TestAdvisorCheckDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TestAdvisorCheckDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("TestAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("TestAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *TestAdvisorCheckDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *TestAdvisorCheckDefaultBody) UnmarshalBinary(b []byte) error { + var res TestAdvisorCheckDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +TestAdvisorCheckDefaultBodyDetailsItems0 test advisor check default body details items0 +swagger:model TestAdvisorCheckDefaultBodyDetailsItems0 +*/ +type TestAdvisorCheckDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // test advisor check default body details items0 + TestAdvisorCheckDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *TestAdvisorCheckDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv TestAdvisorCheckDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.TestAdvisorCheckDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o TestAdvisorCheckDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.TestAdvisorCheckDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.TestAdvisorCheckDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this test advisor check default body details items0 +func (o *TestAdvisorCheckDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this test advisor check default body details items0 based on context it is used +func (o *TestAdvisorCheckDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *TestAdvisorCheckDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *TestAdvisorCheckDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res TestAdvisorCheckDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +TestAdvisorCheckOKBody test advisor check OK body +swagger:model TestAdvisorCheckOKBody +*/ +type TestAdvisorCheckOKBody struct { + // Findings produced by the check script; empty means the check passed. + Results []*TestAdvisorCheckOKBodyResultsItems0 `json:"results"` + + // Output produced by the script's print() calls, for debugging. + ScriptOutput string `json:"script_output,omitempty"` +} + +// Validate validates this test advisor check OK body +func (o *TestAdvisorCheckOKBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateResults(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TestAdvisorCheckOKBody) validateResults(formats strfmt.Registry) error { + if swag.IsZero(o.Results) { // not required + return nil + } + + for i := 0; i < len(o.Results); i++ { + if swag.IsZero(o.Results[i]) { // not required + continue + } + + if o.Results[i] != nil { + if err := o.Results[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("testAdvisorCheckOk" + "." + "results" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("testAdvisorCheckOk" + "." + "results" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this test advisor check OK body based on the context it is used +func (o *TestAdvisorCheckOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateResults(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TestAdvisorCheckOKBody) contextValidateResults(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Results); i++ { + if o.Results[i] != nil { + + if swag.IsZero(o.Results[i]) { // not required + return nil + } + + if err := o.Results[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("testAdvisorCheckOk" + "." + "results" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("testAdvisorCheckOk" + "." + "results" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *TestAdvisorCheckOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *TestAdvisorCheckOKBody) UnmarshalBinary(b []byte) error { + var res TestAdvisorCheckOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +TestAdvisorCheckOKBodyResultsItems0 TestAdvisorCheckResult is a single finding produced by a test (dry-run) check execution. +swagger:model TestAdvisorCheckOKBodyResultsItems0 +*/ +type TestAdvisorCheckOKBodyResultsItems0 struct { + // summary + Summary string `json:"summary,omitempty"` + + // description + Description string `json:"description,omitempty"` + + // Severity represents severity level of the check result or alert. + // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] + Severity *string `json:"severity,omitempty"` + + // labels + Labels map[string]string `json:"labels,omitempty"` + + // URL containing information on how to resolve an issue detected by the check. + ReadMoreURL string `json:"read_more_url,omitempty"` + + // Name of the monitored service on which the check ran. + ServiceName string `json:"service_name,omitempty"` + + // ID of the monitored service on which the check ran. + ServiceID string `json:"service_id,omitempty"` + + // Name of the tested check. + CheckName string `json:"check_name,omitempty"` +} + +// Validate validates this test advisor check OK body results items0 +func (o *TestAdvisorCheckOKBodyResultsItems0) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateSeverity(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var testAdvisorCheckOkBodyResultsItems0TypeSeverityPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + testAdvisorCheckOkBodyResultsItems0TypeSeverityPropEnum = append(testAdvisorCheckOkBodyResultsItems0TypeSeverityPropEnum, v) + } +} + +const ( + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYALERT captures enum value "SEVERITY_ALERT" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYALERT string = "SEVERITY_ALERT" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYCRITICAL string = "SEVERITY_CRITICAL" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYERROR captures enum value "SEVERITY_ERROR" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYERROR string = "SEVERITY_ERROR" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYWARNING captures enum value "SEVERITY_WARNING" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYWARNING string = "SEVERITY_WARNING" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYNOTICE captures enum value "SEVERITY_NOTICE" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYNOTICE string = "SEVERITY_NOTICE" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYINFO captures enum value "SEVERITY_INFO" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYINFO string = "SEVERITY_INFO" + + // TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYDEBUG captures enum value "SEVERITY_DEBUG" + TestAdvisorCheckOKBodyResultsItems0SeveritySEVERITYDEBUG string = "SEVERITY_DEBUG" +) + +// prop value enum +func (o *TestAdvisorCheckOKBodyResultsItems0) validateSeverityEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, testAdvisorCheckOkBodyResultsItems0TypeSeverityPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *TestAdvisorCheckOKBodyResultsItems0) validateSeverity(formats strfmt.Registry) error { + if swag.IsZero(o.Severity) { // not required + return nil + } + + // value enum + if err := o.validateSeverityEnum("severity", "body", *o.Severity); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this test advisor check OK body results items0 based on context it is used +func (o *TestAdvisorCheckOKBodyResultsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *TestAdvisorCheckOKBodyResultsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *TestAdvisorCheckOKBodyResultsItems0) UnmarshalBinary(b []byte) error { + var res TestAdvisorCheckOKBodyResultsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +TestAdvisorCheckParamsBodyCheck AdvisorCheck contains check name and status. +swagger:model TestAdvisorCheckParamsBodyCheck +*/ +type TestAdvisorCheckParamsBodyCheck struct { + // Machine-readable name (ID) that is used in expression. + Name string `json:"name,omitempty"` + + // True if that check is enabled. + Enabled bool `json:"enabled,omitempty"` + + // Long human-readable description. + Description string `json:"description,omitempty"` + + // Short human-readable summary. + Summary string `json:"summary,omitempty"` + + // AdvisorCheckInterval represents possible execution interval values for checks. + // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] + Interval *string `json:"interval,omitempty"` + + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*TestAdvisorCheckParamsBodyCheckQueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` +} + +// Validate validates this test advisor check params body check +func (o *TestAdvisorCheckParamsBodyCheck) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateInterval(formats); err != nil { + res = append(res, err) + } + + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var testAdvisorCheckParamsBodyCheckTypeIntervalPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + testAdvisorCheckParamsBodyCheckTypeIntervalPropEnum = append(testAdvisorCheckParamsBodyCheckTypeIntervalPropEnum, v) + } +} + +const ( + + // TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED captures enum value "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED string = "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + + // TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALSTANDARD captures enum value "ADVISOR_CHECK_INTERVAL_STANDARD" + TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALSTANDARD string = "ADVISOR_CHECK_INTERVAL_STANDARD" + + // TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALFREQUENT captures enum value "ADVISOR_CHECK_INTERVAL_FREQUENT" + TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALFREQUENT string = "ADVISOR_CHECK_INTERVAL_FREQUENT" + + // TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALRARE captures enum value "ADVISOR_CHECK_INTERVAL_RARE" + TestAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALRARE string = "ADVISOR_CHECK_INTERVAL_RARE" +) + +// prop value enum +func (o *TestAdvisorCheckParamsBodyCheck) validateIntervalEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, testAdvisorCheckParamsBodyCheckTypeIntervalPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *TestAdvisorCheckParamsBodyCheck) validateInterval(formats strfmt.Registry) error { + if swag.IsZero(o.Interval) { // not required + return nil + } + + // value enum + if err := o.validateIntervalEnum("body"+"."+"check"+"."+"interval", "body", *o.Interval); err != nil { + return err + } + + return nil +} + +var testAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + testAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum = append(testAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum, v) + } +} + +const ( + + // TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + + // TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + + // TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + + // TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + TestAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" +) + +// prop value enum +func (o *TestAdvisorCheckParamsBodyCheck) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, testAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *TestAdvisorCheckParamsBodyCheck) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required + return nil + } + + // value enum + if err := o.validateTechnologyEnum("body"+"."+"check"+"."+"technology", "body", *o.Technology); err != nil { + return err + } + + return nil +} + +func (o *TestAdvisorCheckParamsBodyCheck) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this test advisor check params body check based on the context it is used +func (o *TestAdvisorCheckParamsBodyCheck) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TestAdvisorCheckParamsBodyCheck) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *TestAdvisorCheckParamsBodyCheck) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *TestAdvisorCheckParamsBodyCheck) UnmarshalBinary(b []byte) error { + var res TestAdvisorCheckParamsBodyCheck + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +TestAdvisorCheckParamsBodyCheckQueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model TestAdvisorCheckParamsBodyCheckQueriesItems0 +*/ +type TestAdvisorCheckParamsBodyCheckQueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this test advisor check params body check queries items0 +func (o *TestAdvisorCheckParamsBodyCheckQueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this test advisor check params body check queries items0 based on context it is used +func (o *TestAdvisorCheckParamsBodyCheckQueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *TestAdvisorCheckParamsBodyCheckQueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *TestAdvisorCheckParamsBodyCheckQueriesItems0) UnmarshalBinary(b []byte) error { + var res TestAdvisorCheckParamsBodyCheckQueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/update_advisor_check_parameters.go b/api/advisors/v1/json/client/advisor_service/update_advisor_check_parameters.go new file mode 100644 index 00000000000..270439e1ab6 --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/update_advisor_check_parameters.go @@ -0,0 +1,163 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewUpdateAdvisorCheckParams creates a new UpdateAdvisorCheckParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewUpdateAdvisorCheckParams() *UpdateAdvisorCheckParams { + return &UpdateAdvisorCheckParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewUpdateAdvisorCheckParamsWithTimeout creates a new UpdateAdvisorCheckParams object +// with the ability to set a timeout on a request. +func NewUpdateAdvisorCheckParamsWithTimeout(timeout time.Duration) *UpdateAdvisorCheckParams { + return &UpdateAdvisorCheckParams{ + timeout: timeout, + } +} + +// NewUpdateAdvisorCheckParamsWithContext creates a new UpdateAdvisorCheckParams object +// with the ability to set a context for a request. +func NewUpdateAdvisorCheckParamsWithContext(ctx context.Context) *UpdateAdvisorCheckParams { + return &UpdateAdvisorCheckParams{ + Context: ctx, + } +} + +// NewUpdateAdvisorCheckParamsWithHTTPClient creates a new UpdateAdvisorCheckParams object +// with the ability to set a custom HTTPClient for a request. +func NewUpdateAdvisorCheckParamsWithHTTPClient(client *http.Client) *UpdateAdvisorCheckParams { + return &UpdateAdvisorCheckParams{ + HTTPClient: client, + } +} + +/* +UpdateAdvisorCheckParams contains all the parameters to send to the API endpoint + + for the update advisor check operation. + + Typically these are written to a http.Request. +*/ +type UpdateAdvisorCheckParams struct { + // Body. + Body UpdateAdvisorCheckBody + + /* Name. + + Machine-readable name (ID) of the check to update. + */ + Name string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the update advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *UpdateAdvisorCheckParams) WithDefaults() *UpdateAdvisorCheckParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the update advisor check params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *UpdateAdvisorCheckParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the update advisor check params +func (o *UpdateAdvisorCheckParams) WithTimeout(timeout time.Duration) *UpdateAdvisorCheckParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the update advisor check params +func (o *UpdateAdvisorCheckParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the update advisor check params +func (o *UpdateAdvisorCheckParams) WithContext(ctx context.Context) *UpdateAdvisorCheckParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the update advisor check params +func (o *UpdateAdvisorCheckParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the update advisor check params +func (o *UpdateAdvisorCheckParams) WithHTTPClient(client *http.Client) *UpdateAdvisorCheckParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the update advisor check params +func (o *UpdateAdvisorCheckParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the update advisor check params +func (o *UpdateAdvisorCheckParams) WithBody(body UpdateAdvisorCheckBody) *UpdateAdvisorCheckParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the update advisor check params +func (o *UpdateAdvisorCheckParams) SetBody(body UpdateAdvisorCheckBody) { + o.Body = body +} + +// WithName adds the name to the update advisor check params +func (o *UpdateAdvisorCheckParams) WithName(name string) *UpdateAdvisorCheckParams { + o.SetName(name) + return o +} + +// SetName adds the name to the update advisor check params +func (o *UpdateAdvisorCheckParams) SetName(name string) { + o.Name = name +} + +// WriteToRequest writes these params to a swagger request +func (o *UpdateAdvisorCheckParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + + // path param name + if err := r.SetPathParam("name", o.Name); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/api/advisors/v1/json/client/advisor_service/update_advisor_check_responses.go b/api/advisors/v1/json/client/advisor_service/update_advisor_check_responses.go new file mode 100644 index 00000000000..61ac509162d --- /dev/null +++ b/api/advisors/v1/json/client/advisor_service/update_advisor_check_responses.go @@ -0,0 +1,1204 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package advisor_service + +import ( + "context" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// UpdateAdvisorCheckReader is a Reader for the UpdateAdvisorCheck structure. +type UpdateAdvisorCheckReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *UpdateAdvisorCheckReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewUpdateAdvisorCheckOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewUpdateAdvisorCheckDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewUpdateAdvisorCheckOK creates a UpdateAdvisorCheckOK with default headers values +func NewUpdateAdvisorCheckOK() *UpdateAdvisorCheckOK { + return &UpdateAdvisorCheckOK{} +} + +/* +UpdateAdvisorCheckOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type UpdateAdvisorCheckOK struct { + Payload *UpdateAdvisorCheckOKBody +} + +// IsSuccess returns true when this update advisor check Ok response has a 2xx status code +func (o *UpdateAdvisorCheckOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this update advisor check Ok response has a 3xx status code +func (o *UpdateAdvisorCheckOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this update advisor check Ok response has a 4xx status code +func (o *UpdateAdvisorCheckOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this update advisor check Ok response has a 5xx status code +func (o *UpdateAdvisorCheckOK) IsServerError() bool { + return false +} + +// IsCode returns true when this update advisor check Ok response a status code equal to that given +func (o *UpdateAdvisorCheckOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the update advisor check Ok response +func (o *UpdateAdvisorCheckOK) Code() int { + return 200 +} + +func (o *UpdateAdvisorCheckOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /v1/advisors/checks/{name}][%d] updateAdvisorCheckOk %s", 200, payload) +} + +func (o *UpdateAdvisorCheckOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /v1/advisors/checks/{name}][%d] updateAdvisorCheckOk %s", 200, payload) +} + +func (o *UpdateAdvisorCheckOK) GetPayload() *UpdateAdvisorCheckOKBody { + return o.Payload +} + +func (o *UpdateAdvisorCheckOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(UpdateAdvisorCheckOKBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewUpdateAdvisorCheckDefault creates a UpdateAdvisorCheckDefault with default headers values +func NewUpdateAdvisorCheckDefault(code int) *UpdateAdvisorCheckDefault { + return &UpdateAdvisorCheckDefault{ + _statusCode: code, + } +} + +/* +UpdateAdvisorCheckDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type UpdateAdvisorCheckDefault struct { + _statusCode int + + Payload *UpdateAdvisorCheckDefaultBody +} + +// IsSuccess returns true when this update advisor check default response has a 2xx status code +func (o *UpdateAdvisorCheckDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this update advisor check default response has a 3xx status code +func (o *UpdateAdvisorCheckDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this update advisor check default response has a 4xx status code +func (o *UpdateAdvisorCheckDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this update advisor check default response has a 5xx status code +func (o *UpdateAdvisorCheckDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this update advisor check default response a status code equal to that given +func (o *UpdateAdvisorCheckDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the update advisor check default response +func (o *UpdateAdvisorCheckDefault) Code() int { + return o._statusCode +} + +func (o *UpdateAdvisorCheckDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /v1/advisors/checks/{name}][%d] UpdateAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *UpdateAdvisorCheckDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /v1/advisors/checks/{name}][%d] UpdateAdvisorCheck default %s", o._statusCode, payload) +} + +func (o *UpdateAdvisorCheckDefault) GetPayload() *UpdateAdvisorCheckDefaultBody { + return o.Payload +} + +func (o *UpdateAdvisorCheckDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + o.Payload = new(UpdateAdvisorCheckDefaultBody) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +/* +UpdateAdvisorCheckBody update advisor check body +swagger:model UpdateAdvisorCheckBody +*/ +type UpdateAdvisorCheckBody struct { + // check + Check *UpdateAdvisorCheckParamsBodyCheck `json:"check,omitempty"` +} + +// Validate validates this update advisor check body +func (o *UpdateAdvisorCheckBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateCheck(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckBody) validateCheck(formats strfmt.Registry) error { + if swag.IsZero(o.Check) { // not required + return nil + } + + if o.Check != nil { + if err := o.Check.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check") + } + + return err + } + } + + return nil +} + +// ContextValidate validate this update advisor check body based on the context it is used +func (o *UpdateAdvisorCheckBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateCheck(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckBody) contextValidateCheck(ctx context.Context, formats strfmt.Registry) error { + if o.Check != nil { + + if swag.IsZero(o.Check) { // not required + return nil + } + + if err := o.Check.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check") + } + + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckBody) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateAdvisorCheckDefaultBody update advisor check default body +swagger:model UpdateAdvisorCheckDefaultBody +*/ +type UpdateAdvisorCheckDefaultBody struct { + // code + Code int32 `json:"code,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // details + Details []*UpdateAdvisorCheckDefaultBodyDetailsItems0 `json:"details"` +} + +// Validate validates this update advisor check default body +func (o *UpdateAdvisorCheckDefaultBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateDetails(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckDefaultBody) validateDetails(formats strfmt.Registry) error { + if swag.IsZero(o.Details) { // not required + return nil + } + + for i := 0; i < len(o.Details); i++ { + if swag.IsZero(o.Details[i]) { // not required + continue + } + + if o.Details[i] != nil { + if err := o.Details[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("UpdateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("UpdateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this update advisor check default body based on the context it is used +func (o *UpdateAdvisorCheckDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateDetails(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Details); i++ { + if o.Details[i] != nil { + + if swag.IsZero(o.Details[i]) { // not required + return nil + } + + if err := o.Details[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("UpdateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("UpdateAdvisorCheck default" + "." + "details" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckDefaultBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckDefaultBody) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckDefaultBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateAdvisorCheckDefaultBodyDetailsItems0 update advisor check default body details items0 +swagger:model UpdateAdvisorCheckDefaultBodyDetailsItems0 +*/ +type UpdateAdvisorCheckDefaultBodyDetailsItems0 struct { + // at type + AtType string `json:"@type,omitempty"` + + // update advisor check default body details items0 + UpdateAdvisorCheckDefaultBodyDetailsItems0 map[string]any `json:"-"` +} + +// UnmarshalJSON unmarshals this object with additional properties from JSON +func (o *UpdateAdvisorCheckDefaultBodyDetailsItems0) UnmarshalJSON(data []byte) error { + // stage 1, bind the properties + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + if err := json.Unmarshal(data, &stage1); err != nil { + return err + } + var rcv UpdateAdvisorCheckDefaultBodyDetailsItems0 + + rcv.AtType = stage1.AtType + *o = rcv + + // stage 2, remove properties and add to map + stage2 := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &stage2); err != nil { + return err + } + + delete(stage2, "@type") + // stage 3, add additional properties values + if len(stage2) > 0 { + result := make(map[string]any) + for k, v := range stage2 { + var toadd any + if err := json.Unmarshal(v, &toadd); err != nil { + return err + } + result[k] = toadd + } + o.UpdateAdvisorCheckDefaultBodyDetailsItems0 = result + } + + return nil +} + +// MarshalJSON marshals this object with additional properties into a JSON object +func (o UpdateAdvisorCheckDefaultBodyDetailsItems0) MarshalJSON() ([]byte, error) { + var stage1 struct { + // at type + AtType string `json:"@type,omitempty"` + } + + stage1.AtType = o.AtType + + // make JSON object for known properties + props, err := json.Marshal(stage1) + if err != nil { + return nil, err + } + + if len(o.UpdateAdvisorCheckDefaultBodyDetailsItems0) == 0 { // no additional properties + return props, nil + } + + // make JSON object for the additional properties + additional, err := json.Marshal(o.UpdateAdvisorCheckDefaultBodyDetailsItems0) + if err != nil { + return nil, err + } + + if len(props) < 3 { // "{}": only additional properties + return additional, nil + } + + // concatenate the 2 objects + return swag.ConcatJSON(props, additional), nil +} + +// Validate validates this update advisor check default body details items0 +func (o *UpdateAdvisorCheckDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this update advisor check default body details items0 based on context it is used +func (o *UpdateAdvisorCheckDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckDefaultBodyDetailsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateAdvisorCheckOKBody update advisor check OK body +swagger:model UpdateAdvisorCheckOKBody +*/ +type UpdateAdvisorCheckOKBody struct { + // check + Check *UpdateAdvisorCheckOKBodyCheck `json:"check,omitempty"` +} + +// Validate validates this update advisor check OK body +func (o *UpdateAdvisorCheckOKBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateCheck(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckOKBody) validateCheck(formats strfmt.Registry) error { + if swag.IsZero(o.Check) { // not required + return nil + } + + if o.Check != nil { + if err := o.Check.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("updateAdvisorCheckOk" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("updateAdvisorCheckOk" + "." + "check") + } + + return err + } + } + + return nil +} + +// ContextValidate validate this update advisor check OK body based on the context it is used +func (o *UpdateAdvisorCheckOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateCheck(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckOKBody) contextValidateCheck(ctx context.Context, formats strfmt.Registry) error { + if o.Check != nil { + + if swag.IsZero(o.Check) { // not required + return nil + } + + if err := o.Check.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("updateAdvisorCheckOk" + "." + "check") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("updateAdvisorCheckOk" + "." + "check") + } + + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckOKBody) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateAdvisorCheckOKBodyCheck AdvisorCheck contains check name and status. +swagger:model UpdateAdvisorCheckOKBodyCheck +*/ +type UpdateAdvisorCheckOKBodyCheck struct { + // Machine-readable name (ID) that is used in expression. + Name string `json:"name,omitempty"` + + // True if that check is enabled. + Enabled bool `json:"enabled,omitempty"` + + // Long human-readable description. + Description string `json:"description,omitempty"` + + // Short human-readable summary. + Summary string `json:"summary,omitempty"` + + // AdvisorCheckInterval represents possible execution interval values for checks. + // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] + Interval *string `json:"interval,omitempty"` + + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*UpdateAdvisorCheckOKBodyCheckQueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` +} + +// Validate validates this update advisor check OK body check +func (o *UpdateAdvisorCheckOKBodyCheck) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateInterval(formats); err != nil { + res = append(res, err) + } + + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var updateAdvisorCheckOkBodyCheckTypeIntervalPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateAdvisorCheckOkBodyCheckTypeIntervalPropEnum = append(updateAdvisorCheckOkBodyCheckTypeIntervalPropEnum, v) + } +} + +const ( + + // UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED captures enum value "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED string = "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + + // UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALSTANDARD captures enum value "ADVISOR_CHECK_INTERVAL_STANDARD" + UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALSTANDARD string = "ADVISOR_CHECK_INTERVAL_STANDARD" + + // UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALFREQUENT captures enum value "ADVISOR_CHECK_INTERVAL_FREQUENT" + UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALFREQUENT string = "ADVISOR_CHECK_INTERVAL_FREQUENT" + + // UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALRARE captures enum value "ADVISOR_CHECK_INTERVAL_RARE" + UpdateAdvisorCheckOKBodyCheckIntervalADVISORCHECKINTERVALRARE string = "ADVISOR_CHECK_INTERVAL_RARE" +) + +// prop value enum +func (o *UpdateAdvisorCheckOKBodyCheck) validateIntervalEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateAdvisorCheckOkBodyCheckTypeIntervalPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *UpdateAdvisorCheckOKBodyCheck) validateInterval(formats strfmt.Registry) error { + if swag.IsZero(o.Interval) { // not required + return nil + } + + // value enum + if err := o.validateIntervalEnum("updateAdvisorCheckOk"+"."+"check"+"."+"interval", "body", *o.Interval); err != nil { + return err + } + + return nil +} + +var updateAdvisorCheckOkBodyCheckTypeTechnologyPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateAdvisorCheckOkBodyCheckTypeTechnologyPropEnum = append(updateAdvisorCheckOkBodyCheckTypeTechnologyPropEnum, v) + } +} + +const ( + + // UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + + // UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + + // UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + + // UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + UpdateAdvisorCheckOKBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" +) + +// prop value enum +func (o *UpdateAdvisorCheckOKBodyCheck) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateAdvisorCheckOkBodyCheckTypeTechnologyPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *UpdateAdvisorCheckOKBodyCheck) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required + return nil + } + + // value enum + if err := o.validateTechnologyEnum("updateAdvisorCheckOk"+"."+"check"+"."+"technology", "body", *o.Technology); err != nil { + return err + } + + return nil +} + +func (o *UpdateAdvisorCheckOKBodyCheck) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("updateAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("updateAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this update advisor check OK body check based on the context it is used +func (o *UpdateAdvisorCheckOKBodyCheck) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckOKBodyCheck) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("updateAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("updateAdvisorCheckOk" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckOKBodyCheck) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckOKBodyCheck) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckOKBodyCheck + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateAdvisorCheckOKBodyCheckQueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model UpdateAdvisorCheckOKBodyCheckQueriesItems0 +*/ +type UpdateAdvisorCheckOKBodyCheckQueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this update advisor check OK body check queries items0 +func (o *UpdateAdvisorCheckOKBodyCheckQueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this update advisor check OK body check queries items0 based on context it is used +func (o *UpdateAdvisorCheckOKBodyCheckQueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckOKBodyCheckQueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckOKBodyCheckQueriesItems0) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckOKBodyCheckQueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateAdvisorCheckParamsBodyCheck AdvisorCheck contains check name and status. +swagger:model UpdateAdvisorCheckParamsBodyCheck +*/ +type UpdateAdvisorCheckParamsBodyCheck struct { + // Machine-readable name (ID) that is used in expression. + Name string `json:"name,omitempty"` + + // True if that check is enabled. + Enabled bool `json:"enabled,omitempty"` + + // Long human-readable description. + Description string `json:"description,omitempty"` + + // Short human-readable summary. + Summary string `json:"summary,omitempty"` + + // AdvisorCheckInterval represents possible execution interval values for checks. + // Enum: ["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"] + Interval *string `json:"interval,omitempty"` + + // technology + // Enum: ["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"] + Technology *string `json:"technology,omitempty"` + + // Category (top-level grouping). + Category string `json:"category,omitempty"` + + // Subcategory (second-level grouping within a category). + Subcategory string `json:"subcategory,omitempty"` + + // True if the check is user-authored (editable/deletable); false for Percona-shipped checks. + UserDefined bool `json:"user_defined,omitempty"` + + // Data-collection queries. Populated by Get/Create/Update; may be empty in list responses. + Queries []*UpdateAdvisorCheckParamsBodyCheckQueriesItems0 `json:"queries"` + + // Starlark source script. Populated by Get/Create/Update; may be empty in list responses. + Script string `json:"script,omitempty"` + + // IDs of services for which this check is disabled. + DisabledServiceIds []string `json:"disabled_service_ids"` +} + +// Validate validates this update advisor check params body check +func (o *UpdateAdvisorCheckParamsBodyCheck) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateInterval(formats); err != nil { + res = append(res, err) + } + + if err := o.validateTechnology(formats); err != nil { + res = append(res, err) + } + + if err := o.validateQueries(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var updateAdvisorCheckParamsBodyCheckTypeIntervalPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_INTERVAL_UNSPECIFIED","ADVISOR_CHECK_INTERVAL_STANDARD","ADVISOR_CHECK_INTERVAL_FREQUENT","ADVISOR_CHECK_INTERVAL_RARE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateAdvisorCheckParamsBodyCheckTypeIntervalPropEnum = append(updateAdvisorCheckParamsBodyCheckTypeIntervalPropEnum, v) + } +} + +const ( + + // UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED captures enum value "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALUNSPECIFIED string = "ADVISOR_CHECK_INTERVAL_UNSPECIFIED" + + // UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALSTANDARD captures enum value "ADVISOR_CHECK_INTERVAL_STANDARD" + UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALSTANDARD string = "ADVISOR_CHECK_INTERVAL_STANDARD" + + // UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALFREQUENT captures enum value "ADVISOR_CHECK_INTERVAL_FREQUENT" + UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALFREQUENT string = "ADVISOR_CHECK_INTERVAL_FREQUENT" + + // UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALRARE captures enum value "ADVISOR_CHECK_INTERVAL_RARE" + UpdateAdvisorCheckParamsBodyCheckIntervalADVISORCHECKINTERVALRARE string = "ADVISOR_CHECK_INTERVAL_RARE" +) + +// prop value enum +func (o *UpdateAdvisorCheckParamsBodyCheck) validateIntervalEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateAdvisorCheckParamsBodyCheckTypeIntervalPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *UpdateAdvisorCheckParamsBodyCheck) validateInterval(formats strfmt.Registry) error { + if swag.IsZero(o.Interval) { // not required + return nil + } + + // value enum + if err := o.validateIntervalEnum("body"+"."+"check"+"."+"interval", "body", *o.Interval); err != nil { + return err + } + + return nil +} + +var updateAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED","ADVISOR_CHECK_TECHNOLOGY_MYSQL","ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL","ADVISOR_CHECK_TECHNOLOGY_MONGODB"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum = append(updateAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum, v) + } +} + +const ( + + // UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED captures enum value "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYUNSPECIFIED string = "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED" + + // UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMYSQL string = "ADVISOR_CHECK_TECHNOLOGY_MYSQL" + + // UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL captures enum value "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYPOSTGRESQL string = "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL" + + // UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB captures enum value "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + UpdateAdvisorCheckParamsBodyCheckTechnologyADVISORCHECKTECHNOLOGYMONGODB string = "ADVISOR_CHECK_TECHNOLOGY_MONGODB" +) + +// prop value enum +func (o *UpdateAdvisorCheckParamsBodyCheck) validateTechnologyEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateAdvisorCheckParamsBodyCheckTypeTechnologyPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *UpdateAdvisorCheckParamsBodyCheck) validateTechnology(formats strfmt.Registry) error { + if swag.IsZero(o.Technology) { // not required + return nil + } + + // value enum + if err := o.validateTechnologyEnum("body"+"."+"check"+"."+"technology", "body", *o.Technology); err != nil { + return err + } + + return nil +} + +func (o *UpdateAdvisorCheckParamsBodyCheck) validateQueries(formats strfmt.Registry) error { + if swag.IsZero(o.Queries) { // not required + return nil + } + + for i := 0; i < len(o.Queries); i++ { + if swag.IsZero(o.Queries[i]) { // not required + continue + } + + if o.Queries[i] != nil { + if err := o.Queries[i].Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + + } + + return nil +} + +// ContextValidate validate this update advisor check params body check based on the context it is used +func (o *UpdateAdvisorCheckParamsBodyCheck) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := o.contextValidateQueries(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateAdvisorCheckParamsBodyCheck) contextValidateQueries(ctx context.Context, formats strfmt.Registry) error { + for i := 0; i < len(o.Queries); i++ { + if o.Queries[i] != nil { + + if swag.IsZero(o.Queries[i]) { // not required + return nil + } + + if err := o.Queries[i].ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "check" + "." + "queries" + "." + strconv.Itoa(i)) + } + + return err + } + } + } + + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckParamsBodyCheck) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckParamsBodyCheck) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckParamsBodyCheck + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + +/* +UpdateAdvisorCheckParamsBodyCheckQueriesItems0 AdvisorCheckQuery is a single data-collection query of an advisor check. +swagger:model UpdateAdvisorCheckParamsBodyCheckQueriesItems0 +*/ +type UpdateAdvisorCheckParamsBodyCheckQueriesItems0 struct { + // Query type, e.g. "MYSQL_SHOW", "POSTGRESQL_SELECT", "METRICS_RANGE". + Type string `json:"type,omitempty"` + + // Query text (may be empty for parameterless types such as MYSQL_SHOW). + Query string `json:"query,omitempty"` + + // Optional query parameters (e.g. range/step for metrics range queries). + Parameters map[string]string `json:"parameters,omitempty"` +} + +// Validate validates this update advisor check params body check queries items0 +func (o *UpdateAdvisorCheckParamsBodyCheckQueriesItems0) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this update advisor check params body check queries items0 based on context it is used +func (o *UpdateAdvisorCheckParamsBodyCheckQueriesItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *UpdateAdvisorCheckParamsBodyCheckQueriesItems0) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *UpdateAdvisorCheckParamsBodyCheckQueriesItems0) UnmarshalBinary(b []byte) error { + var res UpdateAdvisorCheckParamsBodyCheckQueriesItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/api/advisors/v1/json/v1.json b/api/advisors/v1/json/v1.json index c14828b864b..98b76dd5e28 100644 --- a/api/advisors/v1/json/v1.json +++ b/api/advisors/v1/json/v1.json @@ -35,30 +35,35 @@ "type": "object", "properties": { "name": { - "description": "Machine-readable name (ID) that is used in expression.", + "description": "Deprecated: no longer populated; an advisor is identified by its category/subcategory pair.", "type": "string", "x-order": 0 }, "description": { - "description": "Long human-readable description.", + "description": "Deprecated: advisor descriptions were removed.", "type": "string", "x-order": 1 }, "summary": { - "description": "Short human-readable summary.", + "description": "Deprecated: use subcategory instead.", "type": "string", "x-order": 2 }, "comment": { - "description": "Comment.", + "description": "Deprecated: no longer populated.", "type": "string", "x-order": 3 }, "category": { - "description": "Category.", + "description": "Category (top-level grouping).", "type": "string", "x-order": 4 }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 5 + }, "checks": { "description": "Advisor checks.", "type": "array", @@ -98,20 +103,77 @@ ], "x-order": 4 }, - "family": { + "technology": { "type": "string", - "default": "ADVISOR_CHECK_FAMILY_UNSPECIFIED", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", "enum": [ - "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "ADVISOR_CHECK_FAMILY_MYSQL", - "ADVISOR_CHECK_FAMILY_POSTGRESQL", - "ADVISOR_CHECK_FAMILY_MONGODB" + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ], "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 } } }, - "x-order": 5 + "x-order": 6 } } }, @@ -206,16 +268,73 @@ ], "x-order": 4 }, - "family": { + "technology": { "type": "string", - "default": "ADVISOR_CHECK_FAMILY_UNSPECIFIED", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", "enum": [ - "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "ADVISOR_CHECK_FAMILY_MYSQL", - "ADVISOR_CHECK_FAMILY_POSTGRESQL", - "ADVISOR_CHECK_FAMILY_MONGODB" + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ], "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 } } }, @@ -256,36 +375,131 @@ } } } - } - }, - "/v1/advisors/checks/failed": { - "get": { - "description": "Returns the latest check results for a given service.", + }, + "post": { + "description": "Creates a new user-authored advisor check.", "tags": [ "AdvisorService" ], - "summary": "Get Failed Advisor Checks", - "operationId": "GetFailedChecks", + "summary": "Create Advisor Check", + "operationId": "CreateAdvisorCheck", "parameters": [ { - "type": "integer", - "format": "int32", - "description": "Maximum number of results per page.", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "format": "int32", - "description": "Index of the requested page, starts from 0.", - "name": "page_index", - "in": "query" - }, - { - "type": "string", - "description": "Service ID.", - "name": "service_id", - "in": "query" + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } } ], "responses": { @@ -294,85 +508,112 @@ "schema": { "type": "object", "properties": { - "total_items": { - "description": "Total number of results.", - "type": "integer", - "format": "int32", - "x-order": 0 - }, - "total_pages": { - "description": "Total number of pages.", - "type": "integer", - "format": "int32", - "x-order": 1 - }, - "results": { - "type": "array", - "title": "Check results", - "items": { - "description": "CheckResult represents the check results for a given service.", - "type": "object", - "properties": { - "summary": { - "type": "string", - "x-order": 0 - }, - "description": { - "type": "string", - "x-order": 1 - }, - "severity": { - "description": "Severity represents severity level of the check result or alert.", - "type": "string", - "default": "SEVERITY_UNSPECIFIED", - "enum": [ - "SEVERITY_UNSPECIFIED", - "SEVERITY_EMERGENCY", - "SEVERITY_ALERT", - "SEVERITY_CRITICAL", - "SEVERITY_ERROR", - "SEVERITY_WARNING", - "SEVERITY_NOTICE", - "SEVERITY_INFO", - "SEVERITY_DEBUG" - ], - "x-order": 2 - }, - "labels": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-order": 3 - }, - "read_more_url": { - "description": "URL containing information on how to resolve an issue detected by an Advisor check.", - "type": "string", - "x-order": 4 - }, - "service_name": { - "description": "Name of the monitored service on which the check ran.", - "type": "string", - "x-order": 5 - }, - "service_id": { - "description": "ID of the monitored service on which the check ran.", - "type": "string", - "x-order": 6 + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } }, - "check_name": { - "type": "string", - "title": "Name of the check that failed", - "x-order": 7 + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" }, - "silenced": { - "type": "boolean", - "title": "Silence status of the check result", - "x-order": 8 - } + "x-order": 11 } }, - "x-order": 2 + "x-order": 0 } } } @@ -411,64 +652,138 @@ } } }, - "/v1/advisors/checks:batchChange": { - "post": { - "description": "Enables/disables advisor checks or changes their exec interval.", + "/v1/advisors/checks/{name}": { + "get": { + "description": "Returns a single advisor check by name, including its queries and script.", "tags": [ "AdvisorService" ], - "summary": "Change Advisor Checks", - "operationId": "ChangeAdvisorChecks", + "summary": "Get Advisor Check", + "operationId": "GetAdvisorCheck", "parameters": [ { - "name": "body", - "in": "body", - "required": true, + "type": "string", + "description": "Machine-readable name (ID) of the check.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", "schema": { "type": "object", "properties": { - "params": { - "type": "array", - "items": { - "description": "ChangeAdvisorCheckParams specifies a single check parameters.", - "type": "object", - "properties": { - "name": { - "description": "The name of the check to change.", - "type": "string", - "x-order": 0 + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } }, - "enable": { - "type": "boolean", - "x-nullable": true, - "x-order": 1 + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" }, - "interval": { - "description": "AdvisorCheckInterval represents possible execution interval values for checks.", - "type": "string", - "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", - "enum": [ - "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", - "ADVISOR_CHECK_INTERVAL_STANDARD", - "ADVISOR_CHECK_INTERVAL_FREQUENT", - "ADVISOR_CHECK_INTERVAL_RARE" - ], - "x-order": 2 - } + "x-order": 11 } }, "x-order": 0 } } } - } - ], - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "type": "object" - } }, "default": { "description": "An unexpected error response.", @@ -502,17 +817,22 @@ } } } - } - }, - "/v1/advisors/checks:start": { - "post": { - "description": "Executes Advisor checks and returns when all checks are executed. All available checks will be started if check names aren't specified.", + }, + "put": { + "description": "Updates an existing user-authored advisor check. Percona-shipped checks cannot be modified. A check cannot be renamed: the name in the request body must either be empty or match the name in the path.", "tags": [ "AdvisorService" ], - "summary": "Start Advisor Checks", - "operationId": "StartAdvisorChecks", + "summary": "Update Advisor Check", + "operationId": "UpdateAdvisorCheck", "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check to update.", + "name": "name", + "in": "path", + "required": true + }, { "name": "body", "in": "body", @@ -520,13 +840,1357 @@ "schema": { "type": "object", "properties": { - "names": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + }, + "delete": { + "description": "Deletes a user-authored advisor check. Percona-shipped checks cannot be deleted.", + "tags": [ + "AdvisorService" + ], + "summary": "Delete Advisor Check", + "operationId": "DeleteAdvisorCheck", + "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check to delete.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:batchChange": { + "post": { + "description": "Enables/disables advisor checks or changes their exec interval.", + "tags": [ + "AdvisorService" + ], + "summary": "Change Advisor Checks", + "operationId": "ChangeAdvisorChecks", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "params": { + "type": "array", + "items": { + "description": "ChangeAdvisorCheckParams specifies a single check parameters.", + "type": "object", + "properties": { + "name": { + "description": "The name of the check to change.", + "type": "string", + "x-order": 0 + }, + "enable": { + "type": "boolean", + "x-nullable": true, + "x-order": 1 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 2 + }, + "service_ids": { + "description": "IDs of services to apply the enable/disable to. When set, enable/disable\naffects only the given services instead of the whole check; interval\nchanges are not allowed in the same params entry.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 3 + } + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:start": { + "post": { + "description": "Executes Advisor checks and returns when all checks are executed. All available checks will be started if check names aren't specified.", + "tags": [ + "AdvisorService" + ], + "summary": "Start Advisor Checks", + "operationId": "StartAdvisorChecks", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "names": { "description": "Names of the checks that should be started.", "type": "array", "items": { "type": "string" }, "x-order": 0 + }, + "service_ids": { + "description": "IDs of the services to run the checks against. When empty, the checks run\nagainst every monitored service of a matching technology.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "run_id": { + "description": "ID assigned to this run; all check results produced by it share this run_id.", + "type": "string", + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:test": { + "post": { + "description": "Executes an advisor check definition against a single service without saving the check; results are returned and not persisted.", + "tags": [ + "AdvisorService" + ], + "summary": "Test Advisor Check", + "operationId": "TestAdvisorCheck", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + }, + "service_id": { + "description": "ID of the service to run the check against.", + "type": "string", + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "results": { + "description": "Findings produced by the check script; empty means the check passed.", + "type": "array", + "items": { + "description": "TestAdvisorCheckResult is a single finding produced by a test (dry-run) check execution.", + "type": "object", + "properties": { + "summary": { + "type": "string", + "x-order": 0 + }, + "description": { + "type": "string", + "x-order": 1 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 2 + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 3 + }, + "read_more_url": { + "description": "URL containing information on how to resolve an issue detected by the check.", + "type": "string", + "x-order": 4 + }, + "service_name": { + "description": "Name of the monitored service on which the check ran.", + "type": "string", + "x-order": 5 + }, + "service_id": { + "description": "ID of the monitored service on which the check ran.", + "type": "string", + "x-order": 6 + }, + "check_name": { + "description": "Name of the tested check.", + "type": "string", + "x-order": 7 + } + } + }, + "x-order": 0 + }, + "script_output": { + "description": "Output produced by the script's print() calls, for debugging.", + "type": "string", + "x-order": 1 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:testTargets": { + "get": { + "description": "Lists the services an advisor check of the given technology can be tested against.", + "tags": [ + "AdvisorService" + ], + "summary": "List Advisor Check Test Targets", + "operationId": "ListAdvisorCheckTestTargets", + "parameters": [ + { + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "description": "Technology of the check to be tested; determines the eligible service type.", + "name": "technology", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "targets": { + "description": "Services a check of the requested technology can be tested against.", + "type": "array", + "items": { + "description": "AdvisorCheckTestTarget is a service an advisor check can be tested against.", + "type": "object", + "properties": { + "service_id": { + "description": "ID of the eligible service.", + "type": "string", + "x-order": 0 + }, + "service_name": { + "description": "Name of the eligible service.", + "type": "string", + "x-order": 1 + } + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/insights": { + "get": { + "description": "Returns the history of Advisor check results (insights), including their outcomes.", + "tags": [ + "AdvisorService" + ], + "summary": "List Advisor Insights", + "operationId": "ListInsights", + "parameters": [ + { + "type": "integer", + "format": "int32", + "description": "Maximum number of results per page.", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "Index of the requested page, starts from 0.", + "name": "page_index", + "in": "query" + }, + { + "type": "string", + "description": "Filter by service ID.", + "name": "service_id", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "description": "Filter by outcome.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "name": "status", + "in": "query" + }, + { + "type": "boolean", + "description": "Filter by read state.", + "name": "is_read", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only results recorded at or after this time.", + "name": "from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only results recorded at or before this time.", + "name": "to", + "in": "query" + }, + { + "type": "string", + "description": "Filter by service name (partial, case-insensitive match).", + "name": "service_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by node name (partial, case-insensitive match).", + "name": "node_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by advisor category.", + "name": "category", + "in": "query" + }, + { + "type": "string", + "description": "Filter by check name.", + "name": "check_name", + "in": "query" + }, + { + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "description": "Filter by severity.", + "name": "severity", + "in": "query" + }, + { + "type": "string", + "description": "Filter by run ID.", + "name": "run_id", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "description": "Filter by the actor that initiated the run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "name": "triggered_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "total_items": { + "description": "Total number of results.", + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "total_pages": { + "description": "Total number of pages.", + "type": "integer", + "format": "int32", + "x-order": 1 + }, + "results": { + "description": "Insight records.", + "type": "array", + "items": { + "description": "Insight represents a single persisted Advisor check run against a service.", + "type": "object", + "properties": { + "id": { + "description": "Unique identifier of the history record.", + "type": "string", + "x-order": 0 + }, + "run_id": { + "description": "ID of the run this result belongs to; all results produced by one execution share it.", + "type": "string", + "x-order": 1 + }, + "check_name": { + "description": "Name of the check that ran.", + "type": "string", + "x-order": 2 + }, + "category": { + "description": "Category the check belongs to (top-level grouping).", + "type": "string", + "x-order": 3 + }, + "subcategory": { + "description": "Subcategory the check belongs to (second-level grouping within a category).", + "type": "string", + "x-order": 4 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 5 + }, + "service_id": { + "description": "ID of the monitored service on which the check ran.", + "type": "string", + "x-order": 6 + }, + "service_name": { + "description": "Name of the monitored service on which the check ran.", + "type": "string", + "x-order": 7 + }, + "service_type": { + "description": "Type of the monitored service on which the check ran.", + "type": "string", + "x-order": 8 + }, + "node_id": { + "description": "ID of the node the service runs on.", + "type": "string", + "x-order": 9 + }, + "node_name": { + "description": "Name of the node the service runs on.", + "type": "string", + "x-order": 10 + }, + "environment": { + "description": "Environment of the monitored service on which the check ran.", + "type": "string", + "x-order": 11 + }, + "cluster": { + "description": "Cluster of the monitored service on which the check ran.", + "type": "string", + "x-order": 12 + }, + "replication_set": { + "description": "Replication set of the monitored service on which the check ran.", + "type": "string", + "x-order": 13 + }, + "status": { + "description": "AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "x-order": 14 + }, + "summary": { + "description": "Short human-readable summary of the result.", + "type": "string", + "x-order": 15 + }, + "description": { + "description": "Long human-readable description of the result.", + "type": "string", + "x-order": 16 + }, + "read_more_url": { + "description": "URL containing information on how to resolve a detected issue.", + "type": "string", + "x-order": 17 + }, + "outcome": { + "description": "Output returned by the check run (finding details or execution error).", + "type": "string", + "x-order": 18 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 19 + }, + "labels": { + "description": "Result labels.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 20 + }, + "checked_at": { + "description": "Time when the check ran.", + "type": "string", + "format": "date-time", + "x-order": 21 + }, + "is_read": { + "description": "Whether the result has been marked as read.", + "type": "boolean", + "x-order": 22 + }, + "triggered_by": { + "description": "AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "x-order": 23 + }, + "region": { + "description": "Cloud region of the node the service runs on, empty when not applicable.", + "type": "string", + "x-order": 24 + }, + "az": { + "description": "Cloud availability zone of the node the service runs on, empty when not applicable.", + "type": "string", + "x-order": 25 + } + } + }, + "x-order": 2 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/insights:filterValues": { + "get": { + "description": "Returns the distinct service and node names present in the Advisor insights, for populating filter dropdowns.", + "tags": [ + "AdvisorService" + ], + "summary": "List Advisor Insights Filter Values", + "operationId": "ListInsightsFilterValues", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "service_names": { + "description": "Distinct service names present in the check results history, sorted alphabetically.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "node_names": { + "description": "Distinct node names present in the check results history, sorted alphabetically.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 1 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/insights:markRead": { + "post": { + "description": "Sets the read state on the specified Advisor insights. Set is_read to false to mark them unread.", + "tags": [ + "AdvisorService" + ], + "summary": "Mark Advisor Insights Read", + "operationId": "MarkInsightsRead", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "ids": { + "description": "IDs of the insights to update. Takes precedence over filters.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "is_read": { + "description": "Read state to set on the records.", + "type": "boolean", + "x-order": 1 + }, + "filters": { + "description": "InsightsFilters select Advisor insights by attribute; all present fields must match.", + "type": "object", + "properties": { + "check_name": { + "description": "Filter by check name.", + "type": "string", + "x-order": 0 + }, + "service_name": { + "description": "Filter by service name (partial, case-insensitive match).", + "type": "string", + "x-order": 1 + }, + "node_name": { + "description": "Filter by node name (partial, case-insensitive match).", + "type": "string", + "x-order": 2 + }, + "category": { + "description": "Filter by advisor category.", + "type": "string", + "x-order": 3 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-nullable": true, + "x-order": 4 + }, + "status": { + "description": "AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "x-nullable": true, + "x-order": 5 + }, + "is_read": { + "description": "Filter by read state.", + "type": "boolean", + "x-nullable": true, + "x-order": 6 + }, + "run_id": { + "description": "Filter by run ID.", + "type": "string", + "x-order": 7 + } + }, + "x-order": 2 } } } @@ -573,85 +2237,169 @@ } } }, - "/v1/advisors/failedServices": { + "/v1/advisors/runs": { "get": { - "description": "Returns a list of services with failed checks and a summary of check results.", + "description": "Returns the chronological history of Advisor check executions with their totals.", "tags": [ "AdvisorService" ], - "summary": "List Failed Services", - "operationId": "ListFailedServices", + "summary": "List Advisor Runs", + "operationId": "ListRuns", + "parameters": [ + { + "type": "integer", + "format": "int32", + "description": "Maximum number of results per page.", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "Index of the requested page, starts from 0.", + "name": "page_index", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "description": "Filter by the actor that initiated the run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "name": "triggered_by", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only runs started at or after this time.", + "name": "from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only runs started at or before this time.", + "name": "to", + "in": "query" + } + ], "responses": { "200": { "description": "A successful response.", "schema": { "type": "object", "properties": { - "result": { + "total_items": { + "description": "Total number of results.", + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "total_pages": { + "description": "Total number of pages.", + "type": "integer", + "format": "int32", + "x-order": 1 + }, + "results": { + "description": "Runs, most recently started first.", "type": "array", "items": { - "description": "CheckResultSummary is a summary of check results.", + "description": "AdvisorRun is a single execution of Advisor checks. Its totals are recorded on\ncompletion, so they stay accurate after the run's insights have been pruned.", "type": "object", "properties": { - "service_name": { + "id": { + "description": "ID shared by every insight the run produced.", "type": "string", "x-order": 0 }, - "service_id": { + "triggered_by": { + "description": "AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], "x-order": 1 }, - "emergency_count": { - "description": "Number of failed checks for this service with severity level \"EMERGENCY\".", - "type": "integer", - "format": "int64", + "started_at": { + "description": "When the run began.", + "type": "string", + "format": "date-time", "x-order": 2 }, - "alert_count": { - "description": "Number of failed checks for this service with severity level \"ALERT\".", - "type": "integer", - "format": "int64", + "finished_at": { + "description": "When the run completed; unset while it is still running.", + "type": "string", + "format": "date-time", "x-order": 3 }, - "critical_count": { - "description": "Number of failed checks for this service with severity level \"CRITICAL\".", + "checks_count": { + "description": "Number of distinct checks the run executed.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 4 }, - "error_count": { - "description": "Number of failed checks for this service with severity level \"ERROR\".", + "services_count": { + "description": "Number of distinct services the run covered.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 5 }, - "warning_count": { - "description": "Number of failed checks for this service with severity level \"WARNING\".", + "findings_count": { + "description": "Number of findings, i.e. checks that detected an issue.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 6 }, - "notice_count": { - "description": "Number of failed checks for this service with severity level \"NOTICE\".", + "errors_count": { + "description": "Number of checks that could not be executed at all.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 7 }, - "info_count": { - "description": "Number of failed checks for this service with severity level \"INFO\".", - "type": "integer", - "format": "int64", + "severity_counts": { + "description": "Number of findings per severity, most severe first. A repeated field rather\nthan a map so severity stays a typed enum instead of a free-form key.", + "type": "array", + "items": { + "description": "SeverityCount is the number of findings a run produced at a single severity.", + "type": "object", + "properties": { + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 0 + }, + "count": { + "type": "integer", + "format": "int32", + "x-order": 1 + } + } + }, "x-order": 8 - }, - "debug_count": { - "description": "Number of failed checks for this service with severity level \"DEBUG\".", - "type": "integer", - "format": "int64", - "x-order": 9 } } }, - "x-order": 0 + "x-order": 2 } } } diff --git a/api/alerting/v1/alerting.pb.go b/api/alerting/v1/alerting.pb.go index 2b627924970..e3fa56152fb 100644 --- a/api/alerting/v1/alerting.pb.go +++ b/api/alerting/v1/alerting.pb.go @@ -35,7 +35,7 @@ const ( TemplateSource_TEMPLATE_SOURCE_UNSPECIFIED TemplateSource = 0 // Template that is shipped with PMM Server releases. TemplateSource_TEMPLATE_SOURCE_BUILT_IN TemplateSource = 1 - // Template that is downloaded from check.percona.com. + // Template that is downloaded from check.percona.com. Deprecated. TemplateSource_TEMPLATE_SOURCE_SAAS TemplateSource = 2 // Templated loaded from user-suplied file. TemplateSource_TEMPLATE_SOURCE_USER_FILE TemplateSource = 3 diff --git a/api/alerting/v1/alerting.proto b/api/alerting/v1/alerting.proto index 1b7dd727acb..f84d37fc432 100644 --- a/api/alerting/v1/alerting.proto +++ b/api/alerting/v1/alerting.proto @@ -56,7 +56,7 @@ enum TemplateSource { TEMPLATE_SOURCE_UNSPECIFIED = 0; // Template that is shipped with PMM Server releases. TEMPLATE_SOURCE_BUILT_IN = 1; - // Template that is downloaded from check.percona.com. + // Template that is downloaded from check.percona.com. Deprecated. TEMPLATE_SOURCE_SAAS = 2; // Templated loaded from user-suplied file. TEMPLATE_SOURCE_USER_FILE = 3; diff --git a/api/alerting/v1/json/client/alerting_service/list_templates_responses.go b/api/alerting/v1/json/client/alerting_service/list_templates_responses.go index b263d1e67e0..45d3250ac7e 100644 --- a/api/alerting/v1/json/client/alerting_service/list_templates_responses.go +++ b/api/alerting/v1/json/client/alerting_service/list_templates_responses.go @@ -563,7 +563,7 @@ type ListTemplatesOKBodyTemplatesItems0 struct { // TemplateSource defines template source. // // - TEMPLATE_SOURCE_BUILT_IN: Template that is shipped with PMM Server releases. - // - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com. + // - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com. Deprecated. // - TEMPLATE_SOURCE_USER_FILE: Templated loaded from user-suplied file. // - TEMPLATE_SOURCE_USER_API: Templated created via API. // Enum: ["TEMPLATE_SOURCE_UNSPECIFIED","TEMPLATE_SOURCE_BUILT_IN","TEMPLATE_SOURCE_SAAS","TEMPLATE_SOURCE_USER_FILE","TEMPLATE_SOURCE_USER_API"] diff --git a/api/alerting/v1/json/v1.json b/api/alerting/v1/json/v1.json index 7f0ad70f290..ac7350db994 100644 --- a/api/alerting/v1/json/v1.json +++ b/api/alerting/v1/json/v1.json @@ -409,7 +409,7 @@ "x-order": 7 }, "source": { - "description": "TemplateSource defines template source.\n\n - TEMPLATE_SOURCE_BUILT_IN: Template that is shipped with PMM Server releases.\n - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com.\n - TEMPLATE_SOURCE_USER_FILE: Templated loaded from user-suplied file.\n - TEMPLATE_SOURCE_USER_API: Templated created via API.", + "description": "TemplateSource defines template source.\n\n - TEMPLATE_SOURCE_BUILT_IN: Template that is shipped with PMM Server releases.\n - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com. Deprecated.\n - TEMPLATE_SOURCE_USER_FILE: Templated loaded from user-suplied file.\n - TEMPLATE_SOURCE_USER_API: Templated created via API.", "type": "string", "default": "TEMPLATE_SOURCE_UNSPECIFIED", "enum": [ diff --git a/api/descriptor.bin b/api/descriptor.bin index e66dfa7c6f4..a869d01ffca 100644 Binary files a/api/descriptor.bin and b/api/descriptor.bin differ diff --git a/api/server/v1/json/client/server_service/change_settings_responses.go b/api/server/v1/json/client/server_service/change_settings_responses.go index 6aff772de31..0ac7aa65876 100644 --- a/api/server/v1/json/client/server_service/change_settings_responses.go +++ b/api/server/v1/json/client/server_service/change_settings_responses.go @@ -14,6 +14,7 @@ import ( "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + "github.com/go-openapi/validate" ) // ChangeSettingsReader is a Reader for the ChangeSettings structure. @@ -222,6 +223,19 @@ type ChangeSettingsBody struct { // Enable Query Analytics for PMM's internal PG database. EnableInternalPgQAN *bool `json:"enable_internal_pg_qan,omitempty"` + // A number of full days for Advisor check results history retention, i.e. a multiple of 24h: 2592000s, 43200m, 720h. + AdvisorHistoryRetention string `json:"advisor_history_retention,omitempty"` + + // Enable Advisor email notifications. + EnableAdvisorNotifications *bool `json:"enable_advisor_notifications,omitempty"` + + // Severity represents severity level of the check result or alert. + // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] + AdvisorNotificationSeverityThreshold *string `json:"advisor_notification_severity_threshold,omitempty"` + + // advisor notification email addresses + AdvisorNotificationEmailAddresses *ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses `json:"advisor_notification_email_addresses,omitempty"` + // advisor run intervals AdvisorRunIntervals *ChangeSettingsParamsBodyAdvisorRunIntervals `json:"advisor_run_intervals,omitempty"` @@ -236,6 +250,14 @@ type ChangeSettingsBody struct { func (o *ChangeSettingsBody) Validate(formats strfmt.Registry) error { var res []error + if err := o.validateAdvisorNotificationSeverityThreshold(formats); err != nil { + res = append(res, err) + } + + if err := o.validateAdvisorNotificationEmailAddresses(formats); err != nil { + res = append(res, err) + } + if err := o.validateAdvisorRunIntervals(formats); err != nil { res = append(res, err) } @@ -254,6 +276,92 @@ func (o *ChangeSettingsBody) Validate(formats strfmt.Registry) error { return nil } +var changeSettingsBodyTypeAdvisorNotificationSeverityThresholdPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + changeSettingsBodyTypeAdvisorNotificationSeverityThresholdPropEnum = append(changeSettingsBodyTypeAdvisorNotificationSeverityThresholdPropEnum, v) + } +} + +const ( + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYALERT captures enum value "SEVERITY_ALERT" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYALERT string = "SEVERITY_ALERT" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYCRITICAL string = "SEVERITY_CRITICAL" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYERROR captures enum value "SEVERITY_ERROR" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYERROR string = "SEVERITY_ERROR" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYWARNING captures enum value "SEVERITY_WARNING" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYWARNING string = "SEVERITY_WARNING" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYNOTICE captures enum value "SEVERITY_NOTICE" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYNOTICE string = "SEVERITY_NOTICE" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYINFO captures enum value "SEVERITY_INFO" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYINFO string = "SEVERITY_INFO" + + // ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYDEBUG captures enum value "SEVERITY_DEBUG" + ChangeSettingsBodyAdvisorNotificationSeverityThresholdSEVERITYDEBUG string = "SEVERITY_DEBUG" +) + +// prop value enum +func (o *ChangeSettingsBody) validateAdvisorNotificationSeverityThresholdEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, changeSettingsBodyTypeAdvisorNotificationSeverityThresholdPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ChangeSettingsBody) validateAdvisorNotificationSeverityThreshold(formats strfmt.Registry) error { + if swag.IsZero(o.AdvisorNotificationSeverityThreshold) { // not required + return nil + } + + // value enum + if err := o.validateAdvisorNotificationSeverityThresholdEnum("body"+"."+"advisor_notification_severity_threshold", "body", *o.AdvisorNotificationSeverityThreshold); err != nil { + return err + } + + return nil +} + +func (o *ChangeSettingsBody) validateAdvisorNotificationEmailAddresses(formats strfmt.Registry) error { + if swag.IsZero(o.AdvisorNotificationEmailAddresses) { // not required + return nil + } + + if o.AdvisorNotificationEmailAddresses != nil { + if err := o.AdvisorNotificationEmailAddresses.Validate(formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "advisor_notification_email_addresses") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "advisor_notification_email_addresses") + } + + return err + } + } + + return nil +} + func (o *ChangeSettingsBody) validateAdvisorRunIntervals(formats strfmt.Registry) error { if swag.IsZero(o.AdvisorRunIntervals) { // not required return nil @@ -327,6 +435,10 @@ func (o *ChangeSettingsBody) validateMetricsResolutions(formats strfmt.Registry) func (o *ChangeSettingsBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error + if err := o.contextValidateAdvisorNotificationEmailAddresses(ctx, formats); err != nil { + res = append(res, err) + } + if err := o.contextValidateAdvisorRunIntervals(ctx, formats); err != nil { res = append(res, err) } @@ -345,6 +457,30 @@ func (o *ChangeSettingsBody) ContextValidate(ctx context.Context, formats strfmt return nil } +func (o *ChangeSettingsBody) contextValidateAdvisorNotificationEmailAddresses(ctx context.Context, formats strfmt.Registry) error { + if o.AdvisorNotificationEmailAddresses != nil { + + if swag.IsZero(o.AdvisorNotificationEmailAddresses) { // not required + return nil + } + + if err := o.AdvisorNotificationEmailAddresses.ContextValidate(ctx, formats); err != nil { + ve := new(errors.Validation) + if stderrors.As(err, &ve) { + return ve.ValidateName("body" + "." + "advisor_notification_email_addresses") + } + ce := new(errors.CompositeError) + if stderrors.As(err, &ce) { + return ce.ValidateName("body" + "." + "advisor_notification_email_addresses") + } + + return err + } + } + + return nil +} + func (o *ChangeSettingsBody) contextValidateAdvisorRunIntervals(ctx context.Context, formats strfmt.Registry) error { if o.AdvisorRunIntervals != nil { @@ -983,6 +1119,19 @@ type ChangeSettingsOKBodySettings struct { // True if Query Analytics for PMM's internal PG database is enabled. EnableInternalPgQAN bool `json:"enable_internal_pg_qan,omitempty"` + // Advisor check results history retention. + AdvisorHistoryRetention string `json:"advisor_history_retention,omitempty"` + + // True if Advisor email notifications are enabled. + AdvisorNotificationsEnabled bool `json:"advisor_notifications_enabled,omitempty"` + + // Severity represents severity level of the check result or alert. + // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] + AdvisorNotificationSeverityThreshold *string `json:"advisor_notification_severity_threshold,omitempty"` + + // Email addresses Advisor notifications are sent to. + AdvisorNotificationEmailAddresses []string `json:"advisor_notification_email_addresses"` + // advisor run intervals AdvisorRunIntervals *ChangeSettingsOKBodySettingsAdvisorRunIntervals `json:"advisor_run_intervals,omitempty"` @@ -994,6 +1143,10 @@ type ChangeSettingsOKBodySettings struct { func (o *ChangeSettingsOKBodySettings) Validate(formats strfmt.Registry) error { var res []error + if err := o.validateAdvisorNotificationSeverityThreshold(formats); err != nil { + res = append(res, err) + } + if err := o.validateAdvisorRunIntervals(formats); err != nil { res = append(res, err) } @@ -1008,6 +1161,69 @@ func (o *ChangeSettingsOKBodySettings) Validate(formats strfmt.Registry) error { return nil } +var changeSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + changeSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum = append(changeSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum, v) + } +} + +const ( + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYALERT captures enum value "SEVERITY_ALERT" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYALERT string = "SEVERITY_ALERT" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYCRITICAL string = "SEVERITY_CRITICAL" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYERROR captures enum value "SEVERITY_ERROR" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYERROR string = "SEVERITY_ERROR" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYWARNING captures enum value "SEVERITY_WARNING" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYWARNING string = "SEVERITY_WARNING" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYNOTICE captures enum value "SEVERITY_NOTICE" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYNOTICE string = "SEVERITY_NOTICE" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYINFO captures enum value "SEVERITY_INFO" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYINFO string = "SEVERITY_INFO" + + // ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYDEBUG captures enum value "SEVERITY_DEBUG" + ChangeSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYDEBUG string = "SEVERITY_DEBUG" +) + +// prop value enum +func (o *ChangeSettingsOKBodySettings) validateAdvisorNotificationSeverityThresholdEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, changeSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *ChangeSettingsOKBodySettings) validateAdvisorNotificationSeverityThreshold(formats strfmt.Registry) error { + if swag.IsZero(o.AdvisorNotificationSeverityThreshold) { // not required + return nil + } + + // value enum + if err := o.validateAdvisorNotificationSeverityThresholdEnum("changeSettingsOk"+"."+"settings"+"."+"advisor_notification_severity_threshold", "body", *o.AdvisorNotificationSeverityThreshold); err != nil { + return err + } + + return nil +} + func (o *ChangeSettingsOKBodySettings) validateAdvisorRunIntervals(formats strfmt.Registry) error { if swag.IsZero(o.AdvisorRunIntervals) { // not required return nil @@ -1261,6 +1477,43 @@ func (o *ChangeSettingsParamsBodyAWSPartitions) UnmarshalBinary(b []byte) error return nil } +/* +ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses A wrapper for a string array. This type allows to distinguish between an empty array and a null value. +swagger:model ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses +*/ +type ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses struct { + // values + Values []string `json:"values"` +} + +// Validate validates this change settings params body advisor notification email addresses +func (o *ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this change settings params body advisor notification email addresses based on context it is used +func (o *ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses) UnmarshalBinary(b []byte) error { + var res ChangeSettingsParamsBodyAdvisorNotificationEmailAddresses + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} + /* ChangeSettingsParamsBodyAdvisorRunIntervals AdvisorRunIntervals represents intervals between each run of Advisor checks. swagger:model ChangeSettingsParamsBodyAdvisorRunIntervals diff --git a/api/server/v1/json/client/server_service/get_settings_responses.go b/api/server/v1/json/client/server_service/get_settings_responses.go index c588bb2462f..b59ce6d564d 100644 --- a/api/server/v1/json/client/server_service/get_settings_responses.go +++ b/api/server/v1/json/client/server_service/get_settings_responses.go @@ -14,6 +14,7 @@ import ( "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + "github.com/go-openapi/validate" ) // GetSettingsReader is a Reader for the GetSettings structure. @@ -732,6 +733,19 @@ type GetSettingsOKBodySettings struct { // True if Query Analytics for PMM's internal PG database is enabled. EnableInternalPgQAN bool `json:"enable_internal_pg_qan,omitempty"` + // Advisor check results history retention. + AdvisorHistoryRetention string `json:"advisor_history_retention,omitempty"` + + // True if Advisor email notifications are enabled. + AdvisorNotificationsEnabled bool `json:"advisor_notifications_enabled,omitempty"` + + // Severity represents severity level of the check result or alert. + // Enum: ["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"] + AdvisorNotificationSeverityThreshold *string `json:"advisor_notification_severity_threshold,omitempty"` + + // Email addresses Advisor notifications are sent to. + AdvisorNotificationEmailAddresses []string `json:"advisor_notification_email_addresses"` + // advisor run intervals AdvisorRunIntervals *GetSettingsOKBodySettingsAdvisorRunIntervals `json:"advisor_run_intervals,omitempty"` @@ -743,6 +757,10 @@ type GetSettingsOKBodySettings struct { func (o *GetSettingsOKBodySettings) Validate(formats strfmt.Registry) error { var res []error + if err := o.validateAdvisorNotificationSeverityThreshold(formats); err != nil { + res = append(res, err) + } + if err := o.validateAdvisorRunIntervals(formats); err != nil { res = append(res, err) } @@ -757,6 +775,69 @@ func (o *GetSettingsOKBodySettings) Validate(formats strfmt.Registry) error { return nil } +var getSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum []any + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["SEVERITY_UNSPECIFIED","SEVERITY_EMERGENCY","SEVERITY_ALERT","SEVERITY_CRITICAL","SEVERITY_ERROR","SEVERITY_WARNING","SEVERITY_NOTICE","SEVERITY_INFO","SEVERITY_DEBUG"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + getSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum = append(getSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum, v) + } +} + +const ( + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYUNSPECIFIED captures enum value "SEVERITY_UNSPECIFIED" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYUNSPECIFIED string = "SEVERITY_UNSPECIFIED" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYEMERGENCY captures enum value "SEVERITY_EMERGENCY" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYEMERGENCY string = "SEVERITY_EMERGENCY" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYALERT captures enum value "SEVERITY_ALERT" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYALERT string = "SEVERITY_ALERT" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYCRITICAL captures enum value "SEVERITY_CRITICAL" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYCRITICAL string = "SEVERITY_CRITICAL" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYERROR captures enum value "SEVERITY_ERROR" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYERROR string = "SEVERITY_ERROR" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYWARNING captures enum value "SEVERITY_WARNING" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYWARNING string = "SEVERITY_WARNING" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYNOTICE captures enum value "SEVERITY_NOTICE" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYNOTICE string = "SEVERITY_NOTICE" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYINFO captures enum value "SEVERITY_INFO" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYINFO string = "SEVERITY_INFO" + + // GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYDEBUG captures enum value "SEVERITY_DEBUG" + GetSettingsOKBodySettingsAdvisorNotificationSeverityThresholdSEVERITYDEBUG string = "SEVERITY_DEBUG" +) + +// prop value enum +func (o *GetSettingsOKBodySettings) validateAdvisorNotificationSeverityThresholdEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, getSettingsOkBodySettingsTypeAdvisorNotificationSeverityThresholdPropEnum, true); err != nil { + return err + } + return nil +} + +func (o *GetSettingsOKBodySettings) validateAdvisorNotificationSeverityThreshold(formats strfmt.Registry) error { + if swag.IsZero(o.AdvisorNotificationSeverityThreshold) { // not required + return nil + } + + // value enum + if err := o.validateAdvisorNotificationSeverityThresholdEnum("getSettingsOk"+"."+"settings"+"."+"advisor_notification_severity_threshold", "body", *o.AdvisorNotificationSeverityThreshold); err != nil { + return err + } + + return nil +} + func (o *GetSettingsOKBodySettings) validateAdvisorRunIntervals(formats strfmt.Registry) error { if swag.IsZero(o.AdvisorRunIntervals) { // not required return nil diff --git a/api/server/v1/json/v1.json b/api/server/v1/json/v1.json index bd36d1ff478..cc7fcdeec84 100644 --- a/api/server/v1/json/v1.json +++ b/api/server/v1/json/v1.json @@ -311,6 +311,41 @@ "description": "True if Query Analytics for PMM's internal PG database is enabled.", "type": "boolean", "x-order": 17 + }, + "advisor_history_retention": { + "description": "Advisor check results history retention.", + "type": "string", + "x-order": 18 + }, + "advisor_notifications_enabled": { + "description": "True if Advisor email notifications are enabled.", + "type": "boolean", + "x-order": 19 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 20 + }, + "advisor_notification_email_addresses": { + "description": "Email addresses Advisor notifications are sent to.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 21 } }, "x-order": 0 @@ -488,6 +523,49 @@ "type": "boolean", "x-nullable": true, "x-order": 13 + }, + "advisor_history_retention": { + "description": "A number of full days for Advisor check results history retention, i.e. a multiple of 24h: 2592000s, 43200m, 720h.", + "type": "string", + "x-order": 14 + }, + "enable_advisor_notifications": { + "description": "Enable Advisor email notifications.", + "type": "boolean", + "x-nullable": true, + "x-order": 15 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 16 + }, + "advisor_notification_email_addresses": { + "description": "A wrapper for a string array. This type allows to distinguish between an empty array and a null value.", + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + } + }, + "x-nullable": true, + "x-order": 17 } } } @@ -629,6 +707,41 @@ "description": "True if Query Analytics for PMM's internal PG database is enabled.", "type": "boolean", "x-order": 17 + }, + "advisor_history_retention": { + "description": "Advisor check results history retention.", + "type": "string", + "x-order": 18 + }, + "advisor_notifications_enabled": { + "description": "True if Advisor email notifications are enabled.", + "type": "boolean", + "x-order": 19 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 20 + }, + "advisor_notification_email_addresses": { + "description": "Email addresses Advisor notifications are sent to.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 21 } }, "x-order": 0 diff --git a/api/server/v1/server.pb.go b/api/server/v1/server.pb.go index a5e3364cd38..d966a8daf34 100644 --- a/api/server/v1/server.pb.go +++ b/api/server/v1/server.pb.go @@ -19,6 +19,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" common "github.com/percona/pmm/api/common" + v1 "github.com/percona/pmm/api/management/v1" ) const ( @@ -883,8 +884,16 @@ type Settings struct { DefaultRoleId uint32 `protobuf:"varint,18,opt,name=default_role_id,json=defaultRoleId,proto3" json:"default_role_id,omitempty"` // True if Query Analytics for PMM's internal PG database is enabled. EnableInternalPgQan bool `protobuf:"varint,19,opt,name=enable_internal_pg_qan,json=enableInternalPgQan,proto3" json:"enable_internal_pg_qan,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Advisor check results history retention. + AdvisorHistoryRetention *durationpb.Duration `protobuf:"bytes,21,opt,name=advisor_history_retention,json=advisorHistoryRetention,proto3" json:"advisor_history_retention,omitempty"` + // True if Advisor email notifications are enabled. + AdvisorNotificationsEnabled bool `protobuf:"varint,22,opt,name=advisor_notifications_enabled,json=advisorNotificationsEnabled,proto3" json:"advisor_notifications_enabled,omitempty"` + // Least-severe level that triggers an Advisor notification. + AdvisorNotificationSeverityThreshold v1.Severity `protobuf:"varint,23,opt,name=advisor_notification_severity_threshold,json=advisorNotificationSeverityThreshold,proto3,enum=management.v1.Severity" json:"advisor_notification_severity_threshold,omitempty"` + // Email addresses Advisor notifications are sent to. + AdvisorNotificationEmailAddresses []string `protobuf:"bytes,24,rep,name=advisor_notification_email_addresses,json=advisorNotificationEmailAddresses,proto3" json:"advisor_notification_email_addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Settings) Reset() { @@ -1045,6 +1054,34 @@ func (x *Settings) GetEnableInternalPgQan() bool { return false } +func (x *Settings) GetAdvisorHistoryRetention() *durationpb.Duration { + if x != nil { + return x.AdvisorHistoryRetention + } + return nil +} + +func (x *Settings) GetAdvisorNotificationsEnabled() bool { + if x != nil { + return x.AdvisorNotificationsEnabled + } + return false +} + +func (x *Settings) GetAdvisorNotificationSeverityThreshold() v1.Severity { + if x != nil { + return x.AdvisorNotificationSeverityThreshold + } + return v1.Severity(0) +} + +func (x *Settings) GetAdvisorNotificationEmailAddresses() []string { + if x != nil { + return x.AdvisorNotificationEmailAddresses + } + return nil +} + // ReadOnlySettings represents a stripped-down version of PMM Server settings that can be accessed by users of all roles. type ReadOnlySettings struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1339,8 +1376,17 @@ type ChangeSettingsRequest struct { EnableAccessControl *bool `protobuf:"varint,13,opt,name=enable_access_control,json=enableAccessControl,proto3,oneof" json:"enable_access_control,omitempty"` // Enable Query Analytics for PMM's internal PG database. EnableInternalPgQan *bool `protobuf:"varint,14,opt,name=enable_internal_pg_qan,json=enableInternalPgQan,proto3,oneof" json:"enable_internal_pg_qan,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // A number of full days for Advisor check results history retention, i.e. a multiple of 24h: 2592000s, 43200m, 720h. + AdvisorHistoryRetention *durationpb.Duration `protobuf:"bytes,16,opt,name=advisor_history_retention,json=advisorHistoryRetention,proto3" json:"advisor_history_retention,omitempty"` + // Enable Advisor email notifications. + EnableAdvisorNotifications *bool `protobuf:"varint,17,opt,name=enable_advisor_notifications,json=enableAdvisorNotifications,proto3,oneof" json:"enable_advisor_notifications,omitempty"` + // Least-severe level that triggers an Advisor notification. + AdvisorNotificationSeverityThreshold v1.Severity `protobuf:"varint,18,opt,name=advisor_notification_severity_threshold,json=advisorNotificationSeverityThreshold,proto3,enum=management.v1.Severity" json:"advisor_notification_severity_threshold,omitempty"` + // Email addresses Advisor notifications are sent to. Unset leaves them unchanged; an empty + // array clears the list. + AdvisorNotificationEmailAddresses *common.StringArray `protobuf:"bytes,20,opt,name=advisor_notification_email_addresses,json=advisorNotificationEmailAddresses,proto3,oneof" json:"advisor_notification_email_addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ChangeSettingsRequest) Reset() { @@ -1471,6 +1517,34 @@ func (x *ChangeSettingsRequest) GetEnableInternalPgQan() bool { return false } +func (x *ChangeSettingsRequest) GetAdvisorHistoryRetention() *durationpb.Duration { + if x != nil { + return x.AdvisorHistoryRetention + } + return nil +} + +func (x *ChangeSettingsRequest) GetEnableAdvisorNotifications() bool { + if x != nil && x.EnableAdvisorNotifications != nil { + return *x.EnableAdvisorNotifications + } + return false +} + +func (x *ChangeSettingsRequest) GetAdvisorNotificationSeverityThreshold() v1.Severity { + if x != nil { + return x.AdvisorNotificationSeverityThreshold + } + return v1.Severity(0) +} + +func (x *ChangeSettingsRequest) GetAdvisorNotificationEmailAddresses() *common.StringArray { + if x != nil { + return x.AdvisorNotificationEmailAddresses + } + return nil +} + type ChangeSettingsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Settings *Settings `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"` @@ -1519,7 +1593,7 @@ var File_server_v1_server_proto protoreflect.FileDescriptor const file_server_v1_server_proto_rawDesc = "" + "\n" + - "\x16server/v1/server.proto\x12\tserver.v1\x1a\x13common/common.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\"\x84\x01\n" + + "\x16server/v1/server.proto\x12\tserver.v1\x1a\x13common/common.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1cmanagement/v1/severity.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\"\x84\x01\n" + "\vVersionInfo\x12\x18\n" + "\aversion\x18\x01 \x01(\tR\aversion\x12!\n" + "\ffull_version\x18\x02 \x01(\tR\vfullVersion\x128\n" + @@ -1563,7 +1637,8 @@ const file_server_v1_server_proto_rawDesc = "" + "\x13AdvisorRunIntervals\x12F\n" + "\x11standard_interval\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\x10standardInterval\x12>\n" + "\rrare_interval\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\frareInterval\x12F\n" + - "\x11frequent_interval\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\x10frequentInterval\"\xbc\a\n" + + "\x11frequent_interval\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\x10frequentInterval\"\x98\n" + + "\n" + "\bSettings\x12'\n" + "\x0fupdates_enabled\x18\x01 \x01(\bR\x0eupdatesEnabled\x12+\n" + "\x11telemetry_enabled\x18\x02 \x01(\bR\x10telemetryEnabled\x12N\n" + @@ -1583,7 +1658,11 @@ const file_server_v1_server_proto_rawDesc = "" + "\x13telemetry_summaries\x18\x10 \x03(\tR\x12telemetrySummaries\x122\n" + "\x15enable_access_control\x18\x11 \x01(\bR\x13enableAccessControl\x12&\n" + "\x0fdefault_role_id\x18\x12 \x01(\rR\rdefaultRoleId\x123\n" + - "\x16enable_internal_pg_qan\x18\x13 \x01(\bR\x13enableInternalPgQanJ\x04\b\x14\x10\x15R\x16update_snooze_duration\"\x8f\x03\n" + + "\x16enable_internal_pg_qan\x18\x13 \x01(\bR\x13enableInternalPgQan\x12U\n" + + "\x19advisor_history_retention\x18\x15 \x01(\v2\x19.google.protobuf.DurationR\x17advisorHistoryRetention\x12B\n" + + "\x1dadvisor_notifications_enabled\x18\x16 \x01(\bR\x1badvisorNotificationsEnabled\x12n\n" + + "'advisor_notification_severity_threshold\x18\x17 \x01(\x0e2\x17.management.v1.SeverityR$advisorNotificationSeverityThreshold\x12O\n" + + "$advisor_notification_email_addresses\x18\x18 \x03(\tR!advisorNotificationEmailAddressesJ\x04\b\x14\x10\x15R\x16update_snooze_duration\"\x8f\x03\n" + "\x10ReadOnlySettings\x12'\n" + "\x0fupdates_enabled\x18\x01 \x01(\bR\x0eupdatesEnabled\x12+\n" + "\x11telemetry_enabled\x18\x02 \x01(\bR\x10telemetryEnabled\x12'\n" + @@ -1598,7 +1677,7 @@ const file_server_v1_server_proto_rawDesc = "" + "\x13GetSettingsResponse\x12/\n" + "\bsettings\x18\x01 \x01(\v2\x13.server.v1.SettingsR\bsettings\"V\n" + "\x1bGetReadOnlySettingsResponse\x127\n" + - "\bsettings\x18\x01 \x01(\v2\x1b.server.v1.ReadOnlySettingsR\bsettings\"\xbd\b\n" + + "\bsettings\x18\x01 \x01(\v2\x1b.server.v1.ReadOnlySettingsR\bsettings\"\x80\f\n" + "\x15ChangeSettingsRequest\x12*\n" + "\x0eenable_updates\x18\x01 \x01(\bH\x00R\renableUpdates\x88\x01\x01\x12.\n" + "\x10enable_telemetry\x18\x02 \x01(\bH\x01R\x0fenableTelemetry\x88\x01\x01\x12N\n" + @@ -1615,7 +1694,11 @@ const file_server_v1_server_proto_rawDesc = "" + "\x18enable_backup_management\x18\f \x01(\bH\bR\x16enableBackupManagement\x88\x01\x01\x127\n" + "\x15enable_access_control\x18\r \x01(\bH\tR\x13enableAccessControl\x88\x01\x01\x128\n" + "\x16enable_internal_pg_qan\x18\x0e \x01(\bH\n" + - "R\x13enableInternalPgQan\x88\x01\x01B\x11\n" + + "R\x13enableInternalPgQan\x88\x01\x01\x12U\n" + + "\x19advisor_history_retention\x18\x10 \x01(\v2\x19.google.protobuf.DurationR\x17advisorHistoryRetention\x12E\n" + + "\x1cenable_advisor_notifications\x18\x11 \x01(\bH\vR\x1aenableAdvisorNotifications\x88\x01\x01\x12n\n" + + "'advisor_notification_severity_threshold\x18\x12 \x01(\x0e2\x17.management.v1.SeverityR$advisorNotificationSeverityThreshold\x12i\n" + + "$advisor_notification_email_addresses\x18\x14 \x01(\v2\x13.common.StringArrayH\fR!advisorNotificationEmailAddresses\x88\x01\x01B\x11\n" + "\x0f_enable_updatesB\x13\n" + "\x11_enable_telemetryB\n" + "\n" + @@ -1627,7 +1710,9 @@ const file_server_v1_server_proto_rawDesc = "" + "\x15_enable_azurediscoverB\x1b\n" + "\x19_enable_backup_managementB\x18\n" + "\x16_enable_access_controlB\x19\n" + - "\x17_enable_internal_pg_qanJ\x04\b\x0f\x10\x10R\x16update_snooze_duration\"I\n" + + "\x17_enable_internal_pg_qanB\x1f\n" + + "\x1d_enable_advisor_notificationsB'\n" + + "%_advisor_notification_email_addressesJ\x04\b\x0f\x10\x10R\x16update_snooze_duration\"I\n" + "\x16ChangeSettingsResponse\x12/\n" + "\bsettings\x18\x01 \x01(\v2\x13.server.v1.SettingsR\bsettings*\xce\x01\n" + "\x12DistributionMethod\x12#\n" + @@ -1690,7 +1775,8 @@ var ( (*ChangeSettingsResponse)(nil), // 22: server.v1.ChangeSettingsResponse (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp (*durationpb.Duration)(nil), // 24: google.protobuf.Duration - (*common.StringArray)(nil), // 25: common.StringArray + v1.Severity(0), // 25: management.v1.Severity + (*common.StringArray)(nil), // 26: common.StringArray } ) @@ -1714,34 +1800,39 @@ var file_server_v1_server_proto_depIdxs = []int32{ 13, // 16: server.v1.Settings.metrics_resolutions:type_name -> server.v1.MetricsResolutions 24, // 17: server.v1.Settings.data_retention:type_name -> google.protobuf.Duration 14, // 18: server.v1.Settings.advisor_run_intervals:type_name -> server.v1.AdvisorRunIntervals - 15, // 19: server.v1.GetSettingsResponse.settings:type_name -> server.v1.Settings - 16, // 20: server.v1.GetReadOnlySettingsResponse.settings:type_name -> server.v1.ReadOnlySettings - 13, // 21: server.v1.ChangeSettingsRequest.metrics_resolutions:type_name -> server.v1.MetricsResolutions - 24, // 22: server.v1.ChangeSettingsRequest.data_retention:type_name -> google.protobuf.Duration - 25, // 23: server.v1.ChangeSettingsRequest.aws_partitions:type_name -> common.StringArray - 14, // 24: server.v1.ChangeSettingsRequest.advisor_run_intervals:type_name -> server.v1.AdvisorRunIntervals - 15, // 25: server.v1.ChangeSettingsResponse.settings:type_name -> server.v1.Settings - 2, // 26: server.v1.ServerService.Version:input_type -> server.v1.VersionRequest - 4, // 27: server.v1.ServerService.Readiness:input_type -> server.v1.ReadinessRequest - 6, // 28: server.v1.ServerService.LeaderHealthCheck:input_type -> server.v1.LeaderHealthCheckRequest - 8, // 29: server.v1.ServerService.CheckUpdates:input_type -> server.v1.CheckUpdatesRequest - 11, // 30: server.v1.ServerService.ListChangeLogs:input_type -> server.v1.ListChangeLogsRequest - 17, // 31: server.v1.ServerService.GetSettings:input_type -> server.v1.GetSettingsRequest - 18, // 32: server.v1.ServerService.GetReadOnlySettings:input_type -> server.v1.GetReadOnlySettingsRequest - 21, // 33: server.v1.ServerService.ChangeSettings:input_type -> server.v1.ChangeSettingsRequest - 3, // 34: server.v1.ServerService.Version:output_type -> server.v1.VersionResponse - 5, // 35: server.v1.ServerService.Readiness:output_type -> server.v1.ReadinessResponse - 7, // 36: server.v1.ServerService.LeaderHealthCheck:output_type -> server.v1.LeaderHealthCheckResponse - 10, // 37: server.v1.ServerService.CheckUpdates:output_type -> server.v1.CheckUpdatesResponse - 12, // 38: server.v1.ServerService.ListChangeLogs:output_type -> server.v1.ListChangeLogsResponse - 19, // 39: server.v1.ServerService.GetSettings:output_type -> server.v1.GetSettingsResponse - 20, // 40: server.v1.ServerService.GetReadOnlySettings:output_type -> server.v1.GetReadOnlySettingsResponse - 22, // 41: server.v1.ServerService.ChangeSettings:output_type -> server.v1.ChangeSettingsResponse - 34, // [34:42] is the sub-list for method output_type - 26, // [26:34] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 24, // 19: server.v1.Settings.advisor_history_retention:type_name -> google.protobuf.Duration + 25, // 20: server.v1.Settings.advisor_notification_severity_threshold:type_name -> management.v1.Severity + 15, // 21: server.v1.GetSettingsResponse.settings:type_name -> server.v1.Settings + 16, // 22: server.v1.GetReadOnlySettingsResponse.settings:type_name -> server.v1.ReadOnlySettings + 13, // 23: server.v1.ChangeSettingsRequest.metrics_resolutions:type_name -> server.v1.MetricsResolutions + 24, // 24: server.v1.ChangeSettingsRequest.data_retention:type_name -> google.protobuf.Duration + 26, // 25: server.v1.ChangeSettingsRequest.aws_partitions:type_name -> common.StringArray + 14, // 26: server.v1.ChangeSettingsRequest.advisor_run_intervals:type_name -> server.v1.AdvisorRunIntervals + 24, // 27: server.v1.ChangeSettingsRequest.advisor_history_retention:type_name -> google.protobuf.Duration + 25, // 28: server.v1.ChangeSettingsRequest.advisor_notification_severity_threshold:type_name -> management.v1.Severity + 26, // 29: server.v1.ChangeSettingsRequest.advisor_notification_email_addresses:type_name -> common.StringArray + 15, // 30: server.v1.ChangeSettingsResponse.settings:type_name -> server.v1.Settings + 2, // 31: server.v1.ServerService.Version:input_type -> server.v1.VersionRequest + 4, // 32: server.v1.ServerService.Readiness:input_type -> server.v1.ReadinessRequest + 6, // 33: server.v1.ServerService.LeaderHealthCheck:input_type -> server.v1.LeaderHealthCheckRequest + 8, // 34: server.v1.ServerService.CheckUpdates:input_type -> server.v1.CheckUpdatesRequest + 11, // 35: server.v1.ServerService.ListChangeLogs:input_type -> server.v1.ListChangeLogsRequest + 17, // 36: server.v1.ServerService.GetSettings:input_type -> server.v1.GetSettingsRequest + 18, // 37: server.v1.ServerService.GetReadOnlySettings:input_type -> server.v1.GetReadOnlySettingsRequest + 21, // 38: server.v1.ServerService.ChangeSettings:input_type -> server.v1.ChangeSettingsRequest + 3, // 39: server.v1.ServerService.Version:output_type -> server.v1.VersionResponse + 5, // 40: server.v1.ServerService.Readiness:output_type -> server.v1.ReadinessResponse + 7, // 41: server.v1.ServerService.LeaderHealthCheck:output_type -> server.v1.LeaderHealthCheckResponse + 10, // 42: server.v1.ServerService.CheckUpdates:output_type -> server.v1.CheckUpdatesResponse + 12, // 43: server.v1.ServerService.ListChangeLogs:output_type -> server.v1.ListChangeLogsResponse + 19, // 44: server.v1.ServerService.GetSettings:output_type -> server.v1.GetSettingsResponse + 20, // 45: server.v1.ServerService.GetReadOnlySettings:output_type -> server.v1.GetReadOnlySettingsResponse + 22, // 46: server.v1.ServerService.ChangeSettings:output_type -> server.v1.ChangeSettingsResponse + 39, // [39:47] is the sub-list for method output_type + 31, // [31:39] is the sub-list for method input_type + 31, // [31:31] is the sub-list for extension type_name + 31, // [31:31] is the sub-list for extension extendee + 0, // [0:31] is the sub-list for field type_name } func init() { file_server_v1_server_proto_init() } diff --git a/api/server/v1/server.pb.validate.go b/api/server/v1/server.pb.validate.go index 7c9c52c587a..60d0ea67e78 100644 --- a/api/server/v1/server.pb.validate.go +++ b/api/server/v1/server.pb.validate.go @@ -17,6 +17,8 @@ import ( "unicode/utf8" "google.golang.org/protobuf/types/known/anypb" + + managementv1 "github.com/percona/pmm/api/management/v1" ) // ensure the imports are used @@ -33,6 +35,8 @@ var ( _ = (*mail.Address)(nil) _ = anypb.Any{} _ = sort.Sort + + _ = managementv1.Severity(0) ) // Validate checks the field values on VersionInfo with the rules defined in @@ -2069,6 +2073,39 @@ func (m *Settings) validate(all bool) error { // no validation rules for EnableInternalPgQan + if all { + switch v := interface{}(m.GetAdvisorHistoryRetention()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, SettingsValidationError{ + field: "AdvisorHistoryRetention", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, SettingsValidationError{ + field: "AdvisorHistoryRetention", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetAdvisorHistoryRetention()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return SettingsValidationError{ + field: "AdvisorHistoryRetention", + reason: "embedded message failed validation", + cause: err, + } + } + } + + // no validation rules for AdvisorNotificationsEnabled + + // no validation rules for AdvisorNotificationSeverityThreshold + if len(errors) > 0 { return SettingsMultiError(errors) } @@ -2844,6 +2881,37 @@ func (m *ChangeSettingsRequest) validate(all bool) error { } } + if all { + switch v := interface{}(m.GetAdvisorHistoryRetention()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ChangeSettingsRequestValidationError{ + field: "AdvisorHistoryRetention", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ChangeSettingsRequestValidationError{ + field: "AdvisorHistoryRetention", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetAdvisorHistoryRetention()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ChangeSettingsRequestValidationError{ + field: "AdvisorHistoryRetention", + reason: "embedded message failed validation", + cause: err, + } + } + } + + // no validation rules for AdvisorNotificationSeverityThreshold + if m.EnableUpdates != nil { // no validation rules for EnableUpdates } @@ -2915,6 +2983,41 @@ func (m *ChangeSettingsRequest) validate(all bool) error { // no validation rules for EnableInternalPgQan } + if m.EnableAdvisorNotifications != nil { + // no validation rules for EnableAdvisorNotifications + } + + if m.AdvisorNotificationEmailAddresses != nil { + if all { + switch v := interface{}(m.GetAdvisorNotificationEmailAddresses()).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ChangeSettingsRequestValidationError{ + field: "AdvisorNotificationEmailAddresses", + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ChangeSettingsRequestValidationError{ + field: "AdvisorNotificationEmailAddresses", + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(m.GetAdvisorNotificationEmailAddresses()).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ChangeSettingsRequestValidationError{ + field: "AdvisorNotificationEmailAddresses", + reason: "embedded message failed validation", + cause: err, + } + } + } + } + if len(errors) > 0 { return ChangeSettingsRequestMultiError(errors) } diff --git a/api/server/v1/server.proto b/api/server/v1/server.proto index 0fe88df85d9..8f1f5b45f23 100644 --- a/api/server/v1/server.proto +++ b/api/server/v1/server.proto @@ -6,6 +6,7 @@ import "common/common.proto"; import "google/api/annotations.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; +import "management/v1/severity.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; // DistributionMethod defines PMM Server distribution method: Docker image, OVF/OVA, or AMI. @@ -156,6 +157,14 @@ message Settings { // Field 20 (update_snooze_duration) was removed when GUI-triggered upgrades were deprecated. reserved 20; reserved "update_snooze_duration"; + // Advisor check results history retention. + google.protobuf.Duration advisor_history_retention = 21; + // True if Advisor email notifications are enabled. + bool advisor_notifications_enabled = 22; + // Least-severe level that triggers an Advisor notification. + management.v1.Severity advisor_notification_severity_threshold = 23; + // Email addresses Advisor notifications are sent to. + repeated string advisor_notification_email_addresses = 24; } // ReadOnlySettings represents a stripped-down version of PMM Server settings that can be accessed by users of all roles. @@ -217,6 +226,15 @@ message ChangeSettingsRequest { // Field 15 (update_snooze_duration) was removed when GUI-triggered upgrades were deprecated. reserved 15; reserved "update_snooze_duration"; + // A number of full days for Advisor check results history retention, i.e. a multiple of 24h: 2592000s, 43200m, 720h. + google.protobuf.Duration advisor_history_retention = 16; + // Enable Advisor email notifications. + optional bool enable_advisor_notifications = 17; + // Least-severe level that triggers an Advisor notification. + management.v1.Severity advisor_notification_severity_threshold = 18; + // Email addresses Advisor notifications are sent to. Unset leaves them unchanged; an empty + // array clears the list. + optional common.StringArray advisor_notification_email_addresses = 20; } message ChangeSettingsResponse { diff --git a/api/swagger/swagger-dev.json b/api/swagger/swagger-dev.json index ac4a6f1a126..8b9e4e104cf 100644 --- a/api/swagger/swagger-dev.json +++ b/api/swagger/swagger-dev.json @@ -1348,30 +1348,35 @@ "type": "object", "properties": { "name": { - "description": "Machine-readable name (ID) that is used in expression.", + "description": "Deprecated: no longer populated; an advisor is identified by its category/subcategory pair.", "type": "string", "x-order": 0 }, "description": { - "description": "Long human-readable description.", + "description": "Deprecated: advisor descriptions were removed.", "type": "string", "x-order": 1 }, "summary": { - "description": "Short human-readable summary.", + "description": "Deprecated: use subcategory instead.", "type": "string", "x-order": 2 }, "comment": { - "description": "Comment.", + "description": "Deprecated: no longer populated.", "type": "string", "x-order": 3 }, "category": { - "description": "Category.", + "description": "Category (top-level grouping).", "type": "string", "x-order": 4 }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 5 + }, "checks": { "description": "Advisor checks.", "type": "array", @@ -1411,20 +1416,77 @@ ], "x-order": 4 }, - "family": { + "technology": { "type": "string", - "default": "ADVISOR_CHECK_FAMILY_UNSPECIFIED", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", "enum": [ - "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "ADVISOR_CHECK_FAMILY_MYSQL", - "ADVISOR_CHECK_FAMILY_POSTGRESQL", - "ADVISOR_CHECK_FAMILY_MONGODB" + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ], "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 } } }, - "x-order": 5 + "x-order": 6 } } }, @@ -1519,173 +1581,77 @@ ], "x-order": 4 }, - "family": { - "type": "string", - "default": "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "enum": [ - "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "ADVISOR_CHECK_FAMILY_MYSQL", - "ADVISOR_CHECK_FAMILY_POSTGRESQL", - "ADVISOR_CHECK_FAMILY_MONGODB" - ], - "x-order": 5 - } - } - }, - "x-order": 0 - } - } - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32", - "x-order": 0 - }, - "message": { - "type": "string", - "x-order": 1 - }, - "details": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "x-order": 0 - } - }, - "additionalProperties": {} - }, - "x-order": 2 - } - } - } - } - } - } - }, - "/v1/advisors/checks/failed": { - "get": { - "description": "Returns the latest check results for a given service.", - "tags": [ - "AdvisorService" - ], - "summary": "Get Failed Advisor Checks", - "operationId": "GetFailedChecks", - "parameters": [ - { - "type": "integer", - "format": "int32", - "description": "Maximum number of results per page.", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "format": "int32", - "description": "Index of the requested page, starts from 0.", - "name": "page_index", - "in": "query" - }, - { - "type": "string", - "description": "Service ID.", - "name": "service_id", - "in": "query" - } - ], - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "type": "object", - "properties": { - "total_items": { - "description": "Total number of results.", - "type": "integer", - "format": "int32", - "x-order": 0 - }, - "total_pages": { - "description": "Total number of pages.", - "type": "integer", - "format": "int32", - "x-order": 1 - }, - "results": { - "type": "array", - "title": "Check results", - "items": { - "description": "CheckResult represents the check results for a given service.", - "type": "object", - "properties": { - "summary": { - "type": "string", - "x-order": 0 - }, - "description": { - "type": "string", - "x-order": 1 - }, - "severity": { - "description": "Severity represents severity level of the check result or alert.", + "technology": { "type": "string", - "default": "SEVERITY_UNSPECIFIED", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", "enum": [ - "SEVERITY_UNSPECIFIED", - "SEVERITY_EMERGENCY", - "SEVERITY_ALERT", - "SEVERITY_CRITICAL", - "SEVERITY_ERROR", - "SEVERITY_WARNING", - "SEVERITY_NOTICE", - "SEVERITY_INFO", - "SEVERITY_DEBUG" + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ], - "x-order": 2 - }, - "labels": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-order": 3 - }, - "read_more_url": { - "description": "URL containing information on how to resolve an issue detected by an Advisor check.", - "type": "string", - "x-order": 4 - }, - "service_name": { - "description": "Name of the monitored service on which the check ran.", - "type": "string", "x-order": 5 }, - "service_id": { - "description": "ID of the monitored service on which the check ran.", + "category": { + "description": "Category (top-level grouping).", "type": "string", "x-order": 6 }, - "check_name": { + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", "type": "string", - "title": "Name of the check that failed", "x-order": 7 }, - "silenced": { + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", "type": "boolean", - "title": "Silence status of the check result", "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 } } }, - "x-order": 2 + "x-order": 0 } } } @@ -1722,16 +1688,14 @@ } } } - } - }, - "/v1/advisors/checks:batchChange": { + }, "post": { - "description": "Enables/disables advisor checks or changes their exec interval.", + "description": "Creates a new user-authored advisor check.", "tags": [ "AdvisorService" ], - "summary": "Change Advisor Checks", - "operationId": "ChangeAdvisorChecks", + "summary": "Create Advisor Check", + "operationId": "CreateAdvisorCheck", "parameters": [ { "name": "body", @@ -1740,47 +1704,1673 @@ "schema": { "type": "object", "properties": { - "params": { - "type": "array", - "items": { - "description": "ChangeAdvisorCheckParams specifies a single check parameters.", + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks/{name}": { + "get": { + "description": "Returns a single advisor check by name, including its queries and script.", + "tags": [ + "AdvisorService" + ], + "summary": "Get Advisor Check", + "operationId": "GetAdvisorCheck", + "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + }, + "put": { + "description": "Updates an existing user-authored advisor check. Percona-shipped checks cannot be modified. A check cannot be renamed: the name in the request body must either be empty or match the name in the path.", + "tags": [ + "AdvisorService" + ], + "summary": "Update Advisor Check", + "operationId": "UpdateAdvisorCheck", + "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check to update.", + "name": "name", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + }, + "delete": { + "description": "Deletes a user-authored advisor check. Percona-shipped checks cannot be deleted.", + "tags": [ + "AdvisorService" + ], + "summary": "Delete Advisor Check", + "operationId": "DeleteAdvisorCheck", + "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check to delete.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:batchChange": { + "post": { + "description": "Enables/disables advisor checks or changes their exec interval.", + "tags": [ + "AdvisorService" + ], + "summary": "Change Advisor Checks", + "operationId": "ChangeAdvisorChecks", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "params": { + "type": "array", + "items": { + "description": "ChangeAdvisorCheckParams specifies a single check parameters.", + "type": "object", + "properties": { + "name": { + "description": "The name of the check to change.", + "type": "string", + "x-order": 0 + }, + "enable": { + "type": "boolean", + "x-nullable": true, + "x-order": 1 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 2 + }, + "service_ids": { + "description": "IDs of services to apply the enable/disable to. When set, enable/disable\naffects only the given services instead of the whole check; interval\nchanges are not allowed in the same params entry.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 3 + } + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:start": { + "post": { + "description": "Executes Advisor checks and returns when all checks are executed. All available checks will be started if check names aren't specified.", + "tags": [ + "AdvisorService" + ], + "summary": "Start Advisor Checks", + "operationId": "StartAdvisorChecks", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "names": { + "description": "Names of the checks that should be started.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "service_ids": { + "description": "IDs of the services to run the checks against. When empty, the checks run\nagainst every monitored service of a matching technology.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "run_id": { + "description": "ID assigned to this run; all check results produced by it share this run_id.", + "type": "string", + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:test": { + "post": { + "description": "Executes an advisor check definition against a single service without saving the check; results are returned and not persisted.", + "tags": [ + "AdvisorService" + ], + "summary": "Test Advisor Check", + "operationId": "TestAdvisorCheck", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + }, + "service_id": { + "description": "ID of the service to run the check against.", + "type": "string", + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "results": { + "description": "Findings produced by the check script; empty means the check passed.", + "type": "array", + "items": { + "description": "TestAdvisorCheckResult is a single finding produced by a test (dry-run) check execution.", + "type": "object", + "properties": { + "summary": { + "type": "string", + "x-order": 0 + }, + "description": { + "type": "string", + "x-order": 1 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 2 + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 3 + }, + "read_more_url": { + "description": "URL containing information on how to resolve an issue detected by the check.", + "type": "string", + "x-order": 4 + }, + "service_name": { + "description": "Name of the monitored service on which the check ran.", + "type": "string", + "x-order": 5 + }, + "service_id": { + "description": "ID of the monitored service on which the check ran.", + "type": "string", + "x-order": 6 + }, + "check_name": { + "description": "Name of the tested check.", + "type": "string", + "x-order": 7 + } + } + }, + "x-order": 0 + }, + "script_output": { + "description": "Output produced by the script's print() calls, for debugging.", + "type": "string", + "x-order": 1 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:testTargets": { + "get": { + "description": "Lists the services an advisor check of the given technology can be tested against.", + "tags": [ + "AdvisorService" + ], + "summary": "List Advisor Check Test Targets", + "operationId": "ListAdvisorCheckTestTargets", + "parameters": [ + { + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "description": "Technology of the check to be tested; determines the eligible service type.", + "name": "technology", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "targets": { + "description": "Services a check of the requested technology can be tested against.", + "type": "array", + "items": { + "description": "AdvisorCheckTestTarget is a service an advisor check can be tested against.", + "type": "object", + "properties": { + "service_id": { + "description": "ID of the eligible service.", + "type": "string", + "x-order": 0 + }, + "service_name": { + "description": "Name of the eligible service.", + "type": "string", + "x-order": 1 + } + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/insights": { + "get": { + "description": "Returns the history of Advisor check results (insights), including their outcomes.", + "tags": [ + "AdvisorService" + ], + "summary": "List Advisor Insights", + "operationId": "ListInsights", + "parameters": [ + { + "type": "integer", + "format": "int32", + "description": "Maximum number of results per page.", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "Index of the requested page, starts from 0.", + "name": "page_index", + "in": "query" + }, + { + "type": "string", + "description": "Filter by service ID.", + "name": "service_id", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "description": "Filter by outcome.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "name": "status", + "in": "query" + }, + { + "type": "boolean", + "description": "Filter by read state.", + "name": "is_read", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only results recorded at or after this time.", + "name": "from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only results recorded at or before this time.", + "name": "to", + "in": "query" + }, + { + "type": "string", + "description": "Filter by service name (partial, case-insensitive match).", + "name": "service_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by node name (partial, case-insensitive match).", + "name": "node_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by advisor category.", + "name": "category", + "in": "query" + }, + { + "type": "string", + "description": "Filter by check name.", + "name": "check_name", + "in": "query" + }, + { + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "description": "Filter by severity.", + "name": "severity", + "in": "query" + }, + { + "type": "string", + "description": "Filter by run ID.", + "name": "run_id", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "description": "Filter by the actor that initiated the run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "name": "triggered_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "total_items": { + "description": "Total number of results.", + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "total_pages": { + "description": "Total number of pages.", + "type": "integer", + "format": "int32", + "x-order": 1 + }, + "results": { + "description": "Insight records.", + "type": "array", + "items": { + "description": "Insight represents a single persisted Advisor check run against a service.", + "type": "object", + "properties": { + "id": { + "description": "Unique identifier of the history record.", + "type": "string", + "x-order": 0 + }, + "run_id": { + "description": "ID of the run this result belongs to; all results produced by one execution share it.", + "type": "string", + "x-order": 1 + }, + "check_name": { + "description": "Name of the check that ran.", + "type": "string", + "x-order": 2 + }, + "category": { + "description": "Category the check belongs to (top-level grouping).", + "type": "string", + "x-order": 3 + }, + "subcategory": { + "description": "Subcategory the check belongs to (second-level grouping within a category).", + "type": "string", + "x-order": 4 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 5 + }, + "service_id": { + "description": "ID of the monitored service on which the check ran.", + "type": "string", + "x-order": 6 + }, + "service_name": { + "description": "Name of the monitored service on which the check ran.", + "type": "string", + "x-order": 7 + }, + "service_type": { + "description": "Type of the monitored service on which the check ran.", + "type": "string", + "x-order": 8 + }, + "node_id": { + "description": "ID of the node the service runs on.", + "type": "string", + "x-order": 9 + }, + "node_name": { + "description": "Name of the node the service runs on.", + "type": "string", + "x-order": 10 + }, + "environment": { + "description": "Environment of the monitored service on which the check ran.", + "type": "string", + "x-order": 11 + }, + "cluster": { + "description": "Cluster of the monitored service on which the check ran.", + "type": "string", + "x-order": 12 + }, + "replication_set": { + "description": "Replication set of the monitored service on which the check ran.", + "type": "string", + "x-order": 13 + }, + "status": { + "description": "AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "x-order": 14 + }, + "summary": { + "description": "Short human-readable summary of the result.", + "type": "string", + "x-order": 15 + }, + "description": { + "description": "Long human-readable description of the result.", + "type": "string", + "x-order": 16 + }, + "read_more_url": { + "description": "URL containing information on how to resolve a detected issue.", + "type": "string", + "x-order": 17 + }, + "outcome": { + "description": "Output returned by the check run (finding details or execution error).", + "type": "string", + "x-order": 18 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 19 + }, + "labels": { + "description": "Result labels.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 20 + }, + "checked_at": { + "description": "Time when the check ran.", + "type": "string", + "format": "date-time", + "x-order": 21 + }, + "is_read": { + "description": "Whether the result has been marked as read.", + "type": "boolean", + "x-order": 22 + }, + "triggered_by": { + "description": "AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "x-order": 23 + }, + "region": { + "description": "Cloud region of the node the service runs on, empty when not applicable.", + "type": "string", + "x-order": 24 + }, + "az": { + "description": "Cloud availability zone of the node the service runs on, empty when not applicable.", + "type": "string", + "x-order": 25 + } + } + }, + "x-order": 2 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { "type": "object", "properties": { - "name": { - "description": "The name of the check to change.", + "@type": { "type": "string", "x-order": 0 - }, - "enable": { - "type": "boolean", - "x-nullable": true, - "x-order": 1 - }, - "interval": { - "description": "AdvisorCheckInterval represents possible execution interval values for checks.", - "type": "string", - "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", - "enum": [ - "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", - "ADVISOR_CHECK_INTERVAL_STANDARD", - "ADVISOR_CHECK_INTERVAL_FREQUENT", - "ADVISOR_CHECK_INTERVAL_RARE" - ], - "x-order": 2 } - } + }, + "additionalProperties": {} }, - "x-order": 0 + "x-order": 2 } } } } + } + } + }, + "/v1/advisors/insights:filterValues": { + "get": { + "description": "Returns the distinct service and node names present in the Advisor insights, for populating filter dropdowns.", + "tags": [ + "AdvisorService" ], + "summary": "List Advisor Insights Filter Values", + "operationId": "ListInsightsFilterValues", "responses": { "200": { "description": "A successful response.", "schema": { - "type": "object" + "type": "object", + "properties": { + "service_names": { + "description": "Distinct service names present in the check results history, sorted alphabetically.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "node_names": { + "description": "Distinct node names present in the check results history, sorted alphabetically.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 1 + } + } } }, "default": { @@ -1817,14 +3407,14 @@ } } }, - "/v1/advisors/checks:start": { + "/v1/advisors/insights:markRead": { "post": { - "description": "Executes Advisor checks and returns when all checks are executed. All available checks will be started if check names aren't specified.", + "description": "Sets the read state on the specified Advisor insights. Set is_read to false to mark them unread.", "tags": [ "AdvisorService" ], - "summary": "Start Advisor Checks", - "operationId": "StartAdvisorChecks", + "summary": "Mark Advisor Insights Read", + "operationId": "MarkInsightsRead", "parameters": [ { "name": "body", @@ -1833,13 +3423,87 @@ "schema": { "type": "object", "properties": { - "names": { - "description": "Names of the checks that should be started.", + "ids": { + "description": "IDs of the insights to update. Takes precedence over filters.", "type": "array", "items": { "type": "string" }, "x-order": 0 + }, + "is_read": { + "description": "Read state to set on the records.", + "type": "boolean", + "x-order": 1 + }, + "filters": { + "description": "InsightsFilters select Advisor insights by attribute; all present fields must match.", + "type": "object", + "properties": { + "check_name": { + "description": "Filter by check name.", + "type": "string", + "x-order": 0 + }, + "service_name": { + "description": "Filter by service name (partial, case-insensitive match).", + "type": "string", + "x-order": 1 + }, + "node_name": { + "description": "Filter by node name (partial, case-insensitive match).", + "type": "string", + "x-order": 2 + }, + "category": { + "description": "Filter by advisor category.", + "type": "string", + "x-order": 3 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-nullable": true, + "x-order": 4 + }, + "status": { + "description": "AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "x-nullable": true, + "x-order": 5 + }, + "is_read": { + "description": "Filter by read state.", + "type": "boolean", + "x-nullable": true, + "x-order": 6 + }, + "run_id": { + "description": "Filter by run ID.", + "type": "string", + "x-order": 7 + } + }, + "x-order": 2 } } } @@ -1886,85 +3550,169 @@ } } }, - "/v1/advisors/failedServices": { + "/v1/advisors/runs": { "get": { - "description": "Returns a list of services with failed checks and a summary of check results.", + "description": "Returns the chronological history of Advisor check executions with their totals.", "tags": [ "AdvisorService" ], - "summary": "List Failed Services", - "operationId": "ListFailedServices", + "summary": "List Advisor Runs", + "operationId": "ListRuns", + "parameters": [ + { + "type": "integer", + "format": "int32", + "description": "Maximum number of results per page.", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "Index of the requested page, starts from 0.", + "name": "page_index", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "description": "Filter by the actor that initiated the run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "name": "triggered_by", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only runs started at or after this time.", + "name": "from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only runs started at or before this time.", + "name": "to", + "in": "query" + } + ], "responses": { "200": { "description": "A successful response.", "schema": { "type": "object", "properties": { - "result": { + "total_items": { + "description": "Total number of results.", + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "total_pages": { + "description": "Total number of pages.", + "type": "integer", + "format": "int32", + "x-order": 1 + }, + "results": { + "description": "Runs, most recently started first.", "type": "array", "items": { - "description": "CheckResultSummary is a summary of check results.", + "description": "AdvisorRun is a single execution of Advisor checks. Its totals are recorded on\ncompletion, so they stay accurate after the run's insights have been pruned.", "type": "object", "properties": { - "service_name": { + "id": { + "description": "ID shared by every insight the run produced.", "type": "string", "x-order": 0 }, - "service_id": { + "triggered_by": { + "description": "AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], "x-order": 1 }, - "emergency_count": { - "description": "Number of failed checks for this service with severity level \"EMERGENCY\".", - "type": "integer", - "format": "int64", + "started_at": { + "description": "When the run began.", + "type": "string", + "format": "date-time", "x-order": 2 }, - "alert_count": { - "description": "Number of failed checks for this service with severity level \"ALERT\".", - "type": "integer", - "format": "int64", + "finished_at": { + "description": "When the run completed; unset while it is still running.", + "type": "string", + "format": "date-time", "x-order": 3 }, - "critical_count": { - "description": "Number of failed checks for this service with severity level \"CRITICAL\".", + "checks_count": { + "description": "Number of distinct checks the run executed.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 4 }, - "error_count": { - "description": "Number of failed checks for this service with severity level \"ERROR\".", + "services_count": { + "description": "Number of distinct services the run covered.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 5 }, - "warning_count": { - "description": "Number of failed checks for this service with severity level \"WARNING\".", + "findings_count": { + "description": "Number of findings, i.e. checks that detected an issue.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 6 }, - "notice_count": { - "description": "Number of failed checks for this service with severity level \"NOTICE\".", + "errors_count": { + "description": "Number of checks that could not be executed at all.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 7 }, - "info_count": { - "description": "Number of failed checks for this service with severity level \"INFO\".", - "type": "integer", - "format": "int64", + "severity_counts": { + "description": "Number of findings per severity, most severe first. A repeated field rather\nthan a map so severity stays a typed enum instead of a free-form key.", + "type": "array", + "items": { + "description": "SeverityCount is the number of findings a run produced at a single severity.", + "type": "object", + "properties": { + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 0 + }, + "count": { + "type": "integer", + "format": "int32", + "x-order": 1 + } + } + }, "x-order": 8 - }, - "debug_count": { - "description": "Number of failed checks for this service with severity level \"DEBUG\".", - "type": "integer", - "format": "int64", - "x-order": 9 } } }, - "x-order": 0 + "x-order": 2 } } } @@ -2397,7 +4145,7 @@ "x-order": 7 }, "source": { - "description": "TemplateSource defines template source.\n\n - TEMPLATE_SOURCE_BUILT_IN: Template that is shipped with PMM Server releases.\n - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com.\n - TEMPLATE_SOURCE_USER_FILE: Templated loaded from user-suplied file.\n - TEMPLATE_SOURCE_USER_API: Templated created via API.", + "description": "TemplateSource defines template source.\n\n - TEMPLATE_SOURCE_BUILT_IN: Template that is shipped with PMM Server releases.\n - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com. Deprecated.\n - TEMPLATE_SOURCE_USER_FILE: Templated loaded from user-suplied file.\n - TEMPLATE_SOURCE_USER_API: Templated created via API.", "type": "string", "default": "TEMPLATE_SOURCE_UNSPECIFIED", "enum": [ @@ -32427,6 +34175,41 @@ "description": "True if Query Analytics for PMM's internal PG database is enabled.", "type": "boolean", "x-order": 17 + }, + "advisor_history_retention": { + "description": "Advisor check results history retention.", + "type": "string", + "x-order": 18 + }, + "advisor_notifications_enabled": { + "description": "True if Advisor email notifications are enabled.", + "type": "boolean", + "x-order": 19 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 20 + }, + "advisor_notification_email_addresses": { + "description": "Email addresses Advisor notifications are sent to.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 21 } }, "x-order": 0 @@ -32604,6 +34387,49 @@ "type": "boolean", "x-nullable": true, "x-order": 13 + }, + "advisor_history_retention": { + "description": "A number of full days for Advisor check results history retention, i.e. a multiple of 24h: 2592000s, 43200m, 720h.", + "type": "string", + "x-order": 14 + }, + "enable_advisor_notifications": { + "description": "Enable Advisor email notifications.", + "type": "boolean", + "x-nullable": true, + "x-order": 15 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 16 + }, + "advisor_notification_email_addresses": { + "description": "A wrapper for a string array. This type allows to distinguish between an empty array and a null value.", + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + } + }, + "x-nullable": true, + "x-order": 17 } } } @@ -32745,6 +34571,41 @@ "description": "True if Query Analytics for PMM's internal PG database is enabled.", "type": "boolean", "x-order": 17 + }, + "advisor_history_retention": { + "description": "Advisor check results history retention.", + "type": "string", + "x-order": 18 + }, + "advisor_notifications_enabled": { + "description": "True if Advisor email notifications are enabled.", + "type": "boolean", + "x-order": 19 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 20 + }, + "advisor_notification_email_addresses": { + "description": "Email addresses Advisor notifications are sent to.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 21 } }, "x-order": 0 diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index c40c51e6c46..978a56a5e62 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -831,30 +831,35 @@ "type": "object", "properties": { "name": { - "description": "Machine-readable name (ID) that is used in expression.", + "description": "Deprecated: no longer populated; an advisor is identified by its category/subcategory pair.", "type": "string", "x-order": 0 }, "description": { - "description": "Long human-readable description.", + "description": "Deprecated: advisor descriptions were removed.", "type": "string", "x-order": 1 }, "summary": { - "description": "Short human-readable summary.", + "description": "Deprecated: use subcategory instead.", "type": "string", "x-order": 2 }, "comment": { - "description": "Comment.", + "description": "Deprecated: no longer populated.", "type": "string", "x-order": 3 }, "category": { - "description": "Category.", + "description": "Category (top-level grouping).", "type": "string", "x-order": 4 }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 5 + }, "checks": { "description": "Advisor checks.", "type": "array", @@ -894,20 +899,77 @@ ], "x-order": 4 }, - "family": { + "technology": { "type": "string", - "default": "ADVISOR_CHECK_FAMILY_UNSPECIFIED", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", "enum": [ - "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "ADVISOR_CHECK_FAMILY_MYSQL", - "ADVISOR_CHECK_FAMILY_POSTGRESQL", - "ADVISOR_CHECK_FAMILY_MONGODB" + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ], "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 } } }, - "x-order": 5 + "x-order": 6 } } }, @@ -1002,173 +1064,77 @@ ], "x-order": 4 }, - "family": { - "type": "string", - "default": "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "enum": [ - "ADVISOR_CHECK_FAMILY_UNSPECIFIED", - "ADVISOR_CHECK_FAMILY_MYSQL", - "ADVISOR_CHECK_FAMILY_POSTGRESQL", - "ADVISOR_CHECK_FAMILY_MONGODB" - ], - "x-order": 5 - } - } - }, - "x-order": 0 - } - } - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32", - "x-order": 0 - }, - "message": { - "type": "string", - "x-order": 1 - }, - "details": { - "type": "array", - "items": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "x-order": 0 - } - }, - "additionalProperties": {} - }, - "x-order": 2 - } - } - } - } - } - } - }, - "/v1/advisors/checks/failed": { - "get": { - "description": "Returns the latest check results for a given service.", - "tags": [ - "AdvisorService" - ], - "summary": "Get Failed Advisor Checks", - "operationId": "GetFailedChecks", - "parameters": [ - { - "type": "integer", - "format": "int32", - "description": "Maximum number of results per page.", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "format": "int32", - "description": "Index of the requested page, starts from 0.", - "name": "page_index", - "in": "query" - }, - { - "type": "string", - "description": "Service ID.", - "name": "service_id", - "in": "query" - } - ], - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "type": "object", - "properties": { - "total_items": { - "description": "Total number of results.", - "type": "integer", - "format": "int32", - "x-order": 0 - }, - "total_pages": { - "description": "Total number of pages.", - "type": "integer", - "format": "int32", - "x-order": 1 - }, - "results": { - "type": "array", - "title": "Check results", - "items": { - "description": "CheckResult represents the check results for a given service.", - "type": "object", - "properties": { - "summary": { - "type": "string", - "x-order": 0 - }, - "description": { - "type": "string", - "x-order": 1 - }, - "severity": { - "description": "Severity represents severity level of the check result or alert.", + "technology": { "type": "string", - "default": "SEVERITY_UNSPECIFIED", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", "enum": [ - "SEVERITY_UNSPECIFIED", - "SEVERITY_EMERGENCY", - "SEVERITY_ALERT", - "SEVERITY_CRITICAL", - "SEVERITY_ERROR", - "SEVERITY_WARNING", - "SEVERITY_NOTICE", - "SEVERITY_INFO", - "SEVERITY_DEBUG" + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" ], - "x-order": 2 - }, - "labels": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-order": 3 - }, - "read_more_url": { - "description": "URL containing information on how to resolve an issue detected by an Advisor check.", - "type": "string", - "x-order": 4 - }, - "service_name": { - "description": "Name of the monitored service on which the check ran.", - "type": "string", "x-order": 5 }, - "service_id": { - "description": "ID of the monitored service on which the check ran.", + "category": { + "description": "Category (top-level grouping).", "type": "string", "x-order": 6 }, - "check_name": { + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", "type": "string", - "title": "Name of the check that failed", "x-order": 7 }, - "silenced": { + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", "type": "boolean", - "title": "Silence status of the check result", "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 } } }, - "x-order": 2 + "x-order": 0 } } } @@ -1205,16 +1171,14 @@ } } } - } - }, - "/v1/advisors/checks:batchChange": { + }, "post": { - "description": "Enables/disables advisor checks or changes their exec interval.", + "description": "Creates a new user-authored advisor check.", "tags": [ "AdvisorService" ], - "summary": "Change Advisor Checks", - "operationId": "ChangeAdvisorChecks", + "summary": "Create Advisor Check", + "operationId": "CreateAdvisorCheck", "parameters": [ { "name": "body", @@ -1223,47 +1187,1673 @@ "schema": { "type": "object", "properties": { - "params": { - "type": "array", - "items": { - "description": "ChangeAdvisorCheckParams specifies a single check parameters.", + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks/{name}": { + "get": { + "description": "Returns a single advisor check by name, including its queries and script.", + "tags": [ + "AdvisorService" + ], + "summary": "Get Advisor Check", + "operationId": "GetAdvisorCheck", + "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + }, + "put": { + "description": "Updates an existing user-authored advisor check. Percona-shipped checks cannot be modified. A check cannot be renamed: the name in the request body must either be empty or match the name in the path.", + "tags": [ + "AdvisorService" + ], + "summary": "Update Advisor Check", + "operationId": "UpdateAdvisorCheck", + "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check to update.", + "name": "name", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + }, + "delete": { + "description": "Deletes a user-authored advisor check. Percona-shipped checks cannot be deleted.", + "tags": [ + "AdvisorService" + ], + "summary": "Delete Advisor Check", + "operationId": "DeleteAdvisorCheck", + "parameters": [ + { + "type": "string", + "description": "Machine-readable name (ID) of the check to delete.", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:batchChange": { + "post": { + "description": "Enables/disables advisor checks or changes their exec interval.", + "tags": [ + "AdvisorService" + ], + "summary": "Change Advisor Checks", + "operationId": "ChangeAdvisorChecks", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "params": { + "type": "array", + "items": { + "description": "ChangeAdvisorCheckParams specifies a single check parameters.", + "type": "object", + "properties": { + "name": { + "description": "The name of the check to change.", + "type": "string", + "x-order": 0 + }, + "enable": { + "type": "boolean", + "x-nullable": true, + "x-order": 1 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 2 + }, + "service_ids": { + "description": "IDs of services to apply the enable/disable to. When set, enable/disable\naffects only the given services instead of the whole check; interval\nchanges are not allowed in the same params entry.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 3 + } + } + }, + "x-order": 0 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:start": { + "post": { + "description": "Executes Advisor checks and returns when all checks are executed. All available checks will be started if check names aren't specified.", + "tags": [ + "AdvisorService" + ], + "summary": "Start Advisor Checks", + "operationId": "StartAdvisorChecks", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "names": { + "description": "Names of the checks that should be started.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "service_ids": { + "description": "IDs of the services to run the checks against. When empty, the checks run\nagainst every monitored service of a matching technology.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "run_id": { + "description": "ID assigned to this run; all check results produced by it share this run_id.", + "type": "string", + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:test": { + "post": { + "description": "Executes an advisor check definition against a single service without saving the check; results are returned and not persisted.", + "tags": [ + "AdvisorService" + ], + "summary": "Test Advisor Check", + "operationId": "TestAdvisorCheck", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "check": { + "description": "AdvisorCheck contains check name and status.", + "type": "object", + "properties": { + "name": { + "description": "Machine-readable name (ID) that is used in expression.", + "type": "string", + "x-order": 0 + }, + "enabled": { + "description": "True if that check is enabled.", + "type": "boolean", + "x-order": 1 + }, + "description": { + "description": "Long human-readable description.", + "type": "string", + "x-order": 2 + }, + "summary": { + "description": "Short human-readable summary.", + "type": "string", + "x-order": 3 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 4 + }, + "technology": { + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "x-order": 5 + }, + "category": { + "description": "Category (top-level grouping).", + "type": "string", + "x-order": 6 + }, + "subcategory": { + "description": "Subcategory (second-level grouping within a category).", + "type": "string", + "x-order": 7 + }, + "user_defined": { + "description": "True if the check is user-authored (editable/deletable); false for Percona-shipped checks.", + "type": "boolean", + "x-order": 8 + }, + "queries": { + "description": "Data-collection queries. Populated by Get/Create/Update; may be empty in list responses.", + "type": "array", + "items": { + "description": "AdvisorCheckQuery is a single data-collection query of an advisor check.", + "type": "object", + "properties": { + "type": { + "description": "Query type, e.g. \"MYSQL_SHOW\", \"POSTGRESQL_SELECT\", \"METRICS_RANGE\".", + "type": "string", + "x-order": 0 + }, + "query": { + "description": "Query text (may be empty for parameterless types such as MYSQL_SHOW).", + "type": "string", + "x-order": 1 + }, + "parameters": { + "description": "Optional query parameters (e.g. range/step for metrics range queries).", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 2 + } + } + }, + "x-order": 9 + }, + "script": { + "description": "Starlark source script. Populated by Get/Create/Update; may be empty in list responses.", + "type": "string", + "x-order": 10 + }, + "disabled_service_ids": { + "description": "IDs of services for which this check is disabled.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 11 + } + }, + "x-order": 0 + }, + "service_id": { + "description": "ID of the service to run the check against.", + "type": "string", + "x-order": 1 + } + } + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "results": { + "description": "Findings produced by the check script; empty means the check passed.", + "type": "array", + "items": { + "description": "TestAdvisorCheckResult is a single finding produced by a test (dry-run) check execution.", + "type": "object", + "properties": { + "summary": { + "type": "string", + "x-order": 0 + }, + "description": { + "type": "string", + "x-order": 1 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 2 + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 3 + }, + "read_more_url": { + "description": "URL containing information on how to resolve an issue detected by the check.", + "type": "string", + "x-order": 4 + }, + "service_name": { + "description": "Name of the monitored service on which the check ran.", + "type": "string", + "x-order": 5 + }, + "service_id": { + "description": "ID of the monitored service on which the check ran.", + "type": "string", + "x-order": 6 + }, + "check_name": { + "description": "Name of the tested check.", + "type": "string", + "x-order": 7 + } + } + }, + "x-order": 0 + }, + "script_output": { + "description": "Output produced by the script's print() calls, for debugging.", + "type": "string", + "x-order": 1 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/checks:testTargets": { + "get": { + "description": "Lists the services an advisor check of the given technology can be tested against.", + "tags": [ + "AdvisorService" + ], + "summary": "List Advisor Check Test Targets", + "operationId": "ListAdvisorCheckTestTargets", + "parameters": [ + { + "enum": [ + "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "ADVISOR_CHECK_TECHNOLOGY_MYSQL", + "ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL", + "ADVISOR_CHECK_TECHNOLOGY_MONGODB" + ], + "type": "string", + "default": "ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED", + "description": "Technology of the check to be tested; determines the eligible service type.", + "name": "technology", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "targets": { + "description": "Services a check of the requested technology can be tested against.", + "type": "array", + "items": { + "description": "AdvisorCheckTestTarget is a service an advisor check can be tested against.", + "type": "object", + "properties": { + "service_id": { + "description": "ID of the eligible service.", + "type": "string", + "x-order": 0 + }, + "service_name": { + "description": "Name of the eligible service.", + "type": "string", + "x-order": 1 + } + } + }, + "x-order": 0 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "x-order": 0 + } + }, + "additionalProperties": {} + }, + "x-order": 2 + } + } + } + } + } + } + }, + "/v1/advisors/insights": { + "get": { + "description": "Returns the history of Advisor check results (insights), including their outcomes.", + "tags": [ + "AdvisorService" + ], + "summary": "List Advisor Insights", + "operationId": "ListInsights", + "parameters": [ + { + "type": "integer", + "format": "int32", + "description": "Maximum number of results per page.", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "Index of the requested page, starts from 0.", + "name": "page_index", + "in": "query" + }, + { + "type": "string", + "description": "Filter by service ID.", + "name": "service_id", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "description": "Filter by outcome.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "name": "status", + "in": "query" + }, + { + "type": "boolean", + "description": "Filter by read state.", + "name": "is_read", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only results recorded at or after this time.", + "name": "from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only results recorded at or before this time.", + "name": "to", + "in": "query" + }, + { + "type": "string", + "description": "Filter by service name (partial, case-insensitive match).", + "name": "service_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by node name (partial, case-insensitive match).", + "name": "node_name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by advisor category.", + "name": "category", + "in": "query" + }, + { + "type": "string", + "description": "Filter by check name.", + "name": "check_name", + "in": "query" + }, + { + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "description": "Filter by severity.", + "name": "severity", + "in": "query" + }, + { + "type": "string", + "description": "Filter by run ID.", + "name": "run_id", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "description": "Filter by the actor that initiated the run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "name": "triggered_by", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "total_items": { + "description": "Total number of results.", + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "total_pages": { + "description": "Total number of pages.", + "type": "integer", + "format": "int32", + "x-order": 1 + }, + "results": { + "description": "Insight records.", + "type": "array", + "items": { + "description": "Insight represents a single persisted Advisor check run against a service.", + "type": "object", + "properties": { + "id": { + "description": "Unique identifier of the history record.", + "type": "string", + "x-order": 0 + }, + "run_id": { + "description": "ID of the run this result belongs to; all results produced by one execution share it.", + "type": "string", + "x-order": 1 + }, + "check_name": { + "description": "Name of the check that ran.", + "type": "string", + "x-order": 2 + }, + "category": { + "description": "Category the check belongs to (top-level grouping).", + "type": "string", + "x-order": 3 + }, + "subcategory": { + "description": "Subcategory the check belongs to (second-level grouping within a category).", + "type": "string", + "x-order": 4 + }, + "interval": { + "description": "AdvisorCheckInterval represents possible execution interval values for checks.", + "type": "string", + "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", + "ADVISOR_CHECK_INTERVAL_STANDARD", + "ADVISOR_CHECK_INTERVAL_FREQUENT", + "ADVISOR_CHECK_INTERVAL_RARE" + ], + "x-order": 5 + }, + "service_id": { + "description": "ID of the monitored service on which the check ran.", + "type": "string", + "x-order": 6 + }, + "service_name": { + "description": "Name of the monitored service on which the check ran.", + "type": "string", + "x-order": 7 + }, + "service_type": { + "description": "Type of the monitored service on which the check ran.", + "type": "string", + "x-order": 8 + }, + "node_id": { + "description": "ID of the node the service runs on.", + "type": "string", + "x-order": 9 + }, + "node_name": { + "description": "Name of the node the service runs on.", + "type": "string", + "x-order": 10 + }, + "environment": { + "description": "Environment of the monitored service on which the check ran.", + "type": "string", + "x-order": 11 + }, + "cluster": { + "description": "Cluster of the monitored service on which the check ran.", + "type": "string", + "x-order": 12 + }, + "replication_set": { + "description": "Replication set of the monitored service on which the check ran.", + "type": "string", + "x-order": 13 + }, + "status": { + "description": "AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "x-order": 14 + }, + "summary": { + "description": "Short human-readable summary of the result.", + "type": "string", + "x-order": 15 + }, + "description": { + "description": "Long human-readable description of the result.", + "type": "string", + "x-order": 16 + }, + "read_more_url": { + "description": "URL containing information on how to resolve a detected issue.", + "type": "string", + "x-order": 17 + }, + "outcome": { + "description": "Output returned by the check run (finding details or execution error).", + "type": "string", + "x-order": 18 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 19 + }, + "labels": { + "description": "Result labels.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-order": 20 + }, + "checked_at": { + "description": "Time when the check ran.", + "type": "string", + "format": "date-time", + "x-order": 21 + }, + "is_read": { + "description": "Whether the result has been marked as read.", + "type": "boolean", + "x-order": 22 + }, + "triggered_by": { + "description": "AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "x-order": 23 + }, + "region": { + "description": "Cloud region of the node the service runs on, empty when not applicable.", + "type": "string", + "x-order": 24 + }, + "az": { + "description": "Cloud availability zone of the node the service runs on, empty when not applicable.", + "type": "string", + "x-order": 25 + } + } + }, + "x-order": 2 + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "message": { + "type": "string", + "x-order": 1 + }, + "details": { + "type": "array", + "items": { "type": "object", "properties": { - "name": { - "description": "The name of the check to change.", + "@type": { "type": "string", "x-order": 0 - }, - "enable": { - "type": "boolean", - "x-nullable": true, - "x-order": 1 - }, - "interval": { - "description": "AdvisorCheckInterval represents possible execution interval values for checks.", - "type": "string", - "default": "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", - "enum": [ - "ADVISOR_CHECK_INTERVAL_UNSPECIFIED", - "ADVISOR_CHECK_INTERVAL_STANDARD", - "ADVISOR_CHECK_INTERVAL_FREQUENT", - "ADVISOR_CHECK_INTERVAL_RARE" - ], - "x-order": 2 } - } + }, + "additionalProperties": {} }, - "x-order": 0 + "x-order": 2 } } } } + } + } + }, + "/v1/advisors/insights:filterValues": { + "get": { + "description": "Returns the distinct service and node names present in the Advisor insights, for populating filter dropdowns.", + "tags": [ + "AdvisorService" ], + "summary": "List Advisor Insights Filter Values", + "operationId": "ListInsightsFilterValues", "responses": { "200": { "description": "A successful response.", "schema": { - "type": "object" + "type": "object", + "properties": { + "service_names": { + "description": "Distinct service names present in the check results history, sorted alphabetically.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + }, + "node_names": { + "description": "Distinct node names present in the check results history, sorted alphabetically.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 1 + } + } } }, "default": { @@ -1300,14 +2890,14 @@ } } }, - "/v1/advisors/checks:start": { + "/v1/advisors/insights:markRead": { "post": { - "description": "Executes Advisor checks and returns when all checks are executed. All available checks will be started if check names aren't specified.", + "description": "Sets the read state on the specified Advisor insights. Set is_read to false to mark them unread.", "tags": [ "AdvisorService" ], - "summary": "Start Advisor Checks", - "operationId": "StartAdvisorChecks", + "summary": "Mark Advisor Insights Read", + "operationId": "MarkInsightsRead", "parameters": [ { "name": "body", @@ -1316,13 +2906,87 @@ "schema": { "type": "object", "properties": { - "names": { - "description": "Names of the checks that should be started.", + "ids": { + "description": "IDs of the insights to update. Takes precedence over filters.", "type": "array", "items": { "type": "string" }, "x-order": 0 + }, + "is_read": { + "description": "Read state to set on the records.", + "type": "boolean", + "x-order": 1 + }, + "filters": { + "description": "InsightsFilters select Advisor insights by attribute; all present fields must match.", + "type": "object", + "properties": { + "check_name": { + "description": "Filter by check name.", + "type": "string", + "x-order": 0 + }, + "service_name": { + "description": "Filter by service name (partial, case-insensitive match).", + "type": "string", + "x-order": 1 + }, + "node_name": { + "description": "Filter by node name (partial, case-insensitive match).", + "type": "string", + "x-order": 2 + }, + "category": { + "description": "Filter by advisor category.", + "type": "string", + "x-order": 3 + }, + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-nullable": true, + "x-order": 4 + }, + "status": { + "description": "AdvisorCheckResultStatus represents the outcome of an Advisor check run against a service.\n\n - ADVISOR_CHECK_RESULT_STATUS_OK: The check ran and found no issue.\n - ADVISOR_CHECK_RESULT_STATUS_FAILED: The check ran and detected an issue.\n - ADVISOR_CHECK_RESULT_STATUS_ERROR: The check could not be executed.", + "type": "string", + "default": "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED", + "ADVISOR_CHECK_RESULT_STATUS_OK", + "ADVISOR_CHECK_RESULT_STATUS_FAILED", + "ADVISOR_CHECK_RESULT_STATUS_ERROR" + ], + "x-nullable": true, + "x-order": 5 + }, + "is_read": { + "description": "Filter by read state.", + "type": "boolean", + "x-nullable": true, + "x-order": 6 + }, + "run_id": { + "description": "Filter by run ID.", + "type": "string", + "x-order": 7 + } + }, + "x-order": 2 } } } @@ -1369,85 +3033,169 @@ } } }, - "/v1/advisors/failedServices": { + "/v1/advisors/runs": { "get": { - "description": "Returns a list of services with failed checks and a summary of check results.", + "description": "Returns the chronological history of Advisor check executions with their totals.", "tags": [ "AdvisorService" ], - "summary": "List Failed Services", - "operationId": "ListFailedServices", + "summary": "List Advisor Runs", + "operationId": "ListRuns", + "parameters": [ + { + "type": "integer", + "format": "int32", + "description": "Maximum number of results per page.", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "Index of the requested page, starts from 0.", + "name": "page_index", + "in": "query" + }, + { + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], + "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "description": "Filter by the actor that initiated the run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", + "name": "triggered_by", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only runs started at or after this time.", + "name": "from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Return only runs started at or before this time.", + "name": "to", + "in": "query" + } + ], "responses": { "200": { "description": "A successful response.", "schema": { "type": "object", "properties": { - "result": { + "total_items": { + "description": "Total number of results.", + "type": "integer", + "format": "int32", + "x-order": 0 + }, + "total_pages": { + "description": "Total number of pages.", + "type": "integer", + "format": "int32", + "x-order": 1 + }, + "results": { + "description": "Runs, most recently started first.", "type": "array", "items": { - "description": "CheckResultSummary is a summary of check results.", + "description": "AdvisorRun is a single execution of Advisor checks. Its totals are recorded on\ncompletion, so they stay accurate after the run's insights have been pruned.", "type": "object", "properties": { - "service_name": { + "id": { + "description": "ID shared by every insight the run produced.", "type": "string", "x-order": 0 }, - "service_id": { + "triggered_by": { + "description": "AdvisorCheckTriggeredBy represents the actor that initiated an Advisor check run.\n\n - ADVISOR_CHECK_TRIGGERED_BY_USER: The run was started by a user via the API or UI.\n - ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: The run was started by the built-in scheduler.", "type": "string", + "default": "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "enum": [ + "ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED", + "ADVISOR_CHECK_TRIGGERED_BY_USER", + "ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER" + ], "x-order": 1 }, - "emergency_count": { - "description": "Number of failed checks for this service with severity level \"EMERGENCY\".", - "type": "integer", - "format": "int64", + "started_at": { + "description": "When the run began.", + "type": "string", + "format": "date-time", "x-order": 2 }, - "alert_count": { - "description": "Number of failed checks for this service with severity level \"ALERT\".", - "type": "integer", - "format": "int64", + "finished_at": { + "description": "When the run completed; unset while it is still running.", + "type": "string", + "format": "date-time", "x-order": 3 }, - "critical_count": { - "description": "Number of failed checks for this service with severity level \"CRITICAL\".", + "checks_count": { + "description": "Number of distinct checks the run executed.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 4 }, - "error_count": { - "description": "Number of failed checks for this service with severity level \"ERROR\".", + "services_count": { + "description": "Number of distinct services the run covered.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 5 }, - "warning_count": { - "description": "Number of failed checks for this service with severity level \"WARNING\".", + "findings_count": { + "description": "Number of findings, i.e. checks that detected an issue.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 6 }, - "notice_count": { - "description": "Number of failed checks for this service with severity level \"NOTICE\".", + "errors_count": { + "description": "Number of checks that could not be executed at all.", "type": "integer", - "format": "int64", + "format": "int32", "x-order": 7 }, - "info_count": { - "description": "Number of failed checks for this service with severity level \"INFO\".", - "type": "integer", - "format": "int64", + "severity_counts": { + "description": "Number of findings per severity, most severe first. A repeated field rather\nthan a map so severity stays a typed enum instead of a free-form key.", + "type": "array", + "items": { + "description": "SeverityCount is the number of findings a run produced at a single severity.", + "type": "object", + "properties": { + "severity": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 0 + }, + "count": { + "type": "integer", + "format": "int32", + "x-order": 1 + } + } + }, "x-order": 8 - }, - "debug_count": { - "description": "Number of failed checks for this service with severity level \"DEBUG\".", - "type": "integer", - "format": "int64", - "x-order": 9 } } }, - "x-order": 0 + "x-order": 2 } } } @@ -1880,7 +3628,7 @@ "x-order": 7 }, "source": { - "description": "TemplateSource defines template source.\n\n - TEMPLATE_SOURCE_BUILT_IN: Template that is shipped with PMM Server releases.\n - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com.\n - TEMPLATE_SOURCE_USER_FILE: Templated loaded from user-suplied file.\n - TEMPLATE_SOURCE_USER_API: Templated created via API.", + "description": "TemplateSource defines template source.\n\n - TEMPLATE_SOURCE_BUILT_IN: Template that is shipped with PMM Server releases.\n - TEMPLATE_SOURCE_SAAS: Template that is downloaded from check.percona.com. Deprecated.\n - TEMPLATE_SOURCE_USER_FILE: Templated loaded from user-suplied file.\n - TEMPLATE_SOURCE_USER_API: Templated created via API.", "type": "string", "default": "TEMPLATE_SOURCE_UNSPECIFIED", "enum": [ @@ -31454,6 +33202,41 @@ "description": "True if Query Analytics for PMM's internal PG database is enabled.", "type": "boolean", "x-order": 17 + }, + "advisor_history_retention": { + "description": "Advisor check results history retention.", + "type": "string", + "x-order": 18 + }, + "advisor_notifications_enabled": { + "description": "True if Advisor email notifications are enabled.", + "type": "boolean", + "x-order": 19 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 20 + }, + "advisor_notification_email_addresses": { + "description": "Email addresses Advisor notifications are sent to.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 21 } }, "x-order": 0 @@ -31631,6 +33414,49 @@ "type": "boolean", "x-nullable": true, "x-order": 13 + }, + "advisor_history_retention": { + "description": "A number of full days for Advisor check results history retention, i.e. a multiple of 24h: 2592000s, 43200m, 720h.", + "type": "string", + "x-order": 14 + }, + "enable_advisor_notifications": { + "description": "Enable Advisor email notifications.", + "type": "boolean", + "x-nullable": true, + "x-order": 15 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 16 + }, + "advisor_notification_email_addresses": { + "description": "A wrapper for a string array. This type allows to distinguish between an empty array and a null value.", + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + }, + "x-order": 0 + } + }, + "x-nullable": true, + "x-order": 17 } } } @@ -31772,6 +33598,41 @@ "description": "True if Query Analytics for PMM's internal PG database is enabled.", "type": "boolean", "x-order": 17 + }, + "advisor_history_retention": { + "description": "Advisor check results history retention.", + "type": "string", + "x-order": 18 + }, + "advisor_notifications_enabled": { + "description": "True if Advisor email notifications are enabled.", + "type": "boolean", + "x-order": 19 + }, + "advisor_notification_severity_threshold": { + "description": "Severity represents severity level of the check result or alert.", + "type": "string", + "default": "SEVERITY_UNSPECIFIED", + "enum": [ + "SEVERITY_UNSPECIFIED", + "SEVERITY_EMERGENCY", + "SEVERITY_ALERT", + "SEVERITY_CRITICAL", + "SEVERITY_ERROR", + "SEVERITY_WARNING", + "SEVERITY_NOTICE", + "SEVERITY_INFO", + "SEVERITY_DEBUG" + ], + "x-order": 20 + }, + "advisor_notification_email_addresses": { + "description": "Email addresses Advisor notifications are sent to.", + "type": "array", + "items": { + "type": "string" + }, + "x-order": 21 } }, "x-order": 0 diff --git a/build/ansible/roles/nginx/files/conf.d/pmm.conf b/build/ansible/roles/nginx/files/conf.d/pmm.conf index 6ae7efbe7bc..e6605281012 100644 --- a/build/ansible/roles/nginx/files/conf.d/pmm.conf +++ b/build/ansible/roles/nginx/files/conf.d/pmm.conf @@ -40,6 +40,15 @@ server 127.0.0.1:3000; } + # Return the Grafana session cookie rotated during the auth_request subrequest + # (captured into the variable below) to the browser. Without this, API-only + # clients such as the PMM UI never receive the rotated token and get logged out + # once the old one expires. Declared at http level: a server-level add_header + # would stop inheritance of the http-level headers from pmm-ssl.conf. + # "always" because rotation invalidates the old token, so the new cookie must + # reach the browser even on error responses. An empty value adds no header. + add_header Set-Cookie $auth_request_set_cookie always; + server { listen 8080; listen 8443 ssl; @@ -94,7 +103,15 @@ # Store the value of X-Proxy-Filter header of auth_request subrequest response in the variable. auth_request_set $auth_request_proxy_filter $upstream_http_x_proxy_filter; - proxy_set_header X-Proxy-Filter $auth_request_proxy_filter; + proxy_set_header X-Proxy-Filter $auth_request_proxy_filter; + + # Store the Set-Cookie header of the auth_request subrequest response: it carries + # the Grafana session token rotated by pmm-managed. Returned to the browser by the + # http-level "add_header Set-Cookie" directive above the server block. + # Initialized first so locations with auth_request off don't log + # "using uninitialized variable" warnings. + set $auth_request_set_cookie ""; + auth_request_set $auth_request_set_cookie $upstream_http_set_cookie; # nginx completely ignores auth_request subrequest response body. # We use that directive to send the same request to the same location as a normal request diff --git a/build/packages/rpm/server/SPECS/pmm-managed.spec b/build/packages/rpm/server/SPECS/pmm-managed.spec index ad912c3248f..e2c36220c9e 100644 --- a/build/packages/rpm/server/SPECS/pmm-managed.spec +++ b/build/packages/rpm/server/SPECS/pmm-managed.spec @@ -6,7 +6,7 @@ %global commit 8f3d007617941033867aea6a134c48b39142427f %global shortcommit %(c=%{commit}; echo ${c:0:7}) %define build_timestamp %(date -u +"%y%m%d%H%M") -%define release 21 +%define release 22 %define rpm_release %{release}.%{build_timestamp}.%{shortcommit}%{?dist} # the line below is sed'ed by build/bin/build-server-rpm to set a correct version @@ -39,14 +39,13 @@ export PMM_RELEASE_VERSION=%{full_pmm_version} export PMM_RELEASE_FULLCOMMIT=%{commit} export PMM_RELEASE_BRANCH="" -cd src/github.com/percona/pmm/managed -make release +make -C src/github.com/percona/pmm/managed release %install install -d -p %{buildroot}%{_bindir} install -d -p %{buildroot}%{_sbindir} install -d -p %{buildroot}%{_datadir}/%{name} -install -d -o 1000 %{buildroot}/usr/local/percona/{advisors,checks,alerting-templates} +install -d -o 1000 %{buildroot}/usr/local/percona/{checks,alerting-templates} install -p -m 0755 bin/pmm-managed %{buildroot}%{_sbindir}/pmm-managed install -p -m 0755 bin/pmm-encryption-rotation %{buildroot}%{_sbindir}/pmm-encryption-rotation install -p -m 0755 bin/pmm-managed-init %{buildroot}%{_sbindir}/pmm-managed-init @@ -54,7 +53,6 @@ install -p -m 0755 bin/pmm-managed-starlark %{buildroot}%{_sbindir}/pmm-managed- cd src/github.com/percona/pmm cp -pa ./api/swagger %{buildroot}%{_datadir}/%{name} -cp -pa ./managed/data/advisors/*.yml %{buildroot}/usr/local/percona/advisors/ cp -pa ./managed/data/checks/*.yml %{buildroot}/usr/local/percona/checks/ cp -pa ./managed/data/alerting-templates/*.yml %{buildroot}/usr/local/percona/alerting-templates/ @@ -66,42 +64,46 @@ cp -pa ./managed/data/alerting-templates/*.yml %{buildroot}/usr/local/percona/al %{_sbindir}/pmm-managed-init %{_sbindir}/pmm-managed-starlark %{_datadir}/%{name} -%attr(0644, pmm, root) /usr/local/percona/advisors/*.yml +%attr(0755, pmm, root) %dir /usr/local/percona/checks +%attr(0755, pmm, root) %dir /usr/local/percona/alerting-templates %attr(0644, pmm, root) /usr/local/percona/checks/*.yml %attr(0644, pmm, root) /usr/local/percona/alerting-templates/*.yml %changelog +* Fri Jul 24 2026 Alex Demidoff - 3.0.0-22 +- PMM-14013 improve advisor UX + * Wed Jul 22 2026 Alex Demidoff - 3.0.0-21 - PMM-13776 Move the UI build into the dedicated pmm-ui package -* Thu Sep 4 2025 Michael Okoko - 3.4.0-1 -- PMM-14013 bundle alerting templates with PMM. +* Thu Sep 4 2025 Michael Okoko - 3.0.0-20 +- PMM-14013 bundle alerting templates with PMM -* Wed Jun 11 2025 Michael Okoko - 3.4.0-1 -- PMM-14009 bundle advisors with PMM. +* Wed Jun 11 2025 Michael Okoko - 3.0.0-19 +- PMM-14009 bundle advisors with PMM -* Thu Apr 24 2025 Matej Kubinec - 3.2.0-1 +- Thu Apr 24 2025 Matej Kubinec - 3.0.0-18 - PMM-13722 add pmm compat plugin -* Mon Sep 23 2024 Jiri Ctvrtka - 3.0.0-1 +* Mon Sep 23 2024 Jiri Ctvrtka - 3.0.0-17 - PMM-13132 add PMM encryption rotation tool -* Fri Mar 22 2024 Matej Kubinec - 3.0.0-1 +* Fri Mar 22 2024 Matej Kubinec - 3.0.0-16 - PMM-11231 add pmm ui -* Thu Jul 28 2022 Alex Tymchuk - 2.30.0-1 +* Thu Jul 28 2022 Alex Tymchuk - 2.30.0-15 - PMM-10036 migrate to monorepo -* Fri Jun 17 2022 Anton Bystrov - 2.0.0-17 +* Fri Jun 17 2022 Anton Bystrov - 2.0.0-14 - PMM-10206 merge pmm-managed to monorepo pmm -* Thu Jul 2 2020 Mykyta Solomko - 2.0.0-17 +* Thu Jul 2 2020 Mykyta Solomko - 2.0.0-13 - PMM-5645 built using Golang 1.14 -* Tue May 12 2020 Alexey Palazhchenko - 2.0.0-16 +* Tue May 12 2020 Alexey Palazhchenko - 2.0.0-12 - added pmm-managed-starlark -* Tue Feb 11 2020 Mykyta Solomko - 2.0.0-14 +* Tue Feb 11 2020 Mykyta Solomko - 2.0.0-11 - added pmm-managed-init * Thu Sep 5 2019 Viacheslav Sarzhan - 2.0.0-10 diff --git a/dashboards/dashboards/Insight/Home_Dashboard.json b/dashboards/dashboards/Insight/Home_Dashboard.json index b11bcd0731a..6bfb3399ad9 100644 --- a/dashboards/dashboards/Insight/Home_Dashboard.json +++ b/dashboards/dashboards/Insight/Home_Dashboard.json @@ -1480,24 +1480,70 @@ "type": "stat" }, { - "description": "Number of advisor checks that failed in the most recent run. A non-zero count means PMM has detected conditions that need attention. Review which checks failed and why.", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 13 + "id": 1042, + "type": "stat", + "title": "Connected Agents", + "description": "The current number of connected pmm-agents", + "gridPos": { + "x": 6, + "y": 13, + "h": 5, + "w": 6 + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + }, + "color": { + "mode": "thresholds" + } }, - "id": 1042, - "options": { - "title": "Failed Checks" + "overrides": [] + }, + "pluginVersion": "12.4.5", + "targets": [ + { + "datasource": "Metrics", + "editorMode": "code", + "expr": "count(pmm_managed_inventory_agents{job=\"pmm-managed\"})", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "datasource": "Metrics", + "options": { + "reduceOptions": { + "values": false, + "calcs": [ + "lastNotNull" + ], + "fields": "" }, - "pluginVersion": "11.6.4", - "title": "Failed advisors", - "type": "pmm-check-panel" + "orientation": "auto", + "textMode": "auto", + "wideLayout": true, + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "showPercentChange": false, + "percentChangeColorMode": "standard", + "text": { + "valueSize": 24 + } + } }, { "datasource": "Metrics", diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f74e9f2eb3c..320c5ae88ee 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -46,8 +46,7 @@ services: - go-modules:/root/go/pkg/mod - go-cache:/root/.cache - pmm-data:/srv - # custom advisor files - - ./managed/data/advisors/:/usr/local/percona/advisors/ + # custom advisor checks - ./managed/data/checks/:/usr/local/percona/checks/ - ./managed/data/alerting-templates/:/usr/local/percona/alerting-templates/ # nginx config overrides diff --git a/documentation/docs/advisors/advisor-details.md b/documentation/docs/advisors/advisor-details.md index 7809a30d8ed..3279f7b1fdf 100644 --- a/documentation/docs/advisors/advisor-details.md +++ b/documentation/docs/advisors/advisor-details.md @@ -75,7 +75,7 @@ Every advisor consists of one or more advisor checks. Here is the full list of c | Version configuration| mongodb_version | Provides information on current MongoDB or Percona Server for MongoDB versions used in your environment. It also offers details on other available minor or major versions that you may consider for upgrades. | MongoDB Version Check | | Generic performance| mongodb\_multiple\_services | Warns if multiple mongod services are detected running on a single node. | MongoDB - Multiple mongod Services | | Replication performance| mongodb\_chunk\_imbalance | Warns if the distribution of chunks across shards is imbalanced.| MongoDB Sharding - Chunk Imbalance Across Shards | -| Replication performance| mongodb\_oplog\_size_recommendation |Warns if the oplog window is below a 24-hour period and provides a recommended oplog size based on your instance. | MongoDB - Oplog Recovery Window is Low | +| Replication performance| mongodb\_oplog\_size_recommendation |Warns if the oplog window is below a 24-hour period and provides a recommended oplog size based on your instance. | MongoDB Oplog Recovery Window Low | | Replication performance| mongodb\_replication\_lag | Warns if the replica set member lags behind the primary by more than 10 seconds. | MongoDB Replication Lag | | Index query| mongodb\_shard\_collection\_inconsistent\_indexes | Warns if there are inconsistent indexes across shards for sharded collections. Missing or inconsistent indexes across shards can have a negative impact on performance. | MongoDB Sharding - Inconsistent Indexes Across Shards | | Index query| mongodb\_unused\_index | Warns if there are unused indexes on any database collection in your instance. This requires enabling the "indexStats" collector. | MongoDB - Unused Indexes | @@ -145,7 +145,7 @@ Every advisor consists of one or more advisor checks. Here is the full list of c | :--------- | :---------- | :--- | |Connection configuration| postgresql\_max\_connections_1 | Notifies if the *max_connections* configuration option is set to a high value (above 300). PostgreSQL doesn't cope well with having many connections even if they are idle. The recommended value is below 300. | | Generic configuration | postgresql\_archiver\_failing_1 | Verifies if the archiver has failed. | -| Generic configuration | postgresql\_fsync\_1 | Returns an error if the *fsync* configuration option is set to OFF, as this can lead to database corruptions. | +| Generic configuration | postgresql\_fsync\_1 | Returns an error if the *fsync* configuration option is OFF, as this can lead to database corruption. | | Generic configuration | postgresql\_log\_checkpoints_1 | Notifies if the *log_checkpoints* configuration option is not enabled. It is recommended to enable the logging of checkpoint information, as that provides a lot of useful information with almost no drawbacks. | | Generic configuration | postgresql\_logging\_recommendation_checks | Verifies whether the recommended minimum logging features are enabled.| | Generic configuration | postgresql\_wal\_retention_check | Checks if there are too many WAL files retained in the WAL directory. | diff --git a/documentation/docs/advisors/develop-advisor-checks.md b/documentation/docs/advisors/develop-advisor-checks.md index d64c10213ae..93b0a9f5781 100644 --- a/documentation/docs/advisors/develop-advisor-checks.md +++ b/documentation/docs/advisors/develop-advisor-checks.md @@ -47,7 +47,7 @@ Advisor checks use the following format: summary: Check format V2 description: Checks something important interval: standard - family: MYSQL + technology: MYSQL category: configuration ## Deprecated since PMM 2.36 advisor: dev ## Required since PMM 2.36 queries: @@ -153,20 +153,22 @@ Advisor checks use the following format: The check script assumes that there is a function with `check_context`, that accepts a _list_ where each item represents the result of a single query specified in the check. Each result itself is a _list_ of _docs_ containing returned rows for SQL databases and documents for MongoDB. It returns zero, one, or several check results that are then converted to alerts. +### Finding granularity + +Return one finding per service — or one finding per database for checks that examine per-database objects — rather than one finding per offending object. Aggregate the objects into the finding: put the count in the summary (for example, `3 relation(s) with unused indexes in database app`), list the objects in the description, and set labels such as `database` and `count`. The Advisors Insights list distinguishes findings by their summary and labels, so per-object findings with a shared summary flood the history with rows that look identical. If your check reports issues at different severity levels, return one aggregated finding per severity level. + ## Check severity levels You can label your advisor checks with one of the following available severity levels: -- Emergency -- Alert - Critical - Error - Warning -- Notice - Info -- Debug -PMM groups failed checks by their severity, and displays them under **Advisors Checks > Failed Checks**. +Findings with any other severity level (previously: Emergency, Alert, Notice, Debug) are rejected, and the check run fails with an error. + +PMM groups failed checks by their severity and displays them under **Advisors > Insights**. ## Check fields @@ -176,7 +178,7 @@ Checks can include the following fields: - **Name** (string, required): defines machine-readable name (ID). - **Summary** (string, required): defines short human-readable description. - **Description** (string, required): defines long human-readable description. -- **Family** (string, required): specifies one of the supported database families: MYSQL, POSTGRESQL, MONGODB. This field is only available for Advisor checks v.2. +- **Technology** (string, required): specifies one of the supported database technologies: MYSQL, POSTGRESQL, MONGODB. This field is only available for Advisor checks v.2. - **Advisor** (string, required): specifies the advisor to which this check belongs. For local environments, specify **dev**. - **Interval** (string/enum, optional): defines running interval. Can be one of the predefined intervals in the UI: Standard, Frequent, Rare. - **Queries** (array, required): contains items that specify queries. diff --git a/documentation/docs/install-pmm/install-pmm-server/deployment-options/docker/env_var.md b/documentation/docs/install-pmm/install-pmm-server/deployment-options/docker/env_var.md index 6036dd97063..a0e09b25cdd 100644 --- a/documentation/docs/install-pmm/install-pmm-server/deployment-options/docker/env_var.md +++ b/documentation/docs/install-pmm/install-pmm-server/deployment-options/docker/env_var.md @@ -17,6 +17,7 @@ Fine-tune data retention and collection intervals to balance monitoring detail w | Variable | Default | Description | Example | |----------|---------|-------------|----------| | `PMM_DATA_RETENTION` | `30d` | Duration to retain metrics data (must be in multiples of 24h) | `720h` (30 days) | +| `PMM_ADVISOR_HISTORY_RETENTION` | `30d` | Duration to retain Advisor check results history (must be in multiples of 24h) | `720h` (30 days) | | `PMM_METRICS_RESOLUTION` | `1s` | Base metrics collection interval | `5s` | | `PMM_METRICS_RESOLUTION_HR` | `5s` | High-resolution metrics interval | `10s` | | `PMM_METRICS_RESOLUTION_MR` | `10s` | Medium-resolution metrics interval | `30s` | @@ -44,6 +45,7 @@ Enable or disable specific PMM features: | `PMM_ENABLE_UPDATES` | `true` | Allows version checks and UI updates | | `PMM_ENABLE_TELEMETRY` | `true` | Enables usage data collection | | `PMM_ENABLE_ALERTING` | `true` | Enables Percona Alerting system | +| `PMM_ENABLE_ADVISOR_NOTIFICATIONS` | `false` | Enables email notifications for Advisor check results (requires a configured Grafana email contact point) | | `PMM_ENABLE_BACKUP_MANAGEMENT` | `true` | Enables backup features | | `PMM_ENABLE_AZURE_DISCOVER` | `false` | Enables Azure database discovery | | `PMM_ENABLE_INTERNAL_PG_QAN` | `0` (disabled) | Enables Query Analytics for PMM Server's internal PostgreSQL. Useful for troubleshooting or HA scenarios. Set to `1` to enable. Can also be controlled via **Configuration > Settings > Advanced settings**. See [QAN for PMM Server's internal PostgreSQL](../../../../use/qan/QAN-stored-metrics.md#monitor-pmm-servers-internal-postgresql) diff --git a/go.mod b/go.mod index 1cb367c8e88..af103588f1e 100644 --- a/go.mod +++ b/go.mod @@ -78,6 +78,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad google.golang.org/grpc v1.83.0 google.golang.org/protobuf v1.36.11 + gopkg.in/mail.v2 v2.3.1 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 gopkg.in/reform.v1 v1.5.1 gopkg.in/yaml.v3 v3.0.1 @@ -338,6 +339,7 @@ require ( golang.org/x/term v0.45.0 // indirect google.golang.org/appengine v1.6.8-0.20221117013220-504804fb50de // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 09c030665cf..130ff08cdeb 100644 --- a/go.sum +++ b/go.sum @@ -1079,6 +1079,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1087,6 +1089,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss= gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/reform.v1 v1.5.1 h1:7vhDFW1n1xAPC6oDSvIvVvpRkaRpXlxgJ4QB4s3aDdo= diff --git a/managed/Makefile b/managed/Makefile index 8970e4a5e7e..bbbbebc5932 100644 --- a/managed/Makefile +++ b/managed/Makefile @@ -35,8 +35,8 @@ gen: clean ## Generate files clean: ## Remove generated files find . -name *_reform.go -delete -pi-validate: ## Validate Percona Intelligence advisors and checks - go run ./cmd/pi-validator/main.go advisors --advisors.dir ./data/advisors/ --checks.dir ./data/checks/ +pi-validate: ## Validate Percona Intelligence advisor checks + go run ./cmd/pi-validator/main.go advisors --checks.dir ./data/checks/ release: ## Build pmm-managed release binaries env CGO_ENABLED=0 go build -v $(PMM_LD_FLAGS) -o $(PMM_RELEASE_PATH)/ ./cmd/... diff --git a/managed/cmd/pi-validator/main.go b/managed/cmd/pi-validator/main.go index 2123b9eb333..66b8bf10032 100644 --- a/managed/cmd/pi-validator/main.go +++ b/managed/cmd/pi-validator/main.go @@ -33,8 +33,7 @@ import ( ) type advisorsCommand struct { - AdvisorsDir string `name:"advisors.dir" help:"Advisors directory" default:"data/advisors"` - ChecksDir string `name:"checks.dir" help:"Checks directory" default:"data/checks"` + ChecksDir string `name:"checks.dir" help:"Checks directory" default:"data/checks"` } type templatesCommand struct { @@ -42,7 +41,8 @@ type templatesCommand struct { } func (a *advisorsCommand) Run() error { - return validateAdvisorsAndChecks(a.AdvisorsDir, a.ChecksDir) + _, err := loadAndValidateChecks(a.ChecksDir) + return err } func (t *templatesCommand) Run() error { @@ -60,95 +60,6 @@ func main() { kongCtx.FatalIfErrorf(err) } -func validateAdvisorsAndChecks(advisorsDir, checksDir string) error { - advisors, err := loadAndValidateAdvisors(advisorsDir) - if err != nil { - return err - } - checks, err := loadAndValidateChecks(checksDir) - if err != nil { - return err - } - - for _, c := range checks { - a, ok := advisors[c.Advisor] - if !ok { - log.Fatalf("check '%s' refers unknown advisor '%s'", c.Name, c.Advisor) - } - - a.Checks = append(a.Checks, c) - } - return nil -} - -func loadAndValidateAdvisors(dir string) (map[string]*check.Advisor, error) { - patterns := []string{ - filepath.Join(dir, "*.yml"), - filepath.Join(dir, "*.yml.example"), - } - - var matches []string - - for _, pattern := range patterns { - files, err := filepath.Glob(pattern) - if err != nil { - log.Printf("failed to find advisor files matching '%s': %+v", pattern, err) - } - matches = append(matches, files...) - } - if len(matches) == 0 { - return nil, fmt.Errorf("no advisor files found in %s", dir) - } - - res := make(map[string]*check.Advisor, len(matches)) - for _, file := range matches { - log.Printf("Loading advisor file: %s", file) - _, fileName := filepath.Split(file) - - var validationErrors []error - b, err := os.ReadFile(file) //nolint:gosec - if err != nil { - validationErrors = append(validationErrors, fmt.Errorf("failed to read check file %s: %w", fileName, err)) - } - body := strings.TrimSpace(string(b)) - if !strings.HasPrefix(body, "---") { - validationErrors = append(validationErrors, fmt.Errorf("file %s should start with '---' separator", fileName)) - } - - if len(validationErrors) != 0 { - return nil, errors.Join(validationErrors...) - } - advisors, err := check.ParseAdvisors(strings.NewReader(body), &check.ParseParams{ - DisallowUnknownFields: true, - DisallowInvalidChecks: true, - }) - if err != nil { - validationErrors = append(validationErrors, fmt.Errorf("failed to parse advisors file %s: %w", fileName, err)) - } - - if len(advisors) != 1 { - validationErrors = append(validationErrors, fmt.Errorf("expected exactly one advisor in %s", fileName)) - } - a := advisors[0] - - if a.Name != strings.TrimSuffix(strings.TrimSuffix(fileName, ".example"), ".yml") { - validationErrors = append(validationErrors, fmt.Errorf("advisor name does not match file name %s", file)) - } - - if _, ok := res[a.Name]; ok { - validationErrors = append(validationErrors, fmt.Errorf("advisor name collision detected for: %s", a.Name)) - } - - res[a.Name] = &a - - if len(validationErrors) != 0 { - return nil, errors.Join(validationErrors...) - } - } - - return res, nil -} - func loadAndValidateChecks(dir string) (map[string]check.Check, error) { patterns := []string{ filepath.Join(dir, "*.yml"), @@ -201,6 +112,11 @@ func loadAndValidateChecks(dir string) (map[string]check.Check, error) { validationErrors = append(validationErrors, fmt.Errorf("check name does not match file name %s", file)) } + if strings.HasPrefix(c.Name, check.UserCheckNamePrefix) { + validationErrors = append(validationErrors, + fmt.Errorf("check %s uses the name prefix '%s' reserved for user-authored checks", c.Name, check.UserCheckNamePrefix)) + } + if _, ok := res[c.Name]; ok { validationErrors = append(validationErrors, fmt.Errorf("check name collision detected for: %s", c.Name)) } diff --git a/managed/cmd/pmm-managed-starlark/main.go b/managed/cmd/pmm-managed-starlark/main.go index c70b0ec3cc1..919a08659f8 100644 --- a/managed/cmd/pmm-managed-starlark/main.go +++ b/managed/cmd/pmm-managed-starlark/main.go @@ -41,6 +41,9 @@ const ( cpuLimit = 4 * time.Second memoryLimitBytes = 1024 * 1024 * 1024 + // File descriptor pmm-managed wires for the captured print() output. + printOutputFD = 3 + // Only used for testing. starlarkRecursionFlag = "PMM_DEV_ADVISOR_STARLARK_ALLOW_RECURSION" @@ -90,20 +93,21 @@ func main() { var data checks.StarlarkScriptData err = decoder.Decode(&data) if err != nil { - l.Errorf("Error decoding json data: %s", err) + // write to stderr as plain text so pmm-managed can surface the cause instead of a bare exit code + fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } results, err := runChecks(l, &data) if err != nil { - l.Errorf("Error running starlark script: %+v", err) + fmt.Fprintf(os.Stderr, "%+v\n", err) os.Exit(1) } encoder := json.NewEncoder(os.Stdout) err = encoder.Encode(results) if err != nil { - l.Errorf("Error encoding JSON results: %s", err) + fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } } @@ -111,7 +115,7 @@ func main() { func runChecks(l *logrus.Entry, data *checks.StarlarkScriptData) ([]check.Result, error) { funcs, err := checks.GetFuncsForVersion(data.Version) if err != nil { - return nil, fmt.Errorf("error getting funcs: %w", err) + return nil, err } env, err := starlark.NewEnv(data.Name, data.Script, funcs) @@ -145,16 +149,22 @@ func runChecks(l *logrus.Entry, data *checks.StarlarkScriptData) ([]check.Result } } + // print() output is normally debug-logged; for check test runs it is emitted + // as plain lines on the dedicated pipe (fd 3, wired by pmm-managed) so it + // reaches the check author without mixing into stderr's error channel + var printFn starlark.PrintFunc = l.Debugln + if data.CapturePrintOutput { + printOut := os.NewFile(printOutputFD, "print-output") + printFn = func(args ...any) { + _, _ = fmt.Fprintln(printOut, args...) + } + } + var results []check.Result contextFuncs := checks.GetAdditionalContext() - switch data.Version { - case 1: - results, err = env.Run(data.Name, res[0], contextFuncs, l.Debugln) - case 2: //nolint:mnd - results, err = env.Run(data.Name, res, contextFuncs, l.Debugln) - } + results, err = env.Run(data.Name, res, contextFuncs, printFn) if err != nil { - return nil, fmt.Errorf("error running starlark env: %w", err) + return nil, err } return results, nil @@ -163,12 +173,12 @@ func runChecks(l *logrus.Entry, data *checks.StarlarkScriptData) ([]check.Result func unmarshalQueryResult(qr string) ([]map[string]any, error) { b, err := base64.StdEncoding.DecodeString(qr) if err != nil { - return nil, fmt.Errorf("failed to decode base64 encoded query result: %w", err) + return nil, err } res, err := agentv1.UnmarshalActionQueryResult(b) if err != nil { - return nil, fmt.Errorf("failed to unmarshal query result: %w", err) + return nil, err } return res, nil diff --git a/managed/cmd/pmm-managed-starlark/main_test.go b/managed/cmd/pmm-managed-starlark/main_test.go index ef147f9039b..c261fe3b414 100644 --- a/managed/cmd/pmm-managed-starlark/main_test.go +++ b/managed/cmd/pmm-managed-starlark/main_test.go @@ -19,6 +19,7 @@ import ( "bytes" "context" "encoding/json" + "io" "os" "os/exec" "testing" @@ -33,7 +34,7 @@ import ( ) const ( - invalidStarlarkScriptStderr = "Error running starlark script: error running starlark env: thread invalid starlark script: failed to execute function check_context: function check_context accepts no arguments (2 given)" + invalidStarlarkScriptStderr = "failed to execute function check_context: function check_context accepts no arguments (2 given)" // Possible errors: // fatal error: runtime: out of memory @@ -132,7 +133,7 @@ func TestStarlarkSandbox(t *testing.T) { //nolint:tparallel if !present { releasePath = "./../../bin" } - cmd := exec.Command(releasePath + "/pmm-managed-starlark") //nolint:gosec + cmd := exec.CommandContext(t.Context(), releasePath+"/pmm-managed-starlark") //nolint:gosec var stdin, stderr bytes.Buffer cmd.Stdin = &stdin @@ -168,3 +169,96 @@ func TestStarlarkSandbox(t *testing.T) { //nolint:tparallel }) } } + +func TestPrintOutputCapture(t *testing.T) { //nolint:tparallel + testCases := []struct { + name string + script string + wantErr bool + stderrPart string + }{ + { + name: "prints are captured on success", + script: "def check_context(rows, context):\n print(\"debug line\", 42)\n return []", + wantErr: false, + }, + { + name: "prints stay out of the error output on failure", + script: "def check_context(rows, context):\n print(\"debug line\", 42)\n return rows[99]", + wantErr: true, + stderrPart: "failed to execute function check_context", + }, + } + + ctx, cancel := context.WithTimeout(t.Context(), 120*time.Second) + t.Cleanup(cancel) + // since we run the binary as a child process to test it we need to build it first. + command := exec.CommandContext(ctx, "make", "-C", "../..", "release-starlark") + command.Stdout = os.Stdout + command.Stderr = os.Stderr + err := command.Run() + require.NoError(t, err) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + result, err := agentv1.MarshalActionQueryDocsResult(validQueryActionResult) + require.NoError(t, err) + + data := &checks.StarlarkScriptData{ + Version: 1, + Name: "print_capture", + Script: tc.script, + QueriesResults: []any{result}, + CapturePrintOutput: true, + } + + releasePath, present := os.LookupEnv("PMM_RELEASE_PATH") + if !present { + releasePath = "./../../bin" + } + cmd := exec.CommandContext(t.Context(), releasePath+"/pmm-managed-starlark") //nolint:gosec + + // the pipe pmm-managed wires as fd 3 for the print() output + printR, printW, err := os.Pipe() + require.NoError(t, err) + cmd.ExtraFiles = []*os.File{printW} + + var stdin, stderr bytes.Buffer + cmd.Stdin = &stdin + cmd.Stderr = &stderr + cmd.Env = []string{starlarkRecursionFlag + "=1"} + + encoder := json.NewEncoder(&stdin) + err = encoder.Encode(data) + require.NoError(t, err) + + err = cmd.Start() + require.NoError(t, err) + // close our copy of the write end so the read below sees EOF once the child exits + require.NoError(t, printW.Close()) + runErr := cmd.Wait() + + printOutput, err := io.ReadAll(printR) + require.NoError(t, err) + require.NoError(t, printR.Close()) + + if tc.wantErr { + require.Error(t, runErr) + } else { + require.NoError(t, runErr) + } + + // print() output arrives on the dedicated pipe, prefixed with the source position... + assert.Contains(t, string(printOutput), "print_capture -> check_context:2:") + assert.Contains(t, string(printOutput), "-> debug line 42") + // ...and never pollutes the stderr error channel + stderrContent := stderr.String() + assert.NotContains(t, stderrContent, "debug line") + if tc.stderrPart != "" { + assert.Contains(t, stderrContent, tc.stderrPart) + } + }) + } +} diff --git a/managed/cmd/pmm-managed/main.go b/managed/cmd/pmm-managed/main.go index 2468e9778a0..bf428e35b9d 100644 --- a/managed/cmd/pmm-managed/main.go +++ b/managed/cmd/pmm-managed/main.go @@ -133,6 +133,8 @@ const ( cleanInterval = 10 * time.Minute cleanOlderThan = 30 * time.Minute + advisorHistoryCleanInterval = time.Hour + defaultContextTimeout = 10 * time.Second pProfProfileDuration = 30 * time.Second pProfTraceDuration = 10 * time.Second @@ -907,6 +909,7 @@ func main() { //nolint:gocognit,maintidx,cyclop } cleaner := clean.New(db) + advisorHistoryCleaner := clean.NewInsights(db) externalRules := vmalert.NewExternalRules() vmdb, err := victoriametrics.NewVictoriaMetrics(*victoriaMetricsConfigF, db, vmParams, chParams, haService) if err != nil { @@ -1230,6 +1233,11 @@ func main() { //nolint:gocognit,maintidx,cyclop return nil })) + haService.AddLeaderService(ha.NewContextService("advisor-history-cleaner", func(ctx context.Context) error { + advisorHistoryCleaner.Run(ctx, advisorHistoryCleanInterval) + return nil + })) + wg.Go(func() { err := haService.Run(ctx) if err != nil { diff --git a/managed/data/advisors/configuration_connection.yml b/managed/data/advisors/configuration_connection.yml deleted file mode 100644 index 14f92053cc6..00000000000 --- a/managed/data/advisors/configuration_connection.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: configuration_connection - summary: Connection Configuration - description: Provides recommendations on configuring database connection parameters for improving database performance. - category: configuration diff --git a/managed/data/advisors/configuration_generic.yml b/managed/data/advisors/configuration_generic.yml deleted file mode 100644 index 6d59052d67a..00000000000 --- a/managed/data/advisors/configuration_generic.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: configuration_generic - summary: Generic Configuration - description: Provides basic recommendations for improving your database configuration. - category: configuration diff --git a/managed/data/advisors/configuration_innodb.yml b/managed/data/advisors/configuration_innodb.yml deleted file mode 100644 index 92b6598b1a7..00000000000 --- a/managed/data/advisors/configuration_innodb.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: configuration_innodb - summary: InnoDB Configuration - description: Advises on configuring InnoDB optimization for high performance. - category: configuration diff --git a/managed/data/advisors/configuration_replication.yml b/managed/data/advisors/configuration_replication.yml deleted file mode 100644 index 98480494b8b..00000000000 --- a/managed/data/advisors/configuration_replication.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: configuration_replication - summary: Replication Configuration - description: Provides recommendations for scalable replication in database clusters. - category: configuration diff --git a/managed/data/advisors/configuration_resources.yml b/managed/data/advisors/configuration_resources.yml deleted file mode 100644 index 3007b3a52f6..00000000000 --- a/managed/data/advisors/configuration_resources.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: configuration_resources - summary: Resources Configuration - description: Watches your database and gives you recommendations for efficient management of resources like binaries architecture, CPU number versus DB Configuration, etc. - category: configuration diff --git a/managed/data/advisors/configuration_vacuum.yml b/managed/data/advisors/configuration_vacuum.yml deleted file mode 100644 index e864e21b0f9..00000000000 --- a/managed/data/advisors/configuration_vacuum.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: configuration_vacuum - summary: Vacuum Configuration - description: Provides recommendations on optimizing Vacuum configuration. - category: configuration diff --git a/managed/data/advisors/configuration_version.yml b/managed/data/advisors/configuration_version.yml deleted file mode 100644 index ed0f0bf7f28..00000000000 --- a/managed/data/advisors/configuration_version.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: configuration_version - summary: Version Configuration - description: Notifies of newly released database versions to streamline database maintenance and ensure the most up-to-date performance. - category: configuration diff --git a/managed/data/advisors/example.yml.example b/managed/data/advisors/example.yml.example deleted file mode 100644 index f2e531670e6..00000000000 --- a/managed/data/advisors/example.yml.example +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: example - summary: Example advisor - description: Advisor that gives awesome advises. - category: security diff --git a/managed/data/advisors/performance_generic.yml b/managed/data/advisors/performance_generic.yml deleted file mode 100644 index 190490b5f16..00000000000 --- a/managed/data/advisors/performance_generic.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: performance_generic - summary: Generic Performance - description: Provides basic database configuration recommendations for high-performance query execution. - category: performance diff --git a/managed/data/advisors/performance_replication.yml b/managed/data/advisors/performance_replication.yml deleted file mode 100644 index 63b04784575..00000000000 --- a/managed/data/advisors/performance_replication.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: performance_replication - summary: Replication Performance - description: Checks efficient replication usage of your database. - category: performance diff --git a/managed/data/advisors/performance_vacuum.yml b/managed/data/advisors/performance_vacuum.yml deleted file mode 100644 index f90f9865099..00000000000 --- a/managed/data/advisors/performance_vacuum.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: performance_vacuum - summary: Vacuum Performance - description: Helps improve the efficiency and execution speed of database Vacuum operations. - category: configuration diff --git a/managed/data/advisors/query_index.yml b/managed/data/advisors/query_index.yml deleted file mode 100644 index 129167e6caa..00000000000 --- a/managed/data/advisors/query_index.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: query_index - summary: Index Query - description: Provides query and index optimization strategies for peak database performance. - category: query diff --git a/managed/data/advisors/query_schema_design.yml b/managed/data/advisors/query_schema_design.yml deleted file mode 100644 index c31d78e5529..00000000000 --- a/managed/data/advisors/query_schema_design.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: query_schema_design - summary: Schema Design Query - description: Helps create efficient database schemas by analyzing queries and offering suggestions for optimization. - category: query diff --git a/managed/data/advisors/security_authentication.yml b/managed/data/advisors/security_authentication.yml deleted file mode 100644 index 5d40ee528ba..00000000000 --- a/managed/data/advisors/security_authentication.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: security_authentication - summary: Authentication Security - description: Ensures that all database authentication parameters are configured securely. - category: security diff --git a/managed/data/advisors/security_configuration.yml b/managed/data/advisors/security_configuration.yml deleted file mode 100644 index 0be2be5db20..00000000000 --- a/managed/data/advisors/security_configuration.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: security_configuration - summary: Configuration Security - description: Checks your database configuration to ensure that security best practices are correctly implemented. - category: security diff --git a/managed/data/advisors/security_connection.yml b/managed/data/advisors/security_connection.yml deleted file mode 100644 index e62766ac507..00000000000 --- a/managed/data/advisors/security_connection.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: security_connection - summary: Connection Security - description: Helps identify security issues on network connections and provides recommendations for enhancing security. - category: security diff --git a/managed/data/advisors/security_cve.yml b/managed/data/advisors/security_cve.yml deleted file mode 100644 index f63cad0a2e7..00000000000 --- a/managed/data/advisors/security_cve.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: security_cve - summary: CVE Security - description: Informs you of any database versions affected by CVE. - category: security diff --git a/managed/data/advisors/security_replication.yml b/managed/data/advisors/security_replication.yml deleted file mode 100644 index bd4f40e3995..00000000000 --- a/managed/data/advisors/security_replication.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -advisors: - - version: 1 - name: security_replication - summary: Replication Security - description: Helps safeguard data replication by assessing security risks and providing recommendations for improving protection. - category: security diff --git a/managed/data/checks/exampleV2.yml.example b/managed/data/checks/exampleV2.yml.example index 481ac1e0a0f..1b54d8cf9e5 100644 --- a/managed/data/checks/exampleV2.yml.example +++ b/managed/data/checks/exampleV2.yml.example @@ -5,8 +5,9 @@ checks: summary: Check format V2 description: Checks something important interval: standard - advisor: example - family: MYSQL + category: Security + subcategory: Example + technology: MYSQL queries: - type: MYSQL_SHOW query: VARIABLES diff --git a/managed/data/checks/mongodb_EOL.yml b/managed/data/checks/mongodb_EOL.yml index 0ac7a132f82..ca2ffb076ad 100644 --- a/managed/data/checks/mongodb_EOL.yml +++ b/managed/data/checks/mongodb_EOL.yml @@ -5,11 +5,10 @@ checks: summary: MongoDB version EOL description: This check returns errors or warnings if your current PSMDB or MongoDB version has reached or is about to reach End-of-Life. interval: standard - family: MONGODB - advisor: configuration_version - #category: configuration - #subcategory: version configuration - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Version + # author: Parag Bhayani queries: - type: MONGODB_BUILDINFO script: | diff --git a/managed/data/checks/mongodb_active_vs_available_connections.yml b/managed/data/checks/mongodb_active_vs_available_connections.yml index 6cf2cf53496..10041d2aeac 100644 --- a/managed/data/checks/mongodb_active_vs_available_connections.yml +++ b/managed/data/checks/mongodb_active_vs_available_connections.yml @@ -5,9 +5,10 @@ checks: name: mongodb_active_vs_available_connections summary: MongoDB Active vs Available Connections description: This check returns warnings if the ratio between active and available connections is higher than 75% - family: MONGODB - #author: Corrado Pandiani - advisor: configuration_generic + technology: MONGODB + # author: Corrado Pandiani + category: Configuration + subcategory: Generic interval: standard queries: - type: MONGODB_GETDIAGNOSTICDATA @@ -19,7 +20,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] diff --git a/managed/data/checks/mongodb_auth.yml b/managed/data/checks/mongodb_auth.yml index 3b7e6332930..e08acf876c3 100644 --- a/managed/data/checks/mongodb_auth.yml +++ b/managed/data/checks/mongodb_auth.yml @@ -4,8 +4,9 @@ checks: name: mongodb_auth summary: MongoDB authentication description: Warns if MongoDB authentication is disabled. - family: MONGODB - advisor: security_authentication + technology: MONGODB + category: Security + subcategory: Authentication interval: standard queries: - type: MONGODB_GETCMDLINEOPTS @@ -17,7 +18,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] diff --git a/managed/data/checks/mongodb_authmech_scramsha256.yml b/managed/data/checks/mongodb_authmech_scramsha256.yml index d0e48bbec97..6c8e4036d76 100644 --- a/managed/data/checks/mongodb_authmech_scramsha256.yml +++ b/managed/data/checks/mongodb_authmech_scramsha256.yml @@ -4,9 +4,10 @@ checks: name: mongodb_authmech_scramsha256 summary: MongoDB Security AuthMech Check description: This check returns warnings if MongoDB is not using the default SHA-256 hashing function as its SCRAM authentication method. - family: MONGODB - #author: Kimberly Wilkins - advisor: security_configuration + technology: MONGODB + # author: Kimberly Wilkins + category: Security + subcategory: Configuration interval: standard queries: - type: MONGODB_GETPARAMETER @@ -20,11 +21,11 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] - parsed = rows[0]["parsed"] - authMechanism = "SCRAM-SHA-256" not in parsed.get("authenicationMechanisms") + authMechanisms = rows[0].get("authenticationMechanisms", []) + authMechanism = "SCRAM-SHA-256" not in authMechanisms if authMechanism: results.append({ diff --git a/managed/data/checks/mongodb_balancer.yml b/managed/data/checks/mongodb_balancer.yml index 33de3a10a04..de4b7a038d1 100644 --- a/managed/data/checks/mongodb_balancer.yml +++ b/managed/data/checks/mongodb_balancer.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB Balancer is disabled description: This check warns if the balancer process is disabled interval: standard - family: MONGODB - advisor: performance_replication - #author: Parag bhayani + technology: MONGODB + category: Performance + subcategory: Replication + # author: Parag bhayani queries: - type: METRICS_INSTANT query: avg by(cluster) (mongodb_mongos_sharding_chunks_is_balancer_running{node_name=~"{{.NodeName}}"}) @@ -17,14 +18,19 @@ checks: def check_context(docs, context): results = [] + clusters = [] for row in docs[0]: cluster = row["metric"]["cluster"] chunk_balance = int(row["value"][1]) if chunk_balance != 1: - results.append({ - "summary": "The balancer is disabled", - "description": "The balancer process is disabled on <{}> cluster. This causes uneven data distribution between shards. Please check the following documentation to learn more.".format(cluster), - "read_more_url": read_url.format("mongodb-chunk-imbalance"), - "severity": "warning", - }) - return results + clusters.append(cluster) + + if clusters: + results.append({ + "summary": "The balancer is disabled on {} cluster(s)".format(len(clusters)), + "description": "The balancer process is disabled on the following cluster(s): {}. This causes uneven data distribution between shards. Please check the following documentation to learn more.".format(", ".join(clusters)), + "read_more_url": read_url.format("mongodb-chunk-imbalance"), + "severity": "warning", + "labels": {"count": str(len(clusters))}, + }) + return results diff --git a/managed/data/checks/mongodb_bindip.yml b/managed/data/checks/mongodb_bindip.yml index c86a0ac61f4..27d9311930b 100644 --- a/managed/data/checks/mongodb_bindip.yml +++ b/managed/data/checks/mongodb_bindip.yml @@ -4,9 +4,10 @@ checks: name: mongodb_bindip summary: MonogDB IP bindings description: This check returns warnings if the MongoDB network binding is not set as recommended. - family: MONGODB - #author: Divyanshu Soni - advisor: security_connection + technology: MONGODB + # author: Divyanshu Soni + category: Security + subcategory: Connection interval: standard queries: - type: MONGODB_GETCMDLINEOPTS @@ -20,7 +21,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] parsed = rows[0]["parsed"] diff --git a/managed/data/checks/mongodb_cache_size.yml b/managed/data/checks/mongodb_cache_size.yml index 5a4893abe95..41c794b8ffc 100644 --- a/managed/data/checks/mongodb_cache_size.yml +++ b/managed/data/checks/mongodb_cache_size.yml @@ -5,8 +5,9 @@ checks: summary: Mongo Storage Cache description: Mongo wiredtiger cache size is greater then default 50% interval: standard - family: MONGODB - advisor: configuration_generic + technology: MONGODB + category: Configuration + subcategory: Generic queries: - type: METRICS_INSTANT query: mongodb_sys_memory_MemTotal_kb{node_name="{{.NodeName}}"} @@ -20,14 +21,14 @@ checks: results = [] for row in docs[0]: - state1 = row["metric"]["rs_state"] + state1 = row["metric"].get("rs_state", "") orig = int(int(row["value"][1])/1024) maxValue = int(orig/2) if state1 == "0" or state1 == "7": return results for type in docs[1]: - state = type["metric"]["rs_state"] + state = type["metric"].get("rs_state", "") valueB = int(int(type["value"][1])/1024/1024) if state == "0" or state == "7": return results diff --git a/managed/data/checks/mongodb_clickhouse.yml.example b/managed/data/checks/mongodb_clickhouse.yml.example index 9e1d1fd1988..a89f9de135b 100644 --- a/managed/data/checks/mongodb_clickhouse.yml.example +++ b/managed/data/checks/mongodb_clickhouse.yml.example @@ -3,11 +3,11 @@ checks: - version: 2 name: mongodb_clickhouse summary: Sample Clickhouse advisor that checks if QAN is on a MongoDB instance - advisor: example + category: Security + subcategory: Example description: Sample Clickhouse advisor that checks if QAN is on a MongoDB instance interval: standard - family: MONGODB - category: configuration + technology: MONGODB queries: - type: CLICKHOUSE_SELECT query: service_id, service_name, COUNT(*) as count FROM metrics WHERE service_id='{{.ServiceID}}' GROUP BY (service_id,service_name); diff --git a/managed/data/checks/mongodb_collection_fragmented.yml b/managed/data/checks/mongodb_collection_fragmented.yml index 030c86fc61b..ba1fbf5e28b 100644 --- a/managed/data/checks/mongodb_collection_fragmented.yml +++ b/managed/data/checks/mongodb_collection_fragmented.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB Collections Fragmented description: This check returns a warning if the Storage size is greater than the Data size of a collection. That condition indicates that the collection is fragmented and needs a Compaction or Initial sync to reclaim disk space. interval: rare - family: MONGODB - advisor: configuration_resources - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Resources + # author: Parag Bhayani queries: - type: MONGODB_GETCMDLINEOPTS - type: METRICS_INSTANT @@ -30,10 +31,16 @@ checks: results = [] #Gather DBPATH details. + dbpath = "" for row in info: - dbpath = row["parsed"]["storage"]["dbPath"] + parsed = row["parsed"] + if "storage" in parsed and "dbPath" in parsed["storage"]: + dbpath = parsed["storage"]["dbPath"] + if not dbpath: + return results #Gather DBPATH mount point details and it's disk usage percentage. + usage_per = 0 for type in docs[1]: mount = type["metric"]["mountpoint"] usage = float(type["value"][1]) diff --git a/managed/data/checks/mongodb_connection_sudden_spike.yml b/managed/data/checks/mongodb_connection_sudden_spike.yml index 42814700dc5..594a5b50185 100644 --- a/managed/data/checks/mongodb_connection_sudden_spike.yml +++ b/managed/data/checks/mongodb_connection_sudden_spike.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB - sudden increase in connection count description: This check returns a warning if there is an increase in the number of connections that is higher than 50% of the most recent or normal number of connections. interval: standard - family: MONGODB - advisor: configuration_connection - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Connection + # author: Parag Bhayani queries: - type: METRICS_RANGE query: avg by (node_name) (mongodb_connections{state="current", node_name=~"{{.NodeName}}"}) @@ -46,7 +47,7 @@ checks: if cnt > 0: results.append({ "summary": "MongoDB sudden spike(> 50%) in connections than the usual count in last 24hrs", - "description": "In the last 24 hours, we observed that there were {} times that a sudden spike (> 50%) in the number of connections occurred that was higher than the most recent or normal number of connections. This increase could indicate or cause performance issues unless your overall system is designed to handle spikes of this type. Please check for any changes that may have occurred during that time span to cause this. Here are the stats respectively - Spiked Connections - {}, Previous Connections - {}, Percentage of Spike - {} ".format(cnt, current_conn, previous_conn, actual_perc), + "description": "In the last 24 hours, we observed that there were {} times that a sudden spike (> 50%) in the number of connections occurred that was higher than the most recent or normal number of connections. This increase could indicate or cause performance issues unless your overall system is designed to handle spikes of this type. Please check for any changes that may have occurred during that time span to cause this. Here are the stats respectively - Spiked Connections - {}, Previous Connections - {}, Percentage of Spike - {}.".format(cnt, current_conn, previous_conn, actual_perc), "read_more_url": read_url.format("mongodb-connection-spike"), "severity": "warning", "labels": {}, diff --git a/managed/data/checks/mongodb_connections.yml b/managed/data/checks/mongodb_connections.yml index 514c5d3c3b2..2e7a1017263 100644 --- a/managed/data/checks/mongodb_connections.yml +++ b/managed/data/checks/mongodb_connections.yml @@ -3,13 +3,12 @@ checks: - version: 2 name: mongodb_connections summary: MongoDB High connections - description: This check returns the current number of connections as an informational notice when connection counts are above 5000. + description: This check returns the current number of connections as a warning when connection counts are above 5000. interval: standard - family: MONGODB - advisor: configuration_connection - #category: configuration - #subcategory: connection - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Connection + # author: Parag Bhayani queries: - type: MONGODB_GETDIAGNOSTICDATA script: | @@ -17,7 +16,7 @@ checks: def check_context(docs, context): """ - This check returns a notice if the number of connections is above a default threshold - currently set to 5000. While not necessarily a high number, it can highlight potential configuration issues with connection or driver settings that may lead to increased memory usage if unintended. + This check returns a warning if the number of connections is above a default threshold - currently set to 5000. While not necessarily a high number, it can highlight potential configuration issues with connection or driver settings that may lead to increased memory usage if unintended. """ format_version_num = context.get("format_version_num", fail) @@ -35,7 +34,7 @@ checks: "summary": "The current number of connections for this MongoDB node are - {} ".format(connections), "description": "Please confirm your desired driver and connection settings. You can use mongostat and the currentOp command to see what operations the connections are running. For more information please check the Read More documentation.", "read_more_url": read_url.format("mongo-high-connections"), - "severity": "notice", + "severity": "warning", }) return results diff --git a/managed/data/checks/mongodb_cpucores.yml b/managed/data/checks/mongodb_cpucores.yml index f904f1d9e69..12dc483cbdc 100644 --- a/managed/data/checks/mongodb_cpucores.yml +++ b/managed/data/checks/mongodb_cpucores.yml @@ -4,9 +4,10 @@ checks: name: mongodb_cpucores summary: MongoDB CPU cores description: This check returns warnings if the number of CPU cores does not meet the minimum recommended requirements according to best practices. - family: MONGODB - #author: Parag Bhayani - advisor: configuration_resources + technology: MONGODB + # author: Parag Bhayani + category: Configuration + subcategory: Resources interval: standard queries: - type: MONGODB_GETDIAGNOSTICDATA @@ -31,7 +32,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) print(repr(rows[0])) data = rows[0]["data"] @@ -52,14 +53,14 @@ checks: cpu = systemMetrics.get("cpu", {}) print(repr(cpu)) if cpu: - numcpu = int(cpu.get("num_cpus", fail)) + numcpu = int(cpu.get("num_cpus", cpu.get("num_logical_cores", 0))) print("No. of CPU core:") print(numcpu) - if numcpu < 4: + if numcpu > 0 and numcpu < 4: results.append({ "summary": "MongoDB node as configured does not have the recommended number of CPU cores - {}".format(numcpu), "description": "The returned number of cores is lower than recommended. To avoid performance issues, see the following documentation to determine the number of CPU cores required for your environment", - "read_more_url": PROD_NOTES["mongodb_url"][mm], + "read_more_url": PROD_NOTES["mongodb_url"].get(mm, ""), "severity": "warning", }) # noresults = "no maxS result" diff --git a/managed/data/checks/mongodb_cve_2025_14847_zlib.yml b/managed/data/checks/mongodb_cve_2025_14847_zlib.yml index 0ea40c81fc6..8964987625c 100644 --- a/managed/data/checks/mongodb_cve_2025_14847_zlib.yml +++ b/managed/data/checks/mongodb_cve_2025_14847_zlib.yml @@ -2,14 +2,15 @@ checks: - version: 2 name: mongodb_cve_2025_14847_zlib - summary: MongoDB CVE-2025-14847 - Zlib Compression Heap Memory Vulnerability + summary: MongoDB Zlib Compression Heap Memory Vulnerability (CVE-2025-14847) description: | Checks if MongoDB Server is vulnerable to CVE-2025-14847 with active Zlib compression. Fixed versions: 4.4.30+, 5.0.32+, 6.0.27+, 7.0.28+, 8.0.17+, 8.2.3+ EOL (no fix): 3.6.x, 4.0.x, 4.2.x interval: frequent - family: MONGODB - advisor: security_cve + technology: MONGODB + category: Security + subcategory: Vulnerabilities queries: - type: MONGODB_BUILDINFO - type: METRICS_INSTANT @@ -36,7 +37,7 @@ checks: build_info = docs[0] if len(build_info) != 1: - return "Unexpected number of documents from buildInfo" + return "unexpected number of documents from buildInfo: {}".format(len(build_info)) info = build_info[0] version = parse_version(info["version"]) diff --git a/managed/data/checks/mongodb_cve_version.yml b/managed/data/checks/mongodb_cve_version.yml index 9be698c76e0..37705b50023 100644 --- a/managed/data/checks/mongodb_cve_version.yml +++ b/managed/data/checks/mongodb_cve_version.yml @@ -4,8 +4,9 @@ checks: name: mongodb_cve_version summary: MongoDB CVE Version description: This check returns errors if MongoDB or Percona Server for MongoDB version is less than the latest one with CVE fixes. - family: MONGODB - advisor: security_cve + technology: MONGODB + category: Security + subcategory: Vulnerabilities interval: standard queries: - type: MONGODB_BUILDINFO @@ -58,7 +59,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) info = rows[0] @@ -82,12 +83,14 @@ checks: "summary": "MongoDB CVE Version check is available for Percona Server since PMM 2.17.0", "description": "", "read_more_url": "https://www.percona.com/doc/percona-monitoring-and-management/2.x/release-notes/index.html", - "severity": "notice", + "severity": "warning", "labels": {}, }) return results + if mm not in LATEST_VERSIONS["percona"]: + return results latest = LATEST_VERSIONS["percona"][mm] if latest > numcve: results.append({ @@ -104,6 +107,8 @@ checks: return results if True: # MongoDB + if mm not in LATEST_VERSIONS["mongodb"]: + return results latest = LATEST_VERSIONS["mongodb"][mm] if latest > num: results.append({ diff --git a/managed/data/checks/mongodb_dbpath_mount.yml b/managed/data/checks/mongodb_dbpath_mount.yml index 0b9443031db..51a87e15f07 100644 --- a/managed/data/checks/mongodb_dbpath_mount.yml +++ b/managed/data/checks/mongodb_dbpath_mount.yml @@ -2,12 +2,13 @@ checks: - version: 2 name: mongodb_dbpath_mount - summary: MongoDB - separate mount point other than "/" partition for dbpath. + summary: MongoDB separate mount point other than "/" partition for dbpath description: This check returns a warning if dbpath does not have a dedicated mount point. interval: rare - family: MONGODB - advisor: configuration_resources - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Resources + # author: Parag Bhayani queries: - type: MONGODB_GETCMDLINEOPTS - type: METRICS_INSTANT @@ -23,10 +24,10 @@ checks: #Gather DBPATH details. for row in info: parsed = row["parsed"] - if "storage" in parsed: // Check if key exists - storage = parsed["storage"] // We already know that it's exists, so we can just take the value - if "dbPath" in storage: // Check if key exists - dbpath = storage["dbPath"] // Same thing, we already know that key exists + if "storage" in parsed: # Check if key exists + storage = parsed["storage"] # We already know that it exists, so we can just take the value + if "dbPath" in storage: # Check if key exists + dbpath = storage["dbPath"] # Same thing, we already know that key exists if not parsed.get("storage", {}): return results diff --git a/managed/data/checks/mongodb_fcv_check.yml b/managed/data/checks/mongodb_fcv_check.yml index 1e3da3cee46..932f10c8468 100644 --- a/managed/data/checks/mongodb_fcv_check.yml +++ b/managed/data/checks/mongodb_fcv_check.yml @@ -5,10 +5,10 @@ checks: summary: MongoDB - FCV mismatch description: This check returns a warning if there is a mismatch between the MongoDB version and the internal FCV parameter setting. interval: standard - family: MONGODB - advisor: configuration_resources - #category: configuration - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Resources + # author: Parag Bhayani queries: - type: MONGODB_BUILDINFO - type: MONGODB_GETPARAMETER diff --git a/managed/data/checks/mongodb_journal.yml b/managed/data/checks/mongodb_journal.yml index 8023befdca5..d4d6581a4ca 100644 --- a/managed/data/checks/mongodb_journal.yml +++ b/managed/data/checks/mongodb_journal.yml @@ -5,9 +5,10 @@ checks: name: mongodb_journal summary: MongoDB Journal description: This check returns warnings if journal is disabled. - family: MONGODB - #author: Corrado Pandiani - advisor: configuration_generic + technology: MONGODB + # author: Corrado Pandiani + category: Configuration + subcategory: Generic interval: standard queries: - type: MONGODB_GETCMDLINEOPTS @@ -21,7 +22,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] parsed = rows[0]["parsed"] diff --git a/managed/data/checks/mongodb_localhost_auth_bypass.yml b/managed/data/checks/mongodb_localhost_auth_bypass.yml index c1841a7c4b0..a90f5642d69 100644 --- a/managed/data/checks/mongodb_localhost_auth_bypass.yml +++ b/managed/data/checks/mongodb_localhost_auth_bypass.yml @@ -4,9 +4,10 @@ checks: name: mongodb_localhost_auth_bypass summary: MongoDB localhost authentication bypass enabled description: This check returns warnings if MongoDB localhost bypass is enabled. - family: MONGODB - #author: Divyanshu Soni - advisor: security_authentication + technology: MONGODB + # author: Divyanshu Soni + category: Security + subcategory: Authentication interval: standard queries: - type: MONGODB_GETCMDLINEOPTS @@ -20,7 +21,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] parsed = rows[0]["parsed"] diff --git a/managed/data/checks/mongodb_loglevel.yml b/managed/data/checks/mongodb_loglevel.yml index d3005e5ff4e..1960b3752f5 100644 --- a/managed/data/checks/mongodb_loglevel.yml +++ b/managed/data/checks/mongodb_loglevel.yml @@ -4,9 +4,10 @@ checks: name: mongodb_loglevel summary: MongoDB Non-Default Log Level description: This check returns warnings if MongoDB is not using the default log level. - family: MONGODB - #author: Vinicius Grippa - advisor: configuration_generic + technology: MONGODB + # author: Vinicius Grippa + category: Configuration + subcategory: Generic interval: standard queries: - type: MONGODB_GETCMDLINEOPTS @@ -20,7 +21,7 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] parsed = rows[0]["parsed"] diff --git a/managed/data/checks/mongodb_maxsessions.yml b/managed/data/checks/mongodb_maxsessions.yml index a11c11654bd..29bb79203e9 100644 --- a/managed/data/checks/mongodb_maxsessions.yml +++ b/managed/data/checks/mongodb_maxsessions.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB maxSessions description: This check returns warnings if MongoDB is using more maxSessions value other than the default one 1000000 2 interval: standard - family: MONGODB - advisor: configuration_resources - #author: Vinodh Krishnaswamy/Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Resources + # author: Vinodh Krishnaswamy/Parag Bhayani queries: - type: MONGODB_GETCMDLINEOPTS script: | diff --git a/managed/data/checks/mongodb_multiple_services.yml b/managed/data/checks/mongodb_multiple_services.yml index 950579b1a72..a3f1243016f 100644 --- a/managed/data/checks/mongodb_multiple_services.yml +++ b/managed/data/checks/mongodb_multiple_services.yml @@ -3,11 +3,12 @@ checks: - version: 2 name: mongodb_multiple_services summary: MongoDB - Multiple mongod services - description: This check returns a notice if multiple mongod services are running in a single node. + description: This check returns a warning if multiple mongod services are running in a single node. interval: standard - family: MONGODB - advisor: performance_generic - #author: Parag Bhayani + technology: MONGODB + category: Performance + subcategory: Generic + # author: Parag Bhayani queries: - type: METRICS_INSTANT query: avg by(node_name,rs_nm,cluster) (mongodb_ss_pid{node_name=~"{{.NodeName}}"}) @@ -22,7 +23,6 @@ checks: for row in docs[0]: node = row["metric"]["node_name"] cluster = row["metric"]["cluster"] - replsetname = row["metric"]["rs_nm"] pid = int(row["value"][1]) if pid: mongo_cnt = int(mongo_cnt) + 1 @@ -33,6 +33,6 @@ checks: "summary": "MongoDB - multiple mongod services running", "description": "Multiple mongod services (pids: {}) are running on a single node <{}>. We recommended to use a dedicated node or container for each mongod service. For more information, refer to Read More documentation.".format(pids, node), "read_more_url": read_url.format("multiple-mongod-running-in-a-node"), - "severity": "notice", + "severity": "warning", }) return results diff --git a/managed/data/checks/mongodb_oplog_size_recommendation.yml b/managed/data/checks/mongodb_oplog_size_recommendation.yml index af6669ff730..eb5d038c64b 100644 --- a/managed/data/checks/mongodb_oplog_size_recommendation.yml +++ b/managed/data/checks/mongodb_oplog_size_recommendation.yml @@ -2,12 +2,13 @@ checks: - version: 2 name: mongodb_oplog_size_recommendation - summary: MongoDB - Oplog Recovery Window is low. Please consider resizing your oplog according to the provided recommendation. + summary: MongoDB Oplog Recovery Window Low description: This check returns a warning if the oplog window is below a 24 hour period and offers a recommended oplog size based on your instance. interval: standard - family: MONGODB - advisor: performance_replication - #author: Parag Bhayani + technology: MONGODB + category: Performance + subcategory: Replication + # author: Parag Bhayani queries: - type: MONGODB_GETDIAGNOSTICDATA - type: METRICS_RANGE @@ -26,6 +27,9 @@ checks: for row in docs[0]: #Calculate the configured Oplog size(GB), 95% of configured Oplog size(GB) & current Oplog size(GB) + if "local.oplog.rs.stats" not in row["data"]: + # No oplog (standalone / non-replica-set); nothing to recommend + return results oplog_size = row["data"]["local.oplog.rs.stats"]["maxSize"] oplog_size_GB = float(float(oplog_size) / (1024*1024*1024)) oplog_size_GB_95per = float(oplog_size_GB * 0.95) @@ -38,6 +42,9 @@ checks: window_sum = float(window_sum) + float(type[1]) window_total = int(window_total) + 1 + if window_total == 0: + return results + #Calculate the average Oplog Window for last 8hrs with 5m step window_avg = float(float(window_sum) / int(window_total)) diff --git a/managed/data/checks/mongodb_psa_architecture_check.yml b/managed/data/checks/mongodb_psa_architecture_check.yml index 1dab6793bf4..0883a12a87c 100644 --- a/managed/data/checks/mongodb_psa_architecture_check.yml +++ b/managed/data/checks/mongodb_psa_architecture_check.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB PSA Architecture description: This check returns an error if the replicaSet is using a PSA architecture. interval: standard - family: MONGODB - advisor: configuration_replication - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Replication + # author: Parag Bhayani queries: - type: METRICS_INSTANT query: avg by(cluster,node_name,set) (mongodb_mongod_replset_number_of_members{node_name=~"{{.NodeName}}"}) @@ -20,15 +21,19 @@ checks: info = docs[0] results = [] + members = 0 for row in info: members = int(row["value"][1]) + state = 0 + cluster = "" + replsetname = "" for type in docs[1]: - cluster = type["metric"]["cluster"] + cluster = type["metric"].get("cluster", "") state = int(type["value"][1]) - replsetname = type["metric"]["set"] + replsetname = type["metric"].get("set", "") - if members <= 3 and state == 7: + if members > 0 and members <= 3 and state == 7: results.append({ "summary": "MongoDB PSA Architecture detected", "description": "MongoDB PSA(Primary-Secondary-Arbiter) architecture has been detected in your {}-node ReplicaSet ({}) for {} Cluster. It is not recommended to use this architecture for Production systems. For more details, refer to Read More documentation.".format(members, replsetname, cluster), diff --git a/managed/data/checks/mongodb_read_tickets.yml b/managed/data/checks/mongodb_read_tickets.yml index 07d2b72e126..23505b76185 100644 --- a/managed/data/checks/mongodb_read_tickets.yml +++ b/managed/data/checks/mongodb_read_tickets.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB Read Tickets description: This check returns warnings if MongoDB is using more than 128 read tickets. interval: standard - family: MONGODB - advisor: configuration_generic - #author: Vinicius Grippa/Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Generic + # author: Vinicius Grippa/Parag Bhayani queries: - type: MONGODB_GETCMDLINEOPTS script: | @@ -34,4 +35,5 @@ checks: "read_more_url": read_url.format("mongodb-read-tickets"), "severity": "warning", }) - return results + + return results diff --git a/managed/data/checks/mongodb_replicaset_topology.yml b/managed/data/checks/mongodb_replicaset_topology.yml index 2447cdfc975..f0b7f56884d 100644 --- a/managed/data/checks/mongodb_replicaset_topology.yml +++ b/managed/data/checks/mongodb_replicaset_topology.yml @@ -5,12 +5,13 @@ checks: name: mongodb_replicaset_topology summary: MongoDB Replica Set Topology description: This check returns warnings if the Replica Set has less than 3 data bearing nodes - family: MONGODB - #author: Corrado Pandiani - advisor: configuration_replication + technology: MONGODB + # author: Corrado Pandiani + category: Configuration + subcategory: Replication interval: standard queries: - - type: MONGODB_REPLSETGETSTATUS + - type: MONGODB_GETDIAGNOSTICDATA script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" @@ -21,12 +22,18 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] - set_name = rows[0]["set"] - members_number = len(rows[0]["members"]) + # getDiagnosticData includes replSetGetStatus only for replica-set members; + # on a standalone it is absent, so there is nothing to check. + repl_status = rows[0]["data"].get("replSetGetStatus") + if not repl_status: + return results + + set_name = repl_status["set"] + members_number = len(repl_status["members"]) if members_number < 3: results.append({ diff --git a/managed/data/checks/mongodb_replication_lag.yml b/managed/data/checks/mongodb_replication_lag.yml index 95880572c24..daea6a7f1a9 100644 --- a/managed/data/checks/mongodb_replication_lag.yml +++ b/managed/data/checks/mongodb_replication_lag.yml @@ -5,12 +5,13 @@ checks: name: mongodb_replication_lag summary: MongoDB Replication Lag description: This check returns warnings if the Replica Set member is more than 10 sec behind the primary - advisor: performance_replication - family: MONGODB - #author: Ivan Groenewold + category: Performance + subcategory: Replication + technology: MONGODB + # author: Ivan Groenewold interval: standard queries: - - type: MONGODB_REPLSETGETSTATUS + - type: MONGODB_GETDIAGNOSTICDATA script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" @@ -21,17 +22,26 @@ checks: rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] - set_name = rows[0]["set"] + # getDiagnosticData includes replSetGetStatus only for replica-set members; + # on a standalone it is absent, so there is nothing to check. + repl_status = rows[0]["data"].get("replSetGetStatus") + if not repl_status: + return results - for member in rows[0]["members"]: + set_name = repl_status["set"] + + pri_last_opt_t = None + this_name = None + this_last_opt_t = None + for member in repl_status["members"]: if member["state"] == 1: pri_last_opt_t = member["optimeDate"] - for member in rows[0]["members"]: + for member in repl_status["members"]: if "self" in member.keys(): this_name = member["name"] this_last_opt_t = member["optimeDate"] diff --git a/managed/data/checks/mongodb_shard_collection_inconsistent_indexes.yml b/managed/data/checks/mongodb_shard_collection_inconsistent_indexes.yml index d7959f6f4d4..ce8cd9223ca 100644 --- a/managed/data/checks/mongodb_shard_collection_inconsistent_indexes.yml +++ b/managed/data/checks/mongodb_shard_collection_inconsistent_indexes.yml @@ -2,12 +2,13 @@ checks: - version: 2 name: mongodb_shard_collection_inconsistent_indexes - summary: MongoDB Sharding - Inconsistent Indexes Across Shards. - description: This check warns if there are inconsistent indexes across shards for sharded collections. Missing or inconsistent indexes across the shards can have a negative impact on performance. + summary: MongoDB Inconsistent Indexes Across Shards + description: Warns if there are inconsistent indexes across shards for sharded collections. Missing or inconsistent indexes across the shards can have a negative impact on performance. interval: standard - family: MONGODB - advisor: query_index - #author: Parag Bhayani + technology: MONGODB + category: Query + subcategory: Index + # author: Parag Bhayani queries: - type: METRICS_INSTANT query: avg by(cl_role,cluster,node_name,rs_state) (mongodb_ss_shardedIndexConsistency_numShardedCollectionsWithInconsistentIndexes{rs_state="1", node_name=~"{{.NodeName}}"}) @@ -17,14 +18,19 @@ checks: def check_context(docs, context): results = [] + clusters = [] for row in docs[0]: cluster = row["metric"]["cluster"] inconsistent_index = int(row["value"][1]) if inconsistent_index > 0: - results.append({ - "summary": "MongoDB Sharding - Inconsistent Indexes Across Shards", - "description": "Inconsistent indexes detected across shards for {} sharded collections in <{}> cluster. Either the indexes are missing in some shards or an index has inconsistent properties across collection's shards. To get inconsistent index details, kindly refer to Read More documentation.".format(inconsistent_index, cluster), - "read_more_url": read_url.format("mongodb-inconsistent-indexes-across-shards"), - "severity": "warning", - }) - return results + clusters.append("{} ({} sharded collections)".format(cluster, inconsistent_index)) + + if clusters: + results.append({ + "summary": "Inconsistent indexes across shards on {} cluster(s)".format(len(clusters)), + "description": "Inconsistent indexes detected across shards on the following cluster(s): {}. Either the indexes are missing in some shards or an index has inconsistent properties across collection's shards. To get inconsistent index details, kindly refer to Read More documentation.".format(", ".join(clusters)), + "read_more_url": read_url.format("mongodb-inconsistent-indexes-across-shards"), + "severity": "warning", + "labels": {"count": str(len(clusters))}, + }) + return results diff --git a/managed/data/checks/mongodb_swap_allocation.yml b/managed/data/checks/mongodb_swap_allocation.yml index 2d4df98eba4..052485e9bd7 100644 --- a/managed/data/checks/mongodb_swap_allocation.yml +++ b/managed/data/checks/mongodb_swap_allocation.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB - allocate swap memory description: This check returns a warning if there is no swap memory allocated to your instance. interval: standard - family: MONGODB - advisor: configuration_resources - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Resources + # author: Parag Bhayani queries: - type: METRICS_INSTANT query: avg by(node_name) (node_memory_MemTotal_bytes{node_name=~"{{.NodeName}}"} / (1024*1024*1024)) diff --git a/managed/data/checks/mongodb_taskexecutor.yml b/managed/data/checks/mongodb_taskexecutor.yml index 9b1ca226ff8..a318097dc94 100644 --- a/managed/data/checks/mongodb_taskexecutor.yml +++ b/managed/data/checks/mongodb_taskexecutor.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB TaskExecutorPoolSize High description: MongoDB TaskExecutorPoolSize count is higher than available CPU cores interval: standard - family: MONGODB - #author: Divyanshu Soni/Parag Bhayani - advisor: configuration_resources + technology: MONGODB + # author: Divyanshu Soni/Parag Bhayani + category: Configuration + subcategory: Resources queries: - type: MONGODB_GETPARAMETER - type: METRICS_INSTANT @@ -19,14 +20,16 @@ checks: info = docs[0] results = [] + taskExecutorPoolSize_int = 0 for row in info: taskExecutorPoolSize = row["taskExecutorPoolSize"] taskExecutorPoolSize_int = int(taskExecutorPoolSize) + value = 0 for row in docs[1]: value = int(row["value"][1]) - if taskExecutorPoolSize_int > value: + if value > 0 and taskExecutorPoolSize_int > value: results.append({ "summary": "MongoDB TaskExecutorPoolSize count is higher than available CPU cores. Please reduce it to equal to or less than the available cores.", "description": "See the following to reduce the taskExecutorPools size", diff --git a/managed/data/checks/mongodb_unsupported_version.yml b/managed/data/checks/mongodb_unsupported_version.yml index 7acc8a07551..911d112a6ec 100644 --- a/managed/data/checks/mongodb_unsupported_version.yml +++ b/managed/data/checks/mongodb_unsupported_version.yml @@ -5,11 +5,10 @@ checks: summary: MongoDB Unspported version check description: This check returns errors if your current PSMDB or MongoDB version is not supported. interval: standard - family: MONGODB - advisor: configuration_version - #category: configuration - #subcategory: version configuration - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Version + # author: Parag Bhayani queries: - type: MONGODB_BUILDINFO script: | diff --git a/managed/data/checks/mongodb_unused_index.yml b/managed/data/checks/mongodb_unused_index.yml index 56b875651e6..1d818cd570d 100644 --- a/managed/data/checks/mongodb_unused_index.yml +++ b/managed/data/checks/mongodb_unused_index.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB - Unused Indexes description: This check returns a warning if there are unused indexes on any database collection in your instance (Need to enable "indexStats" collector). interval: standard - family: MONGODB - advisor: query_index - #author: Parag Bhayani + technology: MONGODB + category: Query + subcategory: Index + # author: Parag Bhayani queries: - type: METRICS_INSTANT query: avg by(node_name) (mongodb_instance_uptime_seconds{node_name=~"{{.NodeName}}"} / 86400) diff --git a/managed/data/checks/mongodb_version.yml b/managed/data/checks/mongodb_version.yml index 0b8cd356d4c..b812b3321b6 100644 --- a/managed/data/checks/mongodb_version.yml +++ b/managed/data/checks/mongodb_version.yml @@ -4,10 +4,10 @@ checks: name: mongodb_version summary: MongoDB version check description: This check returns information on current MongoDB or Percona Server for MongoDB versions used in your environment. It also provides information on other available minor or major versions to consider for upgrades. - family: MONGODB - advisor: configuration_version - #category: configuration - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Version + # author: Parag Bhayani interval: standard queries: - type: MONGODB_BUILDINFO @@ -34,8 +34,7 @@ checks: info = docs[0] results = [] - is_version = 'version' in info - latest_major = "6.0" + latest_major = "8.0" for row in info: # Checking MongoDB version @@ -46,34 +45,29 @@ checks: num = version["num"] mm = "{}.{}".format(version["major"], version["minor"]) + if mm not in LATEST_VERSIONS["version_check"]: + # Newer major version we do not track yet; nothing to advise + continue + latest_current_version = LATEST_VERSIONS["version_check"][mm] current_version = row["version"] - if is_version == "": - results.append({ - "summary": "Not able to determine the version details.", - "description": "Not able to determine the current version details. Please check your mongod instance for availability and details.", - "read_more_url": "", - "severity": "warning", - }) - + description = "" if mm < "4.4": description = "Current MongoDB version = {}, Latest Major version = {}. This instance has reached EOL for this MongoDB version. It is highly recommended to perform an upgrade to reach a supported major release version immediately. This upgrade should be performed in a stepped manner without skipping any major version releases.".format(current_version, latest_major) - - if num < latest_current_version and mm < latest_major: + elif num < latest_current_version and mm < latest_major: description = "There is a new minor version {} available. Always consider upgrading to the highest minor version in order to take advantage of the latest bug fixes and performance tweaks. There is also an updated Major version {} available. Please consider planning for and implementing the major version upgrade after completing the minor patch upgrade. Current MongoDB version = {}.".format(format_version_num(latest_current_version), latest_major, current_version) - - if num == latest_current_version and mm < latest_major: + elif num == latest_current_version and mm < latest_major: description = "The instance is currently running MongoDB version {}. This is the latest minor patch for this version. However there is an updated Major version {} available. Please consider planning for and implementing the upgrade to the latest major version.".format(current_version, latest_major) + elif num < latest_current_version: + description = "There is a new minor version {} available. Always consider upgrading to the highest minor version in order to take advantage of the latest bug fixes and performance tweaks. Current MongoDB version = {}.".format(format_version_num(latest_current_version), current_version) - results.append({ - "summary": "Newer version of MongoDB is available.", - "description": description, - "read_more_url": LATEST_VERSIONS["read_url"].format("mongodb-version"), - "severity": "notice", - }) - - if num == latest_current_version and mm == latest_major: - return [] + if description: + results.append({ + "summary": "Newer version of MongoDB is available.", + "description": description, + "read_more_url": LATEST_VERSIONS["read_url"].format("mongodb-version"), + "severity": "warning", + }) - return results + return results diff --git a/managed/data/checks/mongodb_write_tickets.yml b/managed/data/checks/mongodb_write_tickets.yml index 51dbaf91465..aca02a4e32c 100644 --- a/managed/data/checks/mongodb_write_tickets.yml +++ b/managed/data/checks/mongodb_write_tickets.yml @@ -5,9 +5,10 @@ checks: summary: MongoDB write Tickets description: This check returns warnings if MongoDB is using more than 128 write tickets. interval: standard - family: MONGODB - advisor: configuration_generic - #author: Vinicius Grippa/Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Generic + # author: Vinicius Grippa/Parag Bhayani queries: - type: MONGODB_GETCMDLINEOPTS script: | @@ -34,4 +35,5 @@ checks: "read_more_url": read_url.format("mongodb-write-tickets"), "severity": "warning", }) - return results + + return results diff --git a/managed/data/checks/mongodb_write_tickets_runtime.yml b/managed/data/checks/mongodb_write_tickets_runtime.yml index a3177994648..4d95cdc903f 100644 --- a/managed/data/checks/mongodb_write_tickets_runtime.yml +++ b/managed/data/checks/mongodb_write_tickets_runtime.yml @@ -4,23 +4,26 @@ checks: - version: 2 name: mongodb_write_tickets_runtime summary: MongoDB Configuration Write ticket Check - description: This check returns warnings if MongoDB is using more than 128 write tickets during runtime. - family: MONGODB - #author: Vinicius Grippa - advisor: configuration_generic + description: Warns when MongoDB is using more than 128 write tickets during runtime. + technology: MONGODB + # author: Vinicius Grippa + category: Configuration + subcategory: Generic interval: standard queries: - type: MONGODB_GETPARAMETER script: | + read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + def check_context(docs, context): rows = docs[0] if len(rows) != 1: - return "Unexpected number of documents" + return "unexpected number of documents: {}".format(len(rows)) results = [] - parsed = rows[0] - writeParameter = parsed.get("wiredTigerConcurrentWriteTransactions", {}) + row = rows[0] + writeParameter = row.get("wiredTigerConcurrentWriteTransactions", 0) check = (int(writeParameter) >= 128) if check: results.append({ diff --git a/managed/data/checks/mongodb_xfs_ftype.yml b/managed/data/checks/mongodb_xfs_ftype.yml index a594f98a89c..a1c10f733a8 100644 --- a/managed/data/checks/mongodb_xfs_ftype.yml +++ b/managed/data/checks/mongodb_xfs_ftype.yml @@ -5,10 +5,10 @@ checks: summary: MongoDB - xfs description: This check returns a warning if dbpath is not using xfs filesystem type. interval: standard - family: MONGODB - advisor: configuration_resources - #category: configuration - #author: Parag Bhayani + technology: MONGODB + category: Configuration + subcategory: Resources + # author: Parag Bhayani queries: - type: MONGODB_GETCMDLINEOPTS - type: METRICS_INSTANT @@ -33,25 +33,33 @@ checks: if not parsed.get("storage", {}): return results + dbpath_mounts = [] + parent_mounts = [] for type in docs[1]: mount = type["metric"]["mountpoint"] ftype = type["metric"]["fstype"] if dbpath == mount and ftype != "xfs": - results.append({ - "summary": "The DBPATH mount point is not currently using XFS as its filesystem type.", - "description": "The dbPath for this instance is not currently using XFS as its filesystem type. It is strongly recommended to use XFS to provide better performance for data bearing nodes and to avoid performance issues that have been observed when using EXT4 with the wiredTiger engine. DBPATH - {}, MOUNT - {}, FTYPE - {}".format(dbpath, mount, ftype), - "read_more_url": read_url.format("mongodb-xfs"), - "severity": "warning", - }) + dbpath_mounts.append("{} ({})".format(mount, ftype)) - if dbpath.startswith(mount) and dbpath != mount and ftype != "xfs": - actual_mount = mount - if actual_mount != "/": - results.append({ - "summary": "The current mount point is subset of dbPath and is not using XFS as its filesystem type.", - "description": "The dbPath for this instance is not currently using XFS as its filesystem type. It is strongly recommended to use XFS to provide better performance for data bearing nodes and to avoid performance issues that have been observed when using EXT4 with the wiredTiger engine. DBPATH - {}, MOUNT - {}, FTYPE - {}".format(dbpath, mount, ftype), - "read_more_url": read_url.format("mongodb-xfs"), - "severity": "warning", - }) + if dbpath.startswith(mount) and dbpath != mount and ftype != "xfs" and mount != "/": + parent_mounts.append("{} ({})".format(mount, ftype)) + + recommendation = "It is strongly recommended to use XFS to provide better performance for data bearing nodes and to avoid performance issues that have been observed when using EXT4 with the wiredTiger engine." + if dbpath_mounts: + results.append({ + "summary": "The DBPATH mount point is not using XFS as its filesystem type.", + "description": "The dbPath {} for this instance is not currently using XFS as its filesystem type. {} Mount point(s): {}".format(dbpath, recommendation, ", ".join(dbpath_mounts)), + "read_more_url": read_url.format("mongodb-xfs"), + "severity": "warning", + "labels": {"count": str(len(dbpath_mounts))}, + }) + if parent_mounts: + results.append({ + "summary": "{} mount point(s) containing dbPath are not using XFS as their filesystem type.".format(len(parent_mounts)), + "description": "The dbPath {} for this instance resides on mount point(s) not using XFS as their filesystem type. {} Mount point(s): {}".format(dbpath, recommendation, ", ".join(parent_mounts)), + "read_more_url": read_url.format("mongodb-xfs"), + "severity": "warning", + "labels": {"count": str(len(parent_mounts))}, + }) return results diff --git a/managed/data/checks/mysql_32binary_on_64system.yml b/managed/data/checks/mysql_32binary_on_64system.yml index 173ac33d97a..9854cbab969 100644 --- a/managed/data/checks/mysql_32binary_on_64system.yml +++ b/managed/data/checks/mysql_32binary_on_64system.yml @@ -3,10 +3,11 @@ checks: - version: 2 name: mysql_32binary_on_64system summary: Check if binaries are 32 bits - description: This check returns a notice if version_compile_machine equals i686. - family: MYSQL - #author: Carlos Tutte - advisor: configuration_resources + description: This check returns a warning if version_compile_machine equals i686. + technology: MYSQL + # author: Carlos Tutte + category: Configuration + subcategory: Resources interval: standard queries: - type: MYSQL_SELECT @@ -28,7 +29,7 @@ checks: return [{ "summary": "Binaries are from a 32 bit architecture and should be upgraded to be 64 bits", "description": "The binaries are 32 bit instead of 64 bits.Binaries are old and you should consider upgrading to a newer version of 64 bits", - "severity": "notice", + "severity": "warning", "read_more_url":read_url.format("platform-mysql-binaries-32-bit") }] diff --git a/managed/data/checks/mysql_ahi_efficiency_performance_basic_check.yml b/managed/data/checks/mysql_ahi_efficiency_performance_basic_check.yml index 44cb92ef1b1..7b442cbe4df 100644 --- a/managed/data/checks/mysql_ahi_efficiency_performance_basic_check.yml +++ b/managed/data/checks/mysql_ahi_efficiency_performance_basic_check.yml @@ -5,8 +5,9 @@ checks: summary: InnoDB Adaptive Hash Index (AHI) efficiency checker description: Check the efficiency and effectiveness of InnoDB's Adaptive Hash Index (AHI). interval: standard - advisor: configuration_innodb - family: MYSQL + category: Configuration + subcategory: InnoDB + technology: MYSQL queries: - type: MYSQL_SHOW query: VARIABLES @@ -67,7 +68,7 @@ checks: "summary": "InnoDB's Adaptive Hash Index (AHI) is enabled.", "description": desc, "read_more_url": read_more, - "severity": "notice", + "severity": "warning", "labels": {}, }) @@ -78,7 +79,7 @@ checks: sample = float(row[1]) dataPoints.append(sample) samplesSum = samplesSum + sample - hitRatioAvg = int((samplesSum/len(dataPoints))*100) + hitRatioAvg = int((samplesSum/len(dataPoints))*100) if dataPoints else 0 # AHI btr_search_latch load dataPoints = [] @@ -89,12 +90,12 @@ checks: if sample!=0.0: dataPoints.append(sample) samplesSum = samplesSum + sample - loadAvg = int(samplesSum/len(dataPoints)) + loadAvg = int(samplesSum/len(dataPoints)) if dataPoints else 0 # Proposed evaluation: - # Low AHI hit ratio --> NOTICE + # Low AHI hit ratio --> INFO # Low AHI contention --> WARNING - # Medium AHI contention --> MAJOR + # Medium AHI contention --> ERROR # High AHI contention --> CRITICAL # # But how to calculate contention? @@ -109,23 +110,34 @@ checks: thresholdB = nCPUs*2 # - The "warning" threshold is 3/4 of the critical one thresholdA = (thresholdB/4)*3 + # - The "error" threshold sits midway between warning and critical + thresholdMid = (thresholdA+thresholdB)/2 btrWaitLoadHitRatioMsg = "AHI average btr_search_latch wait load is {}, AHI average hit ratio is {}%.".format(loadAvg,hitRatioAvg) if hitRatioAvg > 50 and loadAvg > thresholdA: - if loadAvg < thresholdB: + if loadAvg < thresholdMid: summary = "AHI is experiencing some contention" description = btrWaitLoadHitRatioMsg severity = "warning" + elif loadAvg < thresholdB: + summary = "AHI is experiencing contention" + description = "{}. Consider increasing the number of AHI partitions (currently operating with {}).".format(btrWaitLoadHitRatioMsg,ahi_parts) + severity = "error" else: # loadAvg > thresholdB summary = "AHI is likely experiencing contention" description = "{}. You should consider increasing the number of AHI partitions (currently operating with {}) or disable it altogether.".format(btrWaitLoadHitRatioMsg,ahi_parts) - severity = "error" + severity = "critical" + + elif hitRatioAvg <= 50: + summary = "AHI hit ratio is low" + description = "{}. AHI is enabled but only a small share of searches are served by it; it may not be benefiting this workload.".format(btrWaitLoadHitRatioMsg) + severity = "info" else: summary = "AHI is operating with no contention detected" description = btrWaitLoadHitRatioMsg - severity = "notice" + severity = "warning" results.append({ "summary": summary, @@ -141,7 +153,7 @@ checks: "summary": "InnoDB's Adaptive Hash Index (AHI) is disabled.", "description": "The AHI can speed up queries in certain MySQL read workloads, sometimes at the expense of concurrency. It might be worth experimenting operating with it enabled if you haven't already. Then, keep checking this dashboard for any signs of contention on AHI; test with caution.", "read_more_url": read_more, - "severity": "notice", + "severity": "warning", "labels": {}, }) diff --git a/managed/data/checks/mysql_automatic_expired_password.yml b/managed/data/checks/mysql_automatic_expired_password.yml index 337debe2f85..c963919ce49 100644 --- a/managed/data/checks/mysql_automatic_expired_password.yml +++ b/managed/data/checks/mysql_automatic_expired_password.yml @@ -3,9 +3,10 @@ checks: - version: 2 name: mysql_automatic_expired_password summary: MySQL Automatic User Expired Password - description: This check warns if MySQL parameter automatic password expiry is not active. - family: MYSQL - advisor: security_authentication + description: Warn about MySQL parameter automatic password expiry being inactive. + technology: MYSQL + category: Security + subcategory: Authentication interval: standard queries: - type: MYSQL_SHOW @@ -13,36 +14,38 @@ checks: script: | def check_context(rows, context): """ - This check returns a warning if automatic password expiry is not active. + This check returns a warning if automatic password expiry is inactive. """ summary = "" + description = "" action = "activate" - rows = deps[0] + rows = rows[0] for row in rows: name, value = row["Variable_name"], row["Value"] if name == "default_password_lifetime": print(name, "=", value) if value == "0": - summary = "Automatic password expiry is not active. " + summary = "Automatic password expiry is inactive" elif value >= "365": - summary = "Default password lifetime is too long. It's set to {} days. ".format(value) + summary = "Default password lifetime is too long" + description = "The default password lifetime is set to {} days. ".format(value) action = "change" if name == "disconnect_on_expired_password" and summary: print(name, "=", value) if value == "ON": - summary = summary + "System variable disconnect_on_expired_password is enabled. The server will not allow connecting clients with expired passwords." + description += "System variable disconnect_on_expired_password is enabled. The server will not allow connecting clients with expired passwords. " else: - summary = summary + "System variable disconnect_on_expired_password is disabled. The server permits the client to connect but puts it in sandbox mode." + description += "System variable disconnect_on_expired_password is disabled. The server permits the client to connect but puts it in sandbox mode. " if summary: return [{ - "summary": summary, - "description": "See the following steps to {} ".format(action), - "read_more_url": "https://dev.mysql.com/doc/refman/8.0/en/password-management.html#password-expiration-policy", - "severity": "warning" + "summary": summary, + "description": "{}. {}See the following steps to {}.".format(summary, description, action), + "read_more_url": "https://dev.mysql.com/doc/refman/8.0/en/password-management.html#password-expiration-policy", + "severity": "warning" }] return [] diff --git a/managed/data/checks/mysql_automatic_sp_privileges_enabled.yml b/managed/data/checks/mysql_automatic_sp_privileges_enabled.yml index 0b946f245fe..3ecee8dba74 100644 --- a/managed/data/checks/mysql_automatic_sp_privileges_enabled.yml +++ b/managed/data/checks/mysql_automatic_sp_privileges_enabled.yml @@ -2,11 +2,12 @@ checks: - version: 2 name: mysql_automatic_sp_privileges_enabled - summary: Checks if automatic_sp_privileges configuration is ON. + summary: Checks if automatic_sp_privileges configuration is ON description: This check reviews the automatic_sp_privileges configuration is ON. - family: MYSQL - #author: Kedar Vaijanapurkar - advisor: configuration_generic + technology: MYSQL + # author: Kedar Vaijanapurkar + category: Configuration + subcategory: Generic interval: standard queries: - type: MYSQL_SELECT @@ -19,15 +20,15 @@ checks: version = row["version"] service = row["service"] automatic_sp_privileges = row["automatic_sp_privileges"] - if automatic_sp_privileges != 1: - results.append ({ - "summary": "automatic_sp_privileges is {}, not enabled.".format(automatic_sp_privileges), - "description": "The automatic_sp_privileges when enabled, causes automatic grants or revokes of the EXECUTE and ALTER ROUTINE privileges to the creator of a stored routine upon creation or removal of the routine.", - "severity": "error", - "labels": { - "mysql_version": "{}".format(version), - "service": "{}".format(service), - }, - "read_more_url":read_url.format("mysql_automatic_sp_privileges_enabled") - }) + if automatic_sp_privileges != 1: + results.append ({ + "summary": "automatic_sp_privileges is {}, not enabled.".format(automatic_sp_privileges), + "description": "The automatic_sp_privileges when enabled, causes automatic grants or revokes of the EXECUTE and ALTER ROUTINE privileges to the creator of a stored routine upon creation or removal of the routine.", + "severity": "error", + "labels": { + "mysql_version": "{}".format(version), + "service": "{}".format(service), + }, + "read_more_url":read_url.format("mysql_automatic_sp_privileges_enabled") + }) return results diff --git a/managed/data/checks/mysql_config_binlog_retention_period.yml b/managed/data/checks/mysql_config_binlog_retention_period.yml index 5db27ef9bb2..0220499288c 100644 --- a/managed/data/checks/mysql_config_binlog_retention_period.yml +++ b/managed/data/checks/mysql_config_binlog_retention_period.yml @@ -5,51 +5,56 @@ checks: summary: Binlogs retention check description: Binlogs should not be rotated too often, except very specific cases. interval: standard - advisor: configuration_generic - family: MYSQL + category: Configuration + subcategory: Generic + technology: MYSQL queries: - type: MYSQL_SELECT - query: " @@version version,@@hostname service, @@global.expire_logs_days as exp_days, @@global.binlog_expire_logs_seconds as exp_secs;" + query: " @@version version, @@hostname service, COALESCE((SELECT CAST(VARIABLE_VALUE AS SIGNED) FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'expire_logs_days'), -1) exp_days, COALESCE((SELECT CAST(VARIABLE_VALUE AS SIGNED) FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'binlog_expire_logs_seconds'), -1) exp_secs;" script: | - def check_binlog_retention_period(doc,read_url): + def check_binlog_retention_period(doc, read_url): for row in doc: - exp_secs = row["exp_secs"] - exp_days = row["exp_days"] - version = row["version"] - service = row["service"] - minimum_retention = 7*86400; - if exp_days > 0: - exptime = exp_days*86400 - if exptime < minimum_retention: - return { - "summary": "Binlogs are removed too early and expire_logs_days is deprecated", - "description": "Please consider to use binlog_expire_logs_seconds, and to set it to at least {} seconds".format(minimum_retention), - "read_more_url": read_url.format("binlog-retention-period"), - "severity": "warning", - "labels": { - "mysql_version": "{}".format(version), - "service": "{}".format(service), - } - } - if exp_secs < minimum_retention: - return { - "summary": "Binlogs are removed too early.", - "description": "Please consider to set binlog_expire_logs_seconds to at least {} seconds".format(minimum_retention), - "read_more_url": read_url.format("binlog-retention-period"), - "severity": "warning", - "labels": { - "mysql_version": "{}".format(version), - "service": "{}".format(service), - } - - } + # exp_days / exp_secs are -1 when the variable is absent on this MySQL version + # (expire_logs_days was removed in 8.4; binlog_expire_logs_seconds was added in 8.0.1). + # A value of 0 means automatic purging is disabled, so binlogs are kept indefinitely. + exp_secs = row["exp_secs"] + exp_days = row["exp_days"] + version = row["version"] + service = row["service"] + minimum_retention = 7 * 86400 + # binlog_expire_logs_seconds takes precedence over the legacy expire_logs_days + if exp_secs > 0: + if exp_secs < minimum_retention: + return { + "summary": "Binlogs are removed too early.", + "description": "Please consider to set binlog_expire_logs_seconds to at least {} seconds".format(minimum_retention), + "read_more_url": read_url.format("binlog-retention-period"), + "severity": "warning", + "labels": { + "mysql_version": "{}".format(version), + "service": "{}".format(service), + } + } + elif exp_days > 0: + exptime = exp_days * 86400 + if exptime < minimum_retention: + return { + "summary": "Binlogs are removed too early and expire_logs_days is deprecated", + "description": "Please consider to use binlog_expire_logs_seconds, and to set it to at least {} seconds".format(minimum_retention), + "read_more_url": read_url.format("binlog-retention-period"), + "severity": "warning", + "labels": { + "mysql_version": "{}".format(version), + "service": "{}".format(service), + } + } def check_context(docs, context): # we first define some global variables read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" results = [] - in_result = check_binlog_retention_period(docs[0],read_url) + in_result = check_binlog_retention_period(docs[0], read_url) if in_result: results.append(in_result) diff --git a/managed/data/checks/mysql_config_binlog_row_image.yml b/managed/data/checks/mysql_config_binlog_row_image.yml index ec2c536e22e..d99829d26a6 100644 --- a/managed/data/checks/mysql_config_binlog_row_image.yml +++ b/managed/data/checks/mysql_config_binlog_row_image.yml @@ -5,9 +5,10 @@ checks: summary: Binlogs raw image is not set to FULL description: Please consider setting binlog_row_image=FULL. interval: standard - advisor: configuration_generic - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Generic + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, IF(@@global.binlog_row_image='MINIMAL', 1, 0) AS binlog_row_image, @@global.binlog_row_image as RI_NAME;" diff --git a/managed/data/checks/mysql_config_binlogs_checksummed.yml b/managed/data/checks/mysql_config_binlogs_checksummed.yml index 897f8ab5ac9..215a5e0d953 100644 --- a/managed/data/checks/mysql_config_binlogs_checksummed.yml +++ b/managed/data/checks/mysql_config_binlogs_checksummed.yml @@ -5,9 +5,10 @@ checks: summary: Server is not configured to enforce data integrity description: Please consider setting binlog_checksum=CRC32 to improve consistency and reliability. interval: standard - advisor: configuration_generic - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Generic + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, @@global.binlog_checksum AS binlog_checksum;" diff --git a/managed/data/checks/mysql_config_general_log.yml b/managed/data/checks/mysql_config_general_log.yml index b5f23fc8f3a..eb259b6d6d3 100644 --- a/managed/data/checks/mysql_config_general_log.yml +++ b/managed/data/checks/mysql_config_general_log.yml @@ -5,10 +5,10 @@ checks: summary: General Log is enabled description: Check if the general log is enabled. interval: standard - advisor: configuration_generic - #subcategory: generic - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Generic + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@global.general_log as general_log, @@version as version, @@hostname as service;" diff --git a/managed/data/checks/mysql_config_innodb_redolog_disabled.yml b/managed/data/checks/mysql_config_innodb_redolog_disabled.yml index 99783f2365b..d7eccf578ce 100644 --- a/managed/data/checks/mysql_config_innodb_redolog_disabled.yml +++ b/managed/data/checks/mysql_config_innodb_redolog_disabled.yml @@ -5,9 +5,10 @@ checks: summary: Redo log is disabled in this instance description: The MySQL InnoDB Redo log, is one of the core components to fulfil the ACID paradigm in MySQL. This element is currently OFF, the setting is highly insecure. interval: standard - advisor: configuration_innodb - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: InnoDB + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service/*!80021 ,(select variable_value from performance_schema.global_status where lower(variable_name) = 'innodb_redo_log_enabled') as redolog */;" @@ -16,7 +17,7 @@ checks: for row in doc: version = row["version"] service = row["service"] - redolog = row["redolog"] + redolog = row.get("redolog", "") if redolog == "OFF": return { diff --git a/managed/data/checks/mysql_config_local_infile.yml b/managed/data/checks/mysql_config_local_infile.yml index fb4d89623b1..3193959d134 100644 --- a/managed/data/checks/mysql_config_local_infile.yml +++ b/managed/data/checks/mysql_config_local_infile.yml @@ -5,10 +5,10 @@ checks: summary: Load data in file active description: Identify if a load data in file is active. interval: standard - advisor: security_configuration - #subcategory: generic - #author: The Grinch - family: MYSQL + category: Security + subcategory: Configuration + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, @@global.local_infile as local_infile;" diff --git a/managed/data/checks/mysql_config_log_bin.yml b/managed/data/checks/mysql_config_log_bin.yml index 1841e1927c2..023e54db750 100644 --- a/managed/data/checks/mysql_config_log_bin.yml +++ b/managed/data/checks/mysql_config_log_bin.yml @@ -5,9 +5,10 @@ checks: summary: Binary Log is disabled description: Check if the binlog is enabled or disabled. interval: standard - advisor: configuration_generic - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Generic + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@global.log_bin as log_bin, @@version version,@@hostname service;" diff --git a/managed/data/checks/mysql_config_relay_log_purge.yml b/managed/data/checks/mysql_config_relay_log_purge.yml index 7baddb726b6..061b0b12d83 100644 --- a/managed/data/checks/mysql_config_relay_log_purge.yml +++ b/managed/data/checks/mysql_config_relay_log_purge.yml @@ -5,9 +5,10 @@ checks: summary: Automatic relay log purging is off description: Identify if a replica node has relay-logs purge set. interval: standard - advisor: configuration_replication - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Replication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, count(1) as repl_conf_count, @@global.relay_log_purge as relay_log_purge from performance_schema.replication_connection_configuration;" diff --git a/managed/data/checks/mysql_config_replication_bp1.yml b/managed/data/checks/mysql_config_replication_bp1.yml index 4ca00d14847..f9504c56400 100644 --- a/managed/data/checks/mysql_config_replication_bp1.yml +++ b/managed/data/checks/mysql_config_replication_bp1.yml @@ -2,12 +2,13 @@ checks: - version: 2 name: mysql_config_replication_bp1 - summary: Checks for basic best practices when setting a replica node. + summary: Checks for basic best practices when setting a replica node description: Identify if a replica node is in read-only mode and if checksum. interval: standard - advisor: configuration_replication - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Replication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, count(1) as repl_conf_count, @@global.read_only as read_only, @@slave_sql_verify_checksum as slave_sql_verify_checksum from performance_schema.replication_connection_configuration;" diff --git a/managed/data/checks/mysql_config_slave_parallel_workers.yml b/managed/data/checks/mysql_config_slave_parallel_workers.yml index c3b889a9625..919a3d48ceb 100644 --- a/managed/data/checks/mysql_config_slave_parallel_workers.yml +++ b/managed/data/checks/mysql_config_slave_parallel_workers.yml @@ -5,9 +5,10 @@ checks: summary: Replication is single threaded description: Identify if a replication is single threaded. interval: standard - advisor: configuration_replication - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Replication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, @@global.slave_parallel_workers as slave_parallel_workers, count(1) as repl_conf_count from performance_schema.replication_connection_configuration;" @@ -21,7 +22,7 @@ checks: if slave_parallel_workers < 2 and repl_conf_count > 0: return { - "summary": "Load data in file active", + "summary": "Replication is not multi-threaded", "description": "Replication is single threaded. Current settings = {} ;".format(slave_parallel_workers), "severity": "warning", "read_more_url": read_url.format("sql-processing-not-multi-threaded"), diff --git a/managed/data/checks/mysql_config_sql_mode.yml b/managed/data/checks/mysql_config_sql_mode.yml index fd2ccb95dbe..fb07d4317ff 100644 --- a/managed/data/checks/mysql_config_sql_mode.yml +++ b/managed/data/checks/mysql_config_sql_mode.yml @@ -5,9 +5,10 @@ checks: summary: Server is not configured to enforce data integrity description: In order for maximum data integrity to be set, the server should have specific values configured in sql_mode. interval: standard - advisor: configuration_generic - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Generic + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@global.sql_mode as sql_mode, @@version version,@@hostname service;" diff --git a/managed/data/checks/mysql_config_sync_binlog.yml b/managed/data/checks/mysql_config_sync_binlog.yml index abd44846340..5d30297a84f 100644 --- a/managed/data/checks/mysql_config_sync_binlog.yml +++ b/managed/data/checks/mysql_config_sync_binlog.yml @@ -5,9 +5,10 @@ checks: summary: Sync binlog is disabled description: Check if the binlog synchronized before transaction is committed. interval: standard - advisor: configuration_replication - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Replication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@global.sync_binlog as sync_binlog, @@version version,@@hostname service;" diff --git a/managed/data/checks/mysql_config_tmp_table_size_limit.yml b/managed/data/checks/mysql_config_tmp_table_size_limit.yml index ccc9179dc8a..a8670c73a73 100644 --- a/managed/data/checks/mysql_config_tmp_table_size_limit.yml +++ b/managed/data/checks/mysql_config_tmp_table_size_limit.yml @@ -5,9 +5,10 @@ checks: summary: Temp table size is larger than Heap Table size description: Check if the Temporary table size exceeds the heap table size. interval: standard - advisor: configuration_generic - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Generic + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@global.max_heap_table_size as max_heap_table_size, @@global.tmp_table_size as tmp_table_size, @@version version,@@hostname service;" diff --git a/managed/data/checks/mysql_configuration_innodb_file_format.yml b/managed/data/checks/mysql_configuration_innodb_file_format.yml index d2a8b688d9d..4dbb28a57a1 100644 --- a/managed/data/checks/mysql_configuration_innodb_file_format.yml +++ b/managed/data/checks/mysql_configuration_innodb_file_format.yml @@ -5,14 +5,16 @@ checks: summary: MySQL InnoDB file format description: Check if InnoDB is configured with recommended file format interval: standard - advisor: configuration_innodb - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: InnoDB + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, VARIABLE_NAME as name, VARIABLE_VALUE as value from performance_schema.global_variables where VARIABLE_NAME in ('innodb_file_format','innodb_file_format_max');" script: | - def check_innodb_conf(doc,read_url): + def check_innodb_conf(doc, read_url): + findings = [] for row in doc: version = row["version"] service = row["service"] @@ -20,36 +22,35 @@ checks: value = row["value"] if name == "innodb_file_format" and value != "Barracuda" : - return { + findings.append({ "summary": "InnoDB File Format is not optimal", "description": "InnoDB is using the old file format insted of the new Barracuda. Current setting: {} = {} ;".format(name,value), - "severity": "notice", + "severity": "warning", "read_more_url": read_url.format("configuration-innodb-file-format"), "labels": { "mysql_version": "{}".format(version), "service": "{}".format(service), } - } + }) if name == "innodb_file_format_max" and value != "Barracuda" : - return { + findings.append({ "summary": "InnoDB File Format Max is not optimal", "description": "InnoDB is using the old file format insted of the new Barracuda. Current setting: {} = {} ;".format(name,value), - "severity": "notice", + "severity": "warning", "read_more_url": read_url.format("configuration-innodb-file-format"), "labels": { "mysql_version": "{}".format(version), "service": "{}".format(service), } - } + }) + + return findings def check_context(docs, context): # we first define some global variables read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" results = [] - - result1 = check_innodb_conf(docs[0],read_url) - if result1: - results.append(result1) + results.extend(check_innodb_conf(docs[0],read_url)) return results diff --git a/managed/data/checks/mysql_configuration_innodb_file_maxlimit.yml b/managed/data/checks/mysql_configuration_innodb_file_maxlimit.yml index 48903921e76..df7b22bc5de 100644 --- a/managed/data/checks/mysql_configuration_innodb_file_maxlimit.yml +++ b/managed/data/checks/mysql_configuration_innodb_file_maxlimit.yml @@ -2,12 +2,13 @@ checks: - version: 2 name: mysql_configuration_innodb_file_maxlimit - summary: InnoDB Tablespace size has a maximum limit. + summary: InnoDB Tablespace size has a maximum limit description: Check if InnoDB is configured with recommended auto-extend interval: standard - advisor: configuration_innodb - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: InnoDB + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, VARIABLE_NAME as name, VARIABLE_VALUE as value from performance_schema.global_variables where VARIABLE_NAME in ('innodb_data_file_path');" @@ -23,7 +24,7 @@ checks: return { "summary": "InnoDB Tablespace size has a maximum limit", "description": "InnoDB Tablespace can not grow because of this limit. Current setting: {} = {} ;".format(name,value), - "severity": "notice", + "severity": "warning", "read_more_url": read_url.format("configuration-check-innodb-data-file-path"), "labels": { "mysql_version": "{}".format(version), diff --git a/managed/data/checks/mysql_configuration_innodb_file_per_table_not_enabled.yml b/managed/data/checks/mysql_configuration_innodb_file_per_table_not_enabled.yml index f055fcd04c6..d27ac58c6fc 100644 --- a/managed/data/checks/mysql_configuration_innodb_file_per_table_not_enabled.yml +++ b/managed/data/checks/mysql_configuration_innodb_file_per_table_not_enabled.yml @@ -5,11 +5,10 @@ checks: summary: innodb_file_per_table not enabled description: innodb_file_per_table not enabled interval: standard - advisor: configuration_innodb - #subcategory: performance - #author:Kedar Vaijanapurkar - family: MYSQL - category: configuration + category: Configuration + subcategory: InnoDB + # author: Kedar Vaijanapurkar + technology: MYSQL queries: - type: MYSQL_SELECT query: "@@global.innodb_file_per_table as innodb_file_per_table, @@global.version as version, @@global.hostname as service" diff --git a/managed/data/checks/mysql_configuration_innodb_flush_method.yml b/managed/data/checks/mysql_configuration_innodb_flush_method.yml index 384dd6624d6..8c75d4dc6a9 100644 --- a/managed/data/checks/mysql_configuration_innodb_flush_method.yml +++ b/managed/data/checks/mysql_configuration_innodb_flush_method.yml @@ -5,9 +5,10 @@ checks: summary: MySQL InnoDB flush method description: Check if InnoDB is configured with recommended flush method interval: standard - advisor: configuration_innodb - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: InnoDB + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, VARIABLE_NAME as name, VARIABLE_VALUE as value from performance_schema.global_variables where VARIABLE_NAME in ('innodb_flush_method');" @@ -23,7 +24,7 @@ checks: return { "summary": "MySQL InnoDB flush method", "description": "InnoDB is not configured with recommended flush method. Current setting: {} = {} ;".format(name,value), - "severity": "notice", + "severity": "warning", "read_more_url": read_url.format("configuration-innodb-flush-method"), "labels": { "mysql_version": "{}".format(version), diff --git a/managed/data/checks/mysql_configuration_innodb_strict_mode.yml b/managed/data/checks/mysql_configuration_innodb_strict_mode.yml index e903066d4c6..3b08e720bd0 100644 --- a/managed/data/checks/mysql_configuration_innodb_strict_mode.yml +++ b/managed/data/checks/mysql_configuration_innodb_strict_mode.yml @@ -5,9 +5,10 @@ checks: summary: InnoDB strict mode description: This check warns about password lifetime. interval: standard - advisor: configuration_innodb - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: InnoDB + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, @@global.innodb_strict_mode AS innodb_strict_mode;" @@ -22,7 +23,7 @@ checks: return { "summary": "InnoDB strict mode is disabled", "description": "Please consider setting innodb_strict_mode=ON to improve data consistency. Current settings = {} ;".format(innodb_strict_mode), - "severity": "notice", + "severity": "warning", "read_more_url": read_url.format("innodb-strict-mode"), "labels": { "mysql_version": "{}".format(version), diff --git a/managed/data/checks/mysql_configuration_log_verbosity.yml b/managed/data/checks/mysql_configuration_log_verbosity.yml index 9bd92017bb2..6582fbbbaaf 100644 --- a/managed/data/checks/mysql_configuration_log_verbosity.yml +++ b/managed/data/checks/mysql_configuration_log_verbosity.yml @@ -4,9 +4,10 @@ checks: name: mysql_configuration_log_verbosity summary: Check log verbosity description: Checks that warnings are being printed on the log - family: MYSQL - #author: Carlos - advisor: configuration_generic + technology: MYSQL + # author: Carlos Tutte + category: Configuration + subcategory: Generic interval: standard queries: - type: MYSQL_SHOW diff --git a/managed/data/checks/mysql_configuration_max_connections_usage.yml b/managed/data/checks/mysql_configuration_max_connections_usage.yml index 7ac40326fae..fab47d26afa 100644 --- a/managed/data/checks/mysql_configuration_max_connections_usage.yml +++ b/managed/data/checks/mysql_configuration_max_connections_usage.yml @@ -5,11 +5,10 @@ checks: summary: Check max connections usage description: Checks for MySQL max_connections configuration option for maximum utilization interval: standard - advisor: configuration_connection - #subcategory: connection - #author:Kedar Vaijanapurkar - family: MYSQL - category: configuration + category: Configuration + subcategory: Connection + # author: Kedar Vaijanapurkar + technology: MYSQL queries: - type: MYSQL_SELECT query: "@@global.max_connections as max_connections, @@global.version as version, @@global.hostname as service" diff --git a/managed/data/checks/mysql_configuration_secure_file_priv_empty.yml b/managed/data/checks/mysql_configuration_secure_file_priv_empty.yml index af21b536160..f2fdde82075 100644 --- a/managed/data/checks/mysql_configuration_secure_file_priv_empty.yml +++ b/managed/data/checks/mysql_configuration_secure_file_priv_empty.yml @@ -5,30 +5,30 @@ checks: summary: secure_file_priv is empty description: The secure_file_priv when empty allows users with FILE privilege to create files at any location where MySQL server has write permission. interval: standard - advisor: security_configuration - #subcategory: security - #author:Kedar Vaijanapurkar - family: MYSQL - category: configuration + category: Security + subcategory: Configuration + # author: Kedar Vaijanapurkar + technology: MYSQL queries: - type: MYSQL_SELECT query: "@@global.version as version, @@global.hostname as service, @@global.secure_file_priv as secure_file_priv" script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + def check_context(docs, context): - results = [] - for row in docs[0]: - version = row["version"] - service = row["service"] - if row["secure_file_priv"] == 'NULL': - results.append({ - "summary": "MySQL secure_file_priv empty check", - "description": "MySQL secure_file_priv_empty is {}. To provide more secure installation scope should be restricted to a specific path.".format(row["secure_file_priv"]), - "read_more_url": read_url.format('mysql_configuration_secure_file_priv_empty'), - "labels": { - "mysql_version": "{}".format(version), - "service": "{}".format(service), - }, - "severity": "warning" - }) - return results + results = [] + for row in docs[0]: + version = row["version"] + service = row["service"] + if row["secure_file_priv"] == 'NULL': + results.append({ + "summary": "MySQL secure_file_priv empty check", + "description": "MySQL secure_file_priv_empty is {}. To provide more secure installation scope should be restricted to a specific path.".format(row["secure_file_priv"]), + "read_more_url": read_url.format('mysql_configuration_secure_file_priv_empty'), + "labels": { + "mysql_version": "{}".format(version), + "service": "{}".format(service), + }, + "severity": "warning" + }) + return results diff --git a/managed/data/checks/mysql_indexes_larger.yml b/managed/data/checks/mysql_indexes_larger.yml index c72d6216c04..3596ecebf18 100644 --- a/managed/data/checks/mysql_indexes_larger.yml +++ b/managed/data/checks/mysql_indexes_larger.yml @@ -3,10 +3,11 @@ checks: - version: 2 name: mysql_indexes_larger summary: Are there tables with index sizes larger than data? - description: Check all the tables to see if any have indexes larger than data. This indicates sub-optimial schema and should be reviewed. + description: Check all the tables to see if any have indexes larger than data. This indicates sub-optimal schema and should be reviewed. interval: standard - advisor: query_schema_design - family: MYSQL + category: Query + subcategory: Schema + technology: MYSQL queries: - type: MYSQL_SELECT query: "concat(table_schema, '.', table_name) as tbl_name, concat(round(table_rows/1000000,2),'M') num_rows, concat(round(data_length/(1024*1024*1024),2),'G') data_size, concat(round(index_length/(1024*1024*1024),2),'G') idx_size, concat(round((data_length+index_length)/(1024*1024*1024),2),'G') total_size, round(index_length/data_length,2) idxfrac FROM information_schema.TABLES WHERE table_schema not in ('information_schema', 'mysql', 'performance_schema', 'sys') ORDER BY data_length+index_length DESC LIMIT 10" @@ -18,9 +19,8 @@ checks: if float(value) > 1.0: results.append({ "summary": "Index size is larger than data for table: {}".format(name), - "description": "There is a table where index is larger than data. This typically indicates sub-optimial indexing.", + "description": "There is a table where index is larger than data. This typically indicates sub-optimal indexing.", "read_more_url": "https://dev.mysql.com/doc/refman/en/data-size.html", "severity": "warning", - "labels": {}, }) return results diff --git a/managed/data/checks/mysql_innodb_redo_logs_not_sized_correctly.yml b/managed/data/checks/mysql_innodb_redo_logs_not_sized_correctly.yml index 04090c4880d..ed4abd255e0 100644 --- a/managed/data/checks/mysql_innodb_redo_logs_not_sized_correctly.yml +++ b/managed/data/checks/mysql_innodb_redo_logs_not_sized_correctly.yml @@ -2,13 +2,12 @@ checks: - version: 2 name: mysql_innodb_redo_logs_not_sized_correctly - summary: Checks if InnoDB redo log size is not configured correctly. + summary: Checks if InnoDB redo log size is not configured correctly description: This check reviews InnoDB redo log size and suggests if it is configured too low. - family: MYSQL - category: performance - #subcategory: configuration - #author: Kedar Vaijanapurkar - advisor: configuration_innodb + technology: MYSQL + # author: Kedar Vaijanapurkar + category: Configuration + subcategory: InnoDB interval: standard queries: - type: MYSQL_SELECT @@ -23,7 +22,7 @@ checks: parameters: lookback: 5m - type: METRICS_RANGE - query: " (avg by (service_name) (max_over_time(mysql_global_status_innodb_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_global_status_innodb_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_lsn_checkpoint_age_total{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_lsn_checkpoint_age_total{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_lsn_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_lsn_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[5m]))) * 100 / (avg by (service_name) (max_over_time(mysql_global_status_innodb_checkpoint_max_age{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_global_status_innodb_checkpoint_max_age{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[5m])))\"" + query: " (avg by (service_name) (max_over_time(mysql_global_status_innodb_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_global_status_innodb_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_lsn_checkpoint_age_total{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_lsn_checkpoint_age_total{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_lsn_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_lsn_checkpoint_age{service_name=~\"{{.ServiceName}}\"}[5m]))) * 100 / (avg by (service_name) (max_over_time(mysql_global_status_innodb_checkpoint_max_age{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_global_status_innodb_checkpoint_max_age{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_recovery_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[5m]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[1h]) or max_over_time(mysql_info_schema_innodb_metrics_log_log_max_modified_age_async{service_name=~\"{{.ServiceName}}\"}[5m])))" parameters: range: 168h step: 5m @@ -78,7 +77,7 @@ checks: return { "summary": "Redo log size check.", "read_more": "https://percona.com/blog", - "description": "Current total redo log size is {}M. The relo log written are more than the configured though this hasn't caused system degradation. The average logs written in past 24h is {}M with spikes above redo_log_size being {}% of total samples with max written redolog size being {}M. ".format(int(redo_log_size/1024/1024),int(logs_written_avg), int(cnt_high*100/cnt),int(logs_written_max/1024/1024)), + "description": "Current total redo log size is {}M. The redo log written are more than the configured though this hasn't caused system degradation. The average logs written in past 24h is {}M with spikes above redo_log_size being {}% of total samples with max written redolog size being {}M.".format(int(redo_log_size/1024/1024),int(logs_written_avg), int(cnt_high*100/cnt),int(logs_written_max/1024/1024)), "severity": "warning", "labels": { "mysql_version": "{}".format(version), diff --git a/managed/data/checks/mysql_log_replica_updates.yml b/managed/data/checks/mysql_log_replica_updates.yml index 92aeeee1316..ffc7111430a 100644 --- a/managed/data/checks/mysql_log_replica_updates.yml +++ b/managed/data/checks/mysql_log_replica_updates.yml @@ -5,9 +5,10 @@ checks: summary: MySQL configuration check description: Checks if a replica is safely logging replicated transactions. interval: standard - family: MYSQL - advisor: configuration_replication - #author: tibi/the grinch + technology: MYSQL + category: Configuration + subcategory: Replication + # author: tibi/the grinch queries: - type: MYSQL_SHOW query: VARIABLES @@ -34,8 +35,8 @@ checks: if name == "log_replica_updates" or name == "log_slave_updates": if value == "OFF": results.append({ - "summary": "MySQL server updates received by a replica server from a replication source server are not logged to the replica's own binary log.", - "description": "MySQL server replicating events are not logged.", + "summary": "MySQL server replicating events are not logged", + "description": "MySQL server updates received by a replica server from a replication source server are not logged to the replica's own binary log.", "read_more_url":read_url.format("mysql-log-replica-updates"), "severity": "warning", "labels": {}, diff --git a/managed/data/checks/mysql_password_expiry.yml b/managed/data/checks/mysql_password_expiry.yml index 7fecf470552..30166611882 100644 --- a/managed/data/checks/mysql_password_expiry.yml +++ b/managed/data/checks/mysql_password_expiry.yml @@ -5,9 +5,10 @@ checks: summary: Check MySQL user password expiry description: Checks for MySQL user password expired or expiring within 30 days interval: standard - #author: Kedar Vaijanapurkar - family: MYSQL - advisor: security_configuration + # author: Kedar Vaijanapurkar + technology: MYSQL + category: Security + subcategory: Configuration queries: - type: MYSQL_SELECT query: "@@global.version as version, @@global.hostname as service, @@global.default_password_lifetime as default_password_lifetime" @@ -26,14 +27,16 @@ checks: "summary": "The global automatic password expiration policy is disabled.", "description": "The configuration variable default_password_lifetime defines global automatic password expiration policy. To enable this globally set default_password_lifetime to a positive Integer indicating password lifetime in days.", "read_more_url": read_url.format("mysql_password_expiry"), - "severity": "notice", + "severity": "warning", "labels": { "mysql_version": "{}".format(version), "service": "{}".format(service), } }) + expired_user_list = "" err_user_list = "" warn_user_list = "" + expired_cnt = 0 err_cnt = 0 warn_cnt = 0 for row in docs[1]: @@ -44,16 +47,8 @@ checks: password_lifetime = row["password_lifetime"] pwdresult = row["pwdresult"] if password_expired == 'Y': - results.append({ - "summary": "The password for MySQL user is expired. It is recommended to change the password using ALTER USER command.", - "description": "The password for MySQL user ({}@{}) has been expired. The user cannot connect to the database without changing the password.".format(user, host), - "read_more_url": read_url.format("mysql_password_expiry"), - "severity": "error", - "labels": { - "mysql_version": "{}".format(version), - "service": "{}".format(service), - } - }) + expired_user_list = "{}@{}, {}".format(user, host, expired_user_list) + expired_cnt += 1 else: if pwdresult == "ERROR" : err_user_list = "{}@{}, {}".format(user, host, err_user_list) @@ -64,6 +59,18 @@ checks: if pwdresult == "OK" : continue + if expired_cnt > 0 : + results.append({ + "summary": "{} MySQL user(s) with an expired password".format(expired_cnt), + "description": "The password of the following MySQL users has expired; they cannot connect to the database without changing the password using the ALTER USER command. Users list: {}".format(expired_user_list.removesuffix(', ')), + "read_more_url": read_url.format("mysql_password_expiry"), + "severity": "error", + "labels": { + "mysql_version": "{}".format(version), + "service": "{}".format(service), + "count": "{}".format(expired_cnt), + } + }) if err_cnt > 0 : results.append({ "summary": "MySQL users are marked for password expiry. It is recommended to change the password using ALTER USER command.", diff --git a/managed/data/checks/mysql_performance_temp_ondisk_table_high.yml b/managed/data/checks/mysql_performance_temp_ondisk_table_high.yml index ea2e0dacc1b..397b4929848 100644 --- a/managed/data/checks/mysql_performance_temp_ondisk_table_high.yml +++ b/managed/data/checks/mysql_performance_temp_ondisk_table_high.yml @@ -2,13 +2,12 @@ checks: - version: 2 name: mysql_performance_temp_ondisk_table_high - summary: Too many on disk temporary tables - description: This check warns against too many ondisk temporary tables created due to unoptimized query execution. - family: MYSQL - category: configuration - advisor: query_index - #subcategory: index - #author: Kedar Vaijanapurkar + summary: Too many on-disk temporary tables + description: This check warns against too many on-disk temporary tables created due to unoptimized query execution. + technology: MYSQL + category: Query + subcategory: Index + # author: Kedar Vaijanapurkar interval: standard queries: - type: MYSQL_SHOW @@ -31,12 +30,12 @@ checks: version = row["version"] service = row["service"] - perc = int(var_created_tmp_disk_tables) * 100 // int(var_created_tmp_tables) - if perc > 1 and perc <=5: + perc = int(var_created_tmp_disk_tables) * 100 // int(var_created_tmp_tables) + if perc > 1 and perc <= 5: return { - "summary": "More than {}% of the temporary tables are converted to on disk.".format(perc), + "summary": "More than {}% of the temporary tables are converted to on-disk.".format(perc), "read_more": "https://percona.com/blog", - "description": "More than {}% of the queries are causing temporary table creation on disk. Query and configuration review is recommended.".format(perc), + "description": "More than {}% of the queries are causing temporary table creation on-disk. Query and configuration review is recommended.".format(perc), "severity": "warning", "labels": { "mysql_version": "{}".format(version), @@ -46,10 +45,10 @@ checks: } if perc > 5: return { - "summary": "More than {}% of the temporary tables are converted to on disk.".format(perc), + "summary": "More than {}% of the temporary tables are converted to on-disk.".format(perc), "read_more": "https://percona.com/blog", - "description": "More than {}% of the queries are causing temporary table creation on disk. Query and configuration review is recommended.".format(perc), - "severity": "Error", + "description": "More than {}% of the queries are causing temporary table creation on-disk. Query and configuration review is recommended.".format(perc), + "severity": "error", "labels": { "mysql_version": "{}".format(version), "service": "{}".format(service), diff --git a/managed/data/checks/mysql_private_networks_only.yml b/managed/data/checks/mysql_private_networks_only.yml index bbbbe585184..42ccd549c64 100644 --- a/managed/data/checks/mysql_private_networks_only.yml +++ b/managed/data/checks/mysql_private_networks_only.yml @@ -3,9 +3,10 @@ checks: - version: 2 name: mysql_private_networks_only summary: MySQL Users With Granted Public Networks Access - description: Notifies about MySQL accounts currently allowed to connect from public networks. - family: MYSQL - advisor: security_connection + description: Discovers MySQL accounts currently allowed to connect from public networks. + technology: MYSQL + category: Security + subcategory: Connection interval: standard queries: - type: MYSQL_SELECT @@ -13,7 +14,7 @@ checks: script: | def check_context(docs, context): """ - This check returns a notice if MySQL accounts are allowed to be used from public networks + This check returns a warning if MySQL accounts are allowed to be used from public networks """ func = context.get("ip_is_private", fail) @@ -33,15 +34,11 @@ checks: count = len(users) if count: - desc = "account is" - if count > 1: - desc = "{} accounts are".format(count) - return [{ - "summary": "The following {} allowed to be connected from public networks".format(desc), - "description": " {}".format(users), + "summary": "Some accounts are allowed to be connected from public networks", + "description": "These accounts are allowed to connect from public networks: {}".format(", ".join(users)), "read_more_url": "https://dev.mysql.com/doc/refman/8.0/en/request-access.html", - "severity": "notice", + "severity": "warning", "labels": { "count": str(count), }, diff --git a/managed/data/checks/mysql_replica_running_skipping_errors_or_idempotent_mode.yml b/managed/data/checks/mysql_replica_running_skipping_errors_or_idempotent_mode.yml index 2eced2e9ea0..3752e713498 100644 --- a/managed/data/checks/mysql_replica_running_skipping_errors_or_idempotent_mode.yml +++ b/managed/data/checks/mysql_replica_running_skipping_errors_or_idempotent_mode.yml @@ -2,11 +2,12 @@ checks: - version: 2 name: mysql_replica_running_skipping_errors_or_idempotent_mode - summary: Checks if replica configured is skipping errors or slave_exec_mode is idempotent. + summary: Checks if replica configured is skipping errors or slave_exec_mode is idempotent description: This check reviews replication status to review if it is configured to skip errors or if the slave_exec_mode is configured to be idempotent. - family: MYSQL - #author: Kedar Vaijanapurkar - advisor: configuration_replication + technology: MYSQL + # author: Kedar Vaijanapurkar + category: Configuration + subcategory: Replication interval: standard queries: - type: MYSQL_SELECT diff --git a/managed/data/checks/mysql_replication_grants.yml b/managed/data/checks/mysql_replication_grants.yml index 759ccc17db9..b2e2e1f69a8 100644 --- a/managed/data/checks/mysql_replication_grants.yml +++ b/managed/data/checks/mysql_replication_grants.yml @@ -5,9 +5,10 @@ checks: summary: MySQL security check for replication user description: This check if node has replication configured without a user grants interval: standard - advisor: security_replication - #author: The Grinch - family: MYSQL + category: Security + subcategory: Replication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service /*!80001 , IF(ifnull((select SERVICE_STATE from performance_schema.replication_connection_status),'OFF') = 'ON',(select IF(count(User)< 1,1,0) from mysql.user where Repl_slave_priv='Y' ),0) as found,(select SOURCE_UUID from performance_schema.replication_connection_status) as source */;" @@ -16,8 +17,8 @@ checks: for row in doc: version = row["version"] service = row["service"] - source = row["source"] - found = row["found"] + source = row.get("source", "") + found = row.get("found", 0) if found > 0 : return { diff --git a/managed/data/checks/mysql_require_secure_transport.yml b/managed/data/checks/mysql_require_secure_transport.yml index da8911f74fd..a2bf790568d 100644 --- a/managed/data/checks/mysql_require_secure_transport.yml +++ b/managed/data/checks/mysql_require_secure_transport.yml @@ -5,9 +5,10 @@ checks: summary: MySQL configuration check description: Checks mysql_secure_transport_only. interval: standard - family: MYSQL - advisor: security_configuration - #author:tibi/the grinch + technology: MYSQL + category: Security + subcategory: Configuration + # author: tibi/the grinch queries: - type: MYSQL_SHOW query: VARIABLES diff --git a/managed/data/checks/mysql_security_anonymous_user.yml b/managed/data/checks/mysql_security_anonymous_user.yml index 5e37733b1c8..bacdeac0e7b 100644 --- a/managed/data/checks/mysql_security_anonymous_user.yml +++ b/managed/data/checks/mysql_security_anonymous_user.yml @@ -5,9 +5,10 @@ checks: summary: Anonymous user (you must remove any anonymous user) description: Anonymous user should never be present, that is a security safe best practices. interval: standard - advisor: security_authentication - #author: The Grinch - family: MYSQL + category: Security + subcategory: Authentication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, count(user) found,group_concat( concat(user,'@',host) separator'; ') user from mysql.user where user like '' group by version, service;" diff --git a/managed/data/checks/mysql_security_open_to_world_host.yml b/managed/data/checks/mysql_security_open_to_world_host.yml index 40813729460..693f25f242d 100644 --- a/managed/data/checks/mysql_security_open_to_world_host.yml +++ b/managed/data/checks/mysql_security_open_to_world_host.yml @@ -5,9 +5,10 @@ checks: summary: User(s) has/have host definition '%' which is too open description: Host definition should never be '%' given it is too open . interval: standard - advisor: security_authentication - #author: The Grinch - family: MYSQL + category: Security + subcategory: Authentication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, count(user) found,group_concat( concat(user,'@',host) separator'; ') user from mysql.user where host = '%' group by version, service;" diff --git a/managed/data/checks/mysql_security_password_lifetime.yml b/managed/data/checks/mysql_security_password_lifetime.yml index 4e07a9b16fd..7e775fd0ddd 100644 --- a/managed/data/checks/mysql_security_password_lifetime.yml +++ b/managed/data/checks/mysql_security_password_lifetime.yml @@ -5,9 +5,10 @@ checks: summary: InnoDB password lifetime description: This check warns about password lifetime. interval: standard - advisor: security_configuration - #author: The Grinch - family: MYSQL + category: Security + subcategory: Configuration + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, @@global.default_password_lifetime AS default_password_lifetime;" diff --git a/managed/data/checks/mysql_security_password_policy.yml b/managed/data/checks/mysql_security_password_policy.yml index 34a7f2fee14..70cc430e970 100644 --- a/managed/data/checks/mysql_security_password_policy.yml +++ b/managed/data/checks/mysql_security_password_policy.yml @@ -5,9 +5,10 @@ checks: summary: MySQL security check for password description: This check for password policy. interval: standard - advisor: security_configuration - #author: The Grinch - family: MYSQL + category: Security + subcategory: Configuration + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service,VARIABLE_VALUE as status, (select CASE VARIABLE_VALUE WHEN 'MEDIUM' then 0 WHEN 'STRONG' THEN 0 WHEN 'LOW' THEN 1 END)as found from performance_schema.global_variables where VARIABLE_NAME like 'validate_password_policy';" diff --git a/managed/data/checks/mysql_security_replication_grants_mixed.yml b/managed/data/checks/mysql_security_replication_grants_mixed.yml index 697d37c429d..5849448a6a9 100644 --- a/managed/data/checks/mysql_security_replication_grants_mixed.yml +++ b/managed/data/checks/mysql_security_replication_grants_mixed.yml @@ -5,9 +5,10 @@ checks: summary: Replication privileges description: Check if replication privileges is mixed with more elevated privileges interval: standard - #author: The Grinch - advisor: security_replication - family: MYSQL + # author: The Grinch + category: Security + subcategory: Replication + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@hostname service_name,version,count(user) nuser, group_concat(user SEPARATOR '; ')users, reason from(select @@version version, 1 found, concat(user,'@',host) user,plugin, 1 reason from mysql.user where Repl_slave_priv='Y'AND (Select_priv = 'Y' OR Insert_priv = 'Y' OR Update_priv = 'Y' OR Delete_priv = 'Y' OR Create_priv = 'Y' OR Drop_priv = 'Y' OR Reload_priv = 'Y' OR Shutdown_priv = 'Y' OR Process_priv = 'Y' OR File_priv = 'Y' OR Grant_priv = 'Y' OR References_priv = 'Y' OR Index_priv = 'Y' OR Alter_priv = 'Y' OR Show_db_priv = 'Y' OR Super_priv = 'Y' OR Create_tmp_table_priv = 'Y' OR Lock_tables_priv = 'Y' OR Execute_priv = 'Y' OR Repl_client_priv = 'Y' OR Create_view_priv = 'Y' OR Show_view_priv = 'Y' OR Create_routine_priv = 'Y' OR Alter_routine_priv = 'Y' OR Create_user_priv = 'Y' OR Event_priv = 'Y' OR Trigger_priv = 'Y' OR Create_tablespace_priv = 'Y')) as tt group by reason,service_name,version order by reason;" @@ -29,7 +30,7 @@ checks: for row in docs[0]: results.append({ "summary": "Replication privileges Mixed with more elevated grants", - "description": "{}. This instance has #{} occurrence(s) of users with mixed privileges {} ".format(whois, row.get("nuser"), row.get("users")), + "description": "{}. This instance has #{} occurrence(s) of users with mixed privileges: {}".format(whois, row.get("nuser"), row.get("users")), "severity": "warning", "read_more_url":read_url.format("mysql-replication-privileges"), "labels": { diff --git a/managed/data/checks/mysql_security_root_not_local.yml b/managed/data/checks/mysql_security_root_not_local.yml index 946494cbc65..fb1b496cfd9 100644 --- a/managed/data/checks/mysql_security_root_not_local.yml +++ b/managed/data/checks/mysql_security_root_not_local.yml @@ -5,9 +5,10 @@ checks: summary: Root user can connect from non local location description: Root user has host definition that is not 127.0.0.1 or localhost. interval: standard - advisor: security_authentication - #author: The Grinch - family: MYSQL + category: Security + subcategory: Authentication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, count(user) found,group_concat( concat(user,'@',host) separator'; ') user from mysql.user where user like 'root' AND host NOT IN ('localhost', '127.0.0.1') group by version, service;" diff --git a/managed/data/checks/mysql_security_user_ssl.yml b/managed/data/checks/mysql_security_user_ssl.yml index 75402dc155c..ed12b746dca 100644 --- a/managed/data/checks/mysql_security_user_ssl.yml +++ b/managed/data/checks/mysql_security_user_ssl.yml @@ -5,9 +5,10 @@ checks: summary: User(s) not using secure SSL protocol to connect description: User(s) not using secure SSL protocol to connect. interval: standard - advisor: security_authentication - #author: The Grinch - family: MYSQL + category: Security + subcategory: Authentication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service /*!80001, count(user) found,group_concat( concat(user,'@',host) separator'; ') user from mysql.user where ssl_type='' and user not in ('root','mysql.pxc.internal.session', 'mysql.pxc.sst.role', 'mysql.session','mysql.infoschema','mysql.pxc.sst.user','mysql.sys','pmm')*/;" @@ -16,14 +17,14 @@ checks: for row in doc: version = row["version"] service = row["service"] - user = row["user"] - found = row["found"] + user = row.get("user", "") + found = row.get("found", 0) if found > 0 : return { "summary": "User(s) not using secure SSL protocol to connect", "description": "User(s) not using secure SSL protocol to connect. User list = {} ;".format(user), - "severity": "notice", + "severity": "warning", "read_more_url": read_url.format("security-user-not-using-ssl-protocol"), "labels": { "mysql_version": "{}".format(version), diff --git a/managed/data/checks/mysql_security_user_super_not_local.yml b/managed/data/checks/mysql_security_user_super_not_local.yml index 4bad54d3b17..4f097a77c0c 100644 --- a/managed/data/checks/mysql_security_user_super_not_local.yml +++ b/managed/data/checks/mysql_security_user_super_not_local.yml @@ -2,12 +2,13 @@ checks: - version: 2 name: mysql_security_user_super_not_local - summary: User(s) has/have Super privileges with remote and too open access - description: User has Super privileges but is not connecting from local or the host is not fully restricted (ie 192.168.%). + summary: Users have super privileges with remote or lack access restrictions + description: Users have super privileges but are not connecting from local or the host is not fully restricted (i.e. 192.168.%). interval: standard - advisor: security_authentication - #author: The Grinch - family: MYSQL + category: Security + subcategory: Authentication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, count(user) found,group_concat( concat(user,'@',host) separator'; ') user from mysql.user where Super_priv = 'Y' and user not in('mysql.pxc.internal.session', 'mysql.pxc.sst.role', 'mysql.session','mysql.infoschema','mysql.pxc.sst.user','mysql.sys') and plugin != 'auth_socket' and host like '%'and host not in ('127.0.0.1','localhost') group by version;" @@ -21,9 +22,9 @@ checks: if found > 0 : return { - "summary": "User(s) has/have Super privileges with remote and too open access", - "description": "User has Super privileges but is not connecting from local or the host is not fully restricted (ie 192.168.%). User list = {} ;".format(user), - "severity": "notice", + "summary": "Users have super privileges with remote or lack access restrictions", + "description": "Users have super privileges but are not connecting from local or the host is not fully restricted (i.e. 192.168.%). User list = {} ;".format(user), + "severity": "warning", "read_more_url": read_url.format("security-non-root-accounts-super-privileges"), "labels": { "mysql_version": "{}".format(version), diff --git a/managed/data/checks/mysql_security_user_without_password.yml b/managed/data/checks/mysql_security_user_without_password.yml index c6c6d78cae7..c75c4004893 100644 --- a/managed/data/checks/mysql_security_user_without_password.yml +++ b/managed/data/checks/mysql_security_user_without_password.yml @@ -5,9 +5,10 @@ checks: summary: User(s) without password description: There is/are user(s) without password . interval: standard - advisor: security_authentication - #author: The Grinch - family: MYSQL + category: Security + subcategory: Authentication + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service, count(user) found,group_concat( concat(user,'@',host) separator'; ') user from mysql.user where authentication_string = '' and account_locked = 'N' and plugin not in ('auth_socket','unix_socket','auth_pam','auth_pam_compat','pam','AWSAuthenticationPlugin' ) group by version, service;" diff --git a/managed/data/checks/mysql_tables_without_pk.yml b/managed/data/checks/mysql_tables_without_pk.yml index 9bef4bf1191..8d062318043 100644 --- a/managed/data/checks/mysql_tables_without_pk.yml +++ b/managed/data/checks/mysql_tables_without_pk.yml @@ -5,9 +5,10 @@ checks: summary: MySQL check for table without Primary Key description: Checks tables without primary keys. interval: standard - family: MYSQL - #author:tibi/the grinch - advisor: query_index + technology: MYSQL + # author: tibi/the grinch + category: Query + subcategory: Index queries: - type: MYSQL_SELECT query: | diff --git a/managed/data/checks/mysql_test_database.yml b/managed/data/checks/mysql_test_database.yml index ba14d5fbc3c..ffa1ba4e8fe 100644 --- a/managed/data/checks/mysql_test_database.yml +++ b/managed/data/checks/mysql_test_database.yml @@ -3,9 +3,10 @@ checks: - version: 2 name: mysql_test_database summary: MySQL test Database - description: This check returns a notice if there are databases with name 'test' or 'test_%'. - family: MYSQL - advisor: configuration_generic + description: Identifies databases with name 'test' or 'test_%' + technology: MYSQL + category: Configuration + subcategory: Generic interval: standard queries: - type: MYSQL_SHOW @@ -13,7 +14,7 @@ checks: script: | def check_context(docs, context): """ - This check returns a notice if database with name 'test' or 'test_%' are discovered. + This check identifies databases with name 'test' or 'test_%'. """ dbs = [] @@ -24,13 +25,10 @@ checks: dbs.append(db) if dbs: - description = "s are" - if len(dbs) == 1: - description = " is" return [{ - "summary": "Test database{} detected: {}".format(description,dbs), + "summary": "Test database(s) detected: {}".format(", ".join(dbs)), "description": "Improve MySQL Installation Security by running mysql_secure_installation", - "severity": "notice" + "severity": "info" }] return [] diff --git a/managed/data/checks/mysql_timezone.yml b/managed/data/checks/mysql_timezone.yml index ed8238a2f31..3c548e0bd23 100644 --- a/managed/data/checks/mysql_timezone.yml +++ b/managed/data/checks/mysql_timezone.yml @@ -5,9 +5,10 @@ checks: summary: MySQL configuration check description: Checks if time zone is correctly loaded. interval: standard - family: MYSQL - #author:tibi/the grinch - advisor: configuration_generic + technology: MYSQL + # author: tibi/the grinch + category: Configuration + subcategory: Generic queries: - type: MYSQL_SELECT query: count(*) as timezonecount from mysql.time_zone_name @@ -35,7 +36,7 @@ checks: "summary": "MySQL Time Zone data is not loaded", "description": "MySQL Time Zone data is not loaded.", "read_more_url":read_url.format("time-zone-data-not-loaded"), - "severity": "notice", + "severity": "warning", "labels": {}, }) return results diff --git a/managed/data/checks/mysql_unsupported_version_check.yml b/managed/data/checks/mysql_unsupported_version_check.yml index 88381c26a17..71bc883a574 100644 --- a/managed/data/checks/mysql_unsupported_version_check.yml +++ b/managed/data/checks/mysql_unsupported_version_check.yml @@ -4,9 +4,10 @@ checks: name: mysql_unsupported_version_check summary: Checks mysql version for support description: This check warns against an unsupported mysql version - family: MYSQL - #author: Kedar Vaijanapurkar - advisor: configuration_version + technology: MYSQL + # author: Kedar Vaijanapurkar + category: Configuration + subcategory: Version interval: standard queries: - type: MYSQL_SELECT @@ -49,7 +50,7 @@ checks: return { "summary": "Unsupported Version Check", "description": "You are running a server version({}) that is EOL. It is strongly recommended to upgrade to latest version.".format(version), - "severity": "Error", + "severity": "error", "read_more_url": read_url.format("mysql_unsupported_version_check"), "labels": { "mysql_version": "{}".format(version), diff --git a/managed/data/checks/mysql_version.yml b/managed/data/checks/mysql_version.yml index 57f790739ce..59340d1b405 100644 --- a/managed/data/checks/mysql_version.yml +++ b/managed/data/checks/mysql_version.yml @@ -4,8 +4,9 @@ checks: name: mysql_version summary: MySQL Version description: This check returns warnings if MySQL, Percona Server for MySQL, or MariaDB version is not the latest one. - family: MYSQL - advisor: configuration_version + technology: MYSQL + category: Configuration + subcategory: Version interval: standard queries: - type: MYSQL_SHOW @@ -112,7 +113,7 @@ checks: "description": "Current version is {}, the latest available version is {}.".format( format_version_num(num), format_version_num(latest)), "read_more_url": LATEST_VERSIONS["percona_server_url"][major], - "severity": "notice", + "severity": "warning", "labels": { "current": format_version_num(num), "latest": format_version_num(latest), @@ -129,7 +130,7 @@ checks: "description": "Current version is {}, the latest available version is {}.".format( format_version_num(num), format_version_num(latest)), "read_more_url": LATEST_VERSIONS["percona_xtradb_cluster_url"][major], - "severity": "notice", + "severity": "warning", "labels": { "current": format_version_num(num), "latest": format_version_num(latest), @@ -150,7 +151,7 @@ checks: "description": "Current version is {}, the latest available version is {}.".format( format_version_num(num), format_version_num(latest)), "read_more_url": LATEST_VERSIONS["rds_url"][major], - "severity": "notice", + "severity": "warning", "labels": { "current": format_version_num(num), "latest": format_version_num(latest), @@ -167,7 +168,7 @@ checks: "description": "Current version is {}, the latest available version is {}.".format( format_version_num(num), format_version_num(latest)), "read_more_url": LATEST_VERSIONS["mariadb_url"][major], - "severity": "notice", + "severity": "warning", "labels": { "current": format_version_num(num), "latest": format_version_num(latest), @@ -184,7 +185,7 @@ checks: "description": "Current version is {}, the latest available version is {}.".format( format_version_num(num), format_version_num(latest)), "read_more_url": LATEST_VERSIONS["oracle_url"][major], - "severity": "notice", + "severity": "warning", "labels": { "current": format_version_num(num), "latest": format_version_num(latest), diff --git a/managed/data/checks/mysql_version_eol_57.yml b/managed/data/checks/mysql_version_eol_57.yml index 2813a056328..0db206db888 100644 --- a/managed/data/checks/mysql_version_eol_57.yml +++ b/managed/data/checks/mysql_version_eol_57.yml @@ -2,12 +2,13 @@ checks: - version: 2 name: mysql_version_eol_57 - summary: End Of Life server version (5.7). + summary: End Of Life server version (5.7) description: Check if server version is EOL interval: standard - advisor: configuration_version - #author: The Grinch - family: MYSQL + category: Configuration + subcategory: Version + # author: The Grinch + technology: MYSQL queries: - type: MYSQL_SELECT query: " @@version version,@@hostname service,@@version_comment as comment, @@basedir as basedir,@@datadir as datadir, datediff('2023-10-23',date(now())) as days;" diff --git a/managed/data/checks/postgresql_archiver_failing.yml b/managed/data/checks/postgresql_archiver_failing.yml index b7e48f2f61c..91ed670c9da 100644 --- a/managed/data/checks/postgresql_archiver_failing.yml +++ b/managed/data/checks/postgresql_archiver_failing.yml @@ -4,9 +4,10 @@ checks: name: postgresql_archiver_failing summary: PostgreSQL Archiver is failing description: This check verifies if the archiver has failed. - family: POSTGRESQL - #author: David Gonzales - advisor: configuration_generic + technology: POSTGRESQL + # author: David Gonzales + category: Configuration + subcategory: Generic interval: standard queries: - type: POSTGRESQL_SELECT diff --git a/managed/data/checks/postgresql_cache_hit_ratio.yml b/managed/data/checks/postgresql_cache_hit_ratio.yml index 0c72f1e4c12..b39a4442ca0 100644 --- a/managed/data/checks/postgresql_cache_hit_ratio.yml +++ b/managed/data/checks/postgresql_cache_hit_ratio.yml @@ -4,9 +4,10 @@ checks: name: postgresql_cache_hit_ratio summary: PostgreSQL cache hit ratio description: This check the hitratio of one or more databases and complains when they are too low. - family: POSTGRESQL - #author: David Gonzales - advisor: performance_generic + technology: POSTGRESQL + # author: David Gonzales + category: Performance + subcategory: Generic interval: standard queries: - type: POSTGRESQL_SELECT diff --git a/managed/data/checks/postgresql_config_changes_need_restart.yml b/managed/data/checks/postgresql_config_changes_need_restart.yml index 1e1e7fa8a35..c7a1763a9b2 100644 --- a/managed/data/checks/postgresql_config_changes_need_restart.yml +++ b/managed/data/checks/postgresql_config_changes_need_restart.yml @@ -2,11 +2,12 @@ checks: - version: 2 name: postgresql_config_changes_need_restart - summary: Configuration change requires restart/reload. + summary: Configuration change requires restart/reload description: This check returns a warning if there is any setting/configuration that was changed and needs a server restart/reload. - family: POSTGRESQL - #author: Charly Batista - advisor: performance_generic + technology: POSTGRESQL + # author: Charly Batista + category: Performance + subcategory: Generic interval: standard queries: - type: POSTGRESQL_SELECT diff --git a/managed/data/checks/postgresql_cve_check.yml b/managed/data/checks/postgresql_cve_check.yml index 0b13c79ba19..6c6064774a9 100644 --- a/managed/data/checks/postgresql_cve_check.yml +++ b/managed/data/checks/postgresql_cve_check.yml @@ -8,10 +8,10 @@ checks: # This dictionary is based on the CVE list on the postgresql.org security website https://www.postgresql.org/support/security/ # The key is the CVE reference. # The values are a space separated list of postgresql versions where the CVE has been addressed - advisor: security_cve + category: Security + subcategory: Vulnerabilities interval: standard - family: POSTGRESQL - category: configuration + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: "setting, (setting::int / 10000) major FROM pg_settings WHERE name = 'server_version_num' " @@ -83,7 +83,7 @@ checks: # Lets see if we have CVE's for the version of Postgres we are checking def check_cve(cveRef, current_version, checkedMajor, checkedVersionFull): - msgStr = "" + cveFound = [] cveFixedInVersion = [] for numberOfCves in range(len(cveRef)): @@ -126,7 +126,7 @@ checks: if checkedVersionFull < fixedVersionFull: cveFull = convertShortToFull(fixedVersions[numberOfFixedVersions]) cveFixedInVersion.append(cveFull) - msgStr = msgStr + " ({}), ".format(cveRef[numberOfCves]) + cveFound.append(cveRef[numberOfCves]) if len(cveFixedInVersion) == 0 : @@ -134,9 +134,8 @@ checks: maxVer = max(cveFixedInVersion) fixedInVersion = convertFullToShort(maxVer) - msgStr = msgStr + " fixed with Postgres {} ".format(fixedInVersion) - return msgStr + return "{}. Fixed in Postgres {}.".format(", ".join(cveFound), fixedInVersion) # ----------------------------------------------------------------------------------------------------------- # All these conversion will work with versions 10 and higher. Version 9 has different major minor construct @@ -227,7 +226,7 @@ checks: results.append({ "summary": "Checking for Postgres CVE's", - "description": "Postgres {} has the following CVE's: {}".format( current_version, vul ), + "description": "Postgres {} has the following CVEs: {}".format(current_version, vul), "read_more_url":read_url.format("postgresql-cve-check"), "severity": "error", "labels": {}, diff --git a/managed/data/checks/postgresql_eol_check.yml b/managed/data/checks/postgresql_eol_check.yml index 3ea35730167..ae8a1a2c20f 100644 --- a/managed/data/checks/postgresql_eol_check.yml +++ b/managed/data/checks/postgresql_eol_check.yml @@ -4,10 +4,11 @@ checks: name: postgresql_eol_check summary: Check if PostgreSQL version is EOL description: Checks to see if the currently installed PostgreSQL version is end of life and no longer supported - #author: Jorge Torralba - advisor: configuration_version + # author: Jorge Torralba + category: Configuration + subcategory: Version interval: standard - family: POSTGRESQL + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: "setting, (setting::int / 10000) major FROM pg_settings WHERE name = 'server_version_num' " diff --git a/managed/data/checks/postgresql_expiring_passwd_check.yml b/managed/data/checks/postgresql_expiring_passwd_check.yml index 47c4165b59f..e11a1e6006f 100644 --- a/managed/data/checks/postgresql_expiring_passwd_check.yml +++ b/managed/data/checks/postgresql_expiring_passwd_check.yml @@ -4,34 +4,36 @@ checks: name: postgresql_expiring_passwd_check summary: Check for password expiration description: Check for passwords which are expiring and displays the time left before it expires - #author: Jorge Torralba - #advisor: version_check - advisor: security_configuration + # author: Jorge Torralba + category: Security + subcategory: Configuration interval: standard - family: POSTGRESQL - category: configuration + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: "rolname, rolvaliduntil::timestamp(0)::text, floor((extract(epoch from rolvaliduntil) - extract(epoch from now())) / 86400)::int AS expires FROM pg_roles WHERE rolvaliduntil IS NOT NULL" script: | + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(rows, context): results = [] read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" - description = "" - sev="warning" + roles = [] for row in rows[0]: - rolname, rolvaliduntil, expires = row["rolname"], row["rolvaliduntil"], row["expires"] - + rolname, expires = row["rolname"], row["expires"] if expires <= 10: - sev = "warning" - - results.append({ - "summary": "Checking for expiring passwords", - "description": "Password for {} will expire in {} days".format( rolname, expires ), - "read_more_url":read_url.format("postgresql-expiring-passwd"), - "severity": sev, - "labels": {}, - }) + roles.append("{} (expires in {} days)".format(rolname, expires)) + if roles: + results.append({ + "summary": "{} role(s) with passwords expiring within 10 days".format(len(roles)), + "description": "Passwords of the following roles are about to expire: {}.".format(format_list(roles)), + "read_more_url":read_url.format("postgresql-expiring-passwd"), + "severity": "warning", + "labels": {"count": str(len(roles))}, + }) return results diff --git a/managed/data/checks/postgresql_extension_check.yml b/managed/data/checks/postgresql_extension_check.yml index d2f10ec8f96..fce5c4ef0ea 100644 --- a/managed/data/checks/postgresql_extension_check.yml +++ b/managed/data/checks/postgresql_extension_check.yml @@ -5,8 +5,9 @@ checks: summary: Check for outdated extensions description: This check will list outdated extensions with newer versions available interval: standard - family: POSTGRESQL - advisor: configuration_version + technology: POSTGRESQL + category: Configuration + subcategory: Version queries: - type: POSTGRESQL_SELECT query: "current_database() AS db, name, installed_version, default_version FROM pg_available_extensions WHERE installed_version IS NOT NULL AND default_version IS NOT NULL AND installed_version != default_version" @@ -14,19 +15,30 @@ checks: all_dbs: true script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" - def check_context(tuple, context): + + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + + def check_context(docs, context): results = [] - for checkedDB, rows in tuple[0].items(): + for checkedDB, rows in docs[0].items(): + dbname = "" + outdated = [] for row in rows: dbname = row["db"] - name = row["name"] - installed_version = row["installed_version"] - default_version = row["default_version"] - results.append({ - "summary": "Outdated extensions found", - "description": "database {} has extension {} installed with an outdated version {}. A newer version {} is available.".format(dbname, name, installed_version, default_version), - "read_more_url":read_url.format("postgresql-extension-check"), - "severity": "warning", - }) + outdated.append("{} ({} -> {})".format(row["name"], row["installed_version"], row["default_version"])) + + if not outdated: + continue + + results.append({ + "summary": "{} outdated extension(s) in database {}".format(len(outdated), dbname), + "description": "The database {} has extensions installed with an outdated version: {}.".format(dbname, format_list(outdated)), + "read_more_url":read_url.format("postgresql-extension-check"), + "severity": "warning", + "labels": {"database": dbname, "count": str(len(outdated))}, + }) return results diff --git a/managed/data/checks/postgresql_fsync.yml b/managed/data/checks/postgresql_fsync.yml index cb67418a6b4..badd6cf3e92 100644 --- a/managed/data/checks/postgresql_fsync.yml +++ b/managed/data/checks/postgresql_fsync.yml @@ -4,9 +4,10 @@ checks: name: postgresql_fsync summary: PostgreSQL fsync is set to off description: This check returns an error if the fsync configuration option is off which can lead to database corruption. - family: POSTGRESQL - #author: Jobin Augustine - advisor: configuration_generic + technology: POSTGRESQL + # author: Jobin Augustine + category: Configuration + subcategory: Generic interval: standard queries: - type: POSTGRESQL_SELECT diff --git a/managed/data/checks/postgresql_log_autovacuum_min_duration.yml b/managed/data/checks/postgresql_log_autovacuum_min_duration.yml index e644457b039..0ff8680c735 100644 --- a/managed/data/checks/postgresql_log_autovacuum_min_duration.yml +++ b/managed/data/checks/postgresql_log_autovacuum_min_duration.yml @@ -3,10 +3,11 @@ checks: - version: 2 name: postgresql_log_autovacuum_min_duration summary: PostgreSQL Autovacuum Logging Is Disabled - description: This check returns a notice if the log_autovacuum_min_duration configuration option is set to -1 (disabled). It is recommended to enable the logging of autovacuum run information, as that provides a lot of useful information with almost no drawbacks. - family: POSTGRESQL - #author: Sergey Kuzmichev - advisor: configuration_vacuum + description: This check returns a warning if the log_autovacuum_min_duration configuration option is set to -1 (disabled). It is recommended to enable the logging of autovacuum run information, as that provides a lot of useful information with almost no drawbacks. + technology: POSTGRESQL + # author: Sergey Kuzmichev + category: Configuration + subcategory: Vacuum interval: standard queries: - type: POSTGRESQL_SELECT @@ -27,7 +28,7 @@ checks: "summary": "The current value of log_autovacuum_min_duration is -1", "description": "Logging of autovacuum run information is currently disabled. It is recommended to enable the logging of autovacuum run information, as that provides a lot of useful information with almost no drawbacks.", "read_more_url": read_url.format("configuration-pg-log-autovacuum-disabled"), - "severity": "notice" + "severity": "warning" }) return results diff --git a/managed/data/checks/postgresql_log_checkpoints.yml b/managed/data/checks/postgresql_log_checkpoints.yml index 40ae11e2b34..89465f5f989 100644 --- a/managed/data/checks/postgresql_log_checkpoints.yml +++ b/managed/data/checks/postgresql_log_checkpoints.yml @@ -2,11 +2,12 @@ checks: - version: 2 name: postgresql_log_checkpoints - summary: PostgreSQL Checkpoints Logging is Disabled. - description: This check returns a notice if the log_checkpoints configuration option is not enabled. It is recommended to enable the logging of checkpoint information, as that provides a lot of useful information with almost no drawbacks. - family: POSTGRESQL - #author: Sergey Kuzmichev - advisor: configuration_generic + summary: PostgreSQL Checkpoints Logging Disabled + description: This check returns a warning if the log_checkpoints configuration option is not enabled. It is recommended to enable the logging of checkpoint information, as that provides a lot of useful information with almost no drawbacks. + technology: POSTGRESQL + # author: Sergey Kuzmichev + category: Configuration + subcategory: Generic interval: standard queries: - type: POSTGRESQL_SELECT @@ -27,7 +28,7 @@ checks: "summary": "The current value of log_checkpoints is off", "description": "Logging of checkpoint information is currently disabled. It is recommended to enable the logging of checkpoint information, as that provides a lot of useful information with almost no drawbacks.", "read_more_url": read_url.format("configuration-pg-log-checkpoints-disabled"), - "severity": "notice" + "severity": "warning" }) return results diff --git a/managed/data/checks/postgresql_logging_recommendation_checks.yml b/managed/data/checks/postgresql_logging_recommendation_checks.yml index 5ebec0771fb..39808a2fc57 100644 --- a/managed/data/checks/postgresql_logging_recommendation_checks.yml +++ b/managed/data/checks/postgresql_logging_recommendation_checks.yml @@ -4,10 +4,11 @@ checks: name: postgresql_logging_recommendation_checks summary: Check for minimal logging description: Checks to see if recommended minimum logging features are enabled. - advisor: configuration_generic - #Author: Jorge Torralba + category: Configuration + subcategory: Generic + # author: Jorge Torralba interval: standard - family: POSTGRESQL + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: " s1.setting AS logging_collector, s2.setting AS log_temp_files, s3.setting log_checkpoints, s4.setting log_min_duration_statement, s5.unit unit, s6.min_val AS minval, s7.setting AS log_autovacuum_min_duration FROM pg_settings s1 , pg_settings s2 , pg_settings s3 , pg_settings s4 , pg_settings s5 , pg_settings s6 , pg_settings s7 WHERE s1.name = 'logging_collector' and s2.name = 'log_temp_files' and s3.name = 'log_checkpoints' and s4.name = 'log_min_duration_statement' and s5.name = 'log_min_duration_statement' and s6.name = 'log_min_duration_statement' and s7.name = 'log_autovacuum_min_duration'" @@ -17,7 +18,6 @@ checks: def check_context(rows, context): results = [] description = "" - nl = " " sec = 0 for row in rows[0]: logging_collector = row["logging_collector"] @@ -29,13 +29,13 @@ checks: log_autovacuum_min_duration = row["log_autovacuum_min_duration"] if logging_collector == "off": - description = description + "logging_collector is disabled. " + nl + description += "logging_collector is disabled. " if int(log_temp_files) < 0: - description = description + "log_temp_files is disabled. " + nl + description += "log_temp_files is disabled. " if log_checkpoints == "off": - description = description + "log_checkpoints is disabled. " + nl + description += "log_checkpoints is disabled. " if log_min_duration_statement.isdigit(): @@ -43,25 +43,25 @@ checks: if int(log_min_duration_statement) > 0: sec = int(log_min_duration_statement) / 1000 if int(sec) > 10: - description = description + "The log_min_duration_statement setting of {} seconds seems high and may not log all intended queries. ".format(sec) + nl + description += "The log_min_duration_statement setting of {} seconds seems high and may not log all intended queries. ".format(sec) if sec < 2: - description = description + "The log_min_duration_statement setting of {} seconds seems low and may log more intended queries than intended. ".format(sec) + nl + description += "The log_min_duration_statement setting of {} seconds seems low and may log more queries than intended. ".format(sec) if int(log_min_duration_statement) < 0: - description = description + "log_min_duration_statement is disabled. " + nl + description += "log_min_duration_statement is disabled. " if int(log_min_duration_statement) == 0: - description = description + "log_min_duration_statement is set to log all statements. This could lead to excessive logging IO. " + nl + description += "log_min_duration_statement is set to log all statements. This could lead to excessive logging IO. " if int(log_autovacuum_min_duration) < 0: - description = description + "log_autovacuum_min_duration is disabled. " + nl + description += "log_autovacuum_min_duration is disabled. " if description != "": results.append({ "summary": "Logging check issues found.", - "description": description, + "description": description.strip(), "read_more_url": read_url.format("postgresql-logging-recommendations"), "severity": "warning" }) diff --git a/managed/data/checks/postgresql_max_connections.yml b/managed/data/checks/postgresql_max_connections.yml index 8f36527908a..5b6e8057074 100644 --- a/managed/data/checks/postgresql_max_connections.yml +++ b/managed/data/checks/postgresql_max_connections.yml @@ -2,11 +2,12 @@ checks: - version: 2 name: postgresql_max_connections - summary: PostgreSQL max_connections is too high. - description: This check returns a notice if the max_connections configuration option is set to a high value (above 300). PostgreSQL doesn't cope well with having many connections even if they are idle. Recommended value is below 300. - family: POSTGRESQL - #author: Sergey Kuzmichev - advisor: configuration_connection + summary: PostgreSQL max_connections too high + description: This check returns a warning if the max_connections configuration option is set to a high value (above 300). PostgreSQL doesn't cope well with having many connections even if they are idle. Recommended value is below 300. + technology: POSTGRESQL + # author: Sergey Kuzmichev + category: Configuration + subcategory: Connection interval: standard queries: - type: POSTGRESQL_SELECT @@ -32,7 +33,7 @@ checks: "summary": "The current value of max_connections is set too high", "description": "Current value is {}, while we recommend staying within {}. PostgreSQL doesn't cope well with having many connections even if they are idle.".format(num, RECOMMENDED_MAX_VALUE), "read_more_url": read_url.format("configuration-pg-high-max-connections"), - "severity": "notice" + "severity": "warning" }) return results diff --git a/managed/data/checks/postgresql_multidb.yml.example b/managed/data/checks/postgresql_multidb.yml.example index 8eb745ede6f..d9b56505d44 100644 --- a/managed/data/checks/postgresql_multidb.yml.example +++ b/managed/data/checks/postgresql_multidb.yml.example @@ -5,8 +5,9 @@ checks: summary: PostgreSQL multidb check description: This check demonstrates all_dbs option for PostgreSQL check queries interval: standard - family: POSTGRESQL - advisor: example + technology: POSTGRESQL + category: Security + subcategory: Example queries: - type: POSTGRESQL_SELECT query: current_database() diff --git a/managed/data/checks/postgresql_number_of_index_check.yml b/managed/data/checks/postgresql_number_of_index_check.yml index 26d8f289093..689a225a72e 100644 --- a/managed/data/checks/postgresql_number_of_index_check.yml +++ b/managed/data/checks/postgresql_number_of_index_check.yml @@ -2,11 +2,12 @@ checks: - version: 2 name: postgresql_number_of_index_check - summary: Check for relations have high number of indexes - description: This check will list relations with more than 10 indexes + summary: Check for relations with a high number of indexes + description: This check lists relations with more than 10 indexes interval: standard - family: POSTGRESQL - advisor: query_index + technology: POSTGRESQL + category: Query + subcategory: Index queries: - type: POSTGRESQL_SELECT query: " current_database() AS datname, relname, count(*) AS idxcount FROM pg_stat_user_indexes GROUP BY 1,2 ORDER BY 3 DESC " @@ -14,23 +15,33 @@ checks: all_dbs: true script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(tuple, context): results = [] for checkedDB, rows in tuple[0].items(): + datname = "" + relations = [] for row in rows: - datname = row["datname"] - relname = row["relname"] - idxcount = row["idxcount"] - # Cant get rid of decimal point without formatting to index - idxcount = int(idxcount) + idxcount = int(row["idxcount"]) + if idxcount >= 10: + datname = row["datname"] + relations.append("{} ({} indexes)".format(row["relname"], idxcount)) + + if not relations: + continue - if int(idxcount) >= 10: - results.append({ - "summary": "Relations with more than 10 indexes", - "description": "The relation {}.{} has {} indexes".format(datname, relname, idxcount), - "read_more_url":read_url.format("postgresql-high-number-of-indexes-check"), - "severity": "warning", - }) + results.append({ + "summary": "{} relation(s) with more than 10 indexes in database {}".format(len(relations), datname), + "description": "The database {} has relations with more than 10 indexes: {}.".format(datname, format_list(relations)), + "read_more_url":read_url.format("postgresql-high-number-of-indexes-check"), + "severity": "warning", + "labels": {"database": datname, "count": str(len(relations))}, + }) return results diff --git a/managed/data/checks/postgresql_sequential_scan_check.yml b/managed/data/checks/postgresql_sequential_scan_check.yml index 8fbbe1528bb..ae240527b3e 100644 --- a/managed/data/checks/postgresql_sequential_scan_check.yml +++ b/managed/data/checks/postgresql_sequential_scan_check.yml @@ -5,8 +5,9 @@ checks: summary: PostgreSQL sequential scan check description: This check for tables with excessive sequential scans interval: standard - family: POSTGRESQL - advisor: query_index + technology: POSTGRESQL + category: Query + subcategory: Index queries: - type: POSTGRESQL_SELECT query: " schemaname, relname, n_live_tup, seq_scan, COALESCE(idx_scan,0) AS idx_scan, ROUND(seq_scan / ( seq_scan + COALESCE(idx_scan,0))::numeric * 100)::int AS percent_seq_scan FROM pg_stat_user_tables WHERE seq_scan > 0 AND n_live_tup >= 50000" @@ -14,23 +15,30 @@ checks: all_dbs: true script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(tuple, context): results = [] for checkedDB, rows in tuple[0].items(): + tables = [] for row in rows: - schemaname = row["schemaname"] - relname = row["relname"] - n_live_tup = row["n_live_tup"] - seq_scan = row["seq_scan"] - idx_scan = row["idx_scan"] percent_seq_scan = row["percent_seq_scan"] - fqn = schemaname + "." + relname if percent_seq_scan >= 50: - results.append({ - "summary": "Excessive sequential scans", - "description": "{}% of all scans on {} have been sequential scans.".format(percent_seq_scan, fqn), - "read_more_url":read_url.format("postgresql-sequential-scan-check"), - "severity": "warning", - }) + fqn = row["schemaname"] + "." + row["relname"] + tables.append("{} ({}% sequential)".format(fqn, percent_seq_scan)) + + if not tables: + continue + + results.append({ + "summary": "{} table(s) with excessive sequential scans in database {}".format(len(tables), checkedDB), + "description": "The database {} has tables where sequential scans dominate: {}.".format(checkedDB, format_list(tables)), + "read_more_url":read_url.format("postgresql-sequential-scan-check"), + "severity": "warning", + "labels": {"database": checkedDB, "count": str(len(tables))}, + }) return results diff --git a/managed/data/checks/postgresql_stale_replication_slot.yml b/managed/data/checks/postgresql_stale_replication_slot.yml index 3692a00ffa7..f9d3938ffbb 100644 --- a/managed/data/checks/postgresql_stale_replication_slot.yml +++ b/managed/data/checks/postgresql_stale_replication_slot.yml @@ -4,9 +4,10 @@ checks: name: postgresql_stale_replication_slot summary: PostgreSQL Stale Replication Slot description: This check returns a warning if there is a stale replication slot. Stale replication slots will lead to WAL file accumulation and can result in a DB server outage. - family: POSTGRESQL - #author: Sergey Kuzmichev - advisor: performance_replication + technology: POSTGRESQL + # author: Sergey Kuzmichev + category: Performance + subcategory: Replication interval: standard queries: - type: POSTGRESQL_SELECT diff --git a/managed/data/checks/postgresql_super_role.yml b/managed/data/checks/postgresql_super_role.yml index 1950c44911f..2b17c4ec9ae 100644 --- a/managed/data/checks/postgresql_super_role.yml +++ b/managed/data/checks/postgresql_super_role.yml @@ -3,9 +3,10 @@ checks: - version: 2 name: postgresql_super_role summary: PostgreSQL Super Role - description: This check returns a notice if there are users with superuser role. - family: POSTGRESQL - advisor: security_authentication + description: This check returns a warning if there are users with superuser role. + technology: POSTGRESQL + category: Security + subcategory: Authentication interval: standard queries: - type: POSTGRESQL_SELECT @@ -13,7 +14,7 @@ checks: script: | def check_context(docs, context): """ - This check returns a notice if there are users with superuser role. + This check returns a warning if there are users with superuser role. """ users = [] @@ -33,7 +34,7 @@ checks: return [{ "summary": "User(s) with Superuser role found", "description": "{} - {}".format(desc, users), - "severity": "notice", + "severity": "warning", "labels": { "count": str(count), }, diff --git a/managed/data/checks/postgresql_table_autovac_settings.yml b/managed/data/checks/postgresql_table_autovac_settings.yml index 43655d1abbc..9ff5c21abea 100644 --- a/managed/data/checks/postgresql_table_autovac_settings.yml +++ b/managed/data/checks/postgresql_table_autovac_settings.yml @@ -4,9 +4,10 @@ checks: name: postgresql_table_autovac_settings summary: Check whether there is any table level autovacuum settings description: This check returns those tables where autovacuum parameters are specified along with autovacuum settings specified - family: POSTGRESQL - #author: Jobin Augustine - advisor: configuration_vacuum + technology: POSTGRESQL + # author: Jobin Augustine + category: Configuration + subcategory: Vacuum interval: standard queries: - type: POSTGRESQL_SELECT @@ -18,15 +19,24 @@ checks: # thresholds # RECOMMENDED_MAX_VALUE="on" + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(docs, context): results = [] + tables = [] for row in docs[0]: - tabname, opts = row["relname"], row["opts"] + tables.append("{} ({})".format(row["relname"], row["opts"])) + + if tables: results.append({ - "summary": "Table level autovacuum settings are existing", - "description": "Table {} has autovacuum settings {}".format(tabname, opts), + "summary": "{} table(s) with table-level autovacuum settings".format(len(tables)), + "description": "The following tables override the global autovacuum settings: {}.".format(format_list(tables)), "read_more_url": read_url.format("postgresql-tables-per-table-vacuum-settings"), - "severity": "notice" + "severity": "warning", + "labels": {"count": str(len(tables))}, }) return results diff --git a/managed/data/checks/postgresql_table_bloat_bytes.yml b/managed/data/checks/postgresql_table_bloat_bytes.yml index 41834d0ba76..cc7ebc7c6cf 100644 --- a/managed/data/checks/postgresql_table_bloat_bytes.yml +++ b/managed/data/checks/postgresql_table_bloat_bytes.yml @@ -4,9 +4,10 @@ checks: name: postgresql_table_bloat_bytes summary: Check amount of bloat in tables if greater than 250MB description: Checks check verifies the size of the table bloat in bytes accross all databases and alert accordingly - advisor: performance_vacuum + category: Performance + subcategory: Vacuum interval: standard - family: POSTGRESQL + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT parameters: @@ -15,18 +16,31 @@ checks: script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(tuples, context): results = [] for dbName, rows in tuples[0].items(): + current_database = "" + tables = [] for row in rows: - current_database, schemaname, tblname = row["current_database"], row["schemaname"], row["tblname"] real_size, real_size_pretty = int(row["real_size"]), row["real_size_pretty"] bloat_size_byte, bloat_size_byte_pretty = int(row["bloat_size_byte"]), row["bloat_size_byte_pretty"] if bloat_size_byte >= int(real_size*0.2): - results.append({ - "summary": "Table Bloat size in bytes is high", - "description": "Table {}.{} in database {} has a bloat of {} bytes ({}) from total size of {}".format(schemaname, tblname, current_database, bloat_size_byte, bloat_size_byte_pretty, real_size_pretty), - "read_more_url": read_url.format("postgresql-table-bloat-in-bytes"), - "severity": "warning" - }) + current_database = row["current_database"] + tables.append("{}.{} ({} of {})".format(row["schemaname"], row["tblname"], bloat_size_byte_pretty, real_size_pretty)) + + if not tables: + continue + + results.append({ + "summary": "{} table(s) with high bloat in database {}".format(len(tables), current_database), + "description": "The database {} has tables with a bloat of 20% or more of their total size: {}.".format(current_database, format_list(tables)), + "read_more_url": read_url.format("postgresql-table-bloat-in-bytes"), + "severity": "warning", + "labels": {"database": current_database, "count": str(len(tables))}, + }) return results diff --git a/managed/data/checks/postgresql_table_bloat_in_percentage.yml b/managed/data/checks/postgresql_table_bloat_in_percentage.yml index 71661f2393a..24e1795ba25 100644 --- a/managed/data/checks/postgresql_table_bloat_in_percentage.yml +++ b/managed/data/checks/postgresql_table_bloat_in_percentage.yml @@ -4,9 +4,10 @@ checks: name: postgresql_table_bloat_in_percentage summary: PostgreSQL Table Bloat in percentage of the table size description: This check verifies the size of the table bloat in percentage of the total table size and alert accordingly - family: POSTGRESQL - #author: David Gonzalez - advisor: performance_vacuum + technology: POSTGRESQL + # author: David Gonzales + category: Performance + subcategory: Vacuum interval: standard queries: - type: POSTGRESQL_SELECT @@ -92,6 +93,11 @@ checks: "critical": 50 } + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(docs, context): """ This check verifies the table bloat in percentage of the total table size, in the database where PMM connects. @@ -102,26 +108,28 @@ checks: """ results = [] - # extract information from variables + current_database = "" + # collect offending tables per severity level + critical_tables = [] + warning_tables = [] for row in docs[0]: - current_database, schemaname, tblname = row["current_database"], row["schemaname"], row["tblname"] - real_size, real_size_pretty = int(row["real_size"]), row["real_size_pretty"] - bloat_size_byte, bloat_size_byte_pretty = int(row["bloat_size_byte"]), row["bloat_size_byte_pretty"] - bloat_percentage = int(row["bloat_percentage"]) + current_database = row["current_database"] + table = "{}.{} ({}% of {})".format(row["schemaname"], row["tblname"], int(row["bloat_percentage"]), row["real_size_pretty"]) + + if int(row["bloat_percentage"]) >= check_threshold["critical"]: + critical_tables.append(table) + elif int(row["bloat_percentage"]) >= check_threshold["warning"]: + warning_tables.append(table) - if bloat_percentage >= check_threshold["critical"]: - results.append({ - "summary": "Table Bloat in percentage of table size is high", - "description": "Table {}.{} in database {} has a bloat of {}% from total table size of {}".format(schemaname, tblname, current_database, bloat_percentage, real_size_pretty), - "read_more_url": read_url.format("postgresql-table-bloat-in-percentage-of-table-size"), - "severity": "error" - }) - elif bloat_percentage >= check_threshold["warning"]: - results.append({ - "summary": "Table Bloat in percentage of table size is high", - "description": "Table {}.{} in database {} has a bloat of {}% from total table size of {}".format(schemaname, tblname, current_database, bloat_percentage, real_size_pretty), - "read_more_url": read_url.format("postgresql-table-bloat-in-percentage-of-table-size"), - "severity": "warning" - }) + for severity, threshold, tables in [("error", check_threshold["critical"], critical_tables), ("warning", check_threshold["warning"], warning_tables)]: + if not tables: + continue + results.append({ + "summary": "{} table(s) with bloat of {}% or more in database {}".format(len(tables), threshold, current_database), + "description": "The database {} has tables with a bloat of at least {}% of their total size: {}.".format(current_database, threshold, format_list(tables)), + "read_more_url": read_url.format("postgresql-table-bloat-in-percentage-of-table-size"), + "severity": severity, + "labels": {"database": current_database, "count": str(len(tables))}, + }) return results diff --git a/managed/data/checks/postgresql_tmpfiles_check.yml b/managed/data/checks/postgresql_tmpfiles_check.yml index 5f7ef851e93..3ea18f5398b 100644 --- a/managed/data/checks/postgresql_tmpfiles_check.yml +++ b/managed/data/checks/postgresql_tmpfiles_check.yml @@ -4,25 +4,35 @@ checks: name: postgresql_tmpfiles_check summary: PostgreSQL temporary file statistics description: This check reports the number of temporary files and number of bytes written to disk since last stats reset. - #author: Jorge Torralba - advisor: performance_generic + # author: Jorge Torralba + category: Performance + subcategory: Generic interval: standard - family: POSTGRESQL - category: configuration + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: datname, temp_files, PG_SIZE_PRETTY(temp_bytes) AS temp_bytes, TO_CHAR( AGE(stats_reset), 'MM "Months" DD "Days"') AS age FROM pg_stat_database script: | + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(rows, context): results = [] read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + databases = [] for row in rows[0]: db, tempfiles, tempbytes, age = row["datname"], row["temp_files"], row["temp_bytes"], row["age"] if tempfiles > 100: - results.append({ - "summary": "temporary file statistics", - "read_more_url":read_url.format("postgresql-tmpfiles-check"), - "description": "Database {} has generated {} temporary files. This has resulted in {} written to disk over the past {}.".format(db, tempfiles, tempbytes, age), - "severity": "warning", - }) + databases.append("{} ({} files, {} over the past {})".format(db, tempfiles, tempbytes, age)) + + if databases: + results.append({ + "summary": "{} database(s) with heavy temporary file usage".format(len(databases)), + "read_more_url":read_url.format("postgresql-tmpfiles-check"), + "description": "The following databases have generated more than 100 temporary files written to disk: {}.".format(format_list(databases)), + "severity": "warning", + "labels": {"count": str(len(databases))}, + }) return results diff --git a/managed/data/checks/postgresql_txid_wraparound_approaching.yml b/managed/data/checks/postgresql_txid_wraparound_approaching.yml index f3877020d88..8f9522d8caf 100644 --- a/managed/data/checks/postgresql_txid_wraparound_approaching.yml +++ b/managed/data/checks/postgresql_txid_wraparound_approaching.yml @@ -4,9 +4,10 @@ checks: name: postgresql_txid_wraparound_approaching summary: PostgreSQL Transaction ID Wraparound approaching description: This check verifies databases age and alert if the transaction ID wraparound issue is near - family: POSTGRESQL - #author: David Gonzalez - advisor: configuration_vacuum + technology: POSTGRESQL + # author: David Gonzales + category: Configuration + subcategory: Vacuum interval: standard queries: - type: POSTGRESQL_SELECT @@ -18,6 +19,11 @@ checks: script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + # pg_advisor def check_context(docs, context): """ @@ -29,13 +35,17 @@ checks: results = [] # extract information from variables + databases = [] for row in docs[0]: - datname, age, wraparound_risk_perc = row["datname"], row["age"], row["wraparound_risk_perc"] + databases.append("{} (txid age {}, {}% towards wraparound)".format(row["datname"], row["age"], row["wraparound_risk_perc"])) + + if databases: results.append({ - "summary": "Database Transaction ID Wraparound is approaching", - "description": "Database {} txid age is {} which is {}% towards transaction ID wraparound. VACCUM FREEZE actions are recommended.".format(datname, age, wraparound_risk_perc), + "summary": "{} database(s) approaching transaction ID wraparound".format(len(databases)), + "description": "The following databases are approaching transaction ID wraparound; VACUUM FREEZE actions are recommended: {}.".format(format_list(databases)), "read_more_url": read_url.format("postgresql-transaction-id-wraparound-is-approaching"), - "severity": "warning" + "severity": "warning", + "labels": {"count": str(len(databases))}, }) return results diff --git a/managed/data/checks/postgresql_unsupported_check.yml b/managed/data/checks/postgresql_unsupported_check.yml index 5917cf5e979..9907bc4b8c0 100644 --- a/managed/data/checks/postgresql_unsupported_check.yml +++ b/managed/data/checks/postgresql_unsupported_check.yml @@ -4,10 +4,11 @@ checks: name: postgresql_unsupported_check summary: Check for unsupported PostgreSQL description: Checks to see if the currently installed version is supported by percona - #author: Jorge Torralba - advisor: configuration_version + # author: Jorge Torralba + category: Configuration + subcategory: Version interval: standard - family: POSTGRESQL + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: "setting, (setting::int / 10000) major FROM pg_settings WHERE name = 'server_version_num' " diff --git a/managed/data/checks/postgresql_unused_index_check.yml b/managed/data/checks/postgresql_unused_index_check.yml index 95c9b7f9167..abe65367a59 100644 --- a/managed/data/checks/postgresql_unused_index_check.yml +++ b/managed/data/checks/postgresql_unused_index_check.yml @@ -3,10 +3,11 @@ checks: - version: 2 name: postgresql_unused_index_check summary: Check for relations that have unused indexes - description: This check will list relations with indexes that have not been used since statistics where last reset + description: This check lists relations with indexes that have not been used since statistics were last reset interval: standard - family: POSTGRESQL - advisor: query_index + technology: POSTGRESQL + category: Query + subcategory: Index queries: - type: POSTGRESQL_SELECT # Using this syntax (CURRENT_TIMESTAMP(0)::TIMESTAMP WITHOUT TIME ZONE)::text since PMM does not respect date formatting. Thus converting to a string @@ -15,19 +16,34 @@ checks: all_dbs: true script: | read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" + def format_list(items, cap=10): + if len(items) > cap: + return "; ".join(items[:cap]) + "; and {} more".format(len(items) - cap) + return "; ".join(items) + def check_context(tuple, context): results = [] for checkedDB, rows in tuple[0].items(): + datname = "" + since = "" + relations = [] for row in rows: - stats_reset = row["stats_reset"] datname = row["datname"] - relname = row["relname"] - rundate = row["rundate"] - results.append({ - "summary": "Relations with unused indexes", - "description": "The relation {}.{} has unused indexes as of the last statistics reset on {}. This check was last executed on {}".format(datname, relname, stats_reset, rundate), - "read_more_url":read_url.format("postgresql-unused-index-check"), - "severity": "warning", - }) + if row["stats_reset"]: + since = "since the statistics reset on {}".format(row["stats_reset"]) + else: + since = "since statistics collection began" + relations.append(row["relname"]) + + if not relations: + continue + + results.append({ + "summary": "{} relation(s) with unused indexes in database {}".format(len(relations), datname), + "description": "The database {} has relations with indexes that have not been used {}: {}.".format(datname, since, format_list(relations)), + "read_more_url":read_url.format("postgresql-unused-index-check"), + "severity": "warning", + "labels": {"database": datname, "count": str(len(relations))}, + }) return results diff --git a/managed/data/checks/postgresql_vacuum_sanity_check.yml b/managed/data/checks/postgresql_vacuum_sanity_check.yml index 08fc05401f6..18fa71b3785 100644 --- a/managed/data/checks/postgresql_vacuum_sanity_check.yml +++ b/managed/data/checks/postgresql_vacuum_sanity_check.yml @@ -5,8 +5,9 @@ checks: summary: PostgreSQL vacuum setting quick check description: This performs a quick check of some vacuum parameters interval: standard - family: POSTGRESQL - advisor: configuration_vacuum + technology: POSTGRESQL + category: Configuration + subcategory: Vacuum queries: - type: POSTGRESQL_SELECT query: "current_setting('autovacuum') AS autovacuum, current_setting('autovacuum_max_workers') AS autovacuum_max_workers, current_setting('autovacuum_vacuum_scale_factor') AS autovacuum_vacuum_scale_factor, current_setting('vacuum_cost_page_hit') AS vacuum_cost_page_hit, current_setting('vacuum_cost_delay') AS vacuum_cost_delay, current_setting('vacuum_cost_page_dirty') AS vacuum_cost_page_dirty , current_setting('vacuum_cost_page_miss') AS vacuum_cost_page_miss " @@ -14,65 +15,61 @@ checks: read_url = "https://docs.percona.com/percona-monitoring-and-management/3/advisors/checks/{}.html" def check_context(rows, context): - results = [] - description = "" - alert = 0 + results = [] + description = "" + alert = 0 - if len(rows) != 1 : - results.append({ - "summary": "Autovacuum concerns", - "description": "Error encountered reading configuration parameters", - "read_more_url":read_url.format("postgresql-vacuum-sanity-check"), - "severity": "warning", - "labels": {}, - }) - return results + if len(rows) != 1 : + results.append({ + "summary": "Autovacuum concerns", + "description": "Error encountered reading configuration parameters", + "read_more_url": read_url.format("postgresql-vacuum-sanity-check"), + "severity": "warning", + }) + return results - for row in rows[0]: + for row in rows[0]: - autovacuum = row["autovacuum"] - autovacuum_max_workers = row["autovacuum_max_workers"] - autovacuum_vacuum_scale_factor = row["autovacuum_vacuum_scale_factor"] - vacuum_cost_page_hit = row["vacuum_cost_page_hit"] - vacuum_cost_delay = row["vacuum_cost_delay"] - vacuum_cost_page_dirty = row["vacuum_cost_page_dirty"] - vacuum_cost_page_miss = row["vacuum_cost_page_miss"] + autovacuum = row["autovacuum"] + autovacuum_max_workers = row["autovacuum_max_workers"] + autovacuum_vacuum_scale_factor = row["autovacuum_vacuum_scale_factor"] + vacuum_cost_page_hit = row["vacuum_cost_page_hit"] + vacuum_cost_delay = row["vacuum_cost_delay"] + vacuum_cost_page_dirty = row["vacuum_cost_page_dirty"] + vacuum_cost_page_miss = row["vacuum_cost_page_miss"] - if autovacuum == "off" : - alert = 1 - description = description + " WARNING! autovacuum is set to off. This can lead to performance issues and excessive table bloat." + if autovacuum == "off" : + alert = 1 + description += " WARNING! autovacuum is set to off. This can lead to performance issues and excessive table bloat." - if int(autovacuum_max_workers) == 3 : - alert = 1 - description = description + " autovacuum_max_workers is currently set to the default value of 3. You should consider raising this value \ - if you find autovacuum is not completing it's task." + if int(autovacuum_max_workers) == 3 : + alert = 1 + description += " autovacuum_max_workers is currently set to the default value of 3. You should consider raising this value if you find autovacuum is not completing its task." - if float(autovacuum_vacuum_scale_factor) == .2 : - alert = 1 - description = description + " autovacuum_vacuum_scale_factor is set to the default value of 20%. This means all tables unless customized \ - with storage parameters will not receive autovacuum until at least 20% of data has been updated or deleted." - if int(vacuum_cost_page_hit) == 1 : - alert = 1 - description = description + " vacuum_cost_page_hit is currently set to the default value of 1. " + if float(autovacuum_vacuum_scale_factor) == .2 : + alert = 1 + description += " autovacuum_vacuum_scale_factor is set to the default value of 20%. This means unless customized with storage parameters, all tables will not receive autovacuum until at least 20% of data has been updated or deleted." - if int(vacuum_cost_page_miss) == 10 : - alert = 1 - description = description + " vacuum_cost_page_miss is currently set to the default value of 10." + if int(vacuum_cost_page_hit) == 1 : + alert = 1 + description += " vacuum_cost_page_hit is currently set to the default value of 1." - if int(vacuum_cost_page_dirty) == 20 : - alert = 1 - description = description + " vacuum_cost_page_dirty is currently set to the default value of 20." + if int(vacuum_cost_page_miss) == 10 : + alert = 1 + description += " vacuum_cost_page_miss is currently set to the default value of 10." - if alert == 1 : - description = description + " Please take a moment to read the advisors documentation in the Read More link regarding these findings. " + if int(vacuum_cost_page_dirty) == 20 : + alert = 1 + description += " vacuum_cost_page_dirty is currently set to the default value of 20." - results.append({ - "summary": "Autovacuum concerns", - "description": description, - "read_more_url":read_url.format("postgresql-vacuum-sanity-check"), - "severity": "warning", - "labels": {}, - }) + if alert == 1 : + description += " Please take a moment to read the advisors documentation in the Read More link regarding these findings." + results.append({ + "summary": "Autovacuum concerns", + "description": description.strip(), + "read_more_url":read_url.format("postgresql-vacuum-sanity-check"), + "severity": "warning", + }) - return results + return results diff --git a/managed/data/checks/postgresql_version_check.yml b/managed/data/checks/postgresql_version_check.yml index 8f8962dffab..fad66ef8eed 100644 --- a/managed/data/checks/postgresql_version_check.yml +++ b/managed/data/checks/postgresql_version_check.yml @@ -4,10 +4,11 @@ checks: name: postgresql_version_check summary: Check for newer version of PostgreSQL description: Checks to see if the currently installed version is outdated for it's release level - #author: Jorge Torralba - advisor: configuration_version + # author: Jorge Torralba + category: Configuration + subcategory: Version interval: standard - family: POSTGRESQL + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: "setting, (setting::int / 10000) major, extract(epoch FROM NOW())::int AS today FROM pg_settings WHERE name = 'server_version_num' " @@ -62,7 +63,7 @@ checks: if daysleft <= 0: daysleft = daysleft * -1 - return "WARNING: The version currently installed expired {} days ago".format(daysleft) + return "WARNING: The version currently installed expired {} days ago.".format(daysleft) if daysleft > 0: return "NOTE: Support for current version will end in {} days.".format(daysleft) @@ -115,7 +116,7 @@ checks: if int(ver) < latest_for_current: latest_current = format_version(major, latest_for_current) description = "There is a newer minor version ({}) available. ".format(latest_current) - description = description + daysleft + description += daysleft minoroutdated = True if major < 10: @@ -126,14 +127,14 @@ checks: daysleft = days_left(realmajor,today) if int(ver) < latest_for_current: latest_current = format_version(major, latest_for_current) - description = "There is a newer minor version ({}) available.".format(latest_current) - description = description + daysleft + description = "There is a newer minor version ({}) available. ".format(latest_current) + description += daysleft minoroutdated = True if int(ver) < int(latestpg) and minoroutdated == False: majoroutdated = True - description = "Version ({}) is the latest release for this major/minor version. However, there is a newer major verion ({}) available. ".format(current_version, latest_and_greatest) - description = description + daysleft + description = "Version ({}) is the latest release for this major/minor version. However, there is a newer major version ({}) available. ".format(current_version, latest_and_greatest) + description += daysleft results.append({ "summary": "Currently installed version is ({})".format(current_version), diff --git a/managed/data/checks/postgresql_wal_retention_check.yml b/managed/data/checks/postgresql_wal_retention_check.yml index 4147811bd64..646a3b92e75 100644 --- a/managed/data/checks/postgresql_wal_retention_check.yml +++ b/managed/data/checks/postgresql_wal_retention_check.yml @@ -4,10 +4,11 @@ checks: name: postgresql_wal_retention_check summary: Check for WAL file accumulation description: Checks to see if there are too many WAL files retained in the WAL directory - #author: Jorge Torralba - advisor: configuration_generic + # author: Jorge Torralba + category: Configuration + subcategory: Generic interval: standard - family: POSTGRESQL + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: "* FROM ( WITH cte1 AS ( SELECT current_setting('server_version_num') AS version, name, setting, unit, CASE WHEN current_setting('server_version_num')::int < 100000 THEN (SELECT COUNT(*) FROM pg_ls_dir('pg_xlog') WHERE pg_ls_dir ~ '^[0-9A-F]{24}') WHEN current_setting('server_version_num')::int >= 100000 THEN (SELECT COUNT(*) FROM pg_ls_dir('pg_wal') WHERE pg_ls_dir ~ '^[0-9A-F]{24}') END AS wal_cnt, CASE WHEN setting::int >= 130000 THEN (SELECT setting::bigint FROM pg_settings WHERE name = 'wal_keep_size') WHEN setting::int < 130000 THEN (SELECT setting::int FROM pg_settings WHERE name = 'wal_keep_segments') END AS wal_keep, CASE WHEN (SELECT substring(unit, '[0-9]*') FROM pg_settings WHERE name = 'wal_segment_size') = '' THEN CASE WHEN (SELECT lower(substring(unit, '[A-Za-z]+')) FROM pg_settings WHERE name = 'wal_segment_size') = 'b' THEN 1 WHEN (SELECT lower(substring(unit, '[A-Za-z]+')) FROM pg_settings WHERE name = 'wal_segment_size') = 'kb' THEN 1024 WHEN (SELECT lower(substring(unit, '[A-Za-z]+')) FROM pg_settings WHERE name = 'wal_segment_size') = 'mb' THEN 1024*1024 WHEN (SELECT lower(substring(unit, '[A-Za-z]+')) FROM pg_settings WHERE name = 'wal_segment_size') = 'gb' THEN 1024*1024*1024 WHEN (SELECT lower(substring(unit, '[A-Za-z]+')) FROM pg_settings WHERE name = 'wal_segment_size') = 'tb' THEN 1024*1024*1024*1024::bigint END ELSE (SELECT pg_size_bytes(unit) FROM pg_settings WHERE name = 'wal_segment_size') END as multiplyer FROM pg_settings WHERE name = 'wal_segment_size') SELECT version, name, setting, unit, multiplyer, (multiplyer::bigint * setting::bigint) AS bytes, pg_size_pretty((multiplyer::bigint * setting::bigint)), wal_cnt, wal_keep, (wal_cnt * (multiplyer::bigint * setting::bigint) ) AS space_used, pg_size_pretty((wal_cnt * (multiplyer::bigint * setting::bigint)) ) AS pretty_space_used, CASE WHEN wal_keep::bigint = 0 THEN 0::text WHEN wal_keep::bigint > 0 AND version::int >= 130000 THEN pg_size_pretty(wal_keep*1024*1024::bigint) WHEN wal_keep::int > 0 AND version::int < 130000 THEN pg_size_pretty(wal_keep * (multiplyer::bigint * setting::bigint)) END AS potential_space_used FROM cte1) AS foo" @@ -41,7 +42,7 @@ checks: "summary": "Could not determine version information needed for check", "description": "Unknown version", "read_more_url": "", - "severity": "Error", + "severity": "error", "labels": {}, }) return results diff --git a/managed/models/advisor_check_helpers.go b/managed/models/advisor_check_helpers.go new file mode 100644 index 00000000000..e52b095fa98 --- /dev/null +++ b/managed/models/advisor_check_helpers.go @@ -0,0 +1,267 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "context" + "fmt" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gopkg.in/reform.v1" +) + +// FindAdvisorChecks returns all advisor checks ordered by name. +func FindAdvisorChecks(q *reform.Querier) ([]*AdvisorCheck, error) { + rows, err := q.SelectAllFrom(AdvisorCheckTable, "ORDER BY name") + if err != nil { + return nil, fmt.Errorf("failed to select advisor checks: %w", err) + } + + checks := make([]*AdvisorCheck, 0, len(rows)) + for _, r := range rows { + checks = append(checks, r.(*AdvisorCheck)) //nolint:forcetypeassert + } + return checks, nil +} + +// FindAdvisorCheckByName finds an advisor check by name. +// It returns reform.ErrNoRows if the check does not exist. +func FindAdvisorCheckByName(q *reform.Querier, name string) (*AdvisorCheck, error) { + if name == "" { + return nil, status.Error(codes.InvalidArgument, "Empty advisor check name.") + } + + c := &AdvisorCheck{Name: name} + err := q.Reload(c) + if err != nil { + return nil, err + } + + return c, nil +} + +// CreateAdvisorCheck persists a new user-authored advisor check. +func CreateAdvisorCheck(q *reform.Querier, c *AdvisorCheck) (*AdvisorCheck, error) { + err := q.Insert(c) + if err != nil { + return nil, fmt.Errorf("failed to create advisor check: %w", err) + } + + return c, nil +} + +// UpdateAdvisorCheck updates the content of an existing user-authored advisor check, +// preserving its creation time, source and settings (interval override, disabled state, +// per-service disables). It returns reform.ErrNoRows if the check does not exist. +func UpdateAdvisorCheck(q *reform.Querier, c *AdvisorCheck) (*AdvisorCheck, error) { + existing := &AdvisorCheck{Name: c.Name} + err := q.Reload(existing) + if err != nil { + return nil, err + } + + c.CreatedAt = existing.CreatedAt + c.Source = existing.Source + c.IntervalOverride = existing.IntervalOverride + c.Disabled = existing.Disabled + c.DisabledServiceIDs = existing.DisabledServiceIDs + err = q.Update(c) + if err != nil { + return nil, fmt.Errorf("failed to update advisor check: %w", err) + } + + return c, nil +} + +// UpsertAdvisorCheckContent inserts a built-in advisor check or refreshes its +// content columns if a row with the same name already exists. Settings columns +// (interval_override, disabled, disabled_service_ids) are never touched on update, +// so user-set overrides survive content refreshes across restarts. +func UpsertAdvisorCheckContent(ctx context.Context, q *reform.Querier, c *AdvisorCheck) error { + now := Now() + _, err := q.ExecContext(ctx, ` + INSERT INTO advisor_checks ( + name, source, version, summary, description, category, subcategory, + technology, interval, interval_override, disabled, disabled_service_ids, + queries, script, created_at, updated_at + ) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, NULL, false, NULL, $10, $11, $12, $12 + ) + ON CONFLICT (name) DO UPDATE SET + source = EXCLUDED.source, + version = EXCLUDED.version, + summary = EXCLUDED.summary, + description = EXCLUDED.description, + category = EXCLUDED.category, + subcategory = EXCLUDED.subcategory, + technology = EXCLUDED.technology, + interval = EXCLUDED.interval, + queries = EXCLUDED.queries, + script = EXCLUDED.script, + updated_at = EXCLUDED.updated_at`, + c.Name, BuiltinCheckSource, c.Version, c.Summary, c.Description, c.Category, c.Subcategory, + c.Technology, c.Interval, c.Queries, c.Script, now) + if err != nil { + return fmt.Errorf("failed to upsert advisor check %s: %w", c.Name, err) + } + + return nil +} + +// RemoveAdvisorChecksNotIn deletes built-in advisor checks whose names are not +// in the given list. User-authored checks are never touched. It is used to prune rows +// for checks removed from the checks package. +func RemoveAdvisorChecksNotIn(ctx context.Context, q *reform.Querier, names []string) error { + if len(names) == 0 { + // An empty list means the checks failed to load; do not wipe the table. + return nil + } + + args := make([]any, 0, len(names)+1) + args = append(args, BuiltinCheckSource) + placeholders := make([]string, 0, len(names)) + for i, n := range names { + args = append(args, n) + placeholders = append(placeholders, fmt.Sprintf("$%d", i+2)) //nolint:mnd + } + + _, err := q.ExecContext(ctx, + fmt.Sprintf("DELETE FROM advisor_checks WHERE source = $1 AND name NOT IN (%s)", strings.Join(placeholders, ", ")), + args...) + if err != nil { + return fmt.Errorf("failed to prune advisor checks: %w", err) + } + + return nil +} + +// FindDisabledAdvisorCheckNames returns the names of globally-disabled advisor checks. +func FindDisabledAdvisorCheckNames(ctx context.Context, q *reform.Querier) ([]string, error) { + rows, err := q.WithContext(ctx).SelectAllFrom(AdvisorCheckTable, "WHERE disabled ORDER BY name") + if err != nil { + return nil, fmt.Errorf("failed to select disabled advisor checks: %w", err) + } + + names := make([]string, 0, len(rows)) + for _, r := range rows { + names = append(names, r.(*AdvisorCheck).Name) //nolint:forcetypeassert + } + return names, nil +} + +// SetAdvisorChecksDisabled sets the global disabled flag for the named advisor checks. +// Per-service disable settings are intentionally left untouched: they still apply +// once a check is re-enabled globally. +func SetAdvisorChecksDisabled(ctx context.Context, q *reform.Querier, names []string, disabled bool) error { + if len(names) == 0 { + return nil + } + + args := make([]any, 0, len(names)+1) + args = append(args, disabled) + placeholders := make([]string, 0, len(names)) + for i, n := range names { + args = append(args, n) + placeholders = append(placeholders, fmt.Sprintf("$%d", i+2)) //nolint:mnd + } + + _, err := q.ExecContext(ctx, + fmt.Sprintf("UPDATE advisor_checks SET disabled = $1, updated_at = now() WHERE name IN (%s)", strings.Join(placeholders, ", ")), + args...) + if err != nil { + return fmt.Errorf("failed to change disabled state of advisor checks: %w", err) + } + + return nil +} + +// ChangeAdvisorCheckInterval sets the user interval override for the named advisor check. +// It returns reform.ErrNoRows if the check does not exist. +func ChangeAdvisorCheckInterval(ctx context.Context, q *reform.Querier, name string, interval Interval) (*AdvisorCheck, error) { + c, err := FindAdvisorCheckByName(q.WithContext(ctx), name) + if err != nil { + return nil, err + } + + override := string(interval) + c.IntervalOverride = &override + err = q.WithContext(ctx).Update(c) + if err != nil { + return nil, fmt.Errorf("failed to change interval of advisor check %s: %w", name, err) + } + + return c, nil +} + +// ChangeAdvisorCheckDisabledServices replaces the set of service IDs for which the +// named advisor check is disabled. It returns reform.ErrNoRows if the check does not exist. +func ChangeAdvisorCheckDisabledServices(ctx context.Context, q *reform.Querier, name string, serviceIDs []string) (*AdvisorCheck, error) { + c, err := FindAdvisorCheckByName(q.WithContext(ctx), name) + if err != nil { + return nil, err + } + + err = c.SetDisabledServiceIDs(serviceIDs) + if err != nil { + return nil, err + } + + err = q.WithContext(ctx).Update(c) + if err != nil { + return nil, fmt.Errorf("failed to change disabled services of advisor check %s: %w", name, err) + } + + return c, nil +} + +// FindAdvisorCheckDisabledServices returns a map of check name to the service IDs +// for which that check is disabled. Checks with no per-service disables are omitted. +func FindAdvisorCheckDisabledServices(ctx context.Context, q *reform.Querier) (map[string][]string, error) { + rows, err := q.WithContext(ctx).SelectAllFrom(AdvisorCheckTable, "WHERE disabled_service_ids IS NOT NULL ORDER BY name") + if err != nil { + return nil, fmt.Errorf("failed to select advisor checks with disabled services: %w", err) + } + + res := make(map[string][]string, len(rows)) + for _, r := range rows { + c := r.(*AdvisorCheck) //nolint:forcetypeassert + ids, err := c.GetDisabledServiceIDs() + if err != nil { + return nil, err + } + if len(ids) != 0 { + res[c.Name] = ids + } + } + return res, nil +} + +// RemoveAdvisorCheck deletes a user-authored advisor check by name. +// It returns reform.ErrNoRows if the check does not exist. +func RemoveAdvisorCheck(q *reform.Querier, name string) error { + if name == "" { + return status.Error(codes.InvalidArgument, "Empty advisor check name.") + } + + err := q.Delete(&AdvisorCheck{Name: name}) + if err != nil { + return err + } + + return nil +} diff --git a/managed/models/advisor_check_helpers_test.go b/managed/models/advisor_check_helpers_test.go new file mode 100644 index 00000000000..c551007111e --- /dev/null +++ b/managed/models/advisor_check_helpers_test.go @@ -0,0 +1,233 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/dialects/postgresql" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/utils/testdb" +) + +// builtinCheck returns a minimal valid built-in advisor check model. +func builtinCheck(name string) *models.AdvisorCheck { + return &models.AdvisorCheck{ + Name: name, + Source: models.BuiltinCheckSource, + Version: 2, + Summary: "Test summary", + Description: "Test description", + Category: "Test", + Subcategory: "Helpers", + Technology: "POSTGRESQL", + Interval: "standard", + Queries: []byte(`[{"type":"POSTGRESQL_SELECT","query":"1"}]`), + Script: "def check(): return []", + } +} + +func TestAdvisorCheckHelpers(t *testing.T) { //nolint:tparallel + t.Parallel() + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)) + + tx := func(t *testing.T) *reform.Querier { + t.Helper() + tx, err := db.Begin() + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, tx.Rollback()) + }) + return tx.Querier + } + + t.Run("upsert inserts and refreshes content", func(t *testing.T) { + q := tx(t) + ctx := t.Context() + + err := models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("check1")) + require.NoError(t, err) + + c, err := models.FindAdvisorCheckByName(q, "check1") + require.NoError(t, err) + assert.Equal(t, models.BuiltinCheckSource, c.Source) + assert.Equal(t, "Test summary", c.Summary) + assert.False(t, c.Disabled) + assert.Nil(t, c.IntervalOverride) + + updated := builtinCheck("check1") + updated.Summary = "New summary" + updated.Interval = "rare" + err = models.UpsertAdvisorCheckContent(ctx, q, updated) + require.NoError(t, err) + + c, err = models.FindAdvisorCheckByName(q, "check1") + require.NoError(t, err) + assert.Equal(t, "New summary", c.Summary) + assert.Equal(t, "rare", c.Interval) + }) + + t.Run("upsert preserves settings columns", func(t *testing.T) { + q := tx(t) + ctx := t.Context() + + err := models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("check1")) + require.NoError(t, err) + + // record user overrides + _, err = models.ChangeAdvisorCheckInterval(ctx, q, "check1", models.Rare) + require.NoError(t, err) + err = models.SetAdvisorChecksDisabled(ctx, q, []string{"check1"}, true) + require.NoError(t, err) + _, err = models.ChangeAdvisorCheckDisabledServices(ctx, q, "check1", []string{"svc-1"}) + require.NoError(t, err) + + // a content refresh must not touch them + err = models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("check1")) + require.NoError(t, err) + + c, err := models.FindAdvisorCheckByName(q, "check1") + require.NoError(t, err) + assert.Equal(t, new("rare"), c.IntervalOverride) + assert.True(t, c.Disabled) + ids, err := c.GetDisabledServiceIDs() + require.NoError(t, err) + assert.Equal(t, []string{"svc-1"}, ids) + }) + + t.Run("prune removes only vanished built-in checks", func(t *testing.T) { + q := tx(t) + ctx := t.Context() + + err := models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("builtin1")) + require.NoError(t, err) + err = models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("builtin2")) + require.NoError(t, err) + + user := builtinCheck("user1") + user.Source = models.UserCheckSource + _, err = models.CreateAdvisorCheck(q, user) + require.NoError(t, err) + + err = models.RemoveAdvisorChecksNotIn(ctx, q, []string{"builtin1"}) + require.NoError(t, err) + + checks, err := models.FindAdvisorChecks(q) + require.NoError(t, err) + names := make([]string, 0, len(checks)) + for _, c := range checks { + names = append(names, c.Name) + } + assert.ElementsMatch(t, []string{"builtin1", "user1"}, names) + }) + + t.Run("prune with empty list is a no-op", func(t *testing.T) { + q := tx(t) + ctx := t.Context() + + err := models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("builtin1")) + require.NoError(t, err) + + err = models.RemoveAdvisorChecksNotIn(ctx, q, nil) + require.NoError(t, err) + + _, err = models.FindAdvisorCheckByName(q, "builtin1") + require.NoError(t, err) + }) + + t.Run("disabled names round-trip", func(t *testing.T) { + q := tx(t) + ctx := t.Context() + + err := models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("check1")) + require.NoError(t, err) + err = models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("check2")) + require.NoError(t, err) + + err = models.SetAdvisorChecksDisabled(ctx, q, []string{"check1"}, true) + require.NoError(t, err) + + names, err := models.FindDisabledAdvisorCheckNames(ctx, q) + require.NoError(t, err) + assert.Equal(t, []string{"check1"}, names) + + err = models.SetAdvisorChecksDisabled(ctx, q, []string{"check1"}, false) + require.NoError(t, err) + + names, err = models.FindDisabledAdvisorCheckNames(ctx, q) + require.NoError(t, err) + assert.Empty(t, names) + }) + + t.Run("disabled services round-trip", func(t *testing.T) { + q := tx(t) + ctx := t.Context() + + err := models.UpsertAdvisorCheckContent(ctx, q, builtinCheck("check1")) + require.NoError(t, err) + + _, err = models.ChangeAdvisorCheckDisabledServices(ctx, q, "check1", []string{"svc-1", "svc-2"}) + require.NoError(t, err) + + m, err := models.FindAdvisorCheckDisabledServices(ctx, q) + require.NoError(t, err) + assert.Equal(t, map[string][]string{"check1": {"svc-1", "svc-2"}}, m) + + // clearing the list removes the check from the map + _, err = models.ChangeAdvisorCheckDisabledServices(ctx, q, "check1", nil) + require.NoError(t, err) + + m, err = models.FindAdvisorCheckDisabledServices(ctx, q) + require.NoError(t, err) + assert.Empty(t, m) + }) + + t.Run("update preserves settings columns", func(t *testing.T) { + q := tx(t) + ctx := t.Context() + + user := builtinCheck("user1") + user.Source = models.UserCheckSource + _, err := models.CreateAdvisorCheck(q, user) + require.NoError(t, err) + + _, err = models.ChangeAdvisorCheckInterval(ctx, q, "user1", models.Frequent) + require.NoError(t, err) + err = models.SetAdvisorChecksDisabled(ctx, q, []string{"user1"}, true) + require.NoError(t, err) + + edited := builtinCheck("user1") + edited.Source = models.UserCheckSource + edited.Summary = "Edited summary" + _, err = models.UpdateAdvisorCheck(q, edited) + require.NoError(t, err) + + c, err := models.FindAdvisorCheckByName(q, "user1") + require.NoError(t, err) + assert.Equal(t, "Edited summary", c.Summary) + assert.Equal(t, models.UserCheckSource, c.Source) + assert.Equal(t, new("frequent"), c.IntervalOverride) + assert.True(t, c.Disabled) + }) +} diff --git a/managed/models/advisor_check_model.go b/managed/models/advisor_check_model.go new file mode 100644 index 00000000000..c68ccd4c5f3 --- /dev/null +++ b/managed/models/advisor_check_model.go @@ -0,0 +1,136 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "encoding/json" + "fmt" + "time" + + "gopkg.in/reform.v1" +) + +//go:generate go tool reform + +// Interval represents check execution interval. +type Interval string + +// Available check execution intervals. +const ( + Standard Interval = "standard" + Frequent Interval = "frequent" + Rare Interval = "rare" +) + +// CheckSource represents the origin of an advisor check. +type CheckSource string + +// Available advisor check sources. +const ( + // BuiltinCheckSource is a built-in check shipped with PMM, reconciled from disk at startup. + BuiltinCheckSource = CheckSource("builtin") + // UserCheckSource is a user-authored check created via the API. + UserCheckSource = CheckSource("user") +) + +// AdvisorCheck represents an advisor check stored in the database. +// Percona-shipped checks are reconciled from disk into this table at startup; +// user-authored checks are created via the API. Interval overrides and +// enable/disable state (global and per-service) live in dedicated columns so +// that a content refresh never touches them. +// +//reform:advisor_checks +type AdvisorCheck struct { + Name string `reform:"name,pk"` + Source CheckSource `reform:"source"` + Version uint32 `reform:"version"` + Summary string `reform:"summary"` + Description string `reform:"description"` + Category string `reform:"category"` + Subcategory string `reform:"subcategory"` + Technology string `reform:"technology"` + // Interval is the original author-defined execution interval. + Interval string `reform:"interval"` + // IntervalOverride is the user-set execution interval; nil means no override. + IntervalOverride *string `reform:"interval_override"` + // Disabled reports whether the check is disabled globally. + Disabled bool `reform:"disabled"` + // DisabledServiceIDs holds a JSON-encoded array of service IDs for which + // the check is disabled; nil means none. + DisabledServiceIDs []byte `reform:"disabled_service_ids"` + // Queries holds the JSON-encoded []check.Query. + Queries []byte `reform:"queries"` + Script string `reform:"script"` + CreatedAt time.Time `reform:"created_at"` + UpdatedAt time.Time `reform:"updated_at"` +} + +// GetDisabledServiceIDs decodes the list of service IDs for which the check is disabled. +func (c *AdvisorCheck) GetDisabledServiceIDs() ([]string, error) { + if len(c.DisabledServiceIDs) == 0 { + return nil, nil + } + + var ids []string + err := json.Unmarshal(c.DisabledServiceIDs, &ids) + if err != nil { + return nil, fmt.Errorf("failed to decode disabled service IDs: %w", err) + } + return ids, nil +} + +// SetDisabledServiceIDs encodes the list of service IDs for which the check is disabled. +func (c *AdvisorCheck) SetDisabledServiceIDs(ids []string) error { + if len(ids) == 0 { + c.DisabledServiceIDs = nil + return nil + } + + b, err := json.Marshal(ids) + if err != nil { + return fmt.Errorf("failed to encode disabled service IDs: %w", err) + } + c.DisabledServiceIDs = b + return nil +} + +// BeforeInsert implements reform.BeforeInserter interface. +func (c *AdvisorCheck) BeforeInsert() error { + now := Now() + c.CreatedAt = now + c.UpdatedAt = now + return nil +} + +// BeforeUpdate implements reform.BeforeUpdater interface. +func (c *AdvisorCheck) BeforeUpdate() error { + c.UpdatedAt = Now() + return nil +} + +// AfterFind implements reform.AfterFinder interface. +func (c *AdvisorCheck) AfterFind() error { + c.CreatedAt = c.CreatedAt.UTC() + c.UpdatedAt = c.UpdatedAt.UTC() + return nil +} + +// check interfaces. +var ( + _ reform.BeforeInserter = (*AdvisorCheck)(nil) + _ reform.BeforeUpdater = (*AdvisorCheck)(nil) + _ reform.AfterFinder = (*AdvisorCheck)(nil) +) diff --git a/managed/models/advisor_check_model_reform.go b/managed/models/advisor_check_model_reform.go new file mode 100644 index 00000000000..a16b373e9d6 --- /dev/null +++ b/managed/models/advisor_check_model_reform.go @@ -0,0 +1,206 @@ +// Code generated by gopkg.in/reform.v1. DO NOT EDIT. + +package models + +import ( + "fmt" + "strings" + + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/parse" +) + +type advisorCheckTableType struct { + s parse.StructInfo + z []interface{} +} + +// Schema returns a schema name in SQL database (""). +func (v *advisorCheckTableType) Schema() string { + return v.s.SQLSchema +} + +// Name returns a view or table name in SQL database ("advisor_checks"). +func (v *advisorCheckTableType) Name() string { + return v.s.SQLName +} + +// Columns returns a new slice of column names for that view or table in SQL database. +func (v *advisorCheckTableType) Columns() []string { + return []string{ + "name", + "source", + "version", + "summary", + "description", + "category", + "subcategory", + "technology", + "interval", + "interval_override", + "disabled", + "disabled_service_ids", + "queries", + "script", + "created_at", + "updated_at", + } +} + +// NewStruct makes a new struct for that view or table. +func (v *advisorCheckTableType) NewStruct() reform.Struct { + return new(AdvisorCheck) +} + +// NewRecord makes a new record for that table. +func (v *advisorCheckTableType) NewRecord() reform.Record { + return new(AdvisorCheck) +} + +// PKColumnIndex returns an index of primary key column for that table in SQL database. +func (v *advisorCheckTableType) PKColumnIndex() uint { + return uint(v.s.PKFieldIndex) +} + +// AdvisorCheckTable represents advisor_checks view or table in SQL database. +var AdvisorCheckTable = &advisorCheckTableType{ + s: parse.StructInfo{ + Type: "AdvisorCheck", + SQLName: "advisor_checks", + Fields: []parse.FieldInfo{ + {Name: "Name", Type: "string", Column: "name"}, + {Name: "Source", Type: "CheckSource", Column: "source"}, + {Name: "Version", Type: "uint32", Column: "version"}, + {Name: "Summary", Type: "string", Column: "summary"}, + {Name: "Description", Type: "string", Column: "description"}, + {Name: "Category", Type: "string", Column: "category"}, + {Name: "Subcategory", Type: "string", Column: "subcategory"}, + {Name: "Technology", Type: "string", Column: "technology"}, + {Name: "Interval", Type: "string", Column: "interval"}, + {Name: "IntervalOverride", Type: "*string", Column: "interval_override"}, + {Name: "Disabled", Type: "bool", Column: "disabled"}, + {Name: "DisabledServiceIDs", Type: "[]uint8", Column: "disabled_service_ids"}, + {Name: "Queries", Type: "[]uint8", Column: "queries"}, + {Name: "Script", Type: "string", Column: "script"}, + {Name: "CreatedAt", Type: "time.Time", Column: "created_at"}, + {Name: "UpdatedAt", Type: "time.Time", Column: "updated_at"}, + }, + PKFieldIndex: 0, + }, + z: new(AdvisorCheck).Values(), +} + +// String returns a string representation of this struct or record. +func (s AdvisorCheck) String() string { + res := make([]string, 16) + res[0] = "Name: " + reform.Inspect(s.Name, true) + res[1] = "Source: " + reform.Inspect(s.Source, true) + res[2] = "Version: " + reform.Inspect(s.Version, true) + res[3] = "Summary: " + reform.Inspect(s.Summary, true) + res[4] = "Description: " + reform.Inspect(s.Description, true) + res[5] = "Category: " + reform.Inspect(s.Category, true) + res[6] = "Subcategory: " + reform.Inspect(s.Subcategory, true) + res[7] = "Technology: " + reform.Inspect(s.Technology, true) + res[8] = "Interval: " + reform.Inspect(s.Interval, true) + res[9] = "IntervalOverride: " + reform.Inspect(s.IntervalOverride, true) + res[10] = "Disabled: " + reform.Inspect(s.Disabled, true) + res[11] = "DisabledServiceIDs: " + reform.Inspect(s.DisabledServiceIDs, true) + res[12] = "Queries: " + reform.Inspect(s.Queries, true) + res[13] = "Script: " + reform.Inspect(s.Script, true) + res[14] = "CreatedAt: " + reform.Inspect(s.CreatedAt, true) + res[15] = "UpdatedAt: " + reform.Inspect(s.UpdatedAt, true) + return strings.Join(res, ", ") +} + +// Values returns a slice of struct or record field values. +// Returned interface{} values are never untyped nils. +func (s *AdvisorCheck) Values() []interface{} { + return []interface{}{ + s.Name, + s.Source, + s.Version, + s.Summary, + s.Description, + s.Category, + s.Subcategory, + s.Technology, + s.Interval, + s.IntervalOverride, + s.Disabled, + s.DisabledServiceIDs, + s.Queries, + s.Script, + s.CreatedAt, + s.UpdatedAt, + } +} + +// Pointers returns a slice of pointers to struct or record fields. +// Returned interface{} values are never untyped nils. +func (s *AdvisorCheck) Pointers() []interface{} { + return []interface{}{ + &s.Name, + &s.Source, + &s.Version, + &s.Summary, + &s.Description, + &s.Category, + &s.Subcategory, + &s.Technology, + &s.Interval, + &s.IntervalOverride, + &s.Disabled, + &s.DisabledServiceIDs, + &s.Queries, + &s.Script, + &s.CreatedAt, + &s.UpdatedAt, + } +} + +// View returns View object for that struct. +func (s *AdvisorCheck) View() reform.View { + return AdvisorCheckTable +} + +// Table returns Table object for that record. +func (s *AdvisorCheck) Table() reform.Table { + return AdvisorCheckTable +} + +// PKValue returns a value of primary key for that record. +// Returned interface{} value is never untyped nil. +func (s *AdvisorCheck) PKValue() interface{} { + return s.Name +} + +// PKPointer returns a pointer to primary key field for that record. +// Returned interface{} value is never untyped nil. +func (s *AdvisorCheck) PKPointer() interface{} { + return &s.Name +} + +// HasPK returns true if record has non-zero primary key set, false otherwise. +func (s *AdvisorCheck) HasPK() bool { + return s.Name != AdvisorCheckTable.z[AdvisorCheckTable.s.PKFieldIndex] +} + +// SetPK sets record primary key, if possible. +// +// Deprecated: prefer direct field assignment where possible: s.Name = pk. +func (s *AdvisorCheck) SetPK(pk interface{}) { + reform.SetPK(s, pk) +} + +// check interfaces +var ( + _ reform.View = AdvisorCheckTable + _ reform.Struct = (*AdvisorCheck)(nil) + _ reform.Table = AdvisorCheckTable + _ reform.Record = (*AdvisorCheck)(nil) + _ fmt.Stringer = (*AdvisorCheck)(nil) +) + +func init() { + parse.AssertUpToDate(&AdvisorCheckTable.s, new(AdvisorCheck)) +} diff --git a/managed/models/advisor_run_helpers.go b/managed/models/advisor_run_helpers.go new file mode 100644 index 00000000000..b6960492c88 --- /dev/null +++ b/managed/models/advisor_run_helpers.go @@ -0,0 +1,230 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "gopkg.in/reform.v1" +) + +// StartAdvisorRun records the beginning of an Advisor checks execution. The +// counts stay zero and finished_at stays NULL until FinishAdvisorRun is called. +func StartAdvisorRun(ctx context.Context, q *reform.Querier, r *AdvisorRun) error { + if r.ID == "" { + r.ID = uuid.NewString() + } + return q.WithContext(ctx).Insert(r) +} + +// AdvisorRunCounts holds the totals denormalized onto a run when it completes. +type AdvisorRunCounts struct { + ChecksCount int + ServicesCount int + FindingsCount int + ErrorsCount int + SeverityCounts map[Severity]int +} + +// FinishAdvisorRun marks a run as complete and stores its totals. A missing run +// row is not an error: runs recorded before this table existed have nothing to +// update, and a failed insert must not break the check run itself. +func FinishAdvisorRun(ctx context.Context, q *reform.Querier, id string, finishedAt time.Time, counts AdvisorRunCounts) error { + run := &AdvisorRun{ID: id} + err := q.WithContext(ctx).Reload(run) + if err != nil { + if errors.Is(err, reform.ErrNoRows) { + return nil + } + return fmt.Errorf("failed to load advisor run '%s': %w", id, err) + } + + run.FinishedAt = &finishedAt + run.ChecksCount = counts.ChecksCount + run.ServicesCount = counts.ServicesCount + run.FindingsCount = counts.FindingsCount + run.ErrorsCount = counts.ErrorsCount + err = run.SetSeverityCounts(counts.SeverityCounts) + if err != nil { + return err + } + + err = q.WithContext(ctx).Update(run) + if err != nil { + return fmt.Errorf("failed to update advisor run '%s': %w", id, err) + } + return nil +} + +// ComputeAdvisorRunCounts derives a run's totals from the insights it recorded. +// The insights are the authoritative record of what the run produced, so the +// stored counts cannot drift from the rows they summarize. +func ComputeAdvisorRunCounts(ctx context.Context, q *reform.Querier, runID string) (AdvisorRunCounts, error) { + var counts AdvisorRunCounts + + failed := CheckResultFailed + errored := CheckResultError + err := q.QueryRowContext( + ctx, + "SELECT count(DISTINCT check_name), count(DISTINCT service_id), "+ + "count(*) FILTER (WHERE status = $1), count(*) FILTER (WHERE status = $2) "+ + "FROM "+InsightTable.Name()+" WHERE run_id = $3", + failed, errored, runID, + ).Scan(&counts.ChecksCount, &counts.ServicesCount, &counts.FindingsCount, &counts.ErrorsCount) + if err != nil { + return counts, fmt.Errorf("failed to count insights for run '%s': %w", runID, err) + } + + rows, err := q.QueryContext( + ctx, + "SELECT severity, count(*) FROM "+InsightTable.Name()+ + " WHERE run_id = $1 AND status = $2 GROUP BY severity", + runID, failed, + ) + if err != nil { + return counts, fmt.Errorf("failed to count severities for run '%s': %w", runID, err) + } + defer rows.Close() //nolint:errcheck + + counts.SeverityCounts = make(map[Severity]int) + for rows.Next() { + var severity Severity + var count int + err = rows.Scan(&severity, &count) + if err != nil { + return counts, fmt.Errorf("failed to scan severity count for run '%s': %w", runID, err) + } + counts.SeverityCounts[severity] = count + } + err = rows.Err() + if err != nil { + return counts, fmt.Errorf("failed to read severity counts for run '%s': %w", runID, err) + } + + return counts, nil +} + +// FindUnfinishedAdvisorRuns returns runs that never recorded a completion. After +// a restart these cannot still be running, so the caller closes them out. +func FindUnfinishedAdvisorRuns(ctx context.Context, q *reform.Querier) ([]*AdvisorRun, error) { + rows, err := q.WithContext(ctx).SelectAllFrom(AdvisorRunTable, "WHERE finished_at IS NULL") + if err != nil { + return nil, fmt.Errorf("failed to select unfinished advisor runs: %w", err) + } + + runs := make([]*AdvisorRun, 0, len(rows)) + for _, r := range rows { + runs = append(runs, r.(*AdvisorRun)) //nolint:forcetypeassert + } + return runs, nil +} + +// LastInsightTimeForRun returns when the run last recorded an insight. The +// second result is false when the run produced none. +func LastInsightTimeForRun(ctx context.Context, q *reform.Querier, runID string) (time.Time, bool, error) { + var last *time.Time + err := q.QueryRowContext( + ctx, + "SELECT max(checked_at) FROM "+InsightTable.Name()+" WHERE run_id = $1", runID, + ).Scan(&last) + if err != nil { + return time.Time{}, false, fmt.Errorf("failed to read last insight time for run '%s': %w", runID, err) + } + if last == nil { + return time.Time{}, false, nil + } + return last.UTC(), true, nil +} + +// AdvisorRunFilters specifies filters for querying Advisor runs. +type AdvisorRunFilters struct { + TriggeredBy *CheckTriggeredBy + From *time.Time + To *time.Time +} + +// advisorRunConditions builds the WHERE clause and arguments for the given filters. +func advisorRunConditions(q *reform.Querier, filters AdvisorRunFilters) (string, []any) { + var conditions []string + var args []any + + if filters.TriggeredBy != nil { + conditions = append(conditions, "triggered_by = "+q.Placeholder(len(args)+1)) + args = append(args, *filters.TriggeredBy) + } + if filters.From != nil { + conditions = append(conditions, "started_at >= "+q.Placeholder(len(args)+1)) + args = append(args, *filters.From) + } + if filters.To != nil { + conditions = append(conditions, "started_at <= "+q.Placeholder(len(args)+1)) + args = append(args, *filters.To) + } + + if len(conditions) == 0 { + return "", args + } + return "WHERE " + strings.Join(conditions, " AND "), args +} + +// FindAdvisorRuns returns Advisor runs matching the filters, newest first. When +// pageSize is greater than zero, the results are paginated. +func FindAdvisorRuns(ctx context.Context, q *reform.Querier, filters AdvisorRunFilters, pageIndex, pageSize int) ([]*AdvisorRun, error) { + tail, args := advisorRunConditions(q, filters) + tail += " ORDER BY started_at DESC" + if pageSize > 0 { + tail += " LIMIT " + q.Placeholder(len(args)+1) + args = append(args, pageSize) + tail += " OFFSET " + q.Placeholder(len(args)+1) + args = append(args, pageIndex*pageSize) + } + + rows, err := q.WithContext(ctx).SelectAllFrom(AdvisorRunTable, tail, args...) + if err != nil { + return nil, fmt.Errorf("failed to select advisor runs: %w", err) + } + + runs := make([]*AdvisorRun, 0, len(rows)) + for _, r := range rows { + runs = append(runs, r.(*AdvisorRun)) //nolint:forcetypeassert + } + return runs, nil +} + +// CountAdvisorRuns returns the number of Advisor runs matching the filters. +func CountAdvisorRuns(ctx context.Context, q *reform.Querier, filters AdvisorRunFilters) (int, error) { + where, args := advisorRunConditions(q, filters) + + var count int + err := q.QueryRowContext(ctx, "SELECT count(*) FROM "+AdvisorRunTable.Name()+" "+where, args...).Scan(&count) + if err != nil { + return 0, fmt.Errorf("failed to count advisor runs: %w", err) + } + return count, nil +} + +// CleanupOldAdvisorRuns deletes Advisor runs started at or before the given time. +// Runs are pruned by their own start time rather than with their insights, so a +// run whose insights are already gone still reports its stored totals. +func CleanupOldAdvisorRuns(ctx context.Context, q *reform.Querier, olderThan time.Time) error { + _, err := q.WithContext(ctx).DeleteFrom(AdvisorRunTable, " WHERE started_at <= $1", olderThan) + return err +} diff --git a/managed/models/advisor_run_helpers_test.go b/managed/models/advisor_run_helpers_test.go new file mode 100644 index 00000000000..a2f13bc4df9 --- /dev/null +++ b/managed/models/advisor_run_helpers_test.go @@ -0,0 +1,263 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/dialects/postgresql" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" + "github.com/percona/pmm/managed/utils/testdb" +) + +func TestAdvisorRuns(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)) + q := db.Querier + + start := func(t *testing.T, run *models.AdvisorRun) *models.AdvisorRun { + t.Helper() + require.NoError(t, models.StartAdvisorRun(t.Context(), q, run)) + return run + } + + t.Run("a started run has an ID, no completion and no counts", func(t *testing.T) { + run := start(t, &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: time.Date(2026, 8, 1, 10, 0, 0, 0, time.UTC), + }) + + assert.NotEmpty(t, run.ID) + assert.True(t, run.IsRunning()) + + runs, err := models.FindAdvisorRuns(t.Context(), q, models.AdvisorRunFilters{}, 0, 0) + require.NoError(t, err) + + var found *models.AdvisorRun + for _, r := range runs { + if r.ID == run.ID { + found = r + } + } + require.NotNil(t, found) + assert.True(t, found.IsRunning()) + assert.Zero(t, found.FindingsCount) + + counts, err := found.GetSeverityCounts() + require.NoError(t, err) + assert.Empty(t, counts) + }) + + t.Run("finishing a run stores its completion and counts", func(t *testing.T) { + run := start(t, &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByScheduler, + StartedAt: time.Date(2026, 8, 1, 11, 0, 0, 0, time.UTC), + }) + finishedAt := time.Date(2026, 8, 1, 11, 2, 30, 0, time.UTC) + + require.NoError(t, models.FinishAdvisorRun(t.Context(), q, run.ID, finishedAt, models.AdvisorRunCounts{ + ChecksCount: 107, + ServicesCount: 3, + FindingsCount: 28, + ErrorsCount: 1, + SeverityCounts: map[models.Severity]int{ + models.Severity(common.Error): 4, + models.Severity(common.Warning): 22, + }, + })) + + reloaded := &models.AdvisorRun{ID: run.ID} + require.NoError(t, q.Reload(reloaded)) + + assert.False(t, reloaded.IsRunning()) + require.NotNil(t, reloaded.FinishedAt) + assert.Equal(t, finishedAt, *reloaded.FinishedAt) + assert.Equal(t, 107, reloaded.ChecksCount) + assert.Equal(t, 3, reloaded.ServicesCount) + assert.Equal(t, 28, reloaded.FindingsCount) + assert.Equal(t, 1, reloaded.ErrorsCount) + + counts, err := reloaded.GetSeverityCounts() + require.NoError(t, err) + assert.Equal(t, map[models.Severity]int{ + models.Severity(common.Error): 4, + models.Severity(common.Warning): 22, + }, counts) + }) + + t.Run("finishing an unknown run is not an error", func(t *testing.T) { + require.NoError(t, models.FinishAdvisorRun(t.Context(), q, "no-such-run", time.Now(), models.AdvisorRunCounts{})) + }) + + t.Run("runs come back newest first and paginate", func(t *testing.T) { + base := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC) + ids := make([]string, 0, 3) + for i := range 3 { + run := start(t, &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: base.Add(time.Duration(i) * time.Hour), + }) + ids = append(ids, run.ID) + } + from := base.Add(-time.Minute) + to := base.Add(3 * time.Hour) + filters := models.AdvisorRunFilters{From: &from, To: &to} + + runs, err := models.FindAdvisorRuns(t.Context(), q, filters, 0, 0) + require.NoError(t, err) + require.Len(t, runs, 3) + // newest first, so the last one started comes back first + assert.Equal(t, ids[2], runs[0].ID) + assert.Equal(t, ids[0], runs[2].ID) + + total, err := models.CountAdvisorRuns(t.Context(), q, filters) + require.NoError(t, err) + assert.Equal(t, 3, total) + + page, err := models.FindAdvisorRuns(t.Context(), q, filters, 1, 2) + require.NoError(t, err) + require.Len(t, page, 1) + assert.Equal(t, ids[0], page[0].ID) + }) + + t.Run("filters by trigger", func(t *testing.T) { + from := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC) + to := from.Add(time.Hour) + start(t, &models.AdvisorRun{TriggeredBy: models.CheckTriggeredByUser, StartedAt: from}) + start(t, &models.AdvisorRun{TriggeredBy: models.CheckTriggeredByScheduler, StartedAt: from}) + + scheduler := models.CheckTriggeredByScheduler + runs, err := models.FindAdvisorRuns(t.Context(), q, models.AdvisorRunFilters{ + TriggeredBy: &scheduler, + From: &from, + To: &to, + }, 0, 0) + require.NoError(t, err) + require.Len(t, runs, 1) + assert.Equal(t, models.CheckTriggeredByScheduler, runs[0].TriggeredBy) + }) + + t.Run("counts are derived from the run's insights", func(t *testing.T) { + run := start(t, &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: time.Date(2026, 5, 1, 9, 0, 0, 0, time.UTC), + }) + other := start(t, &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: time.Date(2026, 5, 1, 9, 30, 0, 0, time.UTC), + }) + + insight := func(runID, checkName, serviceID string, status models.CheckResultStatus, severity common.Severity, checkedAt time.Time) { + t.Helper() + require.NoError(t, models.CreateInsight(t.Context(), q, &models.Insight{ + RunID: runID, + CheckName: checkName, + ServiceID: serviceID, + ServiceType: models.MySQLServiceType, + Interval: models.Standard, + Status: status, + Severity: models.Severity(severity), + CheckedAt: checkedAt, + })) + } + + base := time.Date(2026, 5, 1, 9, 1, 0, 0, time.UTC) + // two findings on one check/service pair, one on another, plus a pass and + // a check that could not run at all + insight(run.ID, "check_a", "svc-1", models.CheckResultFailed, common.Error, base) + insight(run.ID, "check_a", "svc-1", models.CheckResultFailed, common.Warning, base) + insight(run.ID, "check_b", "svc-2", models.CheckResultFailed, common.Warning, base.Add(time.Minute)) + insight(run.ID, "check_c", "svc-2", models.CheckResultOK, common.Info, base.Add(2*time.Minute)) + insight(run.ID, "check_d", "svc-3", models.CheckResultError, common.Info, base.Add(3*time.Minute)) + // a different run's rows must not leak into the totals + insight(other.ID, "check_z", "svc-9", models.CheckResultFailed, common.Critical, base) + + counts, err := models.ComputeAdvisorRunCounts(t.Context(), q, run.ID) + require.NoError(t, err) + assert.Equal(t, 4, counts.ChecksCount) + assert.Equal(t, 3, counts.ServicesCount) + // only failed rows are findings; the pass and the error are not + assert.Equal(t, 3, counts.FindingsCount) + assert.Equal(t, 1, counts.ErrorsCount) + assert.Equal(t, map[models.Severity]int{ + models.Severity(common.Error): 1, + models.Severity(common.Warning): 2, + }, counts.SeverityCounts) + + last, ok, err := models.LastInsightTimeForRun(t.Context(), q, run.ID) + require.NoError(t, err) + assert.True(t, ok) + assert.Equal(t, base.Add(3*time.Minute), last) + }) + + t.Run("a run with no insights has no last insight time", func(t *testing.T) { + run := start(t, &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: time.Date(2026, 4, 1, 9, 0, 0, 0, time.UTC), + }) + + _, ok, err := models.LastInsightTimeForRun(t.Context(), q, run.ID) + require.NoError(t, err) + assert.False(t, ok) + }) + + t.Run("unfinished runs are found, and not returned once closed", func(t *testing.T) { + run := start(t, &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: time.Date(2026, 3, 1, 9, 0, 0, 0, time.UTC), + }) + + open, err := models.FindUnfinishedAdvisorRuns(t.Context(), q) + require.NoError(t, err) + ids := make([]string, 0, len(open)) + for _, r := range open { + ids = append(ids, r.ID) + } + assert.Contains(t, ids, run.ID) + + require.NoError(t, models.FinishAdvisorRun(t.Context(), q, run.ID, run.StartedAt, models.AdvisorRunCounts{})) + + open, err = models.FindUnfinishedAdvisorRuns(t.Context(), q) + require.NoError(t, err) + ids = ids[:0] + for _, r := range open { + ids = append(ids, r.ID) + } + assert.NotContains(t, ids, run.ID) + }) + + t.Run("cleanup removes runs by their own start time", func(t *testing.T) { + old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + recent := time.Date(2026, 12, 1, 0, 0, 0, 0, time.UTC) + oldRun := start(t, &models.AdvisorRun{TriggeredBy: models.CheckTriggeredByUser, StartedAt: old}) + recentRun := start(t, &models.AdvisorRun{TriggeredBy: models.CheckTriggeredByUser, StartedAt: recent}) + + require.NoError(t, models.CleanupOldAdvisorRuns(t.Context(), q, old.Add(time.Hour))) + + require.Error(t, q.Reload(&models.AdvisorRun{ID: oldRun.ID})) + require.NoError(t, q.Reload(&models.AdvisorRun{ID: recentRun.ID})) + }) +} diff --git a/managed/models/advisor_run_model.go b/managed/models/advisor_run_model.go new file mode 100644 index 00000000000..9f6fa464be0 --- /dev/null +++ b/managed/models/advisor_run_model.go @@ -0,0 +1,115 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "encoding/json" + "fmt" + "time" + + "gopkg.in/reform.v1" +) + +//go:generate go tool reform + +// AdvisorRun represents a single execution of Advisor checks. Counts are filled +// in when the run finishes, so they survive the pruning of the run's insights. +// +//reform:advisor_runs +type AdvisorRun struct { + ID string `reform:"id,pk"` + TriggeredBy CheckTriggeredBy `reform:"triggered_by"` + StartedAt time.Time `reform:"started_at"` + FinishedAt *time.Time `reform:"finished_at"` + ChecksCount int `reform:"checks_count"` + ServicesCount int `reform:"services_count"` + // FindingsCount counts insights with a failed status, i.e. actual findings. + FindingsCount int `reform:"findings_count"` + // ErrorsCount counts checks that could not be executed at all. + ErrorsCount int `reform:"errors_count"` + SeverityCounts []byte `reform:"severity_counts"` +} + +// BeforeInsert implements reform.BeforeInserter interface. +func (r *AdvisorRun) BeforeInsert() error { + if r.StartedAt.IsZero() { + r.StartedAt = Now() + } + if len(r.SeverityCounts) == 0 { + r.SeverityCounts = nil + } + return nil +} + +// BeforeUpdate implements reform.BeforeUpdater interface. +func (r *AdvisorRun) BeforeUpdate() error { + if len(r.SeverityCounts) == 0 { + r.SeverityCounts = nil + } + return nil +} + +// AfterFind implements reform.AfterFinder interface. +func (r *AdvisorRun) AfterFind() error { + r.StartedAt = r.StartedAt.UTC() + if r.FinishedAt != nil { + finished := r.FinishedAt.UTC() + r.FinishedAt = &finished + } + if len(r.SeverityCounts) == 0 { + r.SeverityCounts = nil + } + return nil +} + +// IsRunning reports whether the run has not recorded a completion yet. +func (r *AdvisorRun) IsRunning() bool { + return r.FinishedAt == nil +} + +// GetSeverityCounts decodes the per-severity finding counts. +func (r *AdvisorRun) GetSeverityCounts() (map[Severity]int, error) { + if len(r.SeverityCounts) == 0 { + return nil, nil //nolint:nilnil + } + m := make(map[Severity]int) + err := json.Unmarshal(r.SeverityCounts, &m) + if err != nil { + return nil, fmt.Errorf("failed to decode severity counts: %w", err) + } + return m, nil +} + +// SetSeverityCounts encodes the per-severity finding counts. +func (r *AdvisorRun) SetSeverityCounts(m map[Severity]int) error { + if len(m) == 0 { + r.SeverityCounts = nil + return nil + } + b, err := json.Marshal(m) + if err != nil { + return fmt.Errorf("failed to encode severity counts: %w", err) + } + r.SeverityCounts = b + return nil +} + +// check interfaces. +var ( + _ reform.BeforeInserter = (*AdvisorRun)(nil) + _ reform.BeforeUpdater = (*AdvisorRun)(nil) + _ reform.AfterFinder = (*AdvisorRun)(nil) +) diff --git a/managed/models/advisor_run_model_reform.go b/managed/models/advisor_run_model_reform.go new file mode 100644 index 00000000000..46cc4a8424b --- /dev/null +++ b/managed/models/advisor_run_model_reform.go @@ -0,0 +1,171 @@ +// Code generated by gopkg.in/reform.v1. DO NOT EDIT. + +package models + +import ( + "fmt" + "strings" + + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/parse" +) + +type advisorRunTableType struct { + s parse.StructInfo + z []interface{} +} + +// Schema returns a schema name in SQL database (""). +func (v *advisorRunTableType) Schema() string { + return v.s.SQLSchema +} + +// Name returns a view or table name in SQL database ("advisor_runs"). +func (v *advisorRunTableType) Name() string { + return v.s.SQLName +} + +// Columns returns a new slice of column names for that view or table in SQL database. +func (v *advisorRunTableType) Columns() []string { + return []string{ + "id", + "triggered_by", + "started_at", + "finished_at", + "checks_count", + "services_count", + "findings_count", + "errors_count", + "severity_counts", + } +} + +// NewStruct makes a new struct for that view or table. +func (v *advisorRunTableType) NewStruct() reform.Struct { + return new(AdvisorRun) +} + +// NewRecord makes a new record for that table. +func (v *advisorRunTableType) NewRecord() reform.Record { + return new(AdvisorRun) +} + +// PKColumnIndex returns an index of primary key column for that table in SQL database. +func (v *advisorRunTableType) PKColumnIndex() uint { + return uint(v.s.PKFieldIndex) +} + +// AdvisorRunTable represents advisor_runs view or table in SQL database. +var AdvisorRunTable = &advisorRunTableType{ + s: parse.StructInfo{ + Type: "AdvisorRun", + SQLName: "advisor_runs", + Fields: []parse.FieldInfo{ + {Name: "ID", Type: "string", Column: "id"}, + {Name: "TriggeredBy", Type: "CheckTriggeredBy", Column: "triggered_by"}, + {Name: "StartedAt", Type: "time.Time", Column: "started_at"}, + {Name: "FinishedAt", Type: "*time.Time", Column: "finished_at"}, + {Name: "ChecksCount", Type: "int", Column: "checks_count"}, + {Name: "ServicesCount", Type: "int", Column: "services_count"}, + {Name: "FindingsCount", Type: "int", Column: "findings_count"}, + {Name: "ErrorsCount", Type: "int", Column: "errors_count"}, + {Name: "SeverityCounts", Type: "[]uint8", Column: "severity_counts"}, + }, + PKFieldIndex: 0, + }, + z: new(AdvisorRun).Values(), +} + +// String returns a string representation of this struct or record. +func (s AdvisorRun) String() string { + res := make([]string, 9) + res[0] = "ID: " + reform.Inspect(s.ID, true) + res[1] = "TriggeredBy: " + reform.Inspect(s.TriggeredBy, true) + res[2] = "StartedAt: " + reform.Inspect(s.StartedAt, true) + res[3] = "FinishedAt: " + reform.Inspect(s.FinishedAt, true) + res[4] = "ChecksCount: " + reform.Inspect(s.ChecksCount, true) + res[5] = "ServicesCount: " + reform.Inspect(s.ServicesCount, true) + res[6] = "FindingsCount: " + reform.Inspect(s.FindingsCount, true) + res[7] = "ErrorsCount: " + reform.Inspect(s.ErrorsCount, true) + res[8] = "SeverityCounts: " + reform.Inspect(s.SeverityCounts, true) + return strings.Join(res, ", ") +} + +// Values returns a slice of struct or record field values. +// Returned interface{} values are never untyped nils. +func (s *AdvisorRun) Values() []interface{} { + return []interface{}{ + s.ID, + s.TriggeredBy, + s.StartedAt, + s.FinishedAt, + s.ChecksCount, + s.ServicesCount, + s.FindingsCount, + s.ErrorsCount, + s.SeverityCounts, + } +} + +// Pointers returns a slice of pointers to struct or record fields. +// Returned interface{} values are never untyped nils. +func (s *AdvisorRun) Pointers() []interface{} { + return []interface{}{ + &s.ID, + &s.TriggeredBy, + &s.StartedAt, + &s.FinishedAt, + &s.ChecksCount, + &s.ServicesCount, + &s.FindingsCount, + &s.ErrorsCount, + &s.SeverityCounts, + } +} + +// View returns View object for that struct. +func (s *AdvisorRun) View() reform.View { + return AdvisorRunTable +} + +// Table returns Table object for that record. +func (s *AdvisorRun) Table() reform.Table { + return AdvisorRunTable +} + +// PKValue returns a value of primary key for that record. +// Returned interface{} value is never untyped nil. +func (s *AdvisorRun) PKValue() interface{} { + return s.ID +} + +// PKPointer returns a pointer to primary key field for that record. +// Returned interface{} value is never untyped nil. +func (s *AdvisorRun) PKPointer() interface{} { + return &s.ID +} + +// HasPK returns true if record has non-zero primary key set, false otherwise. +func (s *AdvisorRun) HasPK() bool { + return s.ID != AdvisorRunTable.z[AdvisorRunTable.s.PKFieldIndex] +} + +// SetPK sets record primary key, if possible. +// +// Deprecated: prefer direct field assignment where possible: s.ID = pk. +func (s *AdvisorRun) SetPK(pk interface{}) { + reform.SetPK(s, pk) +} + +// check interfaces +var ( + _ reform.View = AdvisorRunTable + _ reform.Struct = (*AdvisorRun)(nil) + _ reform.Table = AdvisorRunTable + _ reform.Record = (*AdvisorRun)(nil) + _ fmt.Stringer = (*AdvisorRun)(nil) +) + +func init() { + parse.AssertUpToDate(&AdvisorRunTable.s, new(AdvisorRun)) +} diff --git a/managed/models/check_settings_helper.go b/managed/models/check_settings_helper.go deleted file mode 100644 index cceb51d0344..00000000000 --- a/managed/models/check_settings_helper.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 2023 Percona LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package models - -import ( - "errors" - "fmt" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "gopkg.in/reform.v1" -) - -// FindCheckSettings returns all CheckSettings stored in the table. -func FindCheckSettings(q *reform.Querier) (map[string]Interval, error) { - rows, err := q.SelectAllFrom(CheckSettingsTable, "") - if err != nil { - if errors.Is(err, reform.ErrNoRows) { - return nil, err - } - return nil, err - } - - cs := make(map[string]Interval) - for _, r := range rows { - state := r.(*CheckSettings) //nolint:forcetypeassert - cs[state.Name] = state.Interval - } - return cs, nil -} - -// FindCheckSettingsByName finds CheckSettings by check name. -func FindCheckSettingsByName(q *reform.Querier, name string) (*CheckSettings, error) { - if name == "" { - return nil, status.Error(codes.InvalidArgument, "Empty Check name.") - } - - cs := &CheckSettings{Name: name} - err := q.Reload(cs) - if err != nil { - if errors.Is(err, reform.ErrNoRows) { - return nil, err - } - return nil, err - } - - return cs, nil -} - -// CreateCheckSettings persists CheckSettings. -func CreateCheckSettings(q *reform.Querier, name string, interval Interval) (*CheckSettings, error) { - row := &CheckSettings{ - Name: name, - Interval: interval, - } - - err := q.Insert(row) - if err != nil { - return nil, fmt.Errorf("failed to create check setting: %w", err) - } - - return row, nil -} - -// ChangeCheckSettings updates the interval of a check setting if already present. -func ChangeCheckSettings(q *reform.Querier, name string, interval Interval) (*CheckSettings, error) { - row, err := FindCheckSettingsByName(q, name) - if err != nil { - return nil, err - } - - row.Interval = interval - - err = q.Update(row) - if err != nil { - return nil, fmt.Errorf("failed to update check setting: %w", err) - } - - return row, nil -} diff --git a/managed/models/check_settings_helper_test.go b/managed/models/check_settings_helper_test.go deleted file mode 100644 index 40812756701..00000000000 --- a/managed/models/check_settings_helper_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (C) 2023 Percona LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package models_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/reform.v1" - "gopkg.in/reform.v1/dialects/postgresql" - - "github.com/percona/pmm/managed/models" - "github.com/percona/pmm/managed/utils/testdb" -) - -func TestChecksSettings(t *testing.T) { //nolint:tparallel - t.Parallel() - sqlDB := testdb.Open(t, models.SkipFixtures, nil) - t.Cleanup(func() { - require.NoError(t, sqlDB.Close()) - }) - db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)) - - t.Run("create", func(t *testing.T) { - tx, err := db.Begin() - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, tx.Rollback()) - }) - - q := tx.Querier - - actual, err := models.CreateCheckSettings(q, "check-name", models.Standard) - require.NoError(t, err) - assert.Equal(t, "check-name", actual.Name) - assert.Equal(t, models.Standard, actual.Interval) - }) - - t.Run("change", func(t *testing.T) { - tx, err := db.Begin() - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, tx.Rollback()) - }) - - q := tx.Querier - - oldState, err := models.CreateCheckSettings(q, "check-name", models.Standard) - require.NoError(t, err) - assert.Equal(t, "check-name", oldState.Name) - assert.Equal(t, models.Standard, oldState.Interval) - - newState, err := models.ChangeCheckSettings(q, "check-name", models.Rare) - require.NoError(t, err) - assert.Equal(t, oldState.Name, newState.Name) - assert.NotEqual(t, oldState.Interval, newState.Interval) - assert.Equal(t, models.Rare, newState.Interval) - }) - - t.Run("find by name", func(t *testing.T) { - tx, err := db.Begin() - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, tx.Rollback()) - }) - - q := tx.Querier - - expected, err := models.CreateCheckSettings(q, "check-name", models.Standard) - require.NoError(t, err) - - actual, err := models.FindCheckSettingsByName(q, "check-name") - require.NoError(t, err) - assert.Equal(t, expected, actual) - }) - - t.Run("find all", func(t *testing.T) { - tx, err := db.Begin() - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, tx.Rollback()) - }) - - q := tx.Querier - - _, err = models.CreateCheckSettings(q, "check1", models.Standard) - require.NoError(t, err) - _, err = models.CreateCheckSettings(q, "check2", models.Standard) - require.NoError(t, err) - - actual, err := models.FindCheckSettings(q) - require.NoError(t, err) - assert.Len(t, actual, 2) - assert.Equal(t, models.Standard, actual["check1"]) - assert.Equal(t, models.Standard, actual["check2"]) - }) -} diff --git a/managed/models/check_settings_model.go b/managed/models/check_settings_model.go deleted file mode 100644 index 932b976d227..00000000000 --- a/managed/models/check_settings_model.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2023 Percona LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package models - -//go:generate go tool reform - -// Interval represents check execution interval. -type Interval string - -// Available check execution intervals. -const ( - Standard Interval = "standard" - Frequent Interval = "frequent" - Rare Interval = "rare" -) - -// CheckSettings represents any changes to an Advisor check loaded in pmm-managed. -// -//reform:check_settings -type CheckSettings struct { - Name string `reform:"name,pk"` - Interval Interval `reform:"interval"` -} diff --git a/managed/models/check_settings_model_reform.go b/managed/models/check_settings_model_reform.go deleted file mode 100644 index be8cbb9d32e..00000000000 --- a/managed/models/check_settings_model_reform.go +++ /dev/null @@ -1,136 +0,0 @@ -// Code generated by gopkg.in/reform.v1. DO NOT EDIT. - -package models - -import ( - "fmt" - "strings" - - "gopkg.in/reform.v1" - "gopkg.in/reform.v1/parse" -) - -type checkSettingsTableType struct { - s parse.StructInfo - z []interface{} -} - -// Schema returns a schema name in SQL database (""). -func (v *checkSettingsTableType) Schema() string { - return v.s.SQLSchema -} - -// Name returns a view or table name in SQL database ("check_settings"). -func (v *checkSettingsTableType) Name() string { - return v.s.SQLName -} - -// Columns returns a new slice of column names for that view or table in SQL database. -func (v *checkSettingsTableType) Columns() []string { - return []string{ - "name", - "interval", - } -} - -// NewStruct makes a new struct for that view or table. -func (v *checkSettingsTableType) NewStruct() reform.Struct { - return new(CheckSettings) -} - -// NewRecord makes a new record for that table. -func (v *checkSettingsTableType) NewRecord() reform.Record { - return new(CheckSettings) -} - -// PKColumnIndex returns an index of primary key column for that table in SQL database. -func (v *checkSettingsTableType) PKColumnIndex() uint { - return uint(v.s.PKFieldIndex) -} - -// CheckSettingsTable represents check_settings view or table in SQL database. -var CheckSettingsTable = &checkSettingsTableType{ - s: parse.StructInfo{ - Type: "CheckSettings", - SQLName: "check_settings", - Fields: []parse.FieldInfo{ - {Name: "Name", Type: "string", Column: "name"}, - {Name: "Interval", Type: "Interval", Column: "interval"}, - }, - PKFieldIndex: 0, - }, - z: new(CheckSettings).Values(), -} - -// String returns a string representation of this struct or record. -func (s CheckSettings) String() string { - res := make([]string, 2) - res[0] = "Name: " + reform.Inspect(s.Name, true) - res[1] = "Interval: " + reform.Inspect(s.Interval, true) - return strings.Join(res, ", ") -} - -// Values returns a slice of struct or record field values. -// Returned interface{} values are never untyped nils. -func (s *CheckSettings) Values() []interface{} { - return []interface{}{ - s.Name, - s.Interval, - } -} - -// Pointers returns a slice of pointers to struct or record fields. -// Returned interface{} values are never untyped nils. -func (s *CheckSettings) Pointers() []interface{} { - return []interface{}{ - &s.Name, - &s.Interval, - } -} - -// View returns View object for that struct. -func (s *CheckSettings) View() reform.View { - return CheckSettingsTable -} - -// Table returns Table object for that record. -func (s *CheckSettings) Table() reform.Table { - return CheckSettingsTable -} - -// PKValue returns a value of primary key for that record. -// Returned interface{} value is never untyped nil. -func (s *CheckSettings) PKValue() interface{} { - return s.Name -} - -// PKPointer returns a pointer to primary key field for that record. -// Returned interface{} value is never untyped nil. -func (s *CheckSettings) PKPointer() interface{} { - return &s.Name -} - -// HasPK returns true if record has non-zero primary key set, false otherwise. -func (s *CheckSettings) HasPK() bool { - return s.Name != CheckSettingsTable.z[CheckSettingsTable.s.PKFieldIndex] -} - -// SetPK sets record primary key, if possible. -// -// Deprecated: prefer direct field assignment where possible: s.Name = pk. -func (s *CheckSettings) SetPK(pk interface{}) { - reform.SetPK(s, pk) -} - -// check interfaces -var ( - _ reform.View = CheckSettingsTable - _ reform.Struct = (*CheckSettings)(nil) - _ reform.Table = CheckSettingsTable - _ reform.Record = (*CheckSettings)(nil) - _ fmt.Stringer = (*CheckSettings)(nil) -) - -func init() { - parse.AssertUpToDate(&CheckSettingsTable.s, new(CheckSettings)) -} diff --git a/managed/models/database.go b/managed/models/database.go index fa839fa3be0..220d78a840c 100644 --- a/managed/models/database.go +++ b/managed/models/database.go @@ -1185,6 +1185,116 @@ var databaseSchema = [][]string{ `ALTER TABLE dumps ADD COLUMN encrypted boolean NOT NULL DEFAULT false`, `UPDATE dumps SET encrypted = false`, }, + 119: { + `CREATE TABLE advisor_insights ( + id VARCHAR NOT NULL, + run_id VARCHAR NOT NULL, + check_name VARCHAR NOT NULL CHECK (check_name <> ''), + category VARCHAR NOT NULL, + subcategory VARCHAR NOT NULL, + interval VARCHAR NOT NULL, + service_id VARCHAR NOT NULL, + service_name VARCHAR NOT NULL, + service_type VARCHAR NOT NULL, + node_id VARCHAR NOT NULL, + node_name VARCHAR NOT NULL, + environment VARCHAR NOT NULL, + cluster VARCHAR NOT NULL, + replication_set VARCHAR NOT NULL, + region VARCHAR NOT NULL, + az VARCHAR NOT NULL, + status VARCHAR NOT NULL CHECK (status <> ''), + summary VARCHAR NOT NULL, + description TEXT NOT NULL, + outcome TEXT NOT NULL, + read_more_url VARCHAR NOT NULL, + severity VARCHAR NOT NULL, + labels TEXT, + checked_at TIMESTAMP NOT NULL, + is_read BOOLEAN NOT NULL, + triggered_by VARCHAR NOT NULL, + + PRIMARY KEY (id) + )`, + `CREATE INDEX advisor_insights_run_id_idx ON advisor_insights (run_id)`, + `CREATE INDEX advisor_insights_service_id_idx ON advisor_insights (service_id)`, + `CREATE INDEX advisor_insights_checked_at_idx ON advisor_insights (checked_at)`, + }, + 120: { + `CREATE TABLE advisor_checks ( + name VARCHAR(128) NOT NULL CHECK (name <> ''), + source VARCHAR NOT NULL CHECK (source <> ''), + version INTEGER NOT NULL, + summary VARCHAR NOT NULL, + description TEXT NOT NULL, + category VARCHAR NOT NULL, + subcategory VARCHAR NOT NULL, + technology VARCHAR NOT NULL, + interval VARCHAR NOT NULL, + interval_override VARCHAR, + disabled BOOLEAN NOT NULL, + disabled_service_ids JSONB, + queries TEXT NOT NULL, + script TEXT NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + + PRIMARY KEY (name) + )`, + + // Carry over interval overrides recorded by earlier PMM versions in the + // check_settings table. Content columns are placeholders: the startup + // reconcile refreshes them from the shipped check files and prunes rows + // of checks that no longer exist. + `INSERT INTO advisor_checks ( + name, source, version, summary, description, category, subcategory, + technology, interval, interval_override, disabled, queries, script, + created_at, updated_at + ) + SELECT name, 'builtin', 2, '', '', '', '', '', '', interval, false, '[]', '', now(), now() + FROM check_settings + WHERE name <> ''`, + + // Carry over globally-disabled check names recorded by earlier PMM + // versions in the settings JSON. + `INSERT INTO advisor_checks ( + name, source, version, summary, description, category, subcategory, + technology, interval, interval_override, disabled, queries, script, + created_at, updated_at + ) + SELECT DISTINCT x.name, 'builtin', 2, '', '', '', '', '', '', NULL, true, '[]', '', now(), now() + FROM settings, jsonb_array_elements_text( + CASE WHEN jsonb_typeof(settings #> '{sass,disabled_advisors}') = 'array' + THEN settings #> '{sass,disabled_advisors}' + ELSE '[]'::jsonb END + ) AS x(name) + WHERE x.name <> '' + ON CONFLICT (name) DO UPDATE SET disabled = true`, + + `UPDATE settings SET settings = settings #- '{sass,disabled_advisors}'`, + + `DROP TABLE IF EXISTS check_settings`, + }, + 121: { + // One row per Advisor checks execution. Counts are denormalized on + // completion so a run keeps reporting correct totals after its insights + // have been pruned by the retention cleaner. Deliberately no foreign key + // from advisor_insights.run_id: insight pruning must not touch runs. + `CREATE TABLE advisor_runs ( + id VARCHAR NOT NULL, + triggered_by VARCHAR NOT NULL, + started_at TIMESTAMP NOT NULL, + finished_at TIMESTAMP, + checks_count INTEGER NOT NULL, + services_count INTEGER NOT NULL, + findings_count INTEGER NOT NULL, + errors_count INTEGER NOT NULL, + severity_counts TEXT, + + PRIMARY KEY (id) + )`, + `CREATE INDEX advisor_runs_started_at_idx ON advisor_runs (started_at)`, + }, } // ^^^ Avoid default values in schema definition. ^^^ diff --git a/managed/models/database_test.go b/managed/models/database_test.go index 17b02c5d458..3e11d3601e1 100644 --- a/managed/models/database_test.go +++ b/managed/models/database_test.go @@ -393,4 +393,98 @@ func TestDatabaseMigrations(t *testing.T) { require.Equal(t, "id", agentID) require.True(t, exporterOptions.PushMetrics) }) + + t.Run("advisor checks migration: legacy settings seed advisor_checks", func(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, new(119)) + t.Cleanup(func() { + assert.NoError(t, sqlDB.Close()) + }) + + // legacy interval overrides, one of them also disabled + _, err := sqlDB.ExecContext( + t.Context(), + `INSERT INTO check_settings (name, interval) VALUES ('check_with_override', 'rare'), ('check_disabled_too', 'frequent')`, + ) + require.NoError(t, err) + + // legacy globally-disabled checks in the settings JSON; the nested + // jsonb_set creates the 'sass' object when a fresh DB lacks it + _, err = sqlDB.ExecContext( + t.Context(), + `UPDATE settings SET settings = jsonb_set( + jsonb_set(settings, '{sass}', COALESCE(settings->'sass', '{}'::jsonb)), + '{sass,disabled_advisors}', '["check_disabled_too", "check_disabled"]'::jsonb)`, + ) + require.NoError(t, err) + + // Apply migration + testdb.SetupDB(t, sqlDB, models.SkipFixtures, new(120)) + + rows, err := sqlDB.QueryContext( + t.Context(), + `SELECT name, source, interval_override, disabled FROM advisor_checks ORDER BY name`, + ) + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, rows.Close()) + }) + + type seeded struct { + source string + intervalOverride *string + disabled bool + } + actual := make(map[string]seeded) + for rows.Next() { + var name string + var s seeded + require.NoError(t, rows.Scan(&name, &s.source, &s.intervalOverride, &s.disabled)) + actual[name] = s + } + require.NoError(t, rows.Err()) + + assert.Equal(t, map[string]seeded{ + "check_with_override": {source: "builtin", intervalOverride: new("rare"), disabled: false}, + "check_disabled_too": {source: "builtin", intervalOverride: new("frequent"), disabled: true}, + "check_disabled": {source: "builtin", intervalOverride: nil, disabled: true}, + }, actual) + + // both legacy stores are gone + var disabledAdvisors *string + err = sqlDB.QueryRowContext(t.Context(), `SELECT settings #>> '{sass,disabled_advisors}' FROM settings`).Scan(&disabledAdvisors) + require.NoError(t, err) + assert.Nil(t, disabledAdvisors) + + var checkSettingsExists bool + err = sqlDB.QueryRowContext( + t.Context(), + `SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'check_settings')`, + ).Scan(&checkSettingsExists) + require.NoError(t, err) + assert.False(t, checkSettingsExists) + }) + + t.Run("advisor checks migration: non-array disabled_advisors is ignored", func(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, new(119)) + t.Cleanup(func() { + assert.NoError(t, sqlDB.Close()) + }) + + // A nil Go slice marshals to JSON null, so disabled_advisors can be a + // scalar rather than an array; the migration must not choke on it. + _, err := sqlDB.ExecContext( + t.Context(), + `UPDATE settings SET settings = jsonb_set( + jsonb_set(settings, '{sass}', COALESCE(settings->'sass', '{}'::jsonb)), + '{sass,disabled_advisors}', 'null'::jsonb)`, + ) + require.NoError(t, err) + + testdb.SetupDB(t, sqlDB, models.SkipFixtures, new(120)) + + var count int + err = sqlDB.QueryRowContext(t.Context(), `SELECT count(*) FROM advisor_checks`).Scan(&count) + require.NoError(t, err) + assert.Equal(t, 0, count) + }) } diff --git a/managed/models/insight_helpers.go b/managed/models/insight_helpers.go new file mode 100644 index 00000000000..aabde6a21c5 --- /dev/null +++ b/managed/models/insight_helpers.go @@ -0,0 +1,223 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "gopkg.in/reform.v1" +) + +// CreateInsight inserts a single Advisor check result into the history. +func CreateInsight(ctx context.Context, q *reform.Querier, r *Insight) error { + if r.ID == "" { + r.ID = uuid.NewString() + } + return q.WithContext(ctx).Insert(r) +} + +// InsightFilters specifies filters for querying Advisor insights. +type InsightFilters struct { + ServiceID string + // ServiceName is matched as a case-insensitive substring. + ServiceName string + // NodeName is matched as a case-insensitive substring. + NodeName string + Category string + CheckName string + RunID string + TriggeredBy *CheckTriggeredBy + Severity *Severity + Status *CheckResultStatus + IsRead *bool + From *time.Time + To *time.Time +} + +// insightConditions builds the WHERE clause and arguments for the given filters. +func insightConditions(q *reform.Querier, filters InsightFilters) (string, []any) { + var conditions []string + var args []any + + if filters.ServiceID != "" { + conditions = append(conditions, "service_id = "+q.Placeholder(len(args)+1)) + args = append(args, filters.ServiceID) + } + if filters.ServiceName != "" { + conditions = append(conditions, "service_name ILIKE "+q.Placeholder(len(args)+1)) + args = append(args, "%"+filters.ServiceName+"%") + } + if filters.NodeName != "" { + conditions = append(conditions, "node_name ILIKE "+q.Placeholder(len(args)+1)) + args = append(args, "%"+filters.NodeName+"%") + } + if filters.Category != "" { + conditions = append(conditions, "category = "+q.Placeholder(len(args)+1)) + args = append(args, filters.Category) + } + if filters.CheckName != "" { + conditions = append(conditions, "check_name = "+q.Placeholder(len(args)+1)) + args = append(args, filters.CheckName) + } + if filters.RunID != "" { + conditions = append(conditions, "run_id = "+q.Placeholder(len(args)+1)) + args = append(args, filters.RunID) + } + if filters.TriggeredBy != nil { + conditions = append(conditions, "triggered_by = "+q.Placeholder(len(args)+1)) + args = append(args, *filters.TriggeredBy) + } + if filters.Severity != nil { + conditions = append(conditions, "severity = "+q.Placeholder(len(args)+1)) + args = append(args, *filters.Severity) + } + if filters.Status != nil { + conditions = append(conditions, "status = "+q.Placeholder(len(args)+1)) + args = append(args, *filters.Status) + } + if filters.IsRead != nil { + conditions = append(conditions, "is_read = "+q.Placeholder(len(args)+1)) + args = append(args, *filters.IsRead) + } + if filters.From != nil { + conditions = append(conditions, "checked_at >= "+q.Placeholder(len(args)+1)) + args = append(args, *filters.From) + } + if filters.To != nil { + conditions = append(conditions, "checked_at <= "+q.Placeholder(len(args)+1)) + args = append(args, *filters.To) + } + + if len(conditions) == 0 { + return "", args + } + return "WHERE " + strings.Join(conditions, " AND "), args +} + +// FindInsights returns Advisor insights matching the filters, ordered by +// checked_at descending. When pageSize is greater than zero, the results are paginated. +func FindInsights(ctx context.Context, q *reform.Querier, filters InsightFilters, pageIndex, pageSize int) ([]*Insight, error) { + tail, args := insightConditions(q, filters) + tail += " ORDER BY checked_at DESC" + if pageSize > 0 { + tail += " LIMIT " + q.Placeholder(len(args)+1) + args = append(args, pageSize) + tail += " OFFSET " + q.Placeholder(len(args)+1) + args = append(args, pageIndex*pageSize) + } + + rows, err := q.WithContext(ctx).SelectAllFrom(InsightTable, tail, args...) + if err != nil { + return nil, fmt.Errorf("failed to select insights: %w", err) + } + + results := make([]*Insight, 0, len(rows)) + for _, r := range rows { + results = append(results, r.(*Insight)) //nolint:forcetypeassert + } + return results, nil +} + +// CountInsights returns the number of Advisor insights rows matching the filters. +func CountInsights(ctx context.Context, q *reform.Querier, filters InsightFilters) (int, error) { + where, args := insightConditions(q, filters) + + var count int + err := q.QueryRowContext(ctx, "SELECT count(*) FROM "+InsightTable.Name()+" "+where, args...).Scan(&count) + if err != nil { + return 0, fmt.Errorf("failed to count insights: %w", err) + } + return count, nil +} + +// FindInsightFilterValues returns the distinct service and node names present in the +// Advisor insights, each sorted alphabetically. +func FindInsightFilterValues(ctx context.Context, q *reform.Querier) ([]string, []string, error) { + distinct := func(column string) ([]string, error) { + rows, err := q.QueryContext(ctx, "SELECT DISTINCT "+column+" FROM "+InsightTable.Name()+ + " ORDER BY "+column) + if err != nil { + return nil, fmt.Errorf("failed to select distinct %s: %w", column, err) + } + defer rows.Close() //nolint:errcheck + + var values []string + for rows.Next() { + var value string + err = rows.Scan(&value) + if err != nil { + return nil, fmt.Errorf("failed to scan distinct %s: %w", column, err) + } + values = append(values, value) + } + return values, rows.Err() + } + + serviceNames, err := distinct("service_name") + if err != nil { + return nil, nil, err + } + nodeNames, err := distinct("node_name") + if err != nil { + return nil, nil, err + } + return serviceNames, nodeNames, nil +} + +// MarkInsightsRead sets the read state on the insights with the given IDs. +func MarkInsightsRead(ctx context.Context, q *reform.Querier, ids []string, isRead bool) error { + if len(ids) == 0 { + return nil + } + + args := []any{isRead} + placeholders := make([]string, 0, len(ids)) + for _, id := range ids { + placeholders = append(placeholders, q.Placeholder(len(args)+1)) + args = append(args, id) + } + + query := "UPDATE " + InsightTable.Name() + " SET is_read = " + q.Placeholder(1) + + " WHERE id IN (" + strings.Join(placeholders, ", ") + ")" + _, err := q.ExecContext(ctx, query, args...) + if err != nil { + return fmt.Errorf("failed to mark insights as read: %w", err) + } + return nil +} + +// MarkInsightsReadByFilters sets the read state on all insights matching the filters. +func MarkInsightsReadByFilters(ctx context.Context, q *reform.Querier, filters InsightFilters, isRead bool) error { + where, args := insightConditions(q, filters) + args = append(args, isRead) + + query := "UPDATE " + InsightTable.Name() + " SET is_read = " + q.Placeholder(len(args)) + " " + where + _, err := q.ExecContext(ctx, query, args...) + if err != nil { + return fmt.Errorf("failed to mark insights as read by filters: %w", err) + } + return nil +} + +// CleanupOldInsights deletes Advisor insights older than a specified date. +func CleanupOldInsights(ctx context.Context, q *reform.Querier, olderThan time.Time) error { + _, err := q.WithContext(ctx).DeleteFrom(InsightTable, " WHERE checked_at <= $1", olderThan) + return err +} diff --git a/managed/models/insight_helpers_test.go b/managed/models/insight_helpers_test.go new file mode 100644 index 00000000000..fad602fa797 --- /dev/null +++ b/managed/models/insight_helpers_test.go @@ -0,0 +1,310 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models_test + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/dialects/postgresql" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" + "github.com/percona/pmm/managed/utils/testdb" +) + +func TestInsights(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)) + q := db.Querier + + create := func(t *testing.T, cr *models.Insight) *models.Insight { + t.Helper() + require.NoError(t, models.CreateInsight(t.Context(), q, cr)) + return cr + } + + t.Run("create and find", func(t *testing.T) { + svc := "svc-find" + labels := map[string]string{"k": "v"} + cr := &models.Insight{ + CheckName: "c1", + Category: "performance", + Subcategory: "advisor", + Interval: models.Standard, + ServiceID: svc, + ServiceName: "find-me", + ServiceType: models.MySQLServiceType, + NodeID: "node-id", + NodeName: "node-find", + Status: models.CheckResultFailed, + Summary: "summary", + Description: "description", + Severity: models.Severity(common.Error), + CheckedAt: models.Now(), + } + require.NoError(t, cr.SetLabels(labels)) + create(t, cr) + require.NotEmpty(t, cr.ID) // generated by CreateInsight + + got, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, cr.ID, got[0].ID) + assert.Equal(t, models.CheckResultFailed, got[0].Status) + assert.Equal(t, models.Severity(common.Error), got[0].Severity) + assert.Equal(t, "node-find", got[0].NodeName) + + gotLabels, err := got[0].GetLabels() + require.NoError(t, err) + assert.Equal(t, labels, gotLabels) + }) + + t.Run("filters", func(t *testing.T) { + svc := "svc-filter" + create(t, &models.Insight{ + CheckName: "weak_pwd", Category: "security", ServiceID: svc, ServiceName: "ProdMySQL", + NodeName: "node-A", Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Critical), CheckedAt: models.Now(), + }) + create(t, &models.Insight{ + CheckName: "old_ver", Category: "configuration", ServiceID: svc, ServiceName: "devmysql", + NodeName: "node-B", Status: models.CheckResultOK, Summary: "s", + Severity: models.Severity(common.Info), CheckedAt: models.Now(), + }) + + // service_name: case-insensitive substring. + got, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, ServiceName: "prod"}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "ProdMySQL", got[0].ServiceName) + + // node_name: case-insensitive substring. + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, NodeName: "node-b"}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "node-B", got[0].NodeName) + + // category: exact. + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, Category: "security"}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + + // check_name: exact. + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, CheckName: "old_ver"}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + + // severity: exact. + sev := models.Severity(common.Critical) + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, Severity: &sev}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, models.Severity(common.Critical), got[0].Severity) + + // status. + st := models.CheckResultOK + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, Status: &st}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, models.CheckResultOK, got[0].Status) + + count, err := models.CountInsights(t.Context(), q, models.InsightFilters{ServiceID: svc}) + require.NoError(t, err) + assert.Equal(t, 2, count) + }) + + t.Run("time range filter", func(t *testing.T) { + svc := "svc-time" + create(t, &models.Insight{ + CheckName: "c", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now().Add(-48 * time.Hour), + }) + create(t, &models.Insight{ + CheckName: "c", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + + from := models.Now().Add(-24 * time.Hour) + got, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, From: &from}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + }) + + t.Run("pagination ordered by checked_at desc", func(t *testing.T) { + svc := "svc-page" + base := models.Now() + for i := range 5 { + create(t, &models.Insight{ + CheckName: fmt.Sprintf("c%d", i), ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: base.Add(time.Duration(i) * time.Minute), + }) + } + + // First page, newest first. + got, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc}, 0, 2) + require.NoError(t, err) + require.Len(t, got, 2) + assert.Equal(t, "c4", got[0].CheckName) + assert.Equal(t, "c3", got[1].CheckName) + + // Last page holds the single oldest row. + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc}, 2, 2) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "c0", got[0].CheckName) + + count, err := models.CountInsights(t.Context(), q, models.InsightFilters{ServiceID: svc}) + require.NoError(t, err) + assert.Equal(t, 5, count) + }) + + t.Run("mark read", func(t *testing.T) { + svc := "svc-mark" + cr1 := create(t, &models.Insight{ + CheckName: "c1", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + cr2 := create(t, &models.Insight{ + CheckName: "c2", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + + require.NoError(t, models.MarkInsightsRead(t.Context(), q, []string{cr1.ID}, true)) + + isRead := true + got, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, IsRead: &isRead}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, cr1.ID, got[0].ID) + + notRead := false + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, IsRead: ¬Read}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, cr2.ID, got[0].ID) + + // Unmark. + require.NoError(t, models.MarkInsightsRead(t.Context(), q, []string{cr1.ID}, false)) + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, IsRead: ¬Read}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 2) + }) + + t.Run("mark read by filters", func(t *testing.T) { + svc := "svc-mark-filters" + cr1 := create(t, &models.Insight{ + CheckName: "c1", Category: "security", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + create(t, &models.Insight{ + CheckName: "c2", Category: "configuration", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + + // Only the matching row is updated. + require.NoError(t, models.MarkInsightsReadByFilters(t.Context(), q, models.InsightFilters{ServiceID: svc, Category: "security"}, true)) + + isRead := true + got, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, IsRead: &isRead}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, cr1.ID, got[0].ID) + + // The read-state filter narrows the update to unread rows. + notRead := false + require.NoError(t, models.MarkInsightsReadByFilters(t.Context(), q, models.InsightFilters{ServiceID: svc, IsRead: ¬Read}, true)) + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, IsRead: &isRead}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 2) + + // Empty filters match every record. + require.NoError(t, models.MarkInsightsReadByFilters(t.Context(), q, models.InsightFilters{}, false)) + got, err = models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc, IsRead: &isRead}, 0, 0) + require.NoError(t, err) + require.Empty(t, got) + }) + + t.Run("cleanup old results", func(t *testing.T) { + svc := "svc-clean" + create(t, &models.Insight{ + CheckName: "old", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now().Add(-72 * time.Hour), + }) + create(t, &models.Insight{ + CheckName: "new", ServiceID: svc, ServiceName: "s", NodeName: "n", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + + require.NoError(t, models.CleanupOldInsights(t.Context(), q, models.Now().Add(-24*time.Hour))) + + got, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: svc}, 0, 0) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "new", got[0].CheckName) + }) + + t.Run("filter values", func(t *testing.T) { + // two results on the same service/node to prove deduplication + for range 2 { + create(t, &models.Insight{ + CheckName: "c", ServiceID: "svc-fv", ServiceName: "fv-svc-b", NodeName: "fv-node-b", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + } + create(t, &models.Insight{ + CheckName: "c", ServiceID: "svc-fv", ServiceName: "fv-svc-a", NodeName: "fv-node-a", + Status: models.CheckResultOK, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + }) + + serviceNames, nodeNames, err := models.FindInsightFilterValues(t.Context(), q) + require.NoError(t, err) + + // other subtests insert their own rows, so assert on ours only + assert.Equal(t, []string{"fv-svc-a", "fv-svc-b"}, filterByPrefix(serviceNames, "fv-svc-")) + assert.Equal(t, []string{"fv-node-a", "fv-node-b"}, filterByPrefix(nodeNames, "fv-node-")) + }) +} + +func filterByPrefix(values []string, prefix string) []string { + var result []string + for _, v := range values { + if strings.HasPrefix(v, prefix) { + result = append(result, v) + } + } + return result +} diff --git a/managed/models/insight_model.go b/managed/models/insight_model.go new file mode 100644 index 00000000000..e664e645b2d --- /dev/null +++ b/managed/models/insight_model.go @@ -0,0 +1,125 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +import ( + "time" + + "gopkg.in/reform.v1" +) + +//go:generate go tool reform + +// CheckResultStatus represents the outcome of an Advisor check run against a service. +type CheckResultStatus string + +// Available Advisor check result statuses. +const ( + // CheckResultOK means the check ran and found no issue. + CheckResultOK CheckResultStatus = "ok" + // CheckResultFailed means the check ran and detected an issue. + CheckResultFailed CheckResultStatus = "failed" + // CheckResultError means the check could not be executed. + CheckResultError CheckResultStatus = "error" +) + +// CheckTriggeredBy represents the actor that initiated an Advisor check run. +type CheckTriggeredBy string + +// Available Advisor check run initiators. +const ( + // CheckTriggeredByUser means the run was started by a user via the API or UI. + CheckTriggeredByUser CheckTriggeredBy = "user" + // CheckTriggeredByScheduler means the run was started by the built-in scheduler. + CheckTriggeredByScheduler CheckTriggeredBy = "scheduler" +) + +// Insight represents a single Advisor check run against a target persisted to history. +// +//reform:advisor_insights +type Insight struct { + ID string `reform:"id,pk"` + CheckName string `reform:"check_name"` + Category string `reform:"category"` + Subcategory string `reform:"subcategory"` + Interval Interval `reform:"interval"` + ServiceID string `reform:"service_id"` + ServiceName string `reform:"service_name"` + ServiceType ServiceType `reform:"service_type"` + NodeID string `reform:"node_id"` + NodeName string `reform:"node_name"` + Environment string `reform:"environment"` + Cluster string `reform:"cluster"` + ReplicationSet string `reform:"replication_set"` + Region string `reform:"region"` + AZ string `reform:"az"` + Status CheckResultStatus `reform:"status"` + Summary string `reform:"summary"` + Description string `reform:"description"` + Outcome string `reform:"outcome"` + ReadMoreURL string `reform:"read_more_url"` + Severity Severity `reform:"severity"` + Labels []byte `reform:"labels"` + CheckedAt time.Time `reform:"checked_at"` + IsRead bool `reform:"is_read"` + RunID string `reform:"run_id"` + TriggeredBy CheckTriggeredBy `reform:"triggered_by"` +} + +// BeforeInsert implements reform.BeforeInserter interface. +func (r *Insight) BeforeInsert() error { + if r.CheckedAt.IsZero() { + r.CheckedAt = Now() + } + if len(r.Labels) == 0 { + r.Labels = nil + } + return nil +} + +// BeforeUpdate implements reform.BeforeUpdater interface. +func (r *Insight) BeforeUpdate() error { + if len(r.Labels) == 0 { + r.Labels = nil + } + return nil +} + +// AfterFind implements reform.AfterFinder interface. +func (r *Insight) AfterFind() error { + r.CheckedAt = r.CheckedAt.UTC() + if len(r.Labels) == 0 { + r.Labels = nil + } + return nil +} + +// GetLabels decodes result labels. +func (r *Insight) GetLabels() (map[string]string, error) { + return getLabels(r.Labels) +} + +// SetLabels encodes result labels. +func (r *Insight) SetLabels(m map[string]string) error { + return setLabels(m, &r.Labels) +} + +// check interfaces. +var ( + _ reform.BeforeInserter = (*Insight)(nil) + _ reform.BeforeUpdater = (*Insight)(nil) + _ reform.AfterFinder = (*Insight)(nil) +) diff --git a/managed/models/insight_model_reform.go b/managed/models/insight_model_reform.go new file mode 100644 index 00000000000..22f48a74f53 --- /dev/null +++ b/managed/models/insight_model_reform.go @@ -0,0 +1,256 @@ +// Code generated by gopkg.in/reform.v1. DO NOT EDIT. + +package models + +import ( + "fmt" + "strings" + + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/parse" +) + +type insightTableType struct { + s parse.StructInfo + z []interface{} +} + +// Schema returns a schema name in SQL database (""). +func (v *insightTableType) Schema() string { + return v.s.SQLSchema +} + +// Name returns a view or table name in SQL database ("advisor_insights"). +func (v *insightTableType) Name() string { + return v.s.SQLName +} + +// Columns returns a new slice of column names for that view or table in SQL database. +func (v *insightTableType) Columns() []string { + return []string{ + "id", + "check_name", + "category", + "subcategory", + "interval", + "service_id", + "service_name", + "service_type", + "node_id", + "node_name", + "environment", + "cluster", + "replication_set", + "region", + "az", + "status", + "summary", + "description", + "outcome", + "read_more_url", + "severity", + "labels", + "checked_at", + "is_read", + "run_id", + "triggered_by", + } +} + +// NewStruct makes a new struct for that view or table. +func (v *insightTableType) NewStruct() reform.Struct { + return new(Insight) +} + +// NewRecord makes a new record for that table. +func (v *insightTableType) NewRecord() reform.Record { + return new(Insight) +} + +// PKColumnIndex returns an index of primary key column for that table in SQL database. +func (v *insightTableType) PKColumnIndex() uint { + return uint(v.s.PKFieldIndex) +} + +// InsightTable represents advisor_insights view or table in SQL database. +var InsightTable = &insightTableType{ + s: parse.StructInfo{ + Type: "Insight", + SQLName: "advisor_insights", + Fields: []parse.FieldInfo{ + {Name: "ID", Type: "string", Column: "id"}, + {Name: "CheckName", Type: "string", Column: "check_name"}, + {Name: "Category", Type: "string", Column: "category"}, + {Name: "Subcategory", Type: "string", Column: "subcategory"}, + {Name: "Interval", Type: "Interval", Column: "interval"}, + {Name: "ServiceID", Type: "string", Column: "service_id"}, + {Name: "ServiceName", Type: "string", Column: "service_name"}, + {Name: "ServiceType", Type: "ServiceType", Column: "service_type"}, + {Name: "NodeID", Type: "string", Column: "node_id"}, + {Name: "NodeName", Type: "string", Column: "node_name"}, + {Name: "Environment", Type: "string", Column: "environment"}, + {Name: "Cluster", Type: "string", Column: "cluster"}, + {Name: "ReplicationSet", Type: "string", Column: "replication_set"}, + {Name: "Region", Type: "string", Column: "region"}, + {Name: "AZ", Type: "string", Column: "az"}, + {Name: "Status", Type: "CheckResultStatus", Column: "status"}, + {Name: "Summary", Type: "string", Column: "summary"}, + {Name: "Description", Type: "string", Column: "description"}, + {Name: "Outcome", Type: "string", Column: "outcome"}, + {Name: "ReadMoreURL", Type: "string", Column: "read_more_url"}, + {Name: "Severity", Type: "Severity", Column: "severity"}, + {Name: "Labels", Type: "[]uint8", Column: "labels"}, + {Name: "CheckedAt", Type: "time.Time", Column: "checked_at"}, + {Name: "IsRead", Type: "bool", Column: "is_read"}, + {Name: "RunID", Type: "string", Column: "run_id"}, + {Name: "TriggeredBy", Type: "CheckTriggeredBy", Column: "triggered_by"}, + }, + PKFieldIndex: 0, + }, + z: new(Insight).Values(), +} + +// String returns a string representation of this struct or record. +func (s Insight) String() string { + res := make([]string, 26) + res[0] = "ID: " + reform.Inspect(s.ID, true) + res[1] = "CheckName: " + reform.Inspect(s.CheckName, true) + res[2] = "Category: " + reform.Inspect(s.Category, true) + res[3] = "Subcategory: " + reform.Inspect(s.Subcategory, true) + res[4] = "Interval: " + reform.Inspect(s.Interval, true) + res[5] = "ServiceID: " + reform.Inspect(s.ServiceID, true) + res[6] = "ServiceName: " + reform.Inspect(s.ServiceName, true) + res[7] = "ServiceType: " + reform.Inspect(s.ServiceType, true) + res[8] = "NodeID: " + reform.Inspect(s.NodeID, true) + res[9] = "NodeName: " + reform.Inspect(s.NodeName, true) + res[10] = "Environment: " + reform.Inspect(s.Environment, true) + res[11] = "Cluster: " + reform.Inspect(s.Cluster, true) + res[12] = "ReplicationSet: " + reform.Inspect(s.ReplicationSet, true) + res[13] = "Region: " + reform.Inspect(s.Region, true) + res[14] = "AZ: " + reform.Inspect(s.AZ, true) + res[15] = "Status: " + reform.Inspect(s.Status, true) + res[16] = "Summary: " + reform.Inspect(s.Summary, true) + res[17] = "Description: " + reform.Inspect(s.Description, true) + res[18] = "Outcome: " + reform.Inspect(s.Outcome, true) + res[19] = "ReadMoreURL: " + reform.Inspect(s.ReadMoreURL, true) + res[20] = "Severity: " + reform.Inspect(s.Severity, true) + res[21] = "Labels: " + reform.Inspect(s.Labels, true) + res[22] = "CheckedAt: " + reform.Inspect(s.CheckedAt, true) + res[23] = "IsRead: " + reform.Inspect(s.IsRead, true) + res[24] = "RunID: " + reform.Inspect(s.RunID, true) + res[25] = "TriggeredBy: " + reform.Inspect(s.TriggeredBy, true) + return strings.Join(res, ", ") +} + +// Values returns a slice of struct or record field values. +// Returned interface{} values are never untyped nils. +func (s *Insight) Values() []interface{} { + return []interface{}{ + s.ID, + s.CheckName, + s.Category, + s.Subcategory, + s.Interval, + s.ServiceID, + s.ServiceName, + s.ServiceType, + s.NodeID, + s.NodeName, + s.Environment, + s.Cluster, + s.ReplicationSet, + s.Region, + s.AZ, + s.Status, + s.Summary, + s.Description, + s.Outcome, + s.ReadMoreURL, + s.Severity, + s.Labels, + s.CheckedAt, + s.IsRead, + s.RunID, + s.TriggeredBy, + } +} + +// Pointers returns a slice of pointers to struct or record fields. +// Returned interface{} values are never untyped nils. +func (s *Insight) Pointers() []interface{} { + return []interface{}{ + &s.ID, + &s.CheckName, + &s.Category, + &s.Subcategory, + &s.Interval, + &s.ServiceID, + &s.ServiceName, + &s.ServiceType, + &s.NodeID, + &s.NodeName, + &s.Environment, + &s.Cluster, + &s.ReplicationSet, + &s.Region, + &s.AZ, + &s.Status, + &s.Summary, + &s.Description, + &s.Outcome, + &s.ReadMoreURL, + &s.Severity, + &s.Labels, + &s.CheckedAt, + &s.IsRead, + &s.RunID, + &s.TriggeredBy, + } +} + +// View returns View object for that struct. +func (s *Insight) View() reform.View { + return InsightTable +} + +// Table returns Table object for that record. +func (s *Insight) Table() reform.Table { + return InsightTable +} + +// PKValue returns a value of primary key for that record. +// Returned interface{} value is never untyped nil. +func (s *Insight) PKValue() interface{} { + return s.ID +} + +// PKPointer returns a pointer to primary key field for that record. +// Returned interface{} value is never untyped nil. +func (s *Insight) PKPointer() interface{} { + return &s.ID +} + +// HasPK returns true if record has non-zero primary key set, false otherwise. +func (s *Insight) HasPK() bool { + return s.ID != InsightTable.z[InsightTable.s.PKFieldIndex] +} + +// SetPK sets record primary key, if possible. +// +// Deprecated: prefer direct field assignment where possible: s.ID = pk. +func (s *Insight) SetPK(pk interface{}) { + reform.SetPK(s, pk) +} + +// check interfaces +var ( + _ reform.View = InsightTable + _ reform.Struct = (*Insight)(nil) + _ reform.Table = InsightTable + _ reform.Record = (*Insight)(nil) + _ fmt.Stringer = (*Insight)(nil) +) + +func init() { + parse.AssertUpToDate(&InsightTable.s, new(Insight)) +} diff --git a/managed/models/settings.go b/managed/models/settings.go index d76fc4552cf..b82474d4356 100644 --- a/managed/models/settings.go +++ b/managed/models/settings.go @@ -20,6 +20,8 @@ import ( "time" "github.com/AlekSi/pointer" + + "github.com/percona/pmm/managed/pi/common" ) // Default values for settings. These values are used when settings are not set. @@ -33,6 +35,8 @@ const ( AzureDiscoverEnabledDefault = false AccessControlEnabledDefault = false InternalPgQANEnabledDefault = false + AdvisorNotificationsEnabledDefault = false + AdvisorNotificationSeverityDefault = common.Error awsPartitionID = "aws" ) @@ -53,8 +57,6 @@ func (r *MetricsResolutions) Scan(src any) error { return jsonScan(r, src) } type Advisors struct { // Advisor checks disabled, false by default. Enabled *bool `json:"enabled"` - // List of disabled advisors - DisabledAdvisors []string `json:"disabled_advisors"` // Advisor run intervals AdvisorRunIntervals AdvisorsRunIntervals `json:"advisor_run_intervals"` } @@ -76,6 +78,8 @@ type Settings struct { DataRetention time.Duration `json:"data_retention"` + AdvisorHistoryRetention time.Duration `json:"advisor_history_retention"` + AWSPartitions []string `json:"aws_partitions"` AWSInstanceChecked bool `json:"aws_instance_checked"` @@ -96,6 +100,15 @@ type Settings struct { Enabled *bool `json:"enabled"` } `json:"alerting"` + // AdvisorNotifications controls emailing Advisor check results to the configured recipients. + AdvisorNotifications struct { + Enabled *bool `json:"enabled"` + // SeverityThreshold is the least-severe level that triggers a notification. + SeverityThreshold common.Severity `json:"severity_threshold"` + // EmailAddresses is the recipient list the run summaries are sent to. + EmailAddresses []string `json:"email_addresses"` + } `json:"advisor_notifications"` + Azurediscover struct { Enabled *bool `json:"enabled"` } `json:"azure"` @@ -128,6 +141,14 @@ func (s *Settings) IsAlertingEnabled() bool { return AlertingEnabledDefault } +// IsAdvisorNotificationsEnabled returns true if Advisor email notifications are enabled. +func (s *Settings) IsAdvisorNotificationsEnabled() bool { + if s.AdvisorNotifications.Enabled != nil { + return *s.AdvisorNotifications.Enabled + } + return AdvisorNotificationsEnabledDefault +} + // IsTelemetryEnabled returns true if telemetry is enabled. func (s *Settings) IsTelemetryEnabled() bool { if s.Telemetry.Enabled != nil { @@ -216,6 +237,14 @@ func (s *Settings) fillDefaults() { s.DataRetention = 30 * 24 * time.Hour //nolint:mnd } + if s.AdvisorHistoryRetention == 0 { + s.AdvisorHistoryRetention = 30 * 24 * time.Hour //nolint:mnd + } + + if s.AdvisorNotifications.SeverityThreshold == common.Unknown { + s.AdvisorNotifications.SeverityThreshold = AdvisorNotificationSeverityDefault + } + if len(s.AWSPartitions) == 0 { s.AWSPartitions = []string{awsPartitionID} } diff --git a/managed/models/settings_helpers.go b/managed/models/settings_helpers.go index a48c5d74510..50a4a002767 100644 --- a/managed/models/settings_helpers.go +++ b/managed/models/settings_helpers.go @@ -19,12 +19,14 @@ import ( "encoding/json" "errors" "fmt" + "net/mail" "time" "github.com/AlekSi/pointer" "github.com/google/uuid" "gopkg.in/reform.v1" + "github.com/percona/pmm/managed/pi/common" "github.com/percona/pmm/managed/utils/validators" ) @@ -60,6 +62,15 @@ type ChangeSettingsParams struct { DataRetention time.Duration + AdvisorHistoryRetention time.Duration + + // Enable Advisor email notifications. + EnableAdvisorNotifications *bool + // Least-severe level that triggers an Advisor notification. Unknown means "do not change". + AdvisorNotificationSeverityThreshold common.Severity + // Recipients of Advisor notifications. Nil means "do not change"; an empty non-nil slice clears them. + AdvisorNotificationEmailAddresses []string + // List of AWS partitions to use. If empty - default partitions will be used. If nil - no changes will be made. AWSPartitions []string @@ -70,10 +81,6 @@ type ChangeSettingsParams struct { EnableNomad *bool - // List of Advisor checks to disable - DisableAdvisorChecks []string - // List of Advisor checks to enable - EnableAdvisorChecks []string // Advisors run intervals AdvisorsRunInterval AdvisorsRunIntervals @@ -166,6 +173,22 @@ func UpdateSettings(q reform.DBTX, params *ChangeSettingsParams) (*Settings, err settings.DataRetention = params.DataRetention } + if params.AdvisorHistoryRetention != 0 { + settings.AdvisorHistoryRetention = params.AdvisorHistoryRetention + } + + if params.EnableAdvisorNotifications != nil { + settings.AdvisorNotifications.Enabled = params.EnableAdvisorNotifications + } + + if params.AdvisorNotificationSeverityThreshold != common.Unknown { + settings.AdvisorNotifications.SeverityThreshold = params.AdvisorNotificationSeverityThreshold + } + + if params.AdvisorNotificationEmailAddresses != nil { + settings.AdvisorNotifications.EmailAddresses = deduplicateStrings(params.AdvisorNotificationEmailAddresses) + } + if params.AWSPartitions != nil { settings.AWSPartitions = deduplicateStrings(params.AWSPartitions) } @@ -192,25 +215,6 @@ func UpdateSettings(q reform.DBTX, params *ChangeSettingsParams) (*Settings, err settings.SaaS.AdvisorRunIntervals.FrequentInterval = params.AdvisorsRunInterval.FrequentInterval } - if len(params.DisableAdvisorChecks) != 0 { - settings.SaaS.DisabledAdvisors = deduplicateStrings(append(settings.SaaS.DisabledAdvisors, params.DisableAdvisorChecks...)) - } - - if len(params.EnableAdvisorChecks) != 0 { - m := make(map[string]struct{}, len(params.EnableAdvisorChecks)) - for _, p := range params.EnableAdvisorChecks { - m[p] = struct{}{} - } - - var res []string - for _, c := range settings.SaaS.DisabledAdvisors { - if _, ok := m[c]; !ok { - res = append(res, c) - } - } - settings.SaaS.DisabledAdvisors = res - } - if params.EnableVMCache != nil { settings.VictoriaMetrics.CacheEnabled = params.EnableVMCache } @@ -274,7 +278,7 @@ func ValidateSettings(params *ChangeSettingsParams) error { case validators.MinDurationError: return fmt.Errorf("%s: minimal resolution is 1s", v.fieldName) default: - return fmt.Errorf("%s: unknown error: %w", v.fieldName, err) + return fmt.Errorf("%s: %w", v.fieldName, err) } } } @@ -300,7 +304,7 @@ func ValidateSettings(params *ChangeSettingsParams) error { case validators.MinDurationError: return fmt.Errorf("%s: minimal resolution is 1s", v.fieldName) default: - return fmt.Errorf("%s: unknown error: %w", v.fieldName, err) + return fmt.Errorf("%s: %w", v.fieldName, err) } } } @@ -314,16 +318,80 @@ func ValidateSettings(params *ChangeSettingsParams) error { case validators.MinDurationError: return errors.New("data_retention: minimal resolution is 24h") default: - return fmt.Errorf("data_retention: unknown error: %w", err) + return fmt.Errorf("data_retention: %w", err) + } + } + } + + if params.AdvisorHistoryRetention != 0 { + _, err := validators.ValidateDataRetention(params.AdvisorHistoryRetention) + if err != nil { + switch err.(type) { //nolint:errorlint + case validators.DurationNotAllowedError: + return errors.New("advisor_history_retention: should be a natural number of days") + case validators.MinDurationError: + return errors.New("advisor_history_retention: minimal resolution is 24h") + default: + return fmt.Errorf("advisor_history_retention: %w", err) } } } - err := validators.ValidateAWSPartitions(params.AWSPartitions) + if params.AdvisorNotificationSeverityThreshold != common.Unknown { + err := validateAdvisorSeverityThreshold(params.AdvisorNotificationSeverityThreshold) + if err != nil { + return err + } + } + + err := validateAdvisorNotificationEmailAddresses(params.AdvisorNotificationEmailAddresses) if err != nil { return err } + err = validators.ValidateAWSPartitions(params.AWSPartitions) + if err != nil { + return err + } + + return nil +} + +// validateAdvisorSeverityThreshold accepts only the severities advisors use; +// the remaining common.Severity levels are retired for advisors. +func validateAdvisorSeverityThreshold(s common.Severity) error { + switch s { + case common.Critical, common.Error, common.Warning, common.Info: + return nil + default: + return fmt.Errorf("advisor_notification_severity_threshold: unsupported severity level: %s", s) + } +} + +// maxAdvisorNotificationEmailAddresses caps the recipient list so a single settings change cannot +// turn every run completion into a mass mailing. +const maxAdvisorNotificationEmailAddresses = 20 + +// validateAdvisorNotificationEmailAddresses accepts a nil or empty list ("do not change" and +// "clear" respectively) and otherwise requires every entry to be a bare, parseable address. +func validateAdvisorNotificationEmailAddresses(addresses []string) error { + if len(addresses) > maxAdvisorNotificationEmailAddresses { + return fmt.Errorf("advisor_notification_email_addresses: at most %d addresses are allowed, got %d", + maxAdvisorNotificationEmailAddresses, len(addresses)) + } + + for _, a := range addresses { + parsed, err := mail.ParseAddress(a) + if err != nil { + return fmt.Errorf("advisor_notification_email_addresses: invalid address '%s'", a) + } + // ParseAddress also accepts `Name `; the sender only needs the address itself, and + // accepting display names here would make the stored list ambiguous. + if parsed.Address != a { + return fmt.Errorf("advisor_notification_email_addresses: expected a bare email address, got '%s'", a) + } + } + return nil } diff --git a/managed/models/settings_helpers_test.go b/managed/models/settings_helpers_test.go index cf6b7f075c0..571476ca815 100644 --- a/managed/models/settings_helpers_test.go +++ b/managed/models/settings_helpers_test.go @@ -17,6 +17,7 @@ package models_test import ( "errors" + "fmt" "testing" "time" @@ -24,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" "github.com/percona/pmm/managed/utils/testdb" ) @@ -43,8 +45,9 @@ func TestSettings(t *testing.T) { MR: 10 * time.Second, LR: time.Minute, }, - DataRetention: 30 * 24 * time.Hour, - AWSPartitions: []string{"aws"}, + DataRetention: 30 * 24 * time.Hour, + AdvisorHistoryRetention: 30 * 24 * time.Hour, + AWSPartitions: []string{"aws"}, SaaS: models.Advisors{ AdvisorRunIntervals: models.AdvisorsRunIntervals{ StandardInterval: 24 * time.Hour, @@ -55,6 +58,7 @@ func TestSettings(t *testing.T) { DefaultRoleID: 1, EncryptedItems: actual.EncryptedItems, } + expected.AdvisorNotifications.SeverityThreshold = models.AdvisorNotificationSeverityDefault assert.Equal(t, expected, actual) }) @@ -68,8 +72,9 @@ func TestSettings(t *testing.T) { MR: 10 * time.Second, LR: time.Minute, }, - DataRetention: 30 * 24 * time.Hour, - AWSPartitions: []string{"aws"}, + DataRetention: 30 * 24 * time.Hour, + AdvisorHistoryRetention: 30 * 24 * time.Hour, + AWSPartitions: []string{"aws"}, SaaS: models.Advisors{ AdvisorRunIntervals: models.AdvisorsRunIntervals{ StandardInterval: 24 * time.Hour, @@ -78,9 +83,68 @@ func TestSettings(t *testing.T) { }, }, } + expected.AdvisorNotifications.SeverityThreshold = models.AdvisorNotificationSeverityDefault assert.Equal(t, expected, s) }) + t.Run("AdvisorNotifications", func(t *testing.T) { + settings, err := models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ + EnableAdvisorNotifications: new(true), + AdvisorNotificationSeverityThreshold: common.Warning, + AdvisorHistoryRetention: 48 * time.Hour, + }) + require.NoError(t, err) + assert.True(t, settings.IsAdvisorNotificationsEnabled()) + assert.Equal(t, common.Warning, settings.AdvisorNotifications.SeverityThreshold) + assert.Equal(t, 48*time.Hour, settings.AdvisorHistoryRetention) + + // An out-of-range severity threshold is rejected. + _, err = models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ + AdvisorNotificationSeverityThreshold: common.Severity(42), + }) + require.ErrorContains(t, err, "advisor_notification_severity_threshold") + }) + + t.Run("AdvisorNotificationEmailAddresses", func(t *testing.T) { + settings, err := models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ + AdvisorNotificationEmailAddresses: []string{"a@example.com", "b@example.com", "a@example.com"}, + }) + require.NoError(t, err) + assert.Equal(t, []string{"a@example.com", "b@example.com"}, settings.AdvisorNotifications.EmailAddresses) + + // Nil leaves the stored recipients alone. + settings, err = models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{}) + require.NoError(t, err) + assert.Equal(t, []string{"a@example.com", "b@example.com"}, settings.AdvisorNotifications.EmailAddresses) + + // An empty non-nil slice clears them. + settings, err = models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ + AdvisorNotificationEmailAddresses: []string{}, + }) + require.NoError(t, err) + assert.Empty(t, settings.AdvisorNotifications.EmailAddresses) + + _, err = models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ + AdvisorNotificationEmailAddresses: []string{"not-an-email"}, + }) + require.ErrorContains(t, err, "invalid address 'not-an-email'") + + // A display name would make the stored list ambiguous for the sender. + _, err = models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ + AdvisorNotificationEmailAddresses: []string{"DBA "}, + }) + require.ErrorContains(t, err, "expected a bare email address") + + tooMany := make([]string, 21) + for i := range tooMany { + tooMany[i] = fmt.Sprintf("a%d@example.com", i) + } + _, err = models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ + AdvisorNotificationEmailAddresses: tooMany, + }) + require.ErrorContains(t, err, "at most 20 addresses are allowed") + }) + t.Run("Validation", func(t *testing.T) { t.Run("AWSPartitions", func(t *testing.T) { s := &models.ChangeSettingsParams{ @@ -248,27 +312,6 @@ func TestSettings(t *testing.T) { assert.Empty(t, ns.Telemetry.UUID) }) - t.Run("disable checks", func(t *testing.T) { - disChecks := []string{"one", "two", "three"} - - ns, err := models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{ - DisableAdvisorChecks: disChecks, - }) - require.NoError(t, err) - assert.ElementsMatch(t, ns.SaaS.DisabledAdvisors, disChecks) - }) - - t.Run("enable checks", func(t *testing.T) { - disChecks := []string{"one", "two", "three"} - - _, err := models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{DisableAdvisorChecks: disChecks}) - require.NoError(t, err) - - ns, err := models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{EnableAdvisorChecks: []string{"two"}}) - require.NoError(t, err) - assert.ElementsMatch(t, ns.SaaS.DisabledAdvisors, []string{"one", "three"}) - }) - t.Run("enable azure discover", func(t *testing.T) { s, err := models.UpdateSettings(sqlDB, &models.ChangeSettingsParams{EnableAzurediscover: new(false)}) require.NoError(t, err) diff --git a/managed/pi/check/advisor.go b/managed/pi/check/advisor.go index ed81015b405..6c4944ecce3 100644 --- a/managed/pi/check/advisor.go +++ b/managed/pi/check/advisor.go @@ -15,108 +15,11 @@ package check -import ( - "errors" - "fmt" - "io" - - "gopkg.in/yaml.v3" -) - -// Advisor represents group of checks with the common idea. +// Advisor is an in-memory grouping of checks that share the same +// (Category, Subcategory) pair. It is synthesized at load time from the checks +// themselves and is not authored anywhere. type Advisor struct { - Version uint32 `yaml:"version"` - Name string `yaml:"name"` - Summary string `yaml:"summary"` - Description string `yaml:"description"` - Category string `yaml:"category"` - Checks []Check `yaml:"checks"` -} - -// ParseAdvisors returns a slice of validated advisors parsed from YAML passed via a reader. -// It can handle multi-document YAMLs: parsing result will be a single slice -// that contains advisors from every parsed document. -func ParseAdvisors(reader io.Reader, params *ParseParams) ([]Advisor, error) { - if params == nil { - params = &ParseParams{} - } - - d := yaml.NewDecoder(reader) - d.KnownFields(params.DisallowUnknownFields) - - type advisors struct { - Advisors []Advisor `yaml:"advisors"` - } - - var res []Advisor - - for { - var c advisors - - err := d.Decode(&c) //nolint:musttag - if err != nil { - if errors.Is(err, io.EOF) { - return res, nil - } - - return nil, fmt.Errorf("failed to parse advisors: %w", err) - } - - for _, advisor := range c.Advisors { - err := advisor.Validate() - if err != nil { - if params.DisallowInvalidChecks { - return nil, err - } - - continue // skip invalid advisors - } - - res = append(res, advisor) - } - } -} - -// Validate validates an advisor. -func (a *Advisor) Validate() error { - if a.Version != 1 { - return fmt.Errorf("unexpected version %d", a.Version) - } - - if !nameRE.MatchString(a.Name) { - return errors.New("invalid advisor name") - } - - if a.Summary == "" { - return errors.New("summary is empty") - } - - if a.Description == "" { - return errors.New("description is empty") - } - - if a.Category == "" { - return errors.New("category is empty") - } - - checkNames := make(map[string]struct{}, len(a.Checks)) - for _, check := range a.Checks { - err := check.Validate() - if err != nil { - return err - } - - if check.Advisor != a.Name { - return fmt.Errorf("advisor name '%s' doesn't match name '%s' specified in corresponding check '%s'", - a.Name, check.Advisor, check.Name) - } - - if _, ok := checkNames[check.Name]; ok { - return fmt.Errorf("check name collision `%s` detected in '%s' advisor", check.Name, a.Name) - } - - checkNames[check.Name] = struct{}{} - } - - return nil + Category string + Subcategory string + Checks []Check } diff --git a/managed/pi/check/check.go b/managed/pi/check/check.go index ca25b468a1a..fe9fee0c549 100644 --- a/managed/pi/check/check.go +++ b/managed/pi/check/check.go @@ -179,35 +179,25 @@ func (t Type) Validate() error { } } -func isTypeSupportedByV1(t Type) bool { - switch t { - case MySQLShow, MySQLSelect, PostgreSQLShow, PostgreSQLSelect, MongoDBGetParameter, - MongoDBBuildInfo, MongoDBGetCmdLineOpts, MongoDBReplSetGetStatus, MongoDBGetDiagnosticData: - return true - default: - return false - } -} - -// Supported DB families. +// Supported DB technologies. const ( - MySQL = Family("MYSQL") - PostgreSQL = Family("POSTGRESQL") - MongoDB = Family("MONGODB") + MySQL = Technology("MYSQL") + PostgreSQL = Technology("POSTGRESQL") + MongoDB = Technology("MONGODB") ) -// Family represents monitored service family. -type Family string +// Technology represents monitored service technology. +type Technology string -// Validate validates check family. -func (f Family) Validate() error { +// Validate validates check technology. +func (f Technology) Validate() error { switch f { case MySQL, PostgreSQL, MongoDB: return nil case "": - return errors.New("check family is empty") + return errors.New("check technology is empty") default: - return fmt.Errorf("unknown check family: %s", f) + return fmt.Errorf("unknown check technology: %s", f) } } @@ -244,9 +234,9 @@ const ( // Query represents DB query of specified type. type Query struct { - Query string - Type Type - Parameters map[Parameter]string + Query string `json:"query"` + Type Type `json:"type"` + Parameters map[Parameter]string `json:"parameters,omitempty"` } // Validate validates query. @@ -264,56 +254,57 @@ func (q Query) Validate() error { return validateQueryParameters(q.Type, q.Parameters) } -// Check represents advisor check structure. Fields marked with v1 should not be used for version 2, and vice versa. -type Check struct { - Version uint32 `yaml:"version"` - Name string `yaml:"name"` - Summary string `yaml:"summary"` - Description string `yaml:"description"` - Advisor string `yaml:"advisor"` - Category string `yaml:"category,omitempty"` // deprecated - Type Type `yaml:"type,omitempty"` // for v1 - Family Family `yaml:"family,omitempty"` // for v2, emulated via GetFamily for v1 - Interval Interval `yaml:"interval,omitempty"` - Query string `yaml:"query,omitempty"` // for v1 - Queries []Query `yaml:"queries,omitempty"` // for v2 - Script string `yaml:"script"` -} - -// GetFamily returns check family for both V1 and V2 check formats. -func (c *Check) GetFamily() Family { - switch c.Version { - case 1: - switch c.Type { - case MySQLSelect, MySQLShow: - return MySQL - - case PostgreSQLSelect, PostgreSQLShow: - return PostgreSQL - - case MongoDBGetParameter, MongoDBBuildInfo, MongoDBGetCmdLineOpts, - MongoDBReplSetGetStatus, MongoDBGetDiagnosticData: - return MongoDB +// Supported advisor check format versions. Version 1 is deprecated and rejected. +const ( + // MinSupportedVersion is the minimum supported advisor check format version. + MinSupportedVersion uint32 = 2 + // MaxSupportedVersion is the maximum supported advisor check format version. + MaxSupportedVersion uint32 = 2 +) - case MetricsInstant, MetricsRange, ClickHouseSelect: - return "" // Unsupported query types for V1, check is invalid - } - case 2: //nolint:mnd - return c.Family - } +// UserCheckNamePrefix is the name prefix reserved for user-authored checks. +// Check names are primary identifiers, so the prefix keeps the user namespace +// from ever colliding with Percona-shipped checks: user checks must carry it, +// Percona checks must not (enforced by pi-validator). +const UserCheckNamePrefix = "custom_" - return "" +// Check represents a self-contained advisor check. Category and Subcategory are +// authored as exact display strings; the advisor "group" is the set of distinct +// (Category, Subcategory) pairs across all loaded checks. +type Check struct { + Version uint32 `yaml:"version"` + Name string `yaml:"name"` + Summary string `yaml:"summary"` + Description string `yaml:"description"` + Category string `yaml:"category"` + Subcategory string `yaml:"subcategory"` + Technology Technology `yaml:"technology"` + Interval Interval `yaml:"interval,omitempty"` + Queries []Query `yaml:"queries"` + Script string `yaml:"script"` + // UserDefined is true for checks authored by a user and stored in the DB, + // false for Percona-shipped checks loaded from disk. It is not part of the + // check's YAML/JSON representation. + UserDefined bool `yaml:"-" json:"-"` } // Validate validates check for minimal correctness. func (c *Check) Validate() error { - var err error - if !nameRE.MatchString(c.Name) { return errors.New("invalid check name") } - err = c.Interval.Validate() + if c.Version < MinSupportedVersion { + return fmt.Errorf("check %s: format version %d is no longer supported, minimum supported version is %d", + c.Name, c.Version, MinSupportedVersion) + } + + if c.Version > MaxSupportedVersion { + return fmt.Errorf("check %s: format version %d is not supported, maximum supported version is %d", + c.Name, c.Version, MaxSupportedVersion) + } + + err := c.Interval.Validate() if err != nil { return err } @@ -335,70 +326,20 @@ func (c *Check) Validate() error { return errors.New("description is empty") } - if c.Advisor == "" { - return errors.New("advisor name is missing") + if c.Category == "" { + return errors.New("category is empty") } - switch c.Version { - case 1: - return c.validateV1() - case 2: //nolint:mnd - return c.validateV2() - default: - return fmt.Errorf("unexpected version %d", c.Version) + if c.Subcategory == "" { + return errors.New("subcategory is empty") } -} - -func (c *Check) validateV1() error { - var err error - err = c.Type.Validate() + err = c.Technology.Validate() if err != nil { return err } - if !isTypeSupportedByV1(c.Type) { - return fmt.Errorf("check type '%s' is not supprted in V1", c.Type) - } - - err = validateQuery(c.Type, c.Query) - if err != nil { - return err - } - - if c.Family != "" { - return errors.New("field 'family' is part of check format version 2 and can't be used in version 1") - } - - if len(c.Queries) != 0 { - return errors.New("field 'queries' is part of check format version 2 and can't be used in version 1") - } - - return nil -} - -func (c *Check) validateV2() error { - var err error - - err = c.Family.Validate() - if err != nil { - return err - } - - err = c.validateQueries() - if err != nil { - return err - } - - if c.Type != "" { - return errors.New("field 'type' is part of check format version 1 and can't be used in version 2") - } - - if c.Query != "" { - return errors.New("field 'query' is part of check format version 1 and can't be used in version 2") - } - - return nil + return c.validateQueries() } func (c *Check) validateScript() error { @@ -513,19 +454,19 @@ func (c *Check) validateQueries() error { } } - switch c.Family { + switch c.Technology { case MySQL: - return checkQueryForCompatibilityWithMySQLFamily(c.Queries) + return checkQueryForCompatibilityWithMySQLTechnology(c.Queries) case PostgreSQL: - return checkQueryForCompatibilityWithPostgreSQLFamily(c.Queries) + return checkQueryForCompatibilityWithPostgreSQLTechnology(c.Queries) case MongoDB: - return checkQueryCompatibilityWithMongoDBFamily(c.Queries) + return checkQueryCompatibilityWithMongoDBTechnology(c.Queries) default: - return fmt.Errorf("unknown check family: %s", c.Family) + return fmt.Errorf("unknown check technology: %s", c.Technology) } } -func checkQueryForCompatibilityWithMySQLFamily(queries []Query) error { +func checkQueryForCompatibilityWithMySQLTechnology(queries []Query) error { for _, q := range queries { switch q.Type { case MySQLShow: @@ -534,14 +475,14 @@ func checkQueryForCompatibilityWithMySQLFamily(queries []Query) error { case MetricsRange: case ClickHouseSelect: default: - return fmt.Errorf("unsupported query type '%s' for mySQL family", q.Type) + return fmt.Errorf("unsupported query type '%s' for mySQL technology", q.Type) } } return nil } -func checkQueryForCompatibilityWithPostgreSQLFamily(queries []Query) error { +func checkQueryForCompatibilityWithPostgreSQLTechnology(queries []Query) error { for _, q := range queries { switch q.Type { case PostgreSQLShow: @@ -550,14 +491,14 @@ func checkQueryForCompatibilityWithPostgreSQLFamily(queries []Query) error { case MetricsRange: case ClickHouseSelect: default: - return fmt.Errorf("unsupported query type '%s' for postgreSQL family", q.Type) + return fmt.Errorf("unsupported query type '%s' for postgreSQL technology", q.Type) } } return nil } -func checkQueryCompatibilityWithMongoDBFamily(queries []Query) error { +func checkQueryCompatibilityWithMongoDBTechnology(queries []Query) error { for _, q := range queries { switch q.Type { case MongoDBGetParameter: @@ -569,7 +510,7 @@ func checkQueryCompatibilityWithMongoDBFamily(queries []Query) error { case MetricsRange: case ClickHouseSelect: default: - return fmt.Errorf("unsupported query type '%s' for mongoDB family", q.Type) + return fmt.Errorf("unsupported query type '%s' for mongoDB technology", q.Type) } } diff --git a/managed/pi/starlark/starlark.go b/managed/pi/starlark/starlark.go index 865b834366a..f1f151aea94 100644 --- a/managed/pi/starlark/starlark.go +++ b/managed/pi/starlark/starlark.go @@ -113,9 +113,16 @@ func (env *Env) run(funcName string, args starlark.Tuple, threadName string, pri } if printFunc != nil { thread.Print = func(t *starlark.Thread, msg string) { - // make it look similar to starlark.CallStack.String + // check -> function:line:col -> printed message, so the output + // is easy to trace back to the originating line fr := t.CallFrame(1) - printFunc("thread "+t.Name+":", fr.Pos.String()+":", "in", fr.Name+":", msg) + printFunc( + fr.Pos.Filename(), + "->", + fmt.Sprintf("%s:%d:%d", fr.Name, fr.Pos.Line, fr.Pos.Col), + "->", + msg, + ) } } @@ -124,7 +131,7 @@ func (env *Env) run(funcName string, args starlark.Tuple, threadName string, pri var eErr *starlark.EvalError if ok := errors.As(err, &eErr); ok { // tweak message, but keep original type, callstack, and cause - eErr.Msg = fmt.Sprintf("thread %s: failed to init script: %s\n%s", threadName, eErr.Msg, eErr.CallStack) + eErr.Msg = fmt.Sprintf("failed to init script: %s\n%s", eErr.Msg, eErr.CallStack) return nil, eErr } @@ -135,7 +142,7 @@ func (env *Env) run(funcName string, args starlark.Tuple, threadName string, pri fn := globals[funcName] if fn == nil { - return nil, fmt.Errorf("thread %s: function %s is not defined", threadName, funcName) + return nil, fmt.Errorf("function %s is not defined", funcName) } v, err := starlark.Call(thread, fn, args, nil) @@ -143,7 +150,7 @@ func (env *Env) run(funcName string, args starlark.Tuple, threadName string, pri var eErr *starlark.EvalError if ok := errors.As(err, &eErr); ok { // tweak message, but keep original type, callstack, and cause - eErr.Msg = fmt.Sprintf("thread %s: failed to execute function %s: %s\n%s", threadName, funcName, eErr.Msg, eErr.CallStack) + eErr.Msg = fmt.Sprintf("failed to execute function %s: %s\n%s", funcName, eErr.Msg, eErr.CallStack) return nil, eErr } @@ -274,6 +281,16 @@ func convertResult(m map[string]any) (*check.Result, error) { return nil, err } + // parse severity here, where the raw string is still available for the error message + parsedSeverity := common.ParseSeverity(severity) + err = parsedSeverity.Validate() + if err != nil { + if severity == "" { + return nil, errors.New("severity is required") + } + return nil, fmt.Errorf("unknown severity level: '%s'", severity) + } + var labels map[string]string l, ok := m["labels"] @@ -298,7 +315,7 @@ func convertResult(m map[string]any) (*check.Result, error) { Summary: summary, Description: description, ReadMoreURL: readMoreURL, - Severity: common.ParseSeverity(severity), + Severity: parsedSeverity, Labels: labels, } diff --git a/managed/services/checks/checks.go b/managed/services/checks/checks.go index ec7c9336ae3..36aeb410aa0 100644 --- a/managed/services/checks/checks.go +++ b/managed/services/checks/checks.go @@ -24,10 +24,13 @@ import ( "encoding/json" "errors" "fmt" + "io" + "maps" "net/url" "os" "os/exec" "path/filepath" + "sort" "strconv" "strings" "sync" @@ -35,16 +38,21 @@ import ( "text/template" "time" + "github.com/AlekSi/pointer" + "github.com/google/uuid" v1 "github.com/prometheus/client_golang/api/prometheus/v1" prom "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "gopkg.in/reform.v1" agentv1 "github.com/percona/pmm/api/agent/v1" "github.com/percona/pmm/managed/models" "github.com/percona/pmm/managed/pi/check" + "github.com/percona/pmm/managed/pi/common" "github.com/percona/pmm/managed/services" "github.com/percona/pmm/utils/pdeathsig" "github.com/percona/pmm/utils/sqlrows" @@ -55,9 +63,7 @@ const ( defaultStartDelay = time.Minute // Environment variables that affect checks service; only for testing. - envCheckFile = "PMM_DEV_ADVISOR_CHECKS_FILE" envDisableStartDelay = "PMM_ADVISORS_CHECKS_DISABLE_START_DELAY" - builtinAdvisorsPath = "/usr/local/percona/advisors" builtinChecksPath = "/usr/local/percona/checks" checkExecutionTimeout = 5 * time.Minute // limits execution time for every single check @@ -67,16 +73,12 @@ const ( prometheusNamespace = "pmm_managed" prometheusSubsystem = "advisor" - - maxSupportedVersion = 2 ) // pmm-agent versions with known changes in Query Actions. // To match all pre-release versions, add a '-0' suffix to the specified version. var ( - pmmAgent2_6_0 = version.MustParse("2.6.0") - pmmAgent2_7_0 = version.MustParse("2.7.0") - pmmAgent2_27_0 = version.MustParse("2.27.0-0") + pmmAgent3_0_0 = version.MustParse("3.0.0-0") pmmAgentInvalid = version.MustParse("3.0.0-invalid") b64 = base64.StdEncoding @@ -84,19 +86,18 @@ var ( // Service is responsible for interactions with Percona Check service. type Service struct { - agentsRegistry agentsRegistry - db *reform.DB - alertsRegistry *registry - vmClient v1.API - clickhouseDB *sql.DB + agentsRegistry agentsRegistry + db *reform.DB + resultsRegistry *registry + vmClient v1.API + clickhouseDB *sql.DB - l *logrus.Entry - startDelay time.Duration - customCheckFile string // For testing + l *logrus.Entry + startDelay time.Duration // startCheckCh delivers on-demand check runs from StartChecks to // runChecksLoop, which owns the service lifecycle context. - startCheckCh chan []string + startCheckCh chan checkRunRequest am sync.Mutex advisors []check.Advisor @@ -129,16 +130,15 @@ func New( l := logrus.WithField("component", "checks") s := &Service{ - db: db, - agentsRegistry: agentsRegistry, - alertsRegistry: newRegistry(), - vmClient: vmClient, - clickhouseDB: clickhouseDB, + db: db, + agentsRegistry: agentsRegistry, + resultsRegistry: newRegistry(), + vmClient: vmClient, + clickhouseDB: clickhouseDB, - l: l, - startDelay: defaultStartDelay, - customCheckFile: os.Getenv(envCheckFile), - startCheckCh: make(chan []string, 1), + l: l, + startDelay: defaultStartDelay, + startCheckCh: make(chan checkRunRequest, 1), mChecksExecuted: prom.NewCounterVec(prom.CounterOpts{ Namespace: prometheusNamespace, @@ -176,6 +176,12 @@ func (s *Service) Run(ctx context.Context) { s.l.Info("Starting...") defer s.l.Info("Done.") + err := s.reconcileBuiltinChecks(ctx) + if err != nil { + // Keep going with whatever is already stored in the DB. + s.l.Errorf("Failed to reconcile built-in checks: %+v.", err) + } + s.finalizeInterruptedRuns(ctx) s.UpdateAdvisorsList(ctx) settings, err := models.GetSettings(s.db) if err != nil { @@ -226,10 +232,10 @@ func (s *Service) runChecksLoop(ctx context.Context) { select { case <-ctx.Done(): return - case checkNames := <-s.startCheckCh: + case req := <-s.startCheckCh: // On-demand run requested via StartChecks. s.UpdateAdvisorsList(ctx) - err = s.run(ctx, "", checkNames) + err = s.run(ctx, "", req.checkNames, req.serviceIDs, req.ri) case <-s.rareTicker.C: // Start all checks from rare group. err = s.runChecksGroup(ctx, check.Rare) @@ -243,18 +249,52 @@ func (s *Service) runChecksLoop(ctx context.Context) { } } -// GetChecksResults returns the failed checks for a given service. -func (s *Service) GetChecksResults(_ context.Context, serviceID string) ([]services.CheckResult, error) { - settings, err := models.GetSettings(s.db) +// GetInsights returns Advisor insights matching the filters, +// together with the total number of matching rows (ignoring pagination). +func (s *Service) GetInsights(ctx context.Context, filters models.InsightFilters, pageIndex, pageSize int) ([]*models.Insight, int, error) { + results, err := models.FindInsights(ctx, s.db.Querier, filters, pageIndex, pageSize) if err != nil { - return nil, err + return nil, 0, err } - if !settings.IsAdvisorsEnabled() { - return nil, services.ErrAdvisorsDisabled + total, err := models.CountInsights(ctx, s.db.Querier, filters) + if err != nil { + return nil, 0, err } - return s.alertsRegistry.getCheckResults(serviceID), nil + return results, total, nil +} + +// GetRuns returns Advisor check executions matching the filters, plus the total +// number of matches, ignoring pagination. +func (s *Service) GetRuns(ctx context.Context, filters models.AdvisorRunFilters, pageIndex, pageSize int) ([]*models.AdvisorRun, int, error) { + runs, err := models.FindAdvisorRuns(ctx, s.db.Querier, filters, pageIndex, pageSize) + if err != nil { + return nil, 0, err + } + + total, err := models.CountAdvisorRuns(ctx, s.db.Querier, filters) + if err != nil { + return nil, 0, err + } + + return runs, total, nil +} + +// GetInsightsFilterValues returns the distinct service and node names present in the +// Advisor insights. +func (s *Service) GetInsightsFilterValues(ctx context.Context) ([]string, []string, error) { + return models.FindInsightFilterValues(ctx, s.db.Querier) +} + +// MarkInsightsRead sets the read state on the insights with the given IDs. +func (s *Service) MarkInsightsRead(ctx context.Context, ids []string, isRead bool) error { + return models.MarkInsightsRead(ctx, s.db.Querier, ids, isRead) +} + +// MarkInsightsReadByFilters sets the read state on all insights matching the filters. +func (s *Service) MarkInsightsReadByFilters(ctx context.Context, filters models.InsightFilters, isRead bool) error { + return models.MarkInsightsReadByFilters(ctx, s.db.Querier, filters, isRead) } // runChecksGroup downloads and executes Advisors checks that should run in the interval specified by intervalGroup. @@ -270,125 +310,152 @@ func (s *Service) runChecksGroup(ctx context.Context, intervalGroup check.Interv } s.UpdateAdvisorsList(ctx) - return s.run(ctx, intervalGroup, nil) + ri := runInfo{runID: uuid.NewString(), triggeredBy: models.CheckTriggeredByScheduler} + return s.run(ctx, intervalGroup, nil, nil, ri) } -// StartChecks downloads and executes advisor checks in asynchronous way. +// StartChecks downloads and executes advisor checks in asynchronous way and returns the run ID. // If checkNames specified then only matched checks will be executed. -func (s *Service) StartChecks(checkNames []string) error { +// If serviceIDs specified then the checks run only against those services. +func (s *Service) StartChecks(checkNames, serviceIDs []string) (string, error) { settings, err := models.GetSettings(s.db) if err != nil { - return err + return "", err } if !settings.IsAdvisorsEnabled() { - return services.ErrAdvisorsDisabled + return "", services.ErrAdvisorsDisabled } + ri := runInfo{runID: uuid.NewString(), triggeredBy: models.CheckTriggeredByUser} + // Hand the request off to runChecksLoop, which owns the service lifecycle // context. The loop only runs on the leader node, so a non-blocking send // drops the request where there is nothing to execute it. select { - case s.startCheckCh <- checkNames: + case s.startCheckCh <- checkRunRequest{checkNames: checkNames, serviceIDs: serviceIDs, ri: ri}: default: s.l.Warn("Advisor checks run is already pending, skipping the request.") } - return nil + return ri.runID, nil +} + +// runInfo identifies a single Advisor checks execution run. +type runInfo struct { + runID string + triggeredBy models.CheckTriggeredBy } -func (s *Service) run(ctx context.Context, intervalGroup check.Interval, checkNames []string) error { +// checkRunRequest is an on-demand run handed from StartChecks to runChecksLoop. +type checkRunRequest struct { + checkNames []string + serviceIDs []string + ri runInfo +} + +func (s *Service) run(ctx context.Context, intervalGroup check.Interval, checkNames, serviceIDs []string, ri runInfo) error { err := intervalGroup.Validate() if err != nil { return err } - res, err := s.executeChecks(ctx, intervalGroup, checkNames) + s.startRun(ctx, ri) + // Close the run out however execution ends, so a failure part-way through + // does not leave it reported as still running. + defer s.finishRun(ctx, ri.runID) + + res, err := s.executeChecks(ctx, intervalGroup, checkNames, serviceIDs, ri) if err != nil { return err } switch { + case len(checkNames) != 0 && len(serviceIDs) != 0: + // A service-scoped run must not drop the other services' findings. + s.resultsRegistry.deleteByNameAndService(checkNames, serviceIDs) case len(checkNames) != 0: // If we run some specific checks, delete previous results for them. - s.alertsRegistry.deleteByName(checkNames) + s.resultsRegistry.deleteByName(checkNames) case intervalGroup != "": // If we run whole interval group, delete previous results for that group. - s.alertsRegistry.deleteByInterval(intervalGroup) + s.resultsRegistry.deleteByInterval(intervalGroup) default: // If we run all checks, delete all previous results. - s.alertsRegistry.cleanup() + s.resultsRegistry.cleanup() } - s.alertsRegistry.set(res) + s.resultsRegistry.set(res) + + // Best-effort: email the completed run to the configured Advisor contact point. + s.maybeSendAdvisorNotification(ctx, ri.runID, ri.triggeredBy) return nil } -// CleanupAlerts drops all alerts in registry. -func (s *Service) CleanupAlerts() { - s.alertsRegistry.cleanup() +// CleanupCheckResults drops all check results in the registry. +func (s *Service) CleanupCheckResults() { + s.resultsRegistry.cleanup() } // GetAdvisors returns all available advisors. func (s *Service) GetAdvisors() ([]check.Advisor, error) { - cs, err := models.FindCheckSettings(s.db.Querier) - if err != nil { - return nil, err - } - s.am.Lock() defer s.am.Unlock() res := make([]check.Advisor, 0, len(s.advisors)) - for _, a := range s.advisors { - checks := make([]check.Check, 0, len(a.Checks)) - for _, c := range a.Checks { - if interval, ok := cs[c.Name]; ok { - c.Interval = check.Interval(interval) - } - checks = append(checks, c) - } - a.Checks = checks - res = append(res, a) - } + res = append(res, s.advisors...) return res, nil } // GetChecks retrieves a map of checks from the service. func (s *Service) GetChecks() (map[string]check.Check, error) { - cs, err := models.FindCheckSettings(s.db.Querier) - if err != nil { - return nil, err - } - s.am.Lock() defer s.am.Unlock() res := make(map[string]check.Check, len(s.checks)) for _, c := range s.checks { - if interval, ok := cs[c.Name]; ok { - c.Interval = check.Interval(interval) - } - res[c.Name] = c } return res, nil } -// GetDisabledChecks returns disabled checks. -func (s *Service) GetDisabledChecks() ([]string, error) { - settings, err := models.GetSettings(s.db) +// GetDisabledChecks returns the names of globally-disabled checks. +func (s *Service) GetDisabledChecks(ctx context.Context) ([]string, error) { + return models.FindDisabledAdvisorCheckNames(ctx, s.db.Querier) +} + +// GetDisabledServicesForChecks returns a map of check name to the service IDs +// for which that check is disabled. +func (s *Service) GetDisabledServicesForChecks(ctx context.Context) (map[string][]string, error) { + return models.FindAdvisorCheckDisabledServices(ctx, s.db.Querier) +} + +// DisableChecks disables checks with provided names. +func (s *Service) DisableChecks(ctx context.Context, checkNames []string) error { + err := s.setChecksDisabled(ctx, checkNames, true) if err != nil { - return nil, err + return fmt.Errorf("failed to disable checks: %w", err) } - return settings.SaaS.DisabledAdvisors, nil + return nil } -// DisableChecks disables checks with provided names. -func (s *Service) DisableChecks(checkNames []string) error { +// EnableChecks enables checks with provided names. +func (s *Service) EnableChecks(ctx context.Context, checkNames []string) error { + err := s.setChecksDisabled(ctx, checkNames, false) + if err != nil { + return fmt.Errorf("failed to enable checks: %w", err) + } + + return nil +} + +// setChecksDisabled sets the global disabled flag for the named checks. +// Per-service disable settings are left untouched, so they still apply once +// a check is re-enabled globally. +func (s *Service) setChecksDisabled(ctx context.Context, checkNames []string, disabled bool) error { if len(checkNames) == 0 { return nil } @@ -404,88 +471,424 @@ func (s *Service) DisableChecks(checkNames []string) error { } } - errTx := s.db.InTransaction(func(tx *reform.TX) error { - params := models.ChangeSettingsParams{DisableAdvisorChecks: checkNames} - _, err := models.UpdateSettings(tx.Querier, ¶ms) + return s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + return models.SetAdvisorChecksDisabled(ctx, tx.Querier, checkNames, disabled) + }) +} + +// DisableChecksForServices disables a check for the given services, keeping it +// enabled elsewhere. It is not allowed for globally-disabled checks. +func (s *Service) DisableChecksForServices(ctx context.Context, checkName string, serviceIDs []string) error { + if len(serviceIDs) == 0 { + return nil + } + + errTx := s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + c, err := models.FindAdvisorCheckByName(tx.WithContext(ctx), checkName) + if err != nil { + if errors.Is(err, reform.ErrNoRows) { + return status.Errorf(codes.NotFound, "advisor check '%s' not found", checkName) + } + return err + } + + if c.Disabled { + return status.Errorf(codes.FailedPrecondition, + "advisor check '%s' is disabled globally; enable it globally to manage per-service settings", checkName) + } + + services, err := models.FindServicesByIDs(tx.WithContext(ctx), serviceIDs) + if err != nil { + return err + } + for _, id := range serviceIDs { + if _, ok := services[id]; !ok { + return status.Errorf(codes.NotFound, "service with ID '%s' not found", id) + } + } + + ids, err := c.GetDisabledServiceIDs() + if err != nil { + return err + } + + existing := make(map[string]struct{}, len(ids)) + for _, id := range ids { + existing[id] = struct{}{} + } + for _, id := range serviceIDs { + if _, ok := existing[id]; !ok { + existing[id] = struct{}{} + ids = append(ids, id) + } + } + + _, err = models.ChangeAdvisorCheckDisabledServices(ctx, tx.Querier, checkName, ids) return err }) if errTx != nil { - return fmt.Errorf("failed to disable checks: %w", errTx) + return errTx } + s.l.Infof("Disabled check %s for services: %s.", checkName, strings.Join(serviceIDs, ", ")) return nil } -// EnableChecks enables checks with provided names. -func (s *Service) EnableChecks(checkNames []string) error { - if len(checkNames) == 0 { +// EnableChecksForServices removes the per-service disable of a check for the +// given services. IDs of already-removed services are accepted so stale +// entries can always be cleaned up. +func (s *Service) EnableChecksForServices(ctx context.Context, checkName string, serviceIDs []string) error { + if len(serviceIDs) == 0 { return nil } - err := s.db.InTransaction(func(tx *reform.TX) error { - params := models.ChangeSettingsParams{EnableAdvisorChecks: checkNames} - _, err := models.UpdateSettings(tx.Querier, ¶ms) + errTx := s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + c, err := models.FindAdvisorCheckByName(tx.WithContext(ctx), checkName) + if err != nil { + if errors.Is(err, reform.ErrNoRows) { + return status.Errorf(codes.NotFound, "advisor check '%s' not found", checkName) + } + return err + } + + ids, err := c.GetDisabledServiceIDs() + if err != nil { + return err + } + + remove := make(map[string]struct{}, len(serviceIDs)) + for _, id := range serviceIDs { + remove[id] = struct{}{} + } + kept := make([]string, 0, len(ids)) + for _, id := range ids { + if _, ok := remove[id]; !ok { + kept = append(kept, id) + } + } + + _, err = models.ChangeAdvisorCheckDisabledServices(ctx, tx.Querier, checkName, kept) return err }) - if err != nil { - return fmt.Errorf("failed to update disabled checks list: %w", err) + if errTx != nil { + return errTx } + s.l.Infof("Enabled check %s for services: %s.", checkName, strings.Join(serviceIDs, ", ")) return nil } // ChangeInterval changes a check's interval to the value received from the UI. -func (s *Service) ChangeInterval(params map[string]check.Interval) error { +func (s *Service) ChangeInterval(ctx context.Context, params map[string]check.Interval) error { checks, err := s.GetChecks() if err != nil { return err } - for name, interval := range params { - c, ok := checks[name] + for name := range params { + _, ok := checks[name] if !ok { return fmt.Errorf("check: %s not found", name) } + } - // since we re-run checks at regular intervals using a call - // to s.runChecksGroup which in turn calls s.UpdateAdvisorsList - // to load/download checks, we must persist any changes - // to check intervals in the DB so that they can be re-applied - // once the checks have been re-loaded on restarts. - errTx := s.db.InTransaction(func(tx *reform.TX) error { - cs, err := models.FindCheckSettingsByName(tx.Querier, name) - if err != nil && !errors.Is(err, reform.ErrNoRows) { + errTx := s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + for name, interval := range params { + _, err := models.ChangeAdvisorCheckInterval(ctx, tx.Querier, name, models.Interval(interval)) + if err != nil { return err } + s.l.Infof("Updated check: %s, interval changed from: %s to: %s", name, checks[name].Interval, interval) + } + return nil + }) + if errTx != nil { + return errTx + } - if cs == nil { - // record interval change for the first time. - _, err = models.CreateCheckSettings(tx.Querier, name, models.Interval(interval)) - if err != nil { - return err - } - s.l.Debugf("Saved interval change for check: %s in DB", name) - } else { - // update existing interval change. - _, err = models.ChangeCheckSettings(tx.Querier, name, models.Interval(interval)) - if err != nil { - return err - } - s.l.Debugf("Updated interval change for check: %s in DB", name) - } + // refresh the in-memory checks so the new effective intervals apply immediately + s.UpdateAdvisorsList(ctx) + return nil +} - return nil - }) - if errTx != nil { - return errTx +// CreateAdvisorCheck creates a new user-authored advisor check and reloads the check list. +func (s *Service) CreateAdvisorCheck(ctx context.Context, c check.Check) error { + c.Version = check.MaxSupportedVersion + c.UserDefined = true + + err := c.Validate() + if err != nil { + return status.Errorf(codes.InvalidArgument, "invalid advisor check: %v", err) + } + + // the reserved prefix keeps user checks from colliding with current or + // future Percona-shipped check names + if !strings.HasPrefix(c.Name, check.UserCheckNamePrefix) { + return status.Errorf(codes.InvalidArgument, + "user check name must start with '%s'", check.UserCheckNamePrefix) + } + + existing, err := s.GetChecks() + if err != nil { + return err + } + if _, ok := existing[c.Name]; ok { + return status.Errorf(codes.AlreadyExists, "advisor check '%s' already exists", c.Name) + } + + m, err := userCheckToModel(c) + if err != nil { + return err + } + + errTx := s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + _, err := models.CreateAdvisorCheck(tx.Querier, m) + return err + }) + if errTx != nil { + return fmt.Errorf("failed to create advisor check: %w", errTx) + } + + s.UpdateAdvisorsList(ctx) + return nil +} + +// UpdateAdvisorCheck updates an existing user-authored advisor check and reloads the check list. +func (s *Service) UpdateAdvisorCheck(ctx context.Context, c check.Check) error { + c.Version = check.MaxSupportedVersion + c.UserDefined = true + + err := c.Validate() + if err != nil { + return status.Errorf(codes.InvalidArgument, "invalid advisor check: %v", err) + } + + err = s.ensureUserCheck(c.Name) + if err != nil { + return err + } + + m, err := userCheckToModel(c) + if err != nil { + return err + } + + errTx := s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + _, err := models.UpdateAdvisorCheck(tx.Querier, m) + return err + }) + if errTx != nil { + return fmt.Errorf("failed to update advisor check: %w", errTx) + } + + s.UpdateAdvisorsList(ctx) + return nil +} + +// DeleteAdvisorCheck deletes a user-authored advisor check and reloads the check list. +func (s *Service) DeleteAdvisorCheck(ctx context.Context, name string) error { + err := s.ensureUserCheck(name) + if err != nil { + return err + } + + errTx := s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + return models.RemoveAdvisorCheck(tx.Querier, name) + }) + if errTx != nil { + return fmt.Errorf("failed to delete advisor check: %w", errTx) + } + + s.UpdateAdvisorsList(ctx) + return nil +} + +// TestAdvisorCheck executes an advisor check definition against a single service +// and returns its findings without saving the check or persisting the results. +func (s *Service) TestAdvisorCheck(ctx context.Context, c check.Check, serviceID string) ([]services.CheckResult, string, error) { + settings, err := models.GetSettings(s.db) + if err != nil { + return nil, "", err + } + if !settings.IsAdvisorsEnabled() { + return nil, "", services.ErrAdvisorsDisabled + } + + c.Version = check.MaxSupportedVersion + c.UserDefined = true + + err = c.Validate() + if err != nil { + return nil, "", status.Errorf(codes.InvalidArgument, "Invalid advisor check: %v", err) + } + + serviceType, err := serviceTypeForTechnology(c.Technology) + if err != nil { + return nil, "", err + } + + targets, err := s.findTargets(ctx, serviceType, s.minPMMAgentVersion(c), nil) + if err != nil { + return nil, "", err + } + + for _, target := range targets { + if target.ServiceID != serviceID { + continue + } + // collect the script's print() output so check authors can debug their scripts + var scriptOutput bytes.Buffer + res, err := s.executeCheck(ctx, target, c, &scriptOutput) + output := strings.TrimSpace(scriptOutput.String()) + if err != nil { + // a status error keeps the query/script failure details visible to + // the caller; plain errors are masked by the gRPC interceptor + msg := fmt.Sprintf("Failed to execute check '%s' on service '%s': %v", c.Name, target.ServiceName, err) + if output != "" { + // keep the error itself on top; the print output goes below it + msg += "\n\nScript output:\n" + output + } + return nil, "", status.Error(codes.FailedPrecondition, msg) } + return res, output, nil + } - s.l.Infof("Updated check: %s, interval changed from: %s to: %s", name, c.Interval, interval) + // no target matched - diagnose why so the error states the actual reason + service, err := models.FindServiceByID(s.db.WithContext(ctx), serviceID) + if err != nil { + return nil, "", err } + switch { + case service.ServiceName == models.PMMServerPostgreSQLServiceName: + return nil, "", status.Error(codes.FailedPrecondition, + "PMM Server's internal PostgreSQL database cannot be targeted by advisor checks") + case service.ServiceType != serviceType: + return nil, "", status.Errorf(codes.FailedPrecondition, + "Service '%s' is a %s service, but this check targets %s services", + service.ServiceName, service.ServiceType, serviceType) + default: + return nil, "", status.Errorf(codes.FailedPrecondition, + "Service '%s' has no compatible pmm-agent: it may be missing, disconnected or outdated", + service.ServiceName) + } +} + +// serviceTypeForTechnology maps a check technology to the service type it targets. +func serviceTypeForTechnology(technology check.Technology) (models.ServiceType, error) { + switch technology { + case check.MySQL: + return models.MySQLServiceType, nil + case check.PostgreSQL: + return models.PostgreSQLServiceType, nil + case check.MongoDB: + return models.MongoDBServiceType, nil + default: + return "", status.Errorf(codes.InvalidArgument, "Unknown check technology '%s'", technology) + } +} + +// ListTestTargets returns the services an advisor check of the given technology can +// be tested against. The minimum agent version is check-specific and unknown +// before the check is final, so it is not applied here; checks:test reports an +// outdated agent precisely when it happens. +func (s *Service) ListTestTargets(ctx context.Context, technology check.Technology) ([]services.Target, error) { + serviceType, err := serviceTypeForTechnology(technology) + if err != nil { + return nil, err + } + + targets, err := s.findTargets(ctx, serviceType, nil, nil) + if err != nil { + return nil, err + } + + sort.Slice(targets, func(i, j int) bool { + return targets[i].ServiceName < targets[j].ServiceName + }) + return targets, nil +} + +// ensureUserCheck verifies that the named check exists and is user-authored. +// It returns a NotFound error for unknown checks and a FailedPrecondition error +// for Percona-shipped checks, whose content is immutable. +func (s *Service) ensureUserCheck(name string) error { + c, err := models.FindAdvisorCheckByName(s.db.Querier, name) + if err != nil { + if errors.Is(err, reform.ErrNoRows) { + return status.Errorf(codes.NotFound, "Advisor check '%s' not found", name) + } + return err + } + + if c.Source != models.UserCheckSource { + return status.Errorf(codes.FailedPrecondition, "Advisor check '%s' is shipped by Percona and cannot be modified", name) + } return nil } +// userCheckToModel converts a user-authored check.Check into its DB representation. +func userCheckToModel(c check.Check) (*models.AdvisorCheck, error) { + m, err := checkToModel(c) + if err != nil { + return nil, err + } + + m.Source = models.UserCheckSource + return m, nil +} + +// checkToModel converts a check.Check into its DB representation, leaving the +// source and settings columns at their zero values. +func checkToModel(c check.Check) (*models.AdvisorCheck, error) { + queries, err := json.Marshal(c.Queries) + if err != nil { + return nil, fmt.Errorf("failed to encode queries: %w", err) + } + + return &models.AdvisorCheck{ + Name: c.Name, + Version: c.Version, + Summary: c.Summary, + Description: c.Description, + Category: c.Category, + Subcategory: c.Subcategory, + Technology: string(c.Technology), + Interval: string(c.Interval), + Queries: queries, + Script: c.Script, + }, nil +} + +// modelToCheck converts a DB advisor check into a check.Check with the +// effective execution interval (user override, if any) applied. +func modelToCheck(m *models.AdvisorCheck) (check.Check, error) { + var queries []check.Query + err := json.Unmarshal(m.Queries, &queries) + if err != nil { + return check.Check{}, fmt.Errorf("failed to decode queries: %w", err) + } + + interval := m.Interval + if m.IntervalOverride != nil { + interval = *m.IntervalOverride + } + + return check.Check{ + Version: m.Version, + Name: m.Name, + Summary: m.Summary, + Description: m.Description, + Category: m.Category, + Subcategory: m.Subcategory, + Technology: check.Technology(m.Technology), + Interval: check.Interval(interval), + Queries: queries, + Script: m.Script, + UserDefined: m.Source == models.UserCheckSource, + }, nil +} + // waitForResult periodically checks result state and returns it when complete. func (s *Service) waitForResult(ctx context.Context, resultID string) ([]byte, error) { ctx, cancel := context.WithTimeout(ctx, resultAwaitTimeout) @@ -518,22 +921,15 @@ func (s *Service) waitForResult(ctx context.Context, resultID string) ([]byte, e } func (s *Service) minPMMAgentVersion(c check.Check) *version.Parsed { - switch c.Version { - case 1: - return s.minPMMAgentVersionForType(c.Type) - case 2: //nolint:mnd - res := pmmAgent2_6_0 // minimum version that can be used with advisors - for _, query := range c.Queries { - v := s.minPMMAgentVersionForType(query.Type) - if v != nil && res.Less(v) { - res = v - } + res := pmmAgent3_0_0 // minimum version that can be used with advisors + for _, query := range c.Queries { + v := s.minPMMAgentVersionForType(query.Type) + if v != nil && res.Less(v) { + res = v } - - return res - default: - return pmmAgentInvalid } + + return res } // minPMMAgentVersion returns the minimal version of pmm-agent that can handle the given check type. @@ -550,15 +946,13 @@ func (s *Service) minPMMAgentVersionForType(t check.Type) *version.Parsed { case check.MongoDBBuildInfo: fallthrough case check.MongoDBGetParameter: - return pmmAgent2_6_0 - + fallthrough case check.MongoDBGetCmdLineOpts: - return pmmAgent2_7_0 - + fallthrough case check.MongoDBReplSetGetStatus: fallthrough case check.MongoDBGetDiagnosticData: - return pmmAgent2_27_0 + return pmmAgent3_0_0 case check.MetricsRange: fallthrough @@ -625,13 +1019,27 @@ func (s *Service) getActiveUserServiceTypes() (map[models.ServiceType]struct{}, } // executeChecks runs checks for all reachable services. If intervalGroup specified only checks from that group will be -// executed. If checkNames specified then only matched checks will be executed. -func (s *Service) executeChecks(ctx context.Context, intervalGroup check.Interval, checkNames []string) ([]services.CheckResult, error) { - disabledChecks, err := s.GetDisabledChecks() +// executed. If checkNames specified then only matched checks will be executed. If serviceIDs specified then only those +// services are targeted. +func (s *Service) executeChecks(ctx context.Context, intervalGroup check.Interval, checkNames, serviceIDs []string, ri runInfo) ([]services.CheckResult, error) { //nolint:lll + disabledChecks, err := s.GetDisabledChecks(ctx) if err != nil { return nil, err } + disabledServices, err := s.GetDisabledServicesForChecks(ctx) + if err != nil { + return nil, err + } + disabledTargets := make(map[string]map[string]struct{}, len(disabledServices)) + for name, ids := range disabledServices { + set := make(map[string]struct{}, len(ids)) + for _, id := range ids { + set[id] = struct{}{} + } + disabledTargets[name] = set + } + activeServiceTypes, err := s.getActiveUserServiceTypes() if err != nil { return nil, err @@ -647,7 +1055,7 @@ func (s *Service) executeChecks(ctx context.Context, intervalGroup check.Interva // Execute MySQL checks only if MySQL services exist if _, hasMySQL := activeServiceTypes[models.MySQLServiceType]; hasMySQL { mySQLChecks = s.filterChecks(mySQLChecks, intervalGroup, disabledChecks, checkNames) - mySQLCheckResults := s.executeChecksForTargetType(ctx, models.MySQLServiceType, mySQLChecks) + mySQLCheckResults := s.executeChecksForTargetType(ctx, models.MySQLServiceType, mySQLChecks, disabledTargets, serviceIDs, ri) res = append(res, mySQLCheckResults...) } else { s.l.Info("Skipping MySQL advisor checks: no MySQL services in inventory") @@ -656,7 +1064,7 @@ func (s *Service) executeChecks(ctx context.Context, intervalGroup check.Interva // Execute PostgreSQL checks only if PostgreSQL services exist if _, hasPostgreSQL := activeServiceTypes[models.PostgreSQLServiceType]; hasPostgreSQL { postgreSQLChecks = s.filterChecks(postgreSQLChecks, intervalGroup, disabledChecks, checkNames) - postgreSQLCheckResults := s.executeChecksForTargetType(ctx, models.PostgreSQLServiceType, postgreSQLChecks) + postgreSQLCheckResults := s.executeChecksForTargetType(ctx, models.PostgreSQLServiceType, postgreSQLChecks, disabledTargets, serviceIDs, ri) res = append(res, postgreSQLCheckResults...) } else { s.l.Info("Skipping PostgreSQL advisor checks: no PostgreSQL services in inventory") @@ -665,7 +1073,7 @@ func (s *Service) executeChecks(ctx context.Context, intervalGroup check.Interva // Execute MongoDB checks only if MongoDB services exist if _, hasMongoDB := activeServiceTypes[models.MongoDBServiceType]; hasMongoDB { mongoDBChecks = s.filterChecks(mongoDBChecks, intervalGroup, disabledChecks, checkNames) - mongoDBCheckResults := s.executeChecksForTargetType(ctx, models.MongoDBServiceType, mongoDBChecks) + mongoDBCheckResults := s.executeChecksForTargetType(ctx, models.MongoDBServiceType, mongoDBChecks, disabledTargets, serviceIDs, ri) res = append(res, mongoDBCheckResults...) } else { s.l.Info("Skipping MongoDB advisor checks: no MongoDB services in inventory") @@ -674,48 +1082,215 @@ func (s *Service) executeChecks(ctx context.Context, intervalGroup check.Interva return res, nil } -func (s *Service) executeChecksForTargetType(ctx context.Context, serviceType models.ServiceType, checks map[string]check.Check) []services.CheckResult { +func (s *Service) executeChecksForTargetType(ctx context.Context, serviceType models.ServiceType, checks map[string]check.Check, disabledTargets map[string]map[string]struct{}, serviceIDs []string, ri runInfo) []services.CheckResult { //nolint:lll var res []services.CheckResult + var history []*models.Insight + for _, c := range checks { s.l.Infof("Executing check: %s with interval: %s", c.Name, c.Interval) pmmAgentVersion := s.minPMMAgentVersion(c) - targets, err := s.findTargets(serviceType, pmmAgentVersion) + targets, err := s.findTargets(ctx, serviceType, pmmAgentVersion, serviceIDs) if err != nil { - s.l.Warnf("Failed to find proper agents and services for check type: %s and "+ - "min version: %s, reason: %s.", c.Type, pmmAgentVersion, err) + s.l.Warnf("Failed to find proper agents and services for check technology: %s and "+ + "min version: %s, reason: %s.", c.Technology, pmmAgentVersion, err) continue } for _, target := range targets { - results, err := s.executeCheck(ctx, target, c) + if _, ok := disabledTargets[c.Name][target.ServiceID]; ok { + s.l.Infof("Check %s is disabled for service %s, skipping it.", c.Name, target.ServiceID) + continue + } + + results, err := s.executeCheck(ctx, target, c, nil) + // stamp each (check, target) outcome with its actual completion time + checkedAt := models.Now() if err != nil { - s.l.Warnf("Failed to execute check %s of type %s on target %s: %+v", c.Name, c.Type, target.AgentID, err) - s.mChecksExecuted.WithLabelValues(string(target.ServiceType), c.Advisor, c.Name, "error").Inc() + s.l.Warnf("Failed to execute check %s of technology %s on target %s: %+v", c.Name, c.Technology, target.AgentID, err) + s.mChecksExecuted.WithLabelValues(string(target.ServiceType), c.Subcategory, c.Name, "error").Inc() + history = append(history, newInsightRecord(c, target, models.CheckResultError, check.Result{Description: err.Error()}, checkedAt, ri)) continue } res = append(res, results...) - s.mChecksExecuted.WithLabelValues(string(target.ServiceType), c.Advisor, c.Name, "ok").Inc() + s.mChecksExecuted.WithLabelValues(string(target.ServiceType), c.Subcategory, c.Name, "ok").Inc() + + if len(results) == 0 { + history = append(history, newInsightRecord(c, target, models.CheckResultOK, check.Result{}, checkedAt, ri)) + continue + } + + for _, finding := range results { + history = append(history, newInsightRecord(c, target, models.CheckResultFailed, finding.Result, checkedAt, ri)) + } } } + err := s.saveInsights(ctx, history) + if err != nil { + s.l.Warnf("Failed to save Advisor insights: %+v", err) + } + return res } -func (s *Service) executeCheck(ctx context.Context, target services.Target, c check.Check) ([]services.CheckResult, error) { +// newInsightRecord builds a history record for a single executed (check, target) outcome. +func newInsightRecord( + c check.Check, + target services.Target, + status models.CheckResultStatus, + result check.Result, + checkedAt time.Time, + ri runInfo, +) *models.Insight { + r := &models.Insight{ + CheckName: c.Name, + Category: c.Category, + Subcategory: c.Subcategory, + Interval: models.Interval(c.Interval), + ServiceID: target.ServiceID, + ServiceName: target.ServiceName, + ServiceType: target.ServiceType, + NodeID: target.NodeID, + NodeName: target.NodeName, + Environment: target.Environment, + Cluster: target.Cluster, + ReplicationSet: target.ReplicationSet, + Region: target.Region, + AZ: target.AZ, + Status: status, + Summary: result.Summary, + Description: c.Description, + Outcome: result.Description, + ReadMoreURL: result.ReadMoreURL, + Severity: models.Severity(result.Severity), + CheckedAt: checkedAt, + RunID: ri.runID, + TriggeredBy: ri.triggeredBy, + } + // OK and error outcomes carry no finding; fall back to the check's own summary + if r.Summary == "" { + r.Summary = c.Summary + } + switch status { + case models.CheckResultOK: + r.Severity = models.Severity(common.Info) + r.Outcome = "Check passed" + case models.CheckResultError: + // the check could not be executed, which is a diagnostic concern, not a database issue + r.Severity = models.Severity(common.Info) + case models.CheckResultFailed: + // keep the severity reported by the finding + } + // the target's node/service/agent labels take precedence over any the check script reported + labels := make(map[string]string, len(result.Labels)+len(target.Labels)) + maps.Copy(labels, result.Labels) + maps.Copy(labels, target.Labels) + if len(labels) != 0 { + _ = r.SetLabels(labels) + } + return r +} + +// saveInsights persists Advisor insights in a single transaction. +func (s *Service) saveInsights(ctx context.Context, history []*models.Insight) error { + if len(history) == 0 { + return nil + } + + return s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + for _, r := range history { + err := models.CreateInsight(ctx, tx.Querier, r) + if err != nil { + return err + } + } + return nil + }) +} + +// startRun records the beginning of a run. Failing to record it must not stop +// the checks from running, so the error is only logged. +func (s *Service) startRun(ctx context.Context, ri runInfo) { + run := &models.AdvisorRun{ + ID: ri.runID, + TriggeredBy: ri.triggeredBy, + StartedAt: models.Now(), + } + err := models.StartAdvisorRun(ctx, s.db.Querier, run) + if err != nil { + s.l.Warnf("Failed to record the start of Advisor run %s: %+v", ri.runID, err) + } +} + +// finishRun stamps a run as complete and stores the totals derived from the +// insights it recorded. +func (s *Service) finishRun(ctx context.Context, runID string) { + // The run is over either way, so record it even when the service context is + // already cancelled by a shutdown. + ctx = context.WithoutCancel(ctx) + + err := s.completeRun(ctx, runID, models.Now()) + if err != nil { + s.l.Warnf("Failed to record the completion of Advisor run %s: %+v", runID, err) + } +} + +// completeRun derives a run's totals from its insights and marks it finished. +func (s *Service) completeRun(ctx context.Context, runID string, finishedAt time.Time) error { + counts, err := models.ComputeAdvisorRunCounts(ctx, s.db.Querier, runID) + if err != nil { + return err + } + return models.FinishAdvisorRun(ctx, s.db.Querier, runID, finishedAt, counts) +} + +// finalizeInterruptedRuns closes out runs left open by a restart. Their insights +// are already persisted, so the last one recorded stands in for the completion +// time; a run that produced none is closed at its start. +func (s *Service) finalizeInterruptedRuns(ctx context.Context) { + runs, err := models.FindUnfinishedAdvisorRuns(ctx, s.db.Querier) + if err != nil { + s.l.Warnf("Failed to look for interrupted Advisor runs: %+v", err) + return + } + + for _, run := range runs { + finishedAt, ok, err := models.LastInsightTimeForRun(ctx, s.db.Querier, run.ID) + if err != nil { + s.l.Warnf("Failed to read the last insight of interrupted Advisor run %s: %+v", run.ID, err) + continue + } + if !ok { + finishedAt = run.StartedAt + } + + err = s.completeRun(ctx, run.ID, finishedAt) + if err != nil { + s.l.Warnf("Failed to close out interrupted Advisor run %s: %+v", run.ID, err) + continue + } + s.l.Infof("Closed out Advisor run %s, which was interrupted by a restart.", run.ID) + } +} + +// executeCheck runs a single check against a single target. When scriptOutput is +// non-nil, the script's print() output is collected into it. +func (s *Service) executeCheck(ctx context.Context, target services.Target, c check.Check, scriptOutput *bytes.Buffer) ([]services.CheckResult, error) { ctx, cancel := context.WithTimeout(ctx, checkExecutionTimeout) defer cancel() defer func(t time.Time) { - s.mChecksExecutionTime.WithLabelValues(string(target.ServiceType), c.Advisor, c.Name).Observe(time.Since(t).Seconds()) + s.mChecksExecutionTime.WithLabelValues(string(target.ServiceType), c.Subcategory, c.Name).Observe(time.Since(t).Seconds()) }(time.Now()) - queries := c.Queries - if c.Version == 1 { + if c.Version < check.MinSupportedVersion || c.Version > check.MaxSupportedVersion { return nil, fmt.Errorf("check %s has unsupported version %d", c.Name, c.Version) } + queries := c.Queries + eg, gCtx := errgroup.WithContext(ctx) resData := make([]any, len(queries)) @@ -804,9 +1379,9 @@ func (s *Service) executeCheck(ctx context.Context, target services.Target, c ch return nil, fmt.Errorf("check query failed: %w", err) } - res, err := s.processResults(ctx, c, target, resData) + res, err := s.processResults(ctx, c, target, resData, scriptOutput) if err != nil { - return nil, fmt.Errorf("failed to process query result: %w", err) + return nil, err } return res, nil @@ -1305,19 +1880,27 @@ type StarlarkScriptData struct { Name string `json:"name"` Script string `json:"script"` QueriesResults []any `json:"queries_results"` + // CapturePrintOutput makes the script's print() calls emit plain lines on + // a dedicated pipe (fd 3) so the caller can collect them without mixing + // them into the stderr error channel (used by check test runs). + CapturePrintOutput bool `json:"capture_print_output"` } -func (s *Service) processResults(ctx context.Context, aCheck check.Check, target services.Target, queryResults []any) ([]services.CheckResult, error) { +// processResults runs the check script in the pmm-managed-starlark sandbox and converts its +// findings. When scriptOutput is non-nil, the script's print() output is collected into it, +// on failures too. +func (s *Service) processResults(ctx context.Context, aCheck check.Check, target services.Target, queryResults []any, scriptOutput *bytes.Buffer) ([]services.CheckResult, error) { //nolint:lll l := s.l.WithFields(logrus.Fields{ "name": aCheck.Name, "service_id": target.ServiceID, }) input := &StarlarkScriptData{ - Version: aCheck.Version, - Name: aCheck.Name, - Script: aCheck.Script, - QueriesResults: queryResults, + Version: aCheck.Version, + Name: aCheck.Name, + Script: aCheck.Script, + QueriesResults: queryResults, + CapturePrintOutput: scriptOutput != nil, } cmdCtx, cancel := context.WithTimeout(ctx, scriptExecutionTimeout) @@ -1326,22 +1909,65 @@ func (s *Service) processResults(ctx context.Context, aCheck check.Check, target cmd := exec.CommandContext(cmdCtx, "pmm-managed-starlark") pdeathsig.Set(cmd, syscall.SIGKILL) - var stdin, stderr bytes.Buffer + var stdin, stdout, stderr bytes.Buffer cmd.Stdin = &stdin + cmd.Stdout = &stdout cmd.Stderr = &stderr + // print() output arrives on its own pipe (fd 3 in the child), keeping + // stderr a pure error channel + var printR, printW *os.File + if scriptOutput != nil { + var err error + printR, printW, err = os.Pipe() + if err != nil { + return nil, fmt.Errorf("failed to create print output pipe: %w", err) + } + defer printR.Close() //nolint:errcheck + cmd.ExtraFiles = []*os.File{printW} + } + encoder := json.NewEncoder(&stdin) err := encoder.Encode(input) if err != nil { return nil, fmt.Errorf("error encoding data to STDIN: %w", err) } - procOut, err := cmd.Output() + err = cmd.Start() if err != nil { - l.Errorf("Check script failed:\n%s", stderr.String()) - return nil, err + return nil, fmt.Errorf("failed to start check script: %w", err) + } + + printDone := make(chan struct{}) + if printW != nil { + // the child holds its own copy now; closing ours lets the reader see EOF on child exit + _ = printW.Close() + go func() { + defer close(printDone) + _, _ = io.Copy(scriptOutput, printR) + }() + } else { + close(printDone) } + err = cmd.Wait() + <-printDone + if err != nil { + scriptErr := strings.TrimSpace(stderr.String()) + l.Errorf("Check script failed (%s): %s", err, scriptErr) + switch { + case scriptErr != "": + // the subprocess reported the real cause (script bug, malformed query result, etc.) on stderr + return nil, errors.New(scriptErr) + case cmdCtx.Err() != nil: + return nil, fmt.Errorf("check script execution timed out after %s", scriptExecutionTimeout) + default: + return nil, fmt.Errorf("check script execution failed: %w", err) + } + } + + procOut := stdout.Bytes() + var results []check.Result decoder := json.NewDecoder(bytes.NewReader(procOut)) err = decoder.Decode(&results) @@ -1353,9 +1979,13 @@ func (s *Service) processResults(ctx context.Context, aCheck check.Check, target checkResults := make([]services.CheckResult, len(results)) for i, result := range results { + err = validateAdvisorSeverity(result.Severity) + if err != nil { + return nil, fmt.Errorf("check result %d: %w", i+1, err) + } checkResults[i] = services.CheckResult{ CheckName: aCheck.Name, - AdvisorName: aCheck.Advisor, + Subcategory: aCheck.Subcategory, Interval: aCheck.Interval, Target: target, Result: result, @@ -1364,22 +1994,48 @@ func (s *Service) processResults(ctx context.Context, aCheck check.Check, target return checkResults, nil } +// validateAdvisorSeverity rejects result severities outside the set advisors use. +// The retired levels (emergency, alert, notice, debug) fail the check run with a +// clear message instead of being coerced silently, so check authors notice and +// migrate their scripts. +func validateAdvisorSeverity(s common.Severity) error { + switch s { + case common.Critical, common.Error, common.Warning, common.Info: + return nil + default: + return fmt.Errorf("result severity '%s' is not supported by advisors; use one of: critical, error, warning, info", s) + } +} + // findTargets returns slice of available targets for specified service type. -func (s *Service) findTargets(serviceType models.ServiceType, minPMMAgentVersion *version.Parsed) ([]services.Target, error) { +// If serviceIDs is not empty, only those services are considered. +func (s *Service) findTargets(ctx context.Context, serviceType models.ServiceType, minPMMAgentVersion *version.Parsed, serviceIDs []string) ([]services.Target, error) { //nolint:lll var targets []services.Target - monitoredServices, err := models.FindServices(s.db.Querier, models.ServiceFilters{ServiceType: &serviceType}) + monitoredServices, err := models.FindServices(s.db.WithContext(ctx), models.ServiceFilters{ServiceType: &serviceType}) if err != nil { return nil, err } + wanted := make(map[string]struct{}, len(serviceIDs)) + for _, id := range serviceIDs { + wanted[id] = struct{}{} + } + for _, service := range monitoredServices { - // skip pmm own services - if service.NodeID == models.PMMServerNodeID { - s.l.Debugf("Skip PMM service, name: %s, type: %s.", service.ServiceName, service.ServiceType) + if len(wanted) != 0 { + _, ok := wanted[service.ServiceID] + if !ok { + continue + } + } + + // skip PMM Server's internal PostgreSQL database, but allow other services on the PMM Server node + if service.ServiceName == models.PMMServerPostgreSQLServiceName { + s.l.Debugf("Skip PMM Server's internal PostgreSQL service, name: %s, type: %s.", service.ServiceName, service.ServiceType) continue } - e := s.db.InTransaction(func(tx *reform.TX) error { + e := s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { pmmAgents, err := models.FindPMMAgentsForService(tx.Querier, service.ServiceID) if err != nil { return err @@ -1410,16 +2066,22 @@ func (s *Service) findTargets(serviceType models.ServiceType, minPMMAgentVersion } targets = append(targets, services.Target{ - AgentID: pmmAgent.AgentID, - ServiceID: service.ServiceID, - ServiceName: service.ServiceName, - ServiceType: service.ServiceType, - NodeName: node.NodeName, - Labels: labels, - DSN: DSN, - Files: agent.Files(), - TDP: agent.TemplateDelimiters(service), - TLSSkipVerify: agent.TLSSkipVerify, + AgentID: pmmAgent.AgentID, + ServiceID: service.ServiceID, + ServiceName: service.ServiceName, + ServiceType: service.ServiceType, + NodeID: node.NodeID, + NodeName: node.NodeName, + Environment: service.Environment, + Cluster: service.Cluster, + ReplicationSet: service.ReplicationSet, + Region: pointer.GetString(node.Region), + AZ: node.AZ, + Labels: labels, + DSN: DSN, + Files: agent.Files(), + TDP: agent.TemplateDelimiters(service), + TLSSkipVerify: agent.TLSSkipVerify, }) return nil }) @@ -1431,54 +2093,75 @@ func (s *Service) findTargets(serviceType models.ServiceType, minPMMAgentVersion return targets, nil } -// UpdateAdvisorsList loads advisors from built-in advisors directory or user-defined file, and stores versions supported by this pmm-managed version. +// UpdateAdvisorsList loads built-in checks (plus an optional user-defined file), +// groups them into advisors, and stores versions supported by this pmm-managed version. func (s *Service) UpdateAdvisorsList(ctx context.Context) { - var advisors []check.Advisor - var err error - defer s.refreshChecksInMemoryMetric() - s.l.Infof("Using builtin test checks file: %s", builtinAdvisorsPath) - advisors, err = s.loadBuiltinAdvisors(ctx) + rows, err := models.FindAdvisorChecks(s.db.WithContext(ctx)) if err != nil { - s.l.Errorf("Failed to load built-in advisors: %s.", err) + s.l.Errorf("Failed to load advisor checks: %s.", err) return // keep previously loaded advisors } - // if custom check file is provided, load it and append to the list of advisors - if s.customCheckFile != "" { - s.l.Infof("Using local test checks file: %s.", s.customCheckFile) - checks, err := s.loadChecksFromFiles([]string{s.customCheckFile}) + + // Skip rows that fail to decode or validate with a warning so a single + // bad row cannot break the whole load. + checks := make([]check.Check, 0, len(rows)) + for _, row := range rows { + c, err := modelToCheck(row) if err != nil { - s.l.Errorf("Failed to load local checks file: %s.", err) - return // keep previously loaded advisors + s.l.Warnf("Failed to decode advisor check '%s': %s.", row.Name, err) + continue } - advisors = append(advisors, check.Advisor{ - Version: 2, //nolint:mnd - Name: "dev", - Summary: "Dev Advisor", - Description: "Advisor used for developing checks", - Category: "development", - Checks: checks, - }) + err = c.Validate() + if err != nil { + s.l.Warnf("Advisor check '%s' is invalid and is ignored: %s.", row.Name, err) + continue + } + + checks = append(checks, c) } - s.updateAdvisors(s.filterSupportedChecks(advisors)) + s.updateAdvisors(s.filterSupportedChecks(groupChecksIntoAdvisors(checks))) } -// loadBuiltinAdvisors loads builtin advisors. -func (s *Service) loadBuiltinAdvisors(_ context.Context) ([]check.Advisor, error) { - s.l.Infof("Loading advisors from dir=%s", builtinAdvisorsPath) - advisorFiles, err := filepath.Glob(filepath.Join(builtinAdvisorsPath, "*.yml")) - if err != nil { - return nil, fmt.Errorf("failed to find advisor files: %w", err) - } +// reconcileBuiltinChecks synchronizes Percona-shipped checks from disk into the +// advisor_checks table: content columns are inserted or refreshed (including +// the placeholder rows created by migration 120 from legacy settings), rows of +// checks removed from the package are pruned, and user-set overrides (interval, +// disabled state, per-service disables) are preserved. It runs once at startup; +// picking up changed check files requires a restart. +func (s *Service) reconcileBuiltinChecks(ctx context.Context) error { + checks, err := s.loadBuiltinChecks(ctx) + if err != nil { + return fmt.Errorf("failed to load built-in checks: %w", err) + } + + return s.db.InTransactionContext(ctx, nil, func(tx *reform.TX) error { + // Collisions with user-authored rows are impossible: user check names + // must carry check.UserCheckNamePrefix and Percona checks must not + // (the latter is enforced by pi-validator). + names := make([]string, 0, len(checks)) + for _, c := range checks { + m, err := checkToModel(c) + if err != nil { + return err + } - advisors, err := s.loadAdvisorsFromFiles(advisorFiles) - if err != nil { - return nil, err - } + err = models.UpsertAdvisorCheckContent(ctx, tx.Querier, m) + if err != nil { + return err + } + names = append(names, c.Name) + } + + return models.RemoveAdvisorChecksNotIn(ctx, tx.Querier, names) + }) +} +// loadBuiltinChecks loads builtin checks from the checks directory. +func (s *Service) loadBuiltinChecks(_ context.Context) ([]check.Check, error) { s.l.Infof("Loading checks from dir=%s", builtinChecksPath) checkFiles, err := filepath.Glob(filepath.Join(builtinChecksPath, "*.yml")) @@ -1486,25 +2169,26 @@ func (s *Service) loadBuiltinAdvisors(_ context.Context) ([]check.Advisor, error return nil, fmt.Errorf("failed to find check files: %w", err) } - checks, err := s.loadChecksFromFiles(checkFiles) - if err != nil { - return nil, err - } + return s.loadChecksFromFiles(checkFiles) +} - // Link checks to advisors +// groupChecksIntoAdvisors groups checks into advisors by their (Category, Subcategory) +// pair, preserving first-seen order. +func groupChecksIntoAdvisors(checks []check.Check) []check.Advisor { + index := make(map[string]int, len(checks)) + advisors := make([]check.Advisor, 0, len(checks)) for _, c := range checks { - a, ok := advisors[c.Advisor] + key := c.Category + "\x00" + c.Subcategory + i, ok := index[key] if !ok { - return nil, fmt.Errorf("check '%s' refers to an unknown advisor '%s'", c.Name, c.Advisor) + i = len(advisors) + index[key] = i + advisors = append(advisors, check.Advisor{Category: c.Category, Subcategory: c.Subcategory}) } - a.Checks = append(a.Checks, c) + advisors[i].Checks = append(advisors[i].Checks, c) } - advisorsSlice := make([]check.Advisor, 0, len(advisors)) - for _, a := range advisors { - advisorsSlice = append(advisorsSlice, *a) - } - return advisorsSlice, nil + return advisors } // loadChecksFromFiles loads Advisor checks from a list of given files. @@ -1541,44 +2225,6 @@ func (s *Service) loadChecksFromFiles(files []string) ([]check.Check, error) { return res, nil } -// loadAdvisorsFromFiles loads Advisors from a list of given files. -func (s *Service) loadAdvisorsFromFiles(files []string) (map[string]*check.Advisor, error) { - res := make(map[string]*check.Advisor, len(files)) - for _, file := range files { - s.l.Debugf("Loading advisor file=%s", file) - - b, err := os.ReadFile(file) //nolint:gosec - if err != nil { - return nil, fmt.Errorf("failed to read advisor file %s: %w", file, err) - } - advisors, err := check.ParseAdvisors(bytes.NewReader(b), &check.ParseParams{ - DisallowUnknownFields: true, - DisallowInvalidChecks: true, - }) - if err != nil { - return nil, fmt.Errorf("failed to parse advisor from file %s: %w", file, err) - } - - if len(advisors) != 1 { - return nil, fmt.Errorf("expected exactly one advisor in %s", file) - } - a := advisors[0] - - _, fileName := filepath.Split(file) - if a.Name != strings.TrimSuffix(fileName, ".yml") { - return nil, fmt.Errorf("advisor name does not match file name %s", file) - } - - if _, ok := res[a.Name]; ok { - return nil, fmt.Errorf("advisor name collision detected: %s", a.Name) - } - - res[a.Name] = &a - } - - return res, nil -} - // filterSupportedChecks returns supported advisor checks and prints warning log messages about unsupported. func (s *Service) filterSupportedChecks(advisors []check.Advisor) []check.Advisor { res := make([]check.Advisor, 0, len(advisors)) @@ -1588,24 +2234,16 @@ func (s *Service) filterSupportedChecks(advisors []check.Advisor) []check.Adviso LOOP: for _, c := range advisor.Checks { - if c.Version > maxSupportedVersion { - s.l.Warnf("Unsupported checks version: %d, max supported version: %d.", c.Version, maxSupportedVersion) + if c.Version > check.MaxSupportedVersion { + s.l.Warnf("Unsupported checks version: %d, max supported version: %d.", c.Version, check.MaxSupportedVersion) continue LOOP } - switch c.Version { - case 1: - if ok := isQueryTypeSupported(c.Type); !ok { - s.l.Warnf("Unsupported check type: %s.", c.Type) + for _, query := range c.Queries { + if ok := isQueryTypeSupported(query.Type); !ok { + s.l.Warnf("Unsupported query type: %s.", query.Type) continue LOOP } - case 2: //nolint:mnd - for _, query := range c.Queries { - if ok := isQueryTypeSupported(query.Type); !ok { - s.l.Warnf("Unsupported query type: %s.", query.Type) - continue LOOP - } - } } checks = append(checks, c) @@ -1680,7 +2318,7 @@ func (s *Service) Describe(ch chan<- *prom.Desc) { s.mChecksAvailable.Describe(ch) s.mChecksExecutionTime.Describe(ch) - s.alertsRegistry.Describe(ch) + s.resultsRegistry.Describe(ch) } // Collect implements prom.Collector. @@ -1689,7 +2327,7 @@ func (s *Service) Collect(ch chan<- prom.Metric) { s.mChecksAvailable.Collect(ch) s.mChecksExecutionTime.Collect(ch) - s.alertsRegistry.Collect(ch) + s.resultsRegistry.Collect(ch) } func (s *Service) refreshChecksInMemoryMetric() { @@ -1707,7 +2345,7 @@ func (s *Service) refreshChecksInMemoryMetric() { func (s *Service) incChecksInMemoryMetric(serviceType models.ServiceType, checks map[string]check.Check) { for _, c := range checks { - s.mChecksAvailable.WithLabelValues(string(serviceType), c.Advisor, c.Name).Inc() + s.mChecksAvailable.WithLabelValues(string(serviceType), c.Subcategory, c.Name).Inc() } } @@ -1717,7 +2355,7 @@ func groupChecksByDB(l *logrus.Entry, checks map[string]check.Check) (mySQLCheck postgreSQLChecks = make(map[string]check.Check) mongoDBChecks = make(map[string]check.Check) for _, c := range checks { - switch c.GetFamily() { + switch c.Technology { case check.MySQL: mySQLChecks[c.Name] = c case check.PostgreSQL: @@ -1725,7 +2363,7 @@ func groupChecksByDB(l *logrus.Entry, checks map[string]check.Check) (mySQLCheck case check.MongoDB: mongoDBChecks[c.Name] = c default: - l.Warnf("Unknown check family %s, will be skipped.", c.Family) + l.Warnf("Unknown check technology %s, will be skipped.", c.Technology) } } diff --git a/managed/services/checks/checks_test.go b/managed/services/checks/checks_test.go index 3685a162f88..07b7b8fb356 100644 --- a/managed/services/checks/checks_test.go +++ b/managed/services/checks/checks_test.go @@ -16,8 +16,10 @@ package checks import ( + "bytes" "context" "database/sql" + "os" "testing" "time" @@ -28,6 +30,8 @@ import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "gopkg.in/reform.v1" "gopkg.in/reform.v1/dialects/postgresql" @@ -48,6 +52,34 @@ var ( clickhouseDB *sql.DB ) +// loadTestCheck parses the good-check test fixture into a check.Check. +func loadTestCheck(t *testing.T) check.Check { + t.Helper() + + b, err := os.ReadFile(testChecksFile) + require.NoError(t, err) + + checks, err := check.ParseChecks(bytes.NewReader(b), &check.ParseParams{ + DisallowUnknownFields: true, + DisallowInvalidChecks: true, + }) + require.NoError(t, err) + require.Len(t, checks, 1) + + return checks[0] +} + +// seedUserCheck stores the good-check test fixture as a user-authored check in the DB. +func seedUserCheck(t *testing.T, db *reform.DB) { + t.Helper() + + c := loadTestCheck(t) + m, err := userCheckToModel(c) + require.NoError(t, err) + _, err = models.CreateAdvisorCheck(db.Querier, m) + require.NoError(t, err) +} + func TestLoadBuiltinAdvisors(t *testing.T) { setupClients(t) sqlDB := testdb.Open(t, models.SkipFixtures, nil) @@ -66,9 +98,8 @@ func TestLoadBuiltinAdvisors(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() - dChecks, err := s.loadBuiltinAdvisors(ctx) + err = s.reconcileBuiltinChecks(ctx) require.NoError(t, err) - assert.NotEmpty(t, dChecks) s.UpdateAdvisorsList(ctx) @@ -86,7 +117,7 @@ func TestLoadBuiltinAdvisors(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) defer cancel() - dChecks, err := s.loadBuiltinAdvisors(ctx) + dChecks, err := s.loadBuiltinChecks(ctx) require.NoError(t, err) assert.NotEmpty(t, dChecks) @@ -106,7 +137,7 @@ func TestUpdateAdvisorsList(t *testing.T) { t.Run("collect custom checks", func(t *testing.T) { s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile + seedUserCheck(t, db) s.UpdateAdvisorsList(t.Context()) @@ -114,12 +145,11 @@ func TestUpdateAdvisorsList(t *testing.T) { require.NoError(t, err) require.GreaterOrEqual(t, len(advisors), 1) - // custom checks are loaded last, so we check the last advisor in the list. + // the user check carries a unique (category, subcategory), so it forms + // its own advisor group loaded last. advisor := advisors[len(advisors)-1] - require.Equal(t, "dev", advisor.Name) - require.Equal(t, "Dev Advisor", advisor.Summary) - require.Equal(t, "Advisor used for developing checks", advisor.Description) - require.Equal(t, "development", advisor.Category) + require.Equal(t, "Development", advisor.Category) + require.Equal(t, "Dev", advisor.Subcategory) require.Len(t, advisor.Checks, 1) checkNames := make([]string, 0, len(advisor.Checks)) @@ -142,7 +172,7 @@ func TestDisableChecks(t *testing.T) { db := reform.NewDB(sqlDB, postgresql.Dialect, nil) s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile + seedUserCheck(t, db) s.UpdateAdvisorsList(t.Context()) @@ -150,14 +180,14 @@ func TestDisableChecks(t *testing.T) { require.NoError(t, err) assert.NotEmpty(t, checks) - disChecks, err := s.GetDisabledChecks() + disChecks, err := s.GetDisabledChecks(t.Context()) require.NoError(t, err) assert.Empty(t, disChecks) - err = s.DisableChecks([]string{checks["good_check_pg"].Name}) + err = s.DisableChecks(t.Context(), []string{checks["good_check_pg"].Name}) require.NoError(t, err) - disChecks, err = s.GetDisabledChecks() + disChecks, err = s.GetDisabledChecks(t.Context()) require.NoError(t, err) assert.Len(t, disChecks, 1) }) @@ -171,7 +201,7 @@ func TestDisableChecks(t *testing.T) { db := reform.NewDB(sqlDB, postgresql.Dialect, nil) s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile + seedUserCheck(t, db) s.UpdateAdvisorsList(t.Context()) @@ -179,17 +209,17 @@ func TestDisableChecks(t *testing.T) { require.NoError(t, err) assert.NotEmpty(t, checks) - disChecks, err := s.GetDisabledChecks() + disChecks, err := s.GetDisabledChecks(t.Context()) require.NoError(t, err) assert.Empty(t, disChecks) - err = s.DisableChecks([]string{checks["good_check_pg"].Name}) + err = s.DisableChecks(t.Context(), []string{checks["good_check_pg"].Name}) require.NoError(t, err) - err = s.DisableChecks([]string{checks["good_check_pg"].Name}) + err = s.DisableChecks(t.Context(), []string{checks["good_check_pg"].Name}) require.NoError(t, err) - disChecks, err = s.GetDisabledChecks() + disChecks, err = s.GetDisabledChecks(t.Context()) require.NoError(t, err) assert.Len(t, disChecks, 1) }) @@ -203,14 +233,14 @@ func TestDisableChecks(t *testing.T) { db := reform.NewDB(sqlDB, postgresql.Dialect, nil) s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile + seedUserCheck(t, db) s.UpdateAdvisorsList(t.Context()) - err := s.DisableChecks([]string{"unknown_check"}) + err := s.DisableChecks(t.Context(), []string{"unknown_check"}) require.Error(t, err) - disChecks, err := s.GetDisabledChecks() + disChecks, err := s.GetDisabledChecks(t.Context()) require.NoError(t, err) assert.Empty(t, disChecks) }) @@ -226,7 +256,7 @@ func TestEnableChecks(t *testing.T) { db := reform.NewDB(sqlDB, postgresql.Dialect, nil) s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile + seedUserCheck(t, db) s.UpdateAdvisorsList(t.Context()) @@ -235,10 +265,10 @@ func TestEnableChecks(t *testing.T) { assert.NotEmpty(t, checks, 1) originalLength := len(checks) - err = s.DisableChecks([]string{checks["good_check_pg"].Name}) + err = s.DisableChecks(t.Context(), []string{checks["good_check_pg"].Name}) require.NoError(t, err) - disChecks, err := s.GetDisabledChecks() + disChecks, err := s.GetDisabledChecks(t.Context()) require.NoError(t, err) assert.Equal(t, []string{checks["good_check_pg"].Name}, disChecks) @@ -257,7 +287,7 @@ func TestChangeInterval(t *testing.T) { db := reform.NewDB(sqlDB, postgresql.Dialect, nil) s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile + seedUserCheck(t, db) s.UpdateAdvisorsList(t.Context()) @@ -270,7 +300,7 @@ func TestChangeInterval(t *testing.T) { for _, c := range checks { params[c.Name] = check.Rare } - err = s.ChangeInterval(params) + err = s.ChangeInterval(t.Context(), params) require.NoError(t, err) updatedChecks, err := s.GetChecks() @@ -292,6 +322,101 @@ func TestChangeInterval(t *testing.T) { }) } +func TestChecksForServices(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, nil) + ctx := t.Context() + + s := New(db, nil, vmClient, clickhouseDB) + seedUserCheck(t, db) + s.UpdateAdvisorsList(ctx) + + node, err := models.CreateNode(db.Querier, models.GenericNodeType, &models.CreateNodeParams{ + NodeName: "test-node", + }) + require.NoError(t, err) + + serviceIDs := make([]string, 0, 2) + for _, name := range []string{"mysql1", "mysql2"} { + svc, err := models.AddNewService(db.Querier, models.MySQLServiceType, &models.AddDBMSServiceParams{ + ServiceName: name, + NodeID: node.NodeID, + Address: new("127.0.0.1"), + Port: new(uint16(3306)), + }) + require.NoError(t, err) + serviceIDs = append(serviceIDs, svc.ServiceID) + } + + t.Run("disable and dedup", func(t *testing.T) { + err := s.DisableChecksForServices(ctx, "good_check_pg", []string{serviceIDs[0]}) + require.NoError(t, err) + + // disabling again including an already-disabled service must not duplicate it + err = s.DisableChecksForServices(ctx, "good_check_pg", serviceIDs) + require.NoError(t, err) + + m, err := s.GetDisabledServicesForChecks(ctx) + require.NoError(t, err) + assert.ElementsMatch(t, serviceIDs, m["good_check_pg"]) + }) + + t.Run("unknown check rejected", func(t *testing.T) { + err := s.DisableChecksForServices(ctx, "no_such_check", []string{serviceIDs[0]}) + require.Error(t, err) + assert.Equal(t, codes.NotFound, status.Code(err)) + }) + + t.Run("unknown service rejected", func(t *testing.T) { + err := s.DisableChecksForServices(ctx, "good_check_pg", []string{"no-such-service"}) + require.Error(t, err) + assert.Equal(t, codes.NotFound, status.Code(err)) + }) + + t.Run("globally disabled check rejects per-service changes but keeps them", func(t *testing.T) { + err := s.DisableChecks(ctx, []string{"good_check_pg"}) + require.NoError(t, err) + + err = s.DisableChecksForServices(ctx, "good_check_pg", []string{serviceIDs[0]}) + require.Error(t, err) + assert.Equal(t, codes.FailedPrecondition, status.Code(err)) + + // existing per-service settings survive the global disable... + m, err := s.GetDisabledServicesForChecks(ctx) + require.NoError(t, err) + assert.ElementsMatch(t, serviceIDs, m["good_check_pg"]) + + // ...and still apply after the check is re-enabled globally + err = s.EnableChecks(ctx, []string{"good_check_pg"}) + require.NoError(t, err) + + m, err = s.GetDisabledServicesForChecks(ctx) + require.NoError(t, err) + assert.ElementsMatch(t, serviceIDs, m["good_check_pg"]) + }) + + t.Run("enable removes only given services", func(t *testing.T) { + err := s.EnableChecksForServices(ctx, "good_check_pg", []string{serviceIDs[0]}) + require.NoError(t, err) + + m, err := s.GetDisabledServicesForChecks(ctx) + require.NoError(t, err) + assert.Equal(t, []string{serviceIDs[1]}, m["good_check_pg"]) + + // IDs of unknown (e.g. already removed) services are accepted + err = s.EnableChecksForServices(ctx, "good_check_pg", []string{"no-such-service", serviceIDs[1]}) + require.NoError(t, err) + + m, err = s.GetDisabledServicesForChecks(ctx) + require.NoError(t, err) + assert.Empty(t, m) + }) +} + func TestStartChecks(t *testing.T) { sqlDB := testdb.Open(t, models.SkipFixtures, nil) t.Cleanup(func() { @@ -303,7 +428,6 @@ func TestStartChecks(t *testing.T) { t.Run("unknown interval", func(t *testing.T) { s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile err := s.runChecksGroup(t.Context(), "unknown") require.EqualError(t, err, "unknown check interval: unknown") @@ -312,7 +436,7 @@ func TestStartChecks(t *testing.T) { t.Run("advisors enabled", func(t *testing.T) { s := New(db, nil, vmClient, clickhouseDB) - s.customCheckFile = testChecksFile + seedUserCheck(t, db) s.UpdateAdvisorsList(t.Context()) assert.NotEmpty(t, s.advisors) assert.NotEmpty(t, s.checks) @@ -336,6 +460,216 @@ func TestStartChecks(t *testing.T) { }) } +func TestUserAdvisorChecks(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, nil) + ctx := t.Context() + + s := New(db, nil, vmClient, clickhouseDB) + + // author a valid user check from a known-good template + c := loadTestCheck(t) + c.Name = "custom_test_user_check_crud" + + err := s.CreateAdvisorCheck(ctx, c) + require.NoError(t, err) + + checks, err := s.GetChecks() + require.NoError(t, err) + created, ok := checks[c.Name] + require.True(t, ok) + assert.True(t, created.UserDefined) + assert.Equal(t, c.Summary, created.Summary) + + t.Run("duplicate name rejected", func(t *testing.T) { + err := s.CreateAdvisorCheck(ctx, c) + require.Error(t, err) + assert.Equal(t, codes.AlreadyExists, status.Code(err)) + }) + + t.Run("name without the reserved prefix rejected", func(t *testing.T) { + unprefixed := c + unprefixed.Name = "test_user_check_without_prefix" + err := s.CreateAdvisorCheck(ctx, unprefixed) + require.Error(t, err) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + }) + + t.Run("update", func(t *testing.T) { + updated := c + updated.Summary = "updated summary" + err := s.UpdateAdvisorCheck(ctx, updated) + require.NoError(t, err) + + checks, err := s.GetChecks() + require.NoError(t, err) + require.Contains(t, checks, c.Name) + assert.Equal(t, "updated summary", checks[c.Name].Summary) + }) + + t.Run("update unknown rejected", func(t *testing.T) { + unknown := c + unknown.Name = "no_such_check" + err := s.UpdateAdvisorCheck(ctx, unknown) + require.Error(t, err) + assert.Equal(t, codes.NotFound, status.Code(err)) + }) + + t.Run("delete", func(t *testing.T) { + err := s.DeleteAdvisorCheck(ctx, c.Name) + require.NoError(t, err) + + checks, err := s.GetChecks() + require.NoError(t, err) + assert.NotContains(t, checks, c.Name) + }) + + t.Run("delete unknown rejected", func(t *testing.T) { + err := s.DeleteAdvisorCheck(ctx, "no_such_check") + require.Error(t, err) + assert.Equal(t, codes.NotFound, status.Code(err)) + }) +} + +func TestTestAdvisorCheck(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, nil) + ctx := t.Context() + + s := New(db, nil, vmClient, clickhouseDB) + + c := loadTestCheck(t) + c.Name = "custom_test_dry_run" + + t.Run("invalid check rejected", func(t *testing.T) { + invalid := c + invalid.Script = "" + + res, output, err := s.TestAdvisorCheck(ctx, invalid, "svc-1") + require.Error(t, err) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + assert.Nil(t, res) + assert.Empty(t, output) + }) + + t.Run("unknown check technology rejected", func(t *testing.T) { + unknown := c + unknown.Technology = "unknown" + + res, output, err := s.TestAdvisorCheck(ctx, unknown, "svc-1") + require.Error(t, err) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + assert.Nil(t, res) + assert.Empty(t, output) + }) + + t.Run("unknown service rejected", func(t *testing.T) { + res, output, err := s.TestAdvisorCheck(ctx, c, "no-such-service") + require.Error(t, err) + assert.Equal(t, codes.NotFound, status.Code(err)) + assert.Nil(t, res) + assert.Empty(t, output) + }) + + // ineligible-service diagnosis: each case gets its own precise error + pgCheck := check.Check{ + Name: "custom_test_diagnosis", + Summary: "Diagnosis probe", + Description: "Diagnosis probe", + Category: "test", + Subcategory: "diagnosis", + Technology: check.PostgreSQL, + Interval: check.Standard, + Queries: []check.Query{{Type: check.PostgreSQLSelect, Query: "1"}}, + Script: "def check_context(docs, context):\n return []", + } + + node, err := models.CreateNode(db.Querier, models.GenericNodeType, &models.CreateNodeParams{ + NodeName: "diagnosis-node", + }) + require.NoError(t, err) + + mysqlSvc, err := models.AddNewService(db.Querier, models.MySQLServiceType, &models.AddDBMSServiceParams{ + ServiceName: "mysql-diagnosis-svc", + NodeID: node.NodeID, + Address: new("127.0.0.1"), + Port: new(uint16(3306)), + }) + require.NoError(t, err) + + pgSvc, err := models.AddNewService(db.Querier, models.PostgreSQLServiceType, &models.AddDBMSServiceParams{ + ServiceName: "pg-diagnosis-no-agent", + NodeID: node.NodeID, + Address: new("127.0.0.1"), + Port: new(uint16(5432)), + }) + require.NoError(t, err) + + internalPG, err := models.AddNewService(db.Querier, models.PostgreSQLServiceType, &models.AddDBMSServiceParams{ + ServiceName: models.PMMServerPostgreSQLServiceName, + NodeID: node.NodeID, + Address: new("127.0.0.1"), + Port: new(uint16(5432)), + }) + require.NoError(t, err) + + t.Run("internal PMM Server PostgreSQL rejected", func(t *testing.T) { + res, output, err := s.TestAdvisorCheck(ctx, pgCheck, internalPG.ServiceID) + require.Error(t, err) + assert.Equal(t, codes.FailedPrecondition, status.Code(err)) + assert.Equal(t, + "PMM Server's internal PostgreSQL database cannot be targeted by advisor checks", + status.Convert(err).Message()) + assert.Nil(t, res) + assert.Empty(t, output) + }) + + t.Run("service of another type rejected", func(t *testing.T) { + res, output, err := s.TestAdvisorCheck(ctx, pgCheck, mysqlSvc.ServiceID) + require.Error(t, err) + assert.Equal(t, codes.FailedPrecondition, status.Code(err)) + assert.Equal(t, + "Service 'mysql-diagnosis-svc' is a mysql service, but this check targets postgresql services", + status.Convert(err).Message()) + assert.Nil(t, res) + assert.Empty(t, output) + }) + + t.Run("service without pmm-agent rejected", func(t *testing.T) { + res, output, err := s.TestAdvisorCheck(ctx, pgCheck, pgSvc.ServiceID) + require.Error(t, err) + assert.Equal(t, codes.FailedPrecondition, status.Code(err)) + assert.Equal(t, + "Service 'pg-diagnosis-no-agent' has no compatible pmm-agent: it may be missing, disconnected or outdated", + status.Convert(err).Message()) + assert.Nil(t, res) + assert.Empty(t, output) + }) + + // keep last: it flips the shared test DB settings + t.Run("advisors disabled", func(t *testing.T) { + settings, err := models.GetSettings(db) + require.NoError(t, err) + + settings.SaaS.Enabled = new(false) + err = models.SaveSettings(db, settings) + require.NoError(t, err) + + res, output, err := s.TestAdvisorCheck(ctx, c, "svc-1") + require.ErrorIs(t, err, services.ErrAdvisorsDisabled) + assert.Nil(t, res) + assert.Empty(t, output) + }) +} + func TestNewInitializesStartCheckChannel(t *testing.T) { t.Parallel() // New must initialize the on-demand channel so StartChecks can enqueue a @@ -359,38 +693,23 @@ func TestFilterChecks(t *testing.T) { valid := []check.Advisor{ { - Name: "mysql_advisor", - Summary: "MySQL advisor", - Description: "Test mySQL advisor", - Category: "test", + Category: "Test", + Subcategory: "MySQL", Checks: []check.Check{ - {Name: "MySQLShow", Version: 1, Type: check.MySQLShow}, - {Name: "MySQLSelect", Version: 1, Type: check.MySQLSelect}, {Name: "MySQL check V2", Version: 2, Queries: []check.Query{{Type: check.MySQLShow}, {Type: check.MySQLSelect}}}, }, }, { - Name: "postgresql_advisor", - Summary: "PostgreSQL advisor", - Description: "Test postgreSQL advisor", - Category: "test", + Category: "Test", + Subcategory: "PostgreSQL", Checks: []check.Check{ - {Name: "PostgreSQLShow", Version: 1, Type: check.PostgreSQLShow}, - {Name: "PostgreSQLSelect", Version: 1, Type: check.PostgreSQLSelect}, {Name: "PostgreSQL check V2", Version: 2, Queries: []check.Query{{Type: check.PostgreSQLShow}, {Type: check.PostgreSQLSelect}}}, }, }, { - Name: "mongodb_advisor", - Summary: "MongoDB advisor", - Description: "Test mongoDB advisor", - Category: "test", + Category: "Test", + Subcategory: "MongoDB", Checks: []check.Check{ - {Name: "MongoDBGetParameter", Version: 1, Type: check.MongoDBGetParameter}, - {Name: "MongoDBBuildInfo", Version: 1, Type: check.MongoDBBuildInfo}, - {Name: "MongoDBGetCmdLineOpts", Version: 1, Type: check.MongoDBGetCmdLineOpts}, - {Name: "MongoDBReplSetGetStatus", Version: 1, Type: check.MongoDBReplSetGetStatus}, - {Name: "MongoDBGetDiagnosticData", Version: 1, Type: check.MongoDBGetDiagnosticData}, {Name: "MongoDB check V2", Version: 2, Queries: []check.Query{{Type: check.MongoDBBuildInfo}, {Type: check.MongoDBGetParameter}, {Type: check.MongoDBGetCmdLineOpts}}}, }, }, @@ -398,23 +717,19 @@ func TestFilterChecks(t *testing.T) { invalid := []check.Advisor{ { - Name: "completely_invalid_advisor", - Summary: "Completely invalid advisor", - Description: "Test advisor that contains only unsupported checks", - Category: "test", + Category: "Test", + Subcategory: "CompletelyInvalid", Checks: []check.Check{ - {Name: "unsupported version", Version: maxSupportedVersion + 1, Type: check.MySQLShow}, - {Name: "unsupported type", Version: 1, Type: check.Type("RedisInfo")}, + {Name: "unsupported version", Version: check.MaxSupportedVersion + 1, Queries: []check.Query{{Type: check.MySQLShow}}}, + {Name: "unsupported type", Version: 2, Queries: []check.Query{{Type: check.Type("RedisInfo")}}}, }, }, { - Name: "partially_invalid_advisor", - Summary: "Partially invalid advisor", - Description: "Test advisor that contains some unsupported checks", - Category: "test", + Category: "Test", + Subcategory: "PartiallyInvalid", Checks: []check.Check{ - {Name: "MySQLShow", Version: 1, Type: check.MySQLShow}, - {Name: "missing type", Version: 1}, + {Name: "MySQLShow", Version: 2, Queries: []check.Query{{Type: check.MySQLShow}}}, + {Name: "unsupported type", Version: 2, Queries: []check.Query{{Type: check.Type("RedisInfo")}}}, }, }, } @@ -438,16 +753,16 @@ func TestMinPMMAgents(t *testing.T) { check check.Check minVersion *version.Parsed }{ - {name: "MySQLShow", minVersion: pmmAgent2_6_0, check: check.Check{Version: 1, Type: check.MySQLShow}}, - {name: "MySQLSelect", minVersion: pmmAgent2_6_0, check: check.Check{Version: 1, Type: check.MySQLSelect}}, - {name: "PostgreSQLShow", minVersion: pmmAgent2_6_0, check: check.Check{Version: 1, Type: check.PostgreSQLShow}}, - {name: "PostgreSQLSelect", minVersion: pmmAgent2_6_0, check: check.Check{Version: 1, Type: check.PostgreSQLSelect}}, - {name: "MongoDBGetParameter", minVersion: pmmAgent2_6_0, check: check.Check{Version: 1, Type: check.MongoDBGetParameter}}, - {name: "MongoDBBuildInfo", minVersion: pmmAgent2_6_0, check: check.Check{Version: 1, Type: check.MongoDBBuildInfo}}, - {name: "MongoDBGetCmdLineOpts", minVersion: pmmAgent2_7_0, check: check.Check{Version: 1, Type: check.MongoDBGetCmdLineOpts}}, - {name: "MySQL Family", minVersion: pmmAgent2_6_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MySQLShow}, {Type: check.MySQLSelect}}}}, - {name: "MongoDB Family", minVersion: pmmAgent2_7_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MongoDBBuildInfo}, {Type: check.MongoDBGetParameter}, {Type: check.MongoDBGetCmdLineOpts}}}}, - {name: "PostgreSQL Family", minVersion: pmmAgent2_6_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.PostgreSQLShow}, {Type: check.PostgreSQLSelect}}}}, + {name: "MySQLShow", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MySQLShow}}}}, + {name: "MySQLSelect", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MySQLSelect}}}}, + {name: "PostgreSQLShow", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.PostgreSQLShow}}}}, + {name: "PostgreSQLSelect", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.PostgreSQLSelect}}}}, + {name: "MongoDBGetParameter", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MongoDBGetParameter}}}}, + {name: "MongoDBBuildInfo", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MongoDBBuildInfo}}}}, + {name: "MongoDBGetCmdLineOpts", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MongoDBGetCmdLineOpts}}}}, + {name: "MySQL Technology", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MySQLShow}, {Type: check.MySQLSelect}}}}, + {name: "MongoDB Technology", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.MongoDBBuildInfo}, {Type: check.MongoDBGetParameter}, {Type: check.MongoDBGetCmdLineOpts}}}}, + {name: "PostgreSQL Technology", minVersion: pmmAgent3_0_0, check: check.Check{Version: 2, Queries: []check.Query{{Type: check.PostgreSQLShow}, {Type: check.PostgreSQLSelect}}}}, } s := New(nil, nil, vmClient, clickhouseDB) @@ -514,7 +829,7 @@ func TestFindTargets(t *testing.T) { t.Run("unknown service", func(t *testing.T) { t.Parallel() - targets, err := s.findTargets(models.PostgreSQLServiceType, nil) + targets, err := s.findTargets(t.Context(), models.PostgreSQLServiceType, nil, nil) require.NoError(t, err) assert.Empty(t, targets) }) @@ -550,7 +865,7 @@ func TestFindTargets(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - targets, err := s.findTargets(models.MySQLServiceType, test.minRequiredVersion) + targets, err := s.findTargets(t.Context(), models.MySQLServiceType, test.minRequiredVersion, nil) require.NoError(t, err) assert.Len(t, targets, test.count) }) @@ -558,6 +873,65 @@ func TestFindTargets(t *testing.T) { }) } +func TestFindTargetsSkipsOnlyInternalPostgreSQL(t *testing.T) { + // NOTE: no t.Parallel() - testdb.Open recreates a single shared database, so concurrent + // testdb tests collide. + sqlDB := testdb.Open(t, models.SetupFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)) + + s := New(db, nil, vmClient, clickhouseDB) + + // A user service registered on the PMM Server node must still be a valid target. + setup(t, db, "mysql-on-pmm-node", models.PMMServerNodeID, "") + + mysqlTargets, err := s.findTargets(t.Context(), models.MySQLServiceType, nil, nil) + require.NoError(t, err) + require.Len(t, mysqlTargets, 1) + assert.Equal(t, "mysql-on-pmm-node", mysqlTargets[0].ServiceName) + + // PMM Server's internal PostgreSQL must be skipped, leaving no PostgreSQL targets. + pgTargets, err := s.findTargets(t.Context(), models.PostgreSQLServiceType, nil, nil) + require.NoError(t, err) + assert.Empty(t, pgTargets) +} + +func TestListTestTargets(t *testing.T) { + // NOTE: no t.Parallel() - testdb.Open recreates a single shared database, so concurrent + // testdb tests collide. + sqlDB := testdb.Open(t, models.SetupFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, nil) + + s := New(db, nil, vmClient, clickhouseDB) + + setup(t, db, "mysql-b", models.PMMServerNodeID, "") + setup(t, db, "mysql-a", models.PMMServerNodeID, "") + + targets, err := s.ListTestTargets(t.Context(), check.MySQL) + require.NoError(t, err) + require.Len(t, targets, 2) + // sorted by service name + assert.Equal(t, "mysql-a", targets[0].ServiceName) + assert.Equal(t, "mysql-b", targets[1].ServiceName) + + // the internal PMM Server PostgreSQL is monitored but not a target + pgTargets, err := s.ListTestTargets(t.Context(), check.PostgreSQL) + require.NoError(t, err) + assert.Empty(t, pgTargets) + + // unknown technology rejected + _, err = s.ListTestTargets(t.Context(), check.Technology("unknown")) + require.Error(t, err) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) +} + func TestFilterChecksByInterval(t *testing.T) { t.Parallel() s := New(nil, nil, vmClient, clickhouseDB) @@ -584,143 +958,6 @@ func TestFilterChecksByInterval(t *testing.T) { assert.Equal(t, map[string]check.Check{"frequentCheck": frequentCheck}, frequentChecks) } -func TestGetFailedChecks(t *testing.T) { - sqlDB := testdb.Open(t, models.SkipFixtures, nil) - t.Cleanup(func() { - require.NoError(t, sqlDB.Close()) - }) - - db := reform.NewDB(sqlDB, postgresql.Dialect, nil) - - t.Run("no failed check for service", func(t *testing.T) { - s := New(db, nil, vmClient, clickhouseDB) - - results, err := s.GetChecksResults(t.Context(), "test_svc") - assert.Empty(t, results) - require.NoError(t, err) - }) - - t.Run("non empty failed checks", func(t *testing.T) { - checkResults := []services.CheckResult{ - { - CheckName: "test_check", - Interval: check.Frequent, - Target: services.Target{ - ServiceName: "test_svc1", - ServiceID: "test_svc1", - Labels: map[string]string{ - "targetLabel": "targetLabelValue", - }, - }, - Result: check.Result{ - Summary: "Check summary", - Description: "Check description", - ReadMoreURL: "https://www.example.com", - Severity: common.Error, - Labels: map[string]string{ - "resultLabel": "reslutLabelValue", - }, - }, - }, - { - CheckName: "test_check2", - Interval: check.Frequent, - Target: services.Target{ - ServiceName: "test_svc2", - ServiceID: "test_svc2", - Labels: map[string]string{ - "targetLabel": "targetLabelValue", - }, - }, - Result: check.Result{ - Summary: "Check summary", - Description: "Check description", - ReadMoreURL: "https://www.example.com", - Severity: common.Error, - Labels: map[string]string{ - "resultLabel": "reslutLabelValue", - }, - }, - }, - } - - s := New(db, nil, vmClient, clickhouseDB) - s.alertsRegistry.set(checkResults) - - response, err := s.GetChecksResults(t.Context(), "") - require.NoError(t, err) - assert.ElementsMatch(t, checkResults, response) - }) - - t.Run("non empty failed checks for specific service", func(t *testing.T) { - checkResults := []services.CheckResult{ - { - CheckName: "test_check", - Interval: check.Frequent, - Target: services.Target{ - ServiceName: "test_svc1", - ServiceID: "test_svc1", - Labels: map[string]string{ - "targetLabel": "targetLabelValue", - }, - }, - Result: check.Result{ - Summary: "Check summary", - Description: "Check description", - ReadMoreURL: "https://www.example.com", - Severity: common.Error, - Labels: map[string]string{ - "resultLabel": "reslutLabelValue", - }, - }, - }, - { - CheckName: "test_check2", - Interval: check.Frequent, - Target: services.Target{ - ServiceName: "test_svc2", - ServiceID: "test_svc2", - Labels: map[string]string{ - "targetLabel": "targetLabelValue", - }, - }, - Result: check.Result{ - Summary: "Check summary", - Description: "Check description", - ReadMoreURL: "https://www.example.com", - Severity: common.Error, - Labels: map[string]string{ - "resultLabel": "reslutLabelValue", - }, - }, - }, - } - - s := New(db, nil, vmClient, clickhouseDB) - s.alertsRegistry.set(checkResults) - - response, err := s.GetChecksResults(t.Context(), "test_svc1") - require.NoError(t, err) - require.Len(t, response, 1) - assert.Equal(t, checkResults[0], response[0]) - }) - - t.Run("Advisors disabled", func(t *testing.T) { - s := New(db, nil, vmClient, clickhouseDB) - - settings, err := models.GetSettings(db) - require.NoError(t, err) - - settings.SaaS.Enabled = new(false) - err = models.SaveSettings(db, settings) - require.NoError(t, err) - - results, err := s.GetChecksResults(t.Context(), "test_svc") - assert.Nil(t, results) - require.ErrorIs(t, err, services.ErrAdvisorsDisabled) - }) -} - func TestFillQueryPlaceholders(t *testing.T) { t.Parallel() @@ -803,45 +1040,45 @@ func TestGroupChecksByDB(t *testing.T) { t.Parallel() checks := map[string]check.Check{ - "MySQLShow": {Name: "MySQLShow", Version: 1, Type: check.MySQLShow}, - "MySQLSelect": {Name: "MySQLSelect", Version: 1, Type: check.MySQLSelect}, - "PostgreSQLShow": {Name: "PostgreSQLShow", Version: 1, Type: check.PostgreSQLShow}, - "PostgreSQLSelect": {Name: "PostgreSQLSelect", Version: 1, Type: check.PostgreSQLSelect}, - "MongoDBGetParameter": {Name: "MongoDBGetParameter", Version: 1, Type: check.MongoDBGetParameter}, - "MongoDBBuildInfo": {Name: "MongoDBBuildInfo", Version: 1, Type: check.MongoDBBuildInfo}, - "MongoDBGetCmdLineOpts": {Name: "MongoDBGetCmdLineOpts", Version: 1, Type: check.MongoDBGetCmdLineOpts}, - "MongoDBReplSetGetStatus": {Name: "MongoDBReplSetGetStatus", Version: 1, Type: check.MongoDBReplSetGetStatus}, - "MongoDBGetDiagnosticData": {Name: "MongoDBGetDiagnosticData", Version: 1, Type: check.MongoDBGetDiagnosticData}, - "unsupported type": {Name: "unsupported type", Version: 1, Type: check.Type("RedisInfo")}, - "missing type": {Name: "missing type", Version: 1}, - "MySQL family V2": {Name: "MySQL family V2", Version: 2, Family: check.MySQL}, - "PostgreSQL family V2": {Name: "PostgreSQL family V2", Version: 2, Family: check.PostgreSQL}, - "MongoDB family V2": {Name: "MongoDB family V2", Version: 2, Family: check.MongoDB}, - "missing family": {Name: "missing family", Version: 2}, + "mysql_1": {Name: "mysql_1", Version: 2, Technology: check.MySQL}, + "mysql_2": {Name: "mysql_2", Version: 2, Technology: check.MySQL}, + "mysql_3": {Name: "mysql_3", Version: 2, Technology: check.MySQL}, + "postgresql_1": {Name: "postgresql_1", Version: 2, Technology: check.PostgreSQL}, + "postgresql_2": {Name: "postgresql_2", Version: 2, Technology: check.PostgreSQL}, + "postgresql_3": {Name: "postgresql_3", Version: 2, Technology: check.PostgreSQL}, + "mongodb_1": {Name: "mongodb_1", Version: 2, Technology: check.MongoDB}, + "mongodb_2": {Name: "mongodb_2", Version: 2, Technology: check.MongoDB}, + "mongodb_3": {Name: "mongodb_3", Version: 2, Technology: check.MongoDB}, + "mongodb_4": {Name: "mongodb_4", Version: 2, Technology: check.MongoDB}, + "mongodb_5": {Name: "mongodb_5", Version: 2, Technology: check.MongoDB}, + "mongodb_6": {Name: "mongodb_6", Version: 2, Technology: check.MongoDB}, + "missing technology": {Name: "missing technology", Version: 2}, + "unknown technology": {Name: "unknown technology", Version: 2, Technology: check.Technology("RedisTechnology")}, } l := logrus.WithField("component", "tests") mySQLChecks, postgreSQLChecks, mongoDBChecks := groupChecksByDB(l, checks) + // checks with a missing or unknown technology are skipped require.Len(t, mySQLChecks, 3) require.Len(t, postgreSQLChecks, 3) require.Len(t, mongoDBChecks, 6) - // V1 checks - assert.Equal(t, check.MySQLShow, mySQLChecks["MySQLShow"].Type) - assert.Equal(t, check.MySQLSelect, mySQLChecks["MySQLSelect"].Type) + assert.Equal(t, check.MySQL, mySQLChecks["mysql_1"].Technology) + assert.Equal(t, check.PostgreSQL, postgreSQLChecks["postgresql_1"].Technology) + assert.Equal(t, check.MongoDB, mongoDBChecks["mongodb_1"].Technology) +} - assert.Equal(t, check.PostgreSQLShow, postgreSQLChecks["PostgreSQLShow"].Type) - assert.Equal(t, check.PostgreSQLSelect, postgreSQLChecks["PostgreSQLSelect"].Type) +func TestValidateAdvisorSeverity(t *testing.T) { + t.Parallel() - assert.Equal(t, check.MongoDBGetParameter, mongoDBChecks["MongoDBGetParameter"].Type) - assert.Equal(t, check.MongoDBBuildInfo, mongoDBChecks["MongoDBBuildInfo"].Type) - assert.Equal(t, check.MongoDBGetCmdLineOpts, mongoDBChecks["MongoDBGetCmdLineOpts"].Type) - assert.Equal(t, check.MongoDBReplSetGetStatus, mongoDBChecks["MongoDBReplSetGetStatus"].Type) - assert.Equal(t, check.MongoDBGetDiagnosticData, mongoDBChecks["MongoDBGetDiagnosticData"].Type) + for _, s := range []common.Severity{common.Critical, common.Error, common.Warning, common.Info} { + require.NoError(t, validateAdvisorSeverity(s)) + } - // V2 checks - assert.Equal(t, check.MySQL, mySQLChecks["MySQL family V2"].Family) - assert.Equal(t, check.PostgreSQL, postgreSQLChecks["PostgreSQL family V2"].Family) - assert.Equal(t, check.MongoDB, mongoDBChecks["MongoDB family V2"].Family) + for _, s := range []common.Severity{common.Emergency, common.Alert, common.Notice, common.Debug, common.Unknown} { + err := validateAdvisorSeverity(s) + require.Error(t, err) + assert.Contains(t, err.Error(), "use one of: critical, error, warning, info") + } } diff --git a/managed/services/checks/funcs_test.go b/managed/services/checks/funcs_test.go index 74de6064fae..e564b9db7c0 100644 --- a/managed/services/checks/funcs_test.go +++ b/managed/services/checks/funcs_test.go @@ -58,7 +58,7 @@ def check_context(rows, context): } res, err := env.Run("type", input, nil, t.Log) expectedErr := strings.TrimSpace(` -thread type: failed to execute function check_context: parse_version: expected string argument, got int64 (1) +failed to execute function check_context: parse_version: expected string argument, got int64 (1) Traceback (most recent call last): TestVersion:2:22: in check_context : in parse_version @@ -71,7 +71,7 @@ Traceback (most recent call last): } res, err = env.Run("foo", input, nil, t.Log) expectedErr = strings.TrimSpace(` -thread foo: failed to execute function check_context: parse_version: failed to parse "foo" +failed to execute function check_context: parse_version: failed to parse "foo" Traceback (most recent call last): TestVersion:2:22: in check_context : in parse_version @@ -123,7 +123,7 @@ def check_context(rows, context): }] `), err: strings.TrimSpace(` -thread too many args: failed to execute function check_context: ip_is_private: expected 1 argument, got 2 +failed to execute function check_context: ip_is_private: expected 1 argument, got 2 Traceback (most recent call last): TestAdditionalContext/too_many_args:7:55: in check_context : in ip_is_private @@ -162,7 +162,7 @@ def check_context(rows, context): }] `), err: strings.TrimSpace(` -thread invalid arg type: failed to execute function check_context: ip_is_private: expected string argument, got int64 (1) +failed to execute function check_context: ip_is_private: expected string argument, got int64 (1) Traceback (most recent call last): TestAdditionalContext/invalid_arg_type:7:55: in check_context : in ip_is_private diff --git a/managed/services/checks/insight_record_test.go b/managed/services/checks/insight_record_test.go new file mode 100644 index 00000000000..ed3a7b68d40 --- /dev/null +++ b/managed/services/checks/insight_record_test.go @@ -0,0 +1,125 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package checks + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/check" + "github.com/percona/pmm/managed/pi/common" + "github.com/percona/pmm/managed/services" +) + +func TestNewCheckResultRecord(t *testing.T) { + t.Parallel() + + c := check.Check{Name: "chk", Summary: "Check title", Description: "Check description", Category: "Performance", Subcategory: "Adv", Interval: check.Standard} + target := services.Target{ + ServiceID: "sid", + ServiceName: "sname", + ServiceType: models.MySQLServiceType, + NodeID: "nid", + NodeName: "nname", + Environment: "prod", + Cluster: "cluster-1", + ReplicationSet: "rs-1", + Region: "us-east-1", + AZ: "us-east-1f", + Labels: map[string]string{"az": "us-east-1f", "region": "us-east-1", "k": "target-wins"}, + } + checkedAt := models.Now() + ri := runInfo{runID: "run-1", triggeredBy: models.CheckTriggeredByUser} + + t.Run("failed finding maps all fields", func(t *testing.T) { + t.Parallel() + + result := check.Result{ + Summary: "sum", + Description: "desc", + ReadMoreURL: "https://example.com", + Severity: common.Error, + Labels: map[string]string{"k": "v"}, + } + + rec := newInsightRecord(c, target, models.CheckResultFailed, result, checkedAt, ri) + + assert.Equal(t, "chk", rec.CheckName) + assert.Equal(t, "Performance", rec.Category) + assert.Equal(t, "Adv", rec.Subcategory) + assert.Equal(t, models.Interval(check.Standard), rec.Interval) + assert.Equal(t, "sid", rec.ServiceID) + assert.Equal(t, "sname", rec.ServiceName) + assert.Equal(t, models.MySQLServiceType, rec.ServiceType) + assert.Equal(t, "nid", rec.NodeID) + assert.Equal(t, "nname", rec.NodeName) + assert.Equal(t, models.CheckResultFailed, rec.Status) + assert.Equal(t, "sum", rec.Summary) + assert.Equal(t, "Check description", rec.Description) + assert.Equal(t, "desc", rec.Outcome) + assert.Equal(t, "prod", rec.Environment) + assert.Equal(t, "cluster-1", rec.Cluster) + assert.Equal(t, "rs-1", rec.ReplicationSet) + assert.Equal(t, "us-east-1", rec.Region) + assert.Equal(t, "us-east-1f", rec.AZ) + assert.Equal(t, "https://example.com", rec.ReadMoreURL) + assert.Equal(t, models.Severity(common.Error), rec.Severity) + assert.Equal(t, checkedAt, rec.CheckedAt) + assert.Equal(t, "run-1", rec.RunID) + assert.Equal(t, models.CheckTriggeredByUser, rec.TriggeredBy) + + labels, err := rec.GetLabels() + require.NoError(t, err) + assert.Equal(t, map[string]string{ + "az": "us-east-1f", + "region": "us-east-1", + "k": "target-wins", + }, labels) + }) + + t.Run("ok outcome falls back to check summary and info severity", func(t *testing.T) { + t.Parallel() + + rec := newInsightRecord(c, target, models.CheckResultOK, check.Result{}, checkedAt, ri) + + assert.Equal(t, models.CheckResultOK, rec.Status) + assert.Equal(t, "Check title", rec.Summary) + assert.Equal(t, "Check passed", rec.Outcome) + assert.Equal(t, models.Severity(common.Info), rec.Severity) + + // target labels are carried even when the check reports none of its own + labels, err := rec.GetLabels() + require.NoError(t, err) + assert.Equal(t, target.Labels, labels) + }) + + t.Run("error outcome falls back to check summary and debug severity", func(t *testing.T) { + t.Parallel() + + result := check.Result{Description: "execution failed"} + + rec := newInsightRecord(c, target, models.CheckResultError, result, checkedAt, ri) + + assert.Equal(t, models.CheckResultError, rec.Status) + assert.Equal(t, "Check title", rec.Summary) + assert.Equal(t, "Check description", rec.Description) + assert.Equal(t, "execution failed", rec.Outcome) + assert.Equal(t, models.Severity(common.Info), rec.Severity) + }) +} diff --git a/managed/services/checks/insight_text.go b/managed/services/checks/insight_text.go new file mode 100644 index 00000000000..9685896493d --- /dev/null +++ b/managed/services/checks/insight_text.go @@ -0,0 +1,159 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package checks + +import ( + "fmt" + "sort" + "strings" + "unicode" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" +) + +// insightTimeFormat mirrors TIME_FORMAT in ui/apps/pmm/src/lib/constants.ts. +const insightTimeFormat = "2006-01-02 15:04:05" + +// The maps below mirror the display labels in ui/apps/pmm/src/lib/constants.ts so that the +// webhook payload reads identically to the UI's "Copy to text" output. + +var insightStatusText = map[models.CheckResultStatus]string{ + models.CheckResultOK: "OK", + models.CheckResultFailed: "Failed", + models.CheckResultError: "Error", +} + +var insightSeverityText = map[models.Severity]string{ + models.Severity(common.Critical): "Critical", + models.Severity(common.Error): "Error", + models.Severity(common.Warning): "Warning", + models.Severity(common.Info): "Info", +} + +var insightIntervalText = map[models.Interval]string{ + models.Standard: "Standard", + models.Rare: "Rare", + models.Frequent: "Frequent", +} + +var insightTriggeredByText = map[models.CheckTriggeredBy]string{ + models.CheckTriggeredByUser: "User", + models.CheckTriggeredByScheduler: "Scheduler", +} + +// insightToText renders an Advisor check result as a human-readable narrative, matching the +// UI's "Copy to text" command (insightToText in ui/apps/pmm/src/pages/advisors/insights/AdvisorInsights.utils.ts). +func insightToText(r *models.Insight) (string, error) { + labelsMap, err := r.GetLabels() + if err != nil { + return "", fmt.Errorf("failed to decode labels for check result %q: %w", r.ID, err) + } + + keys := make([]string, 0, len(labelsMap)) + for k := range labelsMap { + keys = append(keys, k) + } + sort.Strings(keys) + labelPairs := make([]string, 0, len(keys)) + for _, k := range keys { + labelPairs = append(labelPairs, fmt.Sprintf("%s=%s", k, labelsMap[k])) + } + labels := strings.Join(labelPairs, ", ") + + var checkedAt string + if !r.CheckedAt.IsZero() { + checkedAt = r.CheckedAt.Format(insightTimeFormat) + } + + details := [][2]string{ + {"ID", r.ID}, + {"Run ID", r.RunID}, + {"Check Name", r.CheckName}, + {"Category", r.Category}, + {"Sub category", r.Subcategory}, + {"Service Name", r.ServiceName}, + {"Service Type", string(r.ServiceType)}, + {"Node Name", r.NodeName}, + {"Environment", r.Environment}, + {"Cluster", r.Cluster}, + {"Replication Set", r.ReplicationSet}, + {"Interval", insightIntervalLabel(r.Interval)}, + {"Triggered By", insightTriggeredByText[r.TriggeredBy]}, + {"Read", insightReadLabel(r.IsRead)}, + {"Summary", r.Summary}, + {"Description", r.Description}, + {"Outcome", r.Outcome}, + {"Severity", insightSeverityLabel(r.Severity)}, + {"Read More", r.ReadMoreURL}, + {"Labels", labels}, + } + + var detailLines []string + for _, d := range details { + if d[1] == "" { + continue + } + detailLines = append(detailLines, fmt.Sprintf(" %s: %s", d[0], d[1])) + } + + return fmt.Sprintf( + "The Advisor Check %q completed at %s with status %q.\n\nCheck Details:\n%s", + r.Summary, checkedAt, insightStatusLabel(r.Status), strings.Join(detailLines, "\n"), + ), nil +} + +// insightIntervalLabel maps a stored interval to its display label; an empty interval means +// standard (see convertModelInterval in managed/services/management/checks.go). +func insightIntervalLabel(interval models.Interval) string { + if interval == "" { + return insightIntervalText[models.Standard] + } + if label, ok := insightIntervalText[interval]; ok { + return label + } + return "Unspecified" +} + +func insightStatusLabel(status models.CheckResultStatus) string { + if label, ok := insightStatusText[status]; ok { + return label + } + return "Unspecified" +} + +func insightSeverityLabel(severity models.Severity) string { + if label, ok := insightSeverityText[severity]; ok { + return label + } + return "Unspecified" +} + +func insightReadLabel(isRead bool) string { + if isRead { + return "Read" + } + return "Unread" +} + +func capitalize(s string) string { + if s == "" { + return "" + } + r := []rune(s) + r[0] = unicode.ToUpper(r[0]) + return string(r) +} diff --git a/managed/services/checks/insight_text_test.go b/managed/services/checks/insight_text_test.go new file mode 100644 index 00000000000..f0d6802ca1d --- /dev/null +++ b/managed/services/checks/insight_text_test.go @@ -0,0 +1,121 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package checks + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" +) + +// TestInsightToText locks the Go output to the UI's "Copy to text" format +// (insightToText in ui/apps/pmm/src/pages/advisors/insights/AdvisorInsights.utils.ts). +func TestInsightToText(t *testing.T) { + t.Parallel() + + t.Run("all fields", func(t *testing.T) { + t.Parallel() + + r := &models.Insight{ + ID: "insight-1", + RunID: "run-1", + CheckName: "mysql_version", + Subcategory: "version_advisor", + Category: "Performance", + ServiceName: "mysql-prod", + ServiceType: "mysql", + NodeName: "node-a", + Environment: "prod", + Cluster: "cluster-1", + ReplicationSet: "rs0", + Interval: models.Standard, + TriggeredBy: models.CheckTriggeredByScheduler, + IsRead: false, + Summary: "Outdated MySQL version", + Description: "The MySQL version is old", + Outcome: "Upgrade recommended", + Severity: models.Severity(common.Warning), + ReadMoreURL: "https://example.com/more", + Status: models.CheckResultFailed, + CheckedAt: time.Date(2026, 7, 16, 10, 30, 0, 0, time.UTC), + } + require.NoError(t, r.SetLabels(map[string]string{"tier": "db", "env": "prod"})) + + want := `The Advisor Check "Outdated MySQL version" completed at 2026-07-16 10:30:00 with status "Failed". + +Check Details: + ID: insight-1 + Run ID: run-1 + Check Name: mysql_version + Category: Performance + Sub category: version_advisor + Service Name: mysql-prod + Service Type: mysql + Node Name: node-a + Environment: prod + Cluster: cluster-1 + Replication Set: rs0 + Interval: Standard + Triggered By: Scheduler + Read: Unread + Summary: Outdated MySQL version + Description: The MySQL version is old + Outcome: Upgrade recommended + Severity: Warning + Read More: https://example.com/more + Labels: env=prod, tier=db` + + got, err := insightToText(r) + require.NoError(t, err) + require.Equal(t, want, got) + }) + + t.Run("empty fields are omitted", func(t *testing.T) { + t.Parallel() + + r := &models.Insight{ + ID: "insight-2", + RunID: "run-2", + CheckName: "pg_check", + Summary: "Issue found", + Severity: models.Severity(common.Error), + Status: models.CheckResultFailed, + TriggeredBy: models.CheckTriggeredByUser, + IsRead: true, + CheckedAt: time.Date(2026, 7, 16, 12, 0, 0, 0, time.UTC), + } + + want := `The Advisor Check "Issue found" completed at 2026-07-16 12:00:00 with status "Failed". + +Check Details: + ID: insight-2 + Run ID: run-2 + Check Name: pg_check + Interval: Standard + Triggered By: User + Read: Read + Summary: Issue found + Severity: Error` + + got, err := insightToText(r) + require.NoError(t, err) + require.Equal(t, want, got) + }) +} diff --git a/managed/services/checks/notification.go b/managed/services/checks/notification.go new file mode 100644 index 00000000000..ee17f27b0bd --- /dev/null +++ b/managed/services/checks/notification.go @@ -0,0 +1,162 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package checks + +import ( + "context" + "fmt" + "strings" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" +) + +// maybeSendAdvisorNotification emails the completed run's insights to the configured recipients +// when notifications are enabled. It is best-effort: every failure is logged and swallowed so it +// never affects the check run. +func (s *Service) maybeSendAdvisorNotification(ctx context.Context, runID string, triggeredBy models.CheckTriggeredBy) { + settings, err := models.GetSettings(s.db.Querier) + if err != nil { + s.l.Warnf("Advisor notification: failed to load settings: %v", err) + return + } + an := settings.AdvisorNotifications + if !settings.IsAdvisorNotificationsEnabled() { + return + } + // ChangeSettings rejects this combination, so it only happens when notifications were enabled + // through PMM_ENABLE_ADVISOR_NOTIFICATIONS without recipients ever being configured. + if len(an.EmailAddresses) == 0 { + s.l.Warnf("Advisor notification: enabled, but no recipients are configured, so run %s was "+ + "not emailed. Set the Advisor notification email addresses in the PMM settings.", runID) + return + } + + results, _, err := s.GetInsights(ctx, models.InsightFilters{RunID: runID}, 0, 0) + if err != nil { + s.l.Warnf("Advisor notification: failed to load results for run %s: %v", runID, err) + return + } + + threshold := an.SeverityThreshold + if threshold == common.Unknown { + threshold = common.Error + } + + sCounts := make(map[common.Severity]int) + tCounts := make(map[models.ServiceType]int) + texts := make([]string, 0, len(results)) + for _, r := range results { + severity := common.Severity(r.Severity) + // Keep only insights at least as severe as the threshold (a smaller value is more severe). + if severity < common.Critical || severity > threshold { + continue + } + text, err := insightToText(r) + if err != nil { + s.l.Warnf("Advisor notification: failed to format insight %s: %v", r.ID, err) + continue + } + sCounts[severity]++ + tCounts[r.ServiceType]++ + texts = append(texts, text) + } + + if len(texts) == 0 { + return + } + + subject := fmt.Sprintf("PMM Advisor Insights: %d finding(s) for run %s", len(texts), runID) + body := buildAdvisorEmailReport(runID, triggeredBy, threshold, sCounts, tCounts, texts) + + err = s.sendAdvisorEmail(an.EmailAddresses, subject, body) + if err != nil { + s.l.Warnf("Advisor notification: failed to email run %s: %v", runID, err) + return + } + s.l.Infof("Advisor notification: emailed %d insight(s) for run %s", len(texts), runID) +} + +// advisorTechnologies lists the technologies advisor checks run against, in report order, paired +// with the service type insights record. Checks can only target these, so the per-technology +// summary covers every insight. +var advisorTechnologies = []struct { + serviceType models.ServiceType + label string +}{ + {models.MySQLServiceType, "MySQL"}, + {models.PostgreSQLServiceType, "PostgreSQL"}, + {models.MongoDBServiceType, "MongoDB"}, +} + +// buildAdvisorEmailReport composes the notification email body: a brief introduction, per-severity +// and per-technology summaries, suggested next steps, and then the insights (formatted like the +// UI's "Copy to text") one after another. +func buildAdvisorEmailReport( + runID string, + triggeredBy models.CheckTriggeredBy, + threshold common.Severity, + sCounts map[common.Severity]int, + tCounts map[models.ServiceType]int, + insights []string, +) string { + var b strings.Builder + + fmt.Fprintf(&b, "Percona Monitoring and Management runs Advisor checks against your monitored "+ + "databases to surface potential issues. This report covers run %s, which was %s. It found %d "+ + "insight(s) at or above the %q severity level that may need your attention.\n\n", + runID, triggerPhrase(triggeredBy), len(insights), capitalize(threshold.String())) + + b.WriteString("Findings by severity:\n") + // Iterate from the most severe advisor level down to the configured threshold + // (Critical=3 .. threshold), skipping the retired Notice level. + for sev := common.Critical; sev <= threshold; sev++ { + if sev == common.Notice { + continue + } + fmt.Fprintf(&b, " %s: %d\n", capitalize(sev.String()), sCounts[sev]) + } + + // Every technology is listed, zero included, so the reader can tell "no findings" apart from + // "not covered by this report". + b.WriteString("\nFindings by technology:\n") + for _, t := range advisorTechnologies { + fmt.Fprintf(&b, " %s: %d\n", t.label, tCounts[t.serviceType]) + } + + b.WriteString("\nNext steps:\n") + b.WriteString(" - Review the insights below, addressing the most severe findings first.\n") + b.WriteString(" - Follow each insight's \"Read More\" link for remediation guidance.\n") + b.WriteString(" - Prioritize issues affecting production services.\n") + b.WriteString(" - See full details in PMM under Advisors -> Insights.\n\n") + + fmt.Fprintf(&b, "Advisor Insights (%d):\n\n", len(insights)) + b.WriteString(strings.Join(insights, "\n\n")) + + return b.String() +} + +// triggerPhrase describes, in prose, how the run was initiated. +func triggerPhrase(triggeredBy models.CheckTriggeredBy) string { + switch triggeredBy { + case models.CheckTriggeredByScheduler: + return "run automatically on schedule" + case models.CheckTriggeredByUser: + return "triggered manually by an operator" + default: + return "run" + } +} diff --git a/managed/services/checks/notification_test.go b/managed/services/checks/notification_test.go new file mode 100644 index 00000000000..ed591b502ea --- /dev/null +++ b/managed/services/checks/notification_test.go @@ -0,0 +1,67 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package checks + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" +) + +func TestBuildAdvisorEmailReport(t *testing.T) { + t.Parallel() + + sCounts := map[common.Severity]int{ + common.Error: 1, + common.Warning: 1, + } + tCounts := map[models.ServiceType]int{ + models.MySQLServiceType: 1, + models.MongoDBServiceType: 1, + } + insights := []string{"Insight A", "Insight B"} + + want := `Percona Monitoring and Management runs Advisor checks against your monitored databases to surface potential issues. This report covers run 7394c7d1-53ae-4094-98b3-b7fe561dbac1, which was triggered manually by an operator. It found 2 insight(s) at or above the "Warning" severity level that may need your attention. + +Findings by severity: + Critical: 0 + Error: 1 + Warning: 1 + +Findings by technology: + MySQL: 1 + PostgreSQL: 0 + MongoDB: 1 + +Next steps: + - Review the insights below, addressing the most severe findings first. + - Follow each insight's "Read More" link for remediation guidance. + - Prioritize issues affecting production services. + - See full details in PMM under Advisors -> Insights. + +Advisor Insights (2): + +Insight A + +Insight B` + + got := buildAdvisorEmailReport("7394c7d1-53ae-4094-98b3-b7fe561dbac1", models.CheckTriggeredByUser, common.Warning, sCounts, + tCounts, insights) + require.Equal(t, want, got) +} diff --git a/managed/services/checks/registry.go b/managed/services/checks/registry.go index 16ece09a879..6dfee342bdf 100644 --- a/managed/services/checks/registry.go +++ b/managed/services/checks/registry.go @@ -24,7 +24,7 @@ import ( "github.com/percona/pmm/managed/services" ) -// registry stores alerts and delay information by IDs. +// registry keeps a snapshot of the current check results and exposes it as the insights metric. type registry struct { rw sync.RWMutex // Results stored grouped by interval and by check name. It allows us to remove results for specific group. @@ -40,8 +40,8 @@ func newRegistry() *registry { Namespace: prometheusNamespace, Subsystem: prometheusSubsystem, Name: "check_insights", - Help: "Number of advisor insights per service type, advisor and check name", - }, []string{"service_type", "advisor", "check_name"}), + Help: "Number of advisor insights per service type, service name, advisor, check name and severity", + }, []string{"service_type", "service_name", "advisor", "check_name", "severity"}), } } @@ -75,6 +75,41 @@ func (r *registry) deleteByName(checkNames []string) { } } +// deleteByNameAndService removes results for the specified checks, but only those +// produced for the specified services, leaving other services' results in place. +func (r *registry) deleteByNameAndService(checkNames, serviceIDs []string) { + r.rw.Lock() + defer r.rw.Unlock() + + wanted := make(map[string]struct{}, len(serviceIDs)) + for _, id := range serviceIDs { + wanted[id] = struct{}{} + } + + for _, intervalGroup := range r.checkResults { + for _, name := range checkNames { + results, ok := intervalGroup[name] + if !ok { + continue + } + + kept := make([]services.CheckResult, 0, len(results)) + for _, result := range results { + _, drop := wanted[result.Target.ServiceID] + if !drop { + kept = append(kept, result) + } + } + + if len(kept) == 0 { + delete(intervalGroup, name) + continue + } + intervalGroup[name] = kept + } + } +} + // deleteByInterval removes results for specified interval. func (r *registry) deleteByInterval(interval check.Interval) { r.rw.Lock() @@ -83,7 +118,7 @@ func (r *registry) deleteByInterval(interval check.Interval) { delete(r.checkResults, interval) } -// cleanup removes all advisors results form registry. +// cleanup removes all check results from the registry. func (r *registry) cleanup() { r.rw.Lock() defer r.rw.Unlock() @@ -91,19 +126,15 @@ func (r *registry) cleanup() { r.checkResults = make(map[check.Interval]map[string][]services.CheckResult) } -// getCheckResults returns checks results for the given service. If serviceID is empty it returns results for all services. -func (r *registry) getCheckResults(serviceID string) []services.CheckResult { +// getCheckResults returns checks results for all services. +func (r *registry) getCheckResults() []services.CheckResult { r.rw.RLock() defer r.rw.RUnlock() var results []services.CheckResult for _, intervalGroup := range r.checkResults { for _, checkNameGroup := range intervalGroup { - for _, checkResult := range checkNameGroup { - if serviceID == "" || checkResult.Target.ServiceID == serviceID { - results = append(results, checkResult) - } - } + results = append(results, checkNameGroup...) } } @@ -118,9 +149,9 @@ func (r *registry) Describe(ch chan<- *prom.Desc) { // Collect implements prom.Collector. func (r *registry) Collect(ch chan<- prom.Metric) { r.mInsights.Reset() - res := r.getCheckResults("") + res := r.getCheckResults() for _, re := range res { - r.mInsights.WithLabelValues(string(re.Target.ServiceType), re.AdvisorName, re.CheckName).Inc() + r.mInsights.WithLabelValues(string(re.Target.ServiceType), re.Target.ServiceName, re.Subcategory, re.CheckName, re.Result.Severity.String()).Inc() } r.mInsights.Collect(ch) } diff --git a/managed/services/checks/registry_test.go b/managed/services/checks/registry_test.go index 90f842bd470..06207c91795 100644 --- a/managed/services/checks/registry_test.go +++ b/managed/services/checks/registry_test.go @@ -16,11 +16,14 @@ package checks import ( + "strings" "testing" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/percona/pmm/managed/models" "github.com/percona/pmm/managed/pi/check" "github.com/percona/pmm/managed/pi/common" "github.com/percona/pmm/managed/services" @@ -63,7 +66,7 @@ func TestRegistry(t *testing.T) { Summary: "check summary 2", Description: "check description 2", ReadMoreURL: "https://www.example2.com", - Severity: common.Notice, + Severity: common.Info, Labels: map[string]string{ "qux": "baz", }, @@ -76,7 +79,7 @@ func TestRegistry(t *testing.T) { // Empty interval means standard checkResults[1].Interval = check.Standard - collectedAlerts := r.getCheckResults("") + collectedAlerts := r.getCheckResults() assert.ElementsMatch(t, checkResults, collectedAlerts) }) @@ -117,7 +120,7 @@ func TestRegistry(t *testing.T) { Summary: "check summary 2", Description: "check description 2", ReadMoreURL: "https://www.example2.com", - Severity: common.Notice, + Severity: common.Info, Labels: map[string]string{ "qux": "baz", }, @@ -128,7 +131,7 @@ func TestRegistry(t *testing.T) { r.set(checkResults) r.deleteByInterval(check.Standard) - collectedAlerts := r.getCheckResults("") + collectedAlerts := r.getCheckResults() require.Len(t, collectedAlerts, 1) assert.Equal(t, checkResults[1], collectedAlerts[0]) }) @@ -170,7 +173,7 @@ func TestRegistry(t *testing.T) { Summary: "check summary 2", Description: "check description 2", ReadMoreURL: "https://www.example2.com", - Severity: common.Notice, + Severity: common.Info, Labels: map[string]string{ "qux": "baz", }, @@ -181,11 +184,44 @@ func TestRegistry(t *testing.T) { r.set(checkResults) r.deleteByName([]string{"name1"}) - collectedAlerts := r.getCheckResults("") + collectedAlerts := r.getCheckResults() require.Len(t, collectedAlerts, 1) assert.Equal(t, checkResults[1], collectedAlerts[0]) }) + t.Run("delete check result by name and service", func(t *testing.T) { + r := newRegistry() + checkResults := []services.CheckResult{ + { + CheckName: "name1", + Interval: check.Standard, + Target: services.Target{AgentID: "123", ServiceID: "123"}, + Result: check.Result{Summary: "service 123", Severity: common.Warning}, + }, + { + CheckName: "name1", + Interval: check.Standard, + Target: services.Target{AgentID: "321", ServiceID: "321"}, + Result: check.Result{Summary: "service 321", Severity: common.Warning}, + }, + { + CheckName: "name2", + Interval: check.Standard, + Target: services.Target{AgentID: "123", ServiceID: "123"}, + Result: check.Result{Summary: "other check", Severity: common.Info}, + }, + } + + r.set(checkResults) + r.deleteByNameAndService([]string{"name1"}, []string{"123"}) + + collectedAlerts := r.getCheckResults() + require.Len(t, collectedAlerts, 2) + assert.NotContains(t, collectedAlerts, checkResults[0]) + assert.Contains(t, collectedAlerts, checkResults[1]) + assert.Contains(t, collectedAlerts, checkResults[2]) + }) + t.Run("empty interval recognized as standard", func(t *testing.T) { r := newRegistry() checkResults := []services.CheckResult{ @@ -222,7 +258,7 @@ func TestRegistry(t *testing.T) { Summary: "check summary 2", Description: "check description 2", ReadMoreURL: "https://www.example2.com", - Severity: common.Notice, + Severity: common.Info, Labels: map[string]string{ "qux": "baz", }, @@ -233,7 +269,35 @@ func TestRegistry(t *testing.T) { r.set(checkResults) r.deleteByInterval(check.Standard) - collectedAlerts := r.getCheckResults("") + collectedAlerts := r.getCheckResults() assert.Empty(t, collectedAlerts) }) } + +func TestRegistryInsightsMetric(t *testing.T) { + r := newRegistry() + r.set([]services.CheckResult{ + { + CheckName: "mysql_version", + Subcategory: "adv", + Interval: check.Standard, + Target: services.Target{ + ServiceID: "svc-id", + ServiceName: "mysql-prod", + ServiceType: models.MySQLServiceType, + }, + Result: check.Result{ + Summary: "outdated", + Severity: common.Error, + }, + }, + }) + + const expected = ` +# HELP pmm_managed_advisor_check_insights Number of advisor insights per service type, service name, advisor, check name and severity +# TYPE pmm_managed_advisor_check_insights gauge +pmm_managed_advisor_check_insights{advisor="adv",check_name="mysql_version",service_name="mysql-prod",service_type="mysql",severity="error"} 1 +` + err := testutil.CollectAndCompare(r, strings.NewReader(expected), "pmm_managed_advisor_check_insights") + require.NoError(t, err) +} diff --git a/managed/services/checks/run_lifecycle_test.go b/managed/services/checks/run_lifecycle_test.go new file mode 100644 index 00000000000..9986565d29c --- /dev/null +++ b/managed/services/checks/run_lifecycle_test.go @@ -0,0 +1,126 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package checks + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/dialects/postgresql" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" + "github.com/percona/pmm/managed/utils/testdb" +) + +func TestRunLifecycle(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + t.Cleanup(func() { + require.NoError(t, sqlDB.Close()) + }) + + db := reform.NewDB(sqlDB, postgresql.Dialect, nil) + s := New(db, nil, nil, nil) + + insight := func(t *testing.T, runID string, status models.CheckResultStatus, severity common.Severity, checkedAt time.Time) { + t.Helper() + require.NoError(t, models.CreateInsight(t.Context(), db.Querier, &models.Insight{ + RunID: runID, + CheckName: "check_" + string(status), + ServiceID: "svc-1", + ServiceType: models.MySQLServiceType, + Interval: models.Standard, + Status: status, + Severity: models.Severity(severity), + CheckedAt: checkedAt, + })) + } + + t.Run("a started run is open, and closing it stores derived totals", func(t *testing.T) { + ri := runInfo{runID: "run-lifecycle-1", triggeredBy: models.CheckTriggeredByUser} + + s.startRun(t.Context(), ri) + + run := &models.AdvisorRun{ID: ri.runID} + require.NoError(t, db.Reload(run)) + assert.True(t, run.IsRunning()) + assert.Equal(t, models.CheckTriggeredByUser, run.TriggeredBy) + + checkedAt := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) + insight(t, ri.runID, models.CheckResultFailed, common.Warning, checkedAt) + insight(t, ri.runID, models.CheckResultError, common.Info, checkedAt) + + s.finishRun(t.Context(), ri.runID) + + require.NoError(t, db.Reload(run)) + require.False(t, run.IsRunning()) + assert.Equal(t, 1, run.FindingsCount) + assert.Equal(t, 1, run.ErrorsCount) + assert.Equal(t, 1, run.ServicesCount) + assert.Equal(t, 2, run.ChecksCount) + + counts, err := run.GetSeverityCounts() + require.NoError(t, err) + assert.Equal(t, map[models.Severity]int{models.Severity(common.Warning): 1}, counts) + }) + + t.Run("an interrupted run is closed at its last insight", func(t *testing.T) { + ri := runInfo{runID: "run-lifecycle-interrupted", triggeredBy: models.CheckTriggeredByScheduler} + s.startRun(t.Context(), ri) + + last := time.Date(2026, 8, 2, 8, 5, 0, 0, time.UTC) + insight(t, ri.runID, models.CheckResultFailed, common.Error, time.Date(2026, 8, 2, 8, 0, 0, 0, time.UTC)) + insight(t, ri.runID, models.CheckResultOK, common.Info, last) + + // stands in for a restart: the run was never closed out + s.finalizeInterruptedRuns(t.Context()) + + run := &models.AdvisorRun{ID: ri.runID} + require.NoError(t, db.Reload(run)) + require.False(t, run.IsRunning()) + require.NotNil(t, run.FinishedAt) + assert.Equal(t, last, *run.FinishedAt) + assert.Equal(t, 1, run.FindingsCount) + }) + + t.Run("an interrupted run with no insights is closed at its start", func(t *testing.T) { + ri := runInfo{runID: "run-lifecycle-empty", triggeredBy: models.CheckTriggeredByUser} + s.startRun(t.Context(), ri) + + started := &models.AdvisorRun{ID: ri.runID} + require.NoError(t, db.Reload(started)) + + s.finalizeInterruptedRuns(t.Context()) + + run := &models.AdvisorRun{ID: ri.runID} + require.NoError(t, db.Reload(run)) + require.False(t, run.IsRunning()) + require.NotNil(t, run.FinishedAt) + assert.Equal(t, started.StartedAt, *run.FinishedAt) + assert.Zero(t, run.FindingsCount) + }) + + t.Run("closing an already closed run leaves nothing open", func(t *testing.T) { + s.finalizeInterruptedRuns(t.Context()) + + open, err := models.FindUnfinishedAdvisorRuns(t.Context(), db.Querier) + require.NoError(t, err) + assert.Empty(t, open) + }) +} diff --git a/managed/services/checks/smtp.go b/managed/services/checks/smtp.go new file mode 100644 index 00000000000..fea8f6964e3 --- /dev/null +++ b/managed/services/checks/smtp.go @@ -0,0 +1,125 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package checks + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "os" + "strconv" + + gomail "gopkg.in/mail.v2" +) + +// smtpConfig holds the SMTP settings PMM reuses from the bundled Grafana. PMM Server configures +// Grafana's SMTP via GF_SMTP_* environment variables, and the pmm-managed process inherits them, +// so email delivery shares Grafana's single SMTP configuration instead of duplicating it. +type smtpConfig struct { + enabled bool + host string + user string + password string + fromAddress string + fromName string + skipVerify bool + startTLS string +} + +// smtpConfigFromEnv reads the Grafana SMTP settings from the inherited GF_SMTP_* environment. +func smtpConfigFromEnv() smtpConfig { + enabled, _ := strconv.ParseBool(os.Getenv("GF_SMTP_ENABLED")) + skipVerify, _ := strconv.ParseBool(os.Getenv("GF_SMTP_SKIP_VERIFY")) + return smtpConfig{ + enabled: enabled, + host: os.Getenv("GF_SMTP_HOST"), + user: os.Getenv("GF_SMTP_USER"), + password: os.Getenv("GF_SMTP_PASSWORD"), + fromAddress: os.Getenv("GF_SMTP_FROM_ADDRESS"), + fromName: os.Getenv("GF_SMTP_FROM_NAME"), + skipVerify: skipVerify, + startTLS: os.Getenv("GF_SMTP_STARTTLS_POLICY"), + } +} + +// dialer builds a gomail dialer from the config, mirroring Grafana's createDialer +// (grafana pkg/services/notifications/smtp.go). +func (c smtpConfig) dialer() (*gomail.Dialer, error) { + host, portStr, err := net.SplitHostPort(c.host) + if err != nil { + return nil, fmt.Errorf("invalid GF_SMTP_HOST %q: %w", c.host, err) + } + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("invalid SMTP port in %q: %w", c.host, err) + } + + d := gomail.NewDialer(host, port, c.user, c.password) + d.TLSConfig = &tls.Config{ + ServerName: host, + // Honors GF_SMTP_SKIP_VERIFY, matching Grafana's own SMTP behavior. + InsecureSkipVerify: c.skipVerify, //nolint:gosec + } + switch c.startTLS { + case "NoStartTLS": + d.StartTLSPolicy = gomail.NoStartTLS + case "MandatoryStartTLS": + d.StartTLSPolicy = gomail.MandatoryStartTLS + default: + d.StartTLSPolicy = gomail.OpportunisticStartTLS + } + + return d, nil +} + +// sendAdvisorEmail emails a pre-composed report to the recipients using the Grafana-configured +// SMTP server. +func (s *Service) sendAdvisorEmail(to []string, subject, body string) error { + if len(to) == 0 { + return errors.New("no recipient addresses configured") + } + + cfg := smtpConfigFromEnv() + if !cfg.enabled { + return errors.New("SMTP is not enabled (GF_SMTP_ENABLED)") + } + if cfg.fromAddress == "" { + return errors.New("no sender address configured (GF_SMTP_FROM_ADDRESS)") + } + + return s.sendEmail(cfg, to, subject, body) +} + +// sendEmail sends a plain-text email to the recipients using the Grafana-configured SMTP server. +func (s *Service) sendEmail(cfg smtpConfig, to []string, subject, body string) error { + dialer, err := cfg.dialer() + if err != nil { + return err + } + + m := gomail.NewMessage() + if cfg.fromName != "" { + m.SetAddressHeader("From", cfg.fromAddress, cfg.fromName) + } else { + m.SetHeader("From", cfg.fromAddress) + } + m.SetHeader("To", to...) + m.SetHeader("Subject", subject) + m.SetBody("text/plain", body) + + return dialer.DialAndSend(m) +} diff --git a/managed/services/grafana/auth_server.go b/managed/services/grafana/auth_server.go index 7bb9927c306..4692cf81c71 100644 --- a/managed/services/grafana/auth_server.go +++ b/managed/services/grafana/auth_server.go @@ -70,7 +70,6 @@ var rules = map[string]role{ "/v1/alerting/rules": editor, "/v1/advisors": editor, "/v1/advisors/checks:": editor, - "/v1/advisors/failedServices": editor, "/v1/actions": viewer, "/v1/actions:": viewer, "/v1/backups": admin, @@ -174,14 +173,18 @@ var ErrInvalidUserID = errors.New("InvalidUserID") // ErrCannotGetUserID is returned when we cannot retrieve user ID. var ErrCannotGetUserID = errors.New("CannotGetUserID") +// cacheItem holds a cached authentication outcome: either the resolved user +// or, for definitive rejections, the authentication error. type cacheItem struct { u authUser + authErr *authError // non-nil for a cached authentication failure created time.Time } // clientInterface exist only to make fuzzing simpler. type clientInterface interface { getAuthUser(ctx context.Context, authHeaders http.Header, l *logrus.Entry) (authUser, error) + rotateSessionToken(ctx context.Context, authHeaders http.Header) ([]string, error) } // AuthServer authenticates incoming requests via Grafana API. @@ -258,7 +261,13 @@ func (s *AuthServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ctx, cancel := context.WithTimeout(req.Context(), authenticationTimeout) defer cancel() - authUser, authErr := s.authenticate(ctx, req, l) + authUser, newCookies, authErr := s.authenticate(ctx, req, l) + // Propagate a rotated Grafana session cookie regardless of the outcome: + // rotation invalidates the old token, so the client must receive the new + // one even on a response nginx turns into an error. + for _, c := range newCookies { + rw.Header().Add("Set-Cookie", c) + } if authErr != nil { // copy grpc-gateway behavior: set correct codes, set both "error" and "message" m := map[string]any{ @@ -322,7 +331,7 @@ func (s *AuthServer) maybeAddLBACFilters(ctx context.Context, rw http.ResponseWr if userID == 0 { l.Debugf("Getting authenticated user info") - authUser, err := s.getAuthUser(ctx, req, l) + authUser, _, err := s.getAuthUser(ctx, req, l) if err != nil { return ErrCannotGetUserID } @@ -509,16 +518,18 @@ func isLocalAgentConnection(req *http.Request) bool { } // authenticate checks if user has access to a specific path. -// It returns user information retrieved during authentication. +// It returns user information retrieved during authentication, and the Set-Cookie +// headers of a Grafana session token rotation, if one happened; they must reach +// the client even when access is denied, since rotation invalidates the old token. // Paths which require no Grafana role return zero value for // some user fields such as authUser.userID. -func (s *AuthServer) authenticate(ctx context.Context, req *http.Request, l *logrus.Entry) (*authUser, *authError) { +func (s *AuthServer) authenticate(ctx context.Context, req *http.Request, l *logrus.Entry) (*authUser, []string, *authError) { // Unescape the URL-encoded parts of the path. p := req.URL.Path cleanedPath, err := cleanPath(p) if err != nil { l.Warnf("Error while unescaping path %s: %q", p, err) - return nil, &authError{ + return nil, nil, &authError{ code: codes.Internal, message: "Internal server error.", } @@ -529,7 +540,7 @@ func (s *AuthServer) authenticate(ctx context.Context, req *http.Request, l *log if minRole == none { l.Debugf("Minimal required role is %s, granting access without checking Grafana.", minRole) - return nil, nil + return nil, nil, nil } var user *authUser @@ -545,28 +556,31 @@ func (s *AuthServer) authenticate(ctx context.Context, req *http.Request, l *log userID: 0, } } - } else { + } + + var newCookies []string + if user == nil { var authErr *authError // Get authenticated user from Grafana - user, authErr = s.getAuthUser(ctx, req, l) + user, newCookies, authErr = s.getAuthUser(ctx, req, l) if authErr != nil { - return nil, authErr + return nil, nil, authErr } } l = l.WithField("role", user.role.String()) if user.role == grafanaAdmin { l.Debugf("Grafana admin, granting access.") - return user, nil + return user, newCookies, nil } if minRole <= user.role { l.Debugf("Minimal required role is %s, granting access.", minRole) - return user, nil + return user, newCookies, nil } l.Warnf("Minimal required role is %s, denying access.", minRole) - return nil, &authError{code: codes.PermissionDenied, message: "Access denied"} + return nil, newCookies, &authError{code: codes.PermissionDenied, message: "Access denied"} } func cleanPath(p string) (string, error) { @@ -587,13 +601,13 @@ func cleanPath(p string) (string, error) { return u.String(), nil } -func (s *AuthServer) getAuthUser(ctx context.Context, req *http.Request, l *logrus.Entry) (*authUser, *authError) { +func (s *AuthServer) getAuthUser(ctx context.Context, req *http.Request, l *logrus.Entry) (*authUser, []string, *authError) { // check Grafana with some headers from request authHeaders := s.authHeaders(req) j, err := json.Marshal(authHeaders) if err != nil { l.Warnf("%s", err) - return nil, &authError{code: codes.Internal, message: "Internal server error."} + return nil, nil, &authError{code: codes.Internal, message: "Internal server error."} } hash := base64.StdEncoding.EncodeToString(j) s.rw.RLock() @@ -603,7 +617,10 @@ func (s *AuthServer) getAuthUser(ctx context.Context, req *http.Request, l *logr // cacheInvalidationInterval, so without this an entry could be served for almost // twice that long. Re-fetch once an entry is older than the interval. if ok && time.Since(item.created) < cacheInvalidationInterval { - return &item.u, nil + if item.authErr != nil { + return nil, nil, item.authErr + } + return &item.u, nil, nil } return s.retrieveRole(ctx, hash, authHeaders, l) @@ -622,20 +639,46 @@ func (s *AuthServer) authHeaders(req *http.Request) http.Header { return authHeaders } -func (s *AuthServer) retrieveRole(ctx context.Context, hash string, authHeaders http.Header, l *logrus.Entry) (*authUser, *authError) { +func (s *AuthServer) retrieveRole(ctx context.Context, hash string, authHeaders http.Header, l *logrus.Entry) (*authUser, []string, *authError) { authUser, err := s.c.getAuthUser(ctx, authHeaders, l) + var newCookies []string + if err != nil && canRetryWithRotatedSession(err, authHeaders) { + rotated, retryUser, retryErr := s.retryWithRotatedSession(ctx, authHeaders, l) + if retryErr != nil { + l.Debugf("Failed to rotate Grafana session token: %s.", retryErr) + } else { + authUser, err = retryUser, nil + newCookies = rotated + } + } if err != nil { l.Warnf("%s", err) cErr, ok := errors.AsType[*clientError](err) - if ok { - code := codes.Internal - if cErr.Code == 401 || cErr.Code == 403 { - code = codes.Unauthenticated + if !ok { + return nil, nil, &authError{code: codes.Internal, message: "Internal server error."} + } + code := codes.Internal + if cErr.Code == 401 || cErr.Code == 403 { + code = codes.Unauthenticated + } + authErr := &authError{code: code, message: cErr.ErrorMessage} + if code == codes.Unauthenticated { + // Cache definitive rejections: clients polling with a dead session would + // otherwise cost Grafana two failed lookups (user + rotation) per request. + // Internal errors are not cached so a Grafana hiccup does not lock + // clients out for the cache lifetime. + s.rw.Lock() + s.cache[hash] = cacheItem{ + authErr: authErr, + created: time.Now(), } - return nil, &authError{code: code, message: cErr.ErrorMessage} + s.rw.Unlock() } - return nil, &authError{code: codes.Internal, message: "Internal server error."} + return nil, nil, authErr } + // Cache under the hash of the original headers even after a rotation: requests + // still carrying the old cookie (sent before the browser stores the new one) + // keep working for the cache lifetime. s.rw.Lock() s.cache[hash] = cacheItem{ u: authUser, @@ -643,5 +686,95 @@ func (s *AuthServer) retrieveRole(ctx context.Context, hash string, authHeaders } s.rw.Unlock() - return &authUser, nil + return &authUser, newCookies, nil +} + +// canRetryWithRotatedSession reports whether the Grafana auth failure may be caused +// by a session token past its rotation interval: Grafana rejects such tokens with +// 401 and expects the client to rotate them explicitly. Only cookie-based sessions +// rotate; Basic auth and API tokens do not. +func canRetryWithRotatedSession(err error, authHeaders http.Header) bool { + cErr, ok := errors.AsType[*clientError](err) + if !ok || cErr.Code != http.StatusUnauthorized { + return false + } + + return !hasAuthorizationHeader(authHeaders) && authHeaders.Get("Cookie") != "" +} + +// retryWithRotatedSession rotates the Grafana session token from the request cookie +// and retries the user lookup with the rotated one. It returns the Set-Cookie headers +// that must reach the client: API-only clients such as the PMM UI never load Grafana +// pages, so this is their only way to receive the rotated token before the old one +// becomes invalid. +func (s *AuthServer) retryWithRotatedSession(ctx context.Context, authHeaders http.Header, l *logrus.Entry) ([]string, authUser, error) { + setCookies, err := s.c.rotateSessionToken(ctx, authHeaders) + if err != nil { + return nil, emptyUser, err + } + + headers := authHeaders + if len(setCookies) != 0 { + headers = headersWithRotatedCookies(authHeaders, setCookies) + } + u, err := s.c.getAuthUser(ctx, headers, l) + if err != nil { + return nil, emptyUser, err + } + + return sessionCookieForClient(setCookies), u, nil +} + +// sessionCookieName is the name of the Grafana session cookie +// (login_cookie_name in grafana.ini). +const sessionCookieName = "pmm_session" + +// sessionCookieForClient extracts the rotated session cookie from setCookies and +// rewrites its path for the top level: Grafana scopes its cookies to the /graph +// sub-path, but API clients need the session cookie on /v1 paths too (nginx does +// the same rewrite for proxied Grafana responses via proxy_cookie_path). A single +// Set-Cookie header is returned because nginx propagates only one from the auth +// subrequest; the companion grafana_session_expiry cookie is dropped - it merely +// schedules the Grafana frontend's own rotation and self-heals on the next +// Grafana page load. +func sessionCookieForClient(setCookies []string) []string { + for _, c := range (&http.Response{Header: http.Header{"Set-Cookie": setCookies}}).Cookies() { + if c.Name != sessionCookieName { + continue + } + c.Path = "/" + return []string{c.String()} + } + + return nil +} + +// headersWithRotatedCookies returns a copy of authHeaders whose Cookie header has +// the cookies from setCookies applied on top of the original ones. +func headersWithRotatedCookies(authHeaders http.Header, setCookies []string) http.Header { + rotated := (&http.Response{Header: http.Header{"Set-Cookie": setCookies}}).Cookies() + byName := make(map[string]string, len(rotated)) + for _, c := range rotated { + byName[c.Name] = c.Value + } + + pairs := make([]string, 0, len(rotated)) + seen := make(map[string]struct{}) + for _, c := range (&http.Request{Header: authHeaders}).Cookies() { + v := c.Value + if nv, ok := byName[c.Name]; ok { + v = nv + } + pairs = append(pairs, c.Name+"="+v) + seen[c.Name] = struct{}{} + } + for _, c := range rotated { + if _, ok := seen[c.Name]; !ok { + pairs = append(pairs, c.Name+"="+c.Value) + } + } + + headers := authHeaders.Clone() + headers.Set("Cookie", strings.Join(pairs, "; ")) + return headers } diff --git a/managed/services/grafana/auth_server_fuzz.go b/managed/services/grafana/auth_server_fuzz.go index d72f17a776a..a33a7c4c928 100644 --- a/managed/services/grafana/auth_server_fuzz.go +++ b/managed/services/grafana/auth_server_fuzz.go @@ -30,8 +30,12 @@ import ( type clientStub struct{} -func (clientStub) getRole(context.Context, http.Header) (role, error) { - return grafanaAdmin, nil +func (clientStub) getAuthUser(context.Context, http.Header, *logrus.Entry) (authUser, error) { + return authUser{role: grafanaAdmin}, nil +} + +func (clientStub) rotateSessionToken(context.Context, http.Header) ([]string, error) { + return nil, nil } func Fuzz(data []byte) int { @@ -45,7 +49,7 @@ func Fuzz(data []byte) int { return 0 } - _ = s.authenticate(context.Background(), req, logrus.NewEntry(logrus.StandardLogger())) + _, _, _ = s.authenticate(context.Background(), req, logrus.NewEntry(logrus.StandardLogger())) return 1 } diff --git a/managed/services/grafana/auth_server_test.go b/managed/services/grafana/auth_server_test.go index a009ab5d185..70cd498f543 100644 --- a/managed/services/grafana/auth_server_test.go +++ b/managed/services/grafana/auth_server_test.go @@ -16,6 +16,7 @@ package grafana import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -62,6 +63,89 @@ func TestNextPrefix(t *testing.T) { } } +func TestHeadersWithRotatedCookies(t *testing.T) { + t.Parallel() + + authHeaders := http.Header{} + authHeaders.Set("Cookie", "other=abc; pmm_session=old") + + headers := headersWithRotatedCookies(authHeaders, []string{"pmm_session=new; Path=/; HttpOnly; SameSite=Lax"}) + assert.Equal(t, "other=abc; pmm_session=new", headers.Get("Cookie")) + // the original headers stay untouched + assert.Equal(t, "other=abc; pmm_session=old", authHeaders.Get("Cookie")) + + // cookie names missing from the original header are appended + headers = headersWithRotatedCookies(authHeaders, []string{"brand_new=v1; Path=/"}) + assert.Equal(t, "other=abc; pmm_session=old; brand_new=v1", headers.Get("Cookie")) +} + +// countingRejectingClient implements clientInterface, rejecting every lookup +// and rotation with 401 while counting the calls. +type countingRejectingClient struct { + getAuthUserCalls int + rotateCalls int +} + +func (c *countingRejectingClient) getAuthUser(context.Context, http.Header, *logrus.Entry) (authUser, error) { + c.getAuthUserCalls++ + return emptyUser, &clientError{Code: http.StatusUnauthorized, ErrorMessage: "Unauthorized"} +} + +func (c *countingRejectingClient) rotateSessionToken(context.Context, http.Header) ([]string, error) { + c.rotateCalls++ + return nil, &clientError{Code: http.StatusUnauthorized, ErrorMessage: "Unauthorized"} +} + +func TestAuthServerNegativeCache(t *testing.T) { + t.Parallel() + + client := &countingRejectingClient{} + s := NewAuthServer(client, nil) + l := logrus.WithField("test", t.Name()) + + newReq := func() *http.Request { + req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/v1/advisors", nil) + req.Header.Set("Cookie", "pmm_session=dead") + return req + } + + _, _, authErr := s.authenticate(t.Context(), newReq(), l) + require.NotNil(t, authErr) + assert.Equal(t, codes.Unauthenticated, authErr.code) + // the dead session cost one lookup and one rotation attempt + assert.Equal(t, 1, client.getAuthUserCalls) + assert.Equal(t, 1, client.rotateCalls) + + // the rejection is served from the cache without further Grafana calls + _, _, authErr = s.authenticate(t.Context(), newReq(), l) + require.NotNil(t, authErr) + assert.Equal(t, codes.Unauthenticated, authErr.code) + assert.Equal(t, 1, client.getAuthUserCalls) + assert.Equal(t, 1, client.rotateCalls) + + // different credentials bypass the cached rejection + req := newReq() + req.Header.Set("Cookie", "pmm_session=another") + _, _, authErr = s.authenticate(t.Context(), req, l) + require.NotNil(t, authErr) + assert.Equal(t, 2, client.getAuthUserCalls) + assert.Equal(t, 2, client.rotateCalls) +} + +func TestSessionCookieForClient(t *testing.T) { + t.Parallel() + + cookies := sessionCookieForClient([]string{ + "pmm_session=new; Path=/graph; Max-Age=2592000; HttpOnly; SameSite=Lax", + "grafana_session_expiry=1784931672; Path=/graph; Max-Age=2592000; SameSite=Lax", + }) + require.Len(t, cookies, 1) + assert.Equal(t, "pmm_session=new; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax", cookies[0]) + + assert.Nil(t, sessionCookieForClient([]string{"grafana_session_expiry=1; Path=/graph"})) + assert.Nil(t, sessionCookieForClient(nil)) +} + func TestResolveRule(t *testing.T) { t.Parallel() @@ -107,7 +191,7 @@ func TestAuthServerAuthenticate(t *testing.T) { require.NoError(t, err) req.SetBasicAuth("admin", "admin") - _, res := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) + _, _, res := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) assert.Nil(t, res) }) @@ -117,7 +201,7 @@ func TestAuthServerAuthenticate(t *testing.T) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil) require.NoError(t, err) - _, res := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) + _, _, res := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) assert.Equal(t, &authError{code: codes.Unauthenticated, message: "Unauthorized"}, res) }) @@ -141,7 +225,7 @@ func TestAuthServerAuthenticate(t *testing.T) { require.NoError(t, err) req.SetBasicAuth(login, login) - _, res := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) + _, _, res := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) if minRole <= role { assert.Nil(t, res) } else { @@ -166,7 +250,7 @@ func TestServerClientConnection(t *testing.T) { require.NoError(t, err) req.SetBasicAuth("admin", "admin") - _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) + _, _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) assert.Nil(t, authError) }) @@ -178,7 +262,7 @@ func TestServerClientConnection(t *testing.T) { require.NoError(t, err) req.SetBasicAuth("admin", "wrong") - _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) + _, _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) assert.Equal(t, codes.Unauthenticated, authError.code) }) @@ -202,7 +286,7 @@ func TestServerClientConnection(t *testing.T) { require.NoError(t, err) req.Header.Set("Authorization", "Bearer "+serviceToken) - _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) + _, _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) assert.Nil(t, authError) }) @@ -213,7 +297,7 @@ func TestServerClientConnection(t *testing.T) { require.NoError(t, err) req.Header.Set("Authorization", "Bearer wrong") - _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) + _, _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name())) assert.Equal(t, codes.Internal, authError.code) }) } diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go index 87e466a0e3d..e6ac601c59c 100644 --- a/managed/services/grafana/client.go +++ b/managed/services/grafana/client.go @@ -108,10 +108,10 @@ func (c *Client) Collect(ch chan<- prom.Metric) { // clientError contains error response details. type clientError struct { - Method string - URL string - Code int - Body string + Method string `json:"-"` + URL string `json:"-"` + Code int `json:"-"` + Body string `json:"-"` ErrorMessage string `json:"message"` // from response JSON object, if any } @@ -142,10 +142,7 @@ func CurrentUserHTTPResponse(err error) (int, map[string]string) { } return http.StatusForbidden, map[string]string{"message": msg} default: - if cErr.Code >= 500 { //nolint:mnd - return http.StatusBadGateway, map[string]string{"message": "Bad Gateway"} - } - // Other Grafana 4xx responses are treated as upstream errors for this proxy endpoint. + // Grafana 5xx and other 4xx responses are treated as upstream errors for this proxy endpoint. return http.StatusBadGateway, map[string]string{"message": "Bad Gateway"} } } @@ -160,7 +157,7 @@ func (c *Client) do(ctx context.Context, method, path, rawQuery string, headers Path: path, RawQuery: rawQuery, } - req, err := http.NewRequest(method, u.String(), bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, method, u.String(), bytes.NewReader(body)) if err != nil { return fmt.Errorf("failed to create http request: %w", err) } @@ -171,7 +168,6 @@ func (c *Client) do(ctx context.Context, method, path, rawQuery string, headers req.Header.Set(k, headers.Get(k)) } - req = req.WithContext(ctx) resp, err := c.http.Do(req) if err != nil { return fmt.Errorf("failed to execute http request: %w", err) @@ -276,7 +272,7 @@ func (c *Client) GetUserID(ctx context.Context) (int, error) { userID, ok := m["id"].(float64) if !ok { - return 0, errors.New("Missing User ID in Grafana response") + return 0, errors.New("missing user ID in Grafana response") } return int(userID), nil @@ -371,6 +367,48 @@ func (c *Client) getAuthUser(ctx context.Context, authHeaders http.Header, l *lo }, nil } +// rotateSessionToken calls the Grafana session token rotation endpoint with the +// session cookie from authHeaders. Grafana rejects tokens older than the rotation +// interval until a client rotates them explicitly. It returns the Set-Cookie +// headers carrying the rotated token; they may be empty if a concurrent request +// has already rotated it. +func (c *Client) rotateSessionToken(ctx context.Context, authHeaders http.Header) ([]string, error) { + u := url.URL{ + Scheme: "http", + Host: c.addr, + Path: "/api/user/auth-tokens/rotate", + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), nil) + if err != nil { + return nil, fmt.Errorf("failed to create http request: %w", err) + } + req.Header.Set("Cookie", authHeaders.Get("Cookie")) + + resp, err := c.http.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to execute http request: %w", err) + } + defer resp.Body.Close() //nolint:gosec,errcheck,nolintlint + + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read http response body: %w", err) + } + if resp.StatusCode != http.StatusOK { + cErr := &clientError{ + Method: req.Method, + URL: req.URL.String(), + Code: resp.StatusCode, + Body: string(b), + } + // add ErrorMessage + _ = json.Unmarshal(b, cErr) + return nil, cErr + } + + return resp.Header.Values("Set-Cookie"), nil +} + func (c *Client) convertRole(role string) role { switch role { case "Viewer": diff --git a/managed/services/management/checks.go b/managed/services/management/checks.go index df286af4d5d..e40bd0899af 100644 --- a/managed/services/management/checks.go +++ b/managed/services/management/checks.go @@ -20,17 +20,18 @@ import ( "errors" "fmt" "maps" - "strings" + "slices" "github.com/AlekSi/pointer" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" advisorsv1 "github.com/percona/pmm/api/advisors/v1" managementv1 "github.com/percona/pmm/api/management/v1" + "github.com/percona/pmm/managed/models" "github.com/percona/pmm/managed/pi/check" - "github.com/percona/pmm/managed/pi/common" "github.com/percona/pmm/managed/services" ) @@ -50,102 +51,114 @@ func NewChecksAPIService(checksService checksService) *ChecksAPIService { } } -// ListFailedServices returns a list of services with failed checks and their summaries. -func (s *ChecksAPIService) ListFailedServices(ctx context.Context, _ *advisorsv1.ListFailedServicesRequest) (*advisorsv1.ListFailedServicesResponse, error) { - results, err := s.checksService.GetChecksResults(ctx, "") - if err != nil { - if errors.Is(err, services.ErrAdvisorsDisabled) { - return nil, status.Errorf(codes.FailedPrecondition, "%v.", err) - } - - return nil, fmt.Errorf("failed to get check results: %w", err) +// ListInsights returns the paginated history of Advisor check results (insights) matching the filters. +func (s *ChecksAPIService) ListInsights( + ctx context.Context, + req *advisorsv1.ListInsightsRequest, +) (*advisorsv1.ListInsightsResponse, error) { + var pageIndex, pageSize int + if req.PageIndex != nil { + pageIndex = int(pointer.GetInt32(req.PageIndex)) + } + if req.PageSize != nil { + pageSize = int(pointer.GetInt32(req.PageSize)) } - summaries := make(map[string]*services.CheckResultSummary) - var svcSummary *services.CheckResultSummary - var exists bool - for _, result := range results { - if svcSummary, exists = summaries[result.Target.ServiceID]; !exists { - svcSummary = &services.CheckResultSummary{ - ServiceName: result.Target.ServiceName, - ServiceID: result.Target.ServiceID, - } - summaries[result.Target.ServiceID] = svcSummary + filters := models.InsightFilters{ + ServiceID: req.ServiceId, + ServiceName: req.ServiceName, + NodeName: req.NodeName, + Category: req.Category, + CheckName: req.CheckName, + RunID: req.RunId, + IsRead: req.IsRead, + } + if req.Status != nil { + if st := convertAPIResultStatus(*req.Status); st != "" { + filters.Status = &st } - switch result.Result.Severity { - case common.Emergency: - svcSummary.EmergencyCount++ - case common.Alert: - svcSummary.AlertCount++ - case common.Critical: - svcSummary.CriticalCount++ - case common.Error: - svcSummary.ErrorCount++ - case common.Warning: - svcSummary.WarningCount++ - case common.Notice: - svcSummary.NoticeCount++ - case common.Info: - svcSummary.InfoCount++ - case common.Debug: - svcSummary.DebugCount++ - case common.Unknown: - continue + } + if req.TriggeredBy != nil { + if tb := convertAPITriggeredBy(*req.TriggeredBy); tb != "" { + filters.TriggeredBy = &tb } } - - failedServices := make([]*advisorsv1.CheckResultSummary, 0, len(summaries)) - for _, result := range summaries { - failedServices = append(failedServices, &advisorsv1.CheckResultSummary{ - ServiceId: result.ServiceID, - ServiceName: result.ServiceName, - EmergencyCount: result.EmergencyCount, - AlertCount: result.AlertCount, - CriticalCount: result.CriticalCount, - ErrorCount: result.ErrorCount, - WarningCount: result.WarningCount, - NoticeCount: result.NoticeCount, - InfoCount: result.InfoCount, - DebugCount: result.DebugCount, - }) + if req.Severity != nil && *req.Severity != managementv1.Severity_SEVERITY_UNSPECIFIED { + severity := models.Severity(*req.Severity) + filters.Severity = &severity + } + if req.From != nil { + from := req.From.AsTime() + filters.From = &from + } + if req.To != nil { + to := req.To.AsTime() + filters.To = &to } - return &advisorsv1.ListFailedServicesResponse{Result: failedServices}, nil -} - -// GetFailedChecks returns details of failed checks for a given service. -func (s *ChecksAPIService) GetFailedChecks(ctx context.Context, req *advisorsv1.GetFailedChecksRequest) (*advisorsv1.GetFailedChecksResponse, error) { - results, err := s.checksService.GetChecksResults(ctx, req.ServiceId) + results, totalItems, err := s.checksService.GetInsights(ctx, filters, pageIndex, pageSize) if err != nil { - if errors.Is(err, services.ErrAdvisorsDisabled) { - return nil, status.Errorf(codes.FailedPrecondition, "%v.", err) - } - - return nil, fmt.Errorf("failed to get check results for service '%s': %w", req.ServiceId, err) + return nil, fmt.Errorf("failed to get insights: %w", err) } - failedChecks := make([]*advisorsv1.CheckResult, 0, len(results)) - for _, result := range results { - labels := make(map[string]string, len(result.Target.Labels)+len(result.Result.Labels)) - maps.Copy(labels, result.Result.Labels) - maps.Copy(labels, result.Target.Labels) + items := make([]*advisorsv1.Insight, 0, len(results)) + for _, r := range results { + labels, err := r.GetLabels() + if err != nil { + return nil, fmt.Errorf("failed to decode labels for insight '%s': %w", r.ID, err) + } - failedChecks = append(failedChecks, &advisorsv1.CheckResult{ - Summary: result.Result.Summary, - CheckName: result.CheckName, - Description: result.Result.Description, - ReadMoreUrl: result.Result.ReadMoreURL, - Severity: managementv1.Severity(result.Result.Severity), - Labels: labels, - ServiceName: result.Target.ServiceName, - ServiceId: result.Target.ServiceID, + items = append(items, &advisorsv1.Insight{ + Id: r.ID, + CheckName: r.CheckName, + RunId: r.RunID, + Category: r.Category, + Subcategory: r.Subcategory, + Severity: managementv1.Severity(r.Severity), //nolint:gosec // severity is a bounded enum (0-8), no overflow + Interval: convertModelInterval(r.Interval), + ServiceId: r.ServiceID, + ServiceName: r.ServiceName, + ServiceType: string(r.ServiceType), + NodeId: r.NodeID, + NodeName: r.NodeName, + Environment: r.Environment, + Cluster: r.Cluster, + ReplicationSet: r.ReplicationSet, + Region: r.Region, + Az: r.AZ, + Status: convertModelResultStatus(r.Status), + Summary: r.Summary, + Description: r.Description, + Outcome: r.Outcome, + ReadMoreUrl: r.ReadMoreURL, + Labels: labels, + CheckedAt: timestamppb.New(r.CheckedAt), + IsRead: r.IsRead, + TriggeredBy: convertModelTriggeredBy(r.TriggeredBy), }) } - var pageIndex, pageSize int - totalPages := int32(1) - totalItems := int32(len(failedChecks)) + totalPages := 1 + if pageSize > 0 { + totalPages = totalItems / pageSize + if totalItems%pageSize > 0 { + totalPages++ + } + } + + return &advisorsv1.ListInsightsResponse{ + Results: items, + TotalItems: int32(totalItems), //nolint:gosec + TotalPages: int32(totalPages), + }, nil +} +// ListRuns returns the paginated history of Advisor check executions. +func (s *ChecksAPIService) ListRuns( + ctx context.Context, + req *advisorsv1.ListRunsRequest, +) (*advisorsv1.ListRunsResponse, error) { + var pageIndex, pageSize int if req.PageIndex != nil { pageIndex = int(pointer.GetInt32(req.PageIndex)) } @@ -153,32 +166,126 @@ func (s *ChecksAPIService) GetFailedChecks(ctx context.Context, req *advisorsv1. pageSize = int(pointer.GetInt32(req.PageSize)) } - from, to := pageIndex*pageSize, (pageIndex+1)*pageSize - if to > len(failedChecks) || to == 0 { - to = len(failedChecks) + var filters models.AdvisorRunFilters + if req.TriggeredBy != nil { + if tb := convertAPITriggeredBy(*req.TriggeredBy); tb != "" { + filters.TriggeredBy = &tb + } + } + if req.From != nil { + from := req.From.AsTime() + filters.From = &from } - if from > len(failedChecks) { - from = len(failedChecks) + if req.To != nil { + to := req.To.AsTime() + filters.To = &to } + runs, totalItems, err := s.checksService.GetRuns(ctx, filters, pageIndex, pageSize) + if err != nil { + return nil, fmt.Errorf("failed to get advisor runs: %w", err) + } + + items := make([]*advisorsv1.AdvisorRun, 0, len(runs)) + for _, r := range runs { + severityCounts, err := r.GetSeverityCounts() + if err != nil { + return nil, fmt.Errorf("failed to decode severity counts for advisor run '%s': %w", r.ID, err) + } + + item := &advisorsv1.AdvisorRun{ + Id: r.ID, + TriggeredBy: convertModelTriggeredBy(r.TriggeredBy), + StartedAt: timestamppb.New(r.StartedAt), + ChecksCount: int32(r.ChecksCount), //nolint:gosec + ServicesCount: int32(r.ServicesCount), //nolint:gosec + FindingsCount: int32(r.FindingsCount), //nolint:gosec + ErrorsCount: int32(r.ErrorsCount), //nolint:gosec + SeverityCounts: convertSeverityCounts(severityCounts), + } + // left unset while the run is still going + if r.FinishedAt != nil { + item.FinishedAt = timestamppb.New(*r.FinishedAt) + } + + items = append(items, item) + } + + totalPages := 1 if pageSize > 0 { - totalPages = int32(len(failedChecks) / pageSize) - if len(failedChecks)%pageSize > 0 { + totalPages = totalItems / pageSize + if totalItems%pageSize > 0 { totalPages++ } } - return &advisorsv1.GetFailedChecksResponse{ - Results: failedChecks[from:to], - TotalItems: totalItems, - TotalPages: totalPages, + return &advisorsv1.ListRunsResponse{ + Results: items, + TotalItems: int32(totalItems), //nolint:gosec + TotalPages: int32(totalPages), + }, nil +} + +// ListInsightsFilterValues returns the distinct values usable as insights filters. +func (s *ChecksAPIService) ListInsightsFilterValues( + ctx context.Context, + _ *advisorsv1.ListInsightsFilterValuesRequest, +) (*advisorsv1.ListInsightsFilterValuesResponse, error) { + serviceNames, nodeNames, err := s.checksService.GetInsightsFilterValues(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get insights filter values: %w", err) + } + + return &advisorsv1.ListInsightsFilterValuesResponse{ + ServiceNames: serviceNames, + NodeNames: nodeNames, }, nil } -// StartAdvisorChecks executes advisor checks and returns when all checks are executed. +// MarkInsightsRead sets the read state on the specified Advisor insights. +func (s *ChecksAPIService) MarkInsightsRead( + ctx context.Context, + req *advisorsv1.MarkInsightsReadRequest, +) (*advisorsv1.MarkInsightsReadResponse, error) { + switch { + case len(req.Ids) > 0: + err := s.checksService.MarkInsightsRead(ctx, req.Ids, req.IsRead) + if err != nil { + return nil, fmt.Errorf("failed to mark insights read: %w", err) + } + case req.Filters != nil: + filters := models.InsightFilters{ + ServiceName: req.Filters.ServiceName, + NodeName: req.Filters.NodeName, + Category: req.Filters.Category, + CheckName: req.Filters.CheckName, + RunID: req.Filters.RunId, + IsRead: req.Filters.IsRead, + } + if req.Filters.Status != nil { + if st := convertAPIResultStatus(*req.Filters.Status); st != "" { + filters.Status = &st + } + } + if req.Filters.Severity != nil && *req.Filters.Severity != managementv1.Severity_SEVERITY_UNSPECIFIED { + severity := models.Severity(*req.Filters.Severity) + filters.Severity = &severity + } + err := s.checksService.MarkInsightsReadByFilters(ctx, filters, req.IsRead) + if err != nil { + return nil, fmt.Errorf("failed to mark insights read by filters: %w", err) + } + default: + return nil, status.Error(codes.InvalidArgument, "Either ids or filters must be provided.") + } + + return &advisorsv1.MarkInsightsReadResponse{}, nil +} + +// StartAdvisorChecks executes advisor checks and returns the ID assigned to this run. func (s *ChecksAPIService) StartAdvisorChecks(_ context.Context, req *advisorsv1.StartAdvisorChecksRequest) (*advisorsv1.StartAdvisorChecksResponse, error) { // Start only specified checks from any group. - err := s.checksService.StartChecks(req.Names) + runID, err := s.checksService.StartChecks(req.Names, req.ServiceIds) if err != nil { if errors.Is(err, services.ErrAdvisorsDisabled) { return nil, status.Errorf(codes.FailedPrecondition, "%v.", err) @@ -187,12 +294,12 @@ func (s *ChecksAPIService) StartAdvisorChecks(_ context.Context, req *advisorsv1 return nil, fmt.Errorf("failed to start advisor checks: %w", err) } - return &advisorsv1.StartAdvisorChecksResponse{}, nil + return &advisorsv1.StartAdvisorChecksResponse{RunId: runID}, nil } // ListAdvisorChecks returns a list of available advisor checks and their statuses. -func (s *ChecksAPIService) ListAdvisorChecks(_ context.Context, _ *advisorsv1.ListAdvisorChecksRequest) (*advisorsv1.ListAdvisorChecksResponse, error) { - disChecks, err := s.checksService.GetDisabledChecks() +func (s *ChecksAPIService) ListAdvisorChecks(ctx context.Context, _ *advisorsv1.ListAdvisorChecksRequest) (*advisorsv1.ListAdvisorChecksResponse, error) { + disChecks, err := s.checksService.GetDisabledChecks(ctx) if err != nil { return nil, fmt.Errorf("failed to get disabled checks list: %w", err) } @@ -202,6 +309,11 @@ func (s *ChecksAPIService) ListAdvisorChecks(_ context.Context, _ *advisorsv1.Li m[c] = struct{}{} } + disServices, err := s.checksService.GetDisabledServicesForChecks(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get disabled services list: %w", err) + } + checks, err := s.checksService.GetChecks() if err != nil { return nil, fmt.Errorf("failed to get available checks list: %w", err) @@ -211,12 +323,16 @@ func (s *ChecksAPIService) ListAdvisorChecks(_ context.Context, _ *advisorsv1.Li for _, c := range checks { _, disabled := m[c.Name] res = append(res, &advisorsv1.AdvisorCheck{ - Name: c.Name, - Enabled: !disabled, - Summary: c.Summary, - Family: convertFamily(c.GetFamily()), - Description: c.Description, - Interval: convertInterval(c.Interval), + Name: c.Name, + Enabled: !disabled, + Summary: c.Summary, + Technology: convertTechnology(c.Technology), + Description: c.Description, + Interval: convertInterval(c.Interval), + Category: c.Category, + Subcategory: c.Subcategory, + UserDefined: c.UserDefined, + DisabledServiceIds: disServices[c.Name], }) } @@ -224,8 +340,8 @@ func (s *ChecksAPIService) ListAdvisorChecks(_ context.Context, _ *advisorsv1.Li } // ListAdvisors retrieves a list of advisors based on the provided request. -func (s *ChecksAPIService) ListAdvisors(_ context.Context, _ *advisorsv1.ListAdvisorsRequest) (*advisorsv1.ListAdvisorsResponse, error) { - disChecks, err := s.checksService.GetDisabledChecks() +func (s *ChecksAPIService) ListAdvisors(ctx context.Context, _ *advisorsv1.ListAdvisorsRequest) (*advisorsv1.ListAdvisorsResponse, error) { + disChecks, err := s.checksService.GetDisabledChecks(ctx) if err != nil { return nil, fmt.Errorf("failed to get disabled checks list: %w", err) } @@ -235,6 +351,11 @@ func (s *ChecksAPIService) ListAdvisors(_ context.Context, _ *advisorsv1.ListAdv m[c] = struct{}{} } + disServices, err := s.checksService.GetDisabledServicesForChecks(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get disabled services list: %w", err) + } + advisors, err := s.checksService.GetAdvisors() if err != nil { return nil, fmt.Errorf("failed to get available checks list: %w", err) @@ -246,21 +367,22 @@ func (s *ChecksAPIService) ListAdvisors(_ context.Context, _ *advisorsv1.ListAdv for _, c := range a.Checks { _, disabled := m[c.Name] checks = append(checks, &advisorsv1.AdvisorCheck{ - Name: c.Name, - Enabled: !disabled, - Summary: c.Summary, - Family: convertFamily(c.GetFamily()), - Description: c.Description, - Interval: convertInterval(c.Interval), + Name: c.Name, + Enabled: !disabled, + Summary: c.Summary, + Technology: convertTechnology(c.Technology), + Description: c.Description, + Interval: convertInterval(c.Interval), + Category: c.Category, + Subcategory: c.Subcategory, + UserDefined: c.UserDefined, + DisabledServiceIds: disServices[c.Name], }) } res = append(res, &advisorsv1.Advisor{ - Name: a.Name, - Description: a.Description, - Summary: a.Summary, - Comment: createComment(a.Checks), Category: a.Category, + Subcategory: a.Subcategory, Checks: checks, }) } @@ -268,72 +390,50 @@ func (s *ChecksAPIService) ListAdvisors(_ context.Context, _ *advisorsv1.ListAdv return &advisorsv1.ListAdvisorsResponse{Advisors: res}, nil } -func createComment(checks []check.Check) string { - var mySQL, postgreSQL, mongoDB bool - for _, c := range checks { - switch c.GetFamily() { - case check.MySQL: - mySQL = true - case check.PostgreSQL: - postgreSQL = true - case check.MongoDB: - mongoDB = true - } - } - - b := make([]string, 0, 3) //nolint:mnd - if mySQL { - b = append(b, "MySQL") - } - if postgreSQL { - b = append(b, "PostgreSQL") - } - if mongoDB { - b = append(b, "MongoDB") - } - - if len(b) == 3 { //nolint:mnd - return "All technologies supported" - } - - return "Partial support (" + strings.Join(b, ", ") + ")" -} - -// ChangeAdvisorChecks enables/disables advisor checks by names or changes its execution interval. -func (s *ChecksAPIService) ChangeAdvisorChecks(_ context.Context, req *advisorsv1.ChangeAdvisorChecksRequest) (*advisorsv1.ChangeAdvisorChecksResponse, error) { +// ChangeAdvisorChecks enables/disables advisor checks — globally or for specific +// services — by names, or changes their execution interval. +func (s *ChecksAPIService) ChangeAdvisorChecks(ctx context.Context, req *advisorsv1.ChangeAdvisorChecksRequest) (*advisorsv1.ChangeAdvisorChecksResponse, error) { var enableChecks, disableChecks []string changeIntervalParams := make(map[string]check.Interval) - for _, check := range req.Params { - if check.Interval != advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_UNSPECIFIED { - interval, err := convertAPIInterval(check.Interval) + for _, p := range req.Params { + if len(p.ServiceIds) != 0 { + err := s.changeChecksForServices(ctx, p) + if err != nil { + return nil, err + } + continue + } + + if p.Interval != advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_UNSPECIFIED { + interval, err := convertAPIInterval(p.Interval) if err != nil { return nil, err } - changeIntervalParams[check.Name] = interval + changeIntervalParams[p.Name] = interval } - if check.Enable != nil { - if *check.Enable { - enableChecks = append(enableChecks, check.Name) + if p.Enable != nil { + if *p.Enable { + enableChecks = append(enableChecks, p.Name) } else { - disableChecks = append(disableChecks, check.Name) + disableChecks = append(disableChecks, p.Name) } } } if len(changeIntervalParams) != 0 { - err := s.checksService.ChangeInterval(changeIntervalParams) + err := s.checksService.ChangeInterval(ctx, changeIntervalParams) if err != nil { return nil, fmt.Errorf("failed to change advisor check interval: %w", err) } } - err := s.checksService.EnableChecks(enableChecks) + err := s.checksService.EnableChecks(ctx, enableChecks) if err != nil { return nil, fmt.Errorf("failed to enable disabled advisor checks: %w", err) } - err = s.checksService.DisableChecks(disableChecks) + err = s.checksService.DisableChecks(ctx, disableChecks) if err != nil { return nil, fmt.Errorf("failed to disable advisor checks: %w", err) } @@ -341,6 +441,189 @@ func (s *ChecksAPIService) ChangeAdvisorChecks(_ context.Context, req *advisorsv return &advisorsv1.ChangeAdvisorChecksResponse{}, nil } +// changeChecksForServices applies a per-service enable/disable params entry: +// the change affects only the given services. The interval stays check-wide +// and cannot be mixed into such an entry. +func (s *ChecksAPIService) changeChecksForServices(ctx context.Context, p *advisorsv1.ChangeAdvisorCheckParams) error { + if p.Interval != advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_UNSPECIFIED { + return status.Errorf(codes.InvalidArgument, "Interval of check %s cannot be changed per service.", p.Name) + } + if p.Enable == nil { + return status.Errorf(codes.InvalidArgument, "Enable flag is required to change check %s per service.", p.Name) + } + + if *p.Enable { + return s.checksService.EnableChecksForServices(ctx, p.Name, p.ServiceIds) + } + return s.checksService.DisableChecksForServices(ctx, p.Name, p.ServiceIds) +} + +// GetAdvisorCheck returns a single advisor check by name, including its queries and script. +func (s *ChecksAPIService) GetAdvisorCheck(ctx context.Context, req *advisorsv1.GetAdvisorCheckRequest) (*advisorsv1.GetAdvisorCheckResponse, error) { + c, enabled, disabledServiceIDs, err := s.getCheck(ctx, req.Name) + if err != nil { + return nil, err + } + + return &advisorsv1.GetAdvisorCheckResponse{Check: advisorCheckToAPI(c, enabled, disabledServiceIDs)}, nil +} + +// CreateAdvisorCheck creates a new user-authored advisor check. +func (s *ChecksAPIService) CreateAdvisorCheck(ctx context.Context, req *advisorsv1.CreateAdvisorCheckRequest) (*advisorsv1.CreateAdvisorCheckResponse, error) { + if req.Check == nil { + return nil, status.Error(codes.InvalidArgument, "Check is required.") + } + + err := s.checksService.CreateAdvisorCheck(ctx, apiToAdvisorCheck(req.Check)) + if err != nil { + return nil, err + } + + c, enabled, disabledServiceIDs, err := s.getCheck(ctx, req.Check.Name) + if err != nil { + return nil, err + } + + return &advisorsv1.CreateAdvisorCheckResponse{Check: advisorCheckToAPI(c, enabled, disabledServiceIDs)}, nil +} + +// UpdateAdvisorCheck updates an existing user-authored advisor check. +func (s *ChecksAPIService) UpdateAdvisorCheck(ctx context.Context, req *advisorsv1.UpdateAdvisorCheckRequest) (*advisorsv1.UpdateAdvisorCheckResponse, error) { + if req.Check == nil { + return nil, status.Error(codes.InvalidArgument, "Check is required.") + } + + // a check cannot be renamed: its name is the primary key and is denormalized + // into insight history, so reject a body name that disagrees with the path + // instead of silently updating the check the path points at. An empty body + // name means "not specified" and keeps working. + if req.Check.Name != "" && req.Check.Name != req.Name { + return nil, status.Errorf(codes.InvalidArgument, + "Advisor check cannot be renamed: name '%s' in the request body does not match '%s'.", + req.Check.Name, req.Name) + } + + c := apiToAdvisorCheck(req.Check) + c.Name = req.Name + + err := s.checksService.UpdateAdvisorCheck(ctx, c) + if err != nil { + return nil, err + } + + updated, enabled, disabledServiceIDs, err := s.getCheck(ctx, req.Name) + if err != nil { + return nil, err + } + + return &advisorsv1.UpdateAdvisorCheckResponse{Check: advisorCheckToAPI(updated, enabled, disabledServiceIDs)}, nil +} + +// DeleteAdvisorCheck deletes a user-authored advisor check. +func (s *ChecksAPIService) DeleteAdvisorCheck(ctx context.Context, req *advisorsv1.DeleteAdvisorCheckRequest) (*advisorsv1.DeleteAdvisorCheckResponse, error) { + err := s.checksService.DeleteAdvisorCheck(ctx, req.Name) + if err != nil { + return nil, err + } + + return &advisorsv1.DeleteAdvisorCheckResponse{}, nil +} + +// TestAdvisorCheck executes an advisor check definition against a single service +// without saving the check or persisting its results. +func (s *ChecksAPIService) TestAdvisorCheck(ctx context.Context, req *advisorsv1.TestAdvisorCheckRequest) (*advisorsv1.TestAdvisorCheckResponse, error) { + if req.Check == nil { + return nil, status.Error(codes.InvalidArgument, "Check is required.") + } + + results, scriptOutput, err := s.checksService.TestAdvisorCheck(ctx, apiToAdvisorCheck(req.Check), req.ServiceId) + if err != nil { + if errors.Is(err, services.ErrAdvisorsDisabled) { + return nil, status.Errorf(codes.FailedPrecondition, "%v.", err) + } + // pass errors through with their message intact (incl. query and + // script failures), so the check can be debugged from the UI + return nil, err + } + + return &advisorsv1.TestAdvisorCheckResponse{ + Results: convertTestCheckResults(results), + ScriptOutput: scriptOutput, + }, nil +} + +// ListAdvisorCheckTestTargets returns the services an advisor check of the given technology can be tested against. +func (s *ChecksAPIService) ListAdvisorCheckTestTargets( + ctx context.Context, + req *advisorsv1.ListAdvisorCheckTestTargetsRequest, +) (*advisorsv1.ListAdvisorCheckTestTargetsResponse, error) { + targets, err := s.checksService.ListTestTargets(ctx, convertAPITechnology(req.Technology)) + if err != nil { + return nil, err + } + + res := make([]*advisorsv1.AdvisorCheckTestTarget, len(targets)) + for i, target := range targets { + res[i] = &advisorsv1.AdvisorCheckTestTarget{ + ServiceId: target.ServiceID, + ServiceName: target.ServiceName, + } + } + + return &advisorsv1.ListAdvisorCheckTestTargetsResponse{Targets: res}, nil +} + +// convertTestCheckResults converts test (dry-run) check execution results to their API representation. +func convertTestCheckResults(results []services.CheckResult) []*advisorsv1.TestAdvisorCheckResult { + converted := make([]*advisorsv1.TestAdvisorCheckResult, 0, len(results)) + for _, result := range results { + labels := make(map[string]string, len(result.Target.Labels)+len(result.Result.Labels)) + maps.Copy(labels, result.Result.Labels) + maps.Copy(labels, result.Target.Labels) + + converted = append(converted, &advisorsv1.TestAdvisorCheckResult{ + Summary: result.Result.Summary, + CheckName: result.CheckName, + Description: result.Result.Description, + ReadMoreUrl: result.Result.ReadMoreURL, + Severity: managementv1.Severity(result.Result.Severity), //nolint:gosec // severity is a bounded enum (0-8), no overflow + Labels: labels, + ServiceName: result.Target.ServiceName, + ServiceId: result.Target.ServiceID, + }) + } + + return converted +} + +// getCheck returns a check by name together with its enabled state and the +// service IDs for which it is disabled. +func (s *ChecksAPIService) getCheck(ctx context.Context, name string) (check.Check, bool, []string, error) { + checks, err := s.checksService.GetChecks() + if err != nil { + return check.Check{}, false, nil, fmt.Errorf("failed to get available checks list: %w", err) + } + + c, ok := checks[name] + if !ok { + return check.Check{}, false, nil, status.Errorf(codes.NotFound, "Advisor check %q not found.", name) + } + + disabled, err := s.checksService.GetDisabledChecks(ctx) + if err != nil { + return check.Check{}, false, nil, fmt.Errorf("failed to get disabled checks list: %w", err) + } + + disServices, err := s.checksService.GetDisabledServicesForChecks(ctx) + if err != nil { + return check.Check{}, false, nil, fmt.Errorf("failed to get disabled services list: %w", err) + } + + enabled := !slices.Contains(disabled, name) + + return c, enabled, disServices[name], nil +} + // convertInterval converts check.Interval type to advisorsv1.AdvisorCheckInterval. func convertInterval(interval check.Interval) advisorsv1.AdvisorCheckInterval { switch interval { @@ -355,17 +638,102 @@ func convertInterval(interval check.Interval) advisorsv1.AdvisorCheckInterval { } } -// convertFamily converts check.Family type to advisorsv1.AdvisorCheckFamily. -func convertFamily(family check.Family) advisorsv1.AdvisorCheckFamily { - switch family { +// convertModelInterval converts models.Interval type to advisorsv1.AdvisorCheckInterval. +func convertModelInterval(interval models.Interval) advisorsv1.AdvisorCheckInterval { + switch interval { + case models.Standard, "": // empty interval means standard + return advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_STANDARD + case models.Frequent: + return advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_FREQUENT + case models.Rare: + return advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_RARE + default: + return advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_UNSPECIFIED + } +} + +// convertModelResultStatus converts models.CheckResultStatus to advisorsv1.AdvisorCheckResultStatus. +func convertModelResultStatus(status models.CheckResultStatus) advisorsv1.AdvisorCheckResultStatus { + switch status { + case models.CheckResultOK: + return advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_OK + case models.CheckResultFailed: + return advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_FAILED + case models.CheckResultError: + return advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_ERROR + default: + return advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_UNSPECIFIED + } +} + +// convertAPIResultStatus converts advisorsv1.AdvisorCheckResultStatus to models.CheckResultStatus. +// An empty value is returned for an unspecified status, meaning "no filter". +func convertAPIResultStatus(status advisorsv1.AdvisorCheckResultStatus) models.CheckResultStatus { + switch status { + case advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_OK: + return models.CheckResultOK + case advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_FAILED: + return models.CheckResultFailed + case advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_ERROR: + return models.CheckResultError + default: + return "" + } +} + +// convertModelTriggeredBy converts models.CheckTriggeredBy to advisorsv1.AdvisorCheckTriggeredBy. +func convertModelTriggeredBy(triggeredBy models.CheckTriggeredBy) advisorsv1.AdvisorCheckTriggeredBy { + switch triggeredBy { + case models.CheckTriggeredByUser: + return advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_USER + case models.CheckTriggeredByScheduler: + return advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER + default: + return advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_UNSPECIFIED + } +} + +// convertSeverityCounts converts a run's per-severity finding counts, ordered +// most severe first so the API output is stable across calls. +func convertSeverityCounts(counts map[models.Severity]int) []*advisorsv1.SeverityCount { + res := make([]*advisorsv1.SeverityCount, 0, len(counts)) + for severity, count := range counts { + res = append(res, &advisorsv1.SeverityCount{ + Severity: managementv1.Severity(severity), //nolint:gosec // severity is a bounded enum (0-8), no overflow + Count: int32(count), //nolint:gosec + }) + } + // lower enum values are more severe, emergency being 1 + slices.SortFunc(res, func(a, b *advisorsv1.SeverityCount) int { + return int(a.Severity) - int(b.Severity) + }) + return res +} + +// convertAPITriggeredBy converts advisorsv1.AdvisorCheckTriggeredBy to models.CheckTriggeredBy. +// An empty string is returned for unknown values. +func convertAPITriggeredBy(triggeredBy advisorsv1.AdvisorCheckTriggeredBy) models.CheckTriggeredBy { + switch triggeredBy { + case advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_USER: + return models.CheckTriggeredByUser + case advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER: + return models.CheckTriggeredByScheduler + default: + return "" + } +} + +// convertTechnology converts check.Technology type to advisorsv1.AdvisorCheckTechnology. +func convertTechnology(technology check.Technology) advisorsv1.AdvisorCheckTechnology { + switch technology { case check.MySQL: - return advisorsv1.AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_MYSQL + return advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MYSQL case check.PostgreSQL: - return advisorsv1.AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_POSTGRESQL + return advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL case check.MongoDB: - return advisorsv1.AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_MONGODB + return advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MONGODB default: - return advisorsv1.AdvisorCheckFamily_ADVISOR_CHECK_FAMILY_UNSPECIFIED + return advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_UNSPECIFIED } } @@ -384,3 +752,123 @@ func convertAPIInterval(interval advisorsv1.AdvisorCheckInterval) (check.Interva return "", errors.New("unknown advisor check interval") } } + +// convertAPITechnology converts advisorsv1.AdvisorCheckTechnology to check.Technology. +// An unspecified technology maps to an empty technology, which fails check validation. +func convertAPITechnology(technology advisorsv1.AdvisorCheckTechnology) check.Technology { + switch technology { + case advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MYSQL: + return check.MySQL + case advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL: + return check.PostgreSQL + case advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MONGODB: + return check.MongoDB + default: + return "" + } +} + +// convertAPIIntervalOptional converts advisorsv1.AdvisorCheckInterval to check.Interval. +// Unlike convertAPIInterval, an unspecified interval maps to an empty interval +// (treated as standard by the loader) rather than an error. +func convertAPIIntervalOptional(interval advisorsv1.AdvisorCheckInterval) check.Interval { + switch interval { + case advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_STANDARD: + return check.Standard + case advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_FREQUENT: + return check.Frequent + case advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_RARE: + return check.Rare + default: + return "" + } +} + +// advisorCheckToAPI converts a check.Check into its full API representation, including queries and script. +func advisorCheckToAPI(c check.Check, enabled bool, disabledServiceIDs []string) *advisorsv1.AdvisorCheck { + return &advisorsv1.AdvisorCheck{ + Name: c.Name, + Enabled: enabled, + Summary: c.Summary, + Description: c.Description, + Technology: convertTechnology(c.Technology), + Interval: convertInterval(c.Interval), + Category: c.Category, + Subcategory: c.Subcategory, + UserDefined: c.UserDefined, + Queries: convertQueriesToAPI(c.Queries), + Script: c.Script, + DisabledServiceIds: disabledServiceIDs, + } +} + +// apiToAdvisorCheck converts an API advisor check (from a create/update request) into a check.Check. +func apiToAdvisorCheck(c *advisorsv1.AdvisorCheck) check.Check { + return check.Check{ + Name: c.Name, + Summary: c.Summary, + Description: c.Description, + Category: c.Category, + Subcategory: c.Subcategory, + Technology: convertAPITechnology(c.Technology), + Interval: convertAPIIntervalOptional(c.Interval), + Queries: convertAPIQueries(c.Queries), + Script: c.Script, + } +} + +// convertQueriesToAPI converts check queries to their API representation. +func convertQueriesToAPI(queries []check.Query) []*advisorsv1.AdvisorCheckQuery { + if len(queries) == 0 { + return nil + } + + res := make([]*advisorsv1.AdvisorCheckQuery, 0, len(queries)) + for _, q := range queries { + var params map[string]string + if len(q.Parameters) != 0 { + params = make(map[string]string, len(q.Parameters)) + for k, v := range q.Parameters { + params[string(k)] = v + } + } + + res = append(res, &advisorsv1.AdvisorCheckQuery{ + Type: string(q.Type), + Query: q.Query, + Parameters: params, + }) + } + + return res +} + +// convertAPIQueries converts API queries into check queries. +func convertAPIQueries(queries []*advisorsv1.AdvisorCheckQuery) []check.Query { + if len(queries) == 0 { + return nil + } + + res := make([]check.Query, 0, len(queries)) + for _, q := range queries { + if q == nil { + continue + } + + var params map[check.Parameter]string + if len(q.Parameters) != 0 { + params = make(map[check.Parameter]string, len(q.Parameters)) + for k, v := range q.Parameters { + params[check.Parameter(k)] = v + } + } + + res = append(res, check.Query{ + Type: check.Type(q.Type), + Query: q.Query, + Parameters: params, + }) + } + + return res +} diff --git a/managed/services/management/checks_test.go b/managed/services/management/checks_test.go index 4b7df573ab9..6bc28f3a8fe 100644 --- a/managed/services/management/checks_test.go +++ b/managed/services/management/checks_test.go @@ -17,17 +17,19 @@ package management import ( "errors" - "fmt" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" advisorsv1 "github.com/percona/pmm/api/advisors/v1" managementv1 "github.com/percona/pmm/api/management/v1" + "github.com/percona/pmm/managed/models" "github.com/percona/pmm/managed/pi/check" "github.com/percona/pmm/managed/pi/common" "github.com/percona/pmm/managed/services" @@ -37,7 +39,7 @@ import ( func TestStartAdvisorChecks(t *testing.T) { t.Run("internal error", func(t *testing.T) { var checksService mockChecksService - checksService.On("StartChecks", []string(nil)).Return(errors.New("random error")) + checksService.On("StartChecks", []string(nil), []string(nil)).Return("", errors.New("random error")) s := NewChecksAPIService(&checksService) @@ -48,7 +50,7 @@ func TestStartAdvisorChecks(t *testing.T) { t.Run("Advisors disabled error", func(t *testing.T) { var checksService mockChecksService - checksService.On("StartChecks", []string(nil)).Return(services.ErrAdvisorsDisabled) + checksService.On("StartChecks", []string(nil), []string(nil)).Return("", services.ErrAdvisorsDisabled) s := NewChecksAPIService(&checksService) @@ -58,242 +60,473 @@ func TestStartAdvisorChecks(t *testing.T) { }) } -func TestGetFailedChecks(t *testing.T) { +func TestTestAdvisorCheck(t *testing.T) { t.Parallel() - t.Run("internal error", func(t *testing.T) { + apiCheck := &advisorsv1.AdvisorCheck{ + Name: "custom_mysql_version", + Summary: "Check summary", + Description: "Check description", + Category: "configuration", + Subcategory: "version", + Technology: advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MYSQL, + Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_STANDARD, + Queries: []*advisorsv1.AdvisorCheckQuery{{Type: "MYSQL_SHOW", Query: "version"}}, + Script: "def check_context(docs, context):\n return []", + } + + t.Run("check is required", func(t *testing.T) { t.Parallel() var checksService mockChecksService - checksService.On("GetChecksResults", mock.Anything, mock.Anything).Return(nil, errors.New("random error")) s := NewChecksAPIService(&checksService) - serviceID := "test_svc" - resp, err := s.GetFailedChecks(t.Context(), &advisorsv1.GetFailedChecksRequest{ - ServiceId: serviceID, - }) - require.EqualError(t, err, fmt.Sprintf("failed to get check results for service '%s': random error", serviceID)) + resp, err := s.TestAdvisorCheck(t.Context(), &advisorsv1.TestAdvisorCheckRequest{ServiceId: "test_svc"}) + tests.AssertGRPCError(t, status.New(codes.InvalidArgument, "Check is required."), err) assert.Nil(t, resp) + checksService.AssertNotCalled(t, "TestAdvisorCheck") }) t.Run("Advisors disabled error", func(t *testing.T) { t.Parallel() var checksService mockChecksService - checksService.On("GetChecksResults", mock.Anything, mock.Anything).Return(nil, services.ErrAdvisorsDisabled) + checksService.On("TestAdvisorCheck", mock.Anything, mock.Anything, "test_svc").Return(nil, "", services.ErrAdvisorsDisabled) s := NewChecksAPIService(&checksService) - resp, err := s.GetFailedChecks(t.Context(), &advisorsv1.GetFailedChecksRequest{ - ServiceId: "test_svc", - }) + resp, err := s.TestAdvisorCheck(t.Context(), &advisorsv1.TestAdvisorCheckRequest{Check: apiCheck, ServiceId: "test_svc"}) tests.AssertGRPCError(t, status.New(codes.FailedPrecondition, "advisor checks are disabled."), err) assert.Nil(t, resp) }) - t.Run("get failed checks for requested service", func(t *testing.T) { + t.Run("execution errors keep their message", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + checksService.On("TestAdvisorCheck", mock.Anything, mock.Anything, "test_svc"). + Return(nil, "", status.Errorf(codes.FailedPrecondition, + "failed to execute check 'custom_mysql_version' on service 'svc': random error")) + + s := NewChecksAPIService(&checksService) + + resp, err := s.TestAdvisorCheck(t.Context(), &advisorsv1.TestAdvisorCheckRequest{Check: apiCheck, ServiceId: "test_svc"}) + tests.AssertGRPCError(t, status.New(codes.FailedPrecondition, + "failed to execute check 'custom_mysql_version' on service 'svc': random error"), err) + assert.Nil(t, resp) + }) + + t.Run("passes the converted check and returns converted results", func(t *testing.T) { t.Parallel() + expectedCheck := check.Check{ + Name: "custom_mysql_version", + Summary: "Check summary", + Description: "Check description", + Category: "configuration", + Subcategory: "version", + Technology: check.MySQL, + Interval: check.Standard, + Queries: []check.Query{{Type: check.MySQLShow, Query: "version"}}, + Script: "def check_context(docs, context):\n return []", + } checkResult := []services.CheckResult{ { Result: check.Result{ Summary: "Check summary", Description: "Check Description", ReadMoreURL: "https://www.example.com", - Severity: common.Emergency, + Severity: common.Warning, Labels: map[string]string{"label_key": "label_value"}, }, Target: services.Target{ServiceName: "svc", ServiceID: "test_svc"}, - CheckName: "test_check", + CheckName: "custom_mysql_version", }, } - response := &advisorsv1.GetFailedChecksResponse{ - Results: []*advisorsv1.CheckResult{ + var checksService mockChecksService + checksService.On("TestAdvisorCheck", mock.Anything, expectedCheck, "test_svc").Return(checkResult, "print output", nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.TestAdvisorCheck(t.Context(), &advisorsv1.TestAdvisorCheckRequest{Check: apiCheck, ServiceId: "test_svc"}) + require.NoError(t, err) + assert.Equal(t, &advisorsv1.TestAdvisorCheckResponse{ + Results: []*advisorsv1.TestAdvisorCheckResult{ { Summary: "Check summary", Description: "Check Description", ReadMoreUrl: "https://www.example.com", - Severity: managementv1.Severity(common.Emergency), + Severity: managementv1.Severity_SEVERITY_WARNING, Labels: map[string]string{"label_key": "label_value"}, ServiceName: "svc", ServiceId: "test_svc", - CheckName: "test_check", + CheckName: "custom_mysql_version", }, }, - TotalPages: 1, - TotalItems: 1, + ScriptOutput: "print output", + }, resp) + }) +} + +func TestListAdvisorCheckTestTargets(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + checksService.On("ListTestTargets", mock.Anything, check.PostgreSQL).Return([]services.Target{ + {ServiceID: "svc-1", ServiceName: "pg-1"}, + {ServiceID: "svc-2", ServiceName: "pg-2"}, + }, nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.ListAdvisorCheckTestTargets(t.Context(), &advisorsv1.ListAdvisorCheckTestTargetsRequest{ + Technology: advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_POSTGRESQL, + }) + require.NoError(t, err) + assert.Equal(t, &advisorsv1.ListAdvisorCheckTestTargetsResponse{ + Targets: []*advisorsv1.AdvisorCheckTestTarget{ + {ServiceId: "svc-1", ServiceName: "pg-1"}, + {ServiceId: "svc-2", ServiceName: "pg-2"}, + }, + }, resp) +} + +func TestListRuns(t *testing.T) { + t.Parallel() + + t.Run("converts runs and pagination totals", func(t *testing.T) { + t.Parallel() + + startedAt := time.Date(2026, time.August, 4, 19, 57, 28, 0, time.UTC) + finishedAt := startedAt.Add(2*time.Minute + 7*time.Second) + finished := &models.AdvisorRun{ + ID: "run-1", + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: startedAt, + FinishedAt: &finishedAt, + ChecksCount: 107, + ServicesCount: 3, + FindingsCount: 28, + ErrorsCount: 1, + } + require.NoError(t, finished.SetSeverityCounts(map[models.Severity]int{ + models.Severity(common.Error): 4, + models.Severity(common.Warning): 22, + })) + // a run still in flight has no completion and no totals yet + running := &models.AdvisorRun{ + ID: "run-2", + TriggeredBy: models.CheckTriggeredByScheduler, + StartedAt: startedAt.Add(5 * time.Minute), } + var checksService mockChecksService - checksService.On("GetChecksResults", mock.Anything, mock.Anything).Return(checkResult, nil) + checksService.On("GetRuns", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return([]*models.AdvisorRun{finished, running}, 3, nil) s := NewChecksAPIService(&checksService) - resp, err := s.GetFailedChecks(t.Context(), &advisorsv1.GetFailedChecksRequest{ - ServiceId: "test_svc", + resp, err := s.ListRuns(t.Context(), &advisorsv1.ListRunsRequest{ + PageSize: new(int32(2)), + PageIndex: new(int32(0)), }) require.NoError(t, err) - assert.Equal(t, response, resp) - }) - - t.Run("get failed checks with pagination", func(t *testing.T) { - t.Parallel() - checkResult := []services.CheckResult{ - { - Result: check.Result{ - Summary: "Check summary", - Description: "Check Description", - ReadMoreURL: "https://www.example.com", - Severity: common.Critical, - Labels: map[string]string{"label_key": "label_value"}, - }, - Target: services.Target{ServiceName: "svc", ServiceID: "test_svc"}, - CheckName: "test_check1", - }, - { - Result: check.Result{ - Summary: "Check summary 2", - Description: "Check Description 2", - ReadMoreURL: "https://www.example.com", - Severity: common.Warning, - Labels: map[string]string{"label_key": "label_value"}, - }, - Target: services.Target{ServiceName: "svc", ServiceID: "test_svc"}, - CheckName: "test_check2", - }, - { - Result: check.Result{ - Summary: "Check summary 3", - Description: "Check Description 3", - ReadMoreURL: "https://www.example.com", - Severity: common.Notice, - Labels: map[string]string{"label_key": "label_value"}, + expected := &advisorsv1.ListRunsResponse{ + Results: []*advisorsv1.AdvisorRun{ + { + Id: "run-1", + TriggeredBy: advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_USER, + StartedAt: timestamppb.New(startedAt), + FinishedAt: timestamppb.New(finishedAt), + ChecksCount: 107, + ServicesCount: 3, + FindingsCount: 28, + ErrorsCount: 1, + SeverityCounts: []*advisorsv1.SeverityCount{ + {Severity: managementv1.Severity_SEVERITY_ERROR, Count: 4}, + {Severity: managementv1.Severity_SEVERITY_WARNING, Count: 22}, + }, }, - Target: services.Target{ServiceName: "svc", ServiceID: "test_svc"}, - CheckName: "test_check3", - }, - } - response := &advisorsv1.GetFailedChecksResponse{ - Results: []*advisorsv1.CheckResult{ { - Summary: "Check summary 2", - Description: "Check Description 2", - ReadMoreUrl: "https://www.example.com", - Severity: managementv1.Severity(common.Warning), - Labels: map[string]string{"label_key": "label_value"}, - ServiceName: "svc", - ServiceId: "test_svc", - CheckName: "test_check2", + Id: "run-2", + TriggeredBy: advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER, + StartedAt: timestamppb.New(startedAt.Add(5 * time.Minute)), + SeverityCounts: []*advisorsv1.SeverityCount{}, }, }, - TotalPages: 3, TotalItems: 3, + TotalPages: 2, } + assert.Equal(t, expected, resp) + checksService.AssertExpectations(t) + }) + + t.Run("converts filters", func(t *testing.T) { + t.Parallel() + + from := time.Date(2026, time.August, 1, 0, 0, 0, 0, time.UTC) + to := time.Date(2026, time.August, 5, 0, 0, 0, 0, time.UTC) + triggeredBy := models.CheckTriggeredByScheduler + var checksService mockChecksService - checksService.On("GetChecksResults", mock.Anything, mock.Anything).Return(checkResult, nil) + checksService.On("GetRuns", mock.Anything, models.AdvisorRunFilters{ + TriggeredBy: &triggeredBy, + From: &from, + To: &to, + }, 0, 25).Return([]*models.AdvisorRun{}, 0, nil) s := NewChecksAPIService(&checksService) - resp, err := s.GetFailedChecks(t.Context(), &advisorsv1.GetFailedChecksRequest{ - ServiceId: "test_svc", - PageSize: new(int32(1)), - PageIndex: new(int32(1)), + _, err := s.ListRuns(t.Context(), &advisorsv1.ListRunsRequest{ + PageSize: new(int32(25)), + TriggeredBy: new(advisorsv1.AdvisorCheckTriggeredBy_ADVISOR_CHECK_TRIGGERED_BY_SCHEDULER), + From: timestamppb.New(from), + To: timestamppb.New(to), }) require.NoError(t, err) - assert.Equal(t, response, resp) + checksService.AssertExpectations(t) + }) + + t.Run("internal error", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + checksService.On("GetRuns", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil, 0, errors.New("boom")) + + s := NewChecksAPIService(&checksService) + + _, err := s.ListRuns(t.Context(), &advisorsv1.ListRunsRequest{}) + require.Error(t, err) }) } -func TestListFailedServices(t *testing.T) { +func TestListInsightsFilterValues(t *testing.T) { t.Parallel() t.Run("internal error", func(t *testing.T) { t.Parallel() var checksService mockChecksService - checksService.On("GetChecksResults", mock.Anything, mock.Anything).Return(nil, errors.New("random error")) + checksService.On("GetInsightsFilterValues", mock.Anything). + Return(nil, nil, errors.New("random error")) s := NewChecksAPIService(&checksService) - resp, err := s.ListFailedServices(t.Context(), &advisorsv1.ListFailedServicesRequest{}) - require.EqualError(t, err, "failed to get check results: random error") + resp, err := s.ListInsightsFilterValues(t.Context(), &advisorsv1.ListInsightsFilterValuesRequest{}) + require.EqualError(t, err, "failed to get insights filter values: random error") assert.Nil(t, resp) }) - t.Run("list services with failed checks", func(t *testing.T) { + t.Run("returns distinct values", func(t *testing.T) { t.Parallel() - checkResult := []services.CheckResult{ - { - Result: check.Result{ - Summary: "Check summary", - Description: "Check Description", - ReadMoreURL: "https://www.example.com", - Severity: common.Critical, - Labels: map[string]string{"label_key": "label_value"}, - }, - Target: services.Target{ServiceName: "svc1", ServiceID: "test_svc1"}, - CheckName: "test_check", - }, - { - Result: check.Result{ - Summary: "Check summary", - Description: "Check Description", - ReadMoreURL: "https://www.example.com", - Severity: common.Error, - Labels: map[string]string{"label_key": "label_value"}, - }, - Target: services.Target{ServiceName: "svc1", ServiceID: "test_svc1"}, - CheckName: "test_check", - }, - { - Result: check.Result{ + var checksService mockChecksService + checksService.On("GetInsightsFilterValues", mock.Anything). + Return([]string{"mysql-1", "pg-1"}, []string{"node-a"}, nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.ListInsightsFilterValues(t.Context(), &advisorsv1.ListInsightsFilterValuesRequest{}) + require.NoError(t, err) + assert.Equal(t, &advisorsv1.ListInsightsFilterValuesResponse{ + ServiceNames: []string{"mysql-1", "pg-1"}, + NodeNames: []string{"node-a"}, + }, resp) + }) +} + +func TestListInsights(t *testing.T) { + t.Parallel() + + t.Run("internal error", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + checksService.On("GetInsights", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil, 0, errors.New("random error")) + + s := NewChecksAPIService(&checksService) + + resp, err := s.ListInsights(t.Context(), &advisorsv1.ListInsightsRequest{}) + require.EqualError(t, err, "failed to get insights: random error") + assert.Nil(t, resp) + }) + + t.Run("returns converted history with pagination totals", func(t *testing.T) { + t.Parallel() + + checkedAt := time.Date(2026, time.June, 27, 10, 0, 0, 0, time.UTC) + record := &models.Insight{ + ID: "id1", + CheckName: "test_check", + Subcategory: "test_advisor", + Category: "configuration", + Interval: models.Standard, + ServiceID: "test_svc", + ServiceName: "svc", + ServiceType: models.MySQLServiceType, + NodeID: "node1", + NodeName: "node", + Status: models.CheckResultFailed, + Summary: "Check summary", + Description: "Check Description", + ReadMoreURL: "https://www.example.com", + Severity: models.Severity(common.Critical), + CheckedAt: checkedAt, + Region: "us-east-1", + AZ: "us-east-1f", + } + require.NoError(t, record.SetLabels(map[string]string{"label_key": "label_value"})) + + var checksService mockChecksService + checksService.On("GetInsights", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return([]*models.Insight{record}, 3, nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.ListInsights(t.Context(), &advisorsv1.ListInsightsRequest{ + ServiceId: "test_svc", + PageSize: new(int32(2)), + PageIndex: new(int32(0)), + }) + require.NoError(t, err) + + expected := &advisorsv1.ListInsightsResponse{ + Results: []*advisorsv1.Insight{ + { + Id: "id1", + CheckName: "test_check", + Subcategory: "test_advisor", + Category: "configuration", + Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_STANDARD, + ServiceId: "test_svc", + ServiceName: "svc", + ServiceType: string(models.MySQLServiceType), + NodeId: "node1", + NodeName: "node", + Status: advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_FAILED, Summary: "Check summary", Description: "Check Description", - ReadMoreURL: "https://www.example.com", - Severity: common.Emergency, - Labels: map[string]string{"label_key": "label_value"}, - }, - Target: services.Target{ServiceName: "svc1", ServiceID: "test_svc1"}, - CheckName: "test_check", - }, - { - Result: check.Result{ - Summary: "Check summary 2", - Description: "Check Description 2", - ReadMoreURL: "https://www.example.com", - Severity: common.Warning, + ReadMoreUrl: "https://www.example.com", + Severity: managementv1.Severity_SEVERITY_CRITICAL, Labels: map[string]string{"label_key": "label_value"}, + CheckedAt: timestamppb.New(checkedAt), + Region: "us-east-1", + Az: "us-east-1f", }, - Target: services.Target{ServiceName: "svc2", ServiceID: "test_svc2"}, - CheckName: "test_check", }, + TotalItems: 3, + TotalPages: 2, } - response := &advisorsv1.ListFailedServicesResponse{ - Result: []*advisorsv1.CheckResultSummary{ - { - ServiceName: "svc1", - ServiceId: "test_svc1", - EmergencyCount: 1, - CriticalCount: 1, - ErrorCount: 1, - }, - { - ServiceName: "svc2", - ServiceId: "test_svc2", - WarningCount: 1, - }, + assert.Equal(t, expected, resp) + }) +} + +func TestMarkInsightsRead(t *testing.T) { + t.Parallel() + + t.Run("internal error", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + checksService.On("MarkInsightsRead", mock.Anything, mock.Anything, mock.Anything). + Return(errors.New("random error")) + + s := NewChecksAPIService(&checksService) + + resp, err := s.MarkInsightsRead(t.Context(), &advisorsv1.MarkInsightsReadRequest{ + Ids: []string{"id1"}, + IsRead: true, + }) + require.EqualError(t, err, "failed to mark insights read: random error") + assert.Nil(t, resp) + }) + + t.Run("passes ids and read state through", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + checksService.On("MarkInsightsRead", mock.Anything, []string{"id1", "id2"}, true).Return(nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.MarkInsightsRead(t.Context(), &advisorsv1.MarkInsightsReadRequest{ + Ids: []string{"id1", "id2"}, + IsRead: true, + }) + require.NoError(t, err) + assert.Equal(t, &advisorsv1.MarkInsightsReadResponse{}, resp) + checksService.AssertExpectations(t) + }) + + t.Run("converts filters", func(t *testing.T) { + t.Parallel() + + severity := models.Severity(common.Warning) + status := models.CheckResultFailed + var checksService mockChecksService + checksService.On("MarkInsightsReadByFilters", mock.Anything, models.InsightFilters{ + ServiceName: "mysql-prod", + NodeName: "node-1", + Category: "security", + CheckName: "mysql_version", + RunID: "run-1", + Severity: &severity, + Status: &status, + IsRead: new(false), + }, true).Return(nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.MarkInsightsRead(t.Context(), &advisorsv1.MarkInsightsReadRequest{ + IsRead: true, + Filters: &advisorsv1.InsightsFilters{ + ServiceName: "mysql-prod", + NodeName: "node-1", + Category: "security", + CheckName: "mysql_version", + RunId: "run-1", + Severity: new(managementv1.Severity_SEVERITY_WARNING), + Status: new(advisorsv1.AdvisorCheckResultStatus_ADVISOR_CHECK_RESULT_STATUS_FAILED), + IsRead: new(false), }, - } + }) + require.NoError(t, err) + assert.Equal(t, &advisorsv1.MarkInsightsReadResponse{}, resp) + checksService.AssertExpectations(t) + }) + + t.Run("empty filters match every record", func(t *testing.T) { + t.Parallel() + var checksService mockChecksService - checksService.On("GetChecksResults", mock.Anything, mock.Anything).Return(checkResult, nil) + checksService.On("MarkInsightsReadByFilters", mock.Anything, models.InsightFilters{}, true).Return(nil) s := NewChecksAPIService(&checksService) - resp, err := s.ListFailedServices(t.Context(), &advisorsv1.ListFailedServicesRequest{}) + resp, err := s.MarkInsightsRead(t.Context(), &advisorsv1.MarkInsightsReadRequest{ + IsRead: true, + Filters: &advisorsv1.InsightsFilters{}, + }) require.NoError(t, err) - assert.ElementsMatch(t, resp.Result, response.Result) + assert.Equal(t, &advisorsv1.MarkInsightsReadResponse{}, resp) + checksService.AssertExpectations(t) + }) + + t.Run("requires ids or filters", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + s := NewChecksAPIService(&checksService) + + resp, err := s.MarkInsightsRead(t.Context(), &advisorsv1.MarkInsightsReadRequest{ + IsRead: true, + }) + tests.AssertGRPCError(t, status.New(codes.InvalidArgument, "Either ids or filters must be provided."), err) + assert.Nil(t, resp) }) } @@ -301,7 +534,9 @@ func TestListAdvisorChecks(t *testing.T) { t.Run("normal", func(t *testing.T) { var checksService mockChecksService checksService.On("GetDisabledChecks", mock.Anything).Return([]string{"two"}, nil) - checksService.On("GetChecks", mock.Anything). + checksService.On("GetDisabledServicesForChecks", mock.Anything). + Return(map[string][]string{"three": {"svc-1", "svc-2"}}, nil) + checksService.On("GetChecks"). Return(map[string]check.Check{ "one": {Name: "one", Interval: check.Standard}, "two": {Name: "two", Interval: check.Frequent}, @@ -320,7 +555,7 @@ func TestListAdvisorChecks(t *testing.T) { []*advisorsv1.AdvisorCheck{ {Name: "one", Enabled: true, Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_STANDARD}, {Name: "two", Enabled: false, Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_FREQUENT}, - {Name: "three", Enabled: true, Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_RARE}, + {Name: "three", Enabled: true, Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_RARE, DisabledServiceIds: []string{"svc-1", "svc-2"}}, {Name: "four", Enabled: true, Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_STANDARD}, }, ) @@ -338,10 +573,110 @@ func TestListAdvisorChecks(t *testing.T) { }) } +func TestUpdateAdvisorCheck(t *testing.T) { + t.Parallel() + + apiCheck := func(name string) *advisorsv1.AdvisorCheck { + return &advisorsv1.AdvisorCheck{ + Name: name, + Summary: "Check summary", + Description: "Check description", + Category: "configuration", + Subcategory: "version", + Technology: advisorsv1.AdvisorCheckTechnology_ADVISOR_CHECK_TECHNOLOGY_MYSQL, + Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_STANDARD, + Queries: []*advisorsv1.AdvisorCheckQuery{{Type: "MYSQL_SHOW", Query: "version"}}, + Script: "def check_context(docs, context):\n return []", + } + } + + t.Run("check is required", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + + s := NewChecksAPIService(&checksService) + + resp, err := s.UpdateAdvisorCheck(t.Context(), &advisorsv1.UpdateAdvisorCheckRequest{Name: "custom_one"}) + tests.AssertGRPCError(t, status.New(codes.InvalidArgument, "Check is required."), err) + assert.Nil(t, resp) + checksService.AssertNotCalled(t, "UpdateAdvisorCheck") + }) + + t.Run("rename is rejected", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + + s := NewChecksAPIService(&checksService) + + resp, err := s.UpdateAdvisorCheck(t.Context(), &advisorsv1.UpdateAdvisorCheckRequest{ + Name: "custom_one", + Check: apiCheck("custom_two"), + }) + tests.AssertGRPCError(t, status.New(codes.InvalidArgument, + "Advisor check cannot be renamed: name 'custom_two' in the request body does not match 'custom_one'."), err) + assert.Nil(t, resp) + checksService.AssertNotCalled(t, "UpdateAdvisorCheck") + }) + + t.Run("an empty body name falls back to the path name", func(t *testing.T) { + t.Parallel() + + expectedCheck := check.Check{ + Name: "custom_one", + Summary: "Check summary", + Description: "Check description", + Category: "configuration", + Subcategory: "version", + Technology: check.MySQL, + Interval: check.Standard, + Queries: []check.Query{{Type: check.MySQLShow, Query: "version"}}, + Script: "def check_context(docs, context):\n return []", + } + + var checksService mockChecksService + checksService.On("UpdateAdvisorCheck", mock.Anything, expectedCheck).Return(nil) + checksService.On("GetChecks").Return(map[string]check.Check{"custom_one": expectedCheck}, nil) + checksService.On("GetDisabledChecks", mock.Anything).Return([]string(nil), nil) + checksService.On("GetDisabledServicesForChecks", mock.Anything).Return(map[string][]string(nil), nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.UpdateAdvisorCheck(t.Context(), &advisorsv1.UpdateAdvisorCheckRequest{ + Name: "custom_one", + Check: apiCheck(""), + }) + require.NoError(t, err) + assert.Equal(t, "custom_one", resp.Check.Name) + checksService.AssertExpectations(t) + }) + + t.Run("a matching body name is accepted", func(t *testing.T) { + t.Parallel() + + var checksService mockChecksService + checksService.On("UpdateAdvisorCheck", mock.Anything, mock.Anything).Return(nil) + checksService.On("GetChecks"). + Return(map[string]check.Check{"custom_one": {Name: "custom_one", Interval: check.Standard}}, nil) + checksService.On("GetDisabledChecks", mock.Anything).Return([]string(nil), nil) + checksService.On("GetDisabledServicesForChecks", mock.Anything).Return(map[string][]string(nil), nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.UpdateAdvisorCheck(t.Context(), &advisorsv1.UpdateAdvisorCheckRequest{ + Name: "custom_one", + Check: apiCheck("custom_one"), + }) + require.NoError(t, err) + assert.Equal(t, "custom_one", resp.Check.Name) + }) +} + func TestUpdateAdvisorChecks(t *testing.T) { t.Run("enable advisor checks error", func(t *testing.T) { var checksService mockChecksService - checksService.On("EnableChecks", mock.Anything).Return(errors.New("random error")) + checksService.On("EnableChecks", mock.Anything, mock.Anything).Return(errors.New("random error")) s := NewChecksAPIService(&checksService) @@ -352,8 +687,8 @@ func TestUpdateAdvisorChecks(t *testing.T) { t.Run("disable advisor checks error", func(t *testing.T) { var checksService mockChecksService - checksService.On("EnableChecks", mock.Anything).Return(nil) - checksService.On("DisableChecks", mock.Anything).Return(errors.New("random error")) + checksService.On("EnableChecks", mock.Anything, mock.Anything).Return(nil) + checksService.On("DisableChecks", mock.Anything, mock.Anything).Return(errors.New("random error")) s := NewChecksAPIService(&checksService) @@ -364,7 +699,7 @@ func TestUpdateAdvisorChecks(t *testing.T) { t.Run("change interval error", func(t *testing.T) { var checksService mockChecksService - checksService.On("ChangeInterval", mock.Anything).Return(errors.New("random error")) + checksService.On("ChangeInterval", mock.Anything, mock.Anything).Return(errors.New("random error")) s := NewChecksAPIService(&checksService) @@ -380,9 +715,9 @@ func TestUpdateAdvisorChecks(t *testing.T) { t.Run("ChangeInterval success", func(t *testing.T) { var checksService mockChecksService - checksService.On("ChangeInterval", mock.Anything).Return(nil) - checksService.On("EnableChecks", mock.Anything).Return(nil) - checksService.On("DisableChecks", mock.Anything).Return(nil) + checksService.On("ChangeInterval", mock.Anything, mock.Anything).Return(nil) + checksService.On("EnableChecks", mock.Anything, mock.Anything).Return(nil) + checksService.On("DisableChecks", mock.Anything, mock.Anything).Return(nil) s := NewChecksAPIService(&checksService) @@ -395,45 +730,79 @@ func TestUpdateAdvisorChecks(t *testing.T) { require.NoError(t, err) assert.Equal(t, &advisorsv1.ChangeAdvisorChecksResponse{}, resp) }) -} -func TestCreateComment(t *testing.T) { - t.Parallel() + t.Run("disable for services", func(t *testing.T) { + var checksService mockChecksService + checksService.On("DisableChecksForServices", mock.Anything, "check-name", []string{"svc-1", "svc-2"}).Return(nil) + checksService.On("EnableChecks", mock.Anything, mock.Anything).Return(nil) + checksService.On("DisableChecks", mock.Anything, mock.Anything).Return(nil) - testCases := []struct { - Name string - Comment string - Checks []check.Check - }{ - { - Name: "all technologies", - Comment: "All technologies supported", - Checks: []check.Check{ - {Version: 1, Name: "a", Type: check.MySQLShow}, - {Version: 1, Name: "b", Type: check.PostgreSQLSelect}, - {Version: 2, Name: "c", Family: check.MongoDB}, - }, - }, - { - Name: "partial support", - Comment: "Partial support (MySQL, MongoDB)", - Checks: []check.Check{ - {Version: 1, Name: "a", Type: check.MySQLShow}, - {Version: 2, Name: "b", Family: check.MongoDB}, - }, - }, - { - Name: "partial support", - Comment: "Partial support (MySQL)", - Checks: []check.Check{ - {Version: 1, Name: "a", Type: check.MySQLShow}, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - t.Parallel() - assert.Equal(t, tc.Comment, createComment(tc.Checks)) + s := NewChecksAPIService(&checksService) + + resp, err := s.ChangeAdvisorChecks(t.Context(), &advisorsv1.ChangeAdvisorChecksRequest{ + Params: []*advisorsv1.ChangeAdvisorCheckParams{{ + Name: "check-name", + Enable: new(false), + ServiceIds: []string{"svc-1", "svc-2"}, + }}, }) - } + require.NoError(t, err) + assert.Equal(t, &advisorsv1.ChangeAdvisorChecksResponse{}, resp) + checksService.AssertCalled(t, "DisableChecksForServices", mock.Anything, "check-name", []string{"svc-1", "svc-2"}) + // a per-service change must not touch the global enable/disable lists + checksService.AssertNotCalled(t, "DisableChecks", mock.Anything, []string{"check-name"}) + }) + + t.Run("enable for services", func(t *testing.T) { + var checksService mockChecksService + checksService.On("EnableChecksForServices", mock.Anything, "check-name", []string{"svc-1"}).Return(nil) + checksService.On("EnableChecks", mock.Anything, mock.Anything).Return(nil) + checksService.On("DisableChecks", mock.Anything, mock.Anything).Return(nil) + + s := NewChecksAPIService(&checksService) + + resp, err := s.ChangeAdvisorChecks(t.Context(), &advisorsv1.ChangeAdvisorChecksRequest{ + Params: []*advisorsv1.ChangeAdvisorCheckParams{{ + Name: "check-name", + Enable: new(true), + ServiceIds: []string{"svc-1"}, + }}, + }) + require.NoError(t, err) + assert.Equal(t, &advisorsv1.ChangeAdvisorChecksResponse{}, resp) + checksService.AssertCalled(t, "EnableChecksForServices", mock.Anything, "check-name", []string{"svc-1"}) + }) + + t.Run("interval change with services rejected", func(t *testing.T) { + var checksService mockChecksService + + s := NewChecksAPIService(&checksService) + + resp, err := s.ChangeAdvisorChecks(t.Context(), &advisorsv1.ChangeAdvisorChecksRequest{ + Params: []*advisorsv1.ChangeAdvisorCheckParams{{ + Name: "check-name", + Interval: advisorsv1.AdvisorCheckInterval_ADVISOR_CHECK_INTERVAL_RARE, + ServiceIds: []string{"svc-1"}, + }}, + }) + require.Error(t, err) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + assert.Nil(t, resp) + }) + + t.Run("services without enable flag rejected", func(t *testing.T) { + var checksService mockChecksService + + s := NewChecksAPIService(&checksService) + + resp, err := s.ChangeAdvisorChecks(t.Context(), &advisorsv1.ChangeAdvisorChecksRequest{ + Params: []*advisorsv1.ChangeAdvisorCheckParams{{ + Name: "check-name", + ServiceIds: []string{"svc-1"}, + }}, + }) + require.Error(t, err) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + assert.Nil(t, resp) + }) } diff --git a/managed/services/management/deps.go b/managed/services/management/deps.go index aa94b71a44a..72fe12cf20e 100644 --- a/managed/services/management/deps.go +++ b/managed/services/management/deps.go @@ -54,15 +54,27 @@ type prometheusService interface { // checksService is a subset of methods of checks.Service used by this package. // We use it instead of real type for testing and to avoid dependency cycle. -type checksService interface { - StartChecks(checkNames []string) error +type checksService interface { //nolint:interfacebloat + StartChecks(checkNames, serviceIDs []string) (string, error) GetChecks() (map[string]check.Check, error) GetAdvisors() ([]check.Advisor, error) - GetChecksResults(ctx context.Context, serviceID string) ([]services.CheckResult, error) - GetDisabledChecks() ([]string, error) - DisableChecks(checkNames []string) error - EnableChecks(checkNames []string) error - ChangeInterval(params map[string]check.Interval) error + GetInsights(ctx context.Context, filters models.InsightFilters, pageIndex, pageSize int) ([]*models.Insight, int, error) + GetRuns(ctx context.Context, filters models.AdvisorRunFilters, pageIndex, pageSize int) ([]*models.AdvisorRun, int, error) + GetInsightsFilterValues(ctx context.Context) ([]string, []string, error) + MarkInsightsRead(ctx context.Context, ids []string, isRead bool) error + MarkInsightsReadByFilters(ctx context.Context, filters models.InsightFilters, isRead bool) error + GetDisabledChecks(ctx context.Context) ([]string, error) + GetDisabledServicesForChecks(ctx context.Context) (map[string][]string, error) + DisableChecks(ctx context.Context, checkNames []string) error + EnableChecks(ctx context.Context, checkNames []string) error + DisableChecksForServices(ctx context.Context, checkName string, serviceIDs []string) error + EnableChecksForServices(ctx context.Context, checkName string, serviceIDs []string) error + ChangeInterval(ctx context.Context, params map[string]check.Interval) error + CreateAdvisorCheck(ctx context.Context, c check.Check) error + UpdateAdvisorCheck(ctx context.Context, c check.Check) error + DeleteAdvisorCheck(ctx context.Context, name string) error + TestAdvisorCheck(ctx context.Context, c check.Check, serviceID string) ([]services.CheckResult, string, error) + ListTestTargets(ctx context.Context, technology check.Technology) ([]services.Target, error) } // grafanaClient is a subset of methods of grafana.Client used by this package. diff --git a/managed/services/management/mock_checks_service_test.go b/managed/services/management/mock_checks_service_test.go index fc74bcbdb4d..0e0c6d529a3 100644 --- a/managed/services/management/mock_checks_service_test.go +++ b/managed/services/management/mock_checks_service_test.go @@ -7,6 +7,7 @@ import ( mock "github.com/stretchr/testify/mock" + models "github.com/percona/pmm/managed/models" check "github.com/percona/pmm/managed/pi/check" services "github.com/percona/pmm/managed/services" ) @@ -16,17 +17,17 @@ type mockChecksService struct { mock.Mock } -// ChangeInterval provides a mock function with given fields: params -func (_m *mockChecksService) ChangeInterval(params map[string]check.Interval) error { - ret := _m.Called(params) +// ChangeInterval provides a mock function with given fields: ctx, params +func (_m *mockChecksService) ChangeInterval(ctx context.Context, params map[string]check.Interval) error { + ret := _m.Called(ctx, params) if len(ret) == 0 { panic("no return value specified for ChangeInterval") } var r0 error - if rf, ok := ret.Get(0).(func(map[string]check.Interval) error); ok { - r0 = rf(params) + if rf, ok := ret.Get(0).(func(context.Context, map[string]check.Interval) error); ok { + r0 = rf(ctx, params) } else { r0 = ret.Error(0) } @@ -34,17 +35,53 @@ func (_m *mockChecksService) ChangeInterval(params map[string]check.Interval) er return r0 } -// DisableChecks provides a mock function with given fields: checkNames -func (_m *mockChecksService) DisableChecks(checkNames []string) error { - ret := _m.Called(checkNames) +// CreateAdvisorCheck provides a mock function with given fields: ctx, c +func (_m *mockChecksService) CreateAdvisorCheck(ctx context.Context, c check.Check) error { + ret := _m.Called(ctx, c) + + if len(ret) == 0 { + panic("no return value specified for CreateAdvisorCheck") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, check.Check) error); ok { + r0 = rf(ctx, c) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteAdvisorCheck provides a mock function with given fields: ctx, name +func (_m *mockChecksService) DeleteAdvisorCheck(ctx context.Context, name string) error { + ret := _m.Called(ctx, name) + + if len(ret) == 0 { + panic("no return value specified for DeleteAdvisorCheck") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, name) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DisableChecks provides a mock function with given fields: ctx, checkNames +func (_m *mockChecksService) DisableChecks(ctx context.Context, checkNames []string) error { + ret := _m.Called(ctx, checkNames) if len(ret) == 0 { panic("no return value specified for DisableChecks") } var r0 error - if rf, ok := ret.Get(0).(func([]string) error); ok { - r0 = rf(checkNames) + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(ctx, checkNames) } else { r0 = ret.Error(0) } @@ -52,17 +89,53 @@ func (_m *mockChecksService) DisableChecks(checkNames []string) error { return r0 } -// EnableChecks provides a mock function with given fields: checkNames -func (_m *mockChecksService) EnableChecks(checkNames []string) error { - ret := _m.Called(checkNames) +// DisableChecksForServices provides a mock function with given fields: ctx, checkName, serviceIDs +func (_m *mockChecksService) DisableChecksForServices(ctx context.Context, checkName string, serviceIDs []string) error { + ret := _m.Called(ctx, checkName, serviceIDs) + + if len(ret) == 0 { + panic("no return value specified for DisableChecksForServices") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, checkName, serviceIDs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EnableChecks provides a mock function with given fields: ctx, checkNames +func (_m *mockChecksService) EnableChecks(ctx context.Context, checkNames []string) error { + ret := _m.Called(ctx, checkNames) if len(ret) == 0 { panic("no return value specified for EnableChecks") } var r0 error - if rf, ok := ret.Get(0).(func([]string) error); ok { - r0 = rf(checkNames) + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(ctx, checkNames) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EnableChecksForServices provides a mock function with given fields: ctx, checkName, serviceIDs +func (_m *mockChecksService) EnableChecksForServices(ctx context.Context, checkName string, serviceIDs []string) error { + ret := _m.Called(ctx, checkName, serviceIDs) + + if len(ret) == 0 { + panic("no return value specified for EnableChecksForServices") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { + r0 = rf(ctx, checkName, serviceIDs) } else { r0 = ret.Error(0) } @@ -130,29 +203,29 @@ func (_m *mockChecksService) GetChecks() (map[string]check.Check, error) { return r0, r1 } -// GetChecksResults provides a mock function with given fields: ctx, serviceID -func (_m *mockChecksService) GetChecksResults(ctx context.Context, serviceID string) ([]services.CheckResult, error) { - ret := _m.Called(ctx, serviceID) +// GetDisabledChecks provides a mock function with given fields: ctx +func (_m *mockChecksService) GetDisabledChecks(ctx context.Context) ([]string, error) { + ret := _m.Called(ctx) if len(ret) == 0 { - panic("no return value specified for GetChecksResults") + panic("no return value specified for GetDisabledChecks") } - var r0 []services.CheckResult + var r0 []string var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) ([]services.CheckResult, error)); ok { - return rf(ctx, serviceID) + if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { + return rf(ctx) } - if rf, ok := ret.Get(0).(func(context.Context, string) []services.CheckResult); ok { - r0 = rf(ctx, serviceID) + if rf, ok := ret.Get(0).(func(context.Context) []string); ok { + r0 = rf(ctx) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]services.CheckResult) + r0 = ret.Get(0).([]string) } } - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, serviceID) + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) } else { r1 = ret.Error(1) } @@ -160,29 +233,172 @@ func (_m *mockChecksService) GetChecksResults(ctx context.Context, serviceID str return r0, r1 } -// GetDisabledChecks provides a mock function with no fields -func (_m *mockChecksService) GetDisabledChecks() ([]string, error) { - ret := _m.Called() +// GetDisabledServicesForChecks provides a mock function with given fields: ctx +func (_m *mockChecksService) GetDisabledServicesForChecks(ctx context.Context) (map[string][]string, error) { + ret := _m.Called(ctx) if len(ret) == 0 { - panic("no return value specified for GetDisabledChecks") + panic("no return value specified for GetDisabledServicesForChecks") } - var r0 []string + var r0 map[string][]string var r1 error - if rf, ok := ret.Get(0).(func() ([]string, error)); ok { - return rf() + if rf, ok := ret.Get(0).(func(context.Context) (map[string][]string, error)); ok { + return rf(ctx) } - if rf, ok := ret.Get(0).(func() []string); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) map[string][]string); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string][]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetInsights provides a mock function with given fields: ctx, filters, pageIndex, pageSize +func (_m *mockChecksService) GetInsights(ctx context.Context, filters models.InsightFilters, pageIndex int, pageSize int) ([]*models.Insight, int, error) { + ret := _m.Called(ctx, filters, pageIndex, pageSize) + + if len(ret) == 0 { + panic("no return value specified for GetInsights") + } + + var r0 []*models.Insight + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, models.InsightFilters, int, int) ([]*models.Insight, int, error)); ok { + return rf(ctx, filters, pageIndex, pageSize) + } + if rf, ok := ret.Get(0).(func(context.Context, models.InsightFilters, int, int) []*models.Insight); ok { + r0 = rf(ctx, filters, pageIndex, pageSize) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Insight) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, models.InsightFilters, int, int) int); ok { + r1 = rf(ctx, filters, pageIndex, pageSize) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(context.Context, models.InsightFilters, int, int) error); ok { + r2 = rf(ctx, filters, pageIndex, pageSize) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetInsightsFilterValues provides a mock function with given fields: ctx +func (_m *mockChecksService) GetInsightsFilterValues(ctx context.Context) ([]string, []string, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetInsightsFilterValues") + } + + var r0 []string + var r1 []string + var r2 error + if rf, ok := ret.Get(0).(func(context.Context) ([]string, []string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []string); ok { + r0 = rf(ctx) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]string) } } - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() + if rf, ok := ret.Get(1).(func(context.Context) []string); ok { + r1 = rf(ctx) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]string) + } + } + + if rf, ok := ret.Get(2).(func(context.Context) error); ok { + r2 = rf(ctx) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetRuns provides a mock function with given fields: ctx, filters, pageIndex, pageSize +func (_m *mockChecksService) GetRuns(ctx context.Context, filters models.AdvisorRunFilters, pageIndex int, pageSize int) ([]*models.AdvisorRun, int, error) { + ret := _m.Called(ctx, filters, pageIndex, pageSize) + + if len(ret) == 0 { + panic("no return value specified for GetRuns") + } + + var r0 []*models.AdvisorRun + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, models.AdvisorRunFilters, int, int) ([]*models.AdvisorRun, int, error)); ok { + return rf(ctx, filters, pageIndex, pageSize) + } + if rf, ok := ret.Get(0).(func(context.Context, models.AdvisorRunFilters, int, int) []*models.AdvisorRun); ok { + r0 = rf(ctx, filters, pageIndex, pageSize) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.AdvisorRun) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, models.AdvisorRunFilters, int, int) int); ok { + r1 = rf(ctx, filters, pageIndex, pageSize) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(context.Context, models.AdvisorRunFilters, int, int) error); ok { + r2 = rf(ctx, filters, pageIndex, pageSize) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// ListTestTargets provides a mock function with given fields: ctx, technology +func (_m *mockChecksService) ListTestTargets(ctx context.Context, technology check.Technology) ([]services.Target, error) { + ret := _m.Called(ctx, technology) + + if len(ret) == 0 { + panic("no return value specified for ListTestTargets") + } + + var r0 []services.Target + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, check.Technology) ([]services.Target, error)); ok { + return rf(ctx, technology) + } + if rf, ok := ret.Get(0).(func(context.Context, check.Technology) []services.Target); ok { + r0 = rf(ctx, technology) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]services.Target) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, check.Technology) error); ok { + r1 = rf(ctx, technology) } else { r1 = ret.Error(1) } @@ -190,17 +406,118 @@ func (_m *mockChecksService) GetDisabledChecks() ([]string, error) { return r0, r1 } -// StartChecks provides a mock function with given fields: checkNames -func (_m *mockChecksService) StartChecks(checkNames []string) error { - ret := _m.Called(checkNames) +// MarkInsightsRead provides a mock function with given fields: ctx, ids, isRead +func (_m *mockChecksService) MarkInsightsRead(ctx context.Context, ids []string, isRead bool) error { + ret := _m.Called(ctx, ids, isRead) + + if len(ret) == 0 { + panic("no return value specified for MarkInsightsRead") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string, bool) error); ok { + r0 = rf(ctx, ids, isRead) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MarkInsightsReadByFilters provides a mock function with given fields: ctx, filters, isRead +func (_m *mockChecksService) MarkInsightsReadByFilters(ctx context.Context, filters models.InsightFilters, isRead bool) error { + ret := _m.Called(ctx, filters, isRead) + + if len(ret) == 0 { + panic("no return value specified for MarkInsightsReadByFilters") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, models.InsightFilters, bool) error); ok { + r0 = rf(ctx, filters, isRead) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// StartChecks provides a mock function with given fields: checkNames, serviceIDs +func (_m *mockChecksService) StartChecks(checkNames []string, serviceIDs []string) (string, error) { + ret := _m.Called(checkNames, serviceIDs) if len(ret) == 0 { panic("no return value specified for StartChecks") } + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func([]string, []string) (string, error)); ok { + return rf(checkNames, serviceIDs) + } + if rf, ok := ret.Get(0).(func([]string, []string) string); ok { + r0 = rf(checkNames, serviceIDs) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func([]string, []string) error); ok { + r1 = rf(checkNames, serviceIDs) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TestAdvisorCheck provides a mock function with given fields: ctx, c, serviceID +func (_m *mockChecksService) TestAdvisorCheck(ctx context.Context, c check.Check, serviceID string) ([]services.CheckResult, string, error) { + ret := _m.Called(ctx, c, serviceID) + + if len(ret) == 0 { + panic("no return value specified for TestAdvisorCheck") + } + + var r0 []services.CheckResult + var r1 string + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, check.Check, string) ([]services.CheckResult, string, error)); ok { + return rf(ctx, c, serviceID) + } + if rf, ok := ret.Get(0).(func(context.Context, check.Check, string) []services.CheckResult); ok { + r0 = rf(ctx, c, serviceID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]services.CheckResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, check.Check, string) string); ok { + r1 = rf(ctx, c, serviceID) + } else { + r1 = ret.Get(1).(string) + } + + if rf, ok := ret.Get(2).(func(context.Context, check.Check, string) error); ok { + r2 = rf(ctx, c, serviceID) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// UpdateAdvisorCheck provides a mock function with given fields: ctx, c +func (_m *mockChecksService) UpdateAdvisorCheck(ctx context.Context, c check.Check) error { + ret := _m.Called(ctx, c) + + if len(ret) == 0 { + panic("no return value specified for UpdateAdvisorCheck") + } + var r0 error - if rf, ok := ret.Get(0).(func([]string) error); ok { - r0 = rf(checkNames) + if rf, ok := ret.Get(0).(func(context.Context, check.Check) error); ok { + r0 = rf(ctx, c) } else { r0 = ret.Error(0) } diff --git a/managed/services/server/advisor_notifications.go b/managed/services/server/advisor_notifications.go new file mode 100644 index 00000000000..82d08836ca6 --- /dev/null +++ b/managed/services/server/advisor_notifications.go @@ -0,0 +1,52 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package server + +import ( + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/percona/pmm/managed/models" +) + +// validateAdvisorNotificationRecipients rejects a settings change that would leave Advisor +// notifications enabled with nobody to notify, which would otherwise fail silently once a check +// run completed. +// +// It resolves the effective post-change state rather than looking at the request alone, because +// enablement and recipients are separate fields and either one may already be stored. Callers that +// bypass the API - PMM_ENABLE_ADVISOR_NOTIFICATIONS via UpdateSettingsFromEnv - are not covered, so +// the delivery path still logs when it finds no recipients. +func validateAdvisorNotificationRecipients(oldSettings *models.Settings, params *models.ChangeSettingsParams) error { + enabled := oldSettings.IsAdvisorNotificationsEnabled() + if params.EnableAdvisorNotifications != nil { + enabled = *params.EnableAdvisorNotifications + } + if !enabled { + return nil + } + + addresses := oldSettings.AdvisorNotifications.EmailAddresses + if params.AdvisorNotificationEmailAddresses != nil { + addresses = params.AdvisorNotificationEmailAddresses + } + if len(addresses) == 0 { + return status.Error(codes.InvalidArgument, "Invalid argument: advisor_notification_email_addresses: "+ + "at least one recipient is required while Advisor notifications are enabled.") + } + + return nil +} diff --git a/managed/services/server/advisor_notifications_test.go b/managed/services/server/advisor_notifications_test.go new file mode 100644 index 00000000000..f37874163a3 --- /dev/null +++ b/managed/services/server/advisor_notifications_test.go @@ -0,0 +1,88 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package server + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/percona/pmm/managed/models" +) + +func TestValidateAdvisorNotificationRecipients(t *testing.T) { + t.Parallel() + + settings := func(enabled bool, addresses ...string) *models.Settings { + s := &models.Settings{} + s.AdvisorNotifications.Enabled = new(enabled) + s.AdvisorNotifications.EmailAddresses = addresses + return s + } + + t.Run("disabled needs no recipients", func(t *testing.T) { + t.Parallel() + + err := validateAdvisorNotificationRecipients(settings(false), &models.ChangeSettingsParams{}) + require.NoError(t, err) + }) + + t.Run("disabling drops the requirement even with no recipients", func(t *testing.T) { + t.Parallel() + + err := validateAdvisorNotificationRecipients(settings(true, "a@example.com"), + &models.ChangeSettingsParams{ + EnableAdvisorNotifications: new(false), + AdvisorNotificationEmailAddresses: []string{}, + }) + require.NoError(t, err) + }) + + t.Run("enabling without recipients is rejected", func(t *testing.T) { + t.Parallel() + + err := validateAdvisorNotificationRecipients(settings(false), + &models.ChangeSettingsParams{EnableAdvisorNotifications: new(true)}) + require.ErrorContains(t, err, "at least one recipient is required") + }) + + t.Run("enabling with recipients in the same request is accepted", func(t *testing.T) { + t.Parallel() + + err := validateAdvisorNotificationRecipients(settings(false), + &models.ChangeSettingsParams{ + EnableAdvisorNotifications: new(true), + AdvisorNotificationEmailAddresses: []string{"a@example.com"}, + }) + require.NoError(t, err) + }) + + t.Run("already enabled, stored recipients satisfy an unrelated change", func(t *testing.T) { + t.Parallel() + + err := validateAdvisorNotificationRecipients(settings(true, "a@example.com"), + &models.ChangeSettingsParams{}) + require.NoError(t, err) + }) + + t.Run("clearing recipients while enabled is rejected", func(t *testing.T) { + t.Parallel() + + err := validateAdvisorNotificationRecipients(settings(true, "a@example.com"), + &models.ChangeSettingsParams{AdvisorNotificationEmailAddresses: []string{}}) + require.ErrorContains(t, err, "at least one recipient is required") + }) +} diff --git a/managed/services/server/deps.go b/managed/services/server/deps.go index 5d1c510130b..84faa777896 100644 --- a/managed/services/server/deps.go +++ b/managed/services/server/deps.go @@ -40,7 +40,7 @@ type grafanaClient interface { //nolint:iface // We use it instead of real type to avoid dependency cycle. // // FIXME Rename to victoriaMetrics.Service, update tests. -type prometheusService interface { //nolint:iface +type prometheusService interface { RequestConfigurationUpdate() // ForceConfigurationUpdate triggers immediate synchronous configuration update, // bypassing the batch delay. Use this for critical updates like port changes. @@ -51,15 +51,15 @@ type prometheusService interface { //nolint:iface // checksService is a subset of methods of checks.Service used by this package. // We use it instead of real type for testing and to avoid dependency cycle. type checksService interface { - StartChecks(checkNames []string) error + StartChecks(checkNames, serviceIDs []string) (string, error) UpdateAdvisorsList(ctx context.Context) - CleanupAlerts() + CleanupCheckResults() UpdateIntervals(rare, standard, frequent time.Duration) } // vmAlertService is a subset of methods of vmalert.Service used by this package. // We use it instead of real type to avoid dependency cycle. -type vmAlertService interface { //nolint:iface +type vmAlertService interface { RequestConfigurationUpdate() healthChecker } @@ -75,7 +75,7 @@ type vmAlertExternalRules interface { // supervisordService is a subset of methods of supervisord.Service used by this package. // We use it instead of real type for testing and to avoid dependency cycle. -type supervisordService interface { +type supervisordService interface { //nolint:iface UpdateConfiguration(settings *models.Settings) error } @@ -114,6 +114,6 @@ type victoriaMetricsParams interface { } // nomadService represents an interface for managing and updating Nomad-related configurations in a given context. -type nomadService interface { +type nomadService interface { //nolint:iface UpdateConfiguration(settings *models.Settings) error } diff --git a/managed/services/server/mock_checks_service_test.go b/managed/services/server/mock_checks_service_test.go index bcb24a68ebc..ff4f0443a62 100644 --- a/managed/services/server/mock_checks_service_test.go +++ b/managed/services/server/mock_checks_service_test.go @@ -14,27 +14,37 @@ type mockChecksService struct { mock.Mock } -// CleanupAlerts provides a mock function with no fields -func (_m *mockChecksService) CleanupAlerts() { +// CleanupCheckResults provides a mock function with no fields +func (_m *mockChecksService) CleanupCheckResults() { _m.Called() } -// StartChecks provides a mock function with given fields: checkNames -func (_m *mockChecksService) StartChecks(checkNames []string) error { - ret := _m.Called(checkNames) +// StartChecks provides a mock function with given fields: checkNames, serviceIDs +func (_m *mockChecksService) StartChecks(checkNames []string, serviceIDs []string) (string, error) { + ret := _m.Called(checkNames, serviceIDs) if len(ret) == 0 { panic("no return value specified for StartChecks") } - var r0 error - if rf, ok := ret.Get(0).(func([]string) error); ok { - r0 = rf(checkNames) + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func([]string, []string) (string, error)); ok { + return rf(checkNames, serviceIDs) + } + if rf, ok := ret.Get(0).(func([]string, []string) string); ok { + r0 = rf(checkNames, serviceIDs) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func([]string, []string) error); ok { + r1 = rf(checkNames, serviceIDs) } else { - r0 = ret.Error(0) + r1 = ret.Error(1) } - return r0 + return r0, r1 } // UpdateAdvisorsList provides a mock function with given fields: ctx diff --git a/managed/services/server/server.go b/managed/services/server/server.go index 199ee2bfed9..6a5371891dd 100644 --- a/managed/services/server/server.go +++ b/managed/services/server/server.go @@ -38,8 +38,10 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "gopkg.in/reform.v1" + managementv1 "github.com/percona/pmm/api/management/v1" serverv1 "github.com/percona/pmm/api/server/v1" "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" "github.com/percona/pmm/managed/utils/distribution" "github.com/percona/pmm/managed/utils/envvars" "github.com/percona/pmm/version" @@ -169,7 +171,7 @@ func (s *Server) Version(_ context.Context, req *serverv1.VersionRequest) (*serv if err != nil { return nil, err } - grpcCode := codes.Code(code) + grpcCode := codes.Code(code) //nolint:gosec // debug-only value from the dummy field return nil, status.Errorf(grpcCode, "gRPC code %d (%s)", grpcCode, grpcCode) } } @@ -347,13 +349,18 @@ func (s *Server) convertSettings(settings *models.Settings, disableInternalPgQan StandardInterval: durationpb.New(settings.SaaS.AdvisorRunIntervals.StandardInterval), FrequentInterval: durationpb.New(settings.SaaS.AdvisorRunIntervals.FrequentInterval), }, - DataRetention: durationpb.New(settings.DataRetention), - SshKey: settings.SSHKey, - AwsPartitions: settings.AWSPartitions, - AdvisorEnabled: settings.IsAdvisorsEnabled(), - AzurediscoverEnabled: settings.IsAzureDiscoverEnabled(), - PmmPublicAddress: settings.PMMPublicAddress, - EnableInternalPgQan: !disableInternalPgQan, + DataRetention: durationpb.New(settings.DataRetention), + AdvisorHistoryRetention: durationpb.New(settings.AdvisorHistoryRetention), + //nolint:gosec // severity is a small bounded enum value + AdvisorNotificationSeverityThreshold: managementv1.Severity(settings.AdvisorNotifications.SeverityThreshold), + AdvisorNotificationsEnabled: settings.IsAdvisorNotificationsEnabled(), + AdvisorNotificationEmailAddresses: settings.AdvisorNotifications.EmailAddresses, + SshKey: settings.SSHKey, + AwsPartitions: settings.AWSPartitions, + AdvisorEnabled: settings.IsAdvisorsEnabled(), + AzurediscoverEnabled: settings.IsAzureDiscoverEnabled(), + PmmPublicAddress: settings.PMMPublicAddress, + EnableInternalPgQan: !disableInternalPgQan, AlertingEnabled: settings.IsAlertingEnabled(), BackupManagementEnabled: settings.IsBackupManagementEnabled(), @@ -468,6 +475,11 @@ func (s *Server) validateChangeSettingsRequest(ctx context.Context, req *serverv return status.Error(codes.FailedPrecondition, "Azure Discover is configured via PMM_ENABLE_AZURE_DISCOVER environment variable.") } + if req.EnableAdvisorNotifications != nil && s.envSettings.EnableAdvisorNotifications != nil && + *req.EnableAdvisorNotifications != *s.envSettings.EnableAdvisorNotifications { + return status.Error(codes.FailedPrecondition, "Advisor notifications are configured via PMM_ENABLE_ADVISOR_NOTIFICATIONS environment variable.") + } + if !canUpdateDurationSetting(metricsRes.GetHr().AsDuration(), s.envSettings.MetricsResolutions.HR) { return status.Error( codes.FailedPrecondition, @@ -487,6 +499,10 @@ func (s *Server) validateChangeSettingsRequest(ctx context.Context, req *serverv return status.Error(codes.FailedPrecondition, "Data retention for queries is set via PMM_DATA_RETENTION environment variable.") } + if !canUpdateDurationSetting(req.AdvisorHistoryRetention.AsDuration(), s.envSettings.AdvisorHistoryRetention) { + return status.Error(codes.FailedPrecondition, "Advisor check results history retention is set via PMM_ADVISOR_HISTORY_RETENTION environment variable.") + } + return nil } @@ -530,8 +546,11 @@ func (s *Server) ChangeSettings(ctx context.Context, req *serverv1.ChangeSetting MR: metricsRes.GetMr().AsDuration(), LR: metricsRes.GetLr().AsDuration(), }, - DataRetention: req.DataRetention.AsDuration(), - SSHKey: req.SshKey, + DataRetention: req.DataRetention.AsDuration(), + AdvisorHistoryRetention: req.AdvisorHistoryRetention.AsDuration(), + EnableAdvisorNotifications: req.EnableAdvisorNotifications, + AdvisorNotificationSeverityThreshold: common.Severity(req.AdvisorNotificationSeverityThreshold), + SSHKey: req.SshKey, } if req.AwsPartitions != nil { @@ -539,6 +558,19 @@ func (s *Server) ChangeSettings(ctx context.Context, req *serverv1.ChangeSetting settingsParams.AWSPartitions = req.AwsPartitions.Values } + if req.AdvisorNotificationEmailAddresses != nil { + // Nil treated as "do not change", empty slice treated as "clear the recipients" + settingsParams.AdvisorNotificationEmailAddresses = req.AdvisorNotificationEmailAddresses.Values + } + + // Notifications with nobody to notify would fail silently at run-completion time, so + // reject the combination here rather than at delivery. The effective state is checked, not + // just this request's fields, because either half may already be stored. + err = validateAdvisorNotificationRecipients(oldSettings, settingsParams) + if err != nil { + return err + } + var errInvalidArgument *models.InvalidArgumentError newSettings, err = models.UpdateSettings(tx, settingsParams) switch { @@ -591,15 +623,15 @@ func (s *Server) ChangeSettings(ctx context.Context, req *serverv1.ChangeSetting var advisorsStarted bool if !oldSettings.IsAdvisorsEnabled() && newSettings.IsAdvisorsEnabled() { advisorsStarted = true - err := s.checksService.StartChecks(nil) + _, err := s.checksService.StartChecks(nil, nil) if err != nil { s.l.Error(err) } } - // When Advisor is moved from enabled to disabled state, drop all existing alerts. + // When Advisor is moved from enabled to disabled state, drop all existing check results. if oldSettings.IsAdvisorsEnabled() && !newSettings.IsAdvisorsEnabled() { - s.checksService.CleanupAlerts() + s.checksService.CleanupCheckResults() } // When telemetry state is switched force alert templates and Advisor check files collection. diff --git a/managed/services/server/server_test.go b/managed/services/server/server_test.go index 7a3ed57a2ff..cf44eca5a7f 100644 --- a/managed/services/server/server_test.go +++ b/managed/services/server/server_test.go @@ -16,8 +16,6 @@ package server import ( - "context" - "errors" "math" "testing" "time" @@ -27,9 +25,12 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/durationpb" "gopkg.in/reform.v1" "gopkg.in/reform.v1/dialects/postgresql" + "github.com/percona/pmm/api/common" + managementv1 "github.com/percona/pmm/api/management/v1" serverv1 "github.com/percona/pmm/api/server/v1" "github.com/percona/pmm/managed/models" "github.com/percona/pmm/managed/utils/testdb" @@ -50,8 +51,8 @@ func TestServer(t *testing.T) { mvmdb.On("RequestConfigurationUpdate").Return(nil) mState := &mockAgentsStateUpdater{} mState.Test(t) - mState.On("UpdateAgentsState", context.TODO()).Return(nil) - mState.On("RequestStateUpdate", context.TODO(), mock.Anything).Return(nil) + mState.On("UpdateAgentsState", t.Context()).Return(nil) + mState.On("RequestStateUpdate", t.Context(), mock.Anything).Return(nil) var mvmalert mockPrometheusService mvmalert.Test(t) @@ -59,11 +60,11 @@ func TestServer(t *testing.T) { var mtemplatesService mockTemplatesService mtemplatesService.Test(t) - mtemplatesService.On("CollectTemplates", context.TODO()).Return(nil) + mtemplatesService.On("CollectTemplates", t.Context()).Return(nil) var mchecksService mockChecksService mchecksService.Test(t) - mchecksService.On("UpdateAdvisorsList", context.TODO()).Return(nil) + mchecksService.On("UpdateAdvisorsList", t.Context()).Return(nil) var par mockVmAlertExternalRules par.Test(t) @@ -82,6 +83,10 @@ func TestServer(t *testing.T) { ha.On("IsLeader").Return(true) ha.On("Params").Return(&models.HAParams{Enabled: false}) + var mgrafana mockGrafanaClient + mgrafana.Test(t) + mgrafana.On("IsReady", mock.Anything).Return(nil) + s, err := NewServer(&Params{ DB: reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)), VMDB: &mvmdb, @@ -94,6 +99,7 @@ func TestServer(t *testing.T) { TelemetryService: &ts, Nomad: &nomad, HAService: &ha, + GrafanaClient: &mgrafana, }) require.NoError(t, err) return s @@ -102,7 +108,7 @@ func TestServer(t *testing.T) { t.Run("UpdateSettingsFromEnv", func(t *testing.T) { t.Run("Typical", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_ENABLE_UPDATES=true", "PMM_ENABLE_TELEMETRY=1", "PMM_METRICS_RESOLUTION_HR=1s", @@ -123,7 +129,7 @@ func TestServer(t *testing.T) { t.Run("Untypical", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_ENABLE_TELEMETRY=TrUe", "PMM_METRICS_RESOLUTION=3S", "PMM_DATA_RETENTION=360H", @@ -136,7 +142,7 @@ func TestServer(t *testing.T) { t.Run("NoValue", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_ENABLE_TELEMETRY", }) require.Len(t, errs, 1) @@ -146,7 +152,7 @@ func TestServer(t *testing.T) { t.Run("InvalidValue", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_ENABLE_TELEMETRY=", }) require.Len(t, errs, 1) @@ -156,43 +162,43 @@ func TestServer(t *testing.T) { t.Run("MetricsLessThenMin", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_METRICS_RESOLUTION=5ns", }) require.Len(t, errs, 1) var errInvalidArgument *models.InvalidArgumentError - assert.True(t, errors.As(errs[0], &errInvalidArgument)) + require.ErrorAs(t, errs[0], &errInvalidArgument) require.EqualError(t, errs[0], `invalid argument: hr: minimal resolution is 1s`) assert.Zero(t, s.envSettings.MetricsResolutions.HR) }) t.Run("DataRetentionLessThenMin", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_DATA_RETENTION=12h", }) require.Len(t, errs, 1) var errInvalidArgument *models.InvalidArgumentError - assert.True(t, errors.As(errs[0], &errInvalidArgument)) + require.ErrorAs(t, errs[0], &errInvalidArgument) require.EqualError(t, errs[0], `invalid argument: data_retention: minimal resolution is 24h`) assert.Zero(t, s.envSettings.DataRetention) }) t.Run("Data retention is not a natural number of days", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_DATA_RETENTION=30h", }) require.Len(t, errs, 1) var errInvalidArgument *models.InvalidArgumentError - assert.True(t, errors.As(errs[0], &errInvalidArgument)) + require.ErrorAs(t, errs[0], &errInvalidArgument) require.EqualError(t, errs[0], `invalid argument: data_retention: should be a natural number of days`) assert.Zero(t, s.envSettings.DataRetention) }) t.Run("Data retention without suffix", func(t *testing.T) { s := newServer(t) - errs := s.UpdateSettingsFromEnv(context.TODO(), []string{ + errs := s.UpdateSettingsFromEnv(t.Context(), []string{ "PMM_DATA_RETENTION=30", }) require.Len(t, errs, 1) @@ -204,7 +210,7 @@ func TestServer(t *testing.T) { t.Run("ValidateChangeSettingsRequest", func(t *testing.T) { s := newServer(t) - ctx := context.TODO() + ctx := t.Context() s.envSettings.EnableUpdates = new(true) expected := status.New(codes.FailedPrecondition, "Updates are configured via PMM_ENABLE_UPDATES environment variable.") @@ -239,17 +245,26 @@ func TestServer(t *testing.T) { require.NoError(t, s.validateChangeSettingsRequest(ctx, &serverv1.ChangeSettingsRequest{ EnableAdvisor: new(true), })) + + s.envSettings.EnableAdvisorNotifications = new(true) + expected = status.New(codes.FailedPrecondition, "Advisor notifications are configured via PMM_ENABLE_ADVISOR_NOTIFICATIONS environment variable.") + tests.AssertGRPCError(t, expected, s.validateChangeSettingsRequest(ctx, &serverv1.ChangeSettingsRequest{ + EnableAdvisorNotifications: new(false), + })) + require.NoError(t, s.validateChangeSettingsRequest(ctx, &serverv1.ChangeSettingsRequest{ + EnableAdvisorNotifications: new(true), + })) }) t.Run("ChangeSettings", func(t *testing.T) { server := newServer(t) - server.UpdateSettingsFromEnv(context.TODO(), []string{ + server.UpdateSettingsFromEnv(t.Context(), []string{ "ENABLE_ALERTING=1", "PMM_ENABLE_AZURE_DISCOVER=1", }) - ctx := context.TODO() + ctx := t.Context() s, err := server.ChangeSettings(ctx, &serverv1.ChangeSettingsRequest{ EnableTelemetry: new(true), @@ -266,9 +281,9 @@ func TestServer(t *testing.T) { t.Run("ChangeSettings Alerting", func(t *testing.T) { server := newServer(t) - server.UpdateSettingsFromEnv(context.TODO(), []string{}) + server.UpdateSettingsFromEnv(t.Context(), []string{}) - ctx := context.TODO() + ctx := t.Context() s, err := server.ChangeSettings(ctx, &serverv1.ChangeSettingsRequest{ EnableAlerting: new(false), }) @@ -281,6 +296,31 @@ func TestServer(t *testing.T) { require.NoError(t, err) require.NotNil(t, s) }) + + t.Run("ChangeSettings Advisor notifications", func(t *testing.T) { + server := newServer(t) + server.UpdateSettingsFromEnv(t.Context(), []string{}) + + ctx := t.Context() + s, err := server.ChangeSettings(ctx, &serverv1.ChangeSettingsRequest{ + EnableAdvisorNotifications: new(true), + AdvisorNotificationSeverityThreshold: managementv1.Severity_SEVERITY_WARNING, + AdvisorHistoryRetention: durationpb.New(48 * time.Hour), + // enabling the notifications requires at least one recipient + AdvisorNotificationEmailAddresses: &common.StringArray{ + Values: []string{"dba@percona.com"}, + }, + }) + require.NoError(t, err) + require.NotNil(t, s) + + settings, err := server.GetSettings(ctx, &serverv1.GetSettingsRequest{}) + require.NoError(t, err) + assert.True(t, settings.Settings.AdvisorNotificationsEnabled) + assert.Equal(t, managementv1.Severity_SEVERITY_WARNING, settings.Settings.AdvisorNotificationSeverityThreshold) + assert.Equal(t, durationpb.New(48*time.Hour), settings.Settings.AdvisorHistoryRetention) + assert.Equal(t, []string{"dba@percona.com"}, settings.Settings.AdvisorNotificationEmailAddresses) + }) } func TestConvertDefaultRoleID(t *testing.T) { diff --git a/managed/services/telemetry/config.default.yml b/managed/services/telemetry/config.default.yml index 326dca0262b..2387ccd6f94 100644 --- a/managed/services/telemetry/config.default.yml +++ b/managed/services/telemetry/config.default.yml @@ -742,7 +742,7 @@ telemetry: - id: AdvisorsChecksDisabled source: PMMDB_SELECT - query: settings->'sass'->'disabled_advisors' as disabled_checks from settings; + query: json_agg(name) as disabled_checks from advisor_checks where disabled; summary: "Advisor - Checks that are disabled on the PMM instance" transform: type: JSON diff --git a/managed/services/types.go b/managed/services/types.go index 1cf34920fab..98f59d5609e 100644 --- a/managed/services/types.go +++ b/managed/services/types.go @@ -24,16 +24,22 @@ import ( // Target contains required info about advisor check target. type Target struct { - AgentID string - ServiceID string - ServiceName string - ServiceType models.ServiceType - NodeName string - Labels map[string]string - DSN string - Files map[string]string - TDP *models.DelimiterPair - TLSSkipVerify bool + AgentID string + ServiceID string + ServiceName string + ServiceType models.ServiceType + NodeID string + NodeName string + Environment string + Cluster string + ReplicationSet string + Region string + AZ string + Labels map[string]string + DSN string + Files map[string]string + TDP *models.DelimiterPair + TLSSkipVerify bool } // Copy creates a copy of the Target instance. @@ -45,38 +51,30 @@ func (t *Target) Copy() Target { maps.Copy(files, t.Files) return Target{ - AgentID: t.AgentID, - ServiceID: t.ServiceID, - ServiceName: t.ServiceName, - ServiceType: t.ServiceType, - NodeName: t.NodeName, - Labels: labels, - DSN: t.DSN, - Files: files, - TDP: new(*t.TDP), - TLSSkipVerify: t.TLSSkipVerify, + AgentID: t.AgentID, + ServiceID: t.ServiceID, + ServiceName: t.ServiceName, + ServiceType: t.ServiceType, + NodeID: t.NodeID, + NodeName: t.NodeName, + Environment: t.Environment, + Cluster: t.Cluster, + ReplicationSet: t.ReplicationSet, + Region: t.Region, + AZ: t.AZ, + Labels: labels, + DSN: t.DSN, + Files: files, + TDP: new(*t.TDP), + TLSSkipVerify: t.TLSSkipVerify, } } // CheckResult contains the output from the check file and other information. type CheckResult struct { CheckName string - AdvisorName string + Subcategory string Interval check.Interval Target Target Result check.Result } - -// CheckResultSummary contains the summary of failed checks for a service. -type CheckResultSummary struct { - ServiceName string - ServiceID string - EmergencyCount uint32 - AlertCount uint32 - CriticalCount uint32 - ErrorCount uint32 - WarningCount uint32 - NoticeCount uint32 - InfoCount uint32 - DebugCount uint32 -} diff --git a/managed/services/types_test.go b/managed/services/types_test.go index 06e14f8e280..cd8a7383f45 100644 --- a/managed/services/types_test.go +++ b/managed/services/types_test.go @@ -30,10 +30,18 @@ func TestTarget_Copy(t1 *testing.T) { ServiceID: "service_id", ServiceName: "service_name", ServiceType: models.MySQLServiceType, + NodeID: "node_id", NodeName: "node_name", - Labels: map[string]string{"label": "value"}, - DSN: "dsn", - Files: map[string]string{"file": "test"}, + // the per-database PostgreSQL path runs checks against copies, so every + // field a copy drops is a field missing from that target's insights + Environment: "prod", + Cluster: "cluster_1", + ReplicationSet: "rs_1", + Region: "us-east-1", + AZ: "us-east-1f", + Labels: map[string]string{"label": "value"}, + DSN: "dsn", + Files: map[string]string{"file": "test"}, TDP: &models.DelimiterPair{ Left: "[", Right: "]", diff --git a/managed/testdata/checks/good_check_pg.yml b/managed/testdata/checks/good_check_pg.yml index 35e95307732..d2f75d7281f 100644 --- a/managed/testdata/checks/good_check_pg.yml +++ b/managed/testdata/checks/good_check_pg.yml @@ -4,8 +4,9 @@ checks: name: good_check_pg summary: Good Check PG description: Good check for PostgreSQL. - advisor: dev - family: POSTGRESQL + category: Development + subcategory: Dev + technology: POSTGRESQL queries: - type: POSTGRESQL_SELECT query: rolpassword FROM pg_authid WHERE rolcanlogin diff --git a/managed/utils/clean/clean.go b/managed/utils/clean/clean.go index 58bbd0cc94e..8a47fe7b858 100644 --- a/managed/utils/clean/clean.go +++ b/managed/utils/clean/clean.go @@ -43,11 +43,7 @@ func (c *Results) Run(ctx context.Context, interval time.Duration, olderThan tim l := logrus.WithField("component", "cleaner") for { - olderThanTS := models.Now().Add(-1 * olderThan) - err := models.CleanupOldActionResults(c.db.Querier, olderThanTS) - if err != nil { - l.Error(err) - } + c.cleanup(l, olderThan) select { case <-ctx.Done(): @@ -56,3 +52,12 @@ func (c *Results) Run(ctx context.Context, interval time.Duration, olderThan tim } } } + +// cleanup performs a single cleanup pass, removing action results older than the given age. +func (c *Results) cleanup(l *logrus.Entry, olderThan time.Duration) { + olderThanTS := models.Now().Add(-1 * olderThan) + err := models.CleanupOldActionResults(c.db.Querier, olderThanTS) + if err != nil { + l.Error(err) + } +} diff --git a/managed/utils/clean/clean_test.go b/managed/utils/clean/clean_test.go index b8c2bbfcd7a..e79e5d48fbf 100644 --- a/managed/utils/clean/clean_test.go +++ b/managed/utils/clean/clean_test.go @@ -16,10 +16,10 @@ package clean import ( - "context" "testing" "time" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/reform.v1" @@ -81,15 +81,8 @@ func TestCleaner(t *testing.T) { db, q, teardown := setup(t) defer teardown(t) - ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second) - defer cancel() - - c := New(db) - go func() { - c.Run(ctx, 5*time.Second, 5*time.Second) // delete rows older that 5 seconds - }() - // give the cleaner the chance to run - time.Sleep(100 * time.Millisecond) + // Run a single cleanup pass synchronously, deleting rows older than 5 seconds. + New(db).cleanup(logrus.WithField("component", "test"), 5*time.Second) _, err := models.FindActionResultByID(q, "A1") require.Error(t, err) diff --git a/managed/utils/clean/insights.go b/managed/utils/clean/insights.go new file mode 100644 index 00000000000..75ea90b3384 --- /dev/null +++ b/managed/utils/clean/insights.go @@ -0,0 +1,75 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package clean + +import ( + "context" + "time" + + "github.com/sirupsen/logrus" + "gopkg.in/reform.v1" + + "github.com/percona/pmm/managed/models" +) + +// Insights cleans up Advisor insights past the configured retention. +type Insights struct { + db *reform.DB +} + +// NewInsights returns a new Insights cleaner. +func NewInsights(db *reform.DB) *Insights { + return &Insights{db: db} +} + +// Run starts the Advisor insights cleanup process. +func (c *Insights) Run(ctx context.Context, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + l := logrus.WithField("component", "advisor-history-cleaner") + + for { + c.cleanup(ctx, l) + + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + +// cleanup performs a single cleanup pass, removing insights past the configured retention. +func (c *Insights) cleanup(ctx context.Context, l *logrus.Entry) { + settings, err := models.GetSettings(c.db) + if err != nil { + l.Error(err) + return + } + + olderThanTS := models.Now().Add(-1 * settings.AdvisorHistoryRetention) + err = models.CleanupOldInsights(ctx, c.db.Querier, olderThanTS) + if err != nil { + l.Error(err) + } + + // Runs share the retention window but are pruned by their own start time, so + // a run keeps reporting its stored totals until it ages out itself. + err = models.CleanupOldAdvisorRuns(ctx, c.db.Querier, olderThanTS) + if err != nil { + l.Error(err) + } +} diff --git a/managed/utils/clean/insights_test.go b/managed/utils/clean/insights_test.go new file mode 100644 index 00000000000..9d7591cd2ab --- /dev/null +++ b/managed/utils/clean/insights_test.go @@ -0,0 +1,78 @@ +// Copyright (C) 2023 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package clean + +import ( + "testing" + "time" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/dialects/postgresql" + + "github.com/percona/pmm/managed/models" + "github.com/percona/pmm/managed/pi/common" + "github.com/percona/pmm/managed/utils/testdb" +) + +func TestInsightsCleaner(t *testing.T) { + sqlDB := testdb.Open(t, models.SkipFixtures, nil) + defer func() { + require.NoError(t, sqlDB.Close()) + }() + + db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)) + q := db.Querier + + // Default retention is 30 days, so this row is past it and must be removed. + require.NoError(t, models.CreateInsight(t.Context(), q, &models.Insight{ + CheckName: "old", ServiceID: "svc", ServiceName: "svc", NodeName: "node", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now().Add(-31 * 24 * time.Hour), + })) + require.NoError(t, models.CreateInsight(t.Context(), q, &models.Insight{ + CheckName: "new", ServiceID: "svc", ServiceName: "svc", NodeName: "node", + Status: models.CheckResultFailed, Summary: "s", + Severity: models.Severity(common.Warning), CheckedAt: models.Now(), + })) + + // Runs share the retention window but age out on their own start time. + oldRun := &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: models.Now().Add(-31 * 24 * time.Hour), + } + require.NoError(t, models.StartAdvisorRun(t.Context(), q, oldRun)) + newRun := &models.AdvisorRun{ + TriggeredBy: models.CheckTriggeredByUser, + StartedAt: models.Now(), + } + require.NoError(t, models.StartAdvisorRun(t.Context(), q, newRun)) + + // Run a single cleanup pass synchronously; the ticker loop in Run is trivial plumbing. + NewInsights(db).cleanup(t.Context(), logrus.WithField("component", "test")) + + results, err := models.FindInsights(t.Context(), q, models.InsightFilters{ServiceID: "svc"}, 0, 0) + require.NoError(t, err) + require.Len(t, results, 1) + assert.Equal(t, "new", results[0].CheckName) + + runs, err := models.FindAdvisorRuns(t.Context(), q, models.AdvisorRunFilters{}, 0, 0) + require.NoError(t, err) + require.Len(t, runs, 1) + assert.Equal(t, newRun.ID, runs[0].ID) +} diff --git a/managed/utils/envvars/parser.go b/managed/utils/envvars/parser.go index fb78164b65a..81d7ab0dfdc 100644 --- a/managed/utils/envvars/parser.go +++ b/managed/utils/envvars/parser.go @@ -63,6 +63,8 @@ func (e InvalidDurationError) Error() string { return string(e) } // - PMM_ENABLE_ALERTING disables Percona Alerting; // - PMM_METRICS_RESOLUTION, PMM_METRICS_RESOLUTION_MR, PMM_METRICS_RESOLUTION_HR, PMM_METRICS_RESOLUTION_LR are durations of metrics resolution; // - PMM_DATA_RETENTION is the duration of how long keep time-series data in ClickHouse; +// - PMM_ADVISOR_HISTORY_RETENTION is the duration of how long to keep Advisor check results history; +// - PMM_ENABLE_ADVISOR_NOTIFICATIONS enables Advisor email notifications; // - PMM_ENABLE_AZURE_DISCOVER enables Azure Discover; // - PMM_ENABLE_ACCESS_CONTROL enables Access control; // - the environment variables prefixed with GF_ passed as related to Grafana. @@ -168,6 +170,12 @@ func ParseEnvVars(envs []string) (*models.ChangeSettingsParams, []error, []strin errs = append(errs, formatEnvVariableError(err, env, v)) continue } + case "PMM_ADVISOR_HISTORY_RETENTION": + envSettings.AdvisorHistoryRetention, err = parseStringDuration(v) + if err != nil { + errs = append(errs, formatEnvVariableError(err, env, v)) + continue + } case "PMM_ENABLE_VM_CACHE": b, err := strconv.ParseBool(v) if err != nil { @@ -183,6 +191,14 @@ func ParseEnvVars(envs []string) (*models.ChangeSettingsParams, []error, []strin } envSettings.EnableAlerting = &b + case "PMM_ENABLE_ADVISOR_NOTIFICATIONS": + b, err := strconv.ParseBool(v) + if err != nil { + errs = append(errs, fmt.Errorf("invalid value %q for environment variable %q", v, k)) + continue + } + envSettings.EnableAdvisorNotifications = &b + case "PMM_ENABLE_AZURE_DISCOVER": b, err := strconv.ParseBool(v) if err != nil { diff --git a/managed/utils/envvars/parser_test.go b/managed/utils/envvars/parser_test.go index 24211d072c7..37dfb2a699e 100644 --- a/managed/utils/envvars/parser_test.go +++ b/managed/utils/envvars/parser_test.go @@ -38,12 +38,16 @@ func TestEnvVarValidator(t *testing.T) { "PMM_METRICS_RESOLUTION_MR=5s", "PMM_METRICS_RESOLUTION_LR=1h", "PMM_DATA_RETENTION=72h", + "PMM_ADVISOR_HISTORY_RETENTION=48h", + "PMM_ENABLE_ADVISOR_NOTIFICATIONS=true", } expectedEnvVars := &models.ChangeSettingsParams{ - DataRetention: 72 * time.Hour, - EnableTelemetry: new(true), - EnableUpdates: new(false), - EnableAdvisors: nil, + DataRetention: 72 * time.Hour, + AdvisorHistoryRetention: 48 * time.Hour, + EnableAdvisorNotifications: new(true), + EnableTelemetry: new(true), + EnableUpdates: new(false), + EnableAdvisors: nil, MetricsResolutions: models.MetricsResolutions{ HR: 5 * time.Minute, MR: 5 * time.Second, diff --git a/ui/AGENTS.md b/ui/AGENTS.md index efbcd6f3718..f1782a125f3 100644 --- a/ui/AGENTS.md +++ b/ui/AGENTS.md @@ -45,18 +45,20 @@ PMM UI runs inside a Grafana iframe. Cross-frame communication uses `CrossFrameM Routes are defined in `ui/apps/pmm/src/router.tsx` using React Router's `createBrowserRouter` with `basename: '/pmm-ui'`: -| Route | Page | -| ------------------ | ------------------------------- | -| `/` | Redirects to `/graph` (Grafana) | -| `/updates` | PMM Server updates | -| `/updates/clients` | Client updates | -| `/help` | Help center | -| `/rta` | Real-Time Analytics tab | -| `/rta/selection` | RTA service selection | -| `/rta/sessions` | RTA sessions list | -| `/rta/overview` | RTA overview | -| `/graph/*` | Grafana iframe | -| `*` | 404 fallback | +| Route | Page | +| -------------------- | ------------------------------- | +| `/` | Redirects to `/graph` (Grafana) | +| `/updates` | PMM Server updates | +| `/updates/clients` | Client updates | +| `/help` | Help center | +| `/rta` | Real-Time Analytics tab | +| `/rta/selection` | RTA service selection | +| `/rta/sessions` | RTA sessions list | +| `/rta/overview` | RTA overview | +| `/advisors` | Advisors | +| `/advisors/insights` | Advisor insights | +| `/graph/*` | Grafana iframe | +| `*` | 404 fallback | ## State Management diff --git a/ui/apps/pmm/package.json b/ui/apps/pmm/package.json index b159a276dee..4795292c593 100644 --- a/ui/apps/pmm/package.json +++ b/ui/apps/pmm/package.json @@ -35,12 +35,14 @@ "date-fns": "4.1.0", "export-to-csv": "^1.4.0", "notistack": "^3.0.2", + "prism-react-renderer": "^2.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.71.2", "react-is": "18.3.1", "react-markdown": "^9.0.1", "react-router-dom": "^6.30.2", + "react-simple-code-editor": "^0.14.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "vite-plugin-svgr": "^4.3.0", diff --git a/ui/apps/pmm/src/App.tsx b/ui/apps/pmm/src/App.tsx index 928c45f1ff5..f5ae3c47027 100644 --- a/ui/apps/pmm/src/App.tsx +++ b/ui/apps/pmm/src/App.tsx @@ -1,18 +1,20 @@ +import type { PaletteMode } from '@mui/material'; +import type { ThemeOptions } from '@mui/material/styles'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { RouterProvider } from 'react-router-dom'; -import router from './router'; -import { SnackbarProvider, CustomContentProps } from 'notistack'; import { + NotistackMuiSnackbar, ThemeContextProvider, pmmThemeOptions, - NotistackMuiSnackbar, } from '@percona/percona-ui'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { addApiErrorInterceptor, removeApiErrorInterceptor } from 'api/api'; import { ThemeClass } from 'components/theme-class'; -import { useEffect } from 'react'; +import { CustomContentProps, SnackbarProvider } from 'notistack'; import type { ComponentType } from 'react'; -import { addApiErrorInterceptor, removeApiErrorInterceptor } from 'api/api'; +import { useEffect } from 'react'; +import { RouterProvider } from 'react-router-dom'; +import router from './router'; const queryClient = new QueryClient({ defaultOptions: { @@ -23,6 +25,30 @@ const queryClient = new QueryClient({ }, }); +const DARK_BACKGROUND_COLOR = 'rgb(10, 10, 18)'; + +// pmmThemeOptions with the dark background overridden; `paper` is included +// because most page surfaces (RTA, Grafana frame, settings) paint with it +const themeOptions = (mode: PaletteMode): ThemeOptions => { + const options = pmmThemeOptions(mode); + + if (mode !== 'dark') { + return options; + } + + return { + ...options, + palette: { + ...options.palette, + background: { + ...options.palette?.background, + default: DARK_BACKGROUND_COLOR, + paper: DARK_BACKGROUND_COLOR, + }, + }, + }; +}; + const App = () => { useEffect(() => { addApiErrorInterceptor(); @@ -32,7 +58,7 @@ const App = () => { }, []); return ( - + { + it('camelizes schema field names', () => { + expect( + camelizeInsights({ + total_items: 2, + results: [{ check_name: 'chk', read_more_url: 'https://example.com' }], + }) + ).toEqual({ + totalItems: 2, + results: [{ checkName: 'chk', readMoreUrl: 'https://example.com' }], + }); + }); + + it('leaves label keys exactly as the API returned them', () => { + const labels = { + service_name: 'mysql-svc', + node_id: 'pmm-server', + agent_type: 'qan-mysql-perfschema-agent', + az: 'us-east-1f', + myCustomLabel: 'kept', + }; + + const result = camelizeInsights({ + results: [{ check_name: 'chk', labels }], + }) as { results: Array<{ labels: Record }> }; + + expect(result.results[0].labels).toEqual(labels); + }); + + it('passes through primitives and nulls', () => { + expect(camelizeInsights(null)).toBeNull(); + expect(camelizeInsights('a_b')).toBe('a_b'); + expect(camelizeInsights(7)).toBe(7); + }); +}); diff --git a/ui/apps/pmm/src/api/advisors.ts b/ui/apps/pmm/src/api/advisors.ts index 536cafbd7dd..9bbb4600cb5 100644 --- a/ui/apps/pmm/src/api/advisors.ts +++ b/ui/apps/pmm/src/api/advisors.ts @@ -1,7 +1,181 @@ -import { Advisor, ListAdvisorsResponse } from 'types/advisors.types'; +import { + Advisor, + AdvisorCheck, + AdvisorCheckInput, + AdvisorCheckTestTarget, + AdvisorRun, + AdvisorTechnology, + ChangeAdvisorCheckParams, + ChangeAdvisorChecksRequest, + Insight, + CreateAdvisorCheckRequest, + CreateAdvisorCheckResponse, + GetAdvisorCheckResponse, + ListAdvisorCheckTestTargetsResponse, + ListAdvisorsResponse, + ListInsightsFilterValuesResponse, + ListInsightsParams, + ListRunsParams, + MarkInsightsReadRequest, + StartAdvisorChecksRequest, + StartAdvisorChecksResponse, + TestAdvisorCheckRequest, + TestAdvisorCheckResponse, + UpdateAdvisorCheckRequest, + UpdateAdvisorCheckResponse, +} from 'types/advisors.types'; +import { EmptyResponse, PaginatedResponse } from 'types/util.types'; import { api } from './api'; export const listAdvisors = async (): Promise => { const res = await api.get('/advisors'); return res.data.advisors; }; + +export const getAdvisorCheck = async (name: string): Promise => { + const res = await api.get( + `/advisors/checks/${encodeURIComponent(name)}` + ); + return res.data.check; +}; + +export const createAdvisorCheck = async ( + check: AdvisorCheckInput +): Promise => { + const payload: CreateAdvisorCheckRequest = { check }; + const res = await api.post( + '/advisors/checks', + payload + ); + return res.data.check; +}; + +export const updateAdvisorCheck = async ( + name: string, + check: AdvisorCheckInput +): Promise => { + const payload: UpdateAdvisorCheckRequest = { check }; + const res = await api.put( + `/advisors/checks/${encodeURIComponent(name)}`, + payload + ); + return res.data.check; +}; + +export const deleteAdvisorCheck = async (name: string): Promise => { + await api.delete( + `/advisors/checks/${encodeURIComponent(name)}` + ); +}; + +export const startAdvisorChecks = async ( + payload: StartAdvisorChecksRequest +): Promise => { + const res = await api.post( + '/advisors/checks:start', + payload + ); + return res.data.runId; +}; + +export const testAdvisorCheck = async ( + payload: TestAdvisorCheckRequest +): Promise => { + // errors are rendered inside the form's test results panel, + // so the global error snackbar is suppressed + const res = await api.post( + '/advisors/checks:test', + payload, + { disableNotifications: true } + ); + return res.data; +}; + +export const listAdvisorCheckTestTargets = async ( + technology: AdvisorTechnology +): Promise => { + const res = await api.get( + '/advisors/checks:testTargets', + { params: { technology } } + ); + return res.data.targets ?? []; +}; + +export const changeAdvisorChecks = async ( + params: ChangeAdvisorCheckParams[] +): Promise => { + const payload: ChangeAdvisorChecksRequest = { params }; + await api.post('/advisors/checks:batchChange', payload); +}; + +// Fields whose value is a free-form map keyed by data, not by a schema field +// name, so its keys must survive verbatim. +const RAW_KEY_FIELDS = ['labels']; + +const camelizeKey = (key: string) => + key.replace(/_([a-z0-9])/g, (_, char: string) => char.toUpperCase()); + +// axios-case-converter camelizes every response key recursively, which rewrites +// label names (service_name -> serviceName). Insights bypass that instance-wide +// transform and are camelized here instead, so labels read exactly as stored. +export const camelizeInsights = (value: unknown): unknown => { + if (Array.isArray(value)) { + return value.map(camelizeInsights); + } + if (value === null || typeof value !== 'object') { + return value; + } + return Object.fromEntries( + Object.entries(value).map(([key, val]) => [ + camelizeKey(key), + RAW_KEY_FIELDS.includes(key) ? val : camelizeInsights(val), + ]) + ); +}; + +export const listInsights = async ( + params: ListInsightsParams +): Promise> => { + const res = await api.get>('/advisors/insights', { + params, + // replaces the instance chain, so the JSON parse happens here too; falling + // back to the raw body on unparseable input keeps the error interceptor, + // which reads `data.message`, working as it does for every other endpoint + transformResponse: [ + (raw: string) => { + if (typeof raw !== 'string' || !raw) { + return raw; + } + try { + return camelizeInsights(JSON.parse(raw)); + } catch { + return raw; + } + }, + ], + }); + return res.data; +}; + +export const listRuns = async ( + params: ListRunsParams +): Promise> => { + const res = await api.get>('/advisors/runs', { + params, + }); + return res.data; +}; + +export const markInsightsRead = async ( + payload: MarkInsightsReadRequest +): Promise => { + await api.post('/advisors/insights:markRead', payload); +}; + +export const listInsightsFilterValues = + async (): Promise => { + const res = await api.get( + '/advisors/insights:filterValues' + ); + return res.data; + }; diff --git a/ui/apps/pmm/src/components/page/Page.tsx b/ui/apps/pmm/src/components/page/Page.tsx index 029e9e3fd6b..60ff408c0e9 100644 --- a/ui/apps/pmm/src/components/page/Page.tsx +++ b/ui/apps/pmm/src/components/page/Page.tsx @@ -24,6 +24,8 @@ export const Page: FC = ({ footer, children, fullWidth, + wide, + fillViewport, surface, roles, }) => { @@ -50,7 +52,7 @@ export const Page: FC = ({ flex: 1, width: '100%', maxWidth: { - lg: 1000, + lg: wide ? 'none' : 1000, }, p: { xs: 2, @@ -61,11 +63,26 @@ export const Page: FC = ({ mx: 'auto', gap: 2, mt: 1, + // pin to the viewport so the content region scrolls instead of the page: + // fill the available height (flex) but never exceed the viewport. + ...(fillViewport && { + mt: 0, + minHeight: 0, + maxHeight: '100vh', + overflow: 'hidden', + }), }} > {topBar} {!!title && {title}} - + {user?.isAuthorized && hasAccess ? ( children ) : ( @@ -84,7 +101,8 @@ export const Page: FC = ({ )} - + {/* footer === null explicitly opts out of the divider + footer */} + {footer !== null && } {footer !== undefined ? footer :