CloudMigration: Interpret error code from migration resource item (#94407)

* start on loading the error code

* error code to message mapping

* use resource code type

* use defined error code

* partial updates from comments

* i18nKey gen

* fixed t

* fixed translations

* typing
This commit is contained in:
Dana Axinte 2024-10-17 09:09:09 -04:00 committed by GitHub
parent 1051561154
commit a50507e645
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 190 additions and 13 deletions

View File

@ -390,6 +390,7 @@ func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.R
RefID: results[i].RefID,
Status: ItemStatus(results[i].Status),
Message: results[i].Error,
ErrorCode: ItemErrorCode(results[i].ErrorCode),
ParentName: results[i].ParentName,
}
}

View File

@ -113,8 +113,9 @@ type MigrateDataResponseItemDTO struct {
// required:true
RefID string `json:"refId"`
// required:true
Status ItemStatus `json:"status"`
Message string `json:"message,omitempty"`
Status ItemStatus `json:"status"`
Message string `json:"message,omitempty"`
ErrorCode ItemErrorCode `json:"errorCode,omitempty"`
}
// swagger:enum MigrateDataType
@ -143,6 +144,21 @@ const (
ItemStatusUnknown ItemStatus = "UNKNOWN"
)
// swagger:enum ItemErrorCode
type ItemErrorCode string
const (
ErrDatasourceNameConflict ItemErrorCode = "DATASOURCE_NAME_CONFLICT"
ErrDashboardAlreadyManaged ItemErrorCode = "DASHBOARD_ALREADY_MANAGED"
ErrLibraryElementNameConflict ItemErrorCode = "LIBRARY_ELEMENT_NAME_CONFLICT"
ErrUnsupportedDataType ItemErrorCode = "UNSUPPORTED_DATA_TYPE"
ErrResourceConflict ItemErrorCode = "RESOURCE_CONFLICT"
ErrUnexpectedStatus ItemErrorCode = "UNEXPECTED_STATUS_CODE"
ErrInternalServiceError ItemErrorCode = "INTERNAL_SERVICE_ERROR"
ErrOnlyCoreDataSources ItemErrorCode = "ONLY_CORE_DATA_SOURCES"
ErrGeneric ItemErrorCode = "GENERIC_ERROR"
)
// swagger:parameters getCloudMigrationRun
type GetMigrationRunParams struct {
// RunUID of a migration run

View File

@ -573,6 +573,13 @@ func (s *Service) GetSnapshot(ctx context.Context, query cloudmigration.GetSnaps
s.log.Error("error applying plugin warnings, please open a bug report: %w", err)
}
// Log the errors for resources with errors at migration
for _, resource := range resources {
if resource.Status == cloudmigration.ItemStatusError && resource.Error != "" {
s.log.Error("Could not migrate resource", "resourceID", resource.RefID, "error", resource.Error)
}
}
// We need to update the snapshot in our db before reporting anything
if err := s.store.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{
UID: snapshot.UID,
@ -832,6 +839,7 @@ func (s *Service) getResourcesWithPluginWarnings(ctx context.Context, results []
// if the plugin is not found, it means it was uninstalled, meaning it wasn't core
if !p.IsCorePlugin() || !found {
r.Status = cloudmigration.ItemStatusWarning
r.ErrorCode = cloudmigration.ErrOnlyCoreDataSources
r.Error = "Only core data sources are supported. Please ensure the plugin is installed on the cloud stack."
}

View File

@ -312,11 +312,11 @@ func (ss *sqlStore) GetSnapshotList(ctx context.Context, query cloudmigration.Li
// If the uid is not known, it uses snapshot_uid + resource_uid as a lookup
func (ss *sqlStore) CreateUpdateSnapshotResources(ctx context.Context, snapshotUid string, resources []cloudmigration.CloudMigrationResource) error {
return ss.db.InTransaction(ctx, func(ctx context.Context) error {
sql := "UPDATE cloud_migration_resource SET status=?, error_string=? WHERE uid=? OR (snapshot_uid=? AND resource_uid=?)"
sql := "UPDATE cloud_migration_resource SET status=?, error_string=?, error_code=? WHERE uid=? OR (snapshot_uid=? AND resource_uid=?)"
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
for _, r := range resources {
// try an update first
result, err := sess.Exec(sql, r.Status, r.Error, r.UID, snapshotUid, r.RefID)
result, err := sess.Exec(sql, r.Status, r.Error, r.ErrorCode, r.UID, snapshotUid, r.RefID)
if err != nil {
return err
}

View File

@ -68,11 +68,12 @@ type CloudMigrationResource struct {
ID int64 `xorm:"pk autoincr 'id'"`
UID string `xorm:"uid"`
Name string `xorm:"name" json:"name"`
Type MigrateDataType `xorm:"resource_type" json:"type"`
RefID string `xorm:"resource_uid" json:"refId"`
Status ItemStatus `xorm:"status" json:"status"`
Error string `xorm:"error_string" json:"error"`
Name string `xorm:"name" json:"name"`
Type MigrateDataType `xorm:"resource_type" json:"type"`
RefID string `xorm:"resource_uid" json:"refId"`
Status ItemStatus `xorm:"status" json:"status"`
Error string `xorm:"error_string" json:"error"`
ErrorCode ResourceErrorCode `xorm:"error_code" json:"error_code"`
SnapshotUID string `xorm:"snapshot_uid"`
ParentName string `xorm:"parent_name" json:"parentName"`
@ -101,6 +102,20 @@ const (
ItemStatusPending ItemStatus = "PENDING"
)
type ResourceErrorCode string
const (
ErrDatasourceNameConflict ResourceErrorCode = "DATASOURCE_NAME_CONFLICT"
ErrDashboardAlreadyManaged ResourceErrorCode = "DASHBOARD_ALREADY_MANAGED"
ErrLibraryElementNameConflict ResourceErrorCode = "LIBRARY_ELEMENT_NAME_CONFLICT"
ErrUnsupportedDataType ResourceErrorCode = "UNSUPPORTED_DATA_TYPE"
ErrResourceConflict ResourceErrorCode = "RESOURCE_CONFLICT"
ErrUnexpectedStatus ResourceErrorCode = "UNEXPECTED_STATUS_CODE"
ErrInternalServiceError ResourceErrorCode = "INTERNAL_SERVICE_ERROR"
ErrOnlyCoreDataSources ResourceErrorCode = "ONLY_CORE_DATA_SOURCES"
ErrGeneric ResourceErrorCode = "GENERIC_ERROR"
)
type SnapshotResourceStats struct {
CountsByType map[MigrateDataType]int
CountsByStatus map[ItemStatus]int

View File

@ -170,4 +170,10 @@ func addCloudMigrationsMigrations(mg *Migrator) {
Type: DB_Text,
Nullable: true,
}))
mg.AddMigration("add cloud_migration_resource.error_code column", NewAddColumnMigration(migrationResourceTable, &Column{
Name: "error_code",
Type: DB_Text,
Nullable: true,
}))
}

View File

@ -5769,6 +5769,20 @@
"status"
],
"properties": {
"errorCode": {
"type": "string",
"enum": [
"DATASOURCE_NAME_CONFLICT",
"DASHBOARD_ALREADY_MANAGED",
"LIBRARY_ELEMENT_NAME_CONFLICT",
"UNSUPPORTED_DATA_TYPE",
"RESOURCE_CONFLICT",
"UNEXPECTED_STATUS_CODE",
"INTERNAL_SERVICE_ERROR",
"ONLY_CORE_DATA_SOURCES",
"GENERIC_ERROR"
]
},
"message": {
"type": "string"
},

View File

@ -17262,6 +17262,20 @@
"status"
],
"properties": {
"errorCode": {
"type": "string",
"enum": [
"DATASOURCE_NAME_CONFLICT",
"DASHBOARD_ALREADY_MANAGED",
"LIBRARY_ELEMENT_NAME_CONFLICT",
"UNSUPPORTED_DATA_TYPE",
"RESOURCE_CONFLICT",
"UNEXPECTED_STATUS_CODE",
"INTERNAL_SERVICE_ERROR",
"ONLY_CORE_DATA_SOURCES",
"GENERIC_ERROR"
]
},
"message": {
"type": "string"
},

View File

@ -164,6 +164,16 @@ export type CreateSnapshotResponseDto = {
uid?: string;
};
export type MigrateDataResponseItemDto = {
errorCode?:
| 'DATASOURCE_NAME_CONFLICT'
| 'DASHBOARD_ALREADY_MANAGED'
| 'LIBRARY_ELEMENT_NAME_CONFLICT'
| 'UNSUPPORTED_DATA_TYPE'
| 'RESOURCE_CONFLICT'
| 'UNEXPECTED_STATUS_CODE'
| 'INTERNAL_SERVICE_ERROR'
| 'ONLY_CORE_DATA_SOURCES'
| 'GENERIC_ERROR';
message?: string;
name?: string;
parentName?: string;

View File

@ -1,6 +1,8 @@
import { Button, Modal, Stack, Text } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { MigrateDataResponseItemDto } from '../api';
import { prettyTypeName } from './TypeCell';
import { ResourceTableItem } from './types';
@ -9,11 +11,65 @@ interface ResourceDetailsModalProps {
onClose: () => void;
}
function getTMessage(errorCode: MigrateDataResponseItemDto['errorCode']): string {
switch (errorCode) {
case 'DATASOURCE_NAME_CONFLICT':
return t(
'migrate-to-cloud.resource-details.error-messages.datasource-name-conflict',
'There is a data source with the same name in the target instance. Rename one of them and try again.'
);
case 'DASHBOARD_ALREADY_MANAGED':
return t(
'migrate-to-cloud.resource-details.error-messages.dashboard-already-managed',
'Dashboard is already provisioned and managed by Grafana in the cloud instance. We recommend using the provisioned dashboard going forward. If you still wish to copy the dashboard to the cloud instance, then change the dashboard ID in the dashboard JSON, save a new snapshot and upload again.'
);
case 'LIBRARY_ELEMENT_NAME_CONFLICT':
return t(
'migrate-to-cloud.resource-details.error-messages.library-element-name-conflict',
'There is a library element with the same name in the target instance. Rename one of them and try again.'
);
case 'UNSUPPORTED_DATA_TYPE':
return t(
'migrate-to-cloud.resource-details.error-messages.unsupported-data-type',
'Migration of this data type is not currently supported.'
);
case 'RESOURCE_CONFLICT':
return t(
'migrate-to-cloud.resource-details.error-messages.resource-conflict',
'There is a resource conflict with the target instance. Please check the Grafana server logs for more details.'
);
case 'ONLY_CORE_DATA_SOURCES':
return t(
'migrate-to-cloud.resource-details.error-messages.only-core-data-sources',
'Only core data sources are supported. Please ensure the plugin is installed on the cloud stack.'
);
case 'UNEXPECTED_STATUS_CODE':
return t(
'migrate-to-cloud.resource-details.error-messages.unexpected-error',
'There has been an error while migrating. Please check the Grafana server logs for more details.'
);
case 'INTERNAL_SERVICE_ERROR':
return t(
'migrate-to-cloud.resource-details.error-messages.internal-service-error',
'There has been an error while migrating. Please check the Grafana server logs for more details.'
);
case 'GENERIC_ERROR':
return t(
'migrate-to-cloud.resource-details.error-messages.generic-error',
'There has been an error while migrating. Please check the cloud migration logs for more information.'
);
// Handle new errors here
default:
return '';
}
}
export function ResourceDetailsModal(props: ResourceDetailsModalProps) {
const { resource, onClose } = props;
const refId = resource?.refId;
const typeName = resource && prettyTypeName(resource.type);
const hasError = resource?.errorCode || resource?.message;
let msgTitle = t('migrate-to-cloud.resource-details.generic-title', 'Resource migration details:');
if (resource?.status === 'ERROR') {
@ -36,12 +92,13 @@ export function ResourceDetailsModal(props: ResourceDetailsModalProps) {
</Trans>
</Text>
{resource.message ? (
{hasError ? (
<>
<Text element="p">{msgTitle}</Text>
<Text element="p" weight="bold">
{resource.message}
<Text element="p">
{getTMessage(resource?.errorCode) ||
resource?.message ||
'There has been an error while migrating. Please check the cloud migration logs for more information.'}
</Text>
</>
) : (

View File

@ -1472,6 +1472,17 @@
},
"resource-details": {
"dismiss-button": "OK",
"error-messages": {
"dashboard-already-managed": "Dashboard is already provisioned and managed by Grafana in the cloud instance. We recommend using the provisioned dashboard going forward. If you still wish to copy the dashboard to the cloud instance, then change the dashboard ID in the dashboard JSON, save a new snapshot and upload again.",
"datasource-name-conflict": "There is a data source with the same name in the target instance. Rename one of them and try again.",
"generic-error": "There has been an error while migrating. Please check the cloud migration logs for more information.",
"internal-service-error": "There has been an error while migrating. Please check the Grafana server logs for more details.",
"library-element-name-conflict": "There is a library element with the same name in the target instance. Rename one of them and try again.",
"only-core-data-sources": "Only core data sources are supported. Please ensure the plugin is installed on the cloud stack.",
"resource-conflict": "There is a resource conflict with the target instance. Please check the Grafana server logs for more details.",
"unexpected-error": "There has been an error while migrating. Please check the Grafana server logs for more details.",
"unsupported-data-type": "Migration of this data type is not currently supported."
},
"error-title": "Unable to migrate this resource:",
"generic-title": "Resource migration details:",
"missing-message": "No message provided.",

View File

@ -1472,6 +1472,17 @@
},
"resource-details": {
"dismiss-button": "ØĶ",
"error-messages": {
"dashboard-already-managed": "Đäşĥþőäřđ įş äľřęäđy přővįşįőʼnęđ äʼnđ mäʼnäģęđ þy Ğřäƒäʼnä įʼn ŧĥę čľőūđ įʼnşŧäʼnčę. Ŵę řęčőmmęʼnđ ūşįʼnģ ŧĥę přővįşįőʼnęđ đäşĥþőäřđ ģőįʼnģ ƒőřŵäřđ. Ĩƒ yőū şŧįľľ ŵįşĥ ŧő čőpy ŧĥę đäşĥþőäřđ ŧő ŧĥę čľőūđ įʼnşŧäʼnčę, ŧĥęʼn čĥäʼnģę ŧĥę đäşĥþőäřđ ĨĐ įʼn ŧĥę đäşĥþőäřđ ĴŜØŃ, şävę ä ʼnęŵ şʼnäpşĥőŧ äʼnđ ūpľőäđ äģäįʼn.",
"datasource-name-conflict": "Ŧĥęřę įş ä đäŧä şőūřčę ŵįŧĥ ŧĥę şämę ʼnämę įʼn ŧĥę ŧäřģęŧ įʼnşŧäʼnčę. Ŗęʼnämę őʼnę őƒ ŧĥęm äʼnđ ŧřy äģäįʼn.",
"generic-error": "Ŧĥęřę ĥäş þęęʼn äʼn ęřřőř ŵĥįľę mįģřäŧįʼnģ. Pľęäşę čĥęčĸ ŧĥę čľőūđ mįģřäŧįőʼn ľőģş ƒőř mőřę įʼnƒőřmäŧįőʼn.",
"internal-service-error": "Ŧĥęřę ĥäş þęęʼn äʼn ęřřőř ŵĥįľę mįģřäŧįʼnģ. Pľęäşę čĥęčĸ ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş.",
"library-element-name-conflict": "Ŧĥęřę įş ä ľįþřäřy ęľęmęʼnŧ ŵįŧĥ ŧĥę şämę ʼnämę įʼn ŧĥę ŧäřģęŧ įʼnşŧäʼnčę. Ŗęʼnämę őʼnę őƒ ŧĥęm äʼnđ ŧřy äģäįʼn.",
"only-core-data-sources": "Øʼnľy čőřę đäŧä şőūřčęş äřę şūppőřŧęđ. Pľęäşę ęʼnşūřę ŧĥę pľūģįʼn įş įʼnşŧäľľęđ őʼn ŧĥę čľőūđ şŧäčĸ.",
"resource-conflict": "Ŧĥęřę įş ä řęşőūřčę čőʼnƒľįčŧ ŵįŧĥ ŧĥę ŧäřģęŧ įʼnşŧäʼnčę. Pľęäşę čĥęčĸ ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş.",
"unexpected-error": "Ŧĥęřę ĥäş þęęʼn äʼn ęřřőř ŵĥįľę mįģřäŧįʼnģ. Pľęäşę čĥęčĸ ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş.",
"unsupported-data-type": "Mįģřäŧįőʼn őƒ ŧĥįş đäŧä ŧypę įş ʼnőŧ čūřřęʼnŧľy şūppőřŧęđ."
},
"error-title": "Ůʼnäþľę ŧő mįģřäŧę ŧĥįş řęşőūřčę:",
"generic-title": "Ŗęşőūřčę mįģřäŧįőʼn đęŧäįľş:",
"missing-message": "Ńő męşşäģę přővįđęđ.",

View File

@ -7215,6 +7215,20 @@
},
"MigrateDataResponseItemDTO": {
"properties": {
"errorCode": {
"enum": [
"DATASOURCE_NAME_CONFLICT",
"DASHBOARD_ALREADY_MANAGED",
"LIBRARY_ELEMENT_NAME_CONFLICT",
"UNSUPPORTED_DATA_TYPE",
"RESOURCE_CONFLICT",
"UNEXPECTED_STATUS_CODE",
"INTERNAL_SERVICE_ERROR",
"ONLY_CORE_DATA_SOURCES",
"GENERIC_ERROR"
],
"type": "string"
},
"message": {
"type": "string"
},