Skip to content

Commit

Permalink
Merge pull request #52 from cloudfoundry/develop
Browse files Browse the repository at this point in the history
Merge Develop
  • Loading branch information
cdlliuy authored Nov 8, 2019
2 parents 09844b8 + 6d9e10f commit ec17088
Show file tree
Hide file tree
Showing 13 changed files with 1,807 additions and 881 deletions.
93 changes: 77 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ cf uninstall-plugin AutoScaler
| [autoscaling-policy, asp](#cf-autoscaling-policy) | Retrieve the scaling policy of an application |
| [attach-autoscaling-policy, aasp](#cf-attach-autoscaling-policy) | Attach a scaling policy to an application |
| [detach-autoscaling-policy, dasp](#cf-detach-autoscaling-policy) | Detach the scaling policy from an application |
| [create-autoscaling-credential, casc](#cf-create-autoscaling-credential) | Create custom metric credential for an application |
| [delete-autoscaling-credential, dasc](#cf-delete-autoscaling-credential) | Delete the custom metric credential of an application |
| [autoscaling-metrics, asm](#cf-autoscaling-metrics) | Retrieve the metrics of an application |
| [autoscaling-history, ash](#cf-autoscaling-history) | Retrieve the scaling history of an application|

Expand Down Expand Up @@ -139,13 +141,13 @@ Showing policy for app APP_NAME...
```
$ cf asp APP_NAME --output PATH_TO_FILE
Showing policy for app APP_NAME...
Saving policy for app APP_NAME to PATH_TO_FILE...
OK
```

### `cf attach-autoscaling-policy`

Attach a scaling policy to an application, the policy file must be a JSON file, refer to [policy specification](https://github.com/cloudfoundry-incubator/blob/master/docs/policy.md) for the policy format.
Attach a scaling policy to an application, the policy file must be a JSON file, refer to [policy specification](https://github.com/cloudfoundry/app-autoscaler/blob/develop/docs/policy.md) for the policy format.

```
cf attach-autoscaling-policy APP_NAME PATH_TO_POLICY_FILE
Expand All @@ -167,7 +169,7 @@ OK
Detach the scaling policy from an application, the policy will be **deleted** when detached.

```
cf detach-as-policy APP_NAME
cf detach-autoscaling-policy APP_NAME
```
#### ALIAS: dasp

Expand All @@ -180,27 +182,87 @@ OK
```


### `cf create-autoscaling-credential`

Credential is required when submitting custom metrics to app-autoscaler. If an application is connecting to autoscaler through a service binding approach, the required credential could be found in Cloud Foundry `VCAP_SERVICES` environment variables. Otherwise, you need to generate the required credential explicitly with this command.

The command will generate autoscaler credential and display it in JSON format. Then you need to set this credential to your application through environment variables or user-provided-service.

Note: Auto-scaler only grants access with the most recent credential, so the newly generated credential will overwritten the old pairs. Please make sure to update the credential setting in your application once you launch the command `create-autoscaling-credential`.

Random credential pair will be created by default when username and password are not specified by `--username` and `--password` option.

```
cf create-autoscaling-credential APP_NAME [--username USERNAME --password PASSWORD] [--output PATH_TO_FILE]
```
#### ALIAS: casc


#### OPTIONS:
- `--username, -u` : username of the custom metric credential, random username will be set if not specified
- `--password, -p` : password of the custom metric credential, random password will be set if not specified
- `--output` : Dump the credential to a file in JSON format

#### EXAMPLES:
- Create and view custom credential with user-defined username and password:
```
$ cf create-autoscaling-credential APP_NAME --username MY_USERNAME --password MY_PASSWORD
Creating custom metric credential for app APP_NAME...
{
"app_id": "<APP_ID>",
"username": "MY_USERNAME",
"password": "MY_PASSWORD",
"url": "https://autoscalermetrics.<DOMAIN>"
}
```
- Create random username and password and dump the credential to a file:
```
$ cf create-autoscaling-credential APP_NAME --output PATH_TO_FILE
Saving new created credential for app APP_NAME to PATH_TO_FILE...
OK
```


### `cf delete-autoscaling-credential`

Delete the custom metric credential of an application.

```
cf delete-autoscaling-credential APP_NAME
```
#### ALIAS: dasc

#### EXAMPLES:
```
$ cf delete-autoscaling-credential APP_NAME
Deleting custom metric credential for app APP_NAME...
OK
```


### `cf autoscaling-metrics`

Retrieve the aggregated metrics of an application. You can specify the start/end time or the number of the returned query result, and the display order(ascending or descending). The metrics will be shown in a table.
Retrieve the aggregated metrics of an application. You can specify the start/end time of the returned query result, and the display order(ascending or descending). The metrics will be shown in a table.

```
cf autoscaling-metrics APP_NAME METRIC_NAME [--number RECORD_NUMBER] [--start START_TIME] [--end END_TIME] [--desc] [--output PATH_TO_FILE]
cf autoscaling-metrics APP_NAME METRIC_NAME [--start START_TIME] [--end END_TIME] [--asc] [--output PATH_TO_FILE]
```
#### ALIAS: asm


#### OPTIONS:
- `METRIC_NAME` : available metric supported: memoryused, memoryutil, responsetime, throughput and cpu.
- `METRIC_NAME` : default metrics "memoryused, memoryutil, responsetime, throughput, cpu" or customized name for your own metrics.
- `--start` : start time of metrics collected with format `yyyy-MM-ddTHH:mm:ss+/-HH:mm` or `yyyy-MM-ddTHH:mm:ssZ`, default to very beginning if not specified.
- `--end` : end time of the metrics collected with format `yyyy-MM-ddTHH:mm:ss+/-HH:mm` or `yyyy-MM-ddTHH:mm:ssZ`, default to current time if not speficied.
- `--number|-n` : the number of the records to return, will be ignored if both start time and end time are specified.
- `--desc` : display in descending order, default to ascending order if not specified
- `--asc` : display in ascending order, default to descending order if not specified
- `--output` : dump the metrics to a file

#### EXAMPLES:
```
$ cf autoscaling-metrics APP_NAME memoryused --start 2018-12-27T11:49:00+08:00 --end 2018-12-27T11:52:20+08:00 --desc
$ cf autoscaling-metrics APP_NAME memoryused --start 2018-12-27T11:49:00+08:00 --end 2018-12-27T11:52:20+08:00 --asc
Retriving aggregated metrics for app APP_NAME...
Metrics Name Value Timestamp
Expand All @@ -216,29 +278,28 @@ memoryused 62MB 2018-12-27T11:51:40+08:00

### `cf autoscaling-history`

Retrieve the scaling event history of an application. You can specify the start/end time or the number of the returned query result, and the display order(ascending or descending). The scaling event history will be shown in a table.
Retrieve the scaling event history of an application. You can specify the start/end time of the returned query result, and the display order(ascending or descending). The scaling event history will be shown in a table.
```
cf autoscaling-history APP_NAME [--number RECORD_NUMBER] [--start START_TIME] [--end END_TIME] [--desc] [--output PATH_TO_FILE]
cf autoscaling-history APP_NAME [--start START_TIME] [--end END_TIME] [--asc] [--output PATH_TO_FILE]
```

#### ALIAS: ash

#### OPTIONS:
- `--start` : start time of the scaling history with format `yyyy-MM-ddTHH:mm:ss+/-HH:mm` or `yyyy-MM-ddTHH:mm:ssZ`, default to very beginning if not specified.
- `--end` : end time of the scaling history with format `yyyy-MM-ddTHH:mm:ss+/-HH:mm` or `yyyy-MM-ddTHH:mm:ssZ`, default to current time if not speficied.
- `--number|-n` : the number of the records to return, will be ignored if both start time and end time are specified.
- `--desc` : display in descending order, default to ascending order if not specified
- `--asc` : display in ascending order, default to descending order if not specified
- `--output` : dump the scaling history to a file

#### EXAMPLES:
```
$ cf autoscaling-history APP_NAME --start 2018-08-16T17:58:53+08:00 --end 2018-08-16T18:01:00+08:00 --number 3 --desc
$ cf autoscaling-history APP_NAME --start 2018-08-16T17:58:53+08:00 --end 2018-08-16T18:01:00+08:00 --asc
Showing history for app APP_NAME...
Scaling Type Status Instance Changes Time Action Error
scheduled succeeded 3->6 2018-08-16T18:00:00+08:00 3 instance(s) because limited by min instances 6
dynamic succeeded 2->3 2018-08-16T17:59:33+08:00 +1 instance(s) because memoryused >= 15MB for 120 seconds
dynamic failed 2->-1 2018-08-16T17:58:53+08:00 -1 instance(s) because throughput < 10rps for 120 seconds app does not have policy set
dynamic succeeded 2->3 2018-08-16T17:59:33+08:00 +1 instance(s) because memoryused >= 15MB for 120 seconds
scheduled succeeded 3->6 2018-08-16T18:00:00+08:00 3 instance(s) because limited by min instances 6
```
- `Scaling Type`: the trigger type of the scaling action, possible scaling types: `dynamic` and `scheduled`
- `dynamic`: the scaling action is triggered by a dynamic rule (memoryused, memoryutil, responsetime or throughput)
Expand Down
156 changes: 139 additions & 17 deletions src/cli/api/apihelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
const (
HealthPath = "/health"
PolicyPath = "/v1/apps/{appId}/policy"
CredentialPath = "/v1/apps/{appId}/credential"
AggregatedMetricPath = "/v1/apps/{appId}/aggregated_metric_histories/{metric_type}"
HistoryPath = "/v1/apps/{appId}/scaling_histories"
)
Expand Down Expand Up @@ -95,16 +96,24 @@ func (helper *APIHelper) DoRequest(req *http.Request) (*http.Response, error) {

}

func parseErrResponse(raw []byte) string {

var f interface{}
err := json.Unmarshal(raw, &f)
if err != nil {
return string(raw)
func parseErrArrayResponse(a []interface{}) string {
retMsg := ""
for _, entry := range a {
mentry := entry.(map[string]interface{})
var context, description string
for ik, iv := range mentry {
if ik == "context" {
context = iv.(string)
} else if ik == "description" {
description,_ = strconv.Unquote(strings.Replace(strconv.Quote(iv.(string)), `\\u`, `\u`, -1))
}
}
retMsg = retMsg + "\n" + fmt.Sprintf("%v: %v", context, description)
}
return retMsg
}

m := f.(map[string]interface{})

func parseErrObjectResponse(m map[string]interface{}) string {
retMsg := ""
for k, v := range m {
if k == "error" {
Expand All @@ -131,12 +140,31 @@ func parseErrResponse(raw []byte) string {
retMsg = fmt.Sprintf("%v", v)
}

} else if k == "message" {
retMsg = fmt.Sprintf("%v", v)
}
}

return retMsg
}

func parseErrResponse(raw []byte) string {

var f interface{}
err := json.Unmarshal(raw, &f)
if err != nil {
return string(raw)
}

switch f.(type) {
case map[string]interface{}:
return parseErrObjectResponse(f.(map[string]interface{}))
case []interface{}:
return parseErrArrayResponse(f.([]interface{}))
default:
return ""
}
}

func (helper *APIHelper) CheckHealth() error {
baseURL := helper.Endpoint.URL
requestURL := fmt.Sprintf("%s%s", baseURL, HealthPath)
Expand Down Expand Up @@ -294,7 +322,7 @@ func (helper *APIHelper) DeletePolicy() error {

}

func (helper *APIHelper) GetAggregatedMetrics(metricName string, startTime, endTime int64, desc bool, page uint64) (bool, [][]string, error) {
func (helper *APIHelper) GetAggregatedMetrics(metricName string, startTime, endTime int64, asc bool, page uint64) (bool, [][]string, error) {

if page <= 1 {
err := helper.CheckHealth()
Expand All @@ -317,10 +345,10 @@ func (helper *APIHelper) GetAggregatedMetrics(metricName string, startTime, endT
if endTime > 0 {
q.Add("end-time", strconv.FormatInt(endTime, 10))
}
if desc {
q.Add("order", "desc")
} else {
if asc {
q.Add("order", "asc")
} else {
q.Add("order", "desc")
}
q.Add("page", strconv.FormatUint(page, 10))
req.URL.RawQuery = q.Encode()
Expand Down Expand Up @@ -362,7 +390,7 @@ func (helper *APIHelper) GetAggregatedMetrics(metricName string, startTime, endT

}

func (helper *APIHelper) GetHistory(startTime, endTime int64, desc bool, page uint64) (bool, [][]string, error) {
func (helper *APIHelper) GetHistory(startTime, endTime int64, asc bool, page uint64) (bool, [][]string, error) {

if page <= 1 {
err := helper.CheckHealth()
Expand All @@ -383,10 +411,10 @@ func (helper *APIHelper) GetHistory(startTime, endTime int64, desc bool, page ui
if endTime > 0 {
q.Add("end-time", strconv.FormatInt(endTime, 10))
}
if desc {
q.Add("order", "desc")
} else {
if asc {
q.Add("order", "asc")
} else {
q.Add("order", "desc")
}
q.Add("page", strconv.FormatUint(page, 10))
req.URL.RawQuery = q.Encode()
Expand Down Expand Up @@ -449,3 +477,97 @@ func (helper *APIHelper) GetHistory(startTime, endTime int64, desc bool, page ui
}

}

func (helper *APIHelper) DeleteCredential() error {

err := helper.CheckHealth()
if err != nil {
return err
}

baseURL := helper.Endpoint.URL
requestURL := fmt.Sprintf("%s%s", baseURL, strings.Replace(CredentialPath, "{appId}", helper.Client.AppId, -1))

req, err := http.NewRequest("DELETE", requestURL, nil)
req.Header.Add("Authorization", helper.Client.AuthToken)

resp, err := helper.DoRequest(req)
if err != nil {
return err
}
defer resp.Body.Close()

raw, err := ioutil.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
var errorMsg string
switch resp.StatusCode {
case 401:
errorMsg = fmt.Sprintf(ui.Unauthorized, baseURL)
default:
errorMsg = parseErrResponse(raw)
}
return errors.New(errorMsg)
}

return nil

}

func (helper *APIHelper) CreateCredential(data interface{}) ([]byte, error) {

err := helper.CheckHealth()
if err != nil {
return nil, err
}

baseURL := helper.Endpoint.URL
requestURL := fmt.Sprintf("%s%s", baseURL, strings.Replace(CredentialPath, "{appId}", helper.Client.AppId, -1))

var body io.Reader
if data != nil {
jsonByte, e := json.Marshal(data)
if e != nil {
return nil, fmt.Errorf(ui.InvalidCredential, e)
}
body = bytes.NewBuffer(jsonByte)
}

req, err := http.NewRequest("PUT", requestURL, body)
req.Header.Add("Authorization", helper.Client.AuthToken)
req.Header.Add("Content-Type", "application/json")

resp, err := helper.DoRequest(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

raw, err := ioutil.ReadAll(resp.Body)

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {

var errorMsg string
switch resp.StatusCode {
case 401:
errorMsg = fmt.Sprintf(ui.Unauthorized, baseURL)
case 400:
errorMsg = fmt.Sprintf(ui.InvalidCredential, parseErrResponse(raw))
default:
errorMsg = parseErrResponse(raw)
}
return nil, errors.New(errorMsg)
}

var credential models.CredentialResponse
err = json.Unmarshal(raw, &credential)
if err != nil {
return nil, err
}

prettyCredential, err := cjson.MarshalWithoutHTMLEscape(credential)
if err != nil {
return nil, err
}

return prettyCredential, nil
}
Loading

0 comments on commit ec17088

Please sign in to comment.