Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide support for Huawei Cloud CloudEye scaler #478

Merged
merged 4 commits into from
Dec 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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