github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/config/zonepb/zone_yaml.go (about)

     1  // Copyright 2018 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  	"fmt"
    15  	"runtime/debug"
    16  	"sort"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/errors"
    20  	"github.com/gogo/protobuf/proto"
    21  	"gopkg.in/yaml.v2"
    22  )
    23  
    24  var _ yaml.Marshaler = LeasePreference{}
    25  var _ yaml.Unmarshaler = &LeasePreference{}
    26  
    27  // MarshalYAML implements yaml.Marshaler.
    28  func (l LeasePreference) MarshalYAML() (interface{}, error) {
    29  	short := make([]string, len(l.Constraints))
    30  	for i, c := range l.Constraints {
    31  		short[i] = c.String()
    32  	}
    33  	return short, nil
    34  }
    35  
    36  // UnmarshalYAML implements yaml.Unmarshaler.
    37  func (l *LeasePreference) UnmarshalYAML(unmarshal func(interface{}) error) error {
    38  	var shortConstraints []string
    39  	if err := unmarshal(&shortConstraints); err != nil {
    40  		return err
    41  	}
    42  	constraints := make([]Constraint, len(shortConstraints))
    43  	for i, short := range shortConstraints {
    44  		if err := constraints[i].FromString(short); err != nil {
    45  			return err
    46  		}
    47  	}
    48  	l.Constraints = constraints
    49  	return nil
    50  }
    51  
    52  var _ yaml.Marshaler = ConstraintsConjunction{}
    53  var _ yaml.Unmarshaler = &ConstraintsConjunction{}
    54  
    55  // MarshalYAML implements yaml.Marshaler.
    56  func (c ConstraintsConjunction) MarshalYAML() (interface{}, error) {
    57  	return nil, fmt.Errorf(
    58  		"MarshalYAML should never be called directly on Constraints (%v): %v", c, debug.Stack())
    59  }
    60  
    61  // UnmarshalYAML implements yaml.Marshaler.
    62  func (c *ConstraintsConjunction) UnmarshalYAML(unmarshal func(interface{}) error) error {
    63  	return fmt.Errorf(
    64  		"UnmarshalYAML should never be called directly on Constraints: %v", debug.Stack())
    65  }
    66  
    67  // ConstraintsList is an alias for a slice of Constraints that can be
    68  // properly marshaled to/from YAML.
    69  type ConstraintsList struct {
    70  	Constraints []ConstraintsConjunction
    71  	Inherited   bool
    72  }
    73  
    74  var _ yaml.Marshaler = ConstraintsList{}
    75  var _ yaml.Unmarshaler = &ConstraintsList{}
    76  
    77  // MarshalYAML implements yaml.Marshaler.
    78  //
    79  // We use two different formats here, dependent on whether per-replica
    80  // constraints are being used in ConstraintsList:
    81  // 1. A legacy format when there are 0 or 1 Constraints and NumReplicas is
    82  //    zero:
    83  //    [c1, c2, c3]
    84  // 2. A per-replica format when NumReplicas is non-zero:
    85  //    {"c1,c2,c3": numReplicas1, "c4,c5": numReplicas2}
    86  func (c ConstraintsList) MarshalYAML() (interface{}, error) {
    87  	// If per-replica Constraints aren't in use, marshal everything into a list
    88  	// for compatibility with pre-2.0-style configs.
    89  	if c.Inherited || len(c.Constraints) == 0 {
    90  		return []string{}, nil
    91  	}
    92  	if len(c.Constraints) == 1 && c.Constraints[0].NumReplicas == 0 {
    93  		short := make([]string, len(c.Constraints[0].Constraints))
    94  		for i, constraint := range c.Constraints[0].Constraints {
    95  			short[i] = constraint.String()
    96  		}
    97  		return short, nil
    98  	}
    99  
   100  	// Otherwise, convert into a map from Constraints to NumReplicas.
   101  	constraintsMap := make(map[string]int32)
   102  	for _, constraints := range c.Constraints {
   103  		short := make([]string, len(constraints.Constraints))
   104  		for i, constraint := range constraints.Constraints {
   105  			short[i] = constraint.String()
   106  		}
   107  		constraintsMap[strings.Join(short, ",")] = constraints.NumReplicas
   108  	}
   109  	return constraintsMap, nil
   110  }
   111  
   112  // UnmarshalYAML implements yaml.Unmarshaler.
   113  func (c *ConstraintsList) UnmarshalYAML(unmarshal func(interface{}) error) error {
   114  	// Note that we're intentionally checking for err == nil here. This handles
   115  	// unmarshaling the legacy Constraints format, which is just a list of
   116  	// strings.
   117  	var strs []string
   118  	c.Inherited = true
   119  	if err := unmarshal(&strs); err == nil {
   120  		constraints := make([]Constraint, len(strs))
   121  		for i, short := range strs {
   122  			if err := constraints[i].FromString(short); err != nil {
   123  				return err
   124  			}
   125  		}
   126  		if len(constraints) == 0 {
   127  			c.Constraints = []ConstraintsConjunction{}
   128  			c.Inherited = false
   129  		} else {
   130  			c.Constraints = []ConstraintsConjunction{
   131  				{
   132  					Constraints: constraints,
   133  					NumReplicas: 0,
   134  				},
   135  			}
   136  			c.Inherited = false
   137  		}
   138  		return nil
   139  	}
   140  
   141  	// Otherwise, the input must be a map that can be converted to per-replica
   142  	// constraints.
   143  	constraintsMap := make(map[string]int32)
   144  	if err := unmarshal(&constraintsMap); err != nil {
   145  		return errors.New(
   146  			"invalid constraints format. expected an array of strings or a map of strings to ints")
   147  	}
   148  
   149  	constraintsList := make([]ConstraintsConjunction, 0, len(constraintsMap))
   150  	for constraintsStr, numReplicas := range constraintsMap {
   151  		shortConstraints := strings.Split(constraintsStr, ",")
   152  		constraints := make([]Constraint, len(shortConstraints))
   153  		for i, short := range shortConstraints {
   154  			if err := constraints[i].FromString(short); err != nil {
   155  				return err
   156  			}
   157  		}
   158  		constraintsList = append(constraintsList, ConstraintsConjunction{
   159  			Constraints: constraints,
   160  			NumReplicas: numReplicas,
   161  		})
   162  	}
   163  
   164  	// Sort the resulting list for reproducible orderings in tests.
   165  	sort.Slice(constraintsList, func(i, j int) bool {
   166  		// First, try to find which Constraints sort first alphabetically in string
   167  		// format, considering the shorter list lesser if they're otherwise equal.
   168  		for k := range constraintsList[i].Constraints {
   169  			if k >= len(constraintsList[j].Constraints) {
   170  				return false
   171  			}
   172  			lStr := constraintsList[i].Constraints[k].String()
   173  			rStr := constraintsList[j].Constraints[k].String()
   174  			if lStr < rStr {
   175  				return true
   176  			}
   177  			if lStr > rStr {
   178  				return false
   179  			}
   180  		}
   181  		if len(constraintsList[i].Constraints) < len(constraintsList[j].Constraints) {
   182  			return true
   183  		}
   184  		// If they're completely equal and the same length, go by NumReplicas.
   185  		return constraintsList[i].NumReplicas < constraintsList[j].NumReplicas
   186  	})
   187  
   188  	c.Constraints = constraintsList
   189  	c.Inherited = false
   190  	return nil
   191  }
   192  
   193  // marshalableZoneConfig should be kept up-to-date with the real,
   194  // auto-generated ZoneConfig type, but with []Constraints changed to
   195  // ConstraintsList for backwards-compatible yaml marshaling and unmarshaling.
   196  // We also support parsing both lease_preferences (for v2.1+) and
   197  // experimental_lease_preferences (for v2.0), copying both into the same proto
   198  // field as needed.
   199  //
   200  // TODO(a-robinson,v2.2): Remove the experimental_lease_preferences field.
   201  type marshalableZoneConfig struct {
   202  	RangeMinBytes                *int64            `json:"range_min_bytes" yaml:"range_min_bytes"`
   203  	RangeMaxBytes                *int64            `json:"range_max_bytes" yaml:"range_max_bytes"`
   204  	GC                           *GCPolicy         `json:"gc"`
   205  	NumReplicas                  *int32            `json:"num_replicas" yaml:"num_replicas"`
   206  	Constraints                  ConstraintsList   `json:"constraints" yaml:"constraints,flow"`
   207  	LeasePreferences             []LeasePreference `json:"lease_preferences" yaml:"lease_preferences,flow"`
   208  	ExperimentalLeasePreferences []LeasePreference `json:"experimental_lease_preferences" yaml:"experimental_lease_preferences,flow,omitempty"`
   209  	Subzones                     []Subzone         `json:"subzones" yaml:"-"`
   210  	SubzoneSpans                 []SubzoneSpan     `json:"subzone_spans" yaml:"-"`
   211  }
   212  
   213  func zoneConfigToMarshalable(c ZoneConfig) marshalableZoneConfig {
   214  	var m marshalableZoneConfig
   215  	if c.RangeMinBytes != nil {
   216  		m.RangeMinBytes = proto.Int64(*c.RangeMinBytes)
   217  	}
   218  	if c.RangeMaxBytes != nil {
   219  		m.RangeMaxBytes = proto.Int64(*c.RangeMaxBytes)
   220  	}
   221  	if c.GC != nil {
   222  		tempGC := *c.GC
   223  		m.GC = &tempGC
   224  	}
   225  	if c.NumReplicas != nil && *c.NumReplicas != 0 {
   226  		m.NumReplicas = proto.Int32(*c.NumReplicas)
   227  	}
   228  	m.Constraints = ConstraintsList{c.Constraints, c.InheritedConstraints}
   229  	if !c.InheritedLeasePreferences {
   230  		m.LeasePreferences = c.LeasePreferences
   231  	}
   232  	// We intentionally do not round-trip ExperimentalLeasePreferences. We never
   233  	// want to return yaml containing it.
   234  	m.Subzones = c.Subzones
   235  	m.SubzoneSpans = c.SubzoneSpans
   236  	return m
   237  }
   238  
   239  // zoneConfigFromMarshalable returns a ZoneConfig from the marshaled struct
   240  // NOTE: The config passed in the parameter is used so we can determine keep
   241  // the original value of the InheritedLeasePreferences field in the output.
   242  func zoneConfigFromMarshalable(m marshalableZoneConfig, c ZoneConfig) ZoneConfig {
   243  	if m.RangeMinBytes != nil {
   244  		c.RangeMinBytes = proto.Int64(*m.RangeMinBytes)
   245  	}
   246  	if m.RangeMaxBytes != nil {
   247  		c.RangeMaxBytes = proto.Int64(*m.RangeMaxBytes)
   248  	}
   249  	if m.GC != nil {
   250  		tempGC := *m.GC
   251  		c.GC = &tempGC
   252  	}
   253  	if m.NumReplicas != nil {
   254  		c.NumReplicas = proto.Int32(*m.NumReplicas)
   255  	}
   256  	c.Constraints = m.Constraints.Constraints
   257  	c.InheritedConstraints = m.Constraints.Inherited
   258  	if m.LeasePreferences != nil {
   259  		c.LeasePreferences = m.LeasePreferences
   260  	}
   261  
   262  	// Prefer a provided m.ExperimentalLeasePreferences value over whatever is in
   263  	// m.LeasePreferences, since we know that m.ExperimentalLeasePreferences can
   264  	// only possibly come from the user-specified input, whereas
   265  	// m.LeasePreferences could be the old value of the field retrieved from
   266  	// internal storage that the user is now trying to overwrite.
   267  	if m.ExperimentalLeasePreferences != nil {
   268  		c.LeasePreferences = m.ExperimentalLeasePreferences
   269  	}
   270  
   271  	if m.LeasePreferences != nil || m.ExperimentalLeasePreferences != nil {
   272  		c.InheritedLeasePreferences = false
   273  	}
   274  	c.Subzones = m.Subzones
   275  	c.SubzoneSpans = m.SubzoneSpans
   276  	return c
   277  }
   278  
   279  var _ yaml.Marshaler = ZoneConfig{}
   280  var _ yaml.Unmarshaler = &ZoneConfig{}
   281  
   282  // MarshalYAML implements yaml.Marshaler.
   283  func (c ZoneConfig) MarshalYAML() (interface{}, error) {
   284  	return zoneConfigToMarshalable(c), nil
   285  }
   286  
   287  // UnmarshalYAML implements yaml.Unmarshaler.
   288  func (c *ZoneConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
   289  	// Pre-initialize aux with the contents of c. This is important for
   290  	// maintaining the behavior of not overwriting existing fields unless the
   291  	// user provided new values for them.
   292  	aux := zoneConfigToMarshalable(*c)
   293  	if err := unmarshal(&aux); err != nil {
   294  		return err
   295  	}
   296  	*c = zoneConfigFromMarshalable(aux, *c)
   297  	return nil
   298  }