mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 13:39:37 +08:00
213 lines
5.6 KiB
Go
213 lines
5.6 KiB
Go
/*
|
|
Copyright 2022 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package local
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/gravitational/teleport/api/client/proto"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
// StatusService manages cluster status info.
|
|
type StatusService struct {
|
|
backend.Backend
|
|
log logrus.FieldLogger
|
|
}
|
|
|
|
func NewStatusService(bk backend.Backend) *StatusService {
|
|
return &StatusService{
|
|
Backend: bk,
|
|
log: logrus.WithField(trace.Component, "status"),
|
|
}
|
|
}
|
|
|
|
func (s *StatusService) GetClusterAlerts(ctx context.Context, query types.GetClusterAlertsRequest) ([]types.ClusterAlert, error) {
|
|
var alerts []types.ClusterAlert
|
|
if query.AlertID != "" {
|
|
alert, err := s.getClusterAlert(ctx, query.AlertID)
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
// return an empty list
|
|
return nil, nil
|
|
}
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
alerts = []types.ClusterAlert{alert}
|
|
} else {
|
|
var err error
|
|
alerts, err = s.getAllClusterAlerts(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
filtered := alerts[:0]
|
|
for _, alert := range alerts {
|
|
if err := alert.CheckAndSetDefaults(); err != nil {
|
|
s.log.Warnf("Skipping invalid cluster alert: %v", err)
|
|
}
|
|
|
|
if !query.Match(alert) {
|
|
continue
|
|
}
|
|
|
|
filtered = append(filtered, alert)
|
|
}
|
|
|
|
return filtered, nil
|
|
}
|
|
|
|
func (s *StatusService) getAllClusterAlerts(ctx context.Context) ([]types.ClusterAlert, error) {
|
|
startKey := backend.Key(clusterAlertPrefix, "")
|
|
result, err := s.Backend.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
alerts := make([]types.ClusterAlert, 0, len(result.Items))
|
|
|
|
for _, item := range result.Items {
|
|
var alert types.ClusterAlert
|
|
if err := utils.FastUnmarshal(item.Value, &alert); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
alerts = append(alerts, alert)
|
|
}
|
|
|
|
return alerts, nil
|
|
}
|
|
|
|
func (s *StatusService) getClusterAlert(ctx context.Context, alertID string) (types.ClusterAlert, error) {
|
|
key := backend.Key(clusterAlertPrefix, alertID)
|
|
item, err := s.Backend.Get(ctx, key)
|
|
if err != nil {
|
|
return types.ClusterAlert{}, trace.Wrap(err)
|
|
}
|
|
|
|
var alert types.ClusterAlert
|
|
if err := utils.FastUnmarshal(item.Value, &alert); err != nil {
|
|
return types.ClusterAlert{}, trace.Wrap(err)
|
|
}
|
|
|
|
return alert, nil
|
|
}
|
|
|
|
func (s *StatusService) UpsertClusterAlert(ctx context.Context, alert types.ClusterAlert) error {
|
|
if err := alert.CheckAndSetDefaults(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if alert.Spec.Created.IsZero() {
|
|
alert.Spec.Created = s.Clock().Now().UTC()
|
|
}
|
|
|
|
if alert.Metadata.Expiry().IsZero() {
|
|
alert.Metadata.SetExpiry(alert.Spec.Created.Add(time.Hour * 24))
|
|
}
|
|
|
|
val, err := utils.FastMarshal(&alert)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
_, err = s.Backend.Put(ctx, backend.Item{
|
|
Key: backend.Key(clusterAlertPrefix, alert.Metadata.Name),
|
|
Value: val,
|
|
Expires: alert.Metadata.Expiry(),
|
|
})
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
func (s *StatusService) DeleteClusterAlert(ctx context.Context, alertID string) error {
|
|
err := s.Backend.Delete(ctx, backend.Key(clusterAlertPrefix, alertID))
|
|
if trace.IsNotFound(err) {
|
|
return trace.NotFound("cluster alert %q not found", alertID)
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// CreateAlertAck marks a cluster alert as acknowledged.
|
|
func (s *StatusService) CreateAlertAck(ctx context.Context, ack types.AlertAcknowledgement) error {
|
|
if err := ack.Check(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
val, err := utils.FastMarshal(&ack)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
_, err = s.Backend.Create(ctx, backend.Item{
|
|
Key: backend.Key(alertAckPrefix, ack.AlertID),
|
|
Value: val,
|
|
Expires: ack.Expires,
|
|
})
|
|
if trace.IsAlreadyExists(err) {
|
|
return trace.AlreadyExists("alert %q has already been acknowledged", ack.AlertID)
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// GetAlertAcks gets active alert ackowledgements.
|
|
func (s *StatusService) GetAlertAcks(ctx context.Context) ([]types.AlertAcknowledgement, error) {
|
|
startKey := backend.Key(alertAckPrefix, "")
|
|
result, err := s.Backend.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
acks := make([]types.AlertAcknowledgement, 0, len(result.Items))
|
|
|
|
for _, item := range result.Items {
|
|
var ack types.AlertAcknowledgement
|
|
if err := utils.FastUnmarshal(item.Value, &ack); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
acks = append(acks, ack)
|
|
}
|
|
|
|
return acks, nil
|
|
}
|
|
|
|
// ClearAlertAcks clears alert acknowledgments.
|
|
func (s *StatusService) ClearAlertAcks(ctx context.Context, req proto.ClearAlertAcksRequest) error {
|
|
if req.AlertID == "" {
|
|
return trace.BadParameter("missing alert id for ack clear")
|
|
}
|
|
if req.AlertID == types.Wildcard {
|
|
startKey := backend.Key(alertAckPrefix, "")
|
|
return trace.Wrap(s.Backend.DeleteRange(ctx, startKey, backend.RangeEnd(startKey)))
|
|
}
|
|
|
|
err := s.Backend.Delete(ctx, backend.Key(alertAckPrefix, req.AlertID))
|
|
if trace.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
const clusterAlertPrefix = "cluster-alerts"
|
|
|
|
const alertAckPrefix = "alert-ack"
|