Skip to content
This repository was archived by the owner on Jul 19, 2023. It is now read-only.

Commit 4e6a951

Browse files
authored
Merge pull request #437 from scottzhlin/feature/support-cos-objstore-provider
feat(objstore): support Tencent COS object storage
2 parents 6a67c7b + 2bd3609 commit 4e6a951

File tree

7 files changed

+288
-18
lines changed

7 files changed

+288
-18
lines changed

docs/sources/operators-guide/configure/reference-configuration-parameters/index.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ tracing:
177177

178178
storage:
179179
# Backend storage to use. Supported backends are: s3, gcs, azure, swift,
180-
# filesystem.
180+
# filesystem, cos.
181181
# CLI flag: -storage.backend
182182
[backend: <string> | default = "filesystem"]
183183

@@ -197,6 +197,69 @@ storage:
197197
# Object Storage (Swift) object storage backend.
198198
[swift: <swift_storage_backend>]
199199

200+
cos:
201+
# COS bucket name
202+
# CLI flag: -storage.cos.bucket
203+
[bucket: <string> | default = ""]
204+
205+
# COS region name
206+
# CLI flag: -storage.cos.region
207+
[region: <string> | default = ""]
208+
209+
# COS app id
210+
# CLI flag: -storage.cos.app-id
211+
[app_id: <string> | default = ""]
212+
213+
# COS storage endpoint
214+
# CLI flag: -storage.cos.endpoint
215+
[endpoint: <string> | default = ""]
216+
217+
# COS secret key
218+
# CLI flag: -storage.cos.secret-key
219+
[secret_key: <string> | default = ""]
220+
221+
# COS secret id
222+
# CLI flag: -storage.cos.secret-id
223+
[secret_id: <string> | default = ""]
224+
225+
http:
226+
# The time an idle connection will remain idle before closing.
227+
# CLI flag: -storage.cos.http.idle-conn-timeout
228+
[idle_conn_timeout: <duration> | default = 1m30s]
229+
230+
# The amount of time the client will wait for a servers response headers.
231+
# CLI flag: -storage.cos.http.response-header-timeout
232+
[response_header_timeout: <duration> | default = 2m]
233+
234+
# If the client connects to COS via HTTPS and this option is enabled, the
235+
# client will accept any certificate and hostname.
236+
# CLI flag: -storage.cos.http.insecure-skip-verify
237+
[insecure_skip_verify: <boolean> | default = false]
238+
239+
# Maximum time to wait for a TLS handshake. 0 means no limit.
240+
# CLI flag: -storage.cos.tls-handshake-timeout
241+
[tls_handshake_timeout: <duration> | default = 10s]
242+
243+
# The time to wait for a server's first response headers after fully
244+
# writing the request headers if the request has an Expect header. 0 to
245+
# send the request body immediately.
246+
# CLI flag: -storage.cos.expect-continue-timeout
247+
[expect_continue_timeout: <duration> | default = 1s]
248+
249+
# Maximum number of idle (keep-alive) connections across all hosts. 0
250+
# means no limit.
251+
# CLI flag: -storage.cos.max-idle-connections
252+
[max_idle_connections: <int> | default = 100]
253+
254+
# Maximum number of idle (keep-alive) connections to keep per-host. If 0,
255+
# a built-in default value is used.
256+
# CLI flag: -storage.cos.max-idle-connections-per-host
257+
[max_idle_connections_per_host: <int> | default = 100]
258+
259+
# Maximum number of connections per host. 0 means no limit.
260+
# CLI flag: -storage.cos.max-connections-per-host
261+
[max_connections_per_host: <int> | default = 0]
262+
200263
# The filesystem_storage_backend block configures the usage of local file
201264
# system as object storage backend.
202265
[filesystem: <filesystem_storage_backend>]

pkg/gen/google/v1/profile.pb.go

Lines changed: 4 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/objstore/client/config.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/thanos-io/objstore"
1313

1414
"github.com/grafana/phlare/pkg/objstore/providers/azure"
15+
"github.com/grafana/phlare/pkg/objstore/providers/cos"
1516
"github.com/grafana/phlare/pkg/objstore/providers/filesystem"
1617
"github.com/grafana/phlare/pkg/objstore/providers/gcs"
1718
"github.com/grafana/phlare/pkg/objstore/providers/s3"
@@ -31,6 +32,9 @@ const (
3132
// Swift is the value for the Openstack Swift storage backend.
3233
Swift = "swift"
3334

35+
// COS is the value for the Tencent Cloud COS storage backend.
36+
COS = "cos"
37+
3438
// Filesystem is the value for the filesystem storage backend.
3539
Filesystem = "filesystem"
3640

@@ -39,7 +43,7 @@ const (
3943
)
4044

4145
var (
42-
SupportedBackends = []string{S3, GCS, Azure, Swift, Filesystem}
46+
SupportedBackends = []string{S3, GCS, Azure, Swift, Filesystem, COS}
4347

4448
ErrUnsupportedStorageBackend = errors.New("unsupported storage backend")
4549
ErrInvalidCharactersInStoragePrefix = errors.New("storage prefix contains invalid characters, it may only contain digits and English alphabet letters")
@@ -53,6 +57,7 @@ type StorageBackendConfig struct {
5357
GCS gcs.Config `yaml:"gcs"`
5458
Azure azure.Config `yaml:"azure"`
5559
Swift swift.Config `yaml:"swift"`
60+
COS cos.Config `yaml:"cos"`
5661
Filesystem filesystem.Config `yaml:"filesystem"`
5762
}
5863

@@ -72,6 +77,7 @@ func (cfg *StorageBackendConfig) RegisterFlagsWithPrefixAndDefaultDirectory(pref
7277
cfg.Azure.RegisterFlagsWithPrefix(prefix, f, logger)
7378
cfg.Swift.RegisterFlagsWithPrefix(prefix, f)
7479
cfg.Filesystem.RegisterFlagsWithPrefixAndDefaultDirectory(prefix, dir, f)
80+
cfg.COS.RegisterFlagsWithPrefix(prefix, f)
7581
f.StringVar(&cfg.Backend, prefix+"backend", Filesystem, fmt.Sprintf("Backend storage to use. Supported backends are: %s.", strings.Join(cfg.supportedBackends(), ", ")))
7682
}
7783

@@ -84,13 +90,14 @@ func (cfg *StorageBackendConfig) Validate() error {
8490
return ErrUnsupportedStorageBackend
8591
}
8692

87-
if cfg.Backend == S3 {
88-
if err := cfg.S3.Validate(); err != nil {
89-
return err
90-
}
93+
switch cfg.Backend {
94+
case S3:
95+
return cfg.S3.Validate()
96+
case COS:
97+
return cfg.COS.Validate()
98+
default:
99+
return nil
91100
}
92-
93-
return nil
94101
}
95102

96103
// Config holds configuration for accessing long-term storage.

pkg/objstore/client/factory.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import (
44
"context"
55

66
"github.com/prometheus/client_golang/prometheus"
7-
87
"github.com/thanos-io/objstore"
98

109
phlareobjstore "github.com/grafana/phlare/pkg/objstore"
1110
"github.com/grafana/phlare/pkg/objstore/client/parquet"
1211
"github.com/grafana/phlare/pkg/objstore/providers/azure"
12+
"github.com/grafana/phlare/pkg/objstore/providers/cos"
1313
"github.com/grafana/phlare/pkg/objstore/providers/filesystem"
1414
"github.com/grafana/phlare/pkg/objstore/providers/gcs"
1515
"github.com/grafana/phlare/pkg/objstore/providers/s3"
@@ -35,6 +35,8 @@ func NewBucket(ctx context.Context, cfg Config, name string) (phlareobjstore.Buc
3535
backendClient, err = azure.NewBucketClient(cfg.Azure, name, logger)
3636
case Swift:
3737
backendClient, err = swift.NewBucketClient(cfg.Swift, name, logger)
38+
case COS:
39+
backendClient, err = cos.NewBucketClient(cfg.COS, name, logger)
3840
case Filesystem:
3941
backendClient, err = filesystem.NewBucket(cfg.Filesystem.Directory)
4042
default:
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package cos
2+
3+
import (
4+
"github.com/go-kit/log"
5+
"github.com/prometheus/common/model"
6+
"github.com/thanos-io/objstore"
7+
"github.com/thanos-io/objstore/exthttp"
8+
"github.com/thanos-io/objstore/providers/cos"
9+
"gopkg.in/yaml.v3"
10+
)
11+
12+
// NewBucketClient creates a bucket client for COS
13+
func NewBucketClient(cfg Config, name string, logger log.Logger) (objstore.Bucket, error) {
14+
bucketConfig := &cos.Config{
15+
Bucket: cfg.Bucket,
16+
Region: cfg.Region,
17+
AppId: cfg.AppID,
18+
Endpoint: cfg.Endpoint,
19+
SecretKey: cfg.SecretKey,
20+
SecretId: cfg.SecretID,
21+
HTTPConfig: exthttp.HTTPConfig{
22+
IdleConnTimeout: model.Duration(cfg.HTTP.IdleConnTimeout),
23+
ResponseHeaderTimeout: model.Duration(cfg.HTTP.ResponseHeaderTimeout),
24+
InsecureSkipVerify: cfg.HTTP.InsecureSkipVerify,
25+
TLSHandshakeTimeout: model.Duration(cfg.HTTP.TLSHandshakeTimeout),
26+
ExpectContinueTimeout: model.Duration(cfg.HTTP.ExpectContinueTimeout),
27+
MaxIdleConns: cfg.HTTP.MaxIdleConns,
28+
MaxIdleConnsPerHost: cfg.HTTP.MaxIdleConnsPerHost,
29+
MaxConnsPerHost: cfg.HTTP.MaxConnsPerHost,
30+
Transport: cfg.HTTP.Transport,
31+
},
32+
}
33+
34+
serializedConfig, err := yaml.Marshal(bucketConfig)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
return cos.NewBucket(logger, serializedConfig, name)
40+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package cos
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
"time"
10+
)
11+
12+
// Config encapsulates the necessary config values to instantiate an cos client.
13+
type Config struct {
14+
Bucket string `yaml:"bucket"`
15+
Region string `yaml:"region"`
16+
AppID string `yaml:"app_id"`
17+
Endpoint string `yaml:"endpoint"`
18+
SecretKey string `yaml:"secret_key"`
19+
SecretID string `yaml:"secret_id"`
20+
HTTP HTTPConfig `yaml:"http"`
21+
}
22+
23+
// Validate validates cos client config and returns error on failure
24+
func (c *Config) Validate() error {
25+
if len(c.Endpoint) != 0 {
26+
if _, err := url.Parse(c.Endpoint); err != nil {
27+
return fmt.Errorf("cos config: failed to parse endpoint: %w", err)
28+
}
29+
30+
if empty(c.SecretKey) || empty(c.SecretID) {
31+
return errors.New("secret id and secret key cannot be empty")
32+
}
33+
return nil
34+
}
35+
36+
if empty(c.Bucket) || empty(c.AppID) || empty(c.Region) || empty(c.SecretID) || empty(c.SecretKey) {
37+
return errors.New("invalid cos configuration, bucket, app_id, region, secret_id and secret_key must be set")
38+
}
39+
return nil
40+
}
41+
42+
func empty(s string) bool {
43+
return len(s) == 0
44+
}
45+
46+
// RegisterFlags registers the flags for COS storage
47+
func (c *Config) RegisterFlags(f *flag.FlagSet) {
48+
c.RegisterFlagsWithPrefix("", f)
49+
}
50+
51+
// RegisterFlagsWithPrefix register the flags for COS storage with provided prefix
52+
func (c *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
53+
f.StringVar(&c.Bucket, prefix+"cos.bucket", "", "COS bucket name")
54+
f.StringVar(&c.Region, prefix+"cos.region", "", "COS region name")
55+
f.StringVar(&c.AppID, prefix+"cos.app-id", "", "COS app id")
56+
f.StringVar(&c.Endpoint, prefix+"cos.endpoint", "", "COS storage endpoint")
57+
f.StringVar(&c.SecretID, prefix+"cos.secret-id", "", "COS secret id")
58+
f.StringVar(&c.SecretKey, prefix+"cos.secret-key", "", "COS secret key")
59+
c.HTTP.RegisterFlagsWithPrefix(prefix, f)
60+
}
61+
62+
// HTTPConfig stores the http.Transport configuration for the COS client.
63+
type HTTPConfig struct {
64+
IdleConnTimeout time.Duration `yaml:"idle_conn_timeout" category:"advanced"`
65+
ResponseHeaderTimeout time.Duration `yaml:"response_header_timeout" category:"advanced"`
66+
InsecureSkipVerify bool `yaml:"insecure_skip_verify" category:"advanced"`
67+
TLSHandshakeTimeout time.Duration `yaml:"tls_handshake_timeout" category:"advanced"`
68+
ExpectContinueTimeout time.Duration `yaml:"expect_continue_timeout" category:"advanced"`
69+
MaxIdleConns int `yaml:"max_idle_connections" category:"advanced"`
70+
MaxIdleConnsPerHost int `yaml:"max_idle_connections_per_host" category:"advanced"`
71+
MaxConnsPerHost int `yaml:"max_connections_per_host" category:"advanced"`
72+
73+
// Allow upstream callers to inject a round tripper
74+
Transport http.RoundTripper `yaml:"-"`
75+
}
76+
77+
// RegisterFlagsWithPrefix registers the flags for COS storage with the provided prefix
78+
func (cfg *HTTPConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
79+
f.DurationVar(&cfg.IdleConnTimeout, prefix+"cos.http.idle-conn-timeout", 90*time.Second, "The time an idle connection will remain idle before closing.")
80+
f.DurationVar(&cfg.ResponseHeaderTimeout, prefix+"cos.http.response-header-timeout", 2*time.Minute, "The amount of time the client will wait for a servers response headers.")
81+
f.BoolVar(&cfg.InsecureSkipVerify, prefix+"cos.http.insecure-skip-verify", false, "If the client connects to COS via HTTPS and this option is enabled, the client will accept any certificate and hostname.")
82+
f.DurationVar(&cfg.TLSHandshakeTimeout, prefix+"cos.tls-handshake-timeout", 10*time.Second, "Maximum time to wait for a TLS handshake. 0 means no limit.")
83+
f.DurationVar(&cfg.ExpectContinueTimeout, prefix+"cos.expect-continue-timeout", 1*time.Second, "The time to wait for a server's first response headers after fully writing the request headers if the request has an Expect header. 0 to send the request body immediately.")
84+
f.IntVar(&cfg.MaxIdleConns, prefix+"cos.max-idle-connections", 100, "Maximum number of idle (keep-alive) connections across all hosts. 0 means no limit.")
85+
f.IntVar(&cfg.MaxIdleConnsPerHost, prefix+"cos.max-idle-connections-per-host", 100, "Maximum number of idle (keep-alive) connections to keep per-host. If 0, a built-in default value is used.")
86+
f.IntVar(&cfg.MaxConnsPerHost, prefix+"cos.max-connections-per-host", 0, "Maximum number of connections per host. 0 means no limit.")
87+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package cos
2+
3+
import "testing"
4+
5+
func TestConfig_Validate(t *testing.T) {
6+
type fields struct {
7+
Bucket string
8+
Region string
9+
AppID string
10+
Endpoint string
11+
SecretKey string
12+
SecretID string
13+
HTTP HTTPConfig
14+
}
15+
tests := []struct {
16+
name string
17+
fields fields
18+
wantErr bool
19+
}{
20+
{
21+
name: "ok endpoint",
22+
fields: fields{
23+
Endpoint: "http://bucket-123.cos.ap-beijing.myqcloud.com",
24+
SecretID: "sid",
25+
SecretKey: "skey",
26+
},
27+
wantErr: false,
28+
},
29+
{
30+
name: "ok bucket-AppID-region",
31+
fields: fields{
32+
Bucket: "bucket",
33+
AppID: "123",
34+
Region: "ap-beijing",
35+
SecretID: "sid",
36+
SecretKey: "skey",
37+
},
38+
wantErr: false,
39+
},
40+
{
41+
name: "missing skey",
42+
fields: fields{
43+
Bucket: "bucket",
44+
AppID: "123",
45+
Region: "ap-beijing",
46+
},
47+
wantErr: true,
48+
},
49+
{
50+
name: "missing bucket",
51+
fields: fields{
52+
AppID: "123",
53+
Region: "ap-beijing",
54+
SecretID: "sid",
55+
SecretKey: "skey",
56+
},
57+
wantErr: true,
58+
},
59+
}
60+
for _, tt := range tests {
61+
t.Run(tt.name, func(t *testing.T) {
62+
c := &Config{
63+
Bucket: tt.fields.Bucket,
64+
Region: tt.fields.Region,
65+
AppID: tt.fields.AppID,
66+
Endpoint: tt.fields.Endpoint,
67+
SecretKey: tt.fields.SecretKey,
68+
SecretID: tt.fields.SecretID,
69+
HTTP: tt.fields.HTTP,
70+
}
71+
if err := c.Validate(); (err != nil) != tt.wantErr {
72+
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
73+
}
74+
})
75+
}
76+
}

0 commit comments

Comments
 (0)