CloudMigrations: Implement snapshot management apis (#89296)
* add new apis * add payloads * create snapshot status type * add some impl * finish implementing update * start implementing build snapshot func * add more fake build logic * add cancel endpoint. do some cleanup * implement GetSnapshot * implement upload snapshot * merge onprem status with gms result * get it working * update comment * rename list endpoint * add query limit and offset * add helper method to snapshot * little bit of cleanup * work on swagger annotations * manual merge * generate swagger specs * clean up curl commands * fix bugs found during final testing * fix linter issue * fix unit test
This commit is contained in:
@@ -40,19 +40,28 @@ func RegisterApi(
|
||||
// registerEndpoints Registers Endpoints on Grafana Router
|
||||
func (cma *CloudMigrationAPI) registerEndpoints() {
|
||||
cma.routeRegister.Group("/api/cloudmigration", func(cloudMigrationRoute routing.RouteRegister) {
|
||||
// destination instance endpoints for token management
|
||||
cloudMigrationRoute.Get("/token", routing.Wrap(cma.GetToken))
|
||||
cloudMigrationRoute.Post("/token", routing.Wrap(cma.CreateToken))
|
||||
cloudMigrationRoute.Delete("/token/:uid", routing.Wrap(cma.DeleteToken))
|
||||
|
||||
// on-prem instance endpoints for managing GMS sessions
|
||||
cloudMigrationRoute.Get("/migration", routing.Wrap(cma.GetSessionList))
|
||||
cloudMigrationRoute.Post("/migration", routing.Wrap(cma.CreateSession))
|
||||
cloudMigrationRoute.Get("/migration/:uid", routing.Wrap(cma.GetSession))
|
||||
cloudMigrationRoute.Delete("/migration/:uid", routing.Wrap(cma.DeleteSession))
|
||||
|
||||
// TODO new APIs for snapshot management to replace these
|
||||
// sync approach to data migration
|
||||
cloudMigrationRoute.Post("/migration/:uid/run", routing.Wrap(cma.RunMigration))
|
||||
cloudMigrationRoute.Get("/migration/:uid/run", routing.Wrap(cma.GetMigrationRunList))
|
||||
cloudMigrationRoute.Get("/migration/run/:runUID", routing.Wrap(cma.GetMigrationRun))
|
||||
|
||||
cloudMigrationRoute.Get("/token", routing.Wrap(cma.GetToken))
|
||||
cloudMigrationRoute.Post("/token", routing.Wrap(cma.CreateToken))
|
||||
cloudMigrationRoute.Delete("/token/:uid", routing.Wrap(cma.DeleteToken))
|
||||
// async approach to data migration using snapshots
|
||||
cloudMigrationRoute.Post("/migration/:uid/snapshot", routing.Wrap(cma.CreateSnapshot))
|
||||
cloudMigrationRoute.Get("/migration/:uid/snapshot/:snapshotUid", routing.Wrap(cma.GetSnapshot))
|
||||
cloudMigrationRoute.Get("/migration/:uid/snapshots", routing.Wrap(cma.GetSnapshotList))
|
||||
cloudMigrationRoute.Post("/migration/:uid/snapshot/:snapshotUid/upload", routing.Wrap(cma.UploadSnapshot))
|
||||
cloudMigrationRoute.Post("/migration/:uid/snapshot/:snapshotUid/cancel", routing.Wrap(cma.CancelSnapshot))
|
||||
}, middleware.ReqOrgAdmin)
|
||||
}
|
||||
|
||||
@@ -121,6 +130,7 @@ func (cma *CloudMigrationAPI) CreateToken(c *contextmodel.ReqContext) response.R
|
||||
//
|
||||
// Responses:
|
||||
// 204: cloudMigrationDeleteTokenResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
@@ -170,6 +180,7 @@ func (cma *CloudMigrationAPI) GetSessionList(c *contextmodel.ReqContext) respons
|
||||
//
|
||||
// Responses:
|
||||
// 200: cloudMigrationSessionResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
@@ -201,6 +212,7 @@ func (cma *CloudMigrationAPI) GetSession(c *contextmodel.ReqContext) response.Re
|
||||
//
|
||||
// Responses:
|
||||
// 200: cloudMigrationSessionResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
@@ -235,6 +247,7 @@ func (cma *CloudMigrationAPI) CreateSession(c *contextmodel.ReqContext) response
|
||||
//
|
||||
// Responses:
|
||||
// 200: cloudMigrationRunResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
@@ -261,6 +274,7 @@ func (cma *CloudMigrationAPI) RunMigration(c *contextmodel.ReqContext) response.
|
||||
//
|
||||
// Responses:
|
||||
// 200: cloudMigrationRunResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
@@ -293,6 +307,7 @@ func (cma *CloudMigrationAPI) GetMigrationRun(c *contextmodel.ReqContext) respon
|
||||
//
|
||||
// Responses:
|
||||
// 200: cloudMigrationRunListResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
@@ -314,7 +329,7 @@ func (cma *CloudMigrationAPI) GetMigrationRunList(c *contextmodel.ReqContext) re
|
||||
for i := 0; i < len(runList.Runs); i++ {
|
||||
runs[i] = MigrateDataResponseListDTO{runList.Runs[i].RunUID}
|
||||
}
|
||||
return response.JSON(http.StatusOK, SnapshotListDTO{
|
||||
return response.JSON(http.StatusOK, CloudMigrationRunListDTO{
|
||||
Runs: runs,
|
||||
})
|
||||
}
|
||||
@@ -326,6 +341,7 @@ func (cma *CloudMigrationAPI) GetMigrationRunList(c *contextmodel.ReqContext) re
|
||||
// Responses:
|
||||
// 200
|
||||
// 401: unauthorisedError
|
||||
// 400: badRequestError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (cma *CloudMigrationAPI) DeleteSession(c *contextmodel.ReqContext) response.Response {
|
||||
@@ -343,3 +359,194 @@ func (cma *CloudMigrationAPI) DeleteSession(c *contextmodel.ReqContext) response
|
||||
}
|
||||
return response.Empty(http.StatusOK)
|
||||
}
|
||||
|
||||
// swagger:route POST /cloudmigration/migration/{uid}/snapshot migrations createSnapshot
|
||||
//
|
||||
// Trigger the creation of an instance snapshot associated with the provided session.
|
||||
// If the snapshot initialization is successful, the snapshot uid is returned.
|
||||
//
|
||||
// Responses:
|
||||
// 200: createSnapshotResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (cma *CloudMigrationAPI) CreateSnapshot(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.CreateSnapshot")
|
||||
defer span.End()
|
||||
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
if err := util.ValidateUID(uid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid session uid", err)
|
||||
}
|
||||
|
||||
ss, err := cma.cloudMigrationService.CreateSnapshot(ctx, uid)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "error creating snapshot", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, CreateSnapshotResponseDTO{
|
||||
SnapshotUID: ss.UID,
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:route GET /cloudmigration/migration/{uid}/snapshot/{snapshotUid} migrations getSnapshot
|
||||
//
|
||||
// Get metadata about a snapshot, including where it is in its processing and final results.
|
||||
//
|
||||
// Responses:
|
||||
// 200: getSnapshotResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (cma *CloudMigrationAPI) GetSnapshot(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetSnapshot")
|
||||
defer span.End()
|
||||
|
||||
sessUid, snapshotUid := web.Params(c.Req)[":uid"], web.Params(c.Req)[":snapshotUid"]
|
||||
if err := util.ValidateUID(sessUid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid session uid", err)
|
||||
}
|
||||
if err := util.ValidateUID(snapshotUid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid snapshot uid", err)
|
||||
}
|
||||
|
||||
snapshot, err := cma.cloudMigrationService.GetSnapshot(ctx, sessUid, snapshotUid)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "error retrieving snapshot", err)
|
||||
}
|
||||
|
||||
result, err := snapshot.GetSnapshotResult()
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "error snapshot reading snapshot results", err)
|
||||
}
|
||||
|
||||
dtoResults := make([]MigrateDataResponseItemDTO, len(result))
|
||||
for i := 0; i < len(result); i++ {
|
||||
dtoResults[i] = MigrateDataResponseItemDTO{
|
||||
Type: MigrateDataType(result[i].Type),
|
||||
RefID: result[i].RefID,
|
||||
Status: ItemStatus(result[i].Status),
|
||||
Error: result[i].Error,
|
||||
}
|
||||
}
|
||||
|
||||
respDto := GetSnapshotResponseDTO{
|
||||
SnapshotDTO: SnapshotDTO{
|
||||
SnapshotUID: snapshot.UID,
|
||||
Status: fromSnapshotStatus(snapshot.Status),
|
||||
SessionUID: sessUid,
|
||||
Created: snapshot.Created,
|
||||
Finished: snapshot.Finished,
|
||||
},
|
||||
Results: dtoResults,
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, respDto)
|
||||
}
|
||||
|
||||
// swagger:route GET /cloudmigration/migration/{uid}/snapshots migrations getShapshotList
|
||||
//
|
||||
// Get a list of snapshots for a session.
|
||||
//
|
||||
// Responses:
|
||||
// 200: snapshotListResponse
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (cma *CloudMigrationAPI) GetSnapshotList(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.GetShapshotList")
|
||||
defer span.End()
|
||||
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
if err := util.ValidateUID(uid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid session uid", err)
|
||||
}
|
||||
q := cloudmigration.ListSnapshotsQuery{
|
||||
SessionUID: uid,
|
||||
Limit: c.QueryInt("limit"),
|
||||
Offset: c.QueryInt("offset"),
|
||||
}
|
||||
if q.Limit == 0 {
|
||||
q.Limit = 100
|
||||
}
|
||||
|
||||
snapshotList, err := cma.cloudMigrationService.GetSnapshotList(ctx, q)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "error retrieving snapshot list", err)
|
||||
}
|
||||
|
||||
dtos := make([]SnapshotDTO, len(snapshotList))
|
||||
for i := 0; i < len(snapshotList); i++ {
|
||||
dtos[i] = SnapshotDTO{
|
||||
SnapshotUID: snapshotList[i].UID,
|
||||
Status: fromSnapshotStatus(snapshotList[i].Status),
|
||||
SessionUID: uid,
|
||||
Created: snapshotList[i].Created,
|
||||
Finished: snapshotList[i].Finished,
|
||||
}
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, SnapshotListResponseDTO{
|
||||
Snapshots: dtos,
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:route POST /cloudmigration/migration/{uid}/snapshot/{snapshotUid}/upload migrations uploadSnapshot
|
||||
//
|
||||
// Upload a snapshot to the Grafana Migration Service for processing.
|
||||
//
|
||||
// Responses:
|
||||
// 200:
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (cma *CloudMigrationAPI) UploadSnapshot(c *contextmodel.ReqContext) response.Response {
|
||||
ctx, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.UploadSnapshot")
|
||||
defer span.End()
|
||||
|
||||
sessUid, snapshotUid := web.Params(c.Req)[":uid"], web.Params(c.Req)[":snapshotUid"]
|
||||
if err := util.ValidateUID(sessUid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid session uid", err)
|
||||
}
|
||||
if err := util.ValidateUID(snapshotUid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid snapshot uid", err)
|
||||
}
|
||||
|
||||
if err := cma.cloudMigrationService.UploadSnapshot(ctx, sessUid, snapshotUid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "error uploading snapshot", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, nil)
|
||||
}
|
||||
|
||||
// swagger:route POST /cloudmigration/migration/{uid}/snapshot/{snapshotUid}/cancel migrations cancelSnapshot
|
||||
//
|
||||
// Cancel a snapshot, wherever it is in its processing chain.
|
||||
// TODO: Implement
|
||||
//
|
||||
// Responses:
|
||||
// 200:
|
||||
// 400: badRequestError
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (cma *CloudMigrationAPI) CancelSnapshot(c *contextmodel.ReqContext) response.Response {
|
||||
_, span := cma.tracer.Start(c.Req.Context(), "MigrationAPI.CancelSnapshot")
|
||||
defer span.End()
|
||||
|
||||
sessUid, snapshotUid := web.Params(c.Req)[":uid"], web.Params(c.Req)[":snapshotUid"]
|
||||
if err := util.ValidateUID(sessUid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid session uid", err)
|
||||
}
|
||||
if err := util.ValidateUID(snapshotUid); err != nil {
|
||||
return response.ErrOrFallback(http.StatusBadRequest, "invalid snapshot uid", err)
|
||||
}
|
||||
|
||||
// Implement
|
||||
|
||||
return response.JSON(http.StatusOK, nil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
[sample token] // NOT A REAL TOKEN
|
||||
eyJUb2tlbiI6ImNvbXBsZXRlbHlfZmFrZV90b2tlbl9jZG9peTFhYzdwdXlwZCIsIkluc3RhbmNlIjp7IlN0YWNrSUQiOjEyMzQ1LCJTbHVnIjoic3R1Ymluc3RhbmNlIiwiUmVnaW9uU2x1ZyI6ImZha2UtcmVnaW9uIiwiQ2x1c3RlclNsdWciOiJmYWtlLWNsdXNlciJ9fQ==
|
||||
|
||||
[create session}
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
http://admin:admin@localhost:3000/api/cloudmigration/migration \
|
||||
-d '{"AuthToken":"eyJUb2tlbiI6ImNvbXBsZXRlbHlfZmFrZV90b2tlbl9jZG9peTFhYzdwdXlwZCIsIkluc3RhbmNlIjp7IlN0YWNrSUQiOjEyMzQ1LCJTbHVnIjoic3R1Ymluc3RhbmNlIiwiUmVnaW9uU2x1ZyI6ImZha2UtcmVnaW9uIiwiQ2x1c3RlclNsdWciOiJmYWtlLWNsdXNlciJ9fQ=="}'
|
||||
|
||||
[create snapshot]
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
http://admin:admin@localhost:3000/api/cloudmigration/migration/{sessionUid}/snapshot
|
||||
|
||||
[get snapshot list]
|
||||
curl -X GET http://admin:admin@localhost:3000/api/cloudmigration/migration/{sessionUid}/snapshots?limit=100&offset=0
|
||||
|
||||
[get snapshot]
|
||||
curl -X GET http://admin:admin@localhost:3000/api/cloudmigration/migration/{sessionUid}/snapshot/{snapshotUid}
|
||||
|
||||
[upload snapshot]
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
http://admin:admin@localhost:3000/api/cloudmigration/migration/{sessionUid}/snapshot/{snapshotUid}/upload
|
||||
@@ -38,7 +38,6 @@ type CreateAccessTokenResponseDTO struct {
|
||||
// swagger:parameters deleteCloudMigrationToken
|
||||
type DeleteCloudMigrationToken struct {
|
||||
// UID of a cloud migration token
|
||||
//
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
@@ -67,7 +66,6 @@ type CloudMigrationSessionListResponseDTO struct {
|
||||
// swagger:parameters getSession
|
||||
type GetCloudMigrationSessionRequest struct {
|
||||
// UID of a migration session
|
||||
//
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
@@ -92,7 +90,6 @@ type CloudMigrationSessionRequestDTO struct {
|
||||
// swagger:parameters runCloudMigration
|
||||
type RunCloudMigrationRequest struct {
|
||||
// UID of a migration
|
||||
//
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
@@ -138,7 +135,6 @@ const (
|
||||
// swagger:parameters getCloudMigrationRun
|
||||
type GetMigrationRunParams struct {
|
||||
// RunUID of a migration run
|
||||
//
|
||||
// in: path
|
||||
RunUID string `json:"runUID"`
|
||||
}
|
||||
@@ -146,7 +142,6 @@ type GetMigrationRunParams struct {
|
||||
// swagger:parameters getCloudMigrationRunList
|
||||
type GetCloudMigrationRunList struct {
|
||||
// UID of a migration
|
||||
//
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
@@ -154,10 +149,10 @@ type GetCloudMigrationRunList struct {
|
||||
// swagger:response cloudMigrationRunListResponse
|
||||
type CloudMigrationRunListResponse struct {
|
||||
// in: body
|
||||
Body SnapshotListDTO
|
||||
Body CloudMigrationRunListDTO
|
||||
}
|
||||
|
||||
type SnapshotListDTO struct {
|
||||
type CloudMigrationRunListDTO struct {
|
||||
Runs []MigrateDataResponseListDTO `json:"runs"`
|
||||
}
|
||||
|
||||
@@ -168,7 +163,6 @@ type MigrateDataResponseListDTO struct {
|
||||
// swagger:parameters deleteSession
|
||||
type DeleteMigrationSessionRequest struct {
|
||||
// UID of a migration session
|
||||
//
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
@@ -207,3 +201,138 @@ func convertMigrateDataResponseToDTO(r cloudmigration.MigrateDataResponse) Migra
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
|
||||
// Base snapshot without results
|
||||
type SnapshotDTO struct {
|
||||
SnapshotUID string `json:"uid"`
|
||||
Status SnapshotStatus `json:"status"`
|
||||
SessionUID string `json:"sessionUid"`
|
||||
Created time.Time `json:"created"`
|
||||
Finished time.Time `json:"finished"`
|
||||
}
|
||||
|
||||
// swagger:enum SnapshotStatus
|
||||
type SnapshotStatus string
|
||||
|
||||
const (
|
||||
SnapshotStatusInitializing SnapshotStatus = "INITIALIZING"
|
||||
SnapshotStatusCreating SnapshotStatus = "CREATING"
|
||||
SnapshotStatusPendingUpload SnapshotStatus = "PENDING_UPLOAD"
|
||||
SnapshotStatusUploading SnapshotStatus = "UPLOADING"
|
||||
SnapshotStatusPendingProcessing SnapshotStatus = "PENDING_PROCESSING"
|
||||
SnapshotStatusProcessing SnapshotStatus = "PROCESSING"
|
||||
SnapshotStatusFinished SnapshotStatus = "FINISHED"
|
||||
SnapshotStatusError SnapshotStatus = "ERROR"
|
||||
SnapshotStatusUnknown SnapshotStatus = "UNKNOWN"
|
||||
)
|
||||
|
||||
func fromSnapshotStatus(status cloudmigration.SnapshotStatus) SnapshotStatus {
|
||||
switch status {
|
||||
case cloudmigration.SnapshotStatusInitializing:
|
||||
return SnapshotStatusInitializing
|
||||
case cloudmigration.SnapshotStatusCreating:
|
||||
return SnapshotStatusCreating
|
||||
case cloudmigration.SnapshotStatusPendingUpload:
|
||||
return SnapshotStatusPendingUpload
|
||||
case cloudmigration.SnapshotStatusUploading:
|
||||
return SnapshotStatusUploading
|
||||
case cloudmigration.SnapshotStatusPendingProcessing:
|
||||
return SnapshotStatusPendingProcessing
|
||||
case cloudmigration.SnapshotStatusProcessing:
|
||||
return SnapshotStatusProcessing
|
||||
case cloudmigration.SnapshotStatusFinished:
|
||||
return SnapshotStatusFinished
|
||||
case cloudmigration.SnapshotStatusError:
|
||||
return SnapshotStatusError
|
||||
default:
|
||||
return SnapshotStatusUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters createSnapshot
|
||||
type CreateSnapshotRequest struct {
|
||||
// UID of a session
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
|
||||
// swagger:response createSnapshotResponse
|
||||
type CreateSnapshotResponse struct {
|
||||
// in: body
|
||||
Body CreateSnapshotResponseDTO
|
||||
}
|
||||
|
||||
type CreateSnapshotResponseDTO struct {
|
||||
SnapshotUID string `json:"uid"`
|
||||
}
|
||||
|
||||
// swagger:parameters getSnapshot
|
||||
type GetSnapshotParams struct {
|
||||
// Session UID of a session
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
|
||||
// UID of a snapshot
|
||||
// in: path
|
||||
SnapshotUID string `json:"snapshotUid"`
|
||||
}
|
||||
|
||||
// swagger:response getSnapshotResponse
|
||||
type GetSnapshotResponse struct {
|
||||
// in: body
|
||||
Body GetSnapshotResponseDTO
|
||||
}
|
||||
|
||||
type GetSnapshotResponseDTO struct {
|
||||
SnapshotDTO
|
||||
Results []MigrateDataResponseItemDTO `json:"results"`
|
||||
}
|
||||
|
||||
// swagger:parameters getShapshotList
|
||||
type GetSnapshotListParams struct {
|
||||
// Offset is used for pagination with limit
|
||||
// in:query
|
||||
// required:false
|
||||
// default: 0
|
||||
Offset int `json:"offset"`
|
||||
// Max limit for results returned.
|
||||
// in:query
|
||||
// required:false
|
||||
// default: 100
|
||||
Limit int `json:"limit"`
|
||||
// Session UID of a session
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
|
||||
// swagger:response snapshotListResponse
|
||||
type SnapshotListResponse struct {
|
||||
// in: body
|
||||
Body SnapshotListResponseDTO
|
||||
}
|
||||
|
||||
type SnapshotListResponseDTO struct {
|
||||
Snapshots []SnapshotDTO `json:"snapshots"`
|
||||
}
|
||||
|
||||
// swagger:parameters uploadSnapshot
|
||||
type UploadSnapshotParams struct {
|
||||
// Session UID of a session
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
|
||||
// UID of a snapshot
|
||||
// in: path
|
||||
SnapshotUID string `json:"snapshotUid"`
|
||||
}
|
||||
|
||||
// swagger:parameters cancelSnapshot
|
||||
type CancelSnapshotParams struct {
|
||||
// Session UID of a session
|
||||
// in: path
|
||||
UID string `json:"uid"`
|
||||
|
||||
// UID of a snapshot
|
||||
// in: path
|
||||
SnapshotUID string `json:"snapshotUid"`
|
||||
}
|
||||
|
||||
@@ -22,5 +22,10 @@ type Service interface {
|
||||
|
||||
RunMigration(ctx context.Context, migUID string) (*MigrateDataResponse, error)
|
||||
GetMigrationStatus(ctx context.Context, runUID string) (*CloudMigrationSnapshot, error)
|
||||
GetMigrationRunList(ctx context.Context, migUID string) (*SnapshotList, error)
|
||||
GetMigrationRunList(ctx context.Context, migUID string) (*CloudMigrationRunList, error)
|
||||
|
||||
CreateSnapshot(ctx context.Context, sessionUid string) (*CloudMigrationSnapshot, error)
|
||||
GetSnapshot(ctx context.Context, sessionUid string, snapshotUid string) (*CloudMigrationSnapshot, error)
|
||||
GetSnapshotList(ctx context.Context, query ListSnapshotsQuery) ([]CloudMigrationSnapshot, error)
|
||||
UploadSnapshot(ctx context.Context, sessionUid string, snapshotUid string) error
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
@@ -17,7 +20,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration"
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration/api"
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration/gmsclient"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@@ -25,6 +27,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/gcom"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
@@ -37,6 +40,9 @@ type Service struct {
|
||||
log *log.ConcreteLogger
|
||||
cfg *setting.Cfg
|
||||
|
||||
buildSnapshotMutex sync.Mutex
|
||||
buildSnapshotError bool
|
||||
|
||||
features featuremgmt.FeatureToggles
|
||||
gmsClient gmsclient.Client
|
||||
|
||||
@@ -398,7 +404,6 @@ func (s *Service) RunMigration(ctx context.Context, uid string) (*cloudmigration
|
||||
return nil, fmt.Errorf("migrate data error: %w", err)
|
||||
}
|
||||
|
||||
// TODO update cloud migration run schema to treat the result as a first-class citizen
|
||||
respData, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
s.log.Error("error marshalling migration response data: %w", err)
|
||||
@@ -419,135 +424,6 @@ func (s *Service) RunMigration(ctx context.Context, uid string) (*cloudmigration
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *Service) getMigrationDataJSON(ctx context.Context) (*cloudmigration.MigrateDataRequest, error) {
|
||||
// Data sources
|
||||
dataSources, err := s.getDataSources(ctx)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get datasources", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Dashboards
|
||||
dashboards, err := s.getDashboards(ctx)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get dashboards", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Folders
|
||||
folders, err := s.getFolders(ctx)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get folders", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
migrationDataSlice := make(
|
||||
[]cloudmigration.MigrateDataRequestItem, 0,
|
||||
len(dataSources)+len(dashboards)+len(folders),
|
||||
)
|
||||
for _, ds := range dataSources {
|
||||
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
|
||||
Type: cloudmigration.DatasourceDataType,
|
||||
RefID: ds.UID,
|
||||
Name: ds.Name,
|
||||
Data: ds,
|
||||
})
|
||||
}
|
||||
|
||||
for _, dashboard := range dashboards {
|
||||
dashboard.Data.Del("id")
|
||||
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
|
||||
Type: cloudmigration.DashboardDataType,
|
||||
RefID: dashboard.UID,
|
||||
Name: dashboard.Title,
|
||||
Data: map[string]any{"dashboard": dashboard.Data},
|
||||
})
|
||||
}
|
||||
|
||||
for _, f := range folders {
|
||||
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
|
||||
Type: cloudmigration.FolderDataType,
|
||||
RefID: f.UID,
|
||||
Name: f.Title,
|
||||
Data: f,
|
||||
})
|
||||
}
|
||||
|
||||
migrationData := &cloudmigration.MigrateDataRequest{
|
||||
Items: migrationDataSlice,
|
||||
}
|
||||
|
||||
return migrationData, nil
|
||||
}
|
||||
|
||||
func (s *Service) getDataSources(ctx context.Context) ([]datasources.AddDataSourceCommand, error) {
|
||||
dataSources, err := s.dsService.GetAllDataSources(ctx, &datasources.GetAllDataSourcesQuery{})
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get all datasources", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []datasources.AddDataSourceCommand{}
|
||||
for _, dataSource := range dataSources {
|
||||
// Decrypt secure json to send raw credentials
|
||||
decryptedData, err := s.secretsService.DecryptJsonData(ctx, dataSource.SecureJsonData)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to decrypt secure json data", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
dataSourceCmd := datasources.AddDataSourceCommand{
|
||||
OrgID: dataSource.OrgID,
|
||||
Name: dataSource.Name,
|
||||
Type: dataSource.Type,
|
||||
Access: dataSource.Access,
|
||||
URL: dataSource.URL,
|
||||
User: dataSource.User,
|
||||
Database: dataSource.Database,
|
||||
BasicAuth: dataSource.BasicAuth,
|
||||
BasicAuthUser: dataSource.BasicAuthUser,
|
||||
WithCredentials: dataSource.WithCredentials,
|
||||
IsDefault: dataSource.IsDefault,
|
||||
JsonData: dataSource.JsonData,
|
||||
SecureJsonData: decryptedData,
|
||||
ReadOnly: dataSource.ReadOnly,
|
||||
UID: dataSource.UID,
|
||||
}
|
||||
result = append(result, dataSourceCmd)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *Service) getFolders(ctx context.Context) ([]folder.Folder, error) {
|
||||
reqCtx := contexthandler.FromContext(ctx)
|
||||
folders, err := s.folderService.GetFolders(ctx, folder.GetFoldersQuery{
|
||||
SignedInUser: reqCtx.SignedInUser,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]folder.Folder, len(folders))
|
||||
for i, folder := range folders {
|
||||
result[i] = *folder
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) getDashboards(ctx context.Context) ([]dashboards.Dashboard, error) {
|
||||
dashs, err := s.dashboardService.GetAllDashboards(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]dashboards.Dashboard, len(dashs))
|
||||
for i, dashboard := range dashs {
|
||||
result[i] = *dashboard
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) createMigrationRun(ctx context.Context, cmr cloudmigration.CloudMigrationSnapshot) (string, error) {
|
||||
uid, err := s.store.CreateMigrationRun(ctx, cmr)
|
||||
if err != nil {
|
||||
@@ -565,13 +441,13 @@ func (s *Service) GetMigrationStatus(ctx context.Context, runUID string) (*cloud
|
||||
return cmr, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetMigrationRunList(ctx context.Context, migUID string) (*cloudmigration.SnapshotList, error) {
|
||||
func (s *Service) GetMigrationRunList(ctx context.Context, migUID string) (*cloudmigration.CloudMigrationRunList, error) {
|
||||
runs, err := s.store.GetMigrationStatusList(ctx, migUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving migration statuses from db: %w", err)
|
||||
}
|
||||
|
||||
runList := &cloudmigration.SnapshotList{Runs: []cloudmigration.MigrateDataResponseList{}}
|
||||
runList := &cloudmigration.CloudMigrationRunList{Runs: []cloudmigration.MigrateDataResponseList{}}
|
||||
for _, s := range runs {
|
||||
runList.Runs = append(runList.Runs, cloudmigration.MigrateDataResponseList{
|
||||
RunUID: s.UID,
|
||||
@@ -589,6 +465,123 @@ func (s *Service) DeleteSession(ctx context.Context, uid string) (*cloudmigratio
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreateSnapshot(ctx context.Context, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.CreateSnapshot")
|
||||
defer span.End()
|
||||
|
||||
// fetch session for the gms auth token
|
||||
session, err := s.store.GetMigrationSessionByUID(ctx, sessionUid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching migration session for uid %s: %w", sessionUid, err)
|
||||
}
|
||||
|
||||
// query gms to establish new snapshot
|
||||
initResp, err := s.gmsClient.InitializeSnapshot(ctx, *session)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing snapshot with GMS for session %s: %w", sessionUid, err)
|
||||
}
|
||||
|
||||
// create new directory for snapshot writing
|
||||
snapshotUid := util.GenerateShortUID()
|
||||
dir := filepath.Join("cloudmigration.snapshots", fmt.Sprintf("snapshot-%s-%s", snapshotUid, initResp.GMSSnapshotUID))
|
||||
err = os.MkdirAll(dir, 0750)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating snapshot directory: %w", err)
|
||||
}
|
||||
|
||||
// save snapshot to the db
|
||||
snapshot := cloudmigration.CloudMigrationSnapshot{
|
||||
UID: snapshotUid,
|
||||
SessionUID: sessionUid,
|
||||
Status: cloudmigration.SnapshotStatusInitializing,
|
||||
EncryptionKey: initResp.EncryptionKey,
|
||||
UploadURL: initResp.UploadURL,
|
||||
GMSSnapshotUID: initResp.GMSSnapshotUID,
|
||||
LocalDir: dir,
|
||||
}
|
||||
|
||||
uid, err := s.store.CreateSnapshot(ctx, snapshot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("saving snapshot: %w", err)
|
||||
}
|
||||
snapshot.UID = uid
|
||||
|
||||
// start building the snapshot asynchronously while we return a success response to the client
|
||||
go s.buildSnapshot(context.Background(), snapshot)
|
||||
|
||||
return &snapshot, nil
|
||||
}
|
||||
|
||||
// GetSnapshot returns the on-prem version of a snapshot, supplemented with processing status from GMS
|
||||
func (s *Service) GetSnapshot(ctx context.Context, sessionUid string, snapshotUid string) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.GetSnapshot")
|
||||
defer span.End()
|
||||
|
||||
snapshot, err := s.store.GetSnapshotByUID(ctx, snapshotUid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching snapshot for uid %s: %w", snapshotUid, err)
|
||||
}
|
||||
|
||||
session, err := s.store.GetMigrationSessionByUID(ctx, sessionUid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching session for uid %s: %w", sessionUid, err)
|
||||
}
|
||||
|
||||
if snapshot.ShouldQueryGMS() {
|
||||
// ask GMS for status if it's in the cloud
|
||||
snapshotMeta, err := s.gmsClient.GetSnapshotStatus(ctx, *session, *snapshot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching snapshot status from GMS: sessionUid: %s, snapshotUid: %s", sessionUid, snapshotUid)
|
||||
}
|
||||
|
||||
// grab any result available
|
||||
// TODO: figure out a more intelligent way to do this, will depend on GMS apis
|
||||
snapshot.Result = snapshotMeta.Result
|
||||
|
||||
if snapshotMeta.Status == cloudmigration.SnapshotStatusFinished {
|
||||
// we need to update the snapshot in our db before reporting anything finished to the client
|
||||
if err := s.store.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{
|
||||
UID: snapshot.UID,
|
||||
Status: cloudmigration.SnapshotStatusFinished,
|
||||
Result: snapshot.Result,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("error updating snapshot status: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.GetSnapshotList")
|
||||
defer span.End()
|
||||
|
||||
snapshotList, err := s.store.GetSnapshotList(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching snapshots for session uid %s: %w", query.SessionUID, err)
|
||||
}
|
||||
return snapshotList, nil
|
||||
}
|
||||
|
||||
func (s *Service) UploadSnapshot(ctx context.Context, sessionUid string, snapshotUid string) error {
|
||||
ctx, span := s.tracer.Start(ctx, "CloudMigrationService.UploadSnapshot")
|
||||
defer span.End()
|
||||
|
||||
snapshot, err := s.GetSnapshot(ctx, sessionUid, snapshotUid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching snapshot with uid %s: %w", snapshotUid, err)
|
||||
}
|
||||
|
||||
s.log.Info("Uploading snapshot with GMS ID %s in local directory %s to url %s", snapshot.GMSSnapshotUID, snapshot.LocalDir, snapshot.UploadURL)
|
||||
s.log.Debug("UploadSnapshot not yet implemented, faking it")
|
||||
|
||||
// start uploading the snapshot asynchronously while we return a success response to the client
|
||||
go s.uploadSnapshot(context.Background(), *snapshot)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) parseCloudMigrationConfig() (string, error) {
|
||||
if s.cfg == nil {
|
||||
return "", fmt.Errorf("cfg cannot be nil")
|
||||
|
||||
@@ -44,7 +44,7 @@ func (s *NoopServiceImpl) GetMigrationStatus(ctx context.Context, runUID string)
|
||||
return nil, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
func (s *NoopServiceImpl) GetMigrationRunList(ctx context.Context, uid string) (*cloudmigration.SnapshotList, error) {
|
||||
func (s *NoopServiceImpl) GetMigrationRunList(ctx context.Context, uid string) (*cloudmigration.CloudMigrationRunList, error) {
|
||||
return nil, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
@@ -59,3 +59,19 @@ func (s *NoopServiceImpl) CreateMigrationRun(context.Context, cloudmigration.Clo
|
||||
func (s *NoopServiceImpl) RunMigration(context.Context, string) (*cloudmigration.MigrateDataResponse, error) {
|
||||
return nil, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
func (s *NoopServiceImpl) CreateSnapshot(ctx context.Context, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
return nil, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
func (s *NoopServiceImpl) GetSnapshot(ctx context.Context, sessionUid string, snapshotUid string) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
return nil, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
func (s *NoopServiceImpl) GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error) {
|
||||
return nil, cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
func (s *NoopServiceImpl) UploadSnapshot(ctx context.Context, sessionUid string, snapshotUid string) error {
|
||||
return cloudmigration.ErrFeatureDisabledError
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration"
|
||||
"github.com/grafana/grafana/pkg/services/gcom"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var fixedDate = time.Date(2024, 6, 5, 17, 30, 40, 0, time.UTC)
|
||||
@@ -122,14 +123,61 @@ func (m FakeServiceImpl) GetMigrationStatus(_ context.Context, _ string) (*cloud
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m FakeServiceImpl) GetMigrationRunList(_ context.Context, _ string) (*cloudmigration.SnapshotList, error) {
|
||||
func (m FakeServiceImpl) GetMigrationRunList(_ context.Context, _ string) (*cloudmigration.CloudMigrationRunList, error) {
|
||||
if m.ReturnError {
|
||||
return nil, fmt.Errorf("mock error")
|
||||
}
|
||||
return &cloudmigration.SnapshotList{
|
||||
return &cloudmigration.CloudMigrationRunList{
|
||||
Runs: []cloudmigration.MigrateDataResponseList{
|
||||
{RunUID: "fake_run_uid_1"},
|
||||
{RunUID: "fake_run_uid_2"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m FakeServiceImpl) CreateSnapshot(ctx context.Context, sessionUid string) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
if m.ReturnError {
|
||||
return nil, fmt.Errorf("mock error")
|
||||
}
|
||||
return &cloudmigration.CloudMigrationSnapshot{
|
||||
UID: util.GenerateShortUID(),
|
||||
SessionUID: sessionUid,
|
||||
Status: cloudmigration.SnapshotStatusUnknown,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m FakeServiceImpl) GetSnapshot(ctx context.Context, sessionUid string, snapshotUid string) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
if m.ReturnError {
|
||||
return nil, fmt.Errorf("mock error")
|
||||
}
|
||||
return &cloudmigration.CloudMigrationSnapshot{
|
||||
UID: util.GenerateShortUID(),
|
||||
SessionUID: sessionUid,
|
||||
Status: cloudmigration.SnapshotStatusUnknown,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m FakeServiceImpl) GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error) {
|
||||
if m.ReturnError {
|
||||
return nil, fmt.Errorf("mock error")
|
||||
}
|
||||
return []cloudmigration.CloudMigrationSnapshot{
|
||||
{
|
||||
UID: util.GenerateShortUID(),
|
||||
SessionUID: query.SessionUID,
|
||||
Status: cloudmigration.SnapshotStatusUnknown,
|
||||
},
|
||||
{
|
||||
UID: util.GenerateShortUID(),
|
||||
SessionUID: query.SessionUID,
|
||||
Status: cloudmigration.SnapshotStatusUnknown,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m FakeServiceImpl) UploadSnapshot(ctx context.Context, sessionUid string, snapshotUid string) error {
|
||||
if m.ReturnError {
|
||||
return fmt.Errorf("mock error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
package cloudmigrationimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/util/retryer"
|
||||
)
|
||||
|
||||
func (s *Service) getMigrationDataJSON(ctx context.Context) (*cloudmigration.MigrateDataRequest, error) {
|
||||
// Data sources
|
||||
dataSources, err := s.getDataSources(ctx)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get datasources", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Dashboards
|
||||
dashboards, err := s.getDashboards(ctx)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get dashboards", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Folders
|
||||
folders, err := s.getFolders(ctx)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get folders", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
migrationDataSlice := make(
|
||||
[]cloudmigration.MigrateDataRequestItem, 0,
|
||||
len(dataSources)+len(dashboards)+len(folders),
|
||||
)
|
||||
|
||||
for _, ds := range dataSources {
|
||||
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
|
||||
Type: cloudmigration.DatasourceDataType,
|
||||
RefID: ds.UID,
|
||||
Name: ds.Name,
|
||||
Data: ds,
|
||||
})
|
||||
}
|
||||
|
||||
for _, dashboard := range dashboards {
|
||||
dashboard.Data.Del("id")
|
||||
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
|
||||
Type: cloudmigration.DashboardDataType,
|
||||
RefID: dashboard.UID,
|
||||
Name: dashboard.Title,
|
||||
Data: map[string]any{"dashboard": dashboard.Data},
|
||||
})
|
||||
}
|
||||
|
||||
for _, f := range folders {
|
||||
migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{
|
||||
Type: cloudmigration.FolderDataType,
|
||||
RefID: f.UID,
|
||||
Name: f.Title,
|
||||
Data: f,
|
||||
})
|
||||
}
|
||||
|
||||
migrationData := &cloudmigration.MigrateDataRequest{
|
||||
Items: migrationDataSlice,
|
||||
}
|
||||
|
||||
return migrationData, nil
|
||||
}
|
||||
|
||||
func (s *Service) getDataSources(ctx context.Context) ([]datasources.AddDataSourceCommand, error) {
|
||||
dataSources, err := s.dsService.GetAllDataSources(ctx, &datasources.GetAllDataSourcesQuery{})
|
||||
if err != nil {
|
||||
s.log.Error("Failed to get all datasources", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []datasources.AddDataSourceCommand{}
|
||||
for _, dataSource := range dataSources {
|
||||
// Decrypt secure json to send raw credentials
|
||||
decryptedData, err := s.secretsService.DecryptJsonData(ctx, dataSource.SecureJsonData)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to decrypt secure json data", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
dataSourceCmd := datasources.AddDataSourceCommand{
|
||||
OrgID: dataSource.OrgID,
|
||||
Name: dataSource.Name,
|
||||
Type: dataSource.Type,
|
||||
Access: dataSource.Access,
|
||||
URL: dataSource.URL,
|
||||
User: dataSource.User,
|
||||
Database: dataSource.Database,
|
||||
BasicAuth: dataSource.BasicAuth,
|
||||
BasicAuthUser: dataSource.BasicAuthUser,
|
||||
WithCredentials: dataSource.WithCredentials,
|
||||
IsDefault: dataSource.IsDefault,
|
||||
JsonData: dataSource.JsonData,
|
||||
SecureJsonData: decryptedData,
|
||||
ReadOnly: dataSource.ReadOnly,
|
||||
UID: dataSource.UID,
|
||||
}
|
||||
result = append(result, dataSourceCmd)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *Service) getFolders(ctx context.Context) ([]folder.Folder, error) {
|
||||
reqCtx := contexthandler.FromContext(ctx)
|
||||
folders, err := s.folderService.GetFolders(ctx, folder.GetFoldersQuery{
|
||||
SignedInUser: reqCtx.SignedInUser,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]folder.Folder, len(folders))
|
||||
for i, folder := range folders {
|
||||
result[i] = *folder
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) getDashboards(ctx context.Context) ([]dashboards.Dashboard, error) {
|
||||
dashs, err := s.dashboardService.GetAllDashboards(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]dashboards.Dashboard, len(dashs))
|
||||
for i, dashboard := range dashs {
|
||||
result[i] = *dashboard
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// asynchronous process for writing the snapshot to the filesystem and updating the snapshot status
|
||||
func (s *Service) buildSnapshot(ctx context.Context, snapshotMeta cloudmigration.CloudMigrationSnapshot) {
|
||||
// TODO -- make sure we can only build one snapshot at a time
|
||||
s.buildSnapshotMutex.Lock()
|
||||
defer s.buildSnapshotMutex.Unlock()
|
||||
s.buildSnapshotError = false
|
||||
|
||||
// update snapshot status to creating, add some retries since this is a background task
|
||||
if err := retryer.Retry(func() (retryer.RetrySignal, error) {
|
||||
err := s.store.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{
|
||||
UID: snapshotMeta.UID,
|
||||
Status: cloudmigration.SnapshotStatusCreating,
|
||||
})
|
||||
return retryer.FuncComplete, err
|
||||
}, 10, time.Millisecond*100, time.Second*10); err != nil {
|
||||
s.log.Error("failed to set snapshot status to 'creating'", "err", err)
|
||||
s.buildSnapshotError = true
|
||||
return
|
||||
}
|
||||
|
||||
// build snapshot
|
||||
// just sleep for now to simulate snapshot creation happening
|
||||
// need to do a couple of fancy things when we implement this:
|
||||
// - some sort of regular check-in so we know we haven't timed out
|
||||
// - a channel to listen for cancel events
|
||||
// - retries baked into the snapshot writing process?
|
||||
s.log.Debug("snapshot meta", "snapshot", snapshotMeta)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// update snapshot status to pending upload with retry
|
||||
if err := retryer.Retry(func() (retryer.RetrySignal, error) {
|
||||
err := s.store.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{
|
||||
UID: snapshotMeta.UID,
|
||||
Status: cloudmigration.SnapshotStatusPendingUpload,
|
||||
})
|
||||
return retryer.FuncComplete, err
|
||||
}, 10, time.Millisecond*100, time.Second*10); err != nil {
|
||||
s.log.Error("failed to set snapshot status to 'pending upload'", "err", err)
|
||||
s.buildSnapshotError = true
|
||||
}
|
||||
}
|
||||
|
||||
// asynchronous process for and updating the snapshot status
|
||||
func (s *Service) uploadSnapshot(ctx context.Context, snapshotMeta cloudmigration.CloudMigrationSnapshot) {
|
||||
// TODO -- make sure we can only upload one snapshot at a time
|
||||
s.buildSnapshotMutex.Lock()
|
||||
defer s.buildSnapshotMutex.Unlock()
|
||||
s.buildSnapshotError = false
|
||||
|
||||
// update snapshot status to uploading, add some retries since this is a background task
|
||||
if err := retryer.Retry(func() (retryer.RetrySignal, error) {
|
||||
err := s.store.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{
|
||||
UID: snapshotMeta.UID,
|
||||
Status: cloudmigration.SnapshotStatusUploading,
|
||||
})
|
||||
return retryer.FuncComplete, err
|
||||
}, 10, time.Millisecond*100, time.Second*10); err != nil {
|
||||
s.log.Error("failed to set snapshot status to 'creating'", "err", err)
|
||||
s.buildSnapshotError = true
|
||||
return
|
||||
}
|
||||
|
||||
// upload snapshot
|
||||
// just sleep for now to simulate snapshot creation happening
|
||||
s.log.Debug("snapshot meta", "snapshot", snapshotMeta)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// update snapshot status to pending processing with retry
|
||||
if err := retryer.Retry(func() (retryer.RetrySignal, error) {
|
||||
err := s.store.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{
|
||||
UID: snapshotMeta.UID,
|
||||
Status: cloudmigration.SnapshotStatusPendingProcessing,
|
||||
})
|
||||
return retryer.FuncComplete, err
|
||||
}, 10, time.Millisecond*100, time.Second*10); err != nil {
|
||||
s.log.Error("failed to set snapshot status to 'pending upload'", "err", err)
|
||||
s.buildSnapshotError = true
|
||||
}
|
||||
|
||||
// simulate the rest
|
||||
// processing
|
||||
time.Sleep(3 * time.Second)
|
||||
if err := s.store.UpdateSnapshot(ctx, cloudmigration.UpdateSnapshotCmd{
|
||||
UID: snapshotMeta.UID,
|
||||
Status: cloudmigration.SnapshotStatusProcessing,
|
||||
}); err != nil {
|
||||
s.log.Error("updating snapshot", "err", err)
|
||||
}
|
||||
// end here as the GetSnapshot handler will fill in the rest when called
|
||||
}
|
||||
@@ -15,4 +15,9 @@ type store interface {
|
||||
CreateMigrationRun(ctx context.Context, cmr cloudmigration.CloudMigrationSnapshot) (string, error)
|
||||
GetMigrationStatus(ctx context.Context, cmrUID string) (*cloudmigration.CloudMigrationSnapshot, error)
|
||||
GetMigrationStatusList(ctx context.Context, migrationUID string) ([]*cloudmigration.CloudMigrationSnapshot, error)
|
||||
|
||||
CreateSnapshot(ctx context.Context, snapshot cloudmigration.CloudMigrationSnapshot) (string, error)
|
||||
UpdateSnapshot(ctx context.Context, snapshot cloudmigration.UpdateSnapshotCmd) error
|
||||
GetSnapshotByUID(ctx context.Context, uid string) (*cloudmigration.CloudMigrationSnapshot, error)
|
||||
GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error)
|
||||
}
|
||||
|
||||
@@ -146,6 +146,106 @@ func (ss *sqlStore) GetMigrationStatusList(ctx context.Context, migrationUID str
|
||||
return runs, nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) CreateSnapshot(ctx context.Context, snapshot cloudmigration.CloudMigrationSnapshot) (string, error) {
|
||||
if err := ss.encryptKey(ctx, &snapshot); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if snapshot.Result == nil {
|
||||
snapshot.Result = make([]byte, 0)
|
||||
}
|
||||
if snapshot.UID == "" {
|
||||
snapshot.UID = util.GenerateShortUID()
|
||||
}
|
||||
|
||||
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
snapshot.Created = time.Now()
|
||||
snapshot.Updated = time.Now()
|
||||
snapshot.UID = util.GenerateShortUID()
|
||||
|
||||
_, err := sess.Insert(&snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return snapshot.UID, nil
|
||||
}
|
||||
|
||||
// UpdateSnapshot takes a snapshot object containing a uid and updates a subset of features in the database.
|
||||
func (ss *sqlStore) UpdateSnapshot(ctx context.Context, update cloudmigration.UpdateSnapshotCmd) error {
|
||||
if update.UID == "" {
|
||||
return fmt.Errorf("missing snapshot uid")
|
||||
}
|
||||
err := ss.db.InTransaction(ctx, func(ctx context.Context) error {
|
||||
// Update status if set
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
if update.Status != "" {
|
||||
rawSQL := "UPDATE cloud_migration_snapshot SET status=? WHERE uid=?"
|
||||
if _, err := sess.Exec(rawSQL, update.Status, update.UID); err != nil {
|
||||
return fmt.Errorf("updating snapshot status for uid %s: %w", update.UID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update result if set
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
if len(update.Result) > 0 {
|
||||
rawSQL := "UPDATE cloud_migration_snapshot SET result=? WHERE uid=?"
|
||||
if _, err := sess.Exec(rawSQL, update.Result, update.UID); err != nil {
|
||||
return fmt.Errorf("updating snapshot result for uid %s: %w", update.UID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetSnapshotByUID(ctx context.Context, uid string) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
var snapshot cloudmigration.CloudMigrationSnapshot
|
||||
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
exist, err := sess.Where("uid=?", uid).Get(&snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return cloudmigration.ErrSnapshotNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := ss.decryptKey(ctx, &snapshot); err != nil {
|
||||
return &snapshot, err
|
||||
}
|
||||
|
||||
return &snapshot, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetSnapshotList(ctx context.Context, query cloudmigration.ListSnapshotsQuery) ([]cloudmigration.CloudMigrationSnapshot, error) {
|
||||
var runs = make([]cloudmigration.CloudMigrationSnapshot, 0)
|
||||
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
sess.Limit(query.Limit, query.Offset)
|
||||
return sess.Find(&runs, &cloudmigration.CloudMigrationSnapshot{
|
||||
SessionUID: query.SessionUID,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return runs, nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) encryptToken(ctx context.Context, cm *cloudmigration.CloudMigrationSession) error {
|
||||
s, err := ss.secretsService.Encrypt(ctx, []byte(cm.AuthToken), secrets.WithoutScope())
|
||||
if err != nil {
|
||||
@@ -171,3 +271,29 @@ func (ss *sqlStore) decryptToken(ctx context.Context, cm *cloudmigration.CloudMi
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) encryptKey(ctx context.Context, snapshot *cloudmigration.CloudMigrationSnapshot) error {
|
||||
s, err := ss.secretsService.Encrypt(ctx, []byte(snapshot.EncryptionKey), secrets.WithoutScope())
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypting key: %w", err)
|
||||
}
|
||||
|
||||
snapshot.EncryptionKey = base64.StdEncoding.EncodeToString(s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) decryptKey(ctx context.Context, snapshot *cloudmigration.CloudMigrationSnapshot) error {
|
||||
decoded, err := base64.StdEncoding.DecodeString(snapshot.EncryptionKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("key could not be decoded")
|
||||
}
|
||||
|
||||
t, err := ss.secretsService.Decrypt(ctx, decoded)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypting key: %w", err)
|
||||
}
|
||||
snapshot.EncryptionKey = string(t)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -152,7 +152,6 @@ func Test_GetMigrationStatusList(t *testing.T) {
|
||||
list, err := s.GetMigrationStatusList(ctx, "qwerty")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(list))
|
||||
// TODO validate that this is ok
|
||||
})
|
||||
|
||||
t.Run("returns no error if migration was not found, just empty list", func(t *testing.T) {
|
||||
@@ -188,11 +187,11 @@ func setUpTest(t *testing.T) (*sqlstore.SQLStore, *sqlStore) {
|
||||
// insert cloud migration run test data
|
||||
_, err = testDB.GetSqlxSession().Exec(ctx, `
|
||||
INSERT INTO
|
||||
cloud_migration_snapshot (session_uid, uid, result, created, updated, finished)
|
||||
cloud_migration_snapshot (session_uid, uid, result, created, updated, finished, status)
|
||||
VALUES
|
||||
('qwerty', 'poiuy', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000'),
|
||||
('qwerty', 'lkjhg', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000'),
|
||||
('zxcvbn', 'mnbvvc', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000');
|
||||
('qwerty', 'poiuy', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000', "finished"),
|
||||
('qwerty', 'lkjhg', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000', "finished"),
|
||||
('zxcvbn', 'mnbvvc', ?, '2024-03-25 15:30:36.000', '2024-03-27 15:30:43.000', '2024-03-27 15:30:43.000', "finished");
|
||||
`,
|
||||
[]byte("ERROR"),
|
||||
[]byte("OK"),
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
type Client interface {
|
||||
ValidateKey(context.Context, cloudmigration.CloudMigrationSession) error
|
||||
MigrateData(context.Context, cloudmigration.CloudMigrationSession, cloudmigration.MigrateDataRequest) (*cloudmigration.MigrateDataResponse, error)
|
||||
InitializeSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.InitializeSnapshotResponse, error)
|
||||
GetSnapshotStatus(context.Context, cloudmigration.CloudMigrationSession, cloudmigration.CloudMigrationSnapshot) (*cloudmigration.CloudMigrationSnapshot, error)
|
||||
}
|
||||
|
||||
const logPrefix = "cloudmigration.gmsclient"
|
||||
|
||||
@@ -111,6 +111,14 @@ func (c *gmsClientImpl) MigrateData(ctx context.Context, cm cloudmigration.Cloud
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *gmsClientImpl) InitializeSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.InitializeSnapshotResponse, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *gmsClientImpl) GetSnapshotStatus(context.Context, cloudmigration.CloudMigrationSession, cloudmigration.CloudMigrationSnapshot) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func convertRequestToDTO(request cloudmigration.MigrateDataRequest) MigrateDataRequestDTO {
|
||||
items := make([]MigrateDataRequestItemDTO, len(request.Items))
|
||||
for i := 0; i < len(request.Items); i++ {
|
||||
|
||||
@@ -2,9 +2,12 @@ package gmsclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// NewInMemoryClient returns an implementation of Client that returns canned responses
|
||||
@@ -12,7 +15,9 @@ func NewInMemoryClient() Client {
|
||||
return &memoryClientImpl{}
|
||||
}
|
||||
|
||||
type memoryClientImpl struct{}
|
||||
type memoryClientImpl struct {
|
||||
snapshot *cloudmigration.InitializeSnapshotResponse
|
||||
}
|
||||
|
||||
func (c *memoryClientImpl) ValidateKey(ctx context.Context, cm cloudmigration.CloudMigrationSession) error {
|
||||
return nil
|
||||
@@ -43,3 +48,50 @@ func (c *memoryClientImpl) MigrateData(
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *memoryClientImpl) InitializeSnapshot(context.Context, cloudmigration.CloudMigrationSession) (*cloudmigration.InitializeSnapshotResponse, error) {
|
||||
c.snapshot = &cloudmigration.InitializeSnapshotResponse{
|
||||
EncryptionKey: util.GenerateShortUID(),
|
||||
GMSSnapshotUID: util.GenerateShortUID(),
|
||||
UploadURL: "localhost:3000",
|
||||
}
|
||||
|
||||
return c.snapshot, nil
|
||||
}
|
||||
|
||||
func (c *memoryClientImpl) GetSnapshotStatus(ctx context.Context, session cloudmigration.CloudMigrationSession, snapshot cloudmigration.CloudMigrationSnapshot) (*cloudmigration.CloudMigrationSnapshot, error) {
|
||||
// just fake an entire response
|
||||
gmsSnapshot := cloudmigration.CloudMigrationSnapshot{
|
||||
Status: cloudmigration.SnapshotStatusFinished,
|
||||
GMSSnapshotUID: util.GenerateShortUID(),
|
||||
Result: []byte{},
|
||||
Finished: time.Now(),
|
||||
}
|
||||
|
||||
result := []cloudmigration.MigrateDataResponseItem{
|
||||
{
|
||||
Type: cloudmigration.DashboardDataType,
|
||||
RefID: util.GenerateShortUID(),
|
||||
Status: cloudmigration.ItemStatusOK,
|
||||
},
|
||||
{
|
||||
Type: cloudmigration.DatasourceDataType,
|
||||
RefID: util.GenerateShortUID(),
|
||||
Status: cloudmigration.ItemStatusError,
|
||||
Error: "fake error",
|
||||
},
|
||||
{
|
||||
Type: cloudmigration.FolderDataType,
|
||||
RefID: util.GenerateShortUID(),
|
||||
Status: cloudmigration.ItemStatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gmsSnapshot.Result = b
|
||||
|
||||
return &gmsSnapshot, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ var (
|
||||
ErrMigrationRunNotFound = errutil.NotFound("cloudmigrations.migrationRunNotFound").Errorf("Migration run not found")
|
||||
ErrMigrationNotDeleted = errutil.Internal("cloudmigrations.sessionNotDeleted").Errorf("Session not deleted")
|
||||
ErrTokenNotFound = errutil.NotFound("cloudmigrations.tokenNotFound").Errorf("Token not found")
|
||||
ErrSnapshotNotFound = errutil.NotFound("cloudmigrations.snapshotNotFound").Errorf("Snapshot not found")
|
||||
)
|
||||
|
||||
// CloudMigration domain structs
|
||||
@@ -31,26 +32,64 @@ type CloudMigrationSession struct {
|
||||
}
|
||||
|
||||
type CloudMigrationSnapshot struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
UID string `xorm:"uid"`
|
||||
SessionUID string `xorm:"session_uid"`
|
||||
Result []byte //store raw gms response body
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
Finished time.Time
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
UID string `xorm:"uid"`
|
||||
SessionUID string `xorm:"session_uid"`
|
||||
Status SnapshotStatus
|
||||
EncryptionKey string `xorm:"encryption_key"` // stored in the unified secrets table
|
||||
UploadURL string `xorm:"upload_url"`
|
||||
LocalDir string `xorm:"local_directory"`
|
||||
GMSSnapshotUID string `xorm:"gms_snapshot_uid"`
|
||||
ErrorString string `xorm:"error_string"`
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
Finished time.Time
|
||||
|
||||
// []MigrateDataResponseItem
|
||||
Result []byte `xorm:"result"` //store raw gms response body
|
||||
}
|
||||
|
||||
func (r CloudMigrationSnapshot) GetResult() (*MigrateDataResponse, error) {
|
||||
type SnapshotStatus string
|
||||
|
||||
const (
|
||||
SnapshotStatusInitializing = "initializing"
|
||||
SnapshotStatusCreating = "creating"
|
||||
SnapshotStatusPendingUpload = "pending_upload"
|
||||
SnapshotStatusUploading = "uploading"
|
||||
SnapshotStatusPendingProcessing = "pending_processing"
|
||||
SnapshotStatusProcessing = "processing"
|
||||
SnapshotStatusFinished = "finished"
|
||||
SnapshotStatusError = "error"
|
||||
SnapshotStatusUnknown = "unknown"
|
||||
)
|
||||
|
||||
// Deprecated, use GetSnapshotResult for the async workflow
|
||||
func (s CloudMigrationSnapshot) GetResult() (*MigrateDataResponse, error) {
|
||||
var result MigrateDataResponse
|
||||
err := json.Unmarshal(r.Result, &result)
|
||||
err := json.Unmarshal(s.Result, &result)
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse result of run")
|
||||
}
|
||||
result.RunUID = r.UID
|
||||
result.RunUID = s.UID
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
type SnapshotList struct {
|
||||
func (s CloudMigrationSnapshot) ShouldQueryGMS() bool {
|
||||
return s.Status == SnapshotStatusPendingProcessing || s.Status == SnapshotStatusProcessing
|
||||
}
|
||||
|
||||
func (s CloudMigrationSnapshot) GetSnapshotResult() ([]MigrateDataResponseItem, error) {
|
||||
var result []MigrateDataResponseItem
|
||||
if len(s.Result) > 0 {
|
||||
err := json.Unmarshal(s.Result, &result)
|
||||
if err != nil {
|
||||
return nil, errors.New("could not parse result of run")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type CloudMigrationRunList struct {
|
||||
Runs []MigrateDataResponseList
|
||||
}
|
||||
|
||||
@@ -69,6 +108,18 @@ type CloudMigrationSessionListResponse struct {
|
||||
Sessions []CloudMigrationSessionResponse
|
||||
}
|
||||
|
||||
type ListSnapshotsQuery struct {
|
||||
SessionUID string
|
||||
Offset int
|
||||
Limit int
|
||||
}
|
||||
|
||||
type UpdateSnapshotCmd struct {
|
||||
UID string
|
||||
Status SnapshotStatus
|
||||
Result []byte //store raw gms response body
|
||||
}
|
||||
|
||||
// access token
|
||||
|
||||
type CreateAccessTokenResponse struct {
|
||||
@@ -140,3 +191,13 @@ type MigrateDataResponseItem struct {
|
||||
Status ItemStatus
|
||||
Error string
|
||||
}
|
||||
|
||||
type CreateSessionResponse struct {
|
||||
SnapshotUid string
|
||||
}
|
||||
|
||||
type InitializeSnapshotResponse struct {
|
||||
EncryptionKey string
|
||||
UploadURL string
|
||||
GMSSnapshotUID string
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ var (
|
||||
AllowSelfServe: false,
|
||||
RequiresRestart: true,
|
||||
},
|
||||
FeatureFlag{
|
||||
{
|
||||
Name: "disableClassicHTTPHistogram",
|
||||
Description: "Disables classic HTTP Histogram (use with enableNativeHTTPHistogram)",
|
||||
Stage: FeatureStageExperimental,
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
func addCloudMigrationsMigrations(mg *Migrator) {
|
||||
// v1 - synchronous workflow
|
||||
// --- v1 - synchronous workflow
|
||||
migrationTable := Table{
|
||||
Name: "cloud_migration",
|
||||
Columns: []*Column{
|
||||
@@ -65,7 +65,7 @@ func addCloudMigrationsMigrations(mg *Migrator) {
|
||||
Cols: []string{"uid"}, Type: UniqueIndex,
|
||||
}))
|
||||
|
||||
// v2 - asynchronous workflow refactor
|
||||
// --- v2 - asynchronous workflow refactor
|
||||
sessionTable := Table{
|
||||
Name: "cloud_migration_session",
|
||||
Columns: []*Column{
|
||||
@@ -120,4 +120,23 @@ func addCloudMigrationsMigrations(mg *Migrator) {
|
||||
"updated": "updated",
|
||||
"finished": "finished",
|
||||
})
|
||||
|
||||
// --- add new columns to snapshots table
|
||||
uploadUrlColumn := Column{Name: "upload_url", Type: DB_Text, Nullable: true}
|
||||
mg.AddMigration("add snapshot upload_url column", NewAddColumnMigration(migrationSnapshotTable, &uploadUrlColumn))
|
||||
|
||||
statusColumn := Column{Name: "status", Type: DB_Text, Nullable: false}
|
||||
mg.AddMigration("add snapshot status column", NewAddColumnMigration(migrationSnapshotTable, &statusColumn))
|
||||
|
||||
localDirColumn := Column{Name: "local_directory", Type: DB_Text, Nullable: true}
|
||||
mg.AddMigration("add snapshot local_directory column", NewAddColumnMigration(migrationSnapshotTable, &localDirColumn))
|
||||
|
||||
gmsSnapshotUIDColumn := Column{Name: "gms_snapshot_uid", Type: DB_Text, Nullable: true}
|
||||
mg.AddMigration("add snapshot gms_snapshot_uid column", NewAddColumnMigration(migrationSnapshotTable, &gmsSnapshotUIDColumn))
|
||||
|
||||
encryptionKeyColumn := Column{Name: "encryption_key", Type: DB_Text, Nullable: true}
|
||||
mg.AddMigration("add snapshot encryption_key column", NewAddColumnMigration(migrationSnapshotTable, &encryptionKeyColumn))
|
||||
|
||||
errorStringColumn := Column{Name: "error_string", Type: DB_Text, Nullable: true}
|
||||
mg.AddMigration("add snapshot error_string column", NewAddColumnMigration(migrationSnapshotTable, &errorStringColumn))
|
||||
}
|
||||
|
||||
@@ -3159,6 +3159,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CloudMigrationRunListDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"runs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/MigrateDataResponseListDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"CloudMigrationSessionListResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -3632,6 +3643,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateSnapshotResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateTeamCommand": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4726,6 +4745,45 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"GetSnapshotResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"finished": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/MigrateDataResponseItemDTO"
|
||||
}
|
||||
},
|
||||
"sessionUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"INITIALIZING",
|
||||
"CREATING",
|
||||
"PENDING_UPLOAD",
|
||||
"UPLOADING",
|
||||
"PENDING_PROCESSING",
|
||||
"PROCESSING",
|
||||
"FINISHED",
|
||||
"ERROR",
|
||||
"UNKNOWN"
|
||||
]
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Hit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7084,13 +7142,47 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"SnapshotListDTO": {
|
||||
"SnapshotDTO": {
|
||||
"description": "Base snapshot without results",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"runs": {
|
||||
"created": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"finished": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"sessionUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"INITIALIZING",
|
||||
"CREATING",
|
||||
"PENDING_UPLOAD",
|
||||
"UPLOADING",
|
||||
"PENDING_PROCESSING",
|
||||
"PROCESSING",
|
||||
"FINISHED",
|
||||
"ERROR",
|
||||
"UNKNOWN"
|
||||
]
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SnapshotListResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"snapshots": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/MigrateDataResponseListDTO"
|
||||
"$ref": "#/definitions/SnapshotDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8394,7 +8486,7 @@
|
||||
"cloudMigrationRunListResponse": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SnapshotListDTO"
|
||||
"$ref": "#/definitions/CloudMigrationRunListDTO"
|
||||
}
|
||||
},
|
||||
"cloudMigrationRunResponse": {
|
||||
@@ -8559,6 +8651,12 @@
|
||||
"$ref": "#/definitions/ServiceAccountDTO"
|
||||
}
|
||||
},
|
||||
"createSnapshotResponse": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/CreateSnapshotResponseDTO"
|
||||
}
|
||||
},
|
||||
"createTeamResponse": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
@@ -9126,6 +9224,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"getSnapshotResponse": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/GetSnapshotResponseDTO"
|
||||
}
|
||||
},
|
||||
"getStatusResponse": {
|
||||
"description": ""
|
||||
},
|
||||
@@ -9582,6 +9686,12 @@
|
||||
"$ref": "#/definitions/RoleAssignmentsDTO"
|
||||
}
|
||||
},
|
||||
"snapshotListResponse": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SnapshotListResponseDTO"
|
||||
}
|
||||
},
|
||||
"unauthorisedError": {
|
||||
"description": "UnauthorizedError is returned when the request is not authenticated.",
|
||||
"schema": {
|
||||
|
||||
+349
-4
@@ -2336,6 +2336,9 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/cloudMigrationSessionResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
@@ -2368,6 +2371,9 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/cloudMigrationRunResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
@@ -2400,6 +2406,9 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/cloudMigrationSessionResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
@@ -2427,6 +2436,9 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
@@ -2459,6 +2471,9 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/cloudMigrationRunListResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
@@ -2490,6 +2505,223 @@
|
||||
"200": {
|
||||
"$ref": "#/responses/cloudMigrationRunResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/internalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot": {
|
||||
"post": {
|
||||
"description": "If the snapshot initialization is successful, the snapshot uid is returned.",
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"summary": "Trigger the creation of an instance snapshot associated with the provided session.",
|
||||
"operationId": "createSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "UID of a session",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/createSnapshotResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/internalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot/{snapshotUid}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"summary": "Get metadata about a snapshot, including where it is in its processing and final results.",
|
||||
"operationId": "getSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session UID of a session",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "UID of a snapshot",
|
||||
"name": "snapshotUid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/getSnapshotResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/internalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot/{snapshotUid}/cancel": {
|
||||
"post": {
|
||||
"description": "TODO: Implement",
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"summary": "Cancel a snapshot, wherever it is in its processing chain.",
|
||||
"operationId": "cancelSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session UID of a session",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "UID of a snapshot",
|
||||
"name": "snapshotUid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "(empty)"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/internalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot/{snapshotUid}/upload": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"summary": "Upload a snapshot to the Grafana Migration Service for processing.",
|
||||
"operationId": "uploadSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session UID of a session",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "UID of a snapshot",
|
||||
"name": "snapshotUid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "(empty)"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/internalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshots": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"summary": "Get a list of snapshots for a session.",
|
||||
"operationId": "getShapshotList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"default": 0,
|
||||
"description": "Offset is used for pagination with limit",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"default": 100,
|
||||
"description": "Max limit for results returned.",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Session UID of a session",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/snapshotListResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
@@ -2569,6 +2801,9 @@
|
||||
"204": {
|
||||
"$ref": "#/responses/cloudMigrationDeleteTokenResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
@@ -13336,6 +13571,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CloudMigrationRunListDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"runs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/MigrateDataResponseListDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"CloudMigrationSessionListResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13877,6 +14123,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateSnapshotResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateTeamCommand": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15270,6 +15524,45 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"GetSnapshotResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"finished": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/MigrateDataResponseItemDTO"
|
||||
}
|
||||
},
|
||||
"sessionUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"INITIALIZING",
|
||||
"CREATING",
|
||||
"PENDING_UPLOAD",
|
||||
"UPLOADING",
|
||||
"PENDING_PROCESSING",
|
||||
"PROCESSING",
|
||||
"FINISHED",
|
||||
"ERROR",
|
||||
"UNKNOWN"
|
||||
]
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GettableAlertmanagers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -19985,13 +20278,47 @@
|
||||
"SmtpNotEnabled": {
|
||||
"$ref": "#/definitions/ResponseDetails"
|
||||
},
|
||||
"SnapshotListDTO": {
|
||||
"SnapshotDTO": {
|
||||
"description": "Base snapshot without results",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"runs": {
|
||||
"created": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"finished": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"sessionUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"INITIALIZING",
|
||||
"CREATING",
|
||||
"PENDING_UPLOAD",
|
||||
"UPLOADING",
|
||||
"PENDING_PROCESSING",
|
||||
"PROCESSING",
|
||||
"FINISHED",
|
||||
"ERROR",
|
||||
"UNKNOWN"
|
||||
]
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SnapshotListResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"snapshots": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/MigrateDataResponseListDTO"
|
||||
"$ref": "#/definitions/SnapshotDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22434,7 +22761,7 @@
|
||||
"cloudMigrationRunListResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SnapshotListDTO"
|
||||
"$ref": "#/definitions/CloudMigrationRunListDTO"
|
||||
}
|
||||
},
|
||||
"cloudMigrationRunResponse": {
|
||||
@@ -22599,6 +22926,12 @@
|
||||
"$ref": "#/definitions/ServiceAccountDTO"
|
||||
}
|
||||
},
|
||||
"createSnapshotResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/CreateSnapshotResponseDTO"
|
||||
}
|
||||
},
|
||||
"createTeamResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
@@ -23166,6 +23499,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"getSnapshotResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/GetSnapshotResponseDTO"
|
||||
}
|
||||
},
|
||||
"getStatusResponse": {
|
||||
"description": "(empty)"
|
||||
},
|
||||
@@ -23631,6 +23970,12 @@
|
||||
"$ref": "#/definitions/RoleAssignmentsDTO"
|
||||
}
|
||||
},
|
||||
"snapshotListResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SnapshotListResponseDTO"
|
||||
}
|
||||
},
|
||||
"unauthorisedError": {
|
||||
"description": "UnauthorizedError is returned when the request is not authenticated.",
|
||||
"schema": {
|
||||
|
||||
+381
-4
@@ -200,7 +200,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SnapshotListDTO"
|
||||
"$ref": "#/components/schemas/CloudMigrationRunListDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -424,6 +424,16 @@
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"createSnapshotResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateSnapshotResponseDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"createTeamResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@@ -1251,6 +1261,16 @@
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"getSnapshotResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetSnapshotResponseDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"getStatusResponse": {
|
||||
"description": "(empty)"
|
||||
},
|
||||
@@ -1912,6 +1932,16 @@
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"snapshotListResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SnapshotListResponseDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"unauthorisedError": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@@ -3683,6 +3713,17 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CloudMigrationRunListDTO": {
|
||||
"properties": {
|
||||
"runs": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MigrateDataResponseListDTO"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CloudMigrationSessionListResponseDTO": {
|
||||
"properties": {
|
||||
"sessions": {
|
||||
@@ -4224,6 +4265,14 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CreateSnapshotResponseDTO": {
|
||||
"properties": {
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CreateTeamCommand": {
|
||||
"properties": {
|
||||
"email": {
|
||||
@@ -5617,6 +5666,45 @@
|
||||
],
|
||||
"title": "Get home dashboard response."
|
||||
},
|
||||
"GetSnapshotResponseDTO": {
|
||||
"properties": {
|
||||
"created": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"finished": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"results": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MigrateDataResponseItemDTO"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"sessionUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"enum": [
|
||||
"INITIALIZING",
|
||||
"CREATING",
|
||||
"PENDING_UPLOAD",
|
||||
"UPLOADING",
|
||||
"PENDING_PROCESSING",
|
||||
"PROCESSING",
|
||||
"FINISHED",
|
||||
"ERROR",
|
||||
"UNKNOWN"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableAlertmanagers": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@@ -10331,11 +10419,45 @@
|
||||
"SmtpNotEnabled": {
|
||||
"$ref": "#/components/schemas/ResponseDetails"
|
||||
},
|
||||
"SnapshotListDTO": {
|
||||
"SnapshotDTO": {
|
||||
"description": "Base snapshot without results",
|
||||
"properties": {
|
||||
"runs": {
|
||||
"created": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"finished": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"enum": [
|
||||
"INITIALIZING",
|
||||
"CREATING",
|
||||
"PENDING_UPLOAD",
|
||||
"UPLOADING",
|
||||
"PENDING_PROCESSING",
|
||||
"PROCESSING",
|
||||
"FINISHED",
|
||||
"ERROR",
|
||||
"UNKNOWN"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SnapshotListResponseDTO": {
|
||||
"properties": {
|
||||
"snapshots": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MigrateDataResponseListDTO"
|
||||
"$ref": "#/components/schemas/SnapshotDTO"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
@@ -15165,6 +15287,9 @@
|
||||
"200": {
|
||||
"$ref": "#/components/responses/cloudMigrationSessionResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
@@ -15199,6 +15324,9 @@
|
||||
"200": {
|
||||
"$ref": "#/components/responses/cloudMigrationRunResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
@@ -15230,6 +15358,9 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
@@ -15262,6 +15393,9 @@
|
||||
"200": {
|
||||
"$ref": "#/components/responses/cloudMigrationSessionResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
@@ -15296,6 +15430,9 @@
|
||||
"200": {
|
||||
"$ref": "#/components/responses/cloudMigrationRunListResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
@@ -15329,6 +15466,9 @@
|
||||
"200": {
|
||||
"$ref": "#/components/responses/cloudMigrationRunResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
@@ -15345,6 +15485,240 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot": {
|
||||
"post": {
|
||||
"description": "If the snapshot initialization is successful, the snapshot uid is returned.",
|
||||
"operationId": "createSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UID of a session",
|
||||
"in": "path",
|
||||
"name": "uid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/createSnapshotResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/internalServerError"
|
||||
}
|
||||
},
|
||||
"summary": "Trigger the creation of an instance snapshot associated with the provided session.",
|
||||
"tags": [
|
||||
"migrations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot/{snapshotUid}": {
|
||||
"get": {
|
||||
"operationId": "getSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Session UID of a session",
|
||||
"in": "path",
|
||||
"name": "uid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "UID of a snapshot",
|
||||
"in": "path",
|
||||
"name": "snapshotUid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/getSnapshotResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/internalServerError"
|
||||
}
|
||||
},
|
||||
"summary": "Get metadata about a snapshot, including where it is in its processing and final results.",
|
||||
"tags": [
|
||||
"migrations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot/{snapshotUid}/cancel": {
|
||||
"post": {
|
||||
"description": "TODO: Implement",
|
||||
"operationId": "cancelSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Session UID of a session",
|
||||
"in": "path",
|
||||
"name": "uid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "UID of a snapshot",
|
||||
"in": "path",
|
||||
"name": "snapshotUid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "(empty)"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/internalServerError"
|
||||
}
|
||||
},
|
||||
"summary": "Cancel a snapshot, wherever it is in its processing chain.",
|
||||
"tags": [
|
||||
"migrations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshot/{snapshotUid}/upload": {
|
||||
"post": {
|
||||
"operationId": "uploadSnapshot",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Session UID of a session",
|
||||
"in": "path",
|
||||
"name": "uid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "UID of a snapshot",
|
||||
"in": "path",
|
||||
"name": "snapshotUid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "(empty)"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/internalServerError"
|
||||
}
|
||||
},
|
||||
"summary": "Upload a snapshot to the Grafana Migration Service for processing.",
|
||||
"tags": [
|
||||
"migrations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration/{uid}/snapshots": {
|
||||
"get": {
|
||||
"operationId": "getShapshotList",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Offset is used for pagination with limit",
|
||||
"in": "query",
|
||||
"name": "offset",
|
||||
"schema": {
|
||||
"default": 0,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Max limit for results returned.",
|
||||
"in": "query",
|
||||
"name": "limit",
|
||||
"schema": {
|
||||
"default": 100,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Session UID of a session",
|
||||
"in": "path",
|
||||
"name": "uid",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/snapshotListResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbiddenError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/internalServerError"
|
||||
}
|
||||
},
|
||||
"summary": "Get a list of snapshots for a session.",
|
||||
"tags": [
|
||||
"migrations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/cloudmigration/token": {
|
||||
"get": {
|
||||
"operationId": "getCloudMigrationToken",
|
||||
@@ -15410,6 +15784,9 @@
|
||||
"204": {
|
||||
"$ref": "#/components/responses/cloudMigrationDeleteTokenResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/badRequestError"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user