Files
cloudpods/pkg/monitor/models/migration_alert.go
2022-06-23 00:25:03 +08:00

328 lines
10 KiB
Go

// Copyright 2019 Yunion
//
// 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 models
import (
"context"
"database/sql"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
)
var (
migrationAlertMan *SMigrationAlertManager
)
func init() {
migrationAlertMan = GetMigrationAlertManager()
}
func GetMigrationAlertManager() *SMigrationAlertManager {
if migrationAlertMan != nil {
return migrationAlertMan
}
migrationAlertMan = &SMigrationAlertManager{
SAlertManager: *NewAlertManager(SMigrationAlert{}, "migrationalert", "migrationalerts"),
}
migrationAlertMan.SetVirtualObject(migrationAlertMan)
return migrationAlertMan
}
type SMigrationAlertManager struct {
SAlertManager
}
type SMigrationAlert struct {
SAlert
MetricType string `create:"admin_required" list:"admin" get:"admin"`
MigrateNotes jsonutils.JSONObject `nullable:"true" list:"admin" get:"admin" update:"admin" create:"admin_optional"`
}
type MigrateNoteGuest struct {
Id string `json:"id"`
Name string `json:"name"`
HostId string `json:"host_id"`
Host string `json:"host"`
VCPUCount int `json:"vcpu_count"`
VMemSize int `json:"vmem_size"`
Score float64 `json:"score"`
}
type MigrateNoteTarget struct {
Id string `json:"id"`
Name string `json:"name"`
Score float64 `json:"score"`
}
type MigrateNote struct {
Guest *MigrateNoteGuest `json:"guest"`
Target *MigrateNoteTarget `json:"target_host"`
Error string `json:"error"`
}
func (m *SMigrationAlertManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query monitor.MigrationAlertListInput) (*sqlchemy.SQuery, error) {
q, err := m.SAlertManager.ListItemFilter(ctx, q, userCred, query.AlertListInput)
if err != nil {
return nil, err
}
if len(query.MetricType) > 0 {
q = q.Equals("metric_type", query.MetricType)
}
q = q.Equals("used_by", AlertNotificationUsedByMigrationAlert)
return q, nil
}
func (m *SMigrationAlertManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *monitor.MigrationAlertCreateInput) (*monitor.MigrationAlertCreateInput, error) {
if input.Period == "" {
input.Period = "5m"
}
if _, err := time.ParseDuration(input.Period); err != nil {
return nil, httperrors.NewInputParameterError("Invalid period format: %s", input.Period)
}
if err := monitor.IsValidMigrationAlertMetricType(input.MetricType); err != nil {
return nil, httperrors.NewInputParameterError("Invalid metric_type %v", err)
}
if input.MigrationSettings == nil {
input.MigrationSettings = &monitor.MigrationAlertSettings{
Source: new(monitor.MigrationAlertSettingsSource),
Target: new(monitor.MigrationAlertSettingsTarget),
}
}
if err := m.ValidateMigrationSettings(input.MigrationSettings); err != nil {
return nil, errors.Wrap(err, "validate migration settings")
}
aInput := *(input.ToAlertCreateInput())
aInput, err := AlertManager.ValidateCreateData(ctx, userCred, nil, query, aInput)
if err != nil {
return input, errors.Wrap(err, "AlertManager.ValidateCreateData")
}
input.AlertCreateInput = aInput
return input, nil
}
func (m *SMigrationAlertManager) ValidateMigrationSettings(s *monitor.MigrationAlertSettings) error {
if s.Source != nil {
if err := m.ValidateMigrationSettingsSource(s.Source); err != nil {
return errors.Wrap(err, "validate source")
}
}
if s.Target != nil {
if err := m.ValidateMigrationSettingsTarget(s.Target); err != nil {
return errors.Wrap(err, "validate target")
}
}
return nil
}
func (m *SMigrationAlertManager) GetResourceByIdOrName(rType string, id string) (jsonutils.JSONObject, error) {
ok, objs := MonitorResourceManager.GetResourceObjByResType(rType)
if !ok {
return nil, errors.Errorf("Get by %q", rType)
}
for _, obj := range objs {
name, _ := obj.GetString("name")
if name == id {
return obj, nil
}
objId, _ := obj.GetString("id")
if objId == id {
return obj, nil
}
}
return nil, errors.Errorf("Not found resource %q by %q", rType, id)
}
func (m *SMigrationAlertManager) GetHostByIdOrName(id string) (jsonutils.JSONObject, error) {
return m.GetResourceByIdOrName(monitor.METRIC_RES_TYPE_HOST, id)
}
func (m *SMigrationAlertManager) GetGuestByIdOrName(id string) (jsonutils.JSONObject, error) {
return m.GetResourceByIdOrName(monitor.METRIC_RES_TYPE_GUEST, id)
}
func (m *SMigrationAlertManager) validateResource(vf func(idOrName string) (jsonutils.JSONObject, error), ids []string) ([]string, error) {
nIds := make([]string, len(ids))
for idx, idName := range ids {
obj, err := vf(idName)
if err != nil {
return nil, errors.Wrapf(err, "find by %s", idName)
}
id, err := obj.GetString("id")
if err != nil {
return nil, errors.Wrap(err, "get id")
}
nIds[idx] = id
}
return nIds, nil
}
func (m *SMigrationAlertManager) ValidateMigrationSettingsSource(input *monitor.MigrationAlertSettingsSource) error {
if len(input.HostIds) != 0 {
hIds, err := m.validateResource(m.GetHostByIdOrName, input.HostIds)
if err != nil {
return errors.Wrap(err, "validate host")
}
input.HostIds = hIds
}
if len(input.GuestIds) != 0 {
gIds, err := m.validateResource(m.GetGuestByIdOrName, input.GuestIds)
if err != nil {
return errors.Wrap(err, "validate guest")
}
input.GuestIds = gIds
}
return nil
}
func (m *SMigrationAlertManager) ValidateMigrationSettingsTarget(input *monitor.MigrationAlertSettingsTarget) error {
if len(input.HostIds) != 0 {
hIds, err := m.validateResource(m.GetHostByIdOrName, input.HostIds)
if err != nil {
return errors.Wrap(err, "validate host")
}
input.HostIds = hIds
}
return nil
}
func (alert *SMigrationAlert) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
out := new(monitor.MigrationAlertCreateInput)
if err := data.Unmarshal(out); err != nil {
return errors.Wrap(err, "Unmarshal to MigrationAlertCreateInput")
}
fs := out.GetMetricDriver().GetQueryFields()
alert.ResType = string(fs.ResourceType)
alert.UsedBy = AlertNotificationUsedByMigrationAlert
if err := alert.SAlert.CustomizeCreate(ctx, userCred, ownerId, query, data); err != nil {
return errors.Wrap(err, "SAlert.CustomizeCreate")
}
return alert.CreateNotification(ctx, userCred)
}
func (m *SMigrationAlertManager) FetchAllMigrationAlerts() ([]SMigrationAlert, error) {
objs := make([]SMigrationAlert, 0)
q := m.Query()
q = q.IsTrue("enabled")
err := db.FetchModelObjects(m, q, &objs)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrap(err, "db.FetchModelObjects")
}
return objs, nil
}
func (m *SMigrationAlertManager) GetInMigrationAlerts() ([]*SMigrationAlert, error) {
alerts, err := m.FetchAllMigrationAlerts()
if err != nil {
return nil, errors.Wrap(err, "FetchAllMigrationAlerts")
}
objs := make([]*SMigrationAlert, 0)
for _, a := range alerts {
if ok, _, _ := a.IsInMigrationProcess(); ok {
tmp := a
objs = append(objs, &tmp)
}
}
return objs, nil
}
func (alert *SMigrationAlert) GetMigrateNotes() (map[string]MigrateNote, error) {
if alert.MigrateNotes == nil {
return make(map[string]MigrateNote, 0), nil
}
objs := make(map[string]MigrateNote, 0)
if err := alert.MigrateNotes.Unmarshal(&objs); err != nil {
return nil, errors.Wrap(err, "Unmarshal")
}
return objs, nil
}
func (alert *SMigrationAlert) SetMigrateNote(ctx context.Context, ns *MigrateNote, isDelete bool) error {
_, err := db.UpdateWithLock(ctx, alert, func() error {
curNotes, err := alert.GetMigrateNotes()
if err != nil {
return errors.Wrap(err, "GetMigrateNotes")
}
if isDelete {
delete(curNotes, ns.Guest.Id)
} else {
curNotes[ns.Guest.Id] = *ns
}
alert.MigrateNotes = jsonutils.Marshal(curNotes)
return nil
})
return err
}
func (alert *SMigrationAlert) IsInMigrationProcess() (bool, map[string]MigrateNote, error) {
notes, err := alert.GetMigrateNotes()
if err != nil {
return false, nil, errors.Wrap(err, "GetMigrateNotes")
}
if len(notes) == 0 {
return false, nil, nil
}
return true, nil, nil
}
func (alert *SMigrationAlert) GetMigrationSettings() (*monitor.MigrationAlertSettings, error) {
if alert.CustomizeConfig == nil {
return nil, errors.Errorf("CustomizeConfig is nil")
}
out := new(monitor.MigrationAlertSettings)
if err := alert.CustomizeConfig.Unmarshal(out); err != nil {
return nil, err
}
return out, nil
}
func (alert *SMigrationAlert) CreateNotification(ctx context.Context, userCred mcclient.TokenCredential) error {
if alert.Id == "" {
alert.Id = db.DefaultUUIDGenerator()
}
noti, err := NotificationManager.CreateAutoMigrationNotification(ctx, userCred, alert)
if err != nil {
return errors.Wrap(err, "CreateAutoMigrationNotification")
}
if _, err := alert.AttachNotification(ctx, userCred, noti, monitor.AlertNotificationStateUnknown, ""); err != nil {
return errors.Wrap(err, "alert.AttachNotification")
}
return nil
}
func (alert *SMigrationAlert) GetMetricType() monitor.MigrationAlertMetricType {
return monitor.MigrationAlertMetricType(alert.MetricType)
}
func (alert *SMigrationAlert) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
if err := alert.deleteNotifications(ctx, userCred, query, data); err != nil {
return errors.Wrap(err, "delete related notification")
}
return alert.SAlert.CustomizeDelete(ctx, userCred, query, data)
}