github.com/m3db/m3@v1.5.0/src/cluster/placement/storage/helper.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 storage
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    28  	"github.com/m3db/m3/src/cluster/kv"
    29  	"github.com/m3db/m3/src/cluster/placement"
    30  
    31  	"github.com/golang/protobuf/proto"
    32  )
    33  
    34  var (
    35  	errInvalidProtoForSinglePlacement    = errors.New("invalid proto for single placement")
    36  	errInvalidProtoForPlacementSnapshots = errors.New("invalid proto for placement snapshots")
    37  	errNoPlacementInTheSnapshots         = errors.New("not placement in the snapshots")
    38  )
    39  
    40  // helper handles placement marshaling and validation.
    41  type helper interface {
    42  	// Placement retrieves the placement stored in kv.Store.
    43  	Placement() (placement.Placement, int, error)
    44  
    45  	// PlacementProto retrieves the proto stored in kv.Store.
    46  	PlacementProto() (proto.Message, int, error)
    47  
    48  	// GenerateProto generates the proto message for the new placement, it may read the kv.Store
    49  	// if existing placement data is needed.
    50  	GenerateProto(p placement.Placement) (proto.Message, error)
    51  
    52  	// ValidateProto validates if the given proto message is valid for placement.
    53  	ValidateProto(proto proto.Message) error
    54  
    55  	// PlacementForVersion returns the placement of a specific version.
    56  	PlacementForVersion(version int) (placement.Placement, error)
    57  }
    58  
    59  // newHelper returns a new placement storage helper.
    60  func newHelper(store kv.Store, key string, opts placement.Options) helper {
    61  	if opts.IsStaged() {
    62  		return newStagedPlacementHelper(store, key, opts.Compress())
    63  	}
    64  
    65  	return newPlacementHelper(store, key)
    66  }
    67  
    68  type placementHelper struct {
    69  	store kv.Store
    70  	key   string
    71  }
    72  
    73  func newPlacementHelper(store kv.Store, key string) helper {
    74  	return &placementHelper{
    75  		store: store,
    76  		key:   key,
    77  	}
    78  }
    79  
    80  func (h *placementHelper) PlacementForVersion(version int) (placement.Placement, error) {
    81  	values, err := h.store.History(h.key, version, version+1)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	if len(values) != 1 {
    87  		return nil, fmt.Errorf("invalid number of placements returned: %d, expecting 1", len(values))
    88  	}
    89  
    90  	return placementFromValue(values[0])
    91  }
    92  
    93  func (h *placementHelper) Placement() (placement.Placement, int, error) {
    94  	v, err := h.store.Get(h.key)
    95  	if err != nil {
    96  		return nil, 0, err
    97  	}
    98  
    99  	p, err := placementFromValue(v)
   100  	return p, v.Version(), err
   101  }
   102  
   103  func (h *placementHelper) PlacementProto() (proto.Message, int, error) {
   104  	v, err := h.store.Get(h.key)
   105  	if err != nil {
   106  		return nil, 0, err
   107  	}
   108  
   109  	p, err := placementProtoFromValue(v)
   110  	return p, v.Version(), err
   111  }
   112  
   113  func (h *placementHelper) GenerateProto(p placement.Placement) (proto.Message, error) {
   114  	return p.Proto()
   115  }
   116  
   117  func (h *placementHelper) ValidateProto(proto proto.Message) error {
   118  	placementProto, ok := proto.(*placementpb.Placement)
   119  	if !ok {
   120  		return errInvalidProtoForSinglePlacement
   121  	}
   122  
   123  	p, err := placement.NewPlacementFromProto(placementProto)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	return placement.Validate(p)
   129  }
   130  
   131  type stagedPlacementHelper struct {
   132  	store    kv.Store
   133  	key      string
   134  	compress bool
   135  }
   136  
   137  func newStagedPlacementHelper(
   138  	store kv.Store,
   139  	key string,
   140  	compress bool,
   141  ) helper {
   142  	return &stagedPlacementHelper{
   143  		store:    store,
   144  		key:      key,
   145  		compress: compress,
   146  	}
   147  }
   148  
   149  // Placement returns the last placement in the snapshots.
   150  func (h *stagedPlacementHelper) Placement() (placement.Placement, int, error) {
   151  	ps, v, err := h.placements()
   152  	if err != nil {
   153  		return nil, 0, err
   154  	}
   155  
   156  	latest := ps.Latest()
   157  	latest.SetVersion(v)
   158  
   159  	return latest, v, nil
   160  }
   161  
   162  func (h *stagedPlacementHelper) PlacementProto() (proto.Message, int, error) {
   163  	value, err := h.store.Get(h.key)
   164  	if err != nil {
   165  		return nil, 0, err
   166  	}
   167  
   168  	ps, err := placementSnapshotsProtoFromValue(value)
   169  	return ps, value.Version(), err
   170  }
   171  
   172  // GenerateProto generates a proto message with placement slice
   173  // containing only single active placement - the specified placement with cutover time set to 0.
   174  // This ensures backward comapatiblity with clients that rely on staged placement
   175  // and expect to find at least one placement snapshot having CutoverNanos < now.
   176  func (h *stagedPlacementHelper) GenerateProto(p placement.Placement) (proto.Message, error) {
   177  	active := p.SetCutoverNanos(0)
   178  	ps, err := placement.NewPlacementsFromLatest(active)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	if h.compress {
   184  		return ps.ProtoCompressed()
   185  	}
   186  
   187  	return ps.Proto()
   188  }
   189  
   190  func (h *stagedPlacementHelper) ValidateProto(proto proto.Message) error {
   191  	placementsProto, ok := proto.(*placementpb.PlacementSnapshots)
   192  	if !ok {
   193  		return errInvalidProtoForPlacementSnapshots
   194  	}
   195  
   196  	_, err := placement.NewPlacementsFromProto(placementsProto)
   197  	return err
   198  }
   199  
   200  func (h *stagedPlacementHelper) placements() (*placement.Placements, int, error) {
   201  	value, err := h.store.Get(h.key)
   202  	if err != nil {
   203  		return nil, 0, err
   204  	}
   205  
   206  	ps, err := placementsFromValue(value)
   207  	return ps, value.Version(), err
   208  }
   209  
   210  func (h *stagedPlacementHelper) PlacementForVersion(version int) (placement.Placement, error) {
   211  	values, err := h.store.History(h.key, version, version+1)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	if len(values) != 1 {
   217  		return nil, fmt.Errorf("invalid number of placements returned: %d, expecting 1", len(values))
   218  	}
   219  
   220  	v := values[0]
   221  	ps, err := placementsFromValue(v)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	latest := ps.Latest()
   227  	latest.SetVersion(v.Version())
   228  
   229  	return latest, nil
   230  }
   231  
   232  func placementProtoFromValue(v kv.Value) (*placementpb.Placement, error) {
   233  	var placementProto placementpb.Placement
   234  	if err := v.Unmarshal(&placementProto); err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	return &placementProto, nil
   239  }
   240  
   241  func placementFromValue(v kv.Value) (placement.Placement, error) {
   242  	placementProto, err := placementProtoFromValue(v)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	p, err := placement.NewPlacementFromProto(placementProto)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	return p.SetVersion(v.Version()), nil
   253  }
   254  
   255  func placementSnapshotsProtoFromValue(v kv.Value) (*placementpb.PlacementSnapshots, error) {
   256  	var placementsProto placementpb.PlacementSnapshots
   257  	if err := v.Unmarshal(&placementsProto); err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	return &placementsProto, nil
   262  }
   263  
   264  func placementsFromValue(v kv.Value) (*placement.Placements, error) {
   265  	placementsProto, err := placementSnapshotsProtoFromValue(v)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	ps, err := placement.NewPlacementsFromProto(placementsProto)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	return ps, nil
   276  }