diff --git a/api/docs.go b/api/docs.go index 0376d82..ae2a9f3 100644 --- a/api/docs.go +++ b/api/docs.go @@ -994,6 +994,65 @@ const docTemplate = `{ } } }, + "/api/v1/load/tests/state/last": { + "get": { + "description": "Retrieve a last load test execution state by given ids.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Load Test State Management]" + ], + "summary": "Get Last Load Test Execution State", + "operationId": "GetLastLoadTestExecutionState", + "parameters": [ + { + "type": "string", + "description": "nsId", + "name": "nsId", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "mciId", + "name": "mciId", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Load test key", + "name": "vmId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved load test execution state information", + "schema": { + "$ref": "#/definitions/app.AntResponse-load_LoadTestExecutionStateResult" + } + }, + "400": { + "description": "Invalid request parameters", + "schema": { + "$ref": "#/definitions/app.AntResponse-string" + } + }, + "500": { + "description": "Failed to retrieve load test execution state information", + "schema": { + "$ref": "#/definitions/app.AntResponse-string" + } + } + } + } + }, "/api/v1/load/tests/state/{loadTestKey}": { "get": { "description": "Retrieve a load test execution state by load test key.", @@ -1480,23 +1539,37 @@ const docTemplate = `{ }, "app.RunLoadGeneratorHttpReq": { "type": "object", + "required": [ + "bodyData", + "hostname", + "method", + "path", + "port", + "protocol" + ], "properties": { "bodyData": { + "description": "{\"xxx\": \"tttt\", \"wwwww\": \"wotjkenr\"}", "type": "string" }, "hostname": { + "description": "xx.xx.xx.xx or asx.bbb.com", "type": "string" }, "method": { + "description": "GET or POST", "type": "string" }, "path": { + "description": "/xxx/www/sss or possibly empty", "type": "string" }, "port": { + "description": "1 ~ 65353", "type": "string" }, "protocol": { + "description": "http or https", "type": "string" } } @@ -1505,17 +1578,16 @@ const docTemplate = `{ "type": "object", "properties": { "agentHostname": { + "description": "basically, it is same as host for vm", "type": "string" }, - "agentInstalled": { + "collectAdditionalSystemMetrics": { + "description": "agent tcp default port is 5555", "type": "boolean" }, "duration": { "type": "string" }, - "hostname": { - "type": "string" - }, "httpReqs": { "type": "array", "items": { @@ -1523,12 +1595,23 @@ const docTemplate = `{ } }, "installLoadGenerator": { - "$ref": "#/definitions/app.InstallLoadGeneratorReq" + "description": "localhost for installing load generator; local | remote", + "allOf": [ + { + "$ref": "#/definitions/app.InstallLoadGeneratorReq" + } + ] }, "loadGeneratorInstallInfoId": { + "description": "if already installed load generator simply put this field", "type": "integer" }, - "port": { + "mciId": { + "description": "for metadata usage", + "type": "string" + }, + "nsId": { + "description": "for validate agent host and connect to tumblebug resources", "type": "string" }, "rampUpSteps": { @@ -1538,10 +1621,15 @@ const docTemplate = `{ "type": "string" }, "testName": { + "description": "test scenario", "type": "string" }, "virtualUsers": { "type": "string" + }, + "vmId": { + "description": "for metadata usage", + "type": "string" } } }, @@ -1550,6 +1638,15 @@ const docTemplate = `{ "properties": { "loadTestKey": { "type": "string" + }, + "mciId": { + "type": "string" + }, + "nsId": { + "type": "string" + }, + "vmId": { + "type": "string" } } }, @@ -1631,30 +1728,16 @@ const docTemplate = `{ "constant.ExecutionStatus": { "type": "string", "enum": [ - "on_preparing", - "on_running", + "on_processing", "on_fetching", "successed", - "test_failed", - "update_failed", - "result_failed", - "failed", - "processing", - "fetching", - "success" + "test_failed" ], "x-enum-varnames": [ - "OnPreparing", - "OnRunning", + "OnProcessing", "OnFetching", "Successed", - "TestFailed", - "UpdateFailed", - "ResultFailed", - "Failed", - "Processing", - "Fetching", - "Success" + "TestFailed" ] }, "constant.InstallLocation": { @@ -2128,9 +2211,6 @@ const docTemplate = `{ "executionDuration": { "type": "string" }, - "hostname": { - "type": "string" - }, "id": { "type": "integer" }, @@ -2149,9 +2229,6 @@ const docTemplate = `{ "loadTestKey": { "type": "string" }, - "port": { - "type": "string" - }, "rampUpSteps": { "type": "string" }, diff --git a/api/swagger.json b/api/swagger.json index 601693c..594b3f8 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -986,6 +986,65 @@ } } }, + "/api/v1/load/tests/state/last": { + "get": { + "description": "Retrieve a last load test execution state by given ids.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "[Load Test State Management]" + ], + "summary": "Get Last Load Test Execution State", + "operationId": "GetLastLoadTestExecutionState", + "parameters": [ + { + "type": "string", + "description": "nsId", + "name": "nsId", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "mciId", + "name": "mciId", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Load test key", + "name": "vmId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved load test execution state information", + "schema": { + "$ref": "#/definitions/app.AntResponse-load_LoadTestExecutionStateResult" + } + }, + "400": { + "description": "Invalid request parameters", + "schema": { + "$ref": "#/definitions/app.AntResponse-string" + } + }, + "500": { + "description": "Failed to retrieve load test execution state information", + "schema": { + "$ref": "#/definitions/app.AntResponse-string" + } + } + } + } + }, "/api/v1/load/tests/state/{loadTestKey}": { "get": { "description": "Retrieve a load test execution state by load test key.", @@ -1472,23 +1531,37 @@ }, "app.RunLoadGeneratorHttpReq": { "type": "object", + "required": [ + "bodyData", + "hostname", + "method", + "path", + "port", + "protocol" + ], "properties": { "bodyData": { + "description": "{\"xxx\": \"tttt\", \"wwwww\": \"wotjkenr\"}", "type": "string" }, "hostname": { + "description": "xx.xx.xx.xx or asx.bbb.com", "type": "string" }, "method": { + "description": "GET or POST", "type": "string" }, "path": { + "description": "/xxx/www/sss or possibly empty", "type": "string" }, "port": { + "description": "1 ~ 65353", "type": "string" }, "protocol": { + "description": "http or https", "type": "string" } } @@ -1497,17 +1570,16 @@ "type": "object", "properties": { "agentHostname": { + "description": "basically, it is same as host for vm", "type": "string" }, - "agentInstalled": { + "collectAdditionalSystemMetrics": { + "description": "agent tcp default port is 5555", "type": "boolean" }, "duration": { "type": "string" }, - "hostname": { - "type": "string" - }, "httpReqs": { "type": "array", "items": { @@ -1515,12 +1587,23 @@ } }, "installLoadGenerator": { - "$ref": "#/definitions/app.InstallLoadGeneratorReq" + "description": "localhost for installing load generator; local | remote", + "allOf": [ + { + "$ref": "#/definitions/app.InstallLoadGeneratorReq" + } + ] }, "loadGeneratorInstallInfoId": { + "description": "if already installed load generator simply put this field", "type": "integer" }, - "port": { + "mciId": { + "description": "for metadata usage", + "type": "string" + }, + "nsId": { + "description": "for validate agent host and connect to tumblebug resources", "type": "string" }, "rampUpSteps": { @@ -1530,10 +1613,15 @@ "type": "string" }, "testName": { + "description": "test scenario", "type": "string" }, "virtualUsers": { "type": "string" + }, + "vmId": { + "description": "for metadata usage", + "type": "string" } } }, @@ -1542,6 +1630,15 @@ "properties": { "loadTestKey": { "type": "string" + }, + "mciId": { + "type": "string" + }, + "nsId": { + "type": "string" + }, + "vmId": { + "type": "string" } } }, @@ -1623,30 +1720,16 @@ "constant.ExecutionStatus": { "type": "string", "enum": [ - "on_preparing", - "on_running", + "on_processing", "on_fetching", "successed", - "test_failed", - "update_failed", - "result_failed", - "failed", - "processing", - "fetching", - "success" + "test_failed" ], "x-enum-varnames": [ - "OnPreparing", - "OnRunning", + "OnProcessing", "OnFetching", "Successed", - "TestFailed", - "UpdateFailed", - "ResultFailed", - "Failed", - "Processing", - "Fetching", - "Success" + "TestFailed" ] }, "constant.InstallLocation": { @@ -2120,9 +2203,6 @@ "executionDuration": { "type": "string" }, - "hostname": { - "type": "string" - }, "id": { "type": "integer" }, @@ -2141,9 +2221,6 @@ "loadTestKey": { "type": "string" }, - "port": { - "type": "string" - }, "rampUpSteps": { "type": "string" }, diff --git a/api/swagger.yaml b/api/swagger.yaml index 83317a5..d22e036 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -232,51 +232,81 @@ definitions: app.RunLoadGeneratorHttpReq: properties: bodyData: + description: '{"xxx": "tttt", "wwwww": "wotjkenr"}' type: string hostname: + description: xx.xx.xx.xx or asx.bbb.com type: string method: + description: GET or POST type: string path: + description: /xxx/www/sss or possibly empty type: string port: + description: 1 ~ 65353 type: string protocol: + description: http or https type: string + required: + - bodyData + - hostname + - method + - path + - port + - protocol type: object app.RunLoadTestReq: properties: agentHostname: + description: basically, it is same as host for vm type: string - agentInstalled: + collectAdditionalSystemMetrics: + description: agent tcp default port is 5555 type: boolean duration: type: string - hostname: - type: string httpReqs: items: $ref: '#/definitions/app.RunLoadGeneratorHttpReq' type: array installLoadGenerator: - $ref: '#/definitions/app.InstallLoadGeneratorReq' + allOf: + - $ref: '#/definitions/app.InstallLoadGeneratorReq' + description: localhost for installing load generator; local | remote loadGeneratorInstallInfoId: + description: if already installed load generator simply put this field type: integer - port: + mciId: + description: for metadata usage + type: string + nsId: + description: for validate agent host and connect to tumblebug resources type: string rampUpSteps: type: string rampUpTime: type: string testName: + description: test scenario type: string virtualUsers: type: string + vmId: + description: for metadata usage + type: string type: object app.StopLoadTestReq: properties: loadTestKey: type: string + mciId: + type: string + nsId: + type: string + vmId: + type: string type: object app.UpdateAndGetEstimateCostReq: properties: @@ -329,30 +359,16 @@ definitions: type: object constant.ExecutionStatus: enum: - - on_preparing - - on_running + - on_processing - on_fetching - successed - test_failed - - update_failed - - result_failed - - failed - - processing - - fetching - - success type: string x-enum-varnames: - - OnPreparing - - OnRunning + - OnProcessing - OnFetching - Successed - TestFailed - - UpdateFailed - - ResultFailed - - Failed - - Processing - - Fetching - - Success constant.InstallLocation: enum: - local @@ -666,8 +682,6 @@ definitions: type: string executionDuration: type: string - hostname: - type: string id: type: integer loadGeneratorInstallInfo: @@ -680,8 +694,6 @@ definitions: $ref: '#/definitions/load.LoadTestExecutionStateResult' loadTestKey: type: string - port: - type: string rampUpSteps: type: string rampUpTime: @@ -1530,6 +1542,46 @@ paths: summary: Get Load Test Execution State tags: - '[Load Test State Management]' + /api/v1/load/tests/state/last: + get: + consumes: + - application/json + description: Retrieve a last load test execution state by given ids. + operationId: GetLastLoadTestExecutionState + parameters: + - description: nsId + in: query + name: nsId + required: true + type: string + - description: mciId + in: query + name: mciId + required: true + type: string + - description: Load test key + in: query + name: vmId + required: true + type: string + produces: + - application/json + responses: + "200": + description: Successfully retrieved load test execution state information + schema: + $ref: '#/definitions/app.AntResponse-load_LoadTestExecutionStateResult' + "400": + description: Invalid request parameters + schema: + $ref: '#/definitions/app.AntResponse-string' + "500": + description: Failed to retrieve load test execution state information + schema: + $ref: '#/definitions/app.AntResponse-string' + summary: Get Last Load Test Execution State + tags: + - '[Load Test State Management]' /api/v1/load/tests/stop: post: consumes: diff --git a/internal/app/performance_evaluation_handler.go b/internal/app/evaluate_performance_handler.go similarity index 82% rename from internal/app/performance_evaluation_handler.go rename to internal/app/evaluate_performance_handler.go index fb038e5..6bcbd67 100644 --- a/internal/app/performance_evaluation_handler.go +++ b/internal/app/evaluate_performance_handler.go @@ -9,14 +9,15 @@ import ( "github.com/cloud-barista/cm-ant/internal/core/common/constant" "github.com/cloud-barista/cm-ant/internal/core/load" "github.com/cloud-barista/cm-ant/internal/utils" + "github.com/google/uuid" "github.com/labstack/echo/v4" + "github.com/rs/zerolog/log" ) const ( seoul = "37.53/127.02" ) - // getAllLoadGeneratorInstallInfo handler function that retrieves all load generator installation information. // @Id GetAllLoadGeneratorInstallInfo // @Summary Get All Load Generator Install Info @@ -175,40 +176,91 @@ func (s *AntServer) runLoadTest(c echo.Context) error { req.InstallLoadGenerator = InstallLoadGeneratorReq{} } else if req.InstallLoadGenerator.InstallLocation != constant.Local && req.InstallLoadGenerator.InstallLocation != constant.Remote { - return errorResponseJson(http.StatusBadRequest, "load test install location is invalid.") + return errorResponseJson(http.StatusBadRequest, "invalid load test install location") + } + + if strings.TrimSpace(req.TestName) == "" { + req.TestName = uuid.New().String() + } + + if _, err := strconv.Atoi(strings.TrimSpace(req.VirtualUsers)); err != nil { + log.Error().Msgf("virtual user count is invalid") + return errorResponseJson(http.StatusBadRequest, "virtual user is not correct. check and retry.") + } + + if _, err := strconv.Atoi(strings.TrimSpace(req.Duration)); err != nil { + log.Error().Msgf("duration is invalid") + return errorResponseJson(http.StatusBadRequest, "duration is not correct. check and retry.") + } + + if _, err := strconv.Atoi(strings.TrimSpace(req.RampUpTime)); err != nil { + log.Error().Msgf("ramp up time is invalid") + return errorResponseJson(http.StatusBadRequest, "ramp up time is not correct. check and retry.") + } + + if _, err := strconv.Atoi(strings.TrimSpace(req.RampUpSteps)); err != nil { + log.Error().Msgf("ramp up steps is invalid") + return errorResponseJson(http.StatusBadRequest, "ramp up steps is not correct. check and retry.") + } + + if len(req.HttpReqs) == 0 { + log.Error().Msgf("http request have to have at least one; %s", req.TestName) + return errorResponseJson(http.StatusBadRequest, "http request have to have at least one or more") } var https []load.RunLoadTestHttpParam for _, h := range req.HttpReqs { + if h.Method == "" { + log.Error().Msgf("method for load test is empty. cannot start load test; %s", req.TestName) + return errorResponseJson(http.StatusBadRequest, "load test running info is not correct.; Method") + } + + if h.Protocol == "" { + h.Protocol = "http" + } + + if h.Hostname == "" { + log.Error().Msgf("hostname for load test is empty. cannot start load test; %s", req.TestName) + return errorResponseJson(http.StatusBadRequest, "load test running info is not correct.; Hostname") + } + + if t, err := strconv.Atoi(h.Port); err != nil && (t < 1 || t > 65353) { + log.Error().Msgf("port range is not valid. check the range of ports") + return errorResponseJson(http.StatusBadRequest, "load test running info is not correct.; Port") + } + hh := load.RunLoadTestHttpParam{ - Method: h.Method, - Protocol: h.Protocol, - Hostname: h.Hostname, - Port: h.Port, - Path: h.Path, - BodyData: h.BodyData, + Method: strings.TrimSpace(h.Method), + Protocol: strings.TrimSpace(h.Protocol), + Hostname: strings.TrimSpace(h.Hostname), + Port: strings.TrimSpace(h.Port), + Path: strings.TrimSpace(h.Path), + BodyData: strings.TrimSpace(h.BodyData), } https = append(https, hh) } arg := load.RunLoadTestParam{ - InstallLoadGenerator: load.InstallLoadGeneratorParam{ InstallLocation: req.InstallLoadGenerator.InstallLocation, Coordinates: []string{seoul}, }, LoadGeneratorInstallInfoId: req.LoadGeneratorInstallInfoId, - TestName: req.TestName, - VirtualUsers: req.VirtualUsers, - Duration: req.Duration, - RampUpTime: req.RampUpTime, - RampUpSteps: req.RampUpSteps, - Hostname: req.Hostname, - Port: req.Port, - AgentInstalled: req.AgentInstalled, - AgentHostname: req.AgentHostname, - HttpReqs: https, + TestName: strings.TrimSpace(req.TestName), + VirtualUsers: strings.TrimSpace(req.VirtualUsers), + Duration: strings.TrimSpace(req.Duration), + RampUpTime: strings.TrimSpace(req.RampUpTime), + RampUpSteps: strings.TrimSpace(req.RampUpSteps), + + CollectAdditionalSystemMetrics: req.CollectAdditionalSystemMetrics, + AgentHostname: strings.TrimSpace(req.AgentHostname), + + NsId: strings.TrimSpace(req.NsId), + MciId: strings.TrimSpace(req.MciId), + VmId: strings.TrimSpace(req.VmId), + + HttpReqs: https, } loadTestKey, err := s.services.loadService.RunLoadTest(arg) @@ -243,7 +295,8 @@ func (s *AntServer) stopLoadTest(c echo.Context) error { return errorResponseJson(http.StatusBadRequest, "load test stop info is not correct.") } - if strings.TrimSpace(req.LoadTestKey) == "" { + if strings.TrimSpace(req.LoadTestKey) == "" || + (strings.TrimSpace(req.NsId) == "" || strings.TrimSpace(req.MciId) == "" || strings.TrimSpace(req.VmId) == "") { return errorResponseJson(http.StatusBadRequest, "load test stop info is not correct.") } @@ -457,6 +510,46 @@ func (s *AntServer) getAllLoadTestExecutionState(c echo.Context) error { return successResponseJson(c, "Successfully retrieved load test execution state information", result) } +// getLoadTestExecutionState handler function that retrieves a load test execution state last executed with given ids. +// @Id GetLastLoadTestExecutionState +// @Summary Get Last Load Test Execution State +// @Description Retrieve a last load test execution state by given ids. +// @Tags [Load Test State Management] +// @Accept json +// @Produce json +// @Param nsId query string true "nsId" +// @Param mciId query string true "mciId" +// @Param vmId query string true "Load test key" +// @Success 200 {object} app.AntResponse[load.LoadTestExecutionStateResult] "Successfully retrieved load test execution state information" +// @Failure 400 {object} app.AntResponse[string] "Invalid request parameters" +// @Failure 500 {object} app.AntResponse[string] "Failed to retrieve load test execution state information" +// @Router /api/v1/load/tests/state/last [get] +func (s *AntServer) getLastLoadTestExecutionState(c echo.Context) error { + var req GetLastLoadTestExecutionStateReq + if err := c.Bind(&req); err != nil { + return errorResponseJson(http.StatusBadRequest, "Invalid request parameters") + } + + if strings.TrimSpace(req.NsId) == "" || strings.TrimSpace(req.MciId) == "" || strings.TrimSpace(req.VmId) == "" { + return errorResponseJson(http.StatusBadRequest, "Invalid request parameters") + } + + arg := load.GetLoadTestExecutionStateParam{ + NsId: req.NsId, + MciId: req.MciId, + VmId: req.VmId, + } + + result, err := s.services.loadService.GetLoadTestExecutionState(arg) + + if err != nil { + return errorResponseJson(http.StatusInternalServerError, "Failed to retrieve load test execution state information") + } + + return successResponseJson(c, "Successfully retrieved load test execution state information", result) + +} + // getLoadTestExecutionState handler function that retrieves a load test execution state by key. // @Id GetLoadTestExecutionState // @Summary Get Load Test Execution State diff --git a/internal/app/evaluate_performance_req.go b/internal/app/evaluate_performance_req.go new file mode 100644 index 0000000..69c129e --- /dev/null +++ b/internal/app/evaluate_performance_req.go @@ -0,0 +1,92 @@ +package app + +import "github.com/cloud-barista/cm-ant/internal/core/common/constant" + +type MonitoringAgentInstallationReq struct { + NsId string `json:"nsId"` + MciId string `json:"mciId"` + VmIds []string `json:"vmIds,omitempty"` +} + +type GetAllMonitoringAgentInfosReq struct { + Page int `query:"page"` + Size int `query:"size"` + NsId string `query:"nsId"` + MciId string `query:"mciId"` + VmId string `query:"vmId"` +} + +type InstallLoadGeneratorReq struct { + InstallLocation constant.InstallLocation `json:"installLocation"` +} + +type GetAllLoadGeneratorInstallInfoReq struct { + Page int `query:"page"` + Size int `query:"size"` + Status string `query:"status"` +} + +type RunLoadTestReq struct { + // localhost for installing load generator; local | remote + InstallLoadGenerator InstallLoadGeneratorReq `json:"installLoadGenerator"` + + // if already installed load generator simply put this field + LoadGeneratorInstallInfoId uint `json:"loadGeneratorInstallInfoId"` + + // test scenario + TestName string `json:"testName"` + VirtualUsers string `json:"virtualUsers"` + Duration string `json:"duration"` + RampUpTime string `json:"rampUpTime"` + RampUpSteps string `json:"rampUpSteps"` + + // for validate agent host and connect to tumblebug resources + NsId string `json:"nsId"` // for metadata usage + MciId string `json:"mciId"` // for metadata usage + VmId string `json:"vmId"` // for metadata usage + + // agent tcp default port is 5555 + CollectAdditionalSystemMetrics bool `json:"collectAdditionalSystemMetrics"` + AgentHostname string `json:"agentHostname"` // basically, it is same as host for vm + + HttpReqs []RunLoadGeneratorHttpReq `json:"httpReqs,omitempty"` +} + +type RunLoadGeneratorHttpReq struct { + Method string `json:"method" validate:"required"` // GET or POST + Protocol string `json:"protocol" validate:"required"` // http or https + Hostname string `json:"hostname,omitempty" validate:"required"` // xx.xx.xx.xx or asx.bbb.com + Port string `json:"port,omitempty" validate:"required"` // 1 ~ 65353 + Path string `json:"path,omitempty" validate:"required"` // /xxx/www/sss or possibly empty + BodyData string `json:"bodyData,omitempty" validate:"required"` // {"xxx": "tttt", "wwwww": "wotjkenr"} +} + +type GetAllLoadTestExecutionStateReq struct { + Page int `query:"page"` + Size int `query:"size"` + LoadTestKey string `query:"loadTestKey"` + ExecutionStatus constant.ExecutionStatus `query:"executionStatus"` +} + +type GetLastLoadTestExecutionStateReq struct { + NsId string `query:"nsId"` + MciId string `query:"mciId"` + VmId string `query:"vmId"` +} + +type GetAllLoadTestExecutionHistoryReq struct { + Page int `query:"page"` + Size int `query:"size"` +} + +type StopLoadTestReq struct { + LoadTestKey string `json:"loadTestKey"` + NsId string `json:"nsId"` + MciId string `json:"mciId"` + VmId string `json:"vmId"` +} + +type GetLoadTestResultReq struct { + LoadTestKey string `query:"loadTestKey"` + Format constant.ResultFormat `query:"format"` +} diff --git a/internal/app/middlewares.go b/internal/app/middlewares.go index 73b318e..a9f157c 100644 --- a/internal/app/middlewares.go +++ b/internal/app/middlewares.go @@ -36,7 +36,7 @@ func setMiddleware(e *echo.Echo) { OnTimeoutRouteErrorHandler: func(err error, c echo.Context) { utils.LogInfo(c.Path()) }, - Timeout: 300 * time.Second, + Timeout: 3000 * time.Second, }, ), diff --git a/internal/app/performance_evaluation_req.go b/internal/app/performance_evaluation_req.go deleted file mode 100644 index 7e98d08..0000000 --- a/internal/app/performance_evaluation_req.go +++ /dev/null @@ -1,73 +0,0 @@ -package app - -import "github.com/cloud-barista/cm-ant/internal/core/common/constant" - -type MonitoringAgentInstallationReq struct { - NsId string `json:"nsId"` - MciId string `json:"mciId"` - VmIds []string `json:"vmIds,omitempty"` -} - -type GetAllMonitoringAgentInfosReq struct { - Page int `query:"page"` - Size int `query:"size"` - NsId string `query:"nsId"` - MciId string `query:"mciId"` - VmId string `query:"vmId"` -} - -type InstallLoadGeneratorReq struct { - InstallLocation constant.InstallLocation `json:"installLocation"` -} - -type GetAllLoadGeneratorInstallInfoReq struct { - Page int `query:"page"` - Size int `query:"size"` - Status string `query:"status"` -} - -type RunLoadTestReq struct { - InstallLoadGenerator InstallLoadGeneratorReq `json:"installLoadGenerator"` - LoadGeneratorInstallInfoId uint `json:"loadGeneratorInstallInfoId"` - TestName string `json:"testName"` - VirtualUsers string `json:"virtualUsers"` - Duration string `json:"duration"` - RampUpTime string `json:"rampUpTime"` - RampUpSteps string `json:"rampUpSteps"` - Hostname string `json:"hostname"` - Port string `json:"port"` - AgentInstalled bool `json:"agentInstalled"` - AgentHostname string `json:"agentHostname"` - - HttpReqs []RunLoadGeneratorHttpReq `json:"httpReqs,omitempty"` -} - -type RunLoadGeneratorHttpReq struct { - Method string `json:"method"` - Protocol string `json:"protocol"` - Hostname string `json:"hostname,omitempty"` - Port string `json:"port,omitempty"` - Path string `json:"path,omitempty"` - BodyData string `json:"bodyData,omitempty"` -} - -type GetAllLoadTestExecutionStateReq struct { - Page int `query:"page"` - Size int `query:"size"` - LoadTestKey string `query:"loadTestKey"` - ExecutionStatus constant.ExecutionStatus `query:"executionStatus"` -} - -type GetAllLoadTestExecutionHistoryReq struct { - Page int `query:"page"` - Size int `query:"size"` -} - -type StopLoadTestReq struct { - LoadTestKey string `json:"loadTestKey"` -} - -type GetLoadTestResultReq struct { - LoadTestKey string `query:"loadTestKey"` - Format constant.ResultFormat `query:"format"` -} diff --git a/internal/app/router.go b/internal/app/router.go index 270b806..888a855 100644 --- a/internal/app/router.go +++ b/internal/app/router.go @@ -45,6 +45,7 @@ func (server *AntServer) InitRouter() error { // load test state loadTestRouter.GET("/state", server.getAllLoadTestExecutionState) loadTestRouter.GET("/state/:loadTestKey", server.getLoadTestExecutionState) + loadTestRouter.GET("/state/last", server.getLastLoadTestExecutionState) // load test history loadTestRouter.GET("/infos", server.getAllLoadTestExecutionInfos) diff --git a/internal/app/server.go b/internal/app/server.go index 338b36c..3cc9c56 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -51,7 +51,7 @@ func NewAntServer() (*AntServer, error) { } client := &http.Client{ - Timeout: 5 * time.Minute, + Timeout: 50 * time.Minute, } tumblebugClient := tumblebug.NewTumblebugClient(client) diff --git a/internal/core/common/constant/constant.go b/internal/core/common/constant/constant.go index 051a9b6..a72aa24 100644 --- a/internal/core/common/constant/constant.go +++ b/internal/core/common/constant/constant.go @@ -16,19 +16,10 @@ const ( type ExecutionStatus string const ( - OnPreparing ExecutionStatus = "on_preparing" - OnRunning ExecutionStatus = "on_running" + OnProcessing ExecutionStatus = "on_processing" OnFetching ExecutionStatus = "on_fetching" Successed ExecutionStatus = "successed" TestFailed ExecutionStatus = "test_failed" - UpdateFailed ExecutionStatus = "update_failed" - ResultFailed ExecutionStatus = "result_failed" - - Failed ExecutionStatus = "failed" - - Processing ExecutionStatus = "processing" - Fetching ExecutionStatus = "fetching" - Success ExecutionStatus = "success" ) type ResultFormat string diff --git a/internal/core/load/dtos.go b/internal/core/load/dtos.go index 19bceb3..30b67af 100644 --- a/internal/core/load/dtos.go +++ b/internal/core/load/dtos.go @@ -101,15 +101,21 @@ type RunLoadTestParam struct { LoadTestKey string `json:"loadTestKey"` InstallLoadGenerator InstallLoadGeneratorParam `json:"installLoadGenerator"` LoadGeneratorInstallInfoId uint `json:"loadGeneratorInstallInfoId"` - TestName string `json:"testName"` - VirtualUsers string `json:"virtualUsers"` - Duration string `json:"duration"` - RampUpTime string `json:"rampUpTime"` - RampUpSteps string `json:"rampUpSteps"` - Hostname string `json:"hostname"` - Port string `json:"port"` - AgentInstalled bool `json:"agentInstalled"` - AgentHostname string `json:"agentHostname"` + + // test scenario + TestName string `json:"testName"` + VirtualUsers string `json:"virtualUsers"` + Duration string `json:"duration"` + RampUpTime string `json:"rampUpTime"` + RampUpSteps string `json:"rampUpSteps"` + + // related tumblebug + NsId string `json:"nsId"` + MciId string `json:"mciId"` + VmId string `json:"vmId"` + + CollectAdditionalSystemMetrics bool + AgentHostname string HttpReqs []RunLoadTestHttpParam `json:"httpReqs,omitempty"` } @@ -153,6 +159,9 @@ type LoadTestExecutionStateResult struct { type GetLoadTestExecutionStateParam struct { LoadTestKey string `json:"loadTestKey"` + NsId string `json:"nsId"` + MciId string `json:"mciId"` + VmId string `json:"vmId"` } type GetAllLoadTestExecutionInfosParam struct { @@ -173,8 +182,6 @@ type LoadTestExecutionInfoResult struct { Duration string `json:"duration,omitempty"` RampUpTime string `json:"rampUpTime,omitempty"` RampUpSteps string `json:"rampUpSteps,omitempty"` - Hostname string `json:"hostname,omitempty"` - Port string `json:"port,omitempty"` AgentHostname string `json:"agentHostname,omitempty"` AgentInstalled bool `json:"agentInstalled,omitempty"` CompileDuration string `json:"compileDuration,omitempty"` @@ -200,6 +207,9 @@ type GetLoadTestExecutionInfoParam struct { type StopLoadTestParam struct { LoadTestKey string `json:"loadTestKey"` + NsId string `json:"nsId"` + MciId string `json:"mciId"` + VmId string `json:"vmId"` } type ResultSummary struct { diff --git a/internal/core/load/jmx_parser.go b/internal/core/load/jmx_parser.go index bd0e8a8..c161ac1 100644 --- a/internal/core/load/jmx_parser.go +++ b/internal/core/load/jmx_parser.go @@ -3,6 +3,7 @@ package load import ( "bytes" "fmt" + "html" "io" "log" "net/url" @@ -107,7 +108,7 @@ var jmxHttpSamplerTemplate = map[string]string{ func parseTestPlanStructToString(w io.Writer, param RunLoadTestParam, loadGeneratorInstallInfo *LoadGeneratorInstallInfo) error { resultPath := fmt.Sprintf("%s/result", loadGeneratorInstallInfo.InstallPath) - httpRequests, err := httpReqParseToJmx(param.Hostname, param.Port, param.HttpReqs) + httpRequests, err := httpReqParseToJmx(param.HttpReqs) if err != nil { return err } @@ -150,7 +151,7 @@ func parseTestPlanStructToString(w io.Writer, param RunLoadTestParam, loadGenera return nil } -func httpReqParseToJmx(hostname, port string, httpReqs []RunLoadTestHttpParam) (string, error) { +func httpReqParseToJmx(httpReqs []RunLoadTestHttpParam) (string, error) { var builder strings.Builder for i, req := range httpReqs { method := strings.ToUpper(req.Method) @@ -167,14 +168,14 @@ func httpReqParseToJmx(hostname, port string, httpReqs []RunLoadTestHttpParam) ( return "", err } - h := req.Hostname + h := strings.TrimSpace(req.Hostname) if h == "" { - h = hostname + continue } - p := req.Port + p := strings.TrimSpace(req.Port) if p == "" { - p = port + continue } jmxHttpTemplateData := jmxHttpTemplateData{ @@ -198,7 +199,7 @@ func httpReqParseToJmx(hostname, port string, httpReqs []RunLoadTestHttpParam) ( } jmxHttpTemplateData.Params = params } else if method == "POST" { - jmxHttpTemplateData.BodyData = req.BodyData + jmxHttpTemplateData.BodyData = ConvertToHTMLEntities(req.BodyData) } var buf bytes.Buffer @@ -214,3 +215,11 @@ func httpReqParseToJmx(hostname, port string, httpReqs []RunLoadTestHttpParam) ( result := builder.String() return result, nil } + +func ConvertToHTMLEntities(jsonStr string) string { + escapedStr := html.EscapeString(jsonStr) + + escapedStr = strings.ReplaceAll(escapedStr, "\n", " ") + + return escapedStr +} diff --git a/internal/core/load/load_test_execution_service.go b/internal/core/load/load_test_execution_service.go index 5233818..2254419 100644 --- a/internal/core/load/load_test_execution_service.go +++ b/internal/core/load/load_test_execution_service.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "log" "os" "strconv" "strings" @@ -14,6 +13,7 @@ import ( "github.com/cloud-barista/cm-ant/internal/core/common/constant" "github.com/cloud-barista/cm-ant/internal/infra/outbound/tumblebug" "github.com/cloud-barista/cm-ant/internal/utils" + "github.com/rs/zerolog/log" ) // RunLoadTest initiates the load test and performs necessary initializations. @@ -25,33 +25,35 @@ func (l *LoadService) RunLoadTest(param RunLoadTestParam) (string, error) { loadTestKey := utils.CreateUniqIdBaseOnUnixTime() param.LoadTestKey = loadTestKey + log.Info().Msgf("Starting load test with key: %s", loadTestKey) - utils.LogInfof("Starting load test with key: %s", loadTestKey) - + // check the installation of load generator. + // if it is not installed, then install it to user selected location. if param.LoadGeneratorInstallInfoId == uint(0) { - utils.LogInfo("No LoadGeneratorInstallInfoId provided, installing load generator...") + log.Info().Msgf("No LoadGeneratorInstallInfoId provided, installing load generator...") result, err := l.InstallLoadGenerator(param.InstallLoadGenerator) if err != nil { - utils.LogErrorf("Error installing load generator: %v", err) + log.Error().Msgf("Error installing load generator: %v", err) return "", err } param.LoadGeneratorInstallInfoId = result.ID - utils.LogInfof("Load generator installed with ID: %d", result.ID) + log.Info().Msgf("Load generator installed with ID: %d", result.ID) } if param.LoadGeneratorInstallInfoId == uint(0) { - utils.LogErrorf("LoadGeneratorInstallInfoId is still 0 after installation.") - return "", nil + log.Error().Msgf("LoadGeneratorInstallInfoId is still 0 after installation.") + return "", errors.New("error while load generator installation process") } - utils.LogInfof("Retrieving load generator installation info with ID: %d", param.LoadGeneratorInstallInfoId) loadGeneratorInstallInfo, err := l.loadRepo.GetValidLoadGeneratorInstallInfoByIdTx(ctx, param.LoadGeneratorInstallInfoId) if err != nil { - utils.LogErrorf("Error retrieving load generator installation info: %v", err) + log.Error().Msgf("Error retrieving load generator installation info: %v", err) return "", err } + log.Info().Msgf("Retrieved load generator installation info with ID: %d", param.LoadGeneratorInstallInfoId) + duration, err := strconv.Atoi(param.Duration) if err != nil { return "", err @@ -63,12 +65,20 @@ func (l *LoadService) RunLoadTest(param RunLoadTestParam) (string, error) { return "", err } + totalExecutionSecond := uint64(duration + rampUpTime) + startAt := time.Now() + e := startAt.Add(time.Duration(totalExecutionSecond) * time.Second) + stateArg := LoadTestExecutionState{ LoadGeneratorInstallInfoId: loadGeneratorInstallInfo.ID, LoadTestKey: loadTestKey, - ExecutionStatus: constant.OnPreparing, - StartAt: time.Now(), - TotalExpectedExcutionSecond: uint64(duration + rampUpTime), + ExecutionStatus: constant.OnProcessing, + StartAt: startAt, + ExpectedFinishAt: e, + TotalExpectedExcutionSecond: totalExecutionSecond, + NsId: param.NsId, + MciId: param.MciId, + VmId: param.VmId, } go l.processLoadTest(param, &loadGeneratorInstallInfo, &stateArg) @@ -89,28 +99,32 @@ func (l *LoadService) RunLoadTest(param RunLoadTestParam) (string, error) { } loadArg := LoadTestExecutionInfo{ - LoadTestKey: loadTestKey, - TestName: param.TestName, - VirtualUsers: param.VirtualUsers, - Duration: param.Duration, - RampUpTime: param.RampUpTime, - RampUpSteps: param.RampUpSteps, - Hostname: param.Hostname, - Port: param.Port, - AgentInstalled: param.AgentInstalled, - AgentHostname: param.AgentHostname, + LoadTestKey: loadTestKey, + TestName: param.TestName, + VirtualUsers: param.VirtualUsers, + Duration: param.Duration, + RampUpTime: param.RampUpTime, + RampUpSteps: param.RampUpSteps, + + NsId: param.NsId, + MciId: param.MciId, + VmId: param.VmId, + + AgentInstalled: param.CollectAdditionalSystemMetrics, + AgentHostname: param.AgentHostname, + LoadGeneratorInstallInfoId: loadGeneratorInstallInfo.ID, LoadTestExecutionHttpInfos: hs, } - utils.LogInfof("Saving load test execution info for key: %s", loadTestKey) + log.Info().Msgf("Saving load test execution info for key: %s", loadTestKey) err = l.loadRepo.SaveForLoadTestExecutionTx(ctx, &loadArg, &stateArg) if err != nil { - utils.LogErrorf("Error saving load test execution info: %v", err) + log.Error().Msgf("Error saving load test execution info: %v", err) return "", err } - utils.LogInfof("Load test started successfully with key: %s", loadTestKey) + log.Info().Msgf("Load test started successfully with key: %s", loadTestKey) return loadTestKey, nil @@ -121,6 +135,53 @@ func (l *LoadService) RunLoadTest(param RunLoadTestParam) (string, error) { // Fetches and saves test results from the local or remote system. func (l *LoadService) processLoadTest(param RunLoadTestParam, loadGeneratorInstallInfo *LoadGeneratorInstallInfo, loadTestExecutionState *LoadTestExecutionState) { + // check the installation of agent for additional metrics + if param.CollectAdditionalSystemMetrics { + if strings.TrimSpace(param.AgentHostname) == "" { + mci, err := l.tumblebugClient.GetMciWithContext(context.Background(), param.NsId, param.MciId) + + if err != nil { + log.Error().Msgf("unexpected error occurred while fetching mci for install metrics agent") + return + } + + if len(mci.Vm) == 0 { + log.Error().Msgf("unexpected error occurred while fetching mci for install metrics agent") + return + } + + if len(mci.Vm) == 1 { + param.AgentHostname = mci.Vm[0].PublicIP + } else { + for _, v := range mci.Vm { + if v.Id == param.VmId { + param.AgentHostname = v.PublicIP + } + } + } + + if param.AgentHostname == "" { + log.Error().Msgf("invalid agent hostname for test %s", param.LoadTestKey) + return + } + } + + arg := MonitoringAgentInstallationParams{ + NsId: param.NsId, + MciId: param.MciId, + VmIds: []string{param.VmId}, + } + + // install and run the agent for collect metrics + _, err := l.InstallMonitoringAgent(arg) + if err != nil { + log.Error().Msgf("unexpected error occurred while fetching mci for ") + return + } + + log.Info().Msgf("metrics agent installed successfully for load test; %s %s %s", arg.NsId, arg.MciId, arg.VmIds) + } + loadTestDone := make(chan bool) var username string @@ -140,17 +201,17 @@ func (l *LoadService) processLoadTest(param RunLoadTestParam, loadGeneratorInsta } dataParam := &fetchDataParam{ - LoadTestDone: loadTestDone, - LoadTestKey: param.LoadTestKey, - InstallLocation: loadGeneratorInstallInfo.InstallLocation, - InstallPath: loadGeneratorInstallInfo.InstallPath, - PublicKeyName: loadGeneratorInstallInfo.PublicKeyName, - PrivateKeyName: loadGeneratorInstallInfo.PrivateKeyName, - Username: username, - PublicIp: publicIp, - Port: port, - AgentInstalled: param.AgentInstalled, - Home: home, + LoadTestDone: loadTestDone, + LoadTestKey: param.LoadTestKey, + InstallLocation: loadGeneratorInstallInfo.InstallLocation, + InstallPath: loadGeneratorInstallInfo.InstallPath, + PublicKeyName: loadGeneratorInstallInfo.PublicKeyName, + PrivateKeyName: loadGeneratorInstallInfo.PrivateKeyName, + Username: username, + PublicIp: publicIp, + Port: port, + CollectAdditionalSystemMetrics: param.CollectAdditionalSystemMetrics, + Home: home, } go l.fetchData(dataParam) @@ -160,9 +221,11 @@ func (l *LoadService) processLoadTest(param RunLoadTestParam, loadGeneratorInsta close(loadTestDone) updateErr := l.loadRepo.UpdateLoadTestExecutionStateTx(context.Background(), loadTestExecutionState) if updateErr != nil { - utils.LogErrorf("Error updating load test execution state: %v", updateErr) + log.Error().Msgf("Error updating load test execution state: %v", updateErr) return } + + log.Info().Msgf("successfully done load test for %s", dataParam.LoadTestKey) }() compileDuration, executionDuration, loadTestErr := l.executeLoadTest(param, loadGeneratorInstallInfo) @@ -180,7 +243,7 @@ func (l *LoadService) processLoadTest(param RunLoadTestParam, loadGeneratorInsta updateErr := l.updateLoadTestExecution(loadTestExecutionState) if updateErr != nil { - loadTestExecutionState.ExecutionStatus = constant.UpdateFailed + loadTestExecutionState.ExecutionStatus = constant.TestFailed loadTestExecutionState.FailureMessage = updateErr.Error() finishAt := time.Now() loadTestExecutionState.FinishAt = &finishAt @@ -313,16 +376,29 @@ func (l *LoadService) StopLoadTest(param StopLoadTestParam) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - state, err := l.loadRepo.GetLoadTestExecutionStateTx(ctx, GetLoadTestExecutionStateParam{ - LoadTestKey: param.LoadTestKey, - }) + var arg GetLoadTestExecutionStateParam + + if param.LoadTestKey == "" { + arg = GetLoadTestExecutionStateParam{ + NsId: param.NsId, + MciId: param.MciId, + VmId: param.VmId, + } + + } else { + arg = GetLoadTestExecutionStateParam{ + LoadTestKey: param.LoadTestKey, + } + } + + state, err := l.loadRepo.GetLoadTestExecutionStateTx(ctx, arg) if err != nil { - return err + return fmt.Errorf("error occurred while retrieve load test execution state: %w", err) } if state.ExecutionStatus == constant.Successed { - return nil + return errors.New("load test is already completed") } installInfo := state.LoadGeneratorInstallInfo @@ -344,7 +420,7 @@ func (l *LoadService) StopLoadTest(param StopLoadTestParam) error { err := utils.InlineCmd(killCmd) if err != nil { - log.Println(err) + log.Error().Msg(err.Error()) return err } } diff --git a/internal/core/load/models.go b/internal/core/load/models.go index 027f906..14534c8 100644 --- a/internal/core/load/models.go +++ b/internal/core/load/models.go @@ -69,10 +69,16 @@ type LoadGeneratorInstallInfo struct { type LoadTestExecutionState struct { gorm.Model - LoadTestKey string `gorm:"index:idx_state_load_test_key,unique"` + LoadTestKey string `gorm:"index:idx_state_load_test_key,unique"` + + NsId string + MciId string + VmId string + ExecutionStatus constant.ExecutionStatus StartAt time.Time FinishAt *time.Time + ExpectedFinishAt time.Time TotalExpectedExcutionSecond uint64 FailureMessage string CompileDuration string @@ -86,16 +92,20 @@ type LoadTestExecutionState struct { type LoadTestExecutionInfo struct { gorm.Model - LoadTestKey string `gorm:"index:idx_info_load_test_key,unique"` - TestName string - VirtualUsers string - Duration string - RampUpTime string - RampUpSteps string - Hostname string - Port string - AgentHostname string - AgentInstalled bool + LoadTestKey string `gorm:"index:idx_info_load_test_key,unique"` + TestName string `gorm:"index:idx_info_load_test_name"` + VirtualUsers string + Duration string + RampUpTime string + RampUpSteps string + + NsId string + MciId string + VmId string + + AgentHostname string + AgentInstalled bool + CompileDuration string ExecutionDuration string LoadTestExecutionHttpInfos []LoadTestExecutionHttpInfo diff --git a/internal/core/load/performace_evaluation_service.go b/internal/core/load/performace_evaluation_service.go index 0bc81f1..6bef8df 100644 --- a/internal/core/load/performace_evaluation_service.go +++ b/internal/core/load/performace_evaluation_service.go @@ -221,8 +221,6 @@ func mapLoadTestExecutionInfoResult(executionInfo LoadTestExecutionInfo) LoadTes Duration: executionInfo.Duration, RampUpTime: executionInfo.RampUpTime, RampUpSteps: executionInfo.RampUpSteps, - Hostname: executionInfo.Hostname, - Port: executionInfo.Port, AgentHostname: executionInfo.AgentHostname, AgentInstalled: executionInfo.AgentInstalled, CompileDuration: executionInfo.CompileDuration, diff --git a/internal/core/load/repository.go b/internal/core/load/repository.go index e7fa317..e3de251 100644 --- a/internal/core/load/repository.go +++ b/internal/core/load/repository.go @@ -370,14 +370,40 @@ func (r *LoadRepository) GetLoadTestExecutionStateTx(ctx context.Context, param var loadTestExecutionState LoadTestExecutionState err := r.execInTransaction(ctx, func(d *gorm.DB) error { - return d.Model(&loadTestExecutionState). + + q := d.Model(&loadTestExecutionState). Preload("LoadGeneratorInstallInfo"). - Preload("LoadGeneratorInstallInfo.LoadGeneratorServers"). - First(&loadTestExecutionState, "load_test_execution_states.load_test_key = ?", param.LoadTestKey). - Error + Preload("LoadGeneratorInstallInfo.LoadGeneratorServers") + + if param.LoadTestKey != "" { + q = q.Where("load_test_execution_states.load_test_key = ?", param.LoadTestKey) + } + + if param.NsId != "" { + q = q.Where("load_test_execution_states.ns_id = ?", param.NsId) + } + + if param.MciId != "" { + q = q.Where("load_test_execution_states.mci_id = ?", param.MciId) + } + + if param.VmId != "" { + q = q.Where("load_test_execution_states.vm_id = ?", param.VmId) + } + + q = q.Order("load_test_execution_states.start_at desc"). + Limit(1) + + return q.First(&loadTestExecutionState).Error }) - return loadTestExecutionState, err + if err != nil { + if err != gorm.ErrRecordNotFound { + return loadTestExecutionState, err + } + } + + return loadTestExecutionState, nil } func (r *LoadRepository) GetPagingLoadTestExecutionHistoryTx(ctx context.Context, param GetAllLoadTestExecutionInfosParam) ([]LoadTestExecutionInfo, int64, error) { diff --git a/internal/core/load/rsync_fetch_service.go b/internal/core/load/rsync_fetch_service.go index 5acab2b..abe3136 100644 --- a/internal/core/load/rsync_fetch_service.go +++ b/internal/core/load/rsync_fetch_service.go @@ -2,28 +2,30 @@ package load import ( "fmt" - "log" + "sync" "time" + "github.com/cloud-barista/cm-ant/internal/config" "github.com/cloud-barista/cm-ant/internal/core/common/constant" "github.com/cloud-barista/cm-ant/internal/utils" + "github.com/rs/zerolog/log" ) type fetchDataParam struct { - LoadTestDone <-chan bool - LoadTestKey string - InstallLocation constant.InstallLocation - InstallPath string - PublicKeyName string - PrivateKeyName string - Username string - PublicIp string - Port string - AgentInstalled bool - fetchMx sync.Mutex - fetchRunning bool - Home string + LoadTestDone <-chan bool + LoadTestKey string + InstallLocation constant.InstallLocation + InstallPath string + PublicKeyName string + PrivateKeyName string + Username string + PublicIp string + Port string + CollectAdditionalSystemMetrics bool + fetchMx sync.Mutex + fetchRunning bool + Home string } func (f *fetchDataParam) setFetchRunning(running bool) { @@ -53,16 +55,16 @@ func (l *LoadService) fetchData(f *fetchDataParam) { if !f.isRunning() { f.setFetchRunning(true) if err := rsyncFiles(f); err != nil { - log.Println(err) + log.Error().Msgf("error while fetching data from rsync %s", err.Error()) } f.setFetchRunning(false) } case <-done: - retry := 3 + retry := config.AppConfig.Load.Retry for retry > 0 { if !f.isRunning() { if err := rsyncFiles(f); err != nil { - log.Println(err) + log.Error().Msgf("error while fetching data from rsync %s", err.Error()) } break } @@ -83,7 +85,7 @@ func rsyncFiles(f *fetchDataParam) error { var wg sync.WaitGroup resultsPrefix := []string{""} - if f.AgentInstalled { + if f.CollectAdditionalSystemMetrics { resultsPrefix = append(resultsPrefix, "_cpu", "_disk", "_memory", "_network") } @@ -101,47 +103,60 @@ func rsyncFiles(f *fetchDataParam) error { return err } - if installLocation == constant.Local { - for _, p := range resultsPrefix { - wg.Add(1) - go func(prefix string) { - defer wg.Done() - fileName := fmt.Sprintf("%s%s_result.csv", loadTestKey, prefix) - fromFilePath := fmt.Sprintf("%s/result/%s", loadGeneratorInstallPath, fileName) - toFilePath := fmt.Sprintf("%s/%s", resultFolderPath, fileName) - - cmd := fmt.Sprintf(`rsync -avz %s %s`, fromFilePath, toFilePath) - utils.LogInfo("cmd for rsync: ", cmd) - err := utils.InlineCmd(cmd) - errorChan <- err - }(p) - } - } else if installLocation == constant.Remote { - for _, p := range resultsPrefix { - wg.Add(1) - go func(prefix string) { - defer wg.Done() - fileName := fmt.Sprintf("%s%s_result.csv", loadTestKey, prefix) - fromFilePath := fmt.Sprintf("%s/result/%s", loadGeneratorInstallPath, fileName) - toFilePath := fmt.Sprintf("%s/%s", resultFolderPath, fileName) - - cmd := fmt.Sprintf(`rsync -avz -e "ssh -i %s -o StrictHostKeyChecking=no" %s@%s:%s %s`, + maxRetries := config.AppConfig.Load.Retry + retryDelay := 2 * time.Second + + rsync := func(prefix string) { + defer wg.Done() + fileName := fmt.Sprintf("%s%s_result.csv", loadTestKey, prefix) + fromFilePath := fmt.Sprintf("%s/result/%s", loadGeneratorInstallPath, fileName) + toFilePath := fmt.Sprintf("%s/%s", resultFolderPath, fileName) + + // Retry logic + for attempt := 1; attempt <= maxRetries; attempt++ { + var cmd string + if installLocation == constant.Local { + cmd = fmt.Sprintf(`rsync -avvz --no-whole-file %s %s`, fromFilePath, toFilePath) + } else if installLocation == constant.Remote { + cmd = fmt.Sprintf(`rsync -avvz -e "ssh -i %s -o StrictHostKeyChecking=no" %s@%s:%s %s`, fmt.Sprintf("%s/.ssh/%s", f.Home, f.PrivateKeyName), f.Username, f.PublicIp, fromFilePath, toFilePath) + } - utils.LogInfo("cmd for rsync: ", cmd) - err := utils.InlineCmd(cmd) - errorChan <- err - }(p) + utils.LogInfo("cmd for rsync: ", cmd) + err := utils.InlineCmd(cmd) + + if err == nil { + errorChan <- nil + return // Success, exit the retry loop + } + + utils.LogError(fmt.Sprintf("Error during rsync attempt %d for %s: %v", attempt, fileName, err)) + + // Wait before retrying + if attempt < maxRetries { + time.Sleep(retryDelay) + retryDelay *= 2 // Exponential backoff + } + } + + errorChan <- fmt.Errorf("failed to rsync %s after %d attempts", fileName, maxRetries) + } + + if installLocation == constant.Local || installLocation == constant.Remote { + for _, p := range resultsPrefix { + wg.Add(1) + go rsync(p) } } wg.Wait() close(errorChan) + // Collect errors from the error channel for err := range errorChan { if err != nil { return err diff --git a/script/install-jmeter.sh b/script/install-jmeter.sh index e6d13c5..1724965 100755 --- a/script/install-jmeter.sh +++ b/script/install-jmeter.sh @@ -10,107 +10,90 @@ JMETER_FOLDER="apache-jmeter-${JMETER_VERSION}" JMETER_FULL_PATH="${JMETER_WORK_DIR}/${JMETER_FOLDER}" JMETER_INSTALL_URL="https://archive.apache.org/dist/jmeter/binaries/${JMETER_FOLDER}.tgz" -echo "This is jmeter working directory >>>>>>>>>>>>>>>>> ${JMETER_WORK_DIR}" -echo "This is jmeter folder name >>>>>>>>>>>>>>>>>>>>>>> ${JMETER_FOLDER}" - -echo -echo - - -if [ ! -e "${JMETER_WORK_DIR}" ]; then - sudo mkdir -p "${JMETER_WORK_DIR}" - echo "jmeter path folder created" -fi - -if [ ! -e "${JMETER_WORK_DIR}/result" ]; then - sudo mkdir -p "${JMETER_WORK_DIR}/result" - echo "test plan path folder created" -fi - - -if [ ! -e "${JMETER_WORK_DIR}/test_plan" ]; then - sudo mkdir -p "${JMETER_WORK_DIR}/test_plan" - echo "test plan path folder created" -fi - +echo "This is jmeter working directory >>> ${JMETER_WORK_DIR}" +echo "This is jmeter folder name >>> ${JMETER_FOLDER}" + +# Create directories if they don't exist +create_directory() { + local dir=$1 + if [ ! -e "$dir" ]; then + sudo mkdir -p "$dir" + echo "Created directory: $dir" + fi +} -echo "[CM-ANT] [Step 1/6] Installing default required tools..." +create_directory "${JMETER_WORK_DIR}" +create_directory "${JMETER_WORK_DIR}/result" +create_directory "${JMETER_WORK_DIR}/test_plan" +echo "[CM-ANT] [Step 1/6] Installing required tools..." sudo apt install -y software-properties-common sudo add-apt-repository universe -y sudo apt-get update -y sudo apt-get install -y wget openjdk-17-jre -sleep 1s - -echo -echo - +# Function to extract JMeter unzip_jmeter() { - sudo tar -xf "${JMETER_FULL_PATH}.tgz" -C "${JMETER_WORK_DIR}" && sudo rm "${JMETER_FULL_PATH}.tgz" + sudo tar -xf "${JMETER_WORK_DIR}/${JMETER_FOLDER}.tgz" -C "${JMETER_WORK_DIR}" && + sudo rm "${JMETER_WORK_DIR}/${JMETER_FOLDER}.tgz" sudo rm -rf "${JMETER_FULL_PATH}/docs" "${JMETER_FULL_PATH}/printable_docs" } echo "[CM-ANT] [Step 2/6] Downloading and Extracting Apache JMeter..." if [ -d "${JMETER_FULL_PATH}" ]; then - echo "[CM-ANT] Jmeter is already installed." -elif [ -f "${JMETER_FULL_PATH}.tgz" ]; then - echo "[CM-ANT] Jmeter gzip file is installed on ${JMETER_WORK_DIR}. Let's do remaining installation." - unzip_jmeter + echo "[CM-ANT] JMeter is already installed." else - echo "[CM-ANT] JMeter is installing on path ${JMETER_WORK_DIR}" - sudo wget "${JMETER_INSTALL_URL}" -P "${JMETER_WORK_DIR}" + if [ ! -f "${JMETER_WORK_DIR}/${JMETER_FOLDER}.tgz" ]; then + echo "[CM-ANT] Downloading JMeter..." + sudo wget "${JMETER_INSTALL_URL}" -P "${JMETER_WORK_DIR}" + fi + echo "[CM-ANT] Extracting JMeter..." unzip_jmeter fi -sudo chmod -R 777 ${JMETER_WORK_DIR} - -echo -echo - -# install cmd runner -echo "[CM-ANT] [Step 3/6] Download CMD Runner to install plugins..." -CMD_RUNNER_VERSION="2.2.1" -CMD_RUNNER_JAR="cmdrunner-$CMD_RUNNER_VERSION.jar" - -if [ ! -e "$CMD_RUNNER_JAR" ]; then - wget "https://repo1.maven.org/maven2/kg/apc/cmdrunner/$CMD_RUNNER_VERSION/$CMD_RUNNER_JAR" && - sudo chmod +x "$CMD_RUNNER_JAR" && - sudo mv $CMD_RUNNER_JAR "$JMETER_FULL_PATH/lib/" - echo "[CB-ANT] Installed cmd runner." -fi - - -echo -echo - -# install plugin manager -echo "[CM-ANT] [Step 4/6] Download Plugin Manager to manage plugins..." -PLUGIN_MANAGER_VERSION="1.6" -PLUGIN_MANAGER_JAR="jmeter-plugins-manager-$PLUGIN_MANAGER_VERSION.jar" - -if [ ! -e "$PLUGIN_MANAGER_JAR" ]; then - wget "https://repo1.maven.org/maven2/kg/apc/jmeter-plugins-manager/$PLUGIN_MANAGER_VERSION/$PLUGIN_MANAGER_JAR" && - sudo chmod +x "$PLUGIN_MANAGER_JAR" && - sudo mv $PLUGIN_MANAGER_JAR "$JMETER_FULL_PATH/lib/ext/" - echo "[CB-ANT] Installed plugin manager." -fi - - -echo -echo +sudo chmod -R 777 "${JMETER_WORK_DIR}" + +# Install CMD Runner +install_cmd_runner() { + local version="2.2.1" + local jar="cmdrunner-${version}.jar" + if [ ! -f "${JMETER_FULL_PATH}/lib/${jar}" ]; then + echo "[CM-ANT] [Step 3/6] Installing CMD Runner..." + wget "https://repo1.maven.org/maven2/kg/apc/cmdrunner/${version}/${jar}" -P "${JMETER_WORK_DIR}" && + sudo chmod +x "${JMETER_WORK_DIR}/${jar}" && + sudo mv "${JMETER_WORK_DIR}/${jar}" "${JMETER_FULL_PATH}/lib/" + fi +} -# install perfmon plugin -echo "[CM-ANT] [Step 5/6] Install required plugins to do load test..." -java -jar "$JMETER_FULL_PATH/lib/$CMD_RUNNER_JAR" --tool org.jmeterplugins.repository.PluginManagerCMD install jpgc-perfmon,jpgc-casutg -echo "[CB-ANT] Installed required plugins." +# Install Plugin Manager +install_plugin_manager() { + local version="1.6" + local jar="jmeter-plugins-manager-${version}.jar" + if [ ! -f "${JMETER_FULL_PATH}/lib/ext/${jar}" ]; then + echo "[CM-ANT] [Step 4/6] Installing Plugin Manager..." + wget "https://repo1.maven.org/maven2/kg/apc/jmeter-plugins-manager/${version}/${jar}" -P "${JMETER_WORK_DIR}" && + sudo chmod +x "${JMETER_WORK_DIR}/${jar}" && + sudo mv "${JMETER_WORK_DIR}/${jar}" "${JMETER_FULL_PATH}/lib/ext/" + fi +} +# Install required plugins +install_required_plugins() { + echo "[CM-ANT] [Step 5/6] Installing required plugins for load testing..." + java -jar "${JMETER_FULL_PATH}/lib/cmdrunner-2.2.1.jar" --tool org.jmeterplugins.repository.PluginManagerCMD install jpgc-perfmon,jpgc-casutg +} -echo -echo +# Configure JMeter +configure_jmeter() { + echo "[CM-ANT] [Step 6/6] Configuring JMeter..." + sudo chmod +x "${JMETER_FULL_PATH}/bin/jmeter.sh" + "${JMETER_FULL_PATH}/bin/jmeter.sh" --version +} -echo "[CM-ANT] [Step 6/6] Configuring JMeter..." -sudo chmod +x "${JMETER_FULL_PATH}/bin/jmeter.sh" -"${JMETER_FULL_PATH}"/bin/jmeter.sh --version +# Execute steps +install_cmd_runner +install_plugin_manager +install_required_plugins +configure_jmeter -echo "[CM-ANT] Jmeter is completely installed on ${JMETER_FULL_PATH}" +echo "[CM-ANT] JMeter installation completed successfully at ${JMETER_FULL_PATH}" diff --git a/script/install-server-agent.sh b/script/install-server-agent.sh index 1e84329..2ff0cfe 100755 --- a/script/install-server-agent.sh +++ b/script/install-server-agent.sh @@ -1,29 +1,61 @@ #!/bin/bash + + set -e -AGENT_WORK_DIR="${AGENT_WORK_DIR:="/opt/perfmon-agent"}" + +AGENT_WORK_DIR="${AGENT_WORK_DIR:=/opt/perfmon-agent}" AGENT_VERSION="2.2.1" AGENT_FILE_NAME="ServerAgent-$AGENT_VERSION.zip" AGENT_DOWNLOAD_URL="https://github.com/undera/perfmon-agent/releases/download/$AGENT_VERSION/$AGENT_FILE_NAME" - TCP_PORT="${TCP_PORT:=5555}" -AUTO_SHUTDOWN="${AUTO_SHUTDOWN:=""}" - -sudo mkdir -p "$AGENT_WORK_DIR" -sudo apt-get update -y -sudo apt-get install -y wget unzip default-jre - -if [ -z "$(ls -A "$AGENT_WORK_DIR")" ]; then - echo "[CM-ANT] perfmon server agent is installing" - sudo wget "${AGENT_DOWNLOAD_URL}" -P "${AGENT_WORK_DIR}" - sudo unzip "${AGENT_WORK_DIR}/${AGENT_FILE_NAME}" -d "${AGENT_WORK_DIR}" - sudo rm "${AGENT_WORK_DIR}/${AGENT_FILE_NAME}" - echo "[CM-ANT] agent installed successfully!!!!" -fi - -if [ $? -eq 0 ] && [ -e "${AGENT_WORK_DIR}/startAgent.sh" ]; then - nohup "${AGENT_WORK_DIR}/startAgent.sh" --udp-port 0 --tcp-port $TCP_PORT $AUTO_SHUTDOWN > /dev/null 2>&1 & - echo "Agent started successfully in the background." -else - echo "Failed to start the agent." -fi +AUTO_SHUTDOWN="${AUTO_SHUTDOWN:=}" + + +terminate_existing_process() { + local pid + pid="$(lsof -t -i :$TCP_PORT)" || true + if [[ -n "$pid" ]]; then + kill -15 "$pid" + echo "Terminated process using port $TCP_PORT" + fi +} + + +install_dependencies() { + sudo apt-get update -y + sudo apt-get install -y wget unzip default-jre +} + + +install_agent() { + if [[ -z "$(ls -A "$AGENT_WORK_DIR")" ]]; then + echo "[CM-ANT] Installing perfmon server agent" + sudo wget "$AGENT_DOWNLOAD_URL" -P "$AGENT_WORK_DIR" + sudo unzip "$AGENT_WORK_DIR/$AGENT_FILE_NAME" -d "$AGENT_WORK_DIR" + sudo rm "$AGENT_WORK_DIR/$AGENT_FILE_NAME" + echo "[CM-ANT] Agent installed successfully" + fi +} + + +start_agent() { + if [[ -e "${AGENT_WORK_DIR}/startAgent.sh" ]]; then + nohup "${AGENT_WORK_DIR}/startAgent.sh" --udp-port 0 --tcp-port "$TCP_PORT" "$AUTO_SHUTDOWN" > /dev/null 2>&1 & + echo "Agent started successfully in the background." + else + echo "Failed to start the agent. startAgent.sh not found." + fi +} + + +main() { + terminate_existing_process + sudo mkdir -p "$AGENT_WORK_DIR" + install_dependencies + install_agent + start_agent +} + + +main diff --git a/script/remove-server-agent.sh b/script/remove-server-agent.sh index 61e75d3..3205a61 100755 --- a/script/remove-server-agent.sh +++ b/script/remove-server-agent.sh @@ -2,11 +2,20 @@ set -e -AGENT_WORK_DIR="${AGENT_WORK_DIR:="/opt/perfmon-agent"}" +# Set default values for variables if not provided +AGENT_WORK_DIR="${AGENT_WORK_DIR:=/opt/perfmon-agent}" TCP_PORT="${TCP_PORT:=5555}" -kill -15 "$(lsof -t -i :${TCP_PORT})" +# Check if a process is using the specified TCP port and terminate it if found +if lsof -t -i :"${TCP_PORT}" &>/dev/null; then + echo "Terminating process on port ${TCP_PORT}..." + kill -15 "$(lsof -t -i :"${TCP_PORT}")" +else + echo "No process found on port ${TCP_PORT}." +fi -sudo rm -rf $AGENT_WORK_DIR +# Remove the agent work directory with sudo privileges +echo "Removing agent work directory: ${AGENT_WORK_DIR}" +sudo rm -rf "${AGENT_WORK_DIR}" -echo "remove server agent completely!!" \ No newline at end of file +echo "Server agent removed completely!" diff --git a/script/uninstall-jmeter.sh b/script/uninstall-jmeter.sh index 3083151..29776ed 100755 --- a/script/uninstall-jmeter.sh +++ b/script/uninstall-jmeter.sh @@ -1,14 +1,13 @@ #!/bin/bash -echo "[CM-ANT] JMeter Uninstallation" +echo "[CM-ANT] Uninstalling JMeter..." set -e -# Base setup -JMETER_WORK_DIR=${JMETER_WORK_DIR:="/opt/ant/jmeter"} -JMETER_VERSION=${JMETER_VERSION:="5.6"} -JMETER_FOLDER="apache-jmeter-${JMETER_VERSION}" -JMETER_FULL_PATH="${JMETER_WORK_DIR}/${JMETER_FOLDER}" +# Default paths +JMETER_WORK_DIR="${JMETER_WORK_DIR:=/opt/ant/jmeter}" +JMETER_VERSION="${JMETER_VERSION:=5.6}" +JMETER_PATH="${JMETER_WORK_DIR}/apache-jmeter-${JMETER_VERSION}" - -sudo rm -rf "${JMETER_FULL_PATH}" -echo "[CM-ANT] Jmeter is completely uninstalled on system." +# Remove JMeter directory +sudo rm -rf "$JMETER_PATH" +echo "[CM-ANT] JMeter uninstalled."