2015-11-07 00:59:32 +00:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-11-10 05:48:35 +00:00
|
|
|
"regexp"
|
2015-11-07 00:59:32 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/consul/structs"
|
|
|
|
"github.com/hashicorp/go-memdb"
|
|
|
|
)
|
|
|
|
|
2015-11-10 05:48:35 +00:00
|
|
|
// validUUID is used to check if a given string looks like a UUID
|
|
|
|
var validUUID = regexp.MustCompile(`^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$`)
|
|
|
|
|
|
|
|
// isUUID returns true if the given string is a valid UUID.
|
|
|
|
func isUUID(str string) bool {
|
|
|
|
return validUUID.MatchString(str)
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PreparedQueries is used to pull all the prepared queries from the snapshot.
|
|
|
|
func (s *StateSnapshot) PreparedQueries() (memdb.ResultIterator, error) {
|
|
|
|
iter, err := s.tx.Get("prepared-queries", "id")
|
2015-11-07 00:59:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return iter, nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PrepparedQuery is used when restoring from a snapshot. For general inserts,
|
|
|
|
// use PreparedQuerySet.
|
|
|
|
func (s *StateRestore) PreparedQuery(query *structs.PreparedQuery) error {
|
|
|
|
if err := s.tx.Insert("prepared-queries", query); err != nil {
|
|
|
|
return fmt.Errorf("failed restoring prepared query: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := indexUpdateMaxTxn(s.tx, query.ModifyIndex, "prepared-queries"); err != nil {
|
2015-11-07 00:59:32 +00:00
|
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
s.watches.Arm("prepared-queries")
|
2015-11-07 00:59:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PreparedQuerySet is used to create or update a prepared query.
|
|
|
|
func (s *StateStore) PreparedQuerySet(idx uint64, query *structs.PreparedQuery) error {
|
2015-11-07 00:59:32 +00:00
|
|
|
tx := s.db.Txn(true)
|
|
|
|
defer tx.Abort()
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := s.preparedQuerySetTxn(tx, idx, query); err != nil {
|
2015-11-07 00:59:32 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// preparedQuerySetTxn is the inner method used to insert a prepared query with
|
|
|
|
// the proper indexes into the state store.
|
|
|
|
func (s *StateStore) preparedQuerySetTxn(tx *memdb.Txn, idx uint64, query *structs.PreparedQuery) error {
|
2015-11-07 00:59:32 +00:00
|
|
|
// Check that the ID is set.
|
|
|
|
if query.ID == "" {
|
|
|
|
return ErrMissingQueryID
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for an existing query.
|
2015-11-10 04:37:41 +00:00
|
|
|
existing, err := tx.First("prepared-queries", "id", query.ID)
|
2015-11-07 00:59:32 +00:00
|
|
|
if err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("failed prepared query lookup: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set the indexes.
|
|
|
|
if existing != nil {
|
|
|
|
query.CreateIndex = existing.(*structs.PreparedQuery).CreateIndex
|
|
|
|
query.ModifyIndex = idx
|
|
|
|
} else {
|
|
|
|
query.CreateIndex = idx
|
|
|
|
query.ModifyIndex = idx
|
|
|
|
}
|
|
|
|
|
2015-11-10 05:15:55 +00:00
|
|
|
// Verify that the name doesn't alias any existing ID. We allow queries
|
|
|
|
// to be looked up by ID *or* name so we don't want anyone to try to
|
|
|
|
// register a query with a name equal to some other query's ID in an
|
|
|
|
// attempt to hijack it. We also look up by ID *then* name in order to
|
|
|
|
// prevent this, but it seems prudent to prevent these types of rogue
|
2015-11-10 05:48:35 +00:00
|
|
|
// queries from ever making it into the state store. Note that we have
|
|
|
|
// to see if the name looks like a UUID before checking since the UUID
|
|
|
|
// index will complain if we look up something that's not formatted
|
|
|
|
// like one.
|
|
|
|
if isUUID(query.Name) {
|
2015-11-10 04:37:41 +00:00
|
|
|
existing, err := tx.First("prepared-queries", "id", query.Name)
|
2015-11-10 05:48:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed prepared query lookup: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
if existing != nil {
|
|
|
|
return fmt.Errorf("name '%s' aliases an existing query id", query.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify that the session exists.
|
|
|
|
if query.Session != "" {
|
|
|
|
sess, err := tx.First("sessions", "id", query.Session)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed session lookup: %s", err)
|
|
|
|
}
|
|
|
|
if sess == nil {
|
|
|
|
return fmt.Errorf("invalid session %#v", query.Session)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify that the service exists.
|
|
|
|
service, err := tx.First("services", "service", query.Service.Service)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed service lookup: %s", err)
|
|
|
|
}
|
|
|
|
if service == nil {
|
|
|
|
return fmt.Errorf("invalid service %#v", query.Service.Service)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert the query.
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := tx.Insert("prepared-queries", query); err != nil {
|
|
|
|
return fmt.Errorf("failed inserting prepared query: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := tx.Insert("index", &IndexEntry{"prepared-queries", idx}); err != nil {
|
2015-11-07 00:59:32 +00:00
|
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
tx.Defer(func() { s.tableWatches["prepared-queries"].Notify() })
|
2015-11-07 00:59:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PreparedQueryDelete deletes the given query by ID.
|
|
|
|
func (s *StateStore) PreparedQueryDelete(idx uint64, queryID string) error {
|
2015-11-07 00:59:32 +00:00
|
|
|
tx := s.db.Txn(true)
|
|
|
|
defer tx.Abort()
|
|
|
|
|
|
|
|
watches := NewDumbWatchManager(s.tableWatches)
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := s.preparedQueryDeleteTxn(tx, idx, watches, queryID); err != nil {
|
|
|
|
return fmt.Errorf("failed prepared query delete: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tx.Defer(func() { watches.Notify() })
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// preparedQueryDeleteTxn is the inner method used to delete a prepared query
|
|
|
|
// with the proper indexes into the state store.
|
|
|
|
func (s *StateStore) preparedQueryDeleteTxn(tx *memdb.Txn, idx uint64, watches *DumbWatchManager,
|
2015-11-07 00:59:32 +00:00
|
|
|
queryID string) error {
|
|
|
|
// Pull the query.
|
2015-11-10 04:37:41 +00:00
|
|
|
query, err := tx.First("prepared-queries", "id", queryID)
|
2015-11-07 00:59:32 +00:00
|
|
|
if err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return fmt.Errorf("failed prepared query lookup: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
if query == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the query and update the index.
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := tx.Delete("prepared-queries", query); err != nil {
|
|
|
|
return fmt.Errorf("failed prepared query delete: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
2015-11-10 04:37:41 +00:00
|
|
|
if err := tx.Insert("index", &IndexEntry{"prepared-queries", idx}); err != nil {
|
2015-11-07 00:59:32 +00:00
|
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
watches.Arm("prepared-queries")
|
2015-11-07 00:59:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PreparedQueryGet returns the given prepared query by ID.
|
|
|
|
func (s *StateStore) PreparedQueryGet(queryID string) (uint64, *structs.PreparedQuery, error) {
|
2015-11-07 00:59:32 +00:00
|
|
|
tx := s.db.Txn(false)
|
|
|
|
defer tx.Abort()
|
|
|
|
|
|
|
|
// Get the table index.
|
2015-11-10 04:37:41 +00:00
|
|
|
idx := maxIndexTxn(tx, s.getWatchTables("PreparedQueryGet")...)
|
2015-11-07 00:59:32 +00:00
|
|
|
|
|
|
|
// Look up the query by its ID.
|
2015-11-10 04:37:41 +00:00
|
|
|
query, err := tx.First("prepared-queries", "id", queryID)
|
2015-11-07 00:59:32 +00:00
|
|
|
if err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
if query != nil {
|
|
|
|
return idx, query.(*structs.PreparedQuery), nil
|
|
|
|
}
|
|
|
|
return idx, nil, nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PreparedQueryLookup returns the given prepared query by looking up an ID or
|
|
|
|
// Name.
|
|
|
|
func (s *StateStore) PreparedQueryLookup(queryIDOrName string) (uint64, *structs.PreparedQuery, error) {
|
2015-11-07 00:59:32 +00:00
|
|
|
tx := s.db.Txn(false)
|
|
|
|
defer tx.Abort()
|
|
|
|
|
|
|
|
// Get the table index.
|
2015-11-10 04:37:41 +00:00
|
|
|
idx := maxIndexTxn(tx, s.getWatchTables("PreparedQueryLookup")...)
|
2015-11-07 00:59:32 +00:00
|
|
|
|
|
|
|
// Explicitly ban an empty query. This will never match an ID and the
|
|
|
|
// schema is set up so it will never match a query with an empty name,
|
|
|
|
// but we check it here to be explicit about it (we'd never want to
|
|
|
|
// return the results from the first query w/o a name).
|
|
|
|
if queryIDOrName == "" {
|
|
|
|
return idx, nil, ErrMissingQueryID
|
|
|
|
}
|
|
|
|
|
2015-11-10 05:48:35 +00:00
|
|
|
// Try first by ID if it looks like they gave us an ID. We check the
|
|
|
|
// format before trying this because the UUID index will complain if
|
|
|
|
// we look up something that's not formatted like one.
|
|
|
|
if isUUID(queryIDOrName) {
|
|
|
|
query, err := tx.First("prepared-queries", "id", queryIDOrName)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err)
|
|
|
|
}
|
|
|
|
if query != nil {
|
|
|
|
return idx, query.(*structs.PreparedQuery), nil
|
|
|
|
}
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Then try by name.
|
2015-11-10 05:48:35 +00:00
|
|
|
query, err := tx.First("prepared-queries", "name", queryIDOrName)
|
2015-11-07 00:59:32 +00:00
|
|
|
if err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
if query != nil {
|
|
|
|
return idx, query.(*structs.PreparedQuery), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return idx, nil, nil
|
|
|
|
}
|
|
|
|
|
2015-11-10 04:37:41 +00:00
|
|
|
// PreparedQueryList returns all the prepared queries.
|
|
|
|
func (s *StateStore) PreparedQueryList() (uint64, structs.PreparedQueries, error) {
|
2015-11-07 00:59:32 +00:00
|
|
|
tx := s.db.Txn(false)
|
|
|
|
defer tx.Abort()
|
|
|
|
|
|
|
|
// Get the table index.
|
2015-11-10 04:37:41 +00:00
|
|
|
idx := maxIndexTxn(tx, s.getWatchTables("PreparedQueryList")...)
|
2015-11-07 00:59:32 +00:00
|
|
|
|
|
|
|
// Query all of the prepared queries in the state store.
|
2015-11-10 04:37:41 +00:00
|
|
|
queries, err := tx.Get("prepared-queries", "id")
|
2015-11-07 00:59:32 +00:00
|
|
|
if err != nil {
|
2015-11-10 04:37:41 +00:00
|
|
|
return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err)
|
2015-11-07 00:59:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Go over all of the queries and build the response.
|
|
|
|
var result structs.PreparedQueries
|
|
|
|
for query := queries.Next(); query != nil; query = queries.Next() {
|
|
|
|
result = append(result, query.(*structs.PreparedQuery))
|
|
|
|
}
|
|
|
|
return idx, result, nil
|
|
|
|
}
|