diff --git a/docs/sources/alerting/set-up/migrating-alerts/_index.md b/docs/sources/alerting/set-up/migrating-alerts/_index.md index b1295fc87b5..55a5568cbee 100644 --- a/docs/sources/alerting/set-up/migrating-alerts/_index.md +++ b/docs/sources/alerting/set-up/migrating-alerts/_index.md @@ -59,7 +59,7 @@ When upgrading with either method, your legacy dashboard alerts and notification In **Alerts & IRM**, the **Alerting** section provides a preview of Grafana Alerting where you can review and modify your upgraded alerts before finalizing the upgrade. -In the **Alerting (legacy) -> Alerting upgrade** section, you can initiate the process to automatically upgrade your existing alert rules and notification channels, and view a summary of the upgrade to Grafana Alerting. +In the **Alerting (legacy) -> Alerting upgrade** section, you can upgrade your existing alert rules and notification channels, and view a summary of the upgrade to Grafana Alerting. Finalize your upgrade by restarting Grafana with the `[unified_alerting]` section enabled in your configuration. @@ -70,8 +70,8 @@ Alerts generated by the new alerting system are visible in the **Alerting** sect ### To upgrade with preview, complete the following steps. 1. **Preview the Upgrade**: - - **Initiate the process**: Access the upgrade functionality within Grafana by visiting the **Alerting upgrade** page in the **Alerting (legacy)** section of the navigation panel. Click on "Preview upgrade" to initiate the process. This will automatically upgrade your existing alert rules and notification channels to the new Grafana Alerting system. - - **Review the summary table:** Review the detailed table outlining how your existing alert rules and notification channels were mapped to resources in the new Grafana Alerting system. + - **Initiate the process**: Access the upgrade functionality within Grafana by visiting the **Alerting upgrade** page in the **Alerting (legacy)** section of the navigation panel. From this page you can upgrade your existing alert rules and notification channels to the new Grafana Alerting system. + - **Review the summary table:** Review the detailed table outlining how your existing alert rules and notification channels were upgraded to resources in the new Grafana Alerting system. 2. **Investigate and Resolve Errors**: - **Identify errors**: Carefully examine the previewed upgrade: - Any alert rules or notification channels that couldn't be automatically upgraded will be highlighted with error indicators. diff --git a/pkg/services/ngalert/api/api_upgrade.go b/pkg/services/ngalert/api/api_upgrade.go index c7f4c4120ac..aaa232aef4e 100644 --- a/pkg/services/ngalert/api/api_upgrade.go +++ b/pkg/services/ngalert/api/api_upgrade.go @@ -35,9 +35,9 @@ func (srv *UpgradeSrv) RoutePostUpgradeOrg(c *contextmodel.ReqContext) response. summary, err := srv.upgradeService.MigrateOrg(c.Req.Context(), c.OrgID, c.QueryBool("skipExisting")) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, summary) } @@ -46,9 +46,9 @@ func (srv *UpgradeSrv) RouteGetOrgUpgrade(c *contextmodel.ReqContext) response.R state, err := srv.upgradeService.GetOrgMigrationState(c.Req.Context(), c.OrgID) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, state) } @@ -57,9 +57,9 @@ func (srv *UpgradeSrv) RouteDeleteOrgUpgrade(c *contextmodel.ReqContext) respons err := srv.upgradeService.RevertOrg(c.Req.Context(), c.OrgID) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, util.DynMap{"message": "Grafana Alerting resources deleted for this organization."}) } @@ -78,9 +78,9 @@ func (srv *UpgradeSrv) RoutePostUpgradeAlert(c *contextmodel.ReqContext, dashboa summary, err := srv.upgradeService.MigrateAlert(c.Req.Context(), c.OrgID, dashboardId, panelId) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, summary) } @@ -94,9 +94,9 @@ func (srv *UpgradeSrv) RoutePostUpgradeDashboard(c *contextmodel.ReqContext, das summary, err := srv.upgradeService.MigrateDashboardAlerts(c.Req.Context(), c.OrgID, dashboardId, c.QueryBool("skipExisting")) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, summary) } @@ -105,9 +105,9 @@ func (srv *UpgradeSrv) RoutePostUpgradeAllDashboards(c *contextmodel.ReqContext) summary, err := srv.upgradeService.MigrateAllDashboardAlerts(c.Req.Context(), c.OrgID, c.QueryBool("skipExisting")) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, summary) } @@ -121,9 +121,9 @@ func (srv *UpgradeSrv) RoutePostUpgradeChannel(c *contextmodel.ReqContext, chann summary, err := srv.upgradeService.MigrateChannel(c.Req.Context(), c.OrgID, channelId) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, summary) } @@ -132,9 +132,9 @@ func (srv *UpgradeSrv) RoutePostUpgradeAllChannels(c *contextmodel.ReqContext) r summary, err := srv.upgradeService.MigrateAllChannels(c.Req.Context(), c.OrgID, c.QueryBool("skipExisting")) if err != nil { if errors.Is(err, migration.ErrUpgradeInProgress) { - return response.Error(http.StatusConflict, "Upgrade already in progress", err) + return ErrResp(http.StatusConflict, err, "Upgrade already in progress") } - return response.Error(http.StatusInternalServerError, "Server error", err) + return ErrResp(http.StatusInternalServerError, err, "Server error") } return response.JSON(http.StatusOK, summary) } diff --git a/pkg/services/ngalert/migration/service.go b/pkg/services/ngalert/migration/service.go index 86ebd43b096..04db5cbb42b 100644 --- a/pkg/services/ngalert/migration/service.go +++ b/pkg/services/ngalert/migration/service.go @@ -73,13 +73,23 @@ func ProvideService( type operation func(ctx context.Context) (*definitions.OrgMigrationSummary, error) -// verifyTry verifies that the org has been migrated, and then attempts to execute the operation. If another operation -// is already in progress, ErrUpgradeInProgress will be returned. -func (ms *migrationService) verifyTry(ctx context.Context, orgID int64, op operation) (definitions.OrgMigrationSummary, error) { - if err := ms.verifyMigrated(ctx, orgID); err != nil { - return definitions.OrgMigrationSummary{}, err +// tryAndSet attempts to execute the operation and then sets the migrated status to true. +// If another operation is already in progress, ErrUpgradeInProgress will be returned +func (ms *migrationService) tryAndSet(ctx context.Context, orgID int64, op operation) (definitions.OrgMigrationSummary, error) { + opAndSet := func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { + s, err := op(ctx) + if err != nil { + return nil, err + } + + err = ms.migrationStore.SetMigrated(ctx, orgID, true) + if err != nil { + return nil, fmt.Errorf("setting migration status: %w", err) + } + + return s, nil } - return ms.try(ctx, op) + return ms.try(ctx, opAndSet) } // try attempts to execute the operation. If another operation is already in progress, ErrUpgradeInProgress will be returned. @@ -110,7 +120,7 @@ func (ms *migrationService) try(ctx context.Context, op operation) (definitions. // MigrateChannel migrates a single legacy notification channel to a unified alerting contact point. func (ms *migrationService) MigrateChannel(ctx context.Context, orgID int64, channelID int64) (definitions.OrgMigrationSummary, error) { - return ms.verifyTry(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { + return ms.tryAndSet(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { summary := definitions.OrgMigrationSummary{} om := ms.newOrgMigration(orgID) oldState, err := om.migrationStore.GetOrgMigrationState(ctx, orgID) @@ -161,7 +171,7 @@ func (ms *migrationService) MigrateChannel(ctx context.Context, orgID int64, cha // MigrateAllChannels migrates all legacy notification channel to unified alerting contact points. func (ms *migrationService) MigrateAllChannels(ctx context.Context, orgID int64, skipExisting bool) (definitions.OrgMigrationSummary, error) { - return ms.verifyTry(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { + return ms.tryAndSet(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { summary := definitions.OrgMigrationSummary{} om := ms.newOrgMigration(orgID) pairs, err := om.migrateOrgChannels(ctx) @@ -181,7 +191,7 @@ func (ms *migrationService) MigrateAllChannels(ctx context.Context, orgID int64, // MigrateAlert migrates a single dashboard alert from legacy alerting to unified alerting. func (ms *migrationService) MigrateAlert(ctx context.Context, orgID int64, dashboardID int64, panelID int64) (definitions.OrgMigrationSummary, error) { - return ms.verifyTry(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { + return ms.tryAndSet(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { summary := definitions.OrgMigrationSummary{} om := ms.newOrgMigration(orgID) oldState, err := om.migrationStore.GetOrgMigrationState(ctx, orgID) @@ -238,7 +248,7 @@ func (ms *migrationService) MigrateAlert(ctx context.Context, orgID int64, dashb // MigrateDashboardAlerts migrates all legacy dashboard alerts from a single dashboard to unified alerting. func (ms *migrationService) MigrateDashboardAlerts(ctx context.Context, orgID int64, dashboardID int64, skipExisting bool) (definitions.OrgMigrationSummary, error) { - return ms.verifyTry(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { + return ms.tryAndSet(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { summary := definitions.OrgMigrationSummary{} om := ms.newOrgMigration(orgID) alerts, err := ms.migrationStore.GetDashboardAlerts(ctx, orgID, dashboardID) @@ -259,7 +269,7 @@ func (ms *migrationService) MigrateDashboardAlerts(ctx context.Context, orgID in // MigrateAllDashboardAlerts migrates all legacy alerts to unified alerting contact points. func (ms *migrationService) MigrateAllDashboardAlerts(ctx context.Context, orgID int64, skipExisting bool) (definitions.OrgMigrationSummary, error) { - return ms.verifyTry(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { + return ms.tryAndSet(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { summary := definitions.OrgMigrationSummary{} om := ms.newOrgMigration(orgID) dashboardUpgrades, err := om.migrateOrgAlerts(ctx) @@ -279,7 +289,7 @@ func (ms *migrationService) MigrateAllDashboardAlerts(ctx context.Context, orgID // MigrateOrg executes the migration for a single org. func (ms *migrationService) MigrateOrg(ctx context.Context, orgID int64, skipExisting bool) (definitions.OrgMigrationSummary, error) { - return ms.try(ctx, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { + return ms.tryAndSet(ctx, orgID, func(ctx context.Context) (*definitions.OrgMigrationSummary, error) { summary := definitions.OrgMigrationSummary{} ms.log.Info("Starting legacy migration for org", "orgId", orgID, "skipExisting", skipExisting) om := ms.newOrgMigration(orgID) @@ -293,11 +303,6 @@ func (ms *migrationService) MigrateOrg(ctx context.Context, orgID int64, skipExi return nil, err } - err = ms.migrationStore.SetMigrated(ctx, orgID, true) - if err != nil { - return nil, fmt.Errorf("setting migration status: %w", err) - } - summary.Add(s) return &summary, nil }) @@ -306,9 +311,6 @@ func (ms *migrationService) MigrateOrg(ctx context.Context, orgID int64, skipExi // GetOrgMigrationState returns the current migration state for an org. This is a potentially expensive operation as it // requires re-hydrating the entire migration state from the database against all current alerting resources. func (ms *migrationService) GetOrgMigrationState(ctx context.Context, orgID int64) (*definitions.OrgMigrationState, error) { - if migrated, err := ms.migrationStore.IsMigrated(ctx, orgID); err != nil || !migrated { - return &definitions.OrgMigrationState{OrgID: orgID}, err - } dState, err := ms.migrationStore.GetOrgMigrationState(ctx, orgID) if err != nil { return nil, err @@ -525,18 +527,6 @@ func (ms *migrationService) RevertAllOrgs(ctx context.Context) error { return err } -// verifyMigrated returns an error if the org has not been migrated. -func (ms *migrationService) verifyMigrated(ctx context.Context, orgID int64) error { - migrated, err := ms.migrationStore.IsMigrated(ctx, orgID) - if err != nil { - return fmt.Errorf("check if migrated: %w", err) - } - if !migrated { - return fmt.Errorf("not migrated") - } - return nil -} - // fromDashboardUpgrades converts DashboardUpgrades to their api representation. This requires rehydrating information // from the database for the current state of dashboards, alerts, and rules. func (ms *migrationService) fromDashboardUpgrades(ctx context.Context, orgID int64, migratedDashboards map[int64]*migrationStore.DashboardUpgrade, amConfig *migmodels.Alertmanager) ([]*definitions.DashboardUpgrade, error) { @@ -616,7 +606,7 @@ func (ms *migrationService) fromDashboardUpgrades(ctx context.Context, orgID int } else { // We could potentially set an error here, but it's not really an error. It just means that the // user deleted the migrated rule after the migration. This could just as easily be intentional. - ms.log.Info("Could not find rule for migrated alert", "alertId", a.ID, "ruleUid", p.NewRuleUID) + ms.log.Debug("Could not find rule for migrated alert", "alertId", a.ID, "ruleUid", p.NewRuleUID) } } } diff --git a/public/app/features/alerting/Upgrade.tsx b/public/app/features/alerting/Upgrade.tsx index 4e116224000..653674ec410 100644 --- a/public/app/features/alerting/Upgrade.tsx +++ b/public/app/features/alerting/Upgrade.tsx @@ -8,14 +8,12 @@ import { useLocation } from 'react-router-dom'; import { useLocalStorage } from 'react-use'; import { GrafanaTheme2, UrlQueryMap } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; import { Stack } from '@grafana/experimental'; import { locationService } from '@grafana/runtime'; import { Alert, Badge, Button, - CallToActionCard, ConfirmModal, FilterInput, HorizontalGroup, @@ -80,7 +78,6 @@ export const UpgradePage = () => { const showError = isFetchError; const showLoading = isLoading; - const showStart = !isLoading && !isFetchError && !hasData; const showData = !isLoading && !isFetchError && hasData; return ( @@ -92,7 +89,6 @@ export const UpgradePage = () => { )} {showLoading && } - {showStart && } {showData && ( <> @@ -188,12 +184,12 @@ const CancelUpgradeButton = () => { icon={'trash-alt'} className={''} > - {'Cancel upgrade'} + {'Reset upgrade'} {showConfirmStartOver && ( All new Grafana Alerting resources will be deleted. @@ -205,7 +201,7 @@ const CancelUpgradeButton = () => { No legacy alerts or notification channels will be affected. } - confirmText="Cancel upgrade" + confirmText="Reset upgrade" onConfirm={cancelUpgrade} dismissText={'Keep reviewing'} onDismiss={() => setShowConfirmStartOver(false)} @@ -240,249 +236,6 @@ function getActiveTabFromUrl(queryParams: UrlQueryMap): QueryParamValues { }; } -const CTAElement = () => { - const styles = useStyles2(getContentBoxStyles); - const { useUpgradeOrgMutation } = upgradeApi; - const [startUpgrade, { isLoading: isUpgradeLoading }] = useUpgradeOrgMutation({ - fixedCacheKey: 'upgrade-org-loading', - }); - const [, { isLoading: isCancelLoading }] = upgradeApi.useCancelOrgUpgradeMutation({ - fixedCacheKey: 'cancel-org-upgrade-loading', - }); - const isLoading = isUpgradeLoading || isCancelLoading; - - const upgradeAlerting = async () => { - await startUpgrade({ skipExisting: false }); - }; - - if (isLoading) { - return ; - } - - const footer = ( - <> - -

