github.com/m3db/m3@v1.5.0/src/metrics/rules/ruleset.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package rules
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"math"
    27  	"sort"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/cluster/kv"
    31  	"github.com/m3db/m3/src/metrics/aggregation"
    32  	merrors "github.com/m3db/m3/src/metrics/errors"
    33  	"github.com/m3db/m3/src/metrics/filters"
    34  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    35  	"github.com/m3db/m3/src/metrics/metric"
    36  	metricid "github.com/m3db/m3/src/metrics/metric/id"
    37  	"github.com/m3db/m3/src/metrics/rules/view"
    38  	"github.com/m3db/m3/src/metrics/rules/view/changes"
    39  	xerrors "github.com/m3db/m3/src/x/errors"
    40  
    41  	"github.com/pborman/uuid"
    42  )
    43  
    44  const (
    45  	timeNanosMax = int64(math.MaxInt64)
    46  )
    47  
    48  var (
    49  	errNilRuleSetProto      = errors.New("nil rule set proto")
    50  	errRuleSetNotTombstoned = errors.New("ruleset is not tombstoned")
    51  	errRuleNotFound         = errors.New("rule not found")
    52  	errNoRuleSnapshots      = errors.New("rule has no snapshots")
    53  	ruleIDNotFoundErrorFmt  = "no rule with id %v"
    54  	ruleActionErrorFmt      = "cannot %s rule %s"
    55  	ruleSetActionErrorFmt   = "cannot %s ruleset %s"
    56  	unknownOpTypeFmt        = "unknown op type %v"
    57  )
    58  
    59  // Matcher matches metrics against rules to determine applicable policies.
    60  type Matcher interface {
    61  	// ForwardMatch matches the applicable policies for a metric id between [fromNanos, toNanos).
    62  	ForwardMatch(id metricid.ID, fromNanos, toNanos int64, opts MatchOptions) (MatchResult, error)
    63  }
    64  
    65  // Fetcher fetches rules.
    66  type Fetcher interface {
    67  	// LatestRollupRules returns the latest rollup rules for a given time.
    68  	LatestRollupRules(namespace []byte, timeNanos int64) ([]view.RollupRule, error)
    69  }
    70  
    71  // ReverseMatcher matches metrics against rules to determine applicable policies.
    72  type ReverseMatcher interface {
    73  	// ReverseMatch reverse matches the applicable policies for a metric id between [fromNanos, toNanos),
    74  	// with aware of the metric type and aggregation type for the given id.
    75  	ReverseMatch(
    76  		id metricid.ID,
    77  		fromNanos, toNanos int64,
    78  		mt metric.Type,
    79  		at aggregation.Type,
    80  		isMultiAggregationTypesAllowed bool,
    81  		aggTypesOpts aggregation.TypesOptions,
    82  	) (MatchResult, error)
    83  }
    84  
    85  // ActiveSet is the currently active RuleSet.
    86  type ActiveSet interface {
    87  	Matcher
    88  	Fetcher
    89  	ReverseMatcher
    90  }
    91  
    92  // RuleSet is a read-only set of rules associated with a namespace.
    93  type RuleSet interface {
    94  	// Namespace is the metrics namespace the ruleset applies to.
    95  	Namespace() []byte
    96  
    97  	// Version returns the ruleset version.
    98  	Version() int
    99  
   100  	// CutoverNanos returns when the ruleset takes effect.
   101  	CutoverNanos() int64
   102  
   103  	// TombStoned returns whether the ruleset is tombstoned.
   104  	Tombstoned() bool
   105  
   106  	// CreatedAtNanos returns the creation time for this ruleset.
   107  	CreatedAtNanos() int64
   108  
   109  	// LastUpdatedAtNanos returns the time when this ruleset was last updated.
   110  	LastUpdatedAtNanos() int64
   111  
   112  	// Proto returns the rulepb.Ruleset representation of this ruleset.
   113  	Proto() (*rulepb.RuleSet, error)
   114  
   115  	// MappingRuleHistory returns a map of mapping rule id to states that rule has been in.
   116  	MappingRules() (view.MappingRules, error)
   117  
   118  	// RollupRuleHistory returns a map of rollup rule id to states that rule has been in.
   119  	RollupRules() (view.RollupRules, error)
   120  
   121  	// Latest returns the latest snapshot of a ruleset containing the latest snapshots
   122  	// of each rule in the ruleset.
   123  	Latest() (view.RuleSet, error)
   124  
   125  	// ActiveSet returns the active ruleset at a given time.
   126  	ActiveSet(timeNanos int64) ActiveSet
   127  
   128  	// ToMutableRuleSet returns a mutable version of this ruleset.
   129  	ToMutableRuleSet() MutableRuleSet
   130  }
   131  
   132  // MutableRuleSet is mutable ruleset.
   133  type MutableRuleSet interface {
   134  	RuleSet
   135  
   136  	// Clone returns a copy of this MutableRuleSet.
   137  	Clone() MutableRuleSet
   138  
   139  	// AppendMappingRule creates a new mapping rule and adds it to this ruleset.
   140  	// Should return the id of the newly created rule.
   141  	AddMappingRule(view.MappingRule, UpdateMetadata) (string, error)
   142  
   143  	// UpdateMappingRule creates a new mapping rule and adds it to this ruleset.
   144  	UpdateMappingRule(view.MappingRule, UpdateMetadata) error
   145  
   146  	// DeleteMappingRule deletes a mapping rule
   147  	DeleteMappingRule(string, UpdateMetadata) error
   148  
   149  	// AppendRollupRule creates a new rollup rule and adds it to this ruleset.
   150  	// Should return the id of the newly created rule.
   151  	AddRollupRule(view.RollupRule, UpdateMetadata) (string, error)
   152  
   153  	// UpdateRollupRule creates a new rollup rule and adds it to this ruleset.
   154  	UpdateRollupRule(view.RollupRule, UpdateMetadata) error
   155  
   156  	// DeleteRollupRule deletes a rollup rule
   157  	DeleteRollupRule(string, UpdateMetadata) error
   158  
   159  	// Tombstone tombstones this ruleset and all of its rules.
   160  	Delete(UpdateMetadata) error
   161  
   162  	// Revive removes the tombstone from this ruleset. It does not revive any rules.
   163  	Revive(UpdateMetadata) error
   164  
   165  	// ApplyRuleSetChanges takes set of rule set changes and applies them to a ruleset.
   166  	ApplyRuleSetChanges(changes.RuleSetChanges, UpdateMetadata) error
   167  }
   168  
   169  type ruleSet struct {
   170  	uuid               string
   171  	version            int
   172  	namespace          []byte
   173  	createdAtNanos     int64
   174  	lastUpdatedAtNanos int64
   175  	lastUpdatedBy      string
   176  	tombstoned         bool
   177  	cutoverNanos       int64
   178  	mappingRules       []*mappingRule
   179  	rollupRules        []*rollupRule
   180  	tagsFilterOpts     filters.TagsFilterOptions
   181  	newRollupIDFn      metricid.NewIDFn
   182  	isRollupIDFn       metricid.MatchIDFn
   183  }
   184  
   185  // NewRuleSetFromProto creates a new RuleSet from a proto object.
   186  func NewRuleSetFromProto(version int, rs *rulepb.RuleSet, opts Options) (RuleSet, error) {
   187  	if rs == nil {
   188  		return nil, errNilRuleSetProto
   189  	}
   190  	tagsFilterOpts := opts.TagsFilterOptions()
   191  	mappingRules := make([]*mappingRule, 0, len(rs.MappingRules))
   192  	for _, mappingRule := range rs.MappingRules {
   193  		mc, err := newMappingRuleFromProto(mappingRule, tagsFilterOpts)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  		mappingRules = append(mappingRules, mc)
   198  	}
   199  	rollupRules := make([]*rollupRule, 0, len(rs.RollupRules))
   200  	for _, rollupRule := range rs.RollupRules {
   201  		rc, err := newRollupRuleFromProto(rollupRule, tagsFilterOpts)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		rollupRules = append(rollupRules, rc)
   206  	}
   207  	return &ruleSet{
   208  		uuid:               rs.Uuid,
   209  		version:            version,
   210  		namespace:          []byte(rs.Namespace),
   211  		createdAtNanos:     rs.CreatedAtNanos,
   212  		lastUpdatedAtNanos: rs.LastUpdatedAtNanos,
   213  		lastUpdatedBy:      rs.LastUpdatedBy,
   214  		tombstoned:         rs.Tombstoned,
   215  		cutoverNanos:       rs.CutoverNanos,
   216  		mappingRules:       mappingRules,
   217  		rollupRules:        rollupRules,
   218  		tagsFilterOpts:     tagsFilterOpts,
   219  		newRollupIDFn:      opts.NewRollupIDFn(),
   220  		isRollupIDFn:       opts.IsRollupIDFn(),
   221  	}, nil
   222  }
   223  
   224  // NewEmptyRuleSet returns an empty ruleset to be used with a new namespace.
   225  func NewEmptyRuleSet(namespaceName string, meta UpdateMetadata) MutableRuleSet {
   226  	rs := &ruleSet{
   227  		uuid:         uuid.NewUUID().String(),
   228  		version:      kv.UninitializedVersion,
   229  		namespace:    []byte(namespaceName),
   230  		tombstoned:   false,
   231  		mappingRules: make([]*mappingRule, 0),
   232  		rollupRules:  make([]*rollupRule, 0),
   233  	}
   234  	rs.updateMetadata(meta)
   235  	return rs
   236  }
   237  
   238  func (rs *ruleSet) Namespace() []byte                { return rs.namespace }
   239  func (rs *ruleSet) Version() int                     { return rs.version }
   240  func (rs *ruleSet) CutoverNanos() int64              { return rs.cutoverNanos }
   241  func (rs *ruleSet) Tombstoned() bool                 { return rs.tombstoned }
   242  func (rs *ruleSet) LastUpdatedAtNanos() int64        { return rs.lastUpdatedAtNanos }
   243  func (rs *ruleSet) CreatedAtNanos() int64            { return rs.createdAtNanos }
   244  func (rs *ruleSet) ToMutableRuleSet() MutableRuleSet { return rs }
   245  
   246  func (rs *ruleSet) ActiveSet(timeNanos int64) ActiveSet {
   247  	mappingRules := make([]*mappingRule, 0, len(rs.mappingRules))
   248  	for _, mappingRule := range rs.mappingRules {
   249  		activeRule := mappingRule.activeRule(timeNanos)
   250  		mappingRules = append(mappingRules, activeRule)
   251  	}
   252  	rollupRules := make([]*rollupRule, 0, len(rs.rollupRules))
   253  	for _, rollupRule := range rs.rollupRules {
   254  		activeRule := rollupRule.activeRule(timeNanos)
   255  		rollupRules = append(rollupRules, activeRule)
   256  	}
   257  	return newActiveRuleSet(
   258  		rs.version,
   259  		mappingRules,
   260  		rollupRules,
   261  		rs.tagsFilterOpts,
   262  		rs.newRollupIDFn,
   263  		rs.isRollupIDFn,
   264  	)
   265  }
   266  
   267  // Proto returns the protobuf representation of a ruleset.
   268  func (rs *ruleSet) Proto() (*rulepb.RuleSet, error) {
   269  	res := &rulepb.RuleSet{
   270  		Uuid:               rs.uuid,
   271  		Namespace:          string(rs.namespace),
   272  		CreatedAtNanos:     rs.createdAtNanos,
   273  		LastUpdatedAtNanos: rs.lastUpdatedAtNanos,
   274  		LastUpdatedBy:      rs.lastUpdatedBy,
   275  		Tombstoned:         rs.tombstoned,
   276  		CutoverNanos:       rs.cutoverNanos,
   277  	}
   278  
   279  	mappingRules := make([]*rulepb.MappingRule, len(rs.mappingRules))
   280  	for i, m := range rs.mappingRules {
   281  		mr, err := m.proto()
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		mappingRules[i] = mr
   286  	}
   287  	res.MappingRules = mappingRules
   288  
   289  	rollupRules := make([]*rulepb.RollupRule, len(rs.rollupRules))
   290  	for i, r := range rs.rollupRules {
   291  		rr, err := r.proto()
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  		rollupRules[i] = rr
   296  	}
   297  	res.RollupRules = rollupRules
   298  
   299  	return res, nil
   300  }
   301  
   302  func (rs *ruleSet) MappingRules() (view.MappingRules, error) {
   303  	mappingRules := make(view.MappingRules, len(rs.mappingRules))
   304  	for _, m := range rs.mappingRules {
   305  		hist, err := m.history()
   306  		if err != nil {
   307  			return nil, err
   308  		}
   309  		mappingRules[m.uuid] = hist
   310  	}
   311  	return mappingRules, nil
   312  }
   313  
   314  func (rs *ruleSet) RollupRules() (view.RollupRules, error) {
   315  	rollupRules := make(view.RollupRules, len(rs.rollupRules))
   316  	for _, r := range rs.rollupRules {
   317  		hist, err := r.history()
   318  		if err != nil {
   319  			return nil, err
   320  		}
   321  		rollupRules[r.uuid] = hist
   322  	}
   323  	return rollupRules, nil
   324  }
   325  
   326  func (rs *ruleSet) Latest() (view.RuleSet, error) {
   327  	mrs, err := rs.latestMappingRules()
   328  	if err != nil {
   329  		return view.RuleSet{}, err
   330  	}
   331  	rrs, err := rs.latestRollupRules()
   332  	if err != nil {
   333  		return view.RuleSet{}, err
   334  	}
   335  	return view.RuleSet{
   336  		Namespace:     string(rs.Namespace()),
   337  		Version:       rs.Version(),
   338  		CutoverMillis: rs.CutoverNanos() / nanosPerMilli,
   339  		MappingRules:  mrs,
   340  		RollupRules:   rrs,
   341  	}, nil
   342  }
   343  
   344  func (rs *ruleSet) Clone() MutableRuleSet {
   345  	namespace := make([]byte, len(rs.namespace))
   346  	copy(namespace, rs.namespace)
   347  
   348  	mappingRules := make([]*mappingRule, len(rs.mappingRules))
   349  	for i, m := range rs.mappingRules {
   350  		c := m.clone()
   351  		mappingRules[i] = &c
   352  	}
   353  
   354  	rollupRules := make([]*rollupRule, len(rs.rollupRules))
   355  	for i, r := range rs.rollupRules {
   356  		c := r.clone()
   357  		rollupRules[i] = &c
   358  	}
   359  
   360  	// This clone deliberately ignores tagFliterOpts and rollupIDFn
   361  	// as they are not useful for the MutableRuleSet.
   362  	return &ruleSet{
   363  		uuid:               rs.uuid,
   364  		version:            rs.version,
   365  		createdAtNanos:     rs.createdAtNanos,
   366  		lastUpdatedAtNanos: rs.lastUpdatedAtNanos,
   367  		lastUpdatedBy:      rs.lastUpdatedBy,
   368  		tombstoned:         rs.tombstoned,
   369  		cutoverNanos:       rs.cutoverNanos,
   370  		namespace:          namespace,
   371  		mappingRules:       mappingRules,
   372  		rollupRules:        rollupRules,
   373  		tagsFilterOpts:     rs.tagsFilterOpts,
   374  		newRollupIDFn:      rs.newRollupIDFn,
   375  		isRollupIDFn:       rs.isRollupIDFn,
   376  	}
   377  }
   378  
   379  func (rs *ruleSet) AddMappingRule(mrv view.MappingRule, meta UpdateMetadata) (string, error) {
   380  	m, err := rs.getMappingRuleByName(mrv.Name)
   381  	if err != nil && err != errRuleNotFound {
   382  		return "", xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "add", mrv.Name))
   383  	}
   384  	if err == errRuleNotFound {
   385  		m = newEmptyMappingRule()
   386  		if err = m.addSnapshot(
   387  			mrv.Name,
   388  			mrv.Filter,
   389  			mrv.AggregationID,
   390  			mrv.StoragePolicies,
   391  			mrv.DropPolicy,
   392  			mrv.Tags,
   393  			meta,
   394  		); err != nil {
   395  			return "", xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "add", mrv.Name))
   396  		}
   397  		rs.mappingRules = append(rs.mappingRules, m)
   398  	} else {
   399  		if err := m.revive(
   400  			mrv.Name,
   401  			mrv.Filter,
   402  			mrv.AggregationID,
   403  			mrv.StoragePolicies,
   404  			mrv.DropPolicy,
   405  			mrv.Tags,
   406  			meta,
   407  		); err != nil {
   408  			return "", xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "revive", mrv.Name))
   409  		}
   410  	}
   411  	rs.updateMetadata(meta)
   412  	return m.uuid, nil
   413  }
   414  
   415  func (rs *ruleSet) UpdateMappingRule(mrv view.MappingRule, meta UpdateMetadata) error {
   416  	m, err := rs.getMappingRuleByID(mrv.ID)
   417  	if err != nil {
   418  		return merrors.NewInvalidInputError(fmt.Sprintf(ruleIDNotFoundErrorFmt, mrv.ID))
   419  	}
   420  	if err := m.addSnapshot(
   421  		mrv.Name,
   422  		mrv.Filter,
   423  		mrv.AggregationID,
   424  		mrv.StoragePolicies,
   425  		mrv.DropPolicy,
   426  		mrv.Tags,
   427  		meta,
   428  	); err != nil {
   429  		return xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "update", mrv.Name))
   430  	}
   431  	rs.updateMetadata(meta)
   432  	return nil
   433  }
   434  
   435  func (rs *ruleSet) DeleteMappingRule(id string, meta UpdateMetadata) error {
   436  	m, err := rs.getMappingRuleByID(id)
   437  	if err != nil {
   438  		return merrors.NewInvalidInputError(fmt.Sprintf(ruleIDNotFoundErrorFmt, id))
   439  	}
   440  
   441  	if err := m.markTombstoned(meta); err != nil {
   442  		return xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "delete", id))
   443  	}
   444  	rs.updateMetadata(meta)
   445  	return nil
   446  }
   447  
   448  func (rs *ruleSet) AddRollupRule(rrv view.RollupRule, meta UpdateMetadata) (string, error) {
   449  	r, err := rs.getRollupRuleByName(rrv.Name)
   450  	if err != nil && err != errRuleNotFound {
   451  		return "", xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "add", rrv.Name))
   452  	}
   453  	targets := newRollupTargetsFromView(rrv.Targets)
   454  	if err == errRuleNotFound {
   455  		r = newEmptyRollupRule()
   456  		if err = r.addSnapshot(
   457  			rrv.Name,
   458  			rrv.Filter,
   459  			targets,
   460  			meta,
   461  			rrv.KeepOriginal,
   462  			rrv.Tags,
   463  		); err != nil {
   464  			return "", xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "add", rrv.Name))
   465  		}
   466  		rs.rollupRules = append(rs.rollupRules, r)
   467  	} else {
   468  		if err := r.revive(
   469  			rrv.Name,
   470  			rrv.Filter,
   471  			targets,
   472  			meta,
   473  			rrv.KeepOriginal,
   474  			rrv.Tags,
   475  		); err != nil {
   476  			return "", xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "revive", rrv.Name))
   477  		}
   478  	}
   479  	rs.updateMetadata(meta)
   480  	return r.uuid, nil
   481  }
   482  
   483  func (rs *ruleSet) UpdateRollupRule(rrv view.RollupRule, meta UpdateMetadata) error {
   484  	r, err := rs.getRollupRuleByID(rrv.ID)
   485  	if err != nil {
   486  		return merrors.NewInvalidInputError(fmt.Sprintf(ruleIDNotFoundErrorFmt, rrv.ID))
   487  	}
   488  	targets := newRollupTargetsFromView(rrv.Targets)
   489  	if err = r.addSnapshot(
   490  		rrv.Name,
   491  		rrv.Filter,
   492  		targets,
   493  		meta,
   494  		rrv.KeepOriginal,
   495  		rrv.Tags,
   496  	); err != nil {
   497  		return xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "update", rrv.Name))
   498  	}
   499  	rs.updateMetadata(meta)
   500  	return nil
   501  }
   502  
   503  func (rs *ruleSet) DeleteRollupRule(id string, meta UpdateMetadata) error {
   504  	r, err := rs.getRollupRuleByID(id)
   505  	if err != nil {
   506  		return merrors.NewInvalidInputError(fmt.Sprintf(ruleIDNotFoundErrorFmt, id))
   507  	}
   508  
   509  	if err := r.markTombstoned(meta); err != nil {
   510  		return xerrors.Wrap(err, fmt.Sprintf(ruleActionErrorFmt, "delete", id))
   511  	}
   512  	rs.updateMetadata(meta)
   513  	return nil
   514  }
   515  
   516  func (rs *ruleSet) Delete(meta UpdateMetadata) error {
   517  	if rs.tombstoned {
   518  		return fmt.Errorf("%s is already tombstoned", string(rs.namespace))
   519  	}
   520  
   521  	rs.tombstoned = true
   522  	rs.updateMetadata(meta)
   523  
   524  	// Make sure that all of the rules in the ruleset are tombstoned as well.
   525  	for _, m := range rs.mappingRules {
   526  		if t := m.tombstoned(); !t {
   527  			_ = m.markTombstoned(meta)
   528  		}
   529  	}
   530  
   531  	for _, r := range rs.rollupRules {
   532  		if t := r.tombstoned(); !t {
   533  			_ = r.markTombstoned(meta)
   534  		}
   535  	}
   536  
   537  	return nil
   538  }
   539  
   540  func (rs *ruleSet) ApplyRuleSetChanges(rsc changes.RuleSetChanges, meta UpdateMetadata) error {
   541  	if err := rs.applyMappingRuleChanges(rsc.MappingRuleChanges, meta); err != nil {
   542  		return err
   543  	}
   544  	return rs.applyRollupRuleChanges(rsc.RollupRuleChanges, meta)
   545  }
   546  
   547  func (rs *ruleSet) Revive(meta UpdateMetadata) error {
   548  	if !rs.Tombstoned() {
   549  		return xerrors.Wrap(errRuleSetNotTombstoned, fmt.Sprintf(ruleSetActionErrorFmt, "revive", string(rs.namespace)))
   550  	}
   551  
   552  	rs.tombstoned = false
   553  	rs.updateMetadata(meta)
   554  	return nil
   555  }
   556  
   557  func (rs *ruleSet) updateMetadata(meta UpdateMetadata) {
   558  	rs.cutoverNanos = meta.cutoverNanos
   559  	rs.lastUpdatedAtNanos = meta.updatedAtNanos
   560  	rs.lastUpdatedBy = meta.updatedBy
   561  }
   562  
   563  func (rs *ruleSet) getMappingRuleByName(name string) (*mappingRule, error) {
   564  	for _, m := range rs.mappingRules {
   565  		n, err := m.name()
   566  		if err != nil {
   567  			continue
   568  		}
   569  
   570  		if n == name {
   571  			return m, nil
   572  		}
   573  	}
   574  	return nil, errRuleNotFound
   575  }
   576  
   577  func (rs *ruleSet) getMappingRuleByID(id string) (*mappingRule, error) {
   578  	for _, m := range rs.mappingRules {
   579  		if m.uuid == id {
   580  			return m, nil
   581  		}
   582  	}
   583  	return nil, errRuleNotFound
   584  }
   585  
   586  func (rs *ruleSet) getRollupRuleByName(name string) (*rollupRule, error) {
   587  	for _, r := range rs.rollupRules {
   588  		n, err := r.name()
   589  		if err != nil {
   590  			return nil, err
   591  		}
   592  
   593  		if n == name {
   594  			return r, nil
   595  		}
   596  	}
   597  	return nil, errRuleNotFound
   598  }
   599  
   600  func (rs *ruleSet) getRollupRuleByID(id string) (*rollupRule, error) {
   601  	for _, r := range rs.rollupRules {
   602  		if r.uuid == id {
   603  			return r, nil
   604  		}
   605  	}
   606  	return nil, errRuleNotFound
   607  }
   608  
   609  func (rs *ruleSet) latestMappingRules() ([]view.MappingRule, error) {
   610  	mrs, err := rs.MappingRules()
   611  	if err != nil {
   612  		return nil, err
   613  	}
   614  	filtered := make([]view.MappingRule, 0, len(mrs))
   615  	for _, m := range mrs {
   616  		if len(m) > 0 && !m[0].Tombstoned {
   617  			// Rule snapshots are sorted by cutover time in descending order.
   618  			filtered = append(filtered, m[0])
   619  		}
   620  	}
   621  	sort.Sort(view.MappingRulesByNameAsc(filtered))
   622  	return filtered, nil
   623  }
   624  
   625  func (rs *ruleSet) latestRollupRules() ([]view.RollupRule, error) {
   626  	rrs, err := rs.RollupRules()
   627  	if err != nil {
   628  		return nil, err
   629  	}
   630  	filtered := make([]view.RollupRule, 0, len(rrs))
   631  	for _, r := range rrs {
   632  		if len(r) > 0 && !r[0].Tombstoned {
   633  			// Rule snapshots are sorted by cutover time in descending order.
   634  			filtered = append(filtered, r[0])
   635  		}
   636  	}
   637  	sort.Sort(view.RollupRulesByNameAsc(filtered))
   638  	return filtered, nil
   639  }
   640  
   641  func (rs *ruleSet) applyMappingRuleChanges(mrChanges []changes.MappingRuleChange, meta UpdateMetadata) error {
   642  	for _, mrChange := range mrChanges {
   643  		switch mrChange.Op {
   644  		case changes.AddOp:
   645  			if _, err := rs.AddMappingRule(*mrChange.RuleData, meta); err != nil {
   646  				return err
   647  			}
   648  		case changes.ChangeOp:
   649  			if err := rs.UpdateMappingRule(*mrChange.RuleData, meta); err != nil {
   650  				return err
   651  			}
   652  		case changes.DeleteOp:
   653  			if err := rs.DeleteMappingRule(*mrChange.RuleID, meta); err != nil {
   654  				return err
   655  			}
   656  		default:
   657  			return merrors.NewInvalidInputError(fmt.Sprintf(unknownOpTypeFmt, mrChange.Op))
   658  		}
   659  	}
   660  
   661  	return nil
   662  }
   663  
   664  func (rs *ruleSet) applyRollupRuleChanges(rrChanges []changes.RollupRuleChange, meta UpdateMetadata) error {
   665  	for _, rrChange := range rrChanges {
   666  		switch rrChange.Op {
   667  		case changes.AddOp:
   668  			if _, err := rs.AddRollupRule(*rrChange.RuleData, meta); err != nil {
   669  				return err
   670  			}
   671  		case changes.ChangeOp:
   672  			if err := rs.UpdateRollupRule(*rrChange.RuleData, meta); err != nil {
   673  				return err
   674  			}
   675  		case changes.DeleteOp:
   676  			if err := rs.DeleteRollupRule(*rrChange.RuleID, meta); err != nil {
   677  				return err
   678  			}
   679  		default:
   680  			return merrors.NewInvalidInputError(fmt.Sprintf(unknownOpTypeFmt, rrChange.Op))
   681  		}
   682  	}
   683  
   684  	return nil
   685  }
   686  
   687  // RuleSetUpdateHelper stores the necessary details to create an UpdateMetadata.
   688  type RuleSetUpdateHelper struct {
   689  	propagationDelay time.Duration
   690  }
   691  
   692  // NewRuleSetUpdateHelper creates a new RuleSetUpdateHelper struct.
   693  func NewRuleSetUpdateHelper(propagationDelay time.Duration) RuleSetUpdateHelper {
   694  	return RuleSetUpdateHelper{propagationDelay: propagationDelay}
   695  }
   696  
   697  // UpdateMetadata contains descriptive information that needs to be updated
   698  // with any modification of the ruleset.
   699  type UpdateMetadata struct {
   700  	cutoverNanos   int64
   701  	updatedAtNanos int64
   702  	updatedBy      string
   703  }
   704  
   705  // NewUpdateMetadata creates a properly initialized UpdateMetadata object.
   706  func (r RuleSetUpdateHelper) NewUpdateMetadata(updateTime int64, updatedBy string) UpdateMetadata {
   707  	cutoverNanos := updateTime + int64(r.propagationDelay)
   708  	return UpdateMetadata{updatedAtNanos: updateTime, cutoverNanos: cutoverNanos, updatedBy: updatedBy}
   709  }