Files
grafana/pkg/services/ngalert/migration/service.go
T
Matthew Jacobson 5a80962de9 Alerting: Add clean_upgrade config and deprecate force_migration (#78324)
* Alerting: Add clean_upgrade config and deprecate force_migration

Upgrading to UA and rolling back will no longer delete any data by default. 
Instead, each set of tables will remain unchanged when switching between 
legacy and UA. As such, the force_migration config has been deprecated 
and no extra configuration is required to roll back to legacy anymore.

If clean_upgrade is set to true when upgrading from legacy alerting to Unified
Alerting, grafana will first delete all existing Unified Alerting resources,
thus re-upgrading all organizations from scratch. If false or unset,
organizations that have previously upgraded will not lose their existing Unified
 Alerting data when switching between legacy and Unified Alerting.

 Similar to force_migration, it should be kept false when not needed as it may
 cause unintended data-loss if left enabled.

---------

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
2023-11-30 11:01:11 -05:00

190 lines
6.0 KiB
Go

package migration
import (
"context"
"fmt"
"time"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/serverlock"
migrationStore "github.com/grafana/grafana/pkg/services/ngalert/migration/store"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/setting"
)
// actionName is the unique row-level lock name for serverlock.ServerLockService.
const actionName = "alerting migration"
type UpgradeService interface {
Run(ctx context.Context) error
}
type migrationService struct {
lock *serverlock.ServerLockService
cfg *setting.Cfg
log log.Logger
store db.DB
migrationStore migrationStore.Store
encryptionService secrets.Service
}
func ProvideService(
lock *serverlock.ServerLockService,
cfg *setting.Cfg,
store db.DB,
migrationStore migrationStore.Store,
encryptionService secrets.Service,
) (UpgradeService, error) {
return &migrationService{
lock: lock,
log: log.New("ngalert.migration"),
cfg: cfg,
store: store,
migrationStore: migrationStore,
encryptionService: encryptionService,
}, nil
}
// Run starts the migration to transition between legacy alerting and unified alerting based on the current and desired
// alerting type as determined by the kvstore and configuration, respectively.
func (ms *migrationService) Run(ctx context.Context) error {
var errMigration error
errLock := ms.lock.LockExecuteAndRelease(ctx, actionName, time.Minute*10, func(ctx context.Context) {
ms.log.Info("Starting")
errMigration = ms.store.InTransaction(ctx, func(ctx context.Context) error {
currentType, err := ms.migrationStore.GetCurrentAlertingType(ctx)
if err != nil {
return fmt.Errorf("getting migration status: %w", err)
}
return ms.applyTransition(ctx, newTransition(currentType, ms.cfg))
})
})
if errLock != nil {
ms.log.Warn("Server lock for alerting migration already exists")
return nil
}
if errMigration != nil {
return fmt.Errorf("migration failed: %w", errMigration)
}
return nil
}
// newTransition creates a transition based on the current alerting type and the current configuration.
func newTransition(currentType migrationStore.AlertingType, cfg *setting.Cfg) transition {
desiredType := migrationStore.Legacy
if cfg.UnifiedAlerting.IsEnabled() {
desiredType = migrationStore.UnifiedAlerting
}
return transition{
CurrentType: currentType,
DesiredType: desiredType,
CleanOnDowngrade: cfg.ForceMigration,
CleanOnUpgrade: cfg.UnifiedAlerting.Upgrade.CleanUpgrade,
}
}
// transition represents a migration from one alerting type to another.
type transition struct {
CurrentType migrationStore.AlertingType
DesiredType migrationStore.AlertingType
CleanOnDowngrade bool
CleanOnUpgrade bool
}
// isNoChange returns true if the migration is a no-op.
func (t transition) isNoChange() bool {
return t.CurrentType == t.DesiredType
}
// isUpgrading returns true if the migration is an upgrade from legacy alerting to unified alerting.
func (t transition) isUpgrading() bool {
return t.CurrentType == migrationStore.Legacy && t.DesiredType == migrationStore.UnifiedAlerting
}
// isDowngrading returns true if the migration is a downgrade from unified alerting to legacy alerting.
func (t transition) isDowngrading() bool {
return t.CurrentType == migrationStore.UnifiedAlerting && t.DesiredType == migrationStore.Legacy
}
// shouldClean returns true if the migration should delete all unified alerting data.
func (t transition) shouldClean() bool {
return t.isDowngrading() && t.CleanOnDowngrade || t.isUpgrading() && t.CleanOnUpgrade
}
// applyTransition applies the transition to the database.
// If the transition is a no-op, nothing will be done.
// If the transition is a downgrade and CleanOnDowngrade is false, nothing will be done.
// If the transition is a downgrade and CleanOnDowngrade is true, all unified alerting data will be deleted.
// If the transition is an upgrade and CleanOnUpgrade is false, all orgs will be migrated.
// If the transition is an upgrade and CleanOnUpgrade is true, all unified alerting data will be deleted and then all orgs will be migrated.
func (ms *migrationService) applyTransition(ctx context.Context, t transition) error {
l := ms.log.New(
"CurrentType", t.CurrentType,
"DesiredType", t.DesiredType,
"CleanOnDowngrade", t.CleanOnDowngrade,
"CleanOnUpgrade", t.CleanOnUpgrade,
)
if t.isNoChange() {
l.Info("Migration already complete")
return nil
}
if t.shouldClean() {
l.Info("Cleaning up unified alerting data")
if err := ms.migrationStore.RevertAllOrgs(ctx); err != nil {
return fmt.Errorf("cleaning up unified alerting data: %w", err)
}
l.Info("Unified alerting data deleted")
}
if t.isUpgrading() {
if err := ms.migrateAllOrgs(ctx); err != nil {
return fmt.Errorf("executing migration: %w", err)
}
}
if err := ms.migrationStore.SetCurrentAlertingType(ctx, t.DesiredType); err != nil {
return fmt.Errorf("setting migration status: %w", err)
}
l.Info("Completed legacy migration")
return nil
}
// migrateAllOrgs executes the migration for all orgs.
func (ms *migrationService) migrateAllOrgs(ctx context.Context) error {
orgs, err := ms.migrationStore.GetAllOrgs(ctx)
if err != nil {
return fmt.Errorf("get orgs: %w", err)
}
for _, o := range orgs {
om := ms.newOrgMigration(o.ID)
migrated, err := ms.migrationStore.IsMigrated(ctx, o.ID)
if err != nil {
return fmt.Errorf("getting migration status for org %d: %w", o.ID, err)
}
if migrated {
om.log.Info("Org already migrated, skipping")
continue
}
if err := om.migrateOrg(ctx); err != nil {
return fmt.Errorf("migrate org %d: %w", o.ID, err)
}
err = om.migrationStore.SetOrgMigrationState(ctx, o.ID, om.state)
if err != nil {
return fmt.Errorf("set org migration state: %w", err)
}
err = ms.migrationStore.SetMigrated(ctx, o.ID, true)
if err != nil {
return fmt.Errorf("setting migration status: %w", err)
}
}
return nil
}