diff --git a/receiver/huaweicloudcesreceiver/config.go b/receiver/huaweicloudcesreceiver/config.go index a4a356c0e914..e5583b1ee314 100644 --- a/receiver/huaweicloudcesreceiver/config.go +++ b/receiver/huaweicloudcesreceiver/config.go @@ -4,10 +4,25 @@ package huaweicloudcesreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver" import ( + "errors" + "fmt" + "slices" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/receiver/scraperhelper" + "go.uber.org/multierr" +) + +var ( + // Predefined error responses for configuration validation failures + errInvalidCollectionInterval = errors.New(`invalid period; must be less than "collection_interval"`) + errMissingProjectID = errors.New(`"project_id" is not specified in config`) + errMissingRegionID = errors.New(`"region_id" is not specified in config`) + errInvalidProxy = errors.New(`"proxy_address" must be specified if "proxy_user" or "proxy_password" is set"`) ) // Config represent a configuration for the CloudWatch logs exporter. @@ -63,3 +78,49 @@ type huaweiSessionConfig struct { ProxyUser string `mapstructure:"proxy_user"` ProxyPassword string `mapstructure:"proxy_password"` } + +var _ component.Config = (*Config)(nil) + +// These valid periods are defined by CES API constraints: https://support.huaweicloud.com/intl/en-us/api-ces/ces_03_0034.html#section3 +var validPeriods = []int32{1, 300, 1200, 3600, 14400, 86400} + +// These valid filters are defined by CES API constraints: https://support.huaweicloud.com/intl/en-us/api-ces/ces_03_0034.html#section3 +var validFilters = map[string]model.ShowMetricDataRequestFilter{ + "max": model.GetShowMetricDataRequestFilterEnum().MAX, + "min": model.GetShowMetricDataRequestFilterEnum().MIN, + "average": model.GetShowMetricDataRequestFilterEnum().AVERAGE, + "sum": model.GetShowMetricDataRequestFilterEnum().SUM, + "variance": model.GetShowMetricDataRequestFilterEnum().VARIANCE, +} + +// Validate config +func (config *Config) Validate() error { + var err error + if config.RegionID == "" { + err = multierr.Append(err, errMissingRegionID) + } + + if config.ProjectID == "" { + err = multierr.Append(err, errMissingProjectID) + } + if index := slices.Index(validPeriods, config.Period); index == -1 { + err = multierr.Append(err, fmt.Errorf("invalid period: got %d; must be one of %v", config.Period, validPeriods)) + } + if _, ok := validFilters[config.Filter]; !ok { + var validFiltersSlice []string + for key := range validFilters { + validFiltersSlice = append(validFiltersSlice, key) + } + err = multierr.Append(err, fmt.Errorf("invalid filter: got %s; must be one of %v", config.Filter, validFiltersSlice)) + } + if config.Period >= int32(config.CollectionInterval.Seconds()) { + err = multierr.Append(err, errInvalidCollectionInterval) + } + + // Validate that ProxyAddress is provided if ProxyUser or ProxyPassword is set + if (config.ProxyUser != "" || config.ProxyPassword != "") && config.ProxyAddress == "" { + err = multierr.Append(err, errInvalidProxy) + } + + return err +} diff --git a/receiver/huaweicloudcesreceiver/factory.go b/receiver/huaweicloudcesreceiver/factory.go index 63ed4582a84c..32c8de4006dc 100644 --- a/receiver/huaweicloudcesreceiver/factory.go +++ b/receiver/huaweicloudcesreceiver/factory.go @@ -41,9 +41,9 @@ func createDefaultConfig() component.Config { func createMetricsReceiver( _ context.Context, - _ receiver.Settings, - _ component.Config, - _ consumer.Metrics, + params receiver.Settings, + cfg component.Config, + next consumer.Metrics, ) (receiver.Metrics, error) { - return nil, nil + return newHuaweiCloudCesReceiver(params, cfg.(*Config), next), nil } diff --git a/receiver/huaweicloudcesreceiver/factory_test.go b/receiver/huaweicloudcesreceiver/factory_test.go new file mode 100644 index 000000000000..94f576ebdb26 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/factory_test.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package huaweicloudcesreceiver + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +func TestNewFactory(t *testing.T) { + factory := NewFactory() + assert.NotNil(t, factory) + assert.Equal(t, component.MustNewType("huaweicloudcesreceiver"), factory.Type()) +} + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + config := factory.CreateDefaultConfig() + assert.NotNil(t, config) + assert.NoError(t, componenttest.CheckConfigStruct(config)) +} + +func TestCreateMetricsReceiver(t *testing.T) { + factory := NewFactory() + config := factory.CreateDefaultConfig() + + rConfig := config.(*Config) + rConfig.CollectionInterval = 60 * time.Second + rConfig.InitialDelay = time.Second + + nextConsumer := new(consumertest.MetricsSink) + receiver, err := factory.CreateMetrics(context.Background(), receivertest.NewNopSettings(), config, nextConsumer) + assert.NoError(t, err) + assert.NotNil(t, receiver) +} diff --git a/receiver/huaweicloudcesreceiver/go.mod b/receiver/huaweicloudcesreceiver/go.mod index 0f1e9a78041d..556befa881d7 100644 --- a/receiver/huaweicloudcesreceiver/go.mod +++ b/receiver/huaweicloudcesreceiver/go.mod @@ -4,6 +4,9 @@ go 1.22.7 require ( github.com/cenkalti/backoff/v4 v4.3.0 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.113 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.114.0 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.114.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/collector/component v0.114.0 go.opentelemetry.io/collector/component/componenttest v0.114.0 @@ -11,11 +14,17 @@ require ( go.opentelemetry.io/collector/config/configopaque v1.20.0 go.opentelemetry.io/collector/config/configretry v1.20.0 go.opentelemetry.io/collector/consumer v0.114.0 + go.opentelemetry.io/collector/consumer/consumertest v0.114.0 + go.opentelemetry.io/collector/pdata v1.20.0 go.opentelemetry.io/collector/receiver v0.114.0 + go.opentelemetry.io/collector/receiver/receivertest v0.114.0 go.uber.org/goleak v1.3.0 + go.uber.org/multierr v1.11.0 + go.uber.org/zap v1.27.0 ) require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -28,32 +37,45 @@ require ( github.com/klauspost/compress v1.17.11 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.114.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + go.mongodb.org/mongo-driver v1.12.0 // indirect go.opentelemetry.io/collector/client v1.20.0 // indirect go.opentelemetry.io/collector/config/configauth v0.114.0 // indirect go.opentelemetry.io/collector/config/configcompression v1.20.0 // indirect go.opentelemetry.io/collector/config/configtelemetry v0.114.0 // indirect go.opentelemetry.io/collector/config/configtls v1.20.0 // indirect go.opentelemetry.io/collector/config/internal v0.114.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror v0.114.0 // indirect + go.opentelemetry.io/collector/consumer/consumerprofiles v0.114.0 // indirect go.opentelemetry.io/collector/extension v0.114.0 // indirect go.opentelemetry.io/collector/extension/auth v0.114.0 // indirect - go.opentelemetry.io/collector/pdata v1.20.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.114.0 // indirect go.opentelemetry.io/collector/pipeline v0.114.0 // indirect + go.opentelemetry.io/collector/receiver/receiverprofiles v0.114.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect go.opentelemetry.io/otel v1.32.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/net v0.31.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/grpc v1.68.0 // indirect google.golang.org/protobuf v1.35.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil => ../../pkg/pdatautil + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest => ../../pkg/pdatatest + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden => ../../pkg/golden diff --git a/receiver/huaweicloudcesreceiver/go.sum b/receiver/huaweicloudcesreceiver/go.sum index 34249c3daa0d..5794c0e3f99d 100644 --- a/receiver/huaweicloudcesreceiver/go.sum +++ b/receiver/huaweicloudcesreceiver/go.sum @@ -1,8 +1,18 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= @@ -14,19 +24,39 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.113 h1:odui9Ua0u1hPfpkutN/tGvtt0ms55I+gQqIdU8K1rlo= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.113/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -38,20 +68,38 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= +go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= go.opentelemetry.io/collector/client v1.20.0 h1:o60wPcj5nLtaRenF+1E5p4QXFS3TDL6vHlw+GOon3rg= go.opentelemetry.io/collector/client v1.20.0/go.mod h1:6aqkszco9FaLWCxyJEVam6PP7cUa8mPRIXeS5eZGj0U= go.opentelemetry.io/collector/component v0.114.0 h1:SVGbm5LvHGSTEDv7p92oPuBgK5tuiWR82I9+LL4TtBE= @@ -90,6 +138,8 @@ go.opentelemetry.io/collector/pdata v1.20.0 h1:ePcwt4bdtISP0loHaE+C9xYoU2ZkIvWv8 go.opentelemetry.io/collector/pdata v1.20.0/go.mod h1:Ox1YVLe87cZDB/TL30i4SUz1cA5s6AM6SpFMfY61ICs= go.opentelemetry.io/collector/pdata/pprofile v0.114.0 h1:pUNfTzsI/JUTiE+DScDM4lsrPoxnVNLI2fbTxR/oapo= go.opentelemetry.io/collector/pdata/pprofile v0.114.0/go.mod h1:4aNcj6WM1n1uXyFSXlhVs4ibrERgNYsTbzcYI2zGhxA= +go.opentelemetry.io/collector/pdata/testdata v0.114.0 h1:+AzszWSL1i4K6meQ8rU0JDDW55SYCXa6FVqfDixhhTo= +go.opentelemetry.io/collector/pdata/testdata v0.114.0/go.mod h1:bv8XFdCTZxG2MQB5l9dKxSxf5zBrcodwO6JOy1+AxXM= go.opentelemetry.io/collector/pipeline v0.114.0 h1:v3YOhc5z0tD6QbO5n/pnftpIeroihM2ks9Z2yKPCcwY= go.opentelemetry.io/collector/pipeline v0.114.0/go.mod h1:4vOvjVsoYTHVGTbfFwqfnQOSV2K3RKUHofh3jNRc2Mg= go.opentelemetry.io/collector/receiver v0.114.0 h1:90SAnXAjNq7/k52/pFmmb06Cf1YauoPYtbio4aOXafY= @@ -119,42 +169,119 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/receiver/huaweicloudcesreceiver/integration_test.go b/receiver/huaweicloudcesreceiver/integration_test.go new file mode 100644 index 000000000000..26eaa5984c76 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/integration_test.go @@ -0,0 +1,141 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:build integration + +package huaweicloudcesreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver" + +import ( + "context" + "path/filepath" + "testing" + "time" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver/internal/mocks" +) + +func TestHuaweiCloudCESReceiverIntegration(t *testing.T) { + mc := mocks.NewCesClient(t) + + mc.On("ListMetrics", mock.Anything).Return(&model.ListMetricsResponse{ + Metrics: &[]model.MetricInfoList{ + { + Namespace: "SYS.ECS", + MetricName: "cpu_util", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "faea5b75-e390-4e2b-8733-9226a9026070", + }, + }, + Unit: "%", + }, + { + Namespace: "SYS.ECS", + MetricName: "mem_util", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "abcea5b75-e390-4e2b-8733-9226a9026070", + }, + }, + Unit: "%", + }, + { + Namespace: "SYS.VPC", + MetricName: "upstream_bandwidth_usage", + Dimensions: []model.MetricsDimension{ + { + Name: "publicip_id", + Value: "faea5b75-e390-4e2b-8733-9226a9026070", + }, + }, + Unit: "%", + }, + }, + }, nil) + + mc.On("ShowMetricData", mock.Anything).Return(&model.ShowMetricDataResponse{ + MetricName: stringPtr("cpu_util"), + Datapoints: &[]model.Datapoint{ + { + Average: float64Ptr(10), + Timestamp: 1556625610000, + }, + { + Average: float64Ptr(20), + Timestamp: 1556625715000, + }, + }, + }, nil).Times(1) + mc.On("ShowMetricData", mock.Anything).Return(&model.ShowMetricDataResponse{ + MetricName: stringPtr("mem_util"), + Datapoints: &[]model.Datapoint{ + { + Average: float64Ptr(30), + Timestamp: 1556625610000, + }, + { + Average: float64Ptr(40), + Timestamp: 1556625715000, + }, + }, + }, nil).Times(1) + mc.On("ShowMetricData", mock.Anything).Return(&model.ShowMetricDataResponse{ + MetricName: stringPtr("upstream_bandwidth_usage"), + Datapoints: &[]model.Datapoint{ + { + Average: float64Ptr(50), + Timestamp: 1556625610000, + }, + { + Average: float64Ptr(60), + Timestamp: 1556625715000, + }, + }, + }, nil).Times(1) + + sink := &consumertest.MetricsSink{} + cfg := createDefaultConfig().(*Config) + cfg.RegionID = "us-east-2" + cfg.CollectionInterval = time.Second + cfg.ProjectID = "my-project" + cfg.Filter = "average" + + recv, err := NewFactory().CreateMetrics( + context.Background(), + receivertest.NewNopSettings(), + cfg, + sink, + ) + require.NoError(t, err) + + rcvr, ok := recv.(*cesReceiver) + require.True(t, ok) + rcvr.client = mc + + err = recv.Start(context.Background(), componenttest.NewNopHost()) + require.NoError(t, err) + + require.Eventually(t, func() bool { + return sink.DataPointCount() > 0 + }, 5*time.Second, 10*time.Millisecond) + + err = recv.Shutdown(context.Background()) + require.NoError(t, err) + + metrics := sink.AllMetrics()[0] + + expectedMetrics, err := golden.ReadMetrics(filepath.Join("testdata", "golden", "metrics_golden.yaml")) + require.NoError(t, err) + require.NoError(t, pmetrictest.CompareMetrics(expectedMetrics, metrics, pmetrictest.IgnoreResourceMetricsOrder())) +} diff --git a/receiver/huaweicloudcesreceiver/internal/backoff.go b/receiver/huaweicloudcesreceiver/internal/backoff.go new file mode 100644 index 000000000000..ac61ab164a26 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/internal/backoff.go @@ -0,0 +1,101 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver/internal" + +import ( + "context" + "fmt" + "time" + + "github.com/cenkalti/backoff/v4" + "go.opentelemetry.io/collector/config/configretry" + "go.uber.org/zap" +) + +func NewExponentialBackOff(backOffConfig *configretry.BackOffConfig) *backoff.ExponentialBackOff { + return &backoff.ExponentialBackOff{ + InitialInterval: backOffConfig.InitialInterval, + RandomizationFactor: backOffConfig.RandomizationFactor, + Multiplier: backOffConfig.Multiplier, + MaxInterval: backOffConfig.MaxInterval, + MaxElapsedTime: backOffConfig.MaxElapsedTime, + Stop: backoff.Stop, + Clock: backoff.SystemClock, + } +} + +// Generic function to make an API call with exponential backoff and context cancellation handling. +func MakeAPICallWithRetry[T any]( + ctx context.Context, + shutdownChan chan struct{}, + logger *zap.Logger, + apiCall func() (*T, error), + isThrottlingError func(error) bool, + backOffConfig *backoff.ExponentialBackOff, +) (*T, error) { + // Immediately check for context cancellation or server shutdown. + select { + case <-ctx.Done(): + return nil, fmt.Errorf("request was cancelled or timed out") + case <-shutdownChan: + return nil, fmt.Errorf("request is cancelled due to server shutdown") + case <-time.After(50 * time.Millisecond): + } + + // Make the initial API call. + resp, err := apiCall() + if err == nil { + return resp, nil + } + + // If the error is not due to request throttling, return the error. + if !isThrottlingError(err) { + return nil, err + } + + // Initialize the backoff mechanism for retrying the API call. + expBackoff := &backoff.ExponentialBackOff{ + InitialInterval: backOffConfig.InitialInterval, + RandomizationFactor: backOffConfig.RandomizationFactor, + Multiplier: backOffConfig.Multiplier, + MaxInterval: backOffConfig.MaxInterval, + MaxElapsedTime: backOffConfig.MaxElapsedTime, + Stop: backoff.Stop, + Clock: backoff.SystemClock, + } + expBackoff.Reset() + attempts := 0 + + // Retry loop for handling throttling errors. + for { + attempts++ + delay := expBackoff.NextBackOff() + if delay == backoff.Stop { + return resp, err + } + logger.Warn("server busy, retrying request", + zap.Int("attempts", attempts), + zap.Duration("delay", delay)) + + // Handle context cancellation or shutdown before retrying. + select { + case <-ctx.Done(): + return nil, fmt.Errorf("request was cancelled or timed out") + case <-shutdownChan: + return nil, fmt.Errorf("request is cancelled due to server shutdown") + case <-time.After(delay): + } + + // Retry the API call. + resp, err = apiCall() + if err == nil { + return resp, nil + } + if !isThrottlingError(err) { + break + } + } + + return nil, err +} diff --git a/receiver/huaweicloudcesreceiver/internal/backoff_test.go b/receiver/huaweicloudcesreceiver/internal/backoff_test.go new file mode 100644 index 000000000000..402a1f23ace8 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/internal/backoff_test.go @@ -0,0 +1,129 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/stretchr/testify/assert" + "go.uber.org/zap/zaptest" +) + +func TestMakeAPICallWithRetrySuccess(t *testing.T) { + logger := zaptest.NewLogger(t) + apiCall := func() (*string, error) { + result := "success" + return &result, nil + } + isThrottlingError := func(_ error) bool { + return false + } + + resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff()) + + assert.NoError(t, err) + assert.Equal(t, "success", *resp) +} + +func TestMakeAPICallWithRetryImmediateFailure(t *testing.T) { + logger := zaptest.NewLogger(t) + apiCall := func() (*string, error) { + return nil, errors.New("some error") + } + isThrottlingError := func(_ error) bool { + return false + } + + resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff()) + + assert.Error(t, err) + assert.Nil(t, resp) + assert.Equal(t, "some error", err.Error()) +} + +func TestMakeAPICallWithRetryThrottlingWithSuccess(t *testing.T) { + logger := zaptest.NewLogger(t) + callCount := 0 + apiCall := func() (*string, error) { + callCount++ + if callCount == 3 { + result := "success" + return &result, nil + } + return nil, errors.New("throttling error") + } + isThrottlingError := func(err error) bool { + return err.Error() == "throttling error" + } + + backOffConfig := backoff.NewExponentialBackOff() + backOffConfig.InitialInterval = 10 * time.Millisecond + + resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backOffConfig) + + assert.NoError(t, err) + assert.Equal(t, "success", *resp) + assert.Equal(t, 3, callCount) +} + +func TestMakeAPICallWithRetryThrottlingMaxRetries(t *testing.T) { + logger := zaptest.NewLogger(t) + apiCall := func() (*string, error) { + return nil, errors.New("throttling error") + } + isThrottlingError := func(err error) bool { + return err.Error() == "throttling error" + } + + backOffConfig := backoff.NewExponentialBackOff() + backOffConfig.MaxElapsedTime = 50 * time.Millisecond + + resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backOffConfig) + + assert.Error(t, err) + assert.Nil(t, resp) + assert.Equal(t, "throttling error", err.Error()) +} + +func TestMakeAPICallWithRetryContextCancellation(t *testing.T) { + logger := zaptest.NewLogger(t) + ctx, cancel := context.WithCancel(context.TODO()) + time.AfterFunc(time.Second, cancel) + + apiCall := func() (*string, error) { + return nil, errors.New("throttling error") + } + isThrottlingError := func(err error) bool { + return err.Error() == "throttling error" + } + + resp, err := MakeAPICallWithRetry(ctx, make(chan struct{}), logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff()) + + assert.Error(t, err) + assert.Nil(t, resp) + assert.Equal(t, "request was cancelled or timed out", err.Error()) +} + +func TestMakeAPICallWithRetryServerShutdown(t *testing.T) { + logger := zaptest.NewLogger(t) + shutdownChan := make(chan struct{}) + time.AfterFunc(time.Second, func() { close(shutdownChan) }) + + apiCall := func() (*string, error) { + return nil, errors.New("throttling error") + } + isThrottlingError := func(err error) bool { + return err.Error() == "throttling error" + } + + resp, err := MakeAPICallWithRetry(context.TODO(), shutdownChan, logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff()) + + assert.Error(t, err) + assert.Nil(t, resp) + assert.Equal(t, "request is cancelled due to server shutdown", err.Error()) +} diff --git a/receiver/huaweicloudcesreceiver/internal/ces_client.go b/receiver/huaweicloudcesreceiver/internal/ces_client.go new file mode 100644 index 000000000000..c57ae716389c --- /dev/null +++ b/receiver/huaweicloudcesreceiver/internal/ces_client.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver/internal" + +import ( + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" +) + +// The functions of the following interface should have the same signature as the ones from https://github.com/huaweicloud/huaweicloud-sdk-go-v3/blob/v0.1.113/services/ces/v1/ces_client.go +// Check https://github.com/vektra/mockery on how to install it on your machine. +// +//go:generate mockery --name CesClient --case=underscore --output=./mocks +type CesClient interface { + ListMetrics(request *model.ListMetricsRequest) (*model.ListMetricsResponse, error) + ShowMetricData(request *model.ShowMetricDataRequest) (*model.ShowMetricDataResponse, error) +} diff --git a/receiver/huaweicloudcesreceiver/internal/ces_to_otlp.go b/receiver/huaweicloudcesreceiver/internal/ces_to_otlp.go new file mode 100644 index 000000000000..2b776fa0ed39 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/internal/ces_to_otlp.go @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver/internal" + +import ( + "fmt" + "strings" + "time" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" +) + +type MetricData struct { + MetricName string + Dimensions []model.MetricsDimension + Namespace string + Unit string + Datapoints []model.Datapoint +} + +func GetMetricKey(m model.MetricInfoList) string { + strArray := make([]string, len(m.Dimensions)) + for i, ms := range m.Dimensions { + strArray[i] = ms.String() + } + return fmt.Sprintf("metric_name=%s,dimensions=%s", m.MetricName, strings.Join(strArray, " ")) +} + +func GetDimension(dimensions []model.MetricsDimension, index int) *string { + if len(dimensions) > index { + dimValue := dimensions[index].Name + "," + dimensions[index].Value + return &dimValue + } + return nil +} + +func ConvertCESMetricsToOTLP(projectID, regionID, filter string, cesMetrics map[string][]*MetricData) pmetric.Metrics { + metrics := pmetric.NewMetrics() + if len(cesMetrics) == 0 { + return metrics + } + resourceMetrics := metrics.ResourceMetrics() + + for namespace, cesMetrics := range cesMetrics { + resourceMetric := resourceMetrics.AppendEmpty() + resource := resourceMetric.Resource() + resourceAttr := resource.Attributes() + resourceAttr.PutStr("cloud.provider", "huawei_cloud") + resourceAttr.PutStr("project.id", projectID) + resourceAttr.PutStr("region.id", regionID) + resourceAttr.PutStr("service.namespace", namespace) + + scopedMetrics := resourceMetric.ScopeMetrics() + for _, cesMetric := range cesMetrics { + scopedMetric := scopedMetrics.AppendEmpty() + scopedMetric.Scope().SetName("huawei_cloud_ces") + scopedMetric.Scope().SetVersion("v1") + + metric := scopedMetric.Metrics().AppendEmpty() + metric.SetName(cesMetric.MetricName) + metric.SetUnit(cesMetric.Unit) + for _, dimension := range cesMetric.Dimensions { + metric.Metadata().PutStr(dimension.Name, dimension.Value) + } + + dataPoints := metric.SetEmptyGauge().DataPoints() + for _, dataPoint := range cesMetric.Datapoints { + dp := dataPoints.AppendEmpty() + dp.SetTimestamp(pcommon.NewTimestampFromTime(time.UnixMilli(dataPoint.Timestamp))) + switch filter { + case "max": + dp.SetDoubleValue(*dataPoint.Max) + case "min": + dp.SetDoubleValue(*dataPoint.Min) + case "average": + dp.SetDoubleValue(*dataPoint.Average) + case "sum": + dp.SetDoubleValue(*dataPoint.Sum) + case "variance": + dp.SetDoubleValue(*dataPoint.Variance) + } + } + } + } + + return metrics +} diff --git a/receiver/huaweicloudcesreceiver/internal/ces_to_otlp_test.go b/receiver/huaweicloudcesreceiver/internal/ces_to_otlp_test.go new file mode 100644 index 000000000000..ed1efd800960 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/internal/ces_to_otlp_test.go @@ -0,0 +1,208 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "testing" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/pmetric" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" +) + +const jsonBytes = `{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "cloud.provider", + "value": { + "stringValue": "huawei_cloud" + } + }, + { + "key": "project.id", + "value": { + "stringValue": "project_1" + } + }, + { + "key": "region.id", + "value": { + "stringValue": "eu-west-101" + } + }, + { + "key": "service.namespace", + "value": { + "stringValue": "SYS.ECS" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "gauge": { + "dataPoints": [ + { + "asDouble": 0.5, + "timeUnixNano": "1056625610000000000" + }, + { + "asDouble": 0.7, + "timeUnixNano": "1236625715000000000" + } + ] + }, + "metadata": [ + { + "key": "instance_id", + "value": { + "stringValue": "faea5b75-e390-4e2b-8733-9226a9026070" + } + } + ], + "name": "cpu_util", + "unit": "%" + } + ], + "scope": { + "name": "huawei_cloud_ces", + "version": "v1" + } + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "cloud.provider", + "value": { + "stringValue": "huawei_cloud" + } + }, + { + "key": "project.id", + "value": { + "stringValue": "project_1" + } + }, + { + "key": "region.id", + "value": { + "stringValue": "eu-west-101" + } + }, + { + "key": "service.namespace", + "value": { + "stringValue": "SYS.VPC" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "gauge": { + "dataPoints": [ + { + "asDouble": 1, + "timeUnixNano": "1056625612000000000" + }, + { + "asDouble": 3, + "timeUnixNano": "1256625717000000000" + } + ] + }, + "metadata": [ + { + "key": "instance_id", + "value": { + "stringValue": "06b4020f-461a-4a52-84da-53fa71c2f42b" + } + } + ], + "name": "network_vm_connections", + "unit": "count" + } + ], + "scope": { + "name": "huawei_cloud_ces", + "version": "v1" + } + } + ] + } + ] +}` + +func TestConvertCESMetricsToOTLP(t *testing.T) { + input := map[string][]*MetricData{ + "SYS.ECS": { + { + MetricName: "cpu_util", + Namespace: "SYS.ECS", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "faea5b75-e390-4e2b-8733-9226a9026070", + }, + }, + Datapoints: []model.Datapoint{ + { + Average: float64Ptr(0.5), + Timestamp: 1056625610000, + }, + { + Average: float64Ptr(0.7), + Timestamp: 1236625715000, + }, + }, + Unit: "%", + }, + }, + "SYS.VPC": { + { + MetricName: "network_vm_connections", + Namespace: "SYS.VPC", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "06b4020f-461a-4a52-84da-53fa71c2f42b", + }, + }, + Datapoints: []model.Datapoint{ + { + Average: float64Ptr(1), + Timestamp: 1056625612000, + }, + { + Average: float64Ptr(3), + Timestamp: 1256625717000, + }, + }, + Unit: "count", + }, + }, + } + unm := pmetric.JSONUnmarshaler{} + expectedMetrics, err := unm.UnmarshalMetrics([]byte(jsonBytes)) + require.NoError(t, err) + got := ConvertCESMetricsToOTLP("project_1", "eu-west-101", "average", input) + assert.NoError(t, pmetrictest.CompareMetrics(expectedMetrics, got, pmetrictest.IgnoreResourceMetricsOrder())) +} + +func float64Ptr(f float64) *float64 { + return &f +} diff --git a/receiver/huaweicloudcesreceiver/internal/mocks/ces_client.go b/receiver/huaweicloudcesreceiver/internal/mocks/ces_client.go new file mode 100644 index 000000000000..fbd4b6132044 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/internal/mocks/ces_client.go @@ -0,0 +1,87 @@ +// Code generated by mockery v2.44.1. DO NOT EDIT. + +package mocks + +import ( + model "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" + mock "github.com/stretchr/testify/mock" +) + +// CesClient is an autogenerated mock type for the CesClient type +type CesClient struct { + mock.Mock +} + +// ListMetrics provides a mock function with given fields: request +func (_m *CesClient) ListMetrics(request *model.ListMetricsRequest) (*model.ListMetricsResponse, error) { + ret := _m.Called(request) + + if len(ret) == 0 { + panic("no return value specified for ListMetrics") + } + + var r0 *model.ListMetricsResponse + var r1 error + if rf, ok := ret.Get(0).(func(*model.ListMetricsRequest) (*model.ListMetricsResponse, error)); ok { + return rf(request) + } + if rf, ok := ret.Get(0).(func(*model.ListMetricsRequest) *model.ListMetricsResponse); ok { + r0 = rf(request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.ListMetricsResponse) + } + } + + if rf, ok := ret.Get(1).(func(*model.ListMetricsRequest) error); ok { + r1 = rf(request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ShowMetricData provides a mock function with given fields: request +func (_m *CesClient) ShowMetricData(request *model.ShowMetricDataRequest) (*model.ShowMetricDataResponse, error) { + ret := _m.Called(request) + + if len(ret) == 0 { + panic("no return value specified for ShowMetricData") + } + + var r0 *model.ShowMetricDataResponse + var r1 error + if rf, ok := ret.Get(0).(func(*model.ShowMetricDataRequest) (*model.ShowMetricDataResponse, error)); ok { + return rf(request) + } + if rf, ok := ret.Get(0).(func(*model.ShowMetricDataRequest) *model.ShowMetricDataResponse); ok { + r0 = rf(request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.ShowMetricDataResponse) + } + } + + if rf, ok := ret.Get(1).(func(*model.ShowMetricDataRequest) error); ok { + r1 = rf(request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewCesClient creates a new instance of CesClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCesClient(t interface { + mock.TestingT + Cleanup(func()) +}) *CesClient { + mock := &CesClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/receiver/huaweicloudcesreceiver/metadata.yaml b/receiver/huaweicloudcesreceiver/metadata.yaml index 53e902feb1de..31b50e271989 100644 --- a/receiver/huaweicloudcesreceiver/metadata.yaml +++ b/receiver/huaweicloudcesreceiver/metadata.yaml @@ -7,7 +7,3 @@ status: distributions: [] codeowners: active: [heitorganzeli, narcis96, mwear] - -tests: - skip_lifecycle: true - skip_shutdown: true \ No newline at end of file diff --git a/receiver/huaweicloudcesreceiver/receiver.go b/receiver/huaweicloudcesreceiver/receiver.go new file mode 100644 index 000000000000..52fe0cf3f9fb --- /dev/null +++ b/receiver/huaweicloudcesreceiver/receiver.go @@ -0,0 +1,260 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package huaweicloudcesreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver" + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" + ces "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/region" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" + + internal "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver/internal" +) + +const ( + // See https://support.huaweicloud.com/intl/en-us/devg-apisign/api-sign-errorcode.html + requestThrottledErrMsg = "APIGW.0308" +) + +type cesReceiver struct { + logger *zap.Logger + client internal.CesClient + cancel context.CancelFunc + + host component.Host + nextConsumer consumer.Metrics + lastSeenTs map[string]time.Time + config *Config + shutdownChan chan struct{} +} + +func newHuaweiCloudCesReceiver(settings receiver.Settings, cfg *Config, next consumer.Metrics) *cesReceiver { + rcvr := &cesReceiver{ + logger: settings.Logger, + config: cfg, + nextConsumer: next, + lastSeenTs: make(map[string]time.Time), + shutdownChan: make(chan struct{}, 1), + } + return rcvr +} + +func (rcvr *cesReceiver) Start(ctx context.Context, host component.Host) error { + rcvr.host = host + ctx, rcvr.cancel = context.WithCancel(ctx) + + if rcvr.client == nil { + client, err := rcvr.createClient() + if err != nil { + rcvr.logger.Error(err.Error()) + return nil + } + rcvr.client = client + } + + go rcvr.startReadingMetrics(ctx) + return nil +} + +func (rcvr *cesReceiver) startReadingMetrics(ctx context.Context) { + if rcvr.config.InitialDelay > 0 { + <-time.After(rcvr.config.InitialDelay) + } + if err := rcvr.pollMetricsAndConsume(ctx); err != nil { + rcvr.logger.Error(err.Error()) + } + ticker := time.NewTicker(rcvr.config.CollectionInterval) + + defer ticker.Stop() + for { + select { + case <-ticker.C: + // TODO: Improve error handling for client-server interactions + // The current implementation lacks robust error handling, especially for + // scenarios such as service unavailability, timeouts, and request errors. + // - Investigate how to handle service unavailability or timeouts gracefully. + // - Implement appropriate actions or retries for different types of request errors. + // - Refer to the Huawei SDK documentation to identify + // all possible client/request errors and determine how to manage them. + // - Consider implementing custom error messages or fallback mechanisms for critical failures. + + if err := rcvr.pollMetricsAndConsume(ctx); err != nil { + rcvr.logger.Error(err.Error()) + } + case <-ctx.Done(): + return + } + } +} + +func (rcvr *cesReceiver) createClient() (*ces.CesClient, error) { + auth, err := basic.NewCredentialsBuilder(). + WithAk(string(rcvr.config.AccessKey)). + WithSk(string(rcvr.config.SecretKey)). + WithProjectId(rcvr.config.ProjectID). + SafeBuild() + if err != nil { + return nil, err + } + + httpConfig, err := createHTTPConfig(rcvr.config.huaweiSessionConfig) + if err != nil { + return nil, err + } + r, err := region.SafeValueOf(rcvr.config.RegionID) + if err != nil { + return nil, err + } + + hcHTTPConfig, err := ces.CesClientBuilder(). + WithRegion(r). + WithCredential(auth). + WithHttpConfig(httpConfig). + SafeBuild() + if err != nil { + return nil, err + } + + client := ces.NewCesClient(hcHTTPConfig) + + return client, nil +} + +func (rcvr *cesReceiver) pollMetricsAndConsume(ctx context.Context) error { + if rcvr.client == nil { + return errors.New("invalid client") + } + metricDefinitions, err := rcvr.listMetricDefinitions(ctx) + if err != nil { + return err + } + metrics := rcvr.listDataPoints(ctx, metricDefinitions) + otpMetrics := internal.ConvertCESMetricsToOTLP(rcvr.config.ProjectID, rcvr.config.RegionID, rcvr.config.Filter, metrics) + if err := rcvr.nextConsumer.ConsumeMetrics(ctx, otpMetrics); err != nil { + return err + } + return nil +} + +func (rcvr *cesReceiver) listMetricDefinitions(ctx context.Context) ([]model.MetricInfoList, error) { + response, err := internal.MakeAPICallWithRetry( + ctx, + rcvr.shutdownChan, + rcvr.logger, + func() (*model.ListMetricsResponse, error) { + return rcvr.client.ListMetrics(&model.ListMetricsRequest{}) + }, + func(err error) bool { return strings.Contains(err.Error(), requestThrottledErrMsg) }, + internal.NewExponentialBackOff(&rcvr.config.BackOffConfig), + ) + if err != nil { + return []model.MetricInfoList{}, err + } + if response == nil || response.Metrics == nil || len((*response.Metrics)) == 0 { + return []model.MetricInfoList{}, errors.New("unexpected empty list of metric definitions") + } + + return *response.Metrics, nil +} + +// listDataPoints retrieves data points for a list of metric definitions. +// The function performs the following operations: +// 1. Generates a unique key for each metric definition (at least one dimenstion is required) and checks for duplicates. +// 2. Determines the time range (from-to) for fetching the metric data points, using the current timestamp +// and the last-seen timestamp for each metric. +// 3. Fetches data points for each metric definition. +// 4. Updates the last-seen timestamp for each metric based on the most recent data point timestamp. +// 5. Returns a map of metric keys to their corresponding MetricData, containing all fetched data points. +// +// Parameters: +// - ctx: Context for controlling cancellation and deadlines. +// - metricDefinitions: A slice of MetricInfoList containing the definitions of metrics to be fetched. +// +// Returns: +// - A map where each key is a unique metric identifier and each value is the associated MetricData. +func (rcvr *cesReceiver) listDataPoints(ctx context.Context, metricDefinitions []model.MetricInfoList) map[string][]*internal.MetricData { + // TODO: Implement deduplication: There may be a need for deduplication, possibly using a Processor to ensure unique metrics are processed. + to := time.Now() + metrics := make(map[string][]*internal.MetricData) + for _, metricDefinition := range metricDefinitions { + if len(metricDefinition.Dimensions) == 0 { + rcvr.logger.Warn("metric has 0 dimensions. skipping it", zap.String("metricName", metricDefinition.MetricName)) + continue + } + key := internal.GetMetricKey(metricDefinition) + from, ok := rcvr.lastSeenTs[key] + if !ok { + from = to.Add(-1 * rcvr.config.CollectionInterval) + } + resp, dpErr := rcvr.listDataPointsForMetric(ctx, from, to, metricDefinition) + if dpErr != nil { + rcvr.logger.Warn(fmt.Sprintf("unable to get datapoints for metric name %+v", metricDefinition), zap.Error(dpErr)) + } + var datapoints []model.Datapoint + if resp != nil && resp.Datapoints != nil { + datapoints = *resp.Datapoints + + var maxdpTs int64 + for _, dp := range datapoints { + if dp.Timestamp > maxdpTs { + maxdpTs = dp.Timestamp + } + } + if maxdpTs > rcvr.lastSeenTs[key].UnixMilli() { + rcvr.lastSeenTs[key] = time.UnixMilli(maxdpTs) + } + } + metrics[metricDefinition.Namespace] = append(metrics[metricDefinition.Namespace], &internal.MetricData{ + MetricName: metricDefinition.MetricName, + Dimensions: metricDefinition.Dimensions, + Namespace: metricDefinition.Namespace, + Unit: metricDefinition.Unit, + Datapoints: datapoints, + }) + } + return metrics +} + +func (rcvr *cesReceiver) listDataPointsForMetric(ctx context.Context, from, to time.Time, infoList model.MetricInfoList) (*model.ShowMetricDataResponse, error) { + return internal.MakeAPICallWithRetry( + ctx, + rcvr.shutdownChan, + rcvr.logger, + func() (*model.ShowMetricDataResponse, error) { + return rcvr.client.ShowMetricData(&model.ShowMetricDataRequest{ + Namespace: infoList.Namespace, + MetricName: infoList.MetricName, + Dim0: infoList.Dimensions[0].Name + "," + infoList.Dimensions[0].Value, + Dim1: internal.GetDimension(infoList.Dimensions, 1), + Dim2: internal.GetDimension(infoList.Dimensions, 2), + Dim3: internal.GetDimension(infoList.Dimensions, 3), + Period: rcvr.config.Period, + Filter: validFilters[rcvr.config.Filter], + From: from.UnixMilli(), + To: to.UnixMilli(), + }) + }, + func(err error) bool { return strings.Contains(err.Error(), requestThrottledErrMsg) }, + internal.NewExponentialBackOff(&rcvr.config.BackOffConfig), + ) +} + +func (rcvr *cesReceiver) Shutdown(_ context.Context) error { + if rcvr.cancel != nil { + rcvr.cancel() + } + rcvr.shutdownChan <- struct{}{} + close(rcvr.shutdownChan) + return nil +} diff --git a/receiver/huaweicloudcesreceiver/receiver_test.go b/receiver/huaweicloudcesreceiver/receiver_test.go new file mode 100644 index 000000000000..267f0f699a6d --- /dev/null +++ b/receiver/huaweicloudcesreceiver/receiver_test.go @@ -0,0 +1,276 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package huaweicloudcesreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver" + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ces/v1/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config/configretry" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" + "go.opentelemetry.io/collector/receiver/scraperhelper" + "go.uber.org/zap/zaptest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver/internal/mocks" +) + +func stringPtr(s string) *string { + return &s +} + +func float64Ptr(f float64) *float64 { + return &f +} + +func TestNewReceiver(t *testing.T) { + cfg := &Config{ + ControllerConfig: scraperhelper.ControllerConfig{ + CollectionInterval: 1 * time.Second, + }, + } + mr := newHuaweiCloudCesReceiver(receivertest.NewNopSettings(), cfg, new(consumertest.MetricsSink)) + assert.NotNil(t, mr) +} + +func TestListMetricDefinitionsSuccess(t *testing.T) { + mockCes := mocks.NewCesClient(t) + + mockResponse := &model.ListMetricsResponse{ + Metrics: &[]model.MetricInfoList{ + { + Namespace: "SYS.ECS", + MetricName: "cpu_util", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "12345", + }, + }, + }, + }, + } + + mockCes.On("ListMetrics", mock.Anything).Return(mockResponse, nil) + + receiver := &cesReceiver{ + client: mockCes, + config: createDefaultConfig().(*Config), + } + + metrics, err := receiver.listMetricDefinitions(context.Background()) + + assert.NoError(t, err) + assert.NotNil(t, metrics) + assert.Equal(t, "SYS.ECS", metrics[0].Namespace) + assert.Equal(t, "cpu_util", metrics[0].MetricName) + assert.Equal(t, "instance_id", metrics[0].Dimensions[0].Name) + assert.Equal(t, "12345", metrics[0].Dimensions[0].Value) + mockCes.AssertExpectations(t) +} + +func TestListMetricDefinitionsFailure(t *testing.T) { + mockCes := mocks.NewCesClient(t) + + mockCes.On("ListMetrics", mock.Anything).Return(nil, errors.New("failed to list metrics")) + receiver := &cesReceiver{ + client: mockCes, + config: createDefaultConfig().(*Config), + } + + metrics, err := receiver.listMetricDefinitions(context.Background()) + + assert.Error(t, err) + assert.Empty(t, metrics) + assert.Equal(t, "failed to list metrics", err.Error()) + mockCes.AssertExpectations(t) +} + +func TestListDataPointsForMetricBackOffWIthDefaultConfig(t *testing.T) { + mockCes := mocks.NewCesClient(t) + next := new(consumertest.MetricsSink) + receiver := newHuaweiCloudCesReceiver(receivertest.NewNopSettings(), createDefaultConfig().(*Config), next) + receiver.client = mockCes + + mockCes.On("ShowMetricData", mock.Anything).Return(nil, errors.New(requestThrottledErrMsg)).Times(3) + mockCes.On("ShowMetricData", mock.Anything).Return(&model.ShowMetricDataResponse{ + MetricName: stringPtr("cpu_util"), + Datapoints: &[]model.Datapoint{ + { + Average: float64Ptr(45.67), + Timestamp: 1556625610000, + }, + { + Average: float64Ptr(89.01), + Timestamp: 1556625715000, + }, + }, + }, nil) + + resp, err := receiver.listDataPointsForMetric(context.Background(), time.Now().Add(10*time.Minute), time.Now(), model.MetricInfoList{ + Namespace: "SYS.ECS", + MetricName: "cpu_util", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "12345", + }, + }, + }) + + require.NoError(t, err) + assert.Len(t, *resp.Datapoints, 2) +} + +func TestListDataPointsForMetricBackOffFails(t *testing.T) { + mockCes := mocks.NewCesClient(t) + next := new(consumertest.MetricsSink) + receiver := newHuaweiCloudCesReceiver(receivertest.NewNopSettings(), &Config{BackOffConfig: configretry.BackOffConfig{ + Enabled: true, + InitialInterval: 100 * time.Millisecond, + MaxInterval: 800 * time.Millisecond, + MaxElapsedTime: 1 * time.Second, + RandomizationFactor: 0, + Multiplier: 2, + }}, next) + receiver.client = mockCes + + mockCes.On("ShowMetricData", mock.Anything).Return(nil, errors.New(requestThrottledErrMsg)).Times(4) + + resp, err := receiver.listDataPointsForMetric(context.Background(), time.Now().Add(10*time.Minute), time.Now(), model.MetricInfoList{ + Namespace: "SYS.ECS", + MetricName: "cpu_util", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "12345", + }, + }, + }) + + require.ErrorContains(t, err, requestThrottledErrMsg) + assert.Nil(t, resp) +} + +func TestPollMetricsAndConsumeSuccess(t *testing.T) { + mockCes := mocks.NewCesClient(t) + next := new(consumertest.MetricsSink) + receiver := newHuaweiCloudCesReceiver(receivertest.NewNopSettings(), &Config{}, next) + receiver.client = mockCes + + mockCes.On("ListMetrics", mock.Anything).Return(&model.ListMetricsResponse{ + Metrics: &[]model.MetricInfoList{ + { + Namespace: "SYS.ECS", + MetricName: "cpu_util", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "12345", + }, + }, + }, + }, + }, nil) + + mockCes.On("ShowMetricData", mock.Anything).Return(&model.ShowMetricDataResponse{ + MetricName: stringPtr("cpu_util"), + Datapoints: &[]model.Datapoint{ + { + Average: float64Ptr(45.67), + Timestamp: 1556625610000, + }, + { + Average: float64Ptr(89.01), + Timestamp: 1556625715000, + }, + }, + }, nil) + + err := receiver.pollMetricsAndConsume(context.Background()) + + require.NoError(t, err) + assert.Equal(t, 2, next.DataPointCount()) +} + +func TestStartReadingMetrics(t *testing.T) { + tests := []struct { + name string + scrapeInterval time.Duration + setupMocks func(*mocks.CesClient) + expectedNumOfDataPoints int + }{ + { + name: "Success case with valid scrape interval", + scrapeInterval: 2 * time.Second, + setupMocks: func(m *mocks.CesClient) { + m.On("ListMetrics", mock.Anything).Return(&model.ListMetricsResponse{ + Metrics: &[]model.MetricInfoList{ + { + Namespace: "SYS.ECS", + MetricName: "cpu_util", + Dimensions: []model.MetricsDimension{ + { + Name: "instance_id", + Value: "12345", + }, + }, + }, + }, + }, nil) + + m.On("ShowMetricData", mock.Anything).Return(&model.ShowMetricDataResponse{ + MetricName: stringPtr("cpu_util"), + Datapoints: &[]model.Datapoint{ + { + Average: float64Ptr(45.67), + Timestamp: 1556625610000, + }, + }, + }, nil) + }, + expectedNumOfDataPoints: 1, + }, + { + name: "Error case with Scrape returning error", + scrapeInterval: 1 * time.Second, + setupMocks: func(m *mocks.CesClient) { + m.On("ListMetrics", mock.Anything).Return(nil, errors.New("server error")) + }, + expectedNumOfDataPoints: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCes := mocks.NewCesClient(t) + next := new(consumertest.MetricsSink) + tt.setupMocks(mockCes) + logger := zaptest.NewLogger(t) + r := &cesReceiver{ + config: &Config{ + ControllerConfig: scraperhelper.ControllerConfig{ + CollectionInterval: tt.scrapeInterval, + InitialDelay: 10 * time.Millisecond, + }, + }, + client: mockCes, + logger: logger, + nextConsumer: next, + lastSeenTs: make(map[string]time.Time), + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r.startReadingMetrics(ctx) + + assert.Equal(t, tt.expectedNumOfDataPoints, next.DataPointCount()) + }) + } +} diff --git a/receiver/huaweicloudcesreceiver/session_config.go b/receiver/huaweicloudcesreceiver/session_config.go new file mode 100644 index 000000000000..c0da09e5fd76 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/session_config.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package huaweicloudcesreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver" + +import ( + "net/url" + "strconv" + + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/config" +) + +func createHTTPConfig(cfg huaweiSessionConfig) (*config.HttpConfig, error) { + if cfg.ProxyAddress == "" { + return config.DefaultHttpConfig().WithIgnoreSSLVerification(cfg.NoVerifySSL), nil + } + proxy, err := configureHTTPProxy(cfg) + if err != nil { + return nil, err + } + return config.DefaultHttpConfig().WithProxy(proxy), nil +} + +func configureHTTPProxy(cfg huaweiSessionConfig) (*config.Proxy, error) { + proxyURL, err := url.Parse(cfg.ProxyAddress) + if err != nil { + return nil, err + } + + proxy := config.NewProxy(). + WithSchema(proxyURL.Scheme). + WithHost(proxyURL.Hostname()) + if len(proxyURL.Port()) > 0 { + if i, err := strconv.Atoi(proxyURL.Port()); err == nil { + proxy = proxy.WithPort(i) + } + } + + // Configure the username and password if the proxy requires authentication + if len(cfg.ProxyUser) > 0 { + proxy = proxy.WithUsername(cfg.ProxyUser).WithPassword(cfg.ProxyPassword) + } + return proxy, nil +} diff --git a/receiver/huaweicloudcesreceiver/session_config_test.go b/receiver/huaweicloudcesreceiver/session_config_test.go new file mode 100644 index 000000000000..68b01d9a0675 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/session_config_test.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package huaweicloudcesreceiver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateHTTPConfigNoVerifySSL(t *testing.T) { + cfg, err := createHTTPConfig(huaweiSessionConfig{NoVerifySSL: true}) + require.NoError(t, err) + assert.True(t, cfg.IgnoreSSLVerification) +} + +func TestCreateHTTPConfigWithProxy(t *testing.T) { + cfg, err := createHTTPConfig(huaweiSessionConfig{ + ProxyAddress: "https://127.0.0.1:8888", + ProxyUser: "admin", + ProxyPassword: "pass", + AccessKey: "123", + SecretKey: "secret", + }) + require.NoError(t, err) + assert.Equal(t, "https", cfg.HttpProxy.Schema) + assert.Equal(t, "127.0.0.1", cfg.HttpProxy.Host) + assert.Equal(t, 8888, cfg.HttpProxy.Port) + assert.False(t, cfg.IgnoreSSLVerification) +} diff --git a/receiver/huaweicloudcesreceiver/testdata/golden/metrics_golden.yaml b/receiver/huaweicloudcesreceiver/testdata/golden/metrics_golden.yaml new file mode 100644 index 000000000000..568ab79f6271 --- /dev/null +++ b/receiver/huaweicloudcesreceiver/testdata/golden/metrics_golden.yaml @@ -0,0 +1,79 @@ +resourceMetrics: + - resource: + attributes: + - key: cloud.provider + value: + stringValue: huawei_cloud + - key: project.id + value: + stringValue: my-project + - key: region.id + value: + stringValue: us-east-2 + - key: service.namespace + value: + stringValue: SYS.ECS + scopeMetrics: + - scope: + name: huawei_cloud_ces + version: v1 + metrics: + - name: cpu_util + gauge: + dataPoints: + - asDouble: 10 + timeUnixNano: "1556625610000000000" + - asDouble: 20 + timeUnixNano: "1556625715000000000" + unit: "%" + metadata: + - key: instance_id + value: + stringValue: faea5b75-e390-4e2b-8733-9226a9026070 + - scope: + name: huawei_cloud_ces + version: v1 + metrics: + - name: mem_util + gauge: + dataPoints: + - asDouble: 30 + timeUnixNano: "1556625610000000000" + - asDouble: 40 + timeUnixNano: "1556625715000000000" + unit: "%" + metadata: + - key: instance_id + value: + stringValue: abcea5b75-e390-4e2b-8733-9226a9026070 + - resource: + attributes: + - key: cloud.provider + value: + stringValue: huawei_cloud + - key: project.id + value: + stringValue: my-project + - key: region.id + value: + stringValue: us-east-2 + - key: service.namespace + value: + stringValue: SYS.VPC + scopeMetrics: + - scope: + name: huawei_cloud_ces + version: v1 + metrics: + - name: upstream_bandwidth_usage + gauge: + dataPoints: + - asDouble: 50 + timeUnixNano: "1556625610000000000" + - asDouble: 60 + timeUnixNano: "1556625715000000000" + unit: "%" + metadata: + - key: publicip_id + value: + stringValue: faea5b75-e390-4e2b-8733-9226a9026070