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 }