- Note:{' '} - {'Previewing the upgrade process will not affect your existing legacy alerts and can be stopped at any time.'} -

-
- - ); - - const cta = ( -
- - - - - -
- ); - - return ( -
- -

How it works

- -
-

Automatic Upgrade

-
-

- The upgrade process seamlessly transfers your existing legacy alert rules and notification channels to - the new Grafana Alerting system. This means your alerting configurations are preserved during the - transition. -

-
-

Preview and Modification

-
-

- Alert Rules, Contact Points, and Notification Policies generated during the upgrade are available for - your review and potential adjustments. However, please note that they won't actively trigger alerts - or send notifications at this stage. -

-
-

Limitations on Real-Time Updates

-
-

- Any changes made to your configurations after initiating the upgrade won't be immediately reflected - in the summary table. You have the flexibility to re-upgrade specific resources like dashboards, alert - rules, and notification channels at any time. -

-
-

Cancellation and Restart

-
-

- If necessary, you can cancel and restart the upgrade process. However, it's important to be aware - that canceling the upgrade will result in the removal of all Grafana Alerting resources created during - the process, including any manual modifications. -

-
-

Completing the Upgrade

-
-

- To enable Grafana Alerting, you'll need to modify the Grafana configuration and restart. Until this - step is completed, Grafana Alerting will remain inactive. -

-
-
-
-
- -

Get started

- -
-

Step 1: Preview the Upgrade

-
-

- Start the upgrade process by clicking on "Preview upgrade." This action will display a summary - table showing how your existing alert rules and notification channels will be mapped to resources in the - new Grafana Alerting system. -

-
-

Step 2: Investigate and Resolve Errors

-
-

- Review the previewed upgrade carefully. Alert rules or notification channels that couldn't be - automatically upgraded will be marked as errors. You have two options to address these errors: -

-
    -
  • - Fix the issues on the legacy side: If possible, resolve the problems within your legacy alerting - setup, and then attempt the upgrade again. -
  • -
  • - Manually create new resources: If fixing legacy issues isn't feasible, manually create new alert - rules, notification policies, or contact points in the new Grafana Alerting system to replace the - problematic ones. -
  • -
-
-

Step 3: Update Your As-Code Setup (Optional)

-
-

- In the new Grafana Alerting, Legacy Alerting methods of provisioning will no longer work. If you use - provisioning to manage alert rules and notification channels, you can export the upgraded versions to - generate Grafana Alerting-compatible provisioning files. This can all be done before completeing the - upgrade process. -

-
-

Step 4: Perform the Upgrade to Grafana Alerting

-
-

- Once you are satisfied with the state of your Grafana Alerting setup, it's time to proceed with the - upgrade: -

-
    -
  • - Contact your Grafana server administrator to restart Grafana with the [unified_alerting] section - enabled in your configuration. -
  • -
  • - During this process, all organizations that have undergone the above upgrade process will continue to - use their configured setup. -
  • -
  • - Any organization that has not yet started the upgrade process will be automatically upgraded as part - of this restart. -
  • -
  • - Note: If the automatic upgrade fails for any reason, Grafana will not start, so it's safer to - address any issues before initiating this step. -
  • -
-
-
- - For more information, please refer to the - - Grafana Alerting Migration Guide - - -
-
- - - -
- ); -}; - -function ContentBox({ children, className }: React.PropsWithChildren<{ className?: string }>) { - const styles = useStyles2(getContentBoxStyles); - - return
{children}
; -} - -const getContentBoxStyles = (theme: GrafanaTheme2) => { - const color = theme.colors['warning']; - return { - box: css({ - padding: theme.spacing(2), - backgroundColor: theme.colors.background.secondary, - borderRadius: theme.shape.radius.default, - }), - - warningIcon: css({ - color: color.text, - }), - - grid: css({ - display: 'grid', - gridTemplateRows: 'min-content auto auto', - gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr', - gap: theme.spacing(2), - }), - - list: css({ - margin: `${theme.spacing(0)} ${theme.spacing(2)}`, - '& > li': { - marginBottom: theme.spacing(1), - }, - }), - - ctaStyle: css({ - textAlign: 'center', - }), - - processBlock: css({ - gridColumn: '1 / span 2', - justifyContent: 'space-between', - }), - - getStartedBlock: css({ - gridColumn: '3 / span 3', - justifyContent: 'space-between', - }), - - ctaBlock: css({ - gridColumn: '1 / span 5', - }), - - header: css({ - marginBottom: theme.spacing(2), - }), - - step: css({ - paddingLeft: theme.spacing(2), - }), - }; -}; - const AlertTabContentWrapper = () => { const columns = useAlertColumns(); const filterParam = 'alertFilter';