github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/upgrades.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/juju/status"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/names"
    13  	"gopkg.in/mgo.v2/bson"
    14  	"gopkg.in/mgo.v2/txn"
    15  )
    16  
    17  var upgradesLogger = loggo.GetLogger("juju.state.upgrade")
    18  
    19  func AddPreferredAddressesToMachines(st *State) error {
    20  	machines, err := st.AllMachines()
    21  	if err != nil {
    22  		return errors.Trace(err)
    23  	}
    24  
    25  	for _, machine := range machines {
    26  		if machine.Life() == Dead {
    27  			continue
    28  		}
    29  		// Setting the addresses is enough to trigger setting the preferred
    30  		// addresses.
    31  		err = machine.SetMachineAddresses(machine.MachineAddresses()...)
    32  		if err != nil {
    33  			return errors.Trace(err)
    34  		}
    35  		err := machine.SetProviderAddresses(machine.ProviderAddresses()...)
    36  		if err != nil {
    37  			return errors.Trace(err)
    38  		}
    39  	}
    40  	return nil
    41  }
    42  
    43  // runForAllEnvStates will run runner function for every env passing a state
    44  // for that env.
    45  func runForAllEnvStates(st *State, runner func(st *State) error) error {
    46  	environments, closer := st.getCollection(modelsC)
    47  	defer closer()
    48  
    49  	var envDocs []bson.M
    50  	err := environments.Find(nil).Select(bson.M{"_id": 1}).All(&envDocs)
    51  	if err != nil {
    52  		return errors.Annotate(err, "failed to read models")
    53  	}
    54  
    55  	for _, envDoc := range envDocs {
    56  		modelUUID := envDoc["_id"].(string)
    57  		envSt, err := st.ForModel(names.NewModelTag(modelUUID))
    58  		if err != nil {
    59  			return errors.Annotatef(err, "failed to open model %q", modelUUID)
    60  		}
    61  		defer envSt.Close()
    62  		if err := runner(envSt); err != nil {
    63  			return errors.Annotatef(err, "model UUID %q", modelUUID)
    64  		}
    65  	}
    66  	return nil
    67  }
    68  
    69  // AddFilesystemStatus ensures each filesystem has a status doc.
    70  func AddFilesystemStatus(st *State) error {
    71  	return runForAllEnvStates(st, func(st *State) error {
    72  		filesystems, err := st.AllFilesystems()
    73  		if err != nil {
    74  			return errors.Trace(err)
    75  		}
    76  		var ops []txn.Op
    77  		for _, filesystem := range filesystems {
    78  			_, err := filesystem.Status()
    79  			if err == nil {
    80  				continue
    81  			}
    82  			if !errors.IsNotFound(err) {
    83  				return errors.Annotate(err, "getting status")
    84  			}
    85  			status, err := upgradingFilesystemStatus(st, filesystem)
    86  			if err != nil {
    87  				return errors.Annotate(err, "deciding filesystem status")
    88  			}
    89  			ops = append(ops, createStatusOp(st, filesystem.globalKey(), statusDoc{
    90  				Status:  status,
    91  				Updated: time.Now().UnixNano(),
    92  			}))
    93  		}
    94  		if len(ops) > 0 {
    95  			return errors.Trace(st.runTransaction(ops))
    96  		}
    97  		return nil
    98  	})
    99  }
   100  
   101  // If the filesystem has not been provisioned, then it should be Pending;
   102  // if it has been provisioned, but there is an unprovisioned attachment, then
   103  // it should be Attaching; otherwise it is Attached.
   104  func upgradingFilesystemStatus(st *State, filesystem Filesystem) (status.Status, error) {
   105  	if _, err := filesystem.Info(); errors.IsNotProvisioned(err) {
   106  		return status.StatusPending, nil
   107  	}
   108  	attachments, err := st.FilesystemAttachments(filesystem.FilesystemTag())
   109  	if err != nil {
   110  		return "", errors.Trace(err)
   111  	}
   112  	for _, attachment := range attachments {
   113  		_, err := attachment.Info()
   114  		if errors.IsNotProvisioned(err) {
   115  			return status.StatusAttaching, nil
   116  		}
   117  	}
   118  	return status.StatusAttached, nil
   119  }
   120  
   121  // MigrateSettingsSchema migrates the schema of the settings collection,
   122  // moving non-reserved keys at the top-level into a subdoc, and introducing
   123  // a top-level "version" field with the initial value matching txn-revno.
   124  //
   125  // This migration takes place both before and after model-uuid migration,
   126  // to get the correct txn-revno value.
   127  func MigrateSettingsSchema(st *State) error {
   128  	coll, closer := st.getRawCollection(settingsC)
   129  	defer closer()
   130  
   131  	upgradesLogger.Debugf("migrating schema of the %s collection", settingsC)
   132  	iter := coll.Find(nil).Iter()
   133  	defer iter.Close()
   134  
   135  	var ops []txn.Op
   136  	var doc bson.M
   137  	for iter.Next(&doc) {
   138  		if !settingsDocNeedsMigration(doc) {
   139  			continue
   140  		}
   141  
   142  		id := doc["_id"]
   143  		txnRevno := doc["txn-revno"].(int64)
   144  
   145  		// Remove reserved attributes; we'll move the remaining
   146  		// ones to the "settings" subdoc.
   147  		delete(doc, "model-uuid")
   148  		delete(doc, "_id")
   149  		delete(doc, "txn-revno")
   150  		delete(doc, "txn-queue")
   151  
   152  		// If there exists a setting by the name "settings",
   153  		// we must remove it first, or it will collide with
   154  		// the dotted-notation $sets.
   155  		if _, ok := doc["settings"]; ok {
   156  			ops = append(ops, txn.Op{
   157  				C:      settingsC,
   158  				Id:     id,
   159  				Assert: txn.DocExists,
   160  				Update: bson.D{{"$unset", bson.D{{"settings", 1}}}},
   161  			})
   162  		}
   163  
   164  		var update bson.D
   165  		for key, value := range doc {
   166  			if key != "settings" && key != "version" {
   167  				// Don't try to unset these fields,
   168  				// as we've unset "settings" above
   169  				// already, and we'll overwrite
   170  				// "version" below.
   171  				update = append(update, bson.DocElem{
   172  					"$unset", bson.D{{key, 1}},
   173  				})
   174  			}
   175  			update = append(update, bson.DocElem{
   176  				"$set", bson.D{{"settings." + key, value}},
   177  			})
   178  		}
   179  		if len(update) == 0 {
   180  			// If there are no settings, then we need
   181  			// to add an empty "settings" map so we
   182  			// can tell for next time that migration
   183  			// is complete, and don't move the "version"
   184  			// field we add.
   185  			update = bson.D{{
   186  				"$set", bson.D{{"settings", bson.M{}}},
   187  			}}
   188  		}
   189  		update = append(update, bson.DocElem{
   190  			"$set", bson.D{{"version", txnRevno}},
   191  		})
   192  
   193  		ops = append(ops, txn.Op{
   194  			C:      settingsC,
   195  			Id:     id,
   196  			Assert: txn.DocExists,
   197  			Update: update,
   198  		})
   199  	}
   200  	if err := iter.Err(); err != nil {
   201  		return errors.Trace(err)
   202  	}
   203  	return st.runRawTransaction(ops)
   204  }
   205  
   206  func settingsDocNeedsMigration(doc bson.M) bool {
   207  	// It is not possible for there to exist a settings value
   208  	// with type bson.M, so we know that it is the new settings
   209  	// field and not just a setting with the name "settings".
   210  	if _, ok := doc["settings"].(bson.M); ok {
   211  		return false
   212  	}
   213  	return true
   214  }
   215  
   216  func addDefaultBindingsToServices(st *State) error {
   217  	services, err := st.AllServices()
   218  	if err != nil {
   219  		return errors.Trace(err)
   220  	}
   221  
   222  	upgradesLogger.Debugf("adding default endpoint bindings to services (where missing)")
   223  	ops := make([]txn.Op, 0, len(services))
   224  	for _, service := range services {
   225  		ch, _, err := service.Charm()
   226  		if err != nil {
   227  			return errors.Annotatef(err, "cannot get charm for service %q", service.Name())
   228  		}
   229  		if _, err := service.EndpointBindings(); err == nil {
   230  			upgradesLogger.Debugf("service %q already has bindings (skipping)", service.Name())
   231  			continue
   232  		} else if !errors.IsNotFound(err) {
   233  			return errors.Annotatef(err, "checking service %q for existing bindings", service.Name())
   234  		}
   235  		// Passing nil for the bindings map will use the defaults.
   236  		createOp, err := createEndpointBindingsOp(st, service.globalKey(), nil, ch.Meta())
   237  		if err != nil {
   238  			return errors.Annotatef(err, "setting default endpoint bindings for service %q", service.Name())
   239  		}
   240  		ops = append(ops, createOp)
   241  	}
   242  	return st.runTransaction(ops)
   243  }
   244  
   245  // AddDefaultEndpointBindingsToServices adds default endpoint bindings for each
   246  // service. As long as the service has a charm URL set, each charm endpoint will
   247  // be bound to the default space.
   248  func AddDefaultEndpointBindingsToServices(st *State) error {
   249  	return runForAllEnvStates(st, addDefaultBindingsToServices)
   250  }