github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/config/zonepb/zone.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package zonepb 12 13 import ( 14 "bytes" 15 "fmt" 16 "strings" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/base" 20 "github.com/cockroachdb/cockroach/pkg/keys" 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/sql/opt/cat" 23 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 24 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 25 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 26 "github.com/cockroachdb/errors" 27 "github.com/gogo/protobuf/proto" 28 ) 29 30 // Several ranges outside of the SQL keyspace are given special names so they 31 // can be targeted by zone configs. 32 const ( 33 DefaultZoneName = "default" 34 LivenessZoneName = "liveness" 35 MetaZoneName = "meta" 36 SystemZoneName = "system" 37 TimeseriesZoneName = "timeseries" 38 ) 39 40 // NamedZones maps named zones to their pseudo-table ID that can be used to 41 // install an entry into the system.zones table. 42 var NamedZones = map[string]uint32{ 43 DefaultZoneName: keys.RootNamespaceID, 44 LivenessZoneName: keys.LivenessRangesID, 45 MetaZoneName: keys.MetaRangesID, 46 SystemZoneName: keys.SystemRangesID, 47 TimeseriesZoneName: keys.TimeseriesRangesID, 48 } 49 50 // NamedZonesByID is the inverse of NamedZones: it maps pseudo-table IDs to 51 // their zone names. 52 var NamedZonesByID = func() map[uint32]string { 53 out := map[uint32]string{} 54 for name, id := range NamedZones { 55 out[id] = name 56 } 57 return out 58 }() 59 60 // ZoneSpecifierFromID creates a tree.ZoneSpecifier for the zone with the 61 // given ID. 62 func ZoneSpecifierFromID( 63 id uint32, resolveID func(id uint32) (parentID uint32, name string, err error), 64 ) (tree.ZoneSpecifier, error) { 65 if name, ok := NamedZonesByID[id]; ok { 66 return tree.ZoneSpecifier{NamedZone: tree.UnrestrictedName(name)}, nil 67 } 68 parentID, name, err := resolveID(id) 69 if err != nil { 70 return tree.ZoneSpecifier{}, err 71 } 72 if parentID == keys.RootNamespaceID { 73 return tree.ZoneSpecifier{Database: tree.Name(name)}, nil 74 } 75 _, db, err := resolveID(parentID) 76 if err != nil { 77 return tree.ZoneSpecifier{}, err 78 } 79 return tree.ZoneSpecifier{ 80 TableOrIndex: tree.TableIndexName{ 81 Table: tree.MakeTableName(tree.Name(db), tree.Name(name)), 82 }, 83 }, nil 84 } 85 86 // ResolveZoneSpecifier converts a zone specifier to the ID of most specific 87 // zone whose config applies. 88 func ResolveZoneSpecifier( 89 zs *tree.ZoneSpecifier, resolveName func(parentID uint32, name string) (id uint32, err error), 90 ) (uint32, error) { 91 // A zone specifier has one of 3 possible structures: 92 // - a predefined named zone; 93 // - a database name; 94 // - a table or index name. 95 if zs.NamedZone != "" { 96 if zs.NamedZone == DefaultZoneName { 97 return keys.RootNamespaceID, nil 98 } 99 if id, ok := NamedZones[string(zs.NamedZone)]; ok { 100 return id, nil 101 } 102 return 0, fmt.Errorf("%q is not a built-in zone", string(zs.NamedZone)) 103 } 104 105 if zs.Database != "" { 106 return resolveName(keys.RootNamespaceID, string(zs.Database)) 107 } 108 109 // Third case: a table or index name. We look up the table part here. 110 111 tn := &zs.TableOrIndex.Table 112 if tn.SchemaName != tree.PublicSchemaName { 113 return 0, pgerror.Newf(pgcode.ReservedName, 114 "only schema \"public\" is supported: %q", tree.ErrString(tn)) 115 } 116 databaseID, err := resolveName(keys.RootNamespaceID, tn.Catalog()) 117 if err != nil { 118 return 0, err 119 } 120 return resolveName(databaseID, tn.Table()) 121 } 122 123 func (c Constraint) String() string { 124 var str string 125 switch c.Type { 126 case Constraint_REQUIRED: 127 str += "+" 128 case Constraint_PROHIBITED: 129 str += "-" 130 } 131 if len(c.Key) > 0 { 132 str += c.Key + "=" 133 } 134 str += c.Value 135 return str 136 } 137 138 // FromString populates the constraint from the constraint shorthand notation. 139 func (c *Constraint) FromString(short string) error { 140 if len(short) == 0 { 141 return fmt.Errorf("the empty string is not a valid constraint") 142 } 143 switch short[0] { 144 case '+': 145 c.Type = Constraint_REQUIRED 146 short = short[1:] 147 case '-': 148 c.Type = Constraint_PROHIBITED 149 short = short[1:] 150 default: 151 c.Type = Constraint_DEPRECATED_POSITIVE 152 } 153 parts := strings.Split(short, "=") 154 if len(parts) == 1 { 155 c.Value = parts[0] 156 } else if len(parts) == 2 { 157 c.Key = parts[0] 158 c.Value = parts[1] 159 } else { 160 return errors.Errorf("constraint needs to be in the form \"(key=)value\", not %q", short) 161 } 162 return nil 163 } 164 165 // NewZoneConfig is the zone configuration used when no custom 166 // config has been specified. 167 func NewZoneConfig() *ZoneConfig { 168 return &ZoneConfig{ 169 InheritedConstraints: true, 170 InheritedLeasePreferences: true, 171 } 172 } 173 174 // EmptyCompleteZoneConfig is the zone configuration where 175 // all fields are set but set to their respective zero values. 176 func EmptyCompleteZoneConfig() *ZoneConfig { 177 return &ZoneConfig{ 178 NumReplicas: proto.Int32(0), 179 RangeMinBytes: proto.Int64(0), 180 RangeMaxBytes: proto.Int64(0), 181 GC: &GCPolicy{TTLSeconds: 0}, 182 InheritedConstraints: true, 183 InheritedLeasePreferences: true, 184 } 185 } 186 187 // DefaultZoneConfig is the default zone configuration used when no custom 188 // config has been specified. 189 func DefaultZoneConfig() ZoneConfig { 190 return ZoneConfig{ 191 NumReplicas: proto.Int32(3), 192 RangeMinBytes: proto.Int64(128 << 20), // 128 MB 193 RangeMaxBytes: proto.Int64(512 << 20), // 512 MB 194 GC: &GCPolicy{ 195 // Use 25 hours instead of the previous 24 to make users successful by 196 // default. Users desiring to take incremental backups every 24h may 197 // incorrectly assume that the previous default 24h was sufficient to do 198 // that. But the equation for incremental backups is: 199 // GC TTLSeconds >= (desired backup interval) + (time to perform incremental backup) 200 // We think most new users' incremental backups will complete within an 201 // hour, and larger clusters will have more experienced operators and will 202 // understand how to change these settings if needed. 203 TTLSeconds: 25 * 60 * 60, 204 }, 205 } 206 } 207 208 // DefaultZoneConfigRef is the default zone configuration used when no custom 209 // config has been specified. 210 func DefaultZoneConfigRef() *ZoneConfig { 211 zoneConfig := DefaultZoneConfig() 212 return &zoneConfig 213 } 214 215 // DefaultSystemZoneConfig is the default zone configuration used when no custom 216 // config has been specified. The DefaultSystemZoneConfig is like the 217 // DefaultZoneConfig but has a replication factor of 5 instead of 3. 218 func DefaultSystemZoneConfig() ZoneConfig { 219 defaultSystemZoneConfig := DefaultZoneConfig() 220 defaultSystemZoneConfig.NumReplicas = proto.Int32(5) 221 return defaultSystemZoneConfig 222 } 223 224 // DefaultSystemZoneConfigRef is the default zone configuration used when no custom 225 // config has been specified. 226 func DefaultSystemZoneConfigRef() *ZoneConfig { 227 systemZoneConfig := DefaultSystemZoneConfig() 228 return &systemZoneConfig 229 } 230 231 // IsComplete returns whether all the fields are set. 232 func (z *ZoneConfig) IsComplete() bool { 233 return ((z.NumReplicas != nil) && (z.RangeMinBytes != nil) && 234 (z.RangeMaxBytes != nil) && (z.GC != nil) && 235 (!z.InheritedConstraints) && (!z.InheritedLeasePreferences)) 236 } 237 238 // ValidateTandemFields returns an error if the ZoneConfig to be written 239 // specifies a configuration that could cause problems with the introduction 240 // of cascading zone configs. 241 func (z *ZoneConfig) ValidateTandemFields() error { 242 var numConstrainedRepls int32 243 for _, constraint := range z.Constraints { 244 numConstrainedRepls += constraint.NumReplicas 245 } 246 247 if numConstrainedRepls > 0 && z.NumReplicas == nil { 248 return fmt.Errorf("when per-replica constraints are set, num_replicas must be set as well") 249 } 250 if (z.RangeMinBytes != nil || z.RangeMaxBytes != nil) && 251 (z.RangeMinBytes == nil || z.RangeMaxBytes == nil) { 252 return fmt.Errorf("range_min_bytes and range_max_bytes must be set together") 253 } 254 if !z.InheritedLeasePreferences && z.InheritedConstraints { 255 return fmt.Errorf("lease preferences can not be set unless the constraints are explicitly set as well") 256 } 257 return nil 258 } 259 260 // Validate returns an error if the ZoneConfig specifies a known-dangerous or 261 // disallowed configuration. 262 func (z *ZoneConfig) Validate() error { 263 for _, s := range z.Subzones { 264 if err := s.Config.Validate(); err != nil { 265 return err 266 } 267 } 268 269 if z.NumReplicas != nil { 270 switch { 271 case *z.NumReplicas < 0: 272 return fmt.Errorf("at least one replica is required") 273 case *z.NumReplicas == 0: 274 if len(z.Subzones) > 0 { 275 // NumReplicas == 0 is allowed when this ZoneConfig is a subzone 276 // placeholder. See IsSubzonePlaceholder. 277 return nil 278 } 279 return fmt.Errorf("at least one replica is required") 280 case *z.NumReplicas == 2: 281 return fmt.Errorf("at least 3 replicas are required for multi-replica configurations") 282 } 283 } 284 285 if z.RangeMaxBytes != nil && *z.RangeMaxBytes < base.MinRangeMaxBytes { 286 return fmt.Errorf("RangeMaxBytes %d less than minimum allowed %d", 287 *z.RangeMaxBytes, base.MinRangeMaxBytes) 288 } 289 290 if z.RangeMinBytes != nil && *z.RangeMinBytes < 0 { 291 return fmt.Errorf("RangeMinBytes %d less than minimum allowed 0", *z.RangeMinBytes) 292 } 293 if z.RangeMinBytes != nil && z.RangeMaxBytes != nil && *z.RangeMinBytes >= *z.RangeMaxBytes { 294 return fmt.Errorf("RangeMinBytes %d is greater than or equal to RangeMaxBytes %d", 295 *z.RangeMinBytes, *z.RangeMaxBytes) 296 } 297 298 // Reserve the value 0 to potentially have some special meaning in the future, 299 // such as to disable GC. 300 if z.GC != nil && z.GC.TTLSeconds < 1 { 301 return fmt.Errorf("GC.TTLSeconds %d less than minimum allowed 1", z.GC.TTLSeconds) 302 } 303 304 for _, constraints := range z.Constraints { 305 for _, constraint := range constraints.Constraints { 306 if constraint.Type == Constraint_DEPRECATED_POSITIVE { 307 return fmt.Errorf("constraints must either be required (prefixed with a '+') or " + 308 "prohibited (prefixed with a '-')") 309 } 310 } 311 } 312 313 // We only need to further validate constraints if per-replica constraints 314 // are in use. The old style of constraints that apply to all replicas don't 315 // require validation. 316 if len(z.Constraints) > 1 || (len(z.Constraints) == 1 && z.Constraints[0].NumReplicas != 0) { 317 var numConstrainedRepls int64 318 for _, constraints := range z.Constraints { 319 if constraints.NumReplicas <= 0 { 320 return fmt.Errorf("constraints must apply to at least one replica") 321 } 322 numConstrainedRepls += int64(constraints.NumReplicas) 323 for _, constraint := range constraints.Constraints { 324 // TODO(a-robinson): Relax this constraint to allow prohibited replicas, 325 // as discussed on #23014. 326 if constraint.Type != Constraint_REQUIRED && z.NumReplicas != nil && constraints.NumReplicas != *z.NumReplicas { 327 return fmt.Errorf( 328 "only required constraints (prefixed with a '+') can be applied to a subset of replicas") 329 } 330 } 331 } 332 if z.NumReplicas != nil && numConstrainedRepls > int64(*z.NumReplicas) { 333 return fmt.Errorf("the number of replicas specified in constraints (%d) cannot be greater "+ 334 "than the number of replicas configured for the zone (%d)", 335 numConstrainedRepls, *z.NumReplicas) 336 } 337 } 338 339 for _, leasePref := range z.LeasePreferences { 340 if len(leasePref.Constraints) == 0 { 341 return fmt.Errorf("every lease preference must include at least one constraint") 342 } 343 for _, constraint := range leasePref.Constraints { 344 if constraint.Type == Constraint_DEPRECATED_POSITIVE { 345 return fmt.Errorf("lease preference constraints must either be required " + 346 "(prefixed with a '+') or prohibited (prefixed with a '-')") 347 } 348 } 349 } 350 351 return nil 352 } 353 354 // InheritFromParent hydrates a zones missing fields from its parent. 355 func (z *ZoneConfig) InheritFromParent(parent *ZoneConfig) { 356 // Allow for subzonePlaceholders to inherit fields from parents if needed. 357 if z.NumReplicas == nil || (z.NumReplicas != nil && *z.NumReplicas == 0) { 358 if parent.NumReplicas != nil { 359 z.NumReplicas = proto.Int32(*parent.NumReplicas) 360 } 361 } 362 if z.RangeMinBytes == nil { 363 if parent.RangeMinBytes != nil { 364 z.RangeMinBytes = proto.Int64(*parent.RangeMinBytes) 365 } 366 } 367 if z.RangeMaxBytes == nil { 368 if parent.RangeMaxBytes != nil { 369 z.RangeMaxBytes = proto.Int64(*parent.RangeMaxBytes) 370 } 371 } 372 if z.GC == nil { 373 if parent.GC != nil { 374 tempGC := *parent.GC 375 z.GC = &tempGC 376 } 377 } 378 if z.InheritedConstraints { 379 if !parent.InheritedConstraints { 380 z.Constraints = parent.Constraints 381 z.InheritedConstraints = false 382 } 383 } 384 if z.InheritedLeasePreferences { 385 if !parent.InheritedLeasePreferences { 386 z.LeasePreferences = parent.LeasePreferences 387 z.InheritedLeasePreferences = false 388 } 389 } 390 } 391 392 // CopyFromZone copies over the specified fields from the other zone. 393 func (z *ZoneConfig) CopyFromZone(other ZoneConfig, fieldList []tree.Name) { 394 for _, fieldName := range fieldList { 395 if fieldName == "num_replicas" { 396 z.NumReplicas = nil 397 if other.NumReplicas != nil { 398 z.NumReplicas = proto.Int32(*other.NumReplicas) 399 } 400 } 401 if fieldName == "range_min_bytes" { 402 z.RangeMinBytes = nil 403 if other.RangeMinBytes != nil { 404 z.RangeMinBytes = proto.Int64(*other.RangeMinBytes) 405 } 406 } 407 if fieldName == "range_max_bytes" { 408 z.RangeMaxBytes = nil 409 if other.RangeMaxBytes != nil { 410 z.RangeMaxBytes = proto.Int64(*other.RangeMaxBytes) 411 } 412 } 413 if fieldName == "gc.ttlseconds" { 414 z.GC = nil 415 if other.GC != nil { 416 tempGC := *other.GC 417 z.GC = &tempGC 418 } 419 } 420 if fieldName == "constraints" { 421 z.Constraints = other.Constraints 422 z.InheritedConstraints = other.InheritedConstraints 423 } 424 if fieldName == "lease_preferences" { 425 z.LeasePreferences = other.LeasePreferences 426 z.InheritedLeasePreferences = other.InheritedLeasePreferences 427 } 428 } 429 } 430 431 // StoreSatisfiesConstraint checks whether a store satisfies the given constraint. 432 // If the constraint is of the PROHIBITED type, satisfying it means the store 433 // not matching the constraint's spec. 434 func StoreSatisfiesConstraint(store roachpb.StoreDescriptor, constraint Constraint) bool { 435 hasConstraint := StoreMatchesConstraint(store, constraint) 436 if (constraint.Type == Constraint_REQUIRED && !hasConstraint) || 437 (constraint.Type == Constraint_PROHIBITED && hasConstraint) { 438 return false 439 } 440 return true 441 } 442 443 // StoreMatchesConstraint returns whether a store's attributes or node's 444 // locality match the constraint's spec. It notably ignores whether the 445 // constraint is required, prohibited, positive, or otherwise. 446 // Also see StoreSatisfiesConstraint(). 447 func StoreMatchesConstraint(store roachpb.StoreDescriptor, c Constraint) bool { 448 if c.Key == "" { 449 for _, attrs := range []roachpb.Attributes{store.Attrs, store.Node.Attrs} { 450 for _, attr := range attrs.Attrs { 451 if attr == c.Value { 452 return true 453 } 454 } 455 } 456 return false 457 } 458 for _, tier := range store.Node.Locality.Tiers { 459 if c.Key == tier.Key && c.Value == tier.Value { 460 return true 461 } 462 } 463 return false 464 } 465 466 // DeleteTableConfig removes any configuration that applies to the table 467 // targeted by this ZoneConfig, leaving only its subzone configs, if any. After 468 // calling DeleteTableConfig, IsSubzonePlaceholder will return true. 469 // 470 // Only table zones can have subzones, so it does not make sense to call this 471 // method on non-table ZoneConfigs. 472 func (z *ZoneConfig) DeleteTableConfig() { 473 *z = ZoneConfig{ 474 // Have to set NumReplicas to 0 so it is recognized as a placeholder. 475 NumReplicas: proto.Int32(0), 476 Subzones: z.Subzones, 477 SubzoneSpans: z.SubzoneSpans, 478 } 479 } 480 481 // IsSubzonePlaceholder returns whether the ZoneConfig exists only to store 482 // subzones. The configuration fields (e.g., RangeMinBytes) in a subzone 483 // placeholder should be ignored; instead, the configuration from the parent 484 // ZoneConfig applies. 485 func (z *ZoneConfig) IsSubzonePlaceholder() bool { 486 // A ZoneConfig with zero replicas is otherwise invalid, so we repurpose it to 487 // indicate that a ZoneConfig is a placeholder for subzones rather than 488 // introducing a dedicated IsPlaceholder flag. 489 return z.NumReplicas != nil && *z.NumReplicas == 0 490 } 491 492 // GetSubzone returns the most specific Subzone that applies to the specified 493 // index ID and partition, if any exists. The partition can be left unspecified 494 // to get the Subzone for an entire index, if it exists. indexID, however, must 495 // always be provided, even when looking for a partition's Subzone. 496 func (z *ZoneConfig) GetSubzone(indexID uint32, partition string) *Subzone { 497 for _, s := range z.Subzones { 498 if s.IndexID == indexID && s.PartitionName == partition { 499 copySubzone := s 500 return ©Subzone 501 } 502 } 503 if partition != "" { 504 return z.GetSubzone(indexID, "") 505 } 506 return nil 507 } 508 509 // GetSubzoneExact is similar to GetSubzone but does not find the most specific 510 // subzone that applies to a specified index and partition, as it finds either the 511 // exact config that applies, or returns nil. 512 func (z *ZoneConfig) GetSubzoneExact(indexID uint32, partition string) *Subzone { 513 for _, s := range z.Subzones { 514 if s.IndexID == indexID && s.PartitionName == partition { 515 copySubzone := s 516 return ©Subzone 517 } 518 } 519 return nil 520 } 521 522 // GetSubzoneForKeySuffix returns the ZoneConfig for the subzone that contains 523 // keySuffix, if it exists and its position in the subzones slice. 524 func (z ZoneConfig) GetSubzoneForKeySuffix(keySuffix []byte) (*Subzone, int32) { 525 // TODO(benesch): Use binary search instead. 526 for _, s := range z.SubzoneSpans { 527 // The span's Key is stored with the prefix removed, so we can compare 528 // directly to keySuffix. An unset EndKey implies Key.PrefixEnd(). 529 if (s.Key.Compare(keySuffix) <= 0) && 530 ((s.EndKey == nil && bytes.HasPrefix(keySuffix, s.Key)) || s.EndKey.Compare(keySuffix) > 0) { 531 copySubzone := z.Subzones[s.SubzoneIndex] 532 return ©Subzone, s.SubzoneIndex 533 } 534 } 535 return nil, -1 536 } 537 538 // SetSubzone installs subzone into the ZoneConfig, overwriting any existing 539 // subzone with the same IndexID and PartitionName. 540 func (z *ZoneConfig) SetSubzone(subzone Subzone) { 541 for i, s := range z.Subzones { 542 if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName { 543 z.Subzones[i] = subzone 544 return 545 } 546 } 547 z.Subzones = append(z.Subzones, subzone) 548 } 549 550 // DeleteSubzone removes the subzone with the specified index ID and partition. 551 // It returns whether it performed any work. 552 func (z *ZoneConfig) DeleteSubzone(indexID uint32, partition string) bool { 553 for i, s := range z.Subzones { 554 if s.IndexID == indexID && s.PartitionName == partition { 555 z.Subzones = append(z.Subzones[:i], z.Subzones[i+1:]...) 556 return true 557 } 558 } 559 return false 560 } 561 562 // DeleteIndexSubzones deletes all subzones that refer to the index with the 563 // specified ID. This includes subzones for partitions of the index as well as 564 // the index subzone itself. 565 func (z *ZoneConfig) DeleteIndexSubzones(indexID uint32) { 566 subzones := z.Subzones[:0] 567 for _, s := range z.Subzones { 568 if s.IndexID != indexID { 569 subzones = append(subzones, s) 570 } 571 } 572 z.Subzones = subzones 573 } 574 575 // SubzoneSplits returns the split points determined by a ZoneConfig's subzones. 576 func (z ZoneConfig) SubzoneSplits() []roachpb.RKey { 577 var out []roachpb.RKey 578 for _, span := range z.SubzoneSpans { 579 // TODO(benesch): avoid a split at the first partition's start key when it 580 // is the minimum possible value. 581 if len(out) == 0 || !out[len(out)-1].Equal(span.Key) { 582 // Only split at the start key when it differs from the last end key. 583 out = append(out, roachpb.RKey(span.Key)) 584 } 585 endKey := span.EndKey 586 if len(endKey) == 0 { 587 endKey = span.Key.PrefixEnd() 588 } 589 out = append(out, roachpb.RKey(endKey)) 590 // TODO(benesch): avoid a split at the last partition's end key when it is 591 // the maximum possible value. 592 } 593 return out 594 } 595 596 // ReplicaConstraintsCount is part of the cat.Zone interface. 597 func (z *ZoneConfig) ReplicaConstraintsCount() int { 598 return len(z.Constraints) 599 } 600 601 // ReplicaConstraints is part of the cat.Zone interface. 602 func (z *ZoneConfig) ReplicaConstraints(i int) cat.ReplicaConstraints { 603 return &z.Constraints[i] 604 } 605 606 // LeasePreferenceCount is part of the cat.Zone interface. 607 func (z *ZoneConfig) LeasePreferenceCount() int { 608 return len(z.LeasePreferences) 609 } 610 611 // LeasePreference is part of the cat.Zone interface. 612 func (z *ZoneConfig) LeasePreference(i int) cat.ConstraintSet { 613 return &z.LeasePreferences[i] 614 } 615 616 // ConstraintCount is part of the cat.LeasePreference interface. 617 func (l *LeasePreference) ConstraintCount() int { 618 return len(l.Constraints) 619 } 620 621 // Constraint is part of the cat.LeasePreference interface. 622 func (l *LeasePreference) Constraint(i int) cat.Constraint { 623 return &l.Constraints[i] 624 } 625 626 func (c ConstraintsConjunction) String() string { 627 var sb strings.Builder 628 for i, cons := range c.Constraints { 629 if i > 0 { 630 sb.WriteRune(',') 631 } 632 sb.WriteString(cons.String()) 633 } 634 if c.NumReplicas != 0 { 635 fmt.Fprintf(&sb, ":%d", c.NumReplicas) 636 } 637 return sb.String() 638 } 639 640 // ReplicaCount is part of the cat.ReplicaConstraints interface. 641 func (c *ConstraintsConjunction) ReplicaCount() int32 { 642 return c.NumReplicas 643 } 644 645 // ConstraintCount is part of the cat.ReplicaConstraints interface. 646 func (c *ConstraintsConjunction) ConstraintCount() int { 647 return len(c.Constraints) 648 } 649 650 // Constraint is part of the cat.ReplicaConstraints interface. 651 func (c *ConstraintsConjunction) Constraint(i int) cat.Constraint { 652 return &c.Constraints[i] 653 } 654 655 // IsRequired is part of the cat.Constraint interface. 656 func (c *Constraint) IsRequired() bool { 657 return c.Type == Constraint_REQUIRED 658 } 659 660 // GetKey is part of the cat.Constraint interface. 661 func (c *Constraint) GetKey() string { 662 return c.Key 663 } 664 665 // GetValue is part of the cat.Constraint interface. 666 func (c *Constraint) GetValue() string { 667 return c.Value 668 } 669 670 // TTL returns the implies TTL as a time.Duration. 671 func (m *GCPolicy) TTL() time.Duration { 672 return time.Duration(m.TTLSeconds) * time.Second 673 }