275 lines
9.0 KiB
Go
275 lines
9.0 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package state
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/go-memdb"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
// UpsertServiceRegistrations is used to insert a number of service
|
|
// registrations into the state store. It uses a single write transaction for
|
|
// efficiency, however, any error means no entries will be committed.
|
|
func (s *StateStore) UpsertServiceRegistrations(
|
|
msgType structs.MessageType, index uint64, services []*structs.ServiceRegistration) error {
|
|
|
|
// Grab a write transaction, so we can use this across all service inserts.
|
|
txn := s.db.WriteTxnMsgT(msgType, index)
|
|
defer txn.Abort()
|
|
|
|
// updated tracks whether any inserts have been made. This allows us to
|
|
// skip updating the index table if we do not need to.
|
|
var updated bool
|
|
|
|
// Iterate the array of services. In the event of a single error, all
|
|
// inserts fail via the txn.Abort() defer.
|
|
for _, service := range services {
|
|
serviceUpdated, err := s.upsertServiceRegistrationTxn(index, txn, service)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Ensure we track whether any inserts have been made.
|
|
updated = updated || serviceUpdated
|
|
}
|
|
|
|
// If we did not perform any inserts, exit early.
|
|
if !updated {
|
|
return nil
|
|
}
|
|
|
|
// Perform the index table update to mark the new inserts.
|
|
if err := txn.Insert(tableIndex, &IndexEntry{TableServiceRegistrations, index}); err != nil {
|
|
return fmt.Errorf("index update failed: %v", err)
|
|
}
|
|
|
|
return txn.Commit()
|
|
}
|
|
|
|
// upsertServiceRegistrationTxn inserts a single service registration into the
|
|
// state store using the provided write transaction. It is the responsibility
|
|
// of the caller to update the index table.
|
|
func (s *StateStore) upsertServiceRegistrationTxn(
|
|
index uint64, txn *txn, service *structs.ServiceRegistration) (bool, error) {
|
|
|
|
existing, err := txn.First(TableServiceRegistrations, indexID, service.Namespace, service.ID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
|
|
// Set up the indexes correctly to ensure existing indexes are maintained.
|
|
if existing != nil {
|
|
exist := existing.(*structs.ServiceRegistration)
|
|
if exist.Equal(service) {
|
|
return false, nil
|
|
}
|
|
service.CreateIndex = exist.CreateIndex
|
|
service.ModifyIndex = index
|
|
} else {
|
|
service.CreateIndex = index
|
|
service.ModifyIndex = index
|
|
}
|
|
|
|
// Insert the service registration into the table.
|
|
if err := txn.Insert(TableServiceRegistrations, service); err != nil {
|
|
return false, fmt.Errorf("service registration insert failed: %v", err)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// DeleteServiceRegistrationByID is responsible for deleting a single service
|
|
// registration based on it's ID and namespace. If the service registration is
|
|
// not found within state, an error will be returned.
|
|
func (s *StateStore) DeleteServiceRegistrationByID(
|
|
msgType structs.MessageType, index uint64, namespace, id string) error {
|
|
|
|
txn := s.db.WriteTxnMsgT(msgType, index)
|
|
defer txn.Abort()
|
|
|
|
if err := s.deleteServiceRegistrationByIDTxn(index, txn, namespace, id); err != nil {
|
|
return err
|
|
}
|
|
return txn.Commit()
|
|
}
|
|
|
|
func (s *StateStore) deleteServiceRegistrationByIDTxn(
|
|
index uint64, txn *txn, namespace, id string) error {
|
|
|
|
// Lookup the service registration by its ID and namespace. This is a
|
|
// unique index and therefore there will be a maximum of one entry.
|
|
existing, err := txn.First(TableServiceRegistrations, indexID, namespace, id)
|
|
if err != nil {
|
|
return fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
if existing == nil {
|
|
return errors.New("service registration not found")
|
|
}
|
|
|
|
// Delete the existing entry from the table.
|
|
if err := txn.Delete(TableServiceRegistrations, existing); err != nil {
|
|
return fmt.Errorf("service registration deletion failed: %v", err)
|
|
}
|
|
|
|
// Update the index table to indicate an update has occurred.
|
|
if err := txn.Insert(tableIndex, &IndexEntry{TableServiceRegistrations, index}); err != nil {
|
|
return fmt.Errorf("index update failed: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteServiceRegistrationByNodeID deletes all service registrations that
|
|
// belong on a single node. If there are no registrations tied to the nodeID,
|
|
// the call will noop without an error.
|
|
func (s *StateStore) DeleteServiceRegistrationByNodeID(
|
|
msgType structs.MessageType, index uint64, nodeID string) error {
|
|
|
|
txn := s.db.WriteTxnMsgT(msgType, index)
|
|
defer txn.Abort()
|
|
|
|
num, err := txn.DeleteAll(TableServiceRegistrations, indexNodeID, nodeID)
|
|
if err != nil {
|
|
return fmt.Errorf("deleting service registrations failed: %v", err)
|
|
}
|
|
|
|
// If we did not delete any entries, do not update the index table.
|
|
// Otherwise, update the table with the latest index.
|
|
switch num {
|
|
case 0:
|
|
return nil
|
|
default:
|
|
if err := txn.Insert(tableIndex, &IndexEntry{TableServiceRegistrations, index}); err != nil {
|
|
return fmt.Errorf("index update failed: %v", err)
|
|
}
|
|
}
|
|
|
|
return txn.Commit()
|
|
}
|
|
|
|
// GetServiceRegistrations returns an iterator that contains all service
|
|
// registrations stored within state. This is primarily useful when performing
|
|
// listings which use the namespace wildcard operator. The caller is
|
|
// responsible for ensuring ACL access is confirmed, or filtering is performed
|
|
// before responding.
|
|
func (s *StateStore) GetServiceRegistrations(ws memdb.WatchSet) (memdb.ResultIterator, error) {
|
|
txn := s.db.ReadTxn()
|
|
|
|
// Walk the entire table.
|
|
iter, err := txn.Get(TableServiceRegistrations, indexID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
return iter, nil
|
|
}
|
|
|
|
// GetServiceRegistrationsByNamespace returns an iterator that contains all
|
|
// registrations belonging to the provided namespace.
|
|
func (s *StateStore) GetServiceRegistrationsByNamespace(
|
|
ws memdb.WatchSet, namespace string) (memdb.ResultIterator, error) {
|
|
txn := s.db.ReadTxn()
|
|
|
|
// Walk the entire table.
|
|
iter, err := txn.Get(TableServiceRegistrations, indexID+"_prefix", namespace, "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
|
|
return iter, nil
|
|
}
|
|
|
|
// GetServiceRegistrationByName returns an iterator that contains all service
|
|
// registrations whose namespace and name match the input parameters. This func
|
|
// therefore represents how to identify a single, collection of services that
|
|
// are logically grouped together.
|
|
func (s *StateStore) GetServiceRegistrationByName(
|
|
ws memdb.WatchSet, namespace, name string) (memdb.ResultIterator, error) {
|
|
|
|
txn := s.db.ReadTxn()
|
|
|
|
iter, err := txn.Get(TableServiceRegistrations, indexServiceName, namespace, name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
|
|
return iter, nil
|
|
}
|
|
|
|
// GetServiceRegistrationByID returns a single registration. The registration
|
|
// will be nil, if no matching entry was found; it is the responsibility of the
|
|
// caller to check for this.
|
|
func (s *StateStore) GetServiceRegistrationByID(
|
|
ws memdb.WatchSet, namespace, id string) (*structs.ServiceRegistration, error) {
|
|
|
|
txn := s.db.ReadTxn()
|
|
|
|
watchCh, existing, err := txn.FirstWatch(TableServiceRegistrations, indexID, namespace, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
ws.Add(watchCh)
|
|
|
|
if existing != nil {
|
|
return existing.(*structs.ServiceRegistration), nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// GetServiceRegistrationsByAllocID returns an iterator containing all the
|
|
// service registrations corresponding to a single allocation.
|
|
func (s *StateStore) GetServiceRegistrationsByAllocID(
|
|
ws memdb.WatchSet, allocID string) (memdb.ResultIterator, error) {
|
|
|
|
txn := s.db.ReadTxn()
|
|
|
|
iter, err := txn.Get(TableServiceRegistrations, indexAllocID, allocID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
|
|
return iter, nil
|
|
}
|
|
|
|
// GetServiceRegistrationsByJobID returns an iterator containing all the
|
|
// service registrations corresponding to a single job.
|
|
func (s *StateStore) GetServiceRegistrationsByJobID(
|
|
ws memdb.WatchSet, namespace, jobID string) (memdb.ResultIterator, error) {
|
|
|
|
txn := s.db.ReadTxn()
|
|
|
|
iter, err := txn.Get(TableServiceRegistrations, indexJob, namespace, jobID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
|
|
return iter, nil
|
|
}
|
|
|
|
// GetServiceRegistrationsByNodeID identifies all service registrations tied to
|
|
// the specified nodeID. This is useful for performing an in-memory lookup in
|
|
// order to avoid calling DeleteServiceRegistrationByNodeID via a Raft message.
|
|
func (s *StateStore) GetServiceRegistrationsByNodeID(
|
|
ws memdb.WatchSet, nodeID string) ([]*structs.ServiceRegistration, error) {
|
|
|
|
txn := s.db.ReadTxn()
|
|
|
|
iter, err := txn.Get(TableServiceRegistrations, indexNodeID, nodeID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service registration lookup failed: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
|
|
var result []*structs.ServiceRegistration
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
result = append(result, raw.(*structs.ServiceRegistration))
|
|
}
|
|
|
|
return result, nil
|
|
}
|