github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ruler/rulestore/objectclient/rule_store.go (about)

     1  package objectclient
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"strings"
    10  
    11  	"github.com/go-kit/log"
    12  	"github.com/go-kit/log/level"
    13  	"github.com/gogo/protobuf/proto"
    14  	"github.com/pkg/errors"
    15  	"golang.org/x/sync/errgroup"
    16  
    17  	"github.com/cortexproject/cortex/pkg/chunk"
    18  	"github.com/cortexproject/cortex/pkg/ruler/rulespb"
    19  	"github.com/cortexproject/cortex/pkg/ruler/rulestore"
    20  )
    21  
    22  // Object Rule Storage Schema
    23  // =======================
    24  // Object Name: "rules/<user_id>/<base64 URL Encoded: namespace>/<base64 URL Encoded: group_name>"
    25  // Storage Format: Encoded RuleGroupDesc
    26  //
    27  // Prometheus Rule Groups can include a large number of characters that are not valid object names
    28  // in common object storage systems. A URL Base64 encoding allows for generic consistent naming
    29  // across all backends
    30  
    31  const (
    32  	delim      = "/"
    33  	rulePrefix = "rules" + delim
    34  )
    35  
    36  // RuleStore allows cortex rules to be stored using an object store backend.
    37  type RuleStore struct {
    38  	client          chunk.ObjectClient
    39  	loadConcurrency int
    40  
    41  	logger log.Logger
    42  }
    43  
    44  // NewRuleStore returns a new RuleStore
    45  func NewRuleStore(client chunk.ObjectClient, loadConcurrency int, logger log.Logger) *RuleStore {
    46  	return &RuleStore{
    47  		client:          client,
    48  		loadConcurrency: loadConcurrency,
    49  		logger:          logger,
    50  	}
    51  }
    52  
    53  // If existing rule group is supplied, it is Reset and reused. If nil, new RuleGroupDesc is allocated.
    54  func (o *RuleStore) getRuleGroup(ctx context.Context, objectKey string, rg *rulespb.RuleGroupDesc) (*rulespb.RuleGroupDesc, error) {
    55  	reader, err := o.client.GetObject(ctx, objectKey)
    56  	if err == chunk.ErrStorageObjectNotFound {
    57  		level.Debug(o.logger).Log("msg", "rule group does not exist", "name", objectKey)
    58  		return nil, errors.Wrapf(rulestore.ErrGroupNotFound, "get rule group user=%q, namespace=%q, name=%q", rg.GetUser(), rg.GetNamespace(), rg.GetName())
    59  	}
    60  
    61  	if err != nil {
    62  		return nil, errors.Wrapf(err, "failed to get rule group %s", objectKey)
    63  	}
    64  	defer func() { _ = reader.Close() }()
    65  
    66  	buf, err := ioutil.ReadAll(reader)
    67  	if err != nil {
    68  		return nil, errors.Wrapf(err, "failed to read rule group %s", objectKey)
    69  	}
    70  
    71  	if rg == nil {
    72  		rg = &rulespb.RuleGroupDesc{}
    73  	} else {
    74  		rg.Reset()
    75  	}
    76  
    77  	err = proto.Unmarshal(buf, rg)
    78  	if err != nil {
    79  		return nil, errors.Wrapf(err, "failed to unmarshal rule group %s", objectKey)
    80  	}
    81  
    82  	return rg, nil
    83  }
    84  
    85  func (o *RuleStore) ListAllUsers(ctx context.Context) ([]string, error) {
    86  	_, prefixes, err := o.client.List(ctx, rulePrefix, delim)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	var result []string
    92  	for _, p := range prefixes {
    93  		s := string(p)
    94  
    95  		s = strings.TrimPrefix(s, rulePrefix)
    96  		s = strings.TrimSuffix(s, delim)
    97  
    98  		if s != "" {
    99  			result = append(result, s)
   100  		}
   101  	}
   102  
   103  	return result, nil
   104  }
   105  
   106  // ListAllRuleGroups implements rules.RuleStore.
   107  func (o *RuleStore) ListAllRuleGroups(ctx context.Context) (map[string]rulespb.RuleGroupList, error) {
   108  	// No delimiter to get *all* rule groups for all users and namespaces.
   109  	ruleGroupObjects, _, err := o.client.List(ctx, rulePrefix, "")
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	return convertRuleGroupObjectsToMap(ruleGroupObjects), nil
   115  }
   116  
   117  func (o *RuleStore) ListRuleGroupsForUserAndNamespace(ctx context.Context, userID, namespace string) (rulespb.RuleGroupList, error) {
   118  	ruleGroupObjects, _, err := o.client.List(ctx, generateRuleObjectKey(userID, namespace, ""), "")
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	return convertRuleGroupObjectsToMap(ruleGroupObjects)[userID], nil
   124  }
   125  
   126  func (o *RuleStore) LoadRuleGroups(ctx context.Context, groupsToLoad map[string]rulespb.RuleGroupList) error {
   127  	ch := make(chan *rulespb.RuleGroupDesc)
   128  
   129  	// Given we store one file per rule group. With this, we create a pool of workers that will
   130  	// download all rule groups in parallel. We limit the number of workers to avoid a
   131  	// particular user having too many rule groups rate limiting us with the object storage.
   132  	g, gCtx := errgroup.WithContext(ctx)
   133  	for i := 0; i < o.loadConcurrency; i++ {
   134  		g.Go(func() error {
   135  			for gr := range ch {
   136  				if gr == nil {
   137  					continue
   138  				}
   139  
   140  				user, namespace, group := gr.GetUser(), gr.GetNamespace(), gr.GetName()
   141  				if user == "" || namespace == "" || group == "" {
   142  					return fmt.Errorf("invalid rule group: user=%q, namespace=%q, group=%q", user, namespace, group)
   143  				}
   144  
   145  				key := generateRuleObjectKey(user, namespace, group)
   146  
   147  				level.Debug(o.logger).Log("msg", "loading rule group", "key", key, "user", user)
   148  				gr, err := o.getRuleGroup(gCtx, key, gr) // reuse group pointer from the map.
   149  				if err != nil {
   150  					level.Error(o.logger).Log("msg", "failed to get rule group", "key", key, "user", user)
   151  					return err
   152  				}
   153  
   154  				if user != gr.User || namespace != gr.Namespace || group != gr.Name {
   155  					return fmt.Errorf("mismatch between requested rule group and loaded rule group, requested: user=%q, namespace=%q, group=%q, loaded: user=%q, namespace=%q, group=%q", user, namespace, group, gr.User, gr.Namespace, gr.Name)
   156  				}
   157  			}
   158  
   159  			return nil
   160  		})
   161  	}
   162  
   163  outer:
   164  	for _, gs := range groupsToLoad {
   165  		for _, g := range gs {
   166  			select {
   167  			case <-gCtx.Done():
   168  				break outer
   169  			case ch <- g:
   170  				// ok
   171  			}
   172  		}
   173  	}
   174  	close(ch)
   175  
   176  	return g.Wait()
   177  }
   178  
   179  func convertRuleGroupObjectsToMap(ruleGroupObjects []chunk.StorageObject) map[string]rulespb.RuleGroupList {
   180  	result := map[string]rulespb.RuleGroupList{}
   181  	for _, rg := range ruleGroupObjects {
   182  		user, namespace, group := decomposeRuleObjectKey(rg.Key)
   183  		if user == "" || namespace == "" || group == "" {
   184  			continue
   185  		}
   186  
   187  		result[user] = append(result[user], &rulespb.RuleGroupDesc{
   188  			User:      user,
   189  			Namespace: namespace,
   190  			Name:      group,
   191  		})
   192  	}
   193  	return result
   194  }
   195  
   196  // GetRuleGroup returns the requested rule group
   197  func (o *RuleStore) GetRuleGroup(ctx context.Context, userID string, namespace string, grp string) (*rulespb.RuleGroupDesc, error) {
   198  	handle := generateRuleObjectKey(userID, namespace, grp)
   199  	return o.getRuleGroup(ctx, handle, nil)
   200  }
   201  
   202  // SetRuleGroup sets provided rule group
   203  func (o *RuleStore) SetRuleGroup(ctx context.Context, userID string, namespace string, group *rulespb.RuleGroupDesc) error {
   204  	data, err := proto.Marshal(group)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	objectKey := generateRuleObjectKey(userID, namespace, group.Name)
   210  	return o.client.PutObject(ctx, objectKey, bytes.NewReader(data))
   211  }
   212  
   213  // DeleteRuleGroup deletes the specified rule group
   214  func (o *RuleStore) DeleteRuleGroup(ctx context.Context, userID string, namespace string, groupName string) error {
   215  	objectKey := generateRuleObjectKey(userID, namespace, groupName)
   216  	err := o.client.DeleteObject(ctx, objectKey)
   217  	if err == chunk.ErrStorageObjectNotFound {
   218  		return rulestore.ErrGroupNotFound
   219  	}
   220  	return err
   221  }
   222  
   223  // DeleteNamespace deletes all the rule groups in the specified namespace
   224  func (o *RuleStore) DeleteNamespace(ctx context.Context, userID, namespace string) error {
   225  	ruleGroupObjects, _, err := o.client.List(ctx, generateRuleObjectKey(userID, namespace, ""), "")
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	if len(ruleGroupObjects) == 0 {
   231  		return rulestore.ErrGroupNamespaceNotFound
   232  	}
   233  
   234  	for _, obj := range ruleGroupObjects {
   235  		if err := ctx.Err(); err != nil {
   236  			return err
   237  		}
   238  
   239  		level.Debug(o.logger).Log("msg", "deleting rule group", "namespace", namespace, "key", obj.Key)
   240  		err = o.client.DeleteObject(ctx, obj.Key)
   241  		if err != nil {
   242  			level.Error(o.logger).Log("msg", "unable to delete rule group from namespace", "err", err, "namespace", namespace, "key", obj.Key)
   243  			return err
   244  		}
   245  	}
   246  
   247  	return nil
   248  }
   249  
   250  func generateRuleObjectKey(userID, namespace, groupName string) string {
   251  	if userID == "" {
   252  		return rulePrefix
   253  	}
   254  
   255  	prefix := rulePrefix + userID + delim
   256  	if namespace == "" {
   257  		return prefix
   258  	}
   259  
   260  	ns := base64.URLEncoding.EncodeToString([]byte(namespace)) + delim
   261  	if groupName == "" {
   262  		return prefix + ns
   263  	}
   264  
   265  	return prefix + ns + base64.URLEncoding.EncodeToString([]byte(groupName))
   266  }
   267  
   268  func decomposeRuleObjectKey(objectKey string) (userID, namespace, groupName string) {
   269  	if !strings.HasPrefix(objectKey, rulePrefix) {
   270  		return
   271  	}
   272  
   273  	components := strings.Split(objectKey, delim)
   274  	if len(components) != 4 {
   275  		return
   276  	}
   277  
   278  	ns, err := base64.URLEncoding.DecodeString(components[2])
   279  	if err != nil {
   280  		return
   281  	}
   282  
   283  	gr, err := base64.URLEncoding.DecodeString(components[3])
   284  	if err != nil {
   285  		return
   286  	}
   287  
   288  	return components[1], string(ns), string(gr)
   289  }