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