github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/settings.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"labix.org/v2/mgo"
    13  	"labix.org/v2/mgo/bson"
    14  	"labix.org/v2/mgo/txn"
    15  )
    16  
    17  // See: http://docs.mongodb.org/manual/faq/developers/#faq-dollar-sign-escaping
    18  // for why we're using those replacements.
    19  const (
    20  	fullWidthDot    = "\uff0e"
    21  	fullWidthDollar = "\uff04"
    22  )
    23  
    24  var (
    25  	escapeReplacer   = strings.NewReplacer(".", fullWidthDot, "$", fullWidthDollar)
    26  	unescapeReplacer = strings.NewReplacer(fullWidthDot, ".", fullWidthDollar, "$")
    27  )
    28  
    29  const (
    30  	ItemAdded = iota
    31  	ItemModified
    32  	ItemDeleted
    33  )
    34  
    35  // ItemChange represents the change of an item in a settings.
    36  type ItemChange struct {
    37  	Type     int
    38  	Key      string
    39  	OldValue interface{}
    40  	NewValue interface{}
    41  }
    42  
    43  // String returns the item change in a readable format.
    44  func (ic *ItemChange) String() string {
    45  	switch ic.Type {
    46  	case ItemAdded:
    47  		return fmt.Sprintf("setting added: %v = %v", ic.Key, ic.NewValue)
    48  	case ItemModified:
    49  		return fmt.Sprintf("setting modified: %v = %v (was %v)",
    50  			ic.Key, ic.NewValue, ic.OldValue)
    51  	case ItemDeleted:
    52  		return fmt.Sprintf("setting deleted: %v (was %v)", ic.Key, ic.OldValue)
    53  	}
    54  	return fmt.Sprintf("unknown setting change type %d: %v = %v (was %v)",
    55  		ic.Type, ic.Key, ic.NewValue, ic.OldValue)
    56  }
    57  
    58  // itemChangeSlice contains a slice of item changes in a config node.
    59  // It implements the sort interface to sort the items changes by key.
    60  type itemChangeSlice []ItemChange
    61  
    62  func (ics itemChangeSlice) Len() int           { return len(ics) }
    63  func (ics itemChangeSlice) Less(i, j int) bool { return ics[i].Key < ics[j].Key }
    64  func (ics itemChangeSlice) Swap(i, j int)      { ics[i], ics[j] = ics[j], ics[i] }
    65  
    66  // A Settings manages changes to settings as a delta in memory and merges
    67  // them back in the database when explicitly requested.
    68  type Settings struct {
    69  	st  *State
    70  	key string
    71  	// disk holds the values in the config node before
    72  	// any keys have been changed. It is reset on Read and Write
    73  	// operations.
    74  	disk map[string]interface{}
    75  	// cache holds the current values in the config node.
    76  	// The difference between disk and core
    77  	// determines the delta to be applied when Settings.Write
    78  	// is called.
    79  	core     map[string]interface{}
    80  	txnRevno int64
    81  }
    82  
    83  // Keys returns the current keys in alphabetical order.
    84  func (c *Settings) Keys() []string {
    85  	keys := []string{}
    86  	for key := range c.core {
    87  		keys = append(keys, key)
    88  	}
    89  	sort.Strings(keys)
    90  	return keys
    91  }
    92  
    93  // Get returns the value of key and whether it was found.
    94  func (c *Settings) Get(key string) (value interface{}, found bool) {
    95  	value, found = c.core[key]
    96  	return
    97  }
    98  
    99  // Map returns all keys and values of the node.
   100  func (c *Settings) Map() map[string]interface{} {
   101  	return copyMap(c.core, nil)
   102  }
   103  
   104  // Set sets key to value
   105  func (c *Settings) Set(key string, value interface{}) {
   106  	c.core[key] = value
   107  }
   108  
   109  // Update sets multiple key/value pairs.
   110  func (c *Settings) Update(kv map[string]interface{}) {
   111  	for key, value := range kv {
   112  		c.core[key] = value
   113  	}
   114  }
   115  
   116  // Delete removes key.
   117  func (c *Settings) Delete(key string) {
   118  	delete(c.core, key)
   119  }
   120  
   121  // cacheKeys returns the keys of all caches as a key=>true map.
   122  func cacheKeys(caches ...map[string]interface{}) map[string]bool {
   123  	keys := make(map[string]bool)
   124  	for _, cache := range caches {
   125  		for key := range cache {
   126  			keys[key] = true
   127  		}
   128  	}
   129  	return keys
   130  }
   131  
   132  // Write writes changes made to c back onto its node.  Changes are written
   133  // as a delta applied on top of the latest version of the node, to prevent
   134  // overwriting unrelated changes made to the node since it was last read.
   135  func (c *Settings) Write() ([]ItemChange, error) {
   136  	changes := []ItemChange{}
   137  	updates := map[string]interface{}{}
   138  	deletions := map[string]int{}
   139  	for key := range cacheKeys(c.disk, c.core) {
   140  		old, ondisk := c.disk[key]
   141  		new, incore := c.core[key]
   142  		if new == old {
   143  			continue
   144  		}
   145  		var change ItemChange
   146  		escapedKey := escapeReplacer.Replace(key)
   147  		switch {
   148  		case incore && ondisk:
   149  			change = ItemChange{ItemModified, key, old, new}
   150  			updates[escapedKey] = new
   151  		case incore && !ondisk:
   152  			change = ItemChange{ItemAdded, key, nil, new}
   153  			updates[escapedKey] = new
   154  		case ondisk && !incore:
   155  			change = ItemChange{ItemDeleted, key, old, nil}
   156  			deletions[escapedKey] = 1
   157  		default:
   158  			panic("unreachable")
   159  		}
   160  		changes = append(changes, change)
   161  	}
   162  	if len(changes) == 0 {
   163  		return []ItemChange{}, nil
   164  	}
   165  	sort.Sort(itemChangeSlice(changes))
   166  	ops := []txn.Op{{
   167  		C:      c.st.settings.Name,
   168  		Id:     c.key,
   169  		Assert: txn.DocExists,
   170  		Update: bson.D{
   171  			{"$set", updates},
   172  			{"$unset", deletions},
   173  		},
   174  	}}
   175  	err := c.st.runTransaction(ops)
   176  	if err == txn.ErrAborted {
   177  		return nil, errors.NotFoundf("settings")
   178  	}
   179  	if err != nil {
   180  		return nil, fmt.Errorf("cannot write settings: %v", err)
   181  	}
   182  	c.disk = copyMap(c.core, nil)
   183  	return changes, nil
   184  }
   185  
   186  func newSettings(st *State, key string) *Settings {
   187  	return &Settings{
   188  		st:   st,
   189  		key:  key,
   190  		core: make(map[string]interface{}),
   191  	}
   192  }
   193  
   194  // cleanSettingsMap cleans the map of version and _id fields and also unescapes
   195  // keys coming out of MongoDB.
   196  func cleanSettingsMap(in map[string]interface{}) {
   197  	delete(in, "_id")
   198  	delete(in, "txn-revno")
   199  	delete(in, "txn-queue")
   200  	replaceKeys(in, unescapeReplacer.Replace)
   201  }
   202  
   203  // replaceKeys will modify the provided map in place by replacing keys with
   204  // their replacement if they have been modified.
   205  func replaceKeys(m map[string]interface{}, replace func(string) string) {
   206  	for key, value := range m {
   207  		if newKey := replace(key); newKey != key {
   208  			delete(m, key)
   209  			m[newKey] = value
   210  		}
   211  	}
   212  	return
   213  }
   214  
   215  // copyMap copies the keys and values of one map into a new one.  If replace
   216  // is non-nil, for each old key k, the new key will be replace(k).
   217  func copyMap(in map[string]interface{}, replace func(string) string) (out map[string]interface{}) {
   218  	out = make(map[string]interface{})
   219  	for key, value := range in {
   220  		if replace != nil {
   221  			key = replace(key)
   222  		}
   223  		out[key] = value
   224  	}
   225  	return
   226  }
   227  
   228  // Read (re)reads the node data into c.
   229  func (c *Settings) Read() error {
   230  	config, txnRevno, err := readSettingsDoc(c.st, c.key)
   231  	if err == mgo.ErrNotFound {
   232  		c.disk = nil
   233  		c.core = make(map[string]interface{})
   234  		return errors.NotFoundf("settings")
   235  	}
   236  	if err != nil {
   237  		return fmt.Errorf("cannot read settings: %v", err)
   238  	}
   239  	c.txnRevno = txnRevno
   240  	c.disk = config
   241  	c.core = copyMap(config, nil)
   242  	return nil
   243  }
   244  
   245  // readSettingsDoc reads the settings with the given
   246  // key. It returns the settings and the current rxnRevno.
   247  func readSettingsDoc(st *State, key string) (map[string]interface{}, int64, error) {
   248  	config := map[string]interface{}{}
   249  	err := st.settings.FindId(key).One(config)
   250  	if err != nil {
   251  		return nil, 0, err
   252  	}
   253  	txnRevno := config["txn-revno"].(int64)
   254  	cleanSettingsMap(config)
   255  	return config, txnRevno, nil
   256  }
   257  
   258  // readSettings returns the Settings for key.
   259  func readSettings(st *State, key string) (*Settings, error) {
   260  	s := newSettings(st, key)
   261  	if err := s.Read(); err != nil {
   262  		return nil, err
   263  	}
   264  	return s, nil
   265  }
   266  
   267  var errSettingsExist = fmt.Errorf("cannot overwrite existing settings")
   268  
   269  func createSettingsOp(st *State, key string, values map[string]interface{}) txn.Op {
   270  	newValues := copyMap(values, escapeReplacer.Replace)
   271  	return txn.Op{
   272  		C:      st.settings.Name,
   273  		Id:     key,
   274  		Assert: txn.DocMissing,
   275  		Insert: newValues,
   276  	}
   277  }
   278  
   279  // createSettings writes an initial config node.
   280  func createSettings(st *State, key string, values map[string]interface{}) (*Settings, error) {
   281  	s := newSettings(st, key)
   282  	s.core = copyMap(values, nil)
   283  	ops := []txn.Op{createSettingsOp(st, key, values)}
   284  	err := s.st.runTransaction(ops)
   285  	if err == txn.ErrAborted {
   286  		return nil, errSettingsExist
   287  	}
   288  	if err != nil {
   289  		return nil, fmt.Errorf("cannot create settings: %v", err)
   290  	}
   291  	return s, nil
   292  }
   293  
   294  // removeSettings removes the Settings for key.
   295  func removeSettings(st *State, key string) error {
   296  	err := st.settings.RemoveId(key)
   297  	if err == mgo.ErrNotFound {
   298  		return errors.NotFoundf("settings")
   299  	}
   300  	return nil
   301  }
   302  
   303  // replaceSettingsOp returns a txn.Op that deletes the document's contents and
   304  // replaces it with the supplied values, and a function that should be called on
   305  // txn failure to determine whether this operation failed (due to a concurrent
   306  // settings change).
   307  func replaceSettingsOp(st *State, key string, values map[string]interface{}) (txn.Op, func() (bool, error), error) {
   308  	s, err := readSettings(st, key)
   309  	if err != nil {
   310  		return txn.Op{}, nil, err
   311  	}
   312  	deletes := map[string]int{}
   313  	for k := range s.disk {
   314  		if _, found := values[k]; !found {
   315  			deletes[escapeReplacer.Replace(k)] = 1
   316  		}
   317  	}
   318  	newValues := copyMap(values, escapeReplacer.Replace)
   319  	op := s.assertUnchangedOp()
   320  	op.Update = bson.D{
   321  		{"$set", newValues},
   322  		{"$unset", deletes},
   323  	}
   324  	assertFailed := func() (bool, error) {
   325  		latest, err := readSettings(st, key)
   326  		if err != nil {
   327  			return false, err
   328  		}
   329  		return latest.txnRevno != s.txnRevno, nil
   330  	}
   331  	return op, assertFailed, nil
   332  }
   333  
   334  func (s *Settings) assertUnchangedOp() txn.Op {
   335  	return txn.Op{
   336  		C:      s.st.settings.Name,
   337  		Id:     s.key,
   338  		Assert: bson.D{{"txn-revno", s.txnRevno}},
   339  	}
   340  }