mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
5.5 KiB
191 lines
5.5 KiB
|
4 years ago
|
package openstack
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"flag"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"io/ioutil"
|
||
|
4 years ago
|
"net/http"
|
||
|
|
"time"
|
||
|
4 years ago
|
|
||
|
|
"github.com/ncw/swift"
|
||
|
4 years ago
|
"github.com/pkg/errors"
|
||
|
4 years ago
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
4 years ago
|
|
||
|
4 years ago
|
bucket_swift "github.com/grafana/loki/pkg/storage/bucket/swift"
|
||
|
4 years ago
|
"github.com/grafana/loki/pkg/storage/chunk/client"
|
||
|
|
"github.com/grafana/loki/pkg/storage/chunk/client/hedging"
|
||
|
4 years ago
|
"github.com/grafana/loki/pkg/util/log"
|
||
|
4 years ago
|
)
|
||
|
|
|
||
|
4 years ago
|
var defaultTransport http.RoundTripper = &http.Transport{
|
||
|
|
Proxy: http.ProxyFromEnvironment,
|
||
|
4 years ago
|
MaxIdleConnsPerHost: 200,
|
||
|
|
MaxIdleConns: 200,
|
||
|
4 years ago
|
ExpectContinueTimeout: 5 * time.Second,
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
type SwiftObjectClient struct {
|
||
|
4 years ago
|
conn *swift.Connection
|
||
|
|
hedgingConn *swift.Connection
|
||
|
|
cfg SwiftConfig
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
|
// SwiftConfig is config for the Swift Chunk Client.
|
||
|
|
type SwiftConfig struct {
|
||
|
4 years ago
|
bucket_swift.Config `yaml:",inline"`
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
|
// RegisterFlags registers flags.
|
||
|
|
func (cfg *SwiftConfig) RegisterFlags(f *flag.FlagSet) {
|
||
|
|
cfg.RegisterFlagsWithPrefix("", f)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate config and returns error on failure
|
||
|
|
func (cfg *SwiftConfig) Validate() error {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// RegisterFlagsWithPrefix registers flags with prefix.
|
||
|
|
func (cfg *SwiftConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
|
||
|
|
cfg.Config.RegisterFlagsWithPrefix(prefix, f)
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewSwiftObjectClient makes a new chunk.Client that writes chunks to OpenStack Swift.
|
||
|
4 years ago
|
func NewSwiftObjectClient(cfg SwiftConfig, hedgingCfg hedging.Config) (*SwiftObjectClient, error) {
|
||
|
4 years ago
|
log.WarnExperimentalUse("OpenStack Swift Storage", log.Logger)
|
||
|
4 years ago
|
|
||
|
4 years ago
|
c, err := createConnection(cfg, hedgingCfg, false)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
// Ensure the container is created, no error is returned if it already exists.
|
||
|
|
if err := c.ContainerCreate(cfg.ContainerName, nil); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
hedging, err := createConnection(cfg, hedgingCfg, true)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &SwiftObjectClient{
|
||
|
|
conn: c,
|
||
|
|
hedgingConn: hedging,
|
||
|
|
cfg: cfg,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func createConnection(cfg SwiftConfig, hedgingCfg hedging.Config, hedging bool) (*swift.Connection, error) {
|
||
|
4 years ago
|
// Create a connection
|
||
|
|
c := &swift.Connection{
|
||
|
|
AuthVersion: cfg.AuthVersion,
|
||
|
|
AuthUrl: cfg.AuthURL,
|
||
|
|
ApiKey: cfg.Password,
|
||
|
|
UserName: cfg.Username,
|
||
|
|
UserId: cfg.UserID,
|
||
|
|
Retries: cfg.MaxRetries,
|
||
|
|
ConnectTimeout: cfg.ConnectTimeout,
|
||
|
|
Timeout: cfg.RequestTimeout,
|
||
|
|
TenantId: cfg.ProjectID,
|
||
|
|
Tenant: cfg.ProjectName,
|
||
|
|
TenantDomain: cfg.ProjectDomainName,
|
||
|
|
TenantDomainId: cfg.ProjectDomainID,
|
||
|
|
Domain: cfg.DomainName,
|
||
|
|
DomainId: cfg.DomainID,
|
||
|
|
Region: cfg.RegionName,
|
||
|
4 years ago
|
Transport: defaultTransport,
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
|
switch {
|
||
|
|
case cfg.UserDomainName != "":
|
||
|
|
c.Domain = cfg.UserDomainName
|
||
|
|
case cfg.UserDomainID != "":
|
||
|
|
c.DomainId = cfg.UserDomainID
|
||
|
|
}
|
||
|
4 years ago
|
if hedging {
|
||
|
4 years ago
|
var err error
|
||
|
4 years ago
|
c.Transport, err = hedgingCfg.RoundTripperWithRegisterer(c.Transport, prometheus.WrapRegistererWithPrefix("loki_", prometheus.DefaultRegisterer))
|
||
|
4 years ago
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
4 years ago
|
}
|
||
|
4 years ago
|
|
||
|
|
err := c.Authenticate()
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
return c, nil
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
|
func (s *SwiftObjectClient) Stop() {
|
||
|
|
s.conn.UnAuthenticate()
|
||
|
4 years ago
|
s.hedgingConn.UnAuthenticate()
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
// GetObject returns a reader and the size for the specified object key from the configured swift container.
|
||
|
|
func (s *SwiftObjectClient) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, int64, error) {
|
||
|
4 years ago
|
var buf bytes.Buffer
|
||
|
4 years ago
|
_, err := s.hedgingConn.ObjectGet(s.cfg.ContainerName, objectKey, &buf, false, nil)
|
||
|
4 years ago
|
if err != nil {
|
||
|
4 years ago
|
return nil, 0, err
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
4 years ago
|
return ioutil.NopCloser(&buf), int64(buf.Len()), nil
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
|
// PutObject puts the specified bytes into the configured Swift container at the provided key
|
||
|
|
func (s *SwiftObjectClient) PutObject(ctx context.Context, objectKey string, object io.ReadSeeker) error {
|
||
|
|
_, err := s.conn.ObjectPut(s.cfg.ContainerName, objectKey, object, false, "", "", nil)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// List only objects from the store non-recursively
|
||
|
4 years ago
|
func (s *SwiftObjectClient) List(ctx context.Context, prefix, delimiter string) ([]client.StorageObject, []client.StorageCommonPrefix, error) {
|
||
|
4 years ago
|
if len(delimiter) > 1 {
|
||
|
|
return nil, nil, fmt.Errorf("delimiter must be a single character but was %s", delimiter)
|
||
|
|
}
|
||
|
|
|
||
|
|
opts := &swift.ObjectsOpts{
|
||
|
|
Prefix: prefix,
|
||
|
|
}
|
||
|
|
if len(delimiter) > 0 {
|
||
|
|
opts.Delimiter = []rune(delimiter)[0]
|
||
|
|
}
|
||
|
|
|
||
|
|
objs, err := s.conn.Objects(s.cfg.ContainerName, opts)
|
||
|
|
if err != nil {
|
||
|
|
return nil, nil, err
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
var storageObjects []client.StorageObject
|
||
|
|
var storagePrefixes []client.StorageCommonPrefix
|
||
|
4 years ago
|
|
||
|
|
for _, obj := range objs {
|
||
|
|
// based on the docs when subdir is set, it means it's a pseudo directory.
|
||
|
|
// see https://docs.openstack.org/swift/latest/api/pseudo-hierarchical-folders-directories.html
|
||
|
|
if obj.SubDir != "" {
|
||
|
4 years ago
|
storagePrefixes = append(storagePrefixes, client.StorageCommonPrefix(obj.SubDir))
|
||
|
4 years ago
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
storageObjects = append(storageObjects, client.StorageObject{
|
||
|
4 years ago
|
Key: obj.Name,
|
||
|
|
ModifiedAt: obj.LastModified,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
return storageObjects, storagePrefixes, nil
|
||
|
|
}
|
||
|
|
|
||
|
4 years ago
|
// DeleteObject deletes the specified object key from the configured Swift container.
|
||
|
4 years ago
|
func (s *SwiftObjectClient) DeleteObject(ctx context.Context, objectKey string) error {
|
||
|
4 years ago
|
return s.conn.ObjectDelete(s.cfg.ContainerName, objectKey)
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsObjectNotFoundErr returns true if error means that object is not found. Relevant to GetObject and DeleteObject operations.
|
||
|
|
func (s *SwiftObjectClient) IsObjectNotFoundErr(err error) bool {
|
||
|
|
return errors.Is(err, swift.ObjectNotFound)
|
||
|
4 years ago
|
}
|