github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/namespace/kvadmin/ns_admin.go (about)

     1  // Copyright (c) 2019 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 kvadmin
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/pborman/uuid"
    28  
    29  	"github.com/m3db/m3/src/cluster/kv"
    30  	nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace"
    31  	"github.com/m3db/m3/src/dbnode/namespace"
    32  	xerrors "github.com/m3db/m3/src/x/errors"
    33  )
    34  
    35  var (
    36  	// ErrNamespaceNotFound is returned when namespace is not found in registry.
    37  	ErrNamespaceNotFound = errors.New("namespace is not found")
    38  	// ErrNamespaceAlreadyExist is returned for addition of a namespace which already exists.
    39  	ErrNamespaceAlreadyExist = errors.New("namespace already exists")
    40  )
    41  
    42  type adminService struct {
    43  	store kv.Store
    44  	key   string
    45  	idGen func() string
    46  }
    47  
    48  const (
    49  	// M3DBNodeNamespacesKey is the KV key that holds namespaces.
    50  	M3DBNodeNamespacesKey = "m3db.node.namespaces"
    51  )
    52  
    53  func NewAdminService(store kv.Store, key string, idGen func() string) NamespaceMetadataAdminService {
    54  	if idGen == nil {
    55  		idGen = func() string {
    56  			return uuid.New()
    57  		}
    58  	}
    59  	if len(key) == 0 {
    60  		key = M3DBNodeNamespacesKey
    61  	}
    62  	return &adminService{
    63  		store: store,
    64  		key:   key,
    65  		idGen: idGen,
    66  	}
    67  }
    68  
    69  func (as *adminService) GetAll() (*nsproto.Registry, error) {
    70  	currentRegistry, _, err := as.currentRegistry()
    71  	if err == kv.ErrNotFound {
    72  		return nil, ErrNamespaceNotFound
    73  	}
    74  	if err != nil {
    75  		return nil, xerrors.Wrapf(err, "failed to load current namespace metadatas for %s", as.key)
    76  	}
    77  	return currentRegistry, nil
    78  }
    79  
    80  func (as *adminService) Get(name string) (*nsproto.NamespaceOptions, error) {
    81  	nsReg, err := as.GetAll()
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	if nsOpt, ok := nsReg.GetNamespaces()[name]; ok {
    86  		return nsOpt, nil
    87  	}
    88  	return nil, ErrNamespaceNotFound
    89  }
    90  
    91  func (as *adminService) Add(name string, options *nsproto.NamespaceOptions) error {
    92  	nsMeta, err := namespace.ToMetadata(name, options)
    93  	if err != nil {
    94  		return xerrors.Wrapf(err, "invalid namespace options for namespace: %v", name)
    95  	}
    96  	currentRegistry, currentVersion, err := as.currentRegistry()
    97  	if err == kv.ErrNotFound {
    98  		_, err = as.store.SetIfNotExists(as.key, &nsproto.Registry{
    99  			Namespaces: map[string]*nsproto.NamespaceOptions{name: options},
   100  		})
   101  		if err != nil {
   102  			return xerrors.Wrapf(err, "failed to add namespace %v", name)
   103  		}
   104  		return nil
   105  	}
   106  	if err != nil {
   107  		return xerrors.Wrapf(err, "failed to load namespace registry at %s", as.key)
   108  	}
   109  
   110  	if _, ok := currentRegistry.GetNamespaces()[name]; ok {
   111  		return ErrNamespaceAlreadyExist
   112  	}
   113  	nsMap, err := namespace.FromProto(*currentRegistry)
   114  	if err != nil {
   115  		return xerrors.Wrap(err, "failed to unmarshall namespace registry")
   116  	}
   117  
   118  	newMap, err := namespace.NewMap(append(nsMap.Metadatas(), nsMeta))
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	protoMap, err := namespace.ToProto(newMap)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	_, err = as.store.CheckAndSet(as.key, currentVersion, protoMap)
   129  	if err != nil {
   130  		return xerrors.Wrapf(err, "failed to add namespace %v", name)
   131  	}
   132  	return nil
   133  }
   134  
   135  func (as *adminService) Set(name string, options *nsproto.NamespaceOptions) error {
   136  	_, err := namespace.ToMetadata(name, options)
   137  	if err != nil {
   138  		return xerrors.Wrapf(err, "invalid options for namespace: %v", name)
   139  	}
   140  	currentRegistry, currentVersion, err := as.currentRegistry()
   141  	if err != nil {
   142  		return xerrors.Wrapf(err, "failed to load namespace registry at %s", as.key)
   143  	}
   144  	if _, ok := currentRegistry.GetNamespaces()[name]; !ok {
   145  		return ErrNamespaceNotFound
   146  	}
   147  
   148  	currentRegistry.Namespaces[name] = options
   149  
   150  	_, err = as.store.CheckAndSet(as.key, currentVersion, currentRegistry)
   151  	if err != nil {
   152  		return xerrors.Wrapf(err, "failed to update namespace %v", name)
   153  	}
   154  	return nil
   155  }
   156  
   157  func (as *adminService) Delete(name string) error {
   158  	currentRegistry, currentVersion, err := as.currentRegistry()
   159  	if err != nil {
   160  		return xerrors.Wrapf(err, "failed to load current namespace metadatas for %s", as.key)
   161  	}
   162  
   163  	nsMap, err := namespace.FromProto(*currentRegistry)
   164  	if err != nil {
   165  		return xerrors.Wrap(err, "failed to unmarshal namespace registry")
   166  	}
   167  
   168  	metadatas := nsMap.Metadatas()
   169  	mdIdx := -1
   170  	for idx, md := range nsMap.Metadatas() {
   171  		if md.ID().String() == name {
   172  			mdIdx = idx
   173  
   174  			break
   175  		}
   176  	}
   177  
   178  	if mdIdx == -1 {
   179  		return ErrNamespaceNotFound
   180  	}
   181  
   182  	if len(metadatas) == 1 {
   183  		if _, err := as.store.Delete(as.key); err != nil {
   184  			return xerrors.Wrap(err, "failed to delete kv key")
   185  		}
   186  
   187  		return nil
   188  	}
   189  
   190  	// Replace the index where we found the metadata with the last element, then truncate
   191  	metadatas[mdIdx] = metadatas[len(metadatas)-1]
   192  	metadatas = metadatas[:len(metadatas)-1]
   193  
   194  	newMap, err := namespace.NewMap(metadatas)
   195  	if err != nil {
   196  		return xerrors.Wrap(err, "namespace map construction failed")
   197  	}
   198  
   199  	protoMap, err := namespace.ToProto(newMap)
   200  	if err != nil {
   201  		return xerrors.Wrap(err, "namespace registry proto conversion failed")
   202  	}
   203  
   204  	_, err = as.store.CheckAndSet(as.key, currentVersion, protoMap)
   205  	if err != nil {
   206  		return xerrors.Wrapf(err, "failed to delete namespace %v", name)
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func (as *adminService) ResetSchema(name string) error {
   213  	currentRegistry, currentVersion, err := as.currentRegistry()
   214  	if err == kv.ErrNotFound {
   215  		return ErrNamespaceNotFound
   216  	}
   217  	if err != nil {
   218  		return xerrors.Wrapf(err, "failed to load current namespace metadatas for %s", as.key)
   219  	}
   220  
   221  	var targetMeta *nsproto.NamespaceOptions
   222  	for nsID, nsOpts := range currentRegistry.GetNamespaces() {
   223  		if nsID == name {
   224  			targetMeta = nsOpts
   225  			break
   226  		}
   227  	}
   228  	if targetMeta == nil {
   229  		return ErrNamespaceNotFound
   230  	}
   231  
   232  	// Clear schema options in place.
   233  	targetMeta.SchemaOptions = nil
   234  
   235  	_, err = as.store.CheckAndSet(as.key, currentVersion, currentRegistry)
   236  	if err != nil {
   237  		return xerrors.Wrapf(err, "failed to reset schema for namespace %s", name)
   238  	}
   239  	return nil
   240  }
   241  
   242  func (as *adminService) DeploySchema(name, protoFileName, msgName string, protos map[string]string) (string, error) {
   243  	currentRegistry, currentVersion, err := as.currentRegistry()
   244  	if err == kv.ErrNotFound {
   245  		return "", ErrNamespaceNotFound
   246  	}
   247  	if err != nil {
   248  		return "", xerrors.Wrapf(err, "failed to load current namespace metadatas for %s", as.key)
   249  	}
   250  	var targetMeta *nsproto.NamespaceOptions
   251  	for nsID, nsOpts := range currentRegistry.GetNamespaces() {
   252  		if nsID == name {
   253  			targetMeta = nsOpts
   254  			break
   255  		}
   256  	}
   257  	if targetMeta == nil {
   258  		return "", ErrNamespaceNotFound
   259  	}
   260  
   261  	deployID := as.idGen()
   262  
   263  	schemaOpt, err := namespace.AppendSchemaOptions(targetMeta.SchemaOptions,
   264  		protoFileName, msgName, protos, deployID)
   265  	if err != nil {
   266  		return "", xerrors.Wrapf(err, "failed to append schema history from %s for message %s", protoFileName, msgName)
   267  	}
   268  
   269  	// Update schema options in place.
   270  	targetMeta.SchemaOptions = schemaOpt
   271  
   272  	_, err = as.store.CheckAndSet(as.key, currentVersion, currentRegistry)
   273  	if err != nil {
   274  		return "", xerrors.Wrapf(err, "failed to deploy schema from %s with version %s to namespace %s", protoFileName, deployID, name)
   275  	}
   276  	return deployID, nil
   277  }
   278  
   279  func (as *adminService) currentRegistry() (*nsproto.Registry, int, error) {
   280  	value, err := as.store.Get(as.key)
   281  	if err != nil {
   282  		return nil, -1, err
   283  	}
   284  
   285  	var protoRegistry nsproto.Registry
   286  	if err := value.Unmarshal(&protoRegistry); err != nil {
   287  		return nil, -1, fmt.Errorf("unable to parse value, err: %v", err)
   288  	}
   289  
   290  	return &protoRegistry, value.Version(), nil
   291  }
   292  
   293  func LoadSchemaRegistryFromKVStore(schemaReg namespace.SchemaRegistry, kvStore kv.Store) error {
   294  	if kvStore == nil {
   295  		return errors.New("m3db metadata store is not configured properly")
   296  	}
   297  	as := NewAdminService(kvStore, "", nil)
   298  	nsReg, err := as.GetAll()
   299  	if err != nil {
   300  		return xerrors.Wrap(err, "could not get metadata from metadata store")
   301  	}
   302  	nsMap, err := namespace.FromProto(*nsReg)
   303  	if err != nil {
   304  		return xerrors.Wrap(err, "could not unmarshal metadata")
   305  	}
   306  	merr := xerrors.NewMultiError()
   307  	for _, metadata := range nsMap.Metadatas() {
   308  		err = schemaReg.SetSchemaHistory(metadata.ID(), metadata.Options().SchemaHistory())
   309  		if err != nil {
   310  			merr = merr.Add(xerrors.Wrapf(err, "could not set schema history for namespace %s", metadata.ID().String()))
   311  		}
   312  	}
   313  	return merr.FinalError()
   314  }