From c3fb296fa2c9f9cc541c792fe76c096aa646baea Mon Sep 17 00:00:00 2001 From: Rohit-PX Date: Tue, 6 Dec 2022 21:15:30 +0000 Subject: [PATCH 1/7] API for exporting stats Signed-off-by: Rohit-PX --- pkg/utils/export_stats.go | 218 ++++++++++++++++++ test/integration_test/common_test.go | 12 +- test/integration_test/export_stats_test.go | 60 +++++ test/integration_test/migration_stats_test.go | 106 +++++++++ test/integration_test/migration_test.go | 2 +- .../elasticsearch-migration/migration.yaml | 14 ++ test/integration_test/specs/stats.tmpl | 52 +++++ 7 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 pkg/utils/export_stats.go create mode 100644 test/integration_test/export_stats_test.go create mode 100644 test/integration_test/migration_stats_test.go create mode 100644 test/integration_test/specs/elasticsearch-migration/migration.yaml create mode 100644 test/integration_test/specs/stats.tmpl diff --git a/pkg/utils/export_stats.go b/pkg/utils/export_stats.go new file mode 100644 index 0000000000..b91d48df05 --- /dev/null +++ b/pkg/utils/export_stats.go @@ -0,0 +1,218 @@ +package utils + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" + + storkv1 "github.com/libopenstorage/stork/pkg/apis/stork/v1alpha1" + "github.com/sirupsen/logrus" +) + +const ( + AetosStatsURL = "http://aetos.pwx.purestorage.com/dashboard/stats?stats_type=MigrationDemoFinal&limit=100" +) + +var StorkVersion string +var PortworxVersion string + +type StatsExportType struct { + id OidType `json: "_id",omitempty` + Name string `json: "name",omitempty` + Product string `json: "product",omitempty` + Version string `json: "version",omitempty` + StatsType string `json: "statsType",omitempty` + Data MigrationStatsType `json: "data",omitempty` +} + +type MigrationStatsType struct { + CreatedOn string `json: "created",omitempty` + TotalNumberOfVolumes json.Number `json: "totalNumberOfVolumes",omitempty` + NumOfMigratedVolumes json.Number `json: "numOfMigratedVolumes",omitempty` + TotalNumberOfResources json.Number `json: "totalNumberOfResources",omitempty` + NumOfMigratedResources json.Number `json: "numOfMigratedResources",omitempty` + TotalBytesMigrated json.Number `json: "totalBytesMigrated",omitempty` + ElapsedTimeForVolumeMigration string `json: "elapsedTimeForVolumeMigration",omitempty` + ElapsedTimeForResourceMigration string `json: "elapsedTimeForResourceMigration",omitempty` + Application string `json: "application",omitempty` + StorkVersion string `json: "storkVersion",omitempty` + PortworxVersion string `json: "portworxVersion",omitempty` +} + +type OidType struct { + oid string `json: "$oid"` +} + +type AllStats []StatsExportType + +func GetMigrationStatsFromAetos(url string) ([]StatsExportType, error) { + client := http.Client{Timeout: time.Second * 3} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error querying Aetos for stats: %v", err) + } + + req.Header.Add("Content-Type", "application/json") + + q := req.URL.Query() + q.Add("format", "json") + req.URL.RawQuery = q.Encode() + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("error querying Aetos metadata: %v", err) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("error querying Aetos metadata: Code %d returned for url %s", resp.StatusCode, req.URL) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error querying Aetos metadata: %v", err) + } + if len(body) == 0 { + return nil, fmt.Errorf("error querying Aetos metadata: Empty response") + } + + //fmt.Printf("\nBody of response: %v\n\n", string(body)) + data := AllStats{} + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("error parsing Aetos metadata: %v", err) + } + + defer func() { + err := resp.Body.Close() + if err != nil { + logrus.Errorf("Error closing body when getting Aetos data: %v", err) + } + }() + return data, nil +} + +func WriteMigrationStatsToAetos(data StatsExportType) error { + body, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("failed to marshal: %v", err) + } + resp, err := http.Post("http://aetos.pwx.purestorage.com/dashboard/stats", "application/json", bytes.NewBuffer(body)) + if err != nil { + return fmt.Errorf("post request to Aetos failed: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + //Failed to read response. + return fmt.Errorf("response from Aetos failed: %v", err) + } + + jsonStr := string(body) + logrus.Infof("Stats successfully pushed to DB. Response: ", jsonStr) + } else { + return fmt.Errorf("Get failed with Reponse status: %s", resp.Status) + } + + return nil +} + +func MockStat() StatsExportType { + mockStat := StatsExportType{ + Name: "stork_integration_test", + Product: "stork", + StatsType: "migration_stats_mock", + Version: "v1alpha1", + Data: MigrationStatsType{ + TotalNumberOfVolumes: "1", + NumOfMigratedVolumes: "1", + TotalNumberOfResources: "5", + NumOfMigratedResources: "1", + TotalBytesMigrated: "12345", + ElapsedTimeForVolumeMigration: "11.1111s", + ElapsedTimeForResourceMigration: "12321.3453s", + Application: "mock_app", + StorkVersion: StorkVersion, + PortworxVersion: PortworxVersion, + }, + } + return mockStat +} + +func NewStat() StatsExportType { + newStat := StatsExportType{ + Name: "stork_integration_test", + Product: "stork", + StatsType: "MigrationDemoFinal", + Version: "v1alpha1", + Data: MigrationStatsType{ + TotalNumberOfVolumes: "", + Application: "", + NumOfMigratedVolumes: "", + TotalNumberOfResources: "", + NumOfMigratedResources: "", + TotalBytesMigrated: "", + ElapsedTimeForVolumeMigration: "", + ElapsedTimeForResourceMigration: "", + StorkVersion: StorkVersion, + PortworxVersion: PortworxVersion, + }, + } + return newStat +} + +func GetExportableStatsFromMigrationObject(mig *storkv1.Migration) StatsExportType { + exportStats := NewStat() + + exportStats.Data.Application = getResourceNamesFromMigration(mig) + + exportStats.Data.CreatedOn = (mig.GetCreationTimestamp()).Format("2006-01-02 15:04:05") + + exportStats.Data.TotalNumberOfVolumes = json.Number(strconv.FormatUint(mig.Status.Summary.TotalNumberOfVolumes, 10)) + + exportStats.Data.NumOfMigratedVolumes = json.Number(strconv.FormatUint(mig.Status.Summary.NumberOfMigratedVolumes, 10)) + + exportStats.Data.TotalNumberOfResources = json.Number(strconv.FormatUint(mig.Status.Summary.TotalNumberOfResources, 10)) + + exportStats.Data.NumOfMigratedResources = json.Number(strconv.FormatUint(mig.Status.Summary.NumberOfMigratedResources, 10)) + + exportStats.Data.TotalBytesMigrated = json.Number(strconv.FormatUint(mig.Status.Summary.TotalBytesMigrated, 10)) + + exportStats.Data.ElapsedTimeForVolumeMigration = mig.Status.Summary.ElapsedTimeForVolumeMigration + + exportStats.Data.ElapsedTimeForResourceMigration = mig.Status.Summary.ElapsedTimeForResourceMigration + + PrettyStruct(exportStats) + + return exportStats + +} + +func PrettyStruct(data interface{}) { + val, err := json.MarshalIndent(data, "", " ") + if err != nil { + logrus.Panicf("Failed to prettify data") + } + fmt.Printf("Exportable migration data: %v", string(val)) +} + +func getResourceNamesFromMigration(mig *storkv1.Migration) string { + var resourceList []string + for _, resource := range mig.Status.Resources { + if resource.Kind == "Deployment" || resource.Kind == "StatefulSet" { + resourceList = append(resourceList, resource.Name) + } + } + if len(resourceList) > 1 { + // return comma separated list of apps if there are multiple apps + return strings.Join(resourceList, ",") + } else if len(resourceList) == 1 { + return resourceList[0] + } + + logrus.Info("App name not found for pushing to DB.") + return "" +} diff --git a/test/integration_test/common_test.go b/test/integration_test/common_test.go index 1973bd7435..3f3b03b077 100644 --- a/test/integration_test/common_test.go +++ b/test/integration_test/common_test.go @@ -26,6 +26,7 @@ import ( storkv1 "github.com/libopenstorage/stork/pkg/apis/stork/v1alpha1" "github.com/libopenstorage/stork/pkg/schedule" "github.com/libopenstorage/stork/pkg/storkctl" + "github.com/libopenstorage/stork/pkg/utils" "github.com/libopenstorage/stork/pkg/version" "github.com/portworx/sched-ops/k8s/apps" "github.com/portworx/sched-ops/k8s/batch" @@ -152,10 +153,11 @@ var backupLocationPath string var genericCsiConfigMap string var externalTest bool var storkVersionCheck bool +var storkVersion string +var pxVersion string var cloudDeletionValidate bool var isInternalLBAws bool var pxNamespace string -var storkVersion string var testrailHostname string var testrailUsername string var testrailPassword string @@ -275,12 +277,20 @@ func setup() error { return fmt.Errorf("stork version not found in configmap: %s", cmName) } storkVersion = getStorkVersion(ver) + utils.StorkVersion = ver + if storkVersionCheck == true { if getStorkVersion(ver) != getStorkVersion(version.Version) { return fmt.Errorf("stork version mismatch, found: %s, expected: %s", getStorkVersion(ver), getStorkVersion(version.Version)) } } } + stc, err := operator.Instance().ListStorageClusters("kube-system") + if err != nil { + logrus.Warnf("failed to list PX storage cluster during setup: %v, probably a daemonset install for portworx", err) + } else { + utils.PortworxVersion = oputils.GetPortworxVersion(&stc.Items[0]).Original() + } isInternalLBAws, err = strconv.ParseBool(os.Getenv(internalLBAws)) if err == nil { diff --git a/test/integration_test/export_stats_test.go b/test/integration_test/export_stats_test.go new file mode 100644 index 0000000000..5bcf1a04bc --- /dev/null +++ b/test/integration_test/export_stats_test.go @@ -0,0 +1,60 @@ +//go:build integrationtest +// +build integrationtest + +package integrationtest + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/libopenstorage/stork/pkg/utils" +) + +const ( + aetosStatsURL = "http://aetos.pwx.purestorage.com/dashboard/stats?stats_type=migration_stats_new&limit=100" +) + +func TestExportStatsGetStats(t *testing.T) { + fmt.Println("Hello") + data, err := utils.GetMigrationStatsFromAetos(aetosStatsURL) + require.NoError(t, err, "Failed to get stats: %v") + + prettyData, err := PrettyStruct(data) + require.NoError(t, err, "Failed to print pretty data: %v") + fmt.Println(prettyData) +} + +func TestExportStatsPushMockStats(t *testing.T) { + err := utils.WriteMigrationStatsToAetos(NewStat()) + require.NoError(t, err, "Failed to write stats: %v") +} + +func NewStat() utils.StatsExportType { + mockStat := utils.StatsExportType{ + Name: "stork_integration_test", + Product: "stork", + StatsType: "migration_stats_mock", + Version: "v1alpha1", + Data: utils.MigrationStatsType{ + TotalNumberOfVolumes: "1", + NumOfMigratedVolumes: "1", + TotalNumberOfResources: "5", + NumOfMigratedResources: "1", + TotalBytesMigrated: "12345", + ElapsedTimeForVolumeMigration: "11.1111s", + ElapsedTimeForResourceMigration: "12321.3453s", + }, + } + return mockStat +} + +func PrettyStruct(data interface{}) (string, error) { + val, err := json.MarshalIndent(data, "", " ") + if err != nil { + return "", err + } + return string(val), nil +} diff --git a/test/integration_test/migration_stats_test.go b/test/integration_test/migration_stats_test.go new file mode 100644 index 0000000000..75982c0aa8 --- /dev/null +++ b/test/integration_test/migration_stats_test.go @@ -0,0 +1,106 @@ +//go:build integrationtest +// +build integrationtest + +package integrationtest + +import ( + "html/template" + "os" + "testing" + + "github.com/libopenstorage/stork/pkg/apis/stork/v1alpha1" + "github.com/libopenstorage/stork/pkg/utils" + storkops "github.com/portworx/sched-ops/k8s/stork" + "github.com/portworx/torpedo/drivers/scheduler" + "github.com/stretchr/testify/require" +) + +type MigrateApps struct { + MigrationKey string + MigrationAppKey string + IncludeVolumes bool + IncludeResources bool + StartApplications bool +} + +var appList = []MigrateApps{ + {"mysql-migration", "mysql-1-pvc", true, true, false}, + {"cassandra-migration", "cassandra", true, true, false}, + {"elasticsearch-migration", "elasticsearch", true, true, false}, +} + +func TestMigrationExportStats(t *testing.T) { + t.Run("migrationStatsExportTest", migrationStatsExportTest) + t.Run("migrationStatsDisplayTest", migrationStatsDisplayTest) +} + +func migrationStatsExportTest(t *testing.T) { + var err error + var includeVolumes = true + // Reset config in case of error + defer func() { + err = setSourceKubeConfig() + require.NoError(t, err, "Error resetting source config") + }() + + for _, app := range appList { + currCtxs, err := schedulerDriver.Schedule(app.MigrationKey, + scheduler.ScheduleOptions{AppKeys: []string{app.MigrationAppKey}}) + require.NoError(t, err, "Error scheduling task") + require.Equal(t, 1, len(currCtxs), "Only one task should have started") + + err = schedulerDriver.WaitForRunning(currCtxs[0], defaultWaitTimeout, defaultWaitInterval) + require.NoError(t, err, "Error waiting for app to get to running state") + + // create, apply and validate cluster pair specs + err = scheduleClusterPair(currCtxs[0], false, true, defaultClusterPairDir, "", false) + require.NoError(t, err, "Error scheduling cluster pair") + + currMigNamespace := app.MigrationAppKey + "-" + app.MigrationKey + currMig, err := createMigration(t, app.MigrationKey, currMigNamespace, "remoteclusterpair", currMigNamespace, &app.IncludeResources, &includeVolumes, &app.StartApplications) + require.NoError(t, err, "failed to create migration: %s in namespace %s", app.MigrationKey, currMigNamespace) + + migrationList := []*v1alpha1.Migration{currMig} + err = WaitForMigration(migrationList) + require.NoError(t, err, "error in migrations for exported stats") + + finishedMig, err := storkops.Instance().GetMigration(currMig.Name, currMig.Namespace) + require.Nil(t, err, "could not get completed migration") + + exportableStat := utils.GetExportableStatsFromMigrationObject(finishedMig) + require.NotNil(t, exportableStat, "could not fetch stats from migration object") + + // Export stats to Aetos Mongo DB + err = utils.WriteMigrationStatsToAetos(exportableStat) + require.NoError(t, err, "error while writing stats to mongo DB") + + } +} + +func migrationStatsDisplayTest(t *testing.T) { + var err error + // Get stats from Aetos Mongo DB + stats, err := utils.GetMigrationStatsFromAetos(utils.AetosStatsURL) + require.NoError(t, err, "error while fetching stats from mongo DB") + + // Print stats to the console for now + utils.PrettyStruct(stats) + + paths := []string{ + "/specs/stats.tmpl", + } + + // create a new file + //err := os.MkdirAll("/reports", 0777) + htmlStatsFile := "/reports/migstats.html" + file, err := os.Create(htmlStatsFile) + require.NoError(t, err, "error during creation of html stats file: %s", htmlStatsFile) + + templ := template.Must(template.ParseFiles(paths...)) + err = templ.Execute(file, stats) + if err != nil { + panic(err) + } + err = file.Close() + require.NoError(t, err, "error during closure of html stats file: %s", htmlStatsFile) +} diff --git a/test/integration_test/migration_test.go b/test/integration_test/migration_test.go index 88e743f45b..ac0d5bdc2e 100644 --- a/test/integration_test/migration_test.go +++ b/test/integration_test/migration_test.go @@ -1402,7 +1402,7 @@ func WaitForMigration(migrationList []*v1alpha1.Migration) error { return "", false, err } if mig.Status.Status != v1alpha1.MigrationStatusSuccessful { - logrus.Infof("Migration %s in namespace %s is pending", m.Name, m.Namespace) + logrus.Infof("Migration %s in namespace %s is pending: %v", m.Name, m.Namespace, mig.Status.Status) isComplete = false } } diff --git a/test/integration_test/specs/elasticsearch-migration/migration.yaml b/test/integration_test/specs/elasticsearch-migration/migration.yaml new file mode 100644 index 0000000000..6d15c1b321 --- /dev/null +++ b/test/integration_test/specs/elasticsearch-migration/migration.yaml @@ -0,0 +1,14 @@ +apiVersion: stork.libopenstorage.org/v1alpha1 +kind: Migration +metadata: + name: elasticsearch-migration +spec: + # This should be the name of the cluster pair + clusterPair: remoteclusterpair + # If set to false this will migrate only the volumes. No PVCs, apps, etc will be migrated + includeResources: true + # If set to false, the deployments and stateful set replicas will be set to 0 on the destination. There will be an annotation with "stork.openstorage.org/migrationReplicas" to store the replica count from the source + startApplications: true + # List of namespaces to migrate + namespaces: + - elasticsearch-elasticsearch-migration diff --git a/test/integration_test/specs/stats.tmpl b/test/integration_test/specs/stats.tmpl new file mode 100644 index 0000000000..98c3b8b5ea --- /dev/null +++ b/test/integration_test/specs/stats.tmpl @@ -0,0 +1,52 @@ + + + + Go To-Do list + + +

+ DR Migration Stats: +

+ + + + + +
+
+
+

+ Migration Statistics +

+ + + + + + + + + + + + + + + {{ range $idx, $item := . }} + + + + + + + + + + + + + + {{ end }} +
Migration Start timeStork VersionPortworx VersionApplicationTotal VolumesMigrated VolumesTotal ResourcesMigrated ResourcesTotal Bytes MigratedElapsed time for Volume MigrationElapsed time for Resource Migration
{{ $item.Data.CreatedOn }}{{ $item.Data.StorkVersion }}{{ $item.Data.PortworxVersion }}{{ $item.Data.Application }}{{ $item.Data.TotalNumberOfVolumes }}{{ $item.Data.NumOfMigratedVolumes }}{{ $item.Data.TotalNumberOfResources }}{{ $item.Data.NumOfMigratedResources }}{{ $item.Data.TotalBytesMigrated }}{{ $item.Data.ElapsedTimeForVolumeMigration }}{{ $item.Data.ElapsedTimeForResourceMigration }}
+ + From 0fe1d55b3e642467dee71181d8c186529420e931 Mon Sep 17 00:00:00 2001 From: Rohit-PX Date: Thu, 6 Jul 2023 23:45:53 +0000 Subject: [PATCH 2/7] staticcheck fixes Signed-off-by: Rohit-PX --- pkg/utils/export_stats.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/utils/export_stats.go b/pkg/utils/export_stats.go index b91d48df05..c4cbddf1ff 100644 --- a/pkg/utils/export_stats.go +++ b/pkg/utils/export_stats.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "strconv" "strings" @@ -22,7 +22,7 @@ var StorkVersion string var PortworxVersion string type StatsExportType struct { - id OidType `json: "_id",omitempty` + //id OidType `json: "_id",omitempty` Name string `json: "name",omitempty` Product string `json: "product",omitempty` Version string `json: "version",omitempty` @@ -44,9 +44,9 @@ type MigrationStatsType struct { PortworxVersion string `json: "portworxVersion",omitempty` } -type OidType struct { - oid string `json: "$oid"` -} +//type OidType struct { +// oid string `json: "$oid"` +//} type AllStats []StatsExportType @@ -70,7 +70,7 @@ func GetMigrationStatsFromAetos(url string) ([]StatsExportType, error) { if resp.StatusCode != 200 { return nil, fmt.Errorf("error querying Aetos metadata: Code %d returned for url %s", resp.StatusCode, req.URL) } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("error querying Aetos metadata: %v", err) } @@ -105,7 +105,7 @@ func WriteMigrationStatsToAetos(data StatsExportType) error { } defer resp.Body.Close() if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK { - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { //Failed to read response. return fmt.Errorf("response from Aetos failed: %v", err) @@ -114,7 +114,7 @@ func WriteMigrationStatsToAetos(data StatsExportType) error { jsonStr := string(body) logrus.Infof("Stats successfully pushed to DB. Response: ", jsonStr) } else { - return fmt.Errorf("Get failed with Reponse status: %s", resp.Status) + return fmt.Errorf("get failed with Reponse status: %s", resp.Status) } return nil From 265b038d0a0837346be38b2e28429d92a040816c Mon Sep 17 00:00:00 2001 From: Rohit-PX Date: Mon, 10 Jul 2023 19:54:26 +0000 Subject: [PATCH 3/7] get migration object from context Signed-off-by: Rohit-PX --- pkg/utils/export_stats.go | 81 ++++++++++++------- test/integration_test/common_test.go | 33 ++++++++ test/integration_test/export_stats_test.go | 16 +--- test/integration_test/migration_stats_test.go | 11 +++ test/integration_test/migration_test.go | 5 ++ 5 files changed, 101 insertions(+), 45 deletions(-) diff --git a/pkg/utils/export_stats.go b/pkg/utils/export_stats.go index c4cbddf1ff..5436ec6604 100644 --- a/pkg/utils/export_stats.go +++ b/pkg/utils/export_stats.go @@ -2,6 +2,7 @@ package utils import ( "bytes" + "crypto/tls" "encoding/json" "fmt" "io" @@ -22,31 +23,31 @@ var StorkVersion string var PortworxVersion string type StatsExportType struct { - //id OidType `json: "_id",omitempty` - Name string `json: "name",omitempty` - Product string `json: "product",omitempty` - Version string `json: "version",omitempty` - StatsType string `json: "statsType",omitempty` - Data MigrationStatsType `json: "data",omitempty` + id OidType `json: "_id",omitempty` + Name string `json:"name",omitempty` + Product string `json:"product",omitempty` + Version string `json:"version",omitempty` + StatsType string `json:"statsType",omitempty` + Data MigrationStatsType `json:"data",omitempty` } type MigrationStatsType struct { - CreatedOn string `json: "created",omitempty` - TotalNumberOfVolumes json.Number `json: "totalNumberOfVolumes",omitempty` - NumOfMigratedVolumes json.Number `json: "numOfMigratedVolumes",omitempty` - TotalNumberOfResources json.Number `json: "totalNumberOfResources",omitempty` - NumOfMigratedResources json.Number `json: "numOfMigratedResources",omitempty` - TotalBytesMigrated json.Number `json: "totalBytesMigrated",omitempty` - ElapsedTimeForVolumeMigration string `json: "elapsedTimeForVolumeMigration",omitempty` - ElapsedTimeForResourceMigration string `json: "elapsedTimeForResourceMigration",omitempty` - Application string `json: "application",omitempty` - StorkVersion string `json: "storkVersion",omitempty` - PortworxVersion string `json: "portworxVersion",omitempty` + CreatedOn string `json:"created",omitempty` + TotalNumberOfVolumes json.Number `json:"totalNumberOfVolumes",omitempty` + NumOfMigratedVolumes json.Number `json:"numOfMigratedVolumes",omitempty` + TotalNumberOfResources json.Number `json:"totalNumberOfResources",omitempty` + NumOfMigratedResources json.Number `json:"numOfMigratedResources",omitempty` + TotalBytesMigrated json.Number `json:"totalBytesMigrated",omitempty` + ElapsedTimeForVolumeMigration string `json:"elapsedTimeForVolumeMigration",omitempty` + ElapsedTimeForResourceMigration string `json:"elapsedTimeForResourceMigration",omitempty` + Application string `json:"application",omitempty` + StorkVersion string `json:"storkVersion",omitempty` + PortworxVersion string `json:"portworxVersion",omitempty` } -//type OidType struct { -// oid string `json: "$oid"` -//} +type OidType struct { + oid string `json: "$oid"` +} type AllStats []StatsExportType @@ -99,22 +100,30 @@ func WriteMigrationStatsToAetos(data StatsExportType) error { if err != nil { return fmt.Errorf("failed to marshal: %v", err) } - resp, err := http.Post("http://aetos.pwx.purestorage.com/dashboard/stats", "application/json", bytes.NewBuffer(body)) + + logrus.Infof("RK=> JSON marshal:\n %s", string(body)) + + transCfg := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: transCfg} + + resp, err := client.Post("https://aetos.pwx.purestorage.com/dashboard/stats", "application/json", bytes.NewBuffer(body)) if err != nil { return fmt.Errorf("post request to Aetos failed: %v", err) } defer resp.Body.Close() - if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK { - body, err := io.ReadAll(resp.Body) - if err != nil { - //Failed to read response. - return fmt.Errorf("response from Aetos failed: %v", err) - } + respBody, err := io.ReadAll(resp.Body) + if err != nil { + //Failed to read response. + return fmt.Errorf("response from Aetos failed: %v", err) + } + jsonStr := string(respBody) - jsonStr := string(body) + if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK { logrus.Infof("Stats successfully pushed to DB. Response: ", jsonStr) } else { - return fmt.Errorf("get failed with Reponse status: %s", resp.Status) + return fmt.Errorf("post failed with Reponse status: %s, %s", resp.Status, jsonStr) } return nil @@ -164,9 +173,21 @@ func NewStat() StatsExportType { return newStat } -func GetExportableStatsFromMigrationObject(mig *storkv1.Migration) StatsExportType { +func GetExportableStatsFromMigrationObject(mig *storkv1.Migration, statArgs ...string) StatsExportType { exportStats := NewStat() + for i := 0; i < len(statArgs); i++ { + switch i { + case 0: + exportStats.Name = statArgs[0] + case 1: + exportStats.Product = statArgs[1] + case 2: + exportStats.StatsType = statArgs[2] + case 3: + exportStats.Version = statArgs[3] + } + } exportStats.Data.Application = getResourceNamesFromMigration(mig) exportStats.Data.CreatedOn = (mig.GetCreationTimestamp()).Format("2006-01-02 15:04:05") diff --git a/test/integration_test/common_test.go b/test/integration_test/common_test.go index 3f3b03b077..4bf25326cb 100644 --- a/test/integration_test/common_test.go +++ b/test/integration_test/common_test.go @@ -133,6 +133,9 @@ const ( testrailUserNameVar = "TESTRAIL_USERNAME" testrailPasswordVar = "TESTRAIL_PASSWORD" testrailMilestoneVar = "TESTRAIL_MILESTONE" + + statsExportName = "stork_integration_test" + statsExportProduct = "stork" ) var nodeDriver node.Driver @@ -162,6 +165,7 @@ var testrailHostname string var testrailUsername string var testrailPassword string var testrailSetupSuccessful bool +var exportStats bool func TestSnapshot(t *testing.T) { t.Run("testSnapshot", testSnapshot) @@ -1487,6 +1491,10 @@ func TestMain(m *testing.M) { "stork-version-check", false, "Turn on/off stork version check before running tests. Default off.") + flag.BoolVar(&exportStats, + "export-stats", + true, + "Turn on/off exporting stats to aetos DB. Default on.") flag.Parse() if err := setup(); err != nil { logrus.Errorf("Setup failed with error: %v", err) @@ -1524,3 +1532,28 @@ func updateClusterDomain(t *testing.T, clusterDomains *storkv1.ClusterDomains, a } } } + +func ExportMigrationStats(ctx *scheduler.Context, name string) error { + if !exportStats { + logrus.Infof("Export flag is off, won't export migration stats") + return nil + } + for _, spec := range ctx.App.SpecList { + if obj, ok := spec.(*storkv1.Migration); ok { + logrus.Infof("Found migration object: %s", obj.Name) + mig, err := storkops.Instance().GetMigration(obj.Name, obj.Namespace) + if err != nil { + return fmt.Errorf("Failed to get migration %s in namespace: %s: %v", obj.Name, obj.Namespace, err) + } + //stats := utils.GetExportableStatsFromMigrationObject(mig, statsExportName, statsExportProduct, name, utils.StorkVersion) + stats := utils.GetExportableStatsFromMigrationObject(mig) + err = utils.WriteMigrationStatsToAetos(stats) + if err != nil { + return fmt.Errorf("Failed to write stats: %v", err) + } + } else { + logrus.Info("Not a migration object") + } + } + return nil +} diff --git a/test/integration_test/export_stats_test.go b/test/integration_test/export_stats_test.go index 5bcf1a04bc..6236d3dc56 100644 --- a/test/integration_test/export_stats_test.go +++ b/test/integration_test/export_stats_test.go @@ -33,21 +33,7 @@ func TestExportStatsPushMockStats(t *testing.T) { } func NewStat() utils.StatsExportType { - mockStat := utils.StatsExportType{ - Name: "stork_integration_test", - Product: "stork", - StatsType: "migration_stats_mock", - Version: "v1alpha1", - Data: utils.MigrationStatsType{ - TotalNumberOfVolumes: "1", - NumOfMigratedVolumes: "1", - TotalNumberOfResources: "5", - NumOfMigratedResources: "1", - TotalBytesMigrated: "12345", - ElapsedTimeForVolumeMigration: "11.1111s", - ElapsedTimeForResourceMigration: "12321.3453s", - }, - } + mockStat := utils.StatsExportType{} return mockStat } diff --git a/test/integration_test/migration_stats_test.go b/test/integration_test/migration_stats_test.go index 75982c0aa8..34134e3708 100644 --- a/test/integration_test/migration_stats_test.go +++ b/test/integration_test/migration_stats_test.go @@ -32,6 +32,7 @@ var appList = []MigrateApps{ func TestMigrationExportStats(t *testing.T) { t.Run("migrationStatsExportTest", migrationStatsExportTest) t.Run("migrationStatsDisplayTest", migrationStatsDisplayTest) + t.Run("migrationStatsPostTest", migrationStatsPostTest) } func migrationStatsExportTest(t *testing.T) { @@ -104,3 +105,13 @@ func migrationStatsDisplayTest(t *testing.T) { err = file.Close() require.NoError(t, err, "error during closure of html stats file: %s", htmlStatsFile) } + +func migrationStatsPostTest(t *testing.T) { + migName, migNamespace := "mysql-migration", "mysql-1-pvc-mysql-migration" + mig, err := storkops.Instance().GetMigration(migName, migNamespace) + require.NoError(t, err, "Failed to get migration %s in namespace: %s", migName, migNamespace) + + stats := utils.GetExportableStatsFromMigrationObject(mig) + err = utils.WriteMigrationStatsToAetos(stats) + require.NoError(t, err, "failed to write stats") +} diff --git a/test/integration_test/migration_test.go b/test/integration_test/migration_test.go index ac0d5bdc2e..6e3adbe4fe 100644 --- a/test/integration_test/migration_test.go +++ b/test/integration_test/migration_test.go @@ -113,6 +113,7 @@ func triggerMigrationTest( ctxs, preMigrationCtx := triggerMigration(t, instanceID, appKey, additionalAppKeys, []string{migrationAppKey}, migrateAllAppsExpected, false, startAppsOnMigration, false, "", nil) validateAndDestroyMigration(t, ctxs, preMigrationCtx, migrationSuccessExpected, startAppsOnMigration, migrateAllAppsExpected, false, false) + } func triggerMigration( @@ -249,6 +250,10 @@ func validateAndDestroyMigration( require.Error(t, err, "Expected migration to fail") } + // export migration stats before deleting it + err = ExportMigrationStats(ctxs[0], t.Name()) + require.NoError(t, err, "Error exporting migration stats") + // destroy app on cluster 1 if !skipAppDeletion { destroyAndWait(t, ctxs) From bc6ea81adcc5cc9e511d2e49074d515b11ee05df Mon Sep 17 00:00:00 2001 From: Rohit-PX Date: Tue, 11 Jul 2023 00:17:01 +0000 Subject: [PATCH 4/7] errcheck fixes Signed-off-by: Rohit-PX --- pkg/utils/export_stats.go | 42 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/pkg/utils/export_stats.go b/pkg/utils/export_stats.go index 5436ec6604..4bec5b5e60 100644 --- a/pkg/utils/export_stats.go +++ b/pkg/utils/export_stats.go @@ -23,31 +23,31 @@ var StorkVersion string var PortworxVersion string type StatsExportType struct { - id OidType `json: "_id",omitempty` - Name string `json:"name",omitempty` - Product string `json:"product",omitempty` - Version string `json:"version",omitempty` - StatsType string `json:"statsType",omitempty` - Data MigrationStatsType `json:"data",omitempty` + // id OidType `json: "_id",omitempty` + Name string `json: "name",omitempty` + Product string `json: "product",omitempty` + Version string `json: "version",omitempty` + StatsType string `json: "statsType",omitempty` + Data MigrationStatsType `json: "data",omitempty` } type MigrationStatsType struct { - CreatedOn string `json:"created",omitempty` - TotalNumberOfVolumes json.Number `json:"totalNumberOfVolumes",omitempty` - NumOfMigratedVolumes json.Number `json:"numOfMigratedVolumes",omitempty` - TotalNumberOfResources json.Number `json:"totalNumberOfResources",omitempty` - NumOfMigratedResources json.Number `json:"numOfMigratedResources",omitempty` - TotalBytesMigrated json.Number `json:"totalBytesMigrated",omitempty` - ElapsedTimeForVolumeMigration string `json:"elapsedTimeForVolumeMigration",omitempty` - ElapsedTimeForResourceMigration string `json:"elapsedTimeForResourceMigration",omitempty` - Application string `json:"application",omitempty` - StorkVersion string `json:"storkVersion",omitempty` - PortworxVersion string `json:"portworxVersion",omitempty` + CreatedOn string `json: "created",omitempty` + TotalNumberOfVolumes json.Number `json: "totalNumberOfVolumes",omitempty` + NumOfMigratedVolumes json.Number `json: "numOfMigratedVolumes",omitempty` + TotalNumberOfResources json.Number `json: "totalNumberOfResources",omitempty` + NumOfMigratedResources json.Number `json: "numOfMigratedResources",omitempty` + TotalBytesMigrated json.Number `json: "totalBytesMigrated",omitempty` + ElapsedTimeForVolumeMigration string `json: "elapsedTimeForVolumeMigration",omitempty` + ElapsedTimeForResourceMigration string `json: "elapsedTimeForResourceMigration",omitempty` + Application string `json: "application",omitempty` + StorkVersion string `json: "storkVersion",omitempty` + PortworxVersion string `json: "portworxVersion",omitempty` } -type OidType struct { - oid string `json: "$oid"` -} +//type OidType struct { +// oid string `json: "$oid"` +//} type AllStats []StatsExportType @@ -101,8 +101,6 @@ func WriteMigrationStatsToAetos(data StatsExportType) error { return fmt.Errorf("failed to marshal: %v", err) } - logrus.Infof("RK=> JSON marshal:\n %s", string(body)) - transCfg := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } From 75f1f081792ed03a0994bf790dd66f98d68f6253 Mon Sep 17 00:00:00 2001 From: Rohit-PX Date: Tue, 11 Jul 2023 07:13:15 +0000 Subject: [PATCH 5/7] change to https Signed-off-by: Rohit-PX --- pkg/utils/export_stats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/export_stats.go b/pkg/utils/export_stats.go index 4bec5b5e60..c2b5875f9a 100644 --- a/pkg/utils/export_stats.go +++ b/pkg/utils/export_stats.go @@ -16,7 +16,7 @@ import ( ) const ( - AetosStatsURL = "http://aetos.pwx.purestorage.com/dashboard/stats?stats_type=MigrationDemoFinal&limit=100" + AetosStatsURL = "https://aetos.pwx.purestorage.com/dashboard/stats?stats_type=MigrationDemoFinal&limit=100" ) var StorkVersion string From 894a7381783635c149633aabb052632d57369e8d Mon Sep 17 00:00:00 2001 From: Rohit-PX Date: Tue, 11 Jul 2023 21:41:50 +0000 Subject: [PATCH 6/7] remove spaces from tags Signed-off-by: Rohit-PX --- pkg/utils/export_stats.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/pkg/utils/export_stats.go b/pkg/utils/export_stats.go index c2b5875f9a..0b46f221fb 100644 --- a/pkg/utils/export_stats.go +++ b/pkg/utils/export_stats.go @@ -24,25 +24,25 @@ var PortworxVersion string type StatsExportType struct { // id OidType `json: "_id",omitempty` - Name string `json: "name",omitempty` - Product string `json: "product",omitempty` - Version string `json: "version",omitempty` - StatsType string `json: "statsType",omitempty` - Data MigrationStatsType `json: "data",omitempty` + Name string `json:"name",omitempty` + Product string `json:"product",omitempty` + Version string `json:"version",omitempty` + StatsType string `json:"statsType",omitempty` + Data MigrationStatsType `json:"data",omitempty` } type MigrationStatsType struct { - CreatedOn string `json: "created",omitempty` - TotalNumberOfVolumes json.Number `json: "totalNumberOfVolumes",omitempty` - NumOfMigratedVolumes json.Number `json: "numOfMigratedVolumes",omitempty` - TotalNumberOfResources json.Number `json: "totalNumberOfResources",omitempty` - NumOfMigratedResources json.Number `json: "numOfMigratedResources",omitempty` - TotalBytesMigrated json.Number `json: "totalBytesMigrated",omitempty` - ElapsedTimeForVolumeMigration string `json: "elapsedTimeForVolumeMigration",omitempty` - ElapsedTimeForResourceMigration string `json: "elapsedTimeForResourceMigration",omitempty` - Application string `json: "application",omitempty` - StorkVersion string `json: "storkVersion",omitempty` - PortworxVersion string `json: "portworxVersion",omitempty` + CreatedOn string `json:"created",omitempty` + TotalNumberOfVolumes json.Number `json:"totalNumberOfVolumes",omitempty` + NumOfMigratedVolumes json.Number `json:"numOfMigratedVolumes",omitempty` + TotalNumberOfResources json.Number `json:"totalNumberOfResources",omitempty` + NumOfMigratedResources json.Number `json:"numOfMigratedResources",omitempty` + TotalBytesMigrated json.Number `json:"totalBytesMigrated",omitempty` + ElapsedTimeForVolumeMigration string `json:"elapsedTimeForVolumeMigration",omitempty` + ElapsedTimeForResourceMigration string `json:"elapsedTimeForResourceMigration",omitempty` + Application string `json:"application",omitempty` + StorkVersion string `json:"storkVersion",omitempty` + PortworxVersion string `json:"portworxVersion",omitempty` } //type OidType struct { @@ -100,6 +100,7 @@ func WriteMigrationStatsToAetos(data StatsExportType) error { if err != nil { return fmt.Errorf("failed to marshal: %v", err) } + logrus.Infof("RK=> Marshalled data\n%s\n", body) transCfg := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, From ee715ea30fdc3da705a7e2c109ea8a7684705819 Mon Sep 17 00:00:00 2001 From: Rohit-PX Date: Tue, 11 Jul 2023 23:09:29 +0000 Subject: [PATCH 7/7] formatting directive Signed-off-by: Rohit-PX --- pkg/utils/export_stats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/export_stats.go b/pkg/utils/export_stats.go index 0b46f221fb..8ff76248fa 100644 --- a/pkg/utils/export_stats.go +++ b/pkg/utils/export_stats.go @@ -120,7 +120,7 @@ func WriteMigrationStatsToAetos(data StatsExportType) error { jsonStr := string(respBody) if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK { - logrus.Infof("Stats successfully pushed to DB. Response: ", jsonStr) + logrus.Infof("Stats successfully pushed to DB. Response: %s", jsonStr) } else { return fmt.Errorf("post failed with Reponse status: %s, %s", resp.Status, jsonStr) }