Skip to content

Commit

Permalink
Provide support for Huawei Cloud CloudEye scaler (#478)
Browse files Browse the repository at this point in the history
* add support haiweicloud ces

* add type assertion  for cloudeye metric data

* support different metrics of the same instance

* add test file
  • Loading branch information
iyacontrol authored and ahmelsayed committed Dec 4, 2019
1 parent fe34db4 commit 1cb5be1
Show file tree
Hide file tree
Showing 5 changed files with 491 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
github.com/Azure/azure-storage-blob-go v0.8.0
github.com/Azure/azure-storage-queue-go v0.0.0-20190416192124-a17745f1cdbf
github.com/Azure/go-autorest v12.0.0+incompatible
github.com/Huawei/gophercloud v0.0.0-20190806033045-3f2c8f6aa160
github.com/Shopify/sarama v1.23.1
github.com/aws/aws-sdk-go v1.25.6
github.com/go-logr/logr v0.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 h1:2T/jmrHeTezcCM58lvEQXs0UpQJCo5SoGAcg+mbSTIg=
github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Huawei/gophercloud v0.0.0-20190806033045-3f2c8f6aa160 h1:2PTY/4OWLFl3/JmjJ0KWiPRNfi6DugNdphaomxy5Ro4=
github.com/Huawei/gophercloud v0.0.0-20190806033045-3f2c8f6aa160/go.mod h1:TUtAO2PE+Nj7/QdfUXbhi5Xu0uFKVccyukPA7UCxD9w=
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
Expand Down
2 changes: 2 additions & 0 deletions pkg/handler/scale_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ func (h *ScaleHandler) getScaler(name, namespace, triggerType string, resolvedEn
return scalers.NewLiiklusScaler(resolvedEnv, triggerMetadata)
case "stan":
return scalers.NewStanScaler(resolvedEnv, triggerMetadata)
case "huawei-cloudeye":
return scalers.NewHuaweiCloudeyeScaler(triggerMetadata, authParams)
default:
return nil, fmt.Errorf("no scaler found for type: %s", triggerType)
}
Expand Down
339 changes: 339 additions & 0 deletions pkg/scalers/huawei_cloudeye_scaler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
package scalers

import (
"context"
"fmt"
"strconv"
"strings"
"time"

"github.com/Huawei/gophercloud"
"github.com/Huawei/gophercloud/auth/aksk"
"github.com/Huawei/gophercloud/openstack"
"github.com/Huawei/gophercloud/openstack/ces/v1/metricdata"
"k8s.io/api/autoscaling/v2beta1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/metrics/pkg/apis/external_metrics"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

const (
defaultCloudeyeMetricCollectionTime = 300
defaultCloudeyeMetricFilter = "average"
defaultCloudeyeMetricPeriod = "300"

defaultHuaweiCloud = "myhuaweicloud.com"
)

type huaweiCloudeyeScaler struct {
metadata *huaweiCloudeyeMetadata
}

type huaweiCloudeyeMetadata struct {
namespace string
metricsName string
dimensionName string
dimensionValue string

targetMetricValue float64
minMetricValue float64

metricCollectionTime int64
metricFilter string
metricPeriod string

huaweiAuthorization huaweiAuthorizationMetadata
}

type huaweiAuthorizationMetadata struct {
IdentityEndpoint string

// user project id
ProjectID string

DomainID string

// region
Region string

//Cloud name
Domain string

//Cloud name
Cloud string

AccessKey string //Access Key
SecretKey string //Secret key
}

var cloudeyeLog = logf.Log.WithName("huawei_cloudeye_scaler")

// NewHuaweiCloudeyeScaler creates a new huaweiCloudeyeScaler
func NewHuaweiCloudeyeScaler(metadata, authParams map[string]string) (Scaler, error) {
meta, err := parseHuaweiCloudeyeMetadata(metadata, authParams)
if err != nil {
return nil, fmt.Errorf("Error parsing Cloudeye metadata: %s", err)
}

return &huaweiCloudeyeScaler{
metadata: meta,
}, nil
}

func parseHuaweiCloudeyeMetadata(metadata, authParams map[string]string) (*huaweiCloudeyeMetadata, error) {
meta := huaweiCloudeyeMetadata{}

meta.metricCollectionTime = defaultCloudeyeMetricCollectionTime
meta.metricFilter = defaultCloudeyeMetricFilter
meta.metricPeriod = defaultCloudeyeMetricPeriod

if val, ok := metadata["namespace"]; ok && val != "" {
meta.namespace = val
} else {
return nil, fmt.Errorf("Namespace not given")
}

if val, ok := metadata["metricName"]; ok && val != "" {
meta.metricsName = val
} else {
return nil, fmt.Errorf("Metric Name not given")
}

if val, ok := metadata["dimensionName"]; ok && val != "" {
meta.dimensionName = val
} else {
return nil, fmt.Errorf("Dimension Name not given")
}

if val, ok := metadata["dimensionValue"]; ok && val != "" {
meta.dimensionValue = val
} else {
return nil, fmt.Errorf("Dimension Value not given")
}

if val, ok := metadata["targetMetricValue"]; ok && val != "" {
targetMetricValue, err := strconv.ParseFloat(val, 64)
if err != nil {
cloudeyeLog.Error(err, "Error parsing targetMetricValue metadata")
} else {
meta.targetMetricValue = targetMetricValue
}
} else {
return nil, fmt.Errorf("target Metric Value not given")
}

if val, ok := metadata["minMetricValue"]; ok && val != "" {
minMetricValue, err := strconv.ParseFloat(val, 64)
if err != nil {
cloudeyeLog.Error(err, "Error parsing minMetricValue metadata")
} else {
meta.minMetricValue = minMetricValue
}
} else {
return nil, fmt.Errorf("Min Metric Value not given")
}

if val, ok := metadata["metricCollectionTime"]; ok && val != "" {
metricCollectionTime, err := strconv.Atoi(val)
if err != nil {
cloudeyeLog.Error(err, "Error parsing metricCollectionTime metadata")
} else {
meta.metricCollectionTime = int64(metricCollectionTime)
}
}

if val, ok := metadata["metricFilter"]; ok && val != "" {
meta.metricFilter = val
}

if val, ok := metadata["metricPeriod"]; ok && val != "" {
_, err := strconv.Atoi(val)
if err != nil {
cloudeyeLog.Error(err, "Error parsing metricPeriod metadata")
} else {
meta.metricPeriod = val
}
}

auth, err := gethuaweiAuthorization(authParams)
if err != nil {
return nil, err
}

meta.huaweiAuthorization = auth

return &meta, nil
}

func gethuaweiAuthorization(authParams map[string]string) (huaweiAuthorizationMetadata, error) {
meta := huaweiAuthorizationMetadata{}

if authParams["IdentityEndpoint"] != "" {
meta.IdentityEndpoint = authParams["IdentityEndpoint"]
} else {
return meta, fmt.Errorf("IdentityEndpoint doesn't exist in the authParams")
}

if authParams["ProjectID"] != "" {
meta.ProjectID = authParams["ProjectID"]
} else {
return meta, fmt.Errorf("ProjectID doesn't exist in the authParams")
}

if authParams["DomainID"] != "" {
meta.DomainID = authParams["DomainID"]
} else {
return meta, fmt.Errorf("DomainID doesn't exist in the authParams")
}

if authParams["Region"] != "" {
meta.Region = authParams["Region"]
} else {
return meta, fmt.Errorf("Region doesn't exist in the authParams")
}

if authParams["Domain"] != "" {
meta.Domain = authParams["Domain"]
} else {
return meta, fmt.Errorf("Domain doesn't exist in the authParams")
}

if authParams["Cloud"] != "" {
meta.Cloud = authParams["Cloud"]
} else {
meta.Cloud = defaultHuaweiCloud
}

if authParams["AccessKey"] != "" {
meta.AccessKey = authParams["AccessKey"]
} else {
return meta, fmt.Errorf("AccessKey doesn't exist in the authParams")
}

if authParams["SecretKey"] != "" {
meta.SecretKey = authParams["SecretKey"]
} else {
return meta, fmt.Errorf("SecretKey doesn't exist in the authParams")
}

return meta, nil
}

func (h *huaweiCloudeyeScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) {
metricValue, err := h.GetCloudeyeMetrics()

if err != nil {
cloudeyeLog.Error(err, "Error getting metric value")
return []external_metrics.ExternalMetricValue{}, err
}

metric := external_metrics.ExternalMetricValue{
MetricName: metricName,
Value: *resource.NewQuantity(int64(metricValue), resource.DecimalSI),
Timestamp: metav1.Now(),
}

return append([]external_metrics.ExternalMetricValue{}, metric), nil
}

func (h *huaweiCloudeyeScaler) GetMetricSpecForScaling() []v2beta1.MetricSpec {
targetMetricValue := resource.NewQuantity(int64(h.metadata.targetMetricValue), resource.DecimalSI)
externalMetric := &v2beta1.ExternalMetricSource{MetricName: fmt.Sprintf("%s-%s-%s-%s", strings.ReplaceAll(h.metadata.namespace, ".", "-"),
h.metadata.metricsName,
h.metadata.dimensionName, h.metadata.dimensionValue),
TargetAverageValue: targetMetricValue}
metricSpec := v2beta1.MetricSpec{External: externalMetric, Type: externalMetricType}
return []v2beta1.MetricSpec{metricSpec}
}

func (h *huaweiCloudeyeScaler) IsActive(ctx context.Context) (bool, error) {
val, err := h.GetCloudeyeMetrics()

if err != nil {
return false, err
}

return val > h.metadata.minMetricValue, nil
}

func (h *huaweiCloudeyeScaler) Close() error {
return nil
}

func (h *huaweiCloudeyeScaler) GetCloudeyeMetrics() (float64, error) {
options := aksk.AKSKOptions{
IdentityEndpoint: h.metadata.huaweiAuthorization.IdentityEndpoint,
ProjectID: h.metadata.huaweiAuthorization.ProjectID,
AccessKey: h.metadata.huaweiAuthorization.AccessKey,
SecretKey: h.metadata.huaweiAuthorization.SecretKey,
Region: h.metadata.huaweiAuthorization.Region,
Domain: h.metadata.huaweiAuthorization.Domain,
DomainID: h.metadata.huaweiAuthorization.DomainID,
Cloud: h.metadata.huaweiAuthorization.Cloud,
}

provider, err := openstack.AuthenticatedClient(options)
if err != nil {
cloudeyeLog.Error(err, "Failed to get the provider")
return -1, err
}
sc, err := openstack.NewCESV1(provider, gophercloud.EndpointOpts{})

if err != nil {
cloudeyeLog.Error(err, "get ces client failed")
if ue, ok := err.(*gophercloud.UnifiedError); ok {
cloudeyeLog.Info("ErrCode:", ue.ErrorCode())
cloudeyeLog.Info("Message:", ue.Message())
}
return -1, err
}

opts := metricdata.BatchQueryOpts{
Metrics: []metricdata.Metric{
{
Namespace: h.metadata.namespace,
Dimensions: []map[string]string{
{
"name": h.metadata.dimensionName,
"value": h.metadata.dimensionValue,
},
},
MetricName: h.metadata.metricsName,
},
},
From: time.Now().Add(time.Second*-1*time.Duration(h.metadata.metricCollectionTime)).UnixNano() / 1e6,
To: time.Now().UnixNano() / 1e6,
Period: h.metadata.metricPeriod,
Filter: h.metadata.metricFilter,
}

metricdatas, err := metricdata.BatchQuery(sc, opts).ExtractMetricDatas()
if err != nil {
cloudeyeLog.Error(err, "query metrics failed")
if ue, ok := err.(*gophercloud.UnifiedError); ok {
cloudeyeLog.Info("ErrCode:", ue.ErrorCode())
cloudeyeLog.Info("Message:", ue.Message())
}
return -1, err
}

cloudeyeLog.V(1).Info("Received Metric Data", "data", metricdatas)

var metricValue float64

if metricdatas[0].Datapoints != nil {
v, ok := metricdatas[0].Datapoints[0][h.metadata.metricFilter].(float64)
if ok {
metricValue = v
} else {
return -1, fmt.Errorf("Metric Data not float64")
}
} else {
return -1, fmt.Errorf("Metric Data not received")
}

return metricValue, nil

}
Loading

0 comments on commit 1cb5be1

Please # to comment.