github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/application/bundle.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the LGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/bundlechanges"
    15  	"github.com/juju/errors"
    16  	"gopkg.in/juju/charm.v6-unstable"
    17  	"gopkg.in/juju/charmrepo.v2-unstable"
    18  	csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
    19  	"gopkg.in/juju/names.v2"
    20  	"gopkg.in/macaroon.v1"
    21  	"gopkg.in/yaml.v1"
    22  
    23  	"github.com/juju/juju/api"
    24  	"github.com/juju/juju/api/application"
    25  	"github.com/juju/juju/api/charms"
    26  	"github.com/juju/juju/apiserver/params"
    27  	"github.com/juju/juju/charmstore"
    28  	"github.com/juju/juju/constraints"
    29  	"github.com/juju/juju/instance"
    30  	"github.com/juju/juju/resource/resourceadapters"
    31  	"github.com/juju/juju/state/multiwatcher"
    32  	"github.com/juju/juju/state/watcher"
    33  	"github.com/juju/juju/storage"
    34  )
    35  
    36  var watchAll = func(c *api.Client) (allWatcher, error) {
    37  	return c.WatchAll()
    38  }
    39  
    40  type allWatcher interface {
    41  	Next() ([]multiwatcher.Delta, error)
    42  	Stop() error
    43  }
    44  
    45  // deploymentLogger is used to notify clients about the bundle deployment
    46  // progress.
    47  type deploymentLogger interface {
    48  	// Infof formats and logs the given message.
    49  	Infof(string, ...interface{})
    50  }
    51  
    52  // deployBundle deploys the given bundle data using the given API client and
    53  // charm store client. The deployment is not transactional, and its progress is
    54  // notified using the given deployment logger.
    55  func deployBundle(
    56  	bundleFilePath string,
    57  	data *charm.BundleData,
    58  	channel csparams.Channel,
    59  	apiRoot DeployAPI,
    60  	log deploymentLogger,
    61  	bundleStorage map[string]map[string]storage.Constraints,
    62  ) (map[*charm.URL]*macaroon.Macaroon, error) {
    63  	verifyConstraints := func(s string) error {
    64  		_, err := constraints.Parse(s)
    65  		return err
    66  	}
    67  	verifyStorage := func(s string) error {
    68  		_, err := storage.ParseConstraints(s)
    69  		return err
    70  	}
    71  	var verifyError error
    72  	if bundleFilePath == "" {
    73  		verifyError = data.Verify(verifyConstraints, verifyStorage)
    74  	} else {
    75  		verifyError = data.VerifyLocal(bundleFilePath, verifyConstraints, verifyStorage)
    76  	}
    77  	if verifyError != nil {
    78  		if verr, ok := verifyError.(*charm.VerificationError); ok {
    79  			errs := make([]string, len(verr.Errors))
    80  			for i, err := range verr.Errors {
    81  				errs[i] = err.Error()
    82  			}
    83  			return nil, errors.New("the provided bundle has the following errors:\n" + strings.Join(errs, "\n"))
    84  		}
    85  		return nil, errors.Annotate(verifyError, "cannot deploy bundle")
    86  	}
    87  
    88  	// Retrieve bundle changes.
    89  	changes := bundlechanges.FromData(data)
    90  	numChanges := len(changes)
    91  
    92  	// Initialize the unit status.
    93  	status, err := apiRoot.Status(nil)
    94  	if err != nil {
    95  		return nil, errors.Annotate(err, "cannot get model status")
    96  	}
    97  	unitStatus := make(map[string]string, numChanges)
    98  	for _, serviceData := range status.Applications {
    99  		for unit, unitData := range serviceData.Units {
   100  			unitStatus[unit] = unitData.Machine
   101  		}
   102  	}
   103  
   104  	// Instantiate a watcher used to follow the deployment progress.
   105  	watcher, err := apiRoot.WatchAll()
   106  	if err != nil {
   107  		return nil, errors.Annotate(err, "cannot watch model")
   108  	}
   109  	defer watcher.Stop()
   110  
   111  	// Instantiate the bundle handler.
   112  	h := &bundleHandler{
   113  		bundleDir:       bundleFilePath,
   114  		changes:         changes,
   115  		results:         make(map[string]string, numChanges),
   116  		channel:         channel,
   117  		api:             apiRoot,
   118  		bundleStorage:   bundleStorage,
   119  		log:             log,
   120  		data:            data,
   121  		unitStatus:      unitStatus,
   122  		ignoredMachines: make(map[string]bool, len(data.Applications)),
   123  		ignoredUnits:    make(map[string]bool, len(data.Applications)),
   124  		watcher:         watcher,
   125  	}
   126  
   127  	// Deploy the bundle.
   128  	csMacs := make(map[*charm.URL]*macaroon.Macaroon)
   129  	channels := make(map[*charm.URL]csparams.Channel)
   130  	for _, change := range changes {
   131  		switch change := change.(type) {
   132  		case *bundlechanges.AddCharmChange:
   133  			cURL, channel, csMac, err2 := h.addCharm(change.Id(), change.Params)
   134  			if err2 == nil {
   135  				csMacs[cURL] = csMac
   136  				channels[cURL] = channel
   137  			}
   138  			err = err2
   139  		case *bundlechanges.AddMachineChange:
   140  			err = h.addMachine(change.Id(), change.Params)
   141  		case *bundlechanges.AddRelationChange:
   142  			err = h.addRelation(change.Id(), change.Params)
   143  		case *bundlechanges.AddApplicationChange:
   144  			var cURL *charm.URL
   145  			cURL, err = charm.ParseURL(resolve(change.Params.Charm, h.results))
   146  			if err == nil {
   147  				chID := charmstore.CharmID{
   148  					URL:     cURL,
   149  					Channel: channels[cURL],
   150  				}
   151  				csMac := csMacs[cURL]
   152  				err = h.addService(apiRoot, change.Id(), change.Params, chID, csMac)
   153  			}
   154  		case *bundlechanges.AddUnitChange:
   155  			err = h.addUnit(change.Id(), change.Params)
   156  		case *bundlechanges.ExposeChange:
   157  			err = h.exposeService(change.Id(), change.Params)
   158  		case *bundlechanges.SetAnnotationsChange:
   159  			err = h.setAnnotations(change.Id(), change.Params)
   160  		default:
   161  			return nil, errors.Errorf("unknown change type: %T", change)
   162  		}
   163  		if err != nil {
   164  			return nil, errors.Annotate(err, "cannot deploy bundle")
   165  		}
   166  	}
   167  	return csMacs, nil
   168  }
   169  
   170  // bundleHandler provides helpers and the state required to deploy a bundle.
   171  type bundleHandler struct {
   172  	// bundleDir is the path where the bundle file is located for local bundles.
   173  	bundleDir string
   174  	// changes holds the changes to be applied in order to deploy the bundle.
   175  	changes []bundlechanges.Change
   176  
   177  	// results collects data resulting from applying changes. Keys identify
   178  	// changes, values result from interacting with the environment, and are
   179  	// stored so that they can be potentially reused later, for instance for
   180  	// resolving a dynamic placeholder included in a change. Specifically, the
   181  	// following values are stored:
   182  	// - when adding a charm, the fully resolved charm is stored;
   183  	// - when deploying an application, the application name is stored;
   184  	// - when adding a machine, the resulting machine id is stored;
   185  	// - when adding a unit, either the id of the machine holding the unit or
   186  	//   the unit name can be stored. The latter happens when a machine is
   187  	//   implicitly created by adding a unit without a machine spec.
   188  	results map[string]string
   189  
   190  	// channel identifies the default channel to use for the bundle.
   191  	channel csparams.Channel
   192  
   193  	// api is used to interact with the environment.
   194  	api DeployAPI
   195  
   196  	// bundleStorage contains a mapping of application-specific storage
   197  	// constraints. For each application, the storage constraints in the
   198  	// map will replace or augment the storage constraints specified
   199  	// in the bundle itself.
   200  	bundleStorage map[string]map[string]storage.Constraints
   201  
   202  	// log is used to output messages to the user, so that the user can keep
   203  	// track of the bundle deployment progress.
   204  	log deploymentLogger
   205  
   206  	// data is the original bundle data that we want to deploy.
   207  	data *charm.BundleData
   208  
   209  	// unitStatus reflects the environment status and maps unit names to their
   210  	// corresponding machine identifiers. This is kept updated by both change
   211  	// handlers (addCharm, addService etc.) and by updateUnitStatus.
   212  	unitStatus map[string]string
   213  
   214  	// ignoredMachines and ignoredUnits map application names to whether a machine
   215  	// or a unit creation has been skipped during the bundle deployment because
   216  	// the current status of the environment does not require them to be added.
   217  	ignoredMachines map[string]bool
   218  	ignoredUnits    map[string]bool
   219  
   220  	// watcher holds an environment mega-watcher used to keep the environment
   221  	// status up to date.
   222  	watcher allWatcher
   223  
   224  	// warnedLXC indicates whether or not we have warned the user that the
   225  	// bundle they're deploying uses lxc containers, which will be treated as
   226  	// LXD.  This flag keeps us from writing the warning more than once per
   227  	// bundle.
   228  	warnedLXC bool
   229  }
   230  
   231  // addCharm adds a charm to the environment.
   232  func (h *bundleHandler) addCharm(id string, p bundlechanges.AddCharmParams) (*charm.URL, csparams.Channel, *macaroon.Macaroon, error) {
   233  	// First attempt to interpret as a local path.
   234  	if strings.HasPrefix(p.Charm, ".") || filepath.IsAbs(p.Charm) {
   235  		charmPath := p.Charm
   236  		if !filepath.IsAbs(charmPath) {
   237  			charmPath = filepath.Join(h.bundleDir, charmPath)
   238  		}
   239  
   240  		var noChannel csparams.Channel
   241  		series := p.Series
   242  		if series == "" {
   243  			series = h.data.Series
   244  		}
   245  		ch, curl, err := charmrepo.NewCharmAtPath(charmPath, series)
   246  		if err != nil && !os.IsNotExist(err) {
   247  			return nil, noChannel, nil, errors.Annotatef(err, "cannot deploy local charm at %q", charmPath)
   248  		}
   249  		if err == nil {
   250  			if curl, err = h.api.AddLocalCharm(curl, ch); err != nil {
   251  				return nil, noChannel, nil, err
   252  			}
   253  			logger.Debugf("added charm %s", curl)
   254  			h.results[id] = curl.String()
   255  			return curl, noChannel, nil, nil
   256  		}
   257  	}
   258  
   259  	// Not a local charm, so grab from the store.
   260  	ch, err := charm.ParseURL(p.Charm)
   261  	if err != nil {
   262  		return nil, "", nil, errors.Trace(err)
   263  	}
   264  	modelCfg, err := getModelConfig(h.api)
   265  	if err != nil {
   266  		return nil, "", nil, errors.Trace(err)
   267  	}
   268  
   269  	url, channel, _, err := h.api.Resolve(modelCfg, ch)
   270  	if err != nil {
   271  		return nil, channel, nil, errors.Annotatef(err, "cannot resolve URL %q", p.Charm)
   272  	}
   273  	if url.Series == "bundle" {
   274  		return nil, channel, nil, errors.Errorf("expected charm URL, got bundle URL %q", p.Charm)
   275  	}
   276  	var csMac *macaroon.Macaroon
   277  	url, csMac, err = addCharmFromURL(h.api, url, channel)
   278  	if err != nil {
   279  		return nil, channel, nil, errors.Annotatef(err, "cannot add charm %q", p.Charm)
   280  	}
   281  	logger.Debugf("added charm %s", url)
   282  	h.results[id] = url.String()
   283  	return url, channel, csMac, nil
   284  }
   285  
   286  // addService deploys or update an application with no units. Service options are
   287  // also set or updated.
   288  func (h *bundleHandler) addService(
   289  	api DeployAPI,
   290  	id string,
   291  	p bundlechanges.AddApplicationParams,
   292  	chID charmstore.CharmID,
   293  	csMac *macaroon.Macaroon,
   294  ) error {
   295  	h.results[id] = p.Application
   296  	ch := chID.URL.String()
   297  	// Handle application configuration.
   298  	configYAML := ""
   299  	if len(p.Options) > 0 {
   300  		config, err := yaml.Marshal(map[string]map[string]interface{}{p.Application: p.Options})
   301  		if err != nil {
   302  			return errors.Annotatef(err, "cannot marshal options for application %q", p.Application)
   303  		}
   304  		configYAML = string(config)
   305  	}
   306  	// Handle application constraints.
   307  	cons, err := constraints.Parse(p.Constraints)
   308  	if err != nil {
   309  		// This should never happen, as the bundle is already verified.
   310  		return errors.Annotate(err, "invalid constraints for application")
   311  	}
   312  	storageConstraints := h.bundleStorage[p.Application]
   313  	if len(p.Storage) > 0 {
   314  		if storageConstraints == nil {
   315  			storageConstraints = make(map[string]storage.Constraints)
   316  		}
   317  		for k, v := range p.Storage {
   318  			if _, ok := storageConstraints[k]; ok {
   319  				// Storage constraints overridden
   320  				// on the command line.
   321  				continue
   322  			}
   323  			cons, err := storage.ParseConstraints(v)
   324  			if err != nil {
   325  				return errors.Annotate(err, "invalid storage constraints")
   326  			}
   327  			storageConstraints[k] = cons
   328  		}
   329  	}
   330  	resources := make(map[string]string)
   331  	for resName, revision := range p.Resources {
   332  		resources[resName] = fmt.Sprint(revision)
   333  	}
   334  	charmInfo, err := h.api.CharmInfo(ch)
   335  	if err != nil {
   336  		return err
   337  	}
   338  	resNames2IDs, err := resourceadapters.DeployResources(
   339  		p.Application,
   340  		chID,
   341  		csMac,
   342  		resources,
   343  		charmInfo.Meta.Resources,
   344  		api,
   345  	)
   346  	if err != nil {
   347  		return errors.Trace(err)
   348  	}
   349  
   350  	// Figure out what series we need to deploy with.
   351  	conf, err := getModelConfig(h.api)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	supportedSeries := charmInfo.Meta.Series
   356  	if len(supportedSeries) == 0 && chID.URL.Series != "" {
   357  		supportedSeries = []string{chID.URL.Series}
   358  	}
   359  	selector := seriesSelector{
   360  		seriesFlag:      p.Series,
   361  		charmURLSeries:  chID.URL.Series,
   362  		supportedSeries: supportedSeries,
   363  		conf:            conf,
   364  		fromBundle:      true,
   365  	}
   366  	series, err := selector.charmSeries()
   367  	if err != nil {
   368  		return errors.Trace(err)
   369  	}
   370  
   371  	// Deploy the application.
   372  	logger.Debugf("application %s is deploying (charm %s)", p.Application, ch)
   373  	h.log.Infof("Deploying charm %q", ch)
   374  	if err := api.Deploy(application.DeployArgs{
   375  		CharmID:          chID,
   376  		Cons:             cons,
   377  		ApplicationName:  p.Application,
   378  		Series:           series,
   379  		ConfigYAML:       configYAML,
   380  		Storage:          storageConstraints,
   381  		Resources:        resNames2IDs,
   382  		EndpointBindings: p.EndpointBindings,
   383  	}); err == nil {
   384  		for resName := range resNames2IDs {
   385  			h.log.Infof("added resource %s", resName)
   386  		}
   387  		return nil
   388  	} else if !isErrServiceExists(err) {
   389  		return errors.Annotatef(err, "cannot deploy application %q", p.Application)
   390  	}
   391  	// The application is already deployed in the environment: check that its
   392  	// charm is compatible with the one declared in the bundle. If it is,
   393  	// reuse the existing application or upgrade to a specified revision.
   394  	// Exit with an error otherwise.
   395  	if err := h.upgradeCharm(api, p.Application, chID, csMac, resources); err != nil {
   396  		return errors.Annotatef(err, "cannot upgrade application %q", p.Application)
   397  	}
   398  	// Update application configuration.
   399  	if configYAML != "" {
   400  		if err := h.api.Update(params.ApplicationUpdate{
   401  			ApplicationName: p.Application,
   402  			SettingsYAML:    configYAML,
   403  		}); err != nil {
   404  			// This should never happen as possible errors are already returned
   405  			// by the application Deploy call above.
   406  			return errors.Annotatef(err, "cannot update options for application %q", p.Application)
   407  		}
   408  		h.log.Infof("configuration updated for application %s", p.Application)
   409  	}
   410  	// Update application constraints.
   411  	if p.Constraints != "" {
   412  		if err := h.api.SetConstraints(p.Application, cons); err != nil {
   413  			// This should never happen, as the bundle is already verified.
   414  			return errors.Annotatef(err, "cannot update constraints for application %q", p.Application)
   415  		}
   416  		h.log.Infof("constraints applied for application %s", p.Application)
   417  	}
   418  	return nil
   419  }
   420  
   421  // addMachine creates a new top-level machine or container in the environment.
   422  func (h *bundleHandler) addMachine(id string, p bundlechanges.AddMachineParams) error {
   423  	services := h.servicesForMachineChange(id)
   424  	// Note that we always have at least one application that justifies the
   425  	// creation of this machine.
   426  	msg := services[0] + " unit"
   427  	svcLen := len(services)
   428  	if svcLen != 1 {
   429  		msg = strings.Join(services[:svcLen-1], ", ") + " and " + services[svcLen-1] + " units"
   430  	}
   431  	// Check whether the desired number of units already exist in the
   432  	// environment, in which case avoid adding other machines to host those
   433  	// application units.
   434  	machine := h.chooseMachine(services...)
   435  	if machine != "" {
   436  		h.results[id] = machine
   437  		notify := make([]string, 0, svcLen)
   438  		for _, application := range services {
   439  			if !h.ignoredMachines[application] {
   440  				h.ignoredMachines[application] = true
   441  				notify = append(notify, application)
   442  			}
   443  		}
   444  		svcLen = len(notify)
   445  		switch svcLen {
   446  		case 0:
   447  			return nil
   448  		case 1:
   449  			msg = notify[0]
   450  		default:
   451  			msg = strings.Join(notify[:svcLen-1], ", ") + " and " + notify[svcLen-1]
   452  		}
   453  		h.log.Infof("avoid creating other machines to host %s units", msg)
   454  		return nil
   455  	}
   456  	cons, err := constraints.Parse(p.Constraints)
   457  	if err != nil {
   458  		// This should never happen, as the bundle is already verified.
   459  		return errors.Annotate(err, "invalid constraints for machine")
   460  	}
   461  	machineParams := params.AddMachineParams{
   462  		Constraints: cons,
   463  		Series:      p.Series,
   464  		Jobs:        []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   465  	}
   466  	if ct := p.ContainerType; ct != "" {
   467  		// for backwards compatibility with 1.x bundles, we treat lxc
   468  		// placement directives as lxd.
   469  		if ct == "lxc" {
   470  			if !h.warnedLXC {
   471  				h.log.Infof("Bundle has one or more containers specified as lxc. lxc containers are deprecated in Juju 2.0. lxd containers will be deployed instead.")
   472  				h.warnedLXC = true
   473  			}
   474  			ct = string(instance.LXD)
   475  		}
   476  		containerType, err := instance.ParseContainerType(ct)
   477  		if err != nil {
   478  			return errors.Annotatef(err, "cannot create machine for holding %s", msg)
   479  		}
   480  		machineParams.ContainerType = containerType
   481  		if p.ParentId != "" {
   482  			machineParams.ParentId, err = h.resolveMachine(p.ParentId)
   483  			if err != nil {
   484  				return errors.Annotatef(err, "cannot retrieve parent placement for %s", msg)
   485  			}
   486  		}
   487  	}
   488  	r, err := h.api.AddMachines([]params.AddMachineParams{machineParams})
   489  	if err != nil {
   490  		return errors.Annotatef(err, "cannot create machine for holding %s", msg)
   491  	}
   492  	if r[0].Error != nil {
   493  		return errors.Annotatef(r[0].Error, "cannot create machine for holding %s", msg)
   494  	}
   495  	machine = r[0].Machine
   496  	if p.ContainerType == "" {
   497  		logger.Debugf("created new machine %s for holding %s", machine, msg)
   498  	} else if p.ParentId == "" {
   499  		logger.Debugf("created %s container in new machine for holding %s", machine, msg)
   500  	} else {
   501  		logger.Debugf("created %s container in machine %s for holding %s", machine, machineParams.ParentId, msg)
   502  	}
   503  	h.results[id] = machine
   504  	return nil
   505  }
   506  
   507  // addRelation creates a relationship between two services.
   508  func (h *bundleHandler) addRelation(id string, p bundlechanges.AddRelationParams) error {
   509  	ep1 := resolveRelation(p.Endpoint1, h.results)
   510  	ep2 := resolveRelation(p.Endpoint2, h.results)
   511  	_, err := h.api.AddRelation(ep1, ep2)
   512  	if err == nil {
   513  		// A new relation has been established.
   514  		h.log.Infof("Related %q and %q", ep1, ep2)
   515  		return nil
   516  	}
   517  	if isErrRelationExists(err) {
   518  		// The relation is already present in the environment.
   519  		logger.Debugf("%s and %s are already related", ep1, ep2)
   520  		return nil
   521  	}
   522  	return errors.Annotatef(err, "cannot add relation between %q and %q", ep1, ep2)
   523  }
   524  
   525  // addUnit adds a single unit to an application already present in the environment.
   526  func (h *bundleHandler) addUnit(id string, p bundlechanges.AddUnitParams) error {
   527  	application := resolve(p.Application, h.results)
   528  	// Check whether the desired number of units already exist in the
   529  	// environment, in which case avoid adding other units.
   530  	machine := h.chooseMachine(application)
   531  	if machine != "" {
   532  		h.results[id] = machine
   533  		if !h.ignoredUnits[application] {
   534  			h.ignoredUnits[application] = true
   535  			num := h.numUnitsForService(application)
   536  			var msg string
   537  			if num == 1 {
   538  				msg = "1 unit already present"
   539  			} else {
   540  				msg = fmt.Sprintf("%d units already present", num)
   541  			}
   542  			h.log.Infof("avoid adding new units to application %s: %s", application, msg)
   543  		}
   544  		return nil
   545  	}
   546  	var machineSpec string
   547  	var placementArg []*instance.Placement
   548  	if p.To != "" {
   549  		var err error
   550  		if machineSpec, err = h.resolveMachine(p.To); err != nil {
   551  			// Should never happen.
   552  			return errors.Annotatef(err, "cannot retrieve placement for %q unit", application)
   553  		}
   554  		placement, err := parsePlacement(machineSpec)
   555  		if err != nil {
   556  			return errors.Errorf("invalid --to parameter %q", machineSpec)
   557  		}
   558  		placementArg = append(placementArg, placement)
   559  	}
   560  	r, err := h.api.AddUnits(application, 1, placementArg)
   561  	if err != nil {
   562  		return errors.Annotatef(err, "cannot add unit for application %q", application)
   563  	}
   564  	unit := r[0]
   565  	if machineSpec == "" {
   566  		logger.Debugf("added %s unit to new machine", unit)
   567  		// In this case, the unit name is stored in results instead of the
   568  		// machine id, which is lazily evaluated later only if required.
   569  		// This way we avoid waiting for watcher updates.
   570  		h.results[id] = unit
   571  	} else {
   572  		logger.Debugf("added %s unit to new machine", unit)
   573  		h.results[id] = machineSpec
   574  	}
   575  	// Note that the machineSpec can be empty for now, resulting in a partially
   576  	// incomplete unit status. That's ok as the missing info is provided later
   577  	// when it is required.
   578  	h.unitStatus[unit] = machineSpec
   579  	return nil
   580  }
   581  
   582  // exposeService exposes an application.
   583  func (h *bundleHandler) exposeService(id string, p bundlechanges.ExposeParams) error {
   584  	application := resolve(p.Application, h.results)
   585  	if err := h.api.Expose(application); err != nil {
   586  		return errors.Annotatef(err, "cannot expose application %s", application)
   587  	}
   588  	h.log.Infof("application %s exposed", application)
   589  	return nil
   590  }
   591  
   592  // setAnnotations sets annotations for an application or a machine.
   593  func (h *bundleHandler) setAnnotations(id string, p bundlechanges.SetAnnotationsParams) error {
   594  	eid := resolve(p.Id, h.results)
   595  	var tag string
   596  	switch p.EntityType {
   597  	case bundlechanges.MachineType:
   598  		tag = names.NewMachineTag(eid).String()
   599  	case bundlechanges.ApplicationType:
   600  		tag = names.NewApplicationTag(eid).String()
   601  	default:
   602  		return errors.Errorf("unexpected annotation entity type %q", p.EntityType)
   603  	}
   604  	result, err := h.api.SetAnnotation(map[string]map[string]string{tag: p.Annotations})
   605  	if err == nil && len(result) > 0 {
   606  		err = result[0].Error
   607  	}
   608  	if err != nil {
   609  		return errors.Annotatef(err, "cannot set annotations for %s %q", p.EntityType, eid)
   610  	}
   611  	logger.Debugf("annotations set for %s %s", p.EntityType, eid)
   612  	return nil
   613  }
   614  
   615  // servicesForMachineChange returns the names of the services for which an
   616  // "addMachine" change is required, as adding machines is required to place
   617  // units, and units belong to services.
   618  // Receive the id of the "addMachine" change.
   619  func (h *bundleHandler) servicesForMachineChange(changeId string) []string {
   620  	services := make(map[string]bool, len(h.data.Applications))
   621  mainloop:
   622  	for _, change := range h.changes {
   623  		for _, required := range change.Requires() {
   624  			if required != changeId {
   625  				continue
   626  			}
   627  			switch change := change.(type) {
   628  			case *bundlechanges.AddMachineChange:
   629  				// The original machine is a container, and its parent is
   630  				// another "addMachines" change. Search again using the
   631  				// parent id.
   632  				for _, application := range h.servicesForMachineChange(change.Id()) {
   633  					services[application] = true
   634  				}
   635  				continue mainloop
   636  			case *bundlechanges.AddUnitChange:
   637  				// We have found the "addUnit" change, which refers to a
   638  				// application: now resolve the application holding the unit.
   639  				application := resolve(change.Params.Application, h.results)
   640  				services[application] = true
   641  				continue mainloop
   642  			case *bundlechanges.SetAnnotationsChange:
   643  				// A machine change is always required to set machine
   644  				// annotations, but this isn't the interesting change here.
   645  				continue mainloop
   646  			default:
   647  				// Should never happen.
   648  				panic(fmt.Sprintf("unexpected change %T", change))
   649  			}
   650  		}
   651  	}
   652  	results := make([]string, 0, len(services))
   653  	for application := range services {
   654  		results = append(results, application)
   655  	}
   656  	sort.Strings(results)
   657  	return results
   658  }
   659  
   660  // chooseMachine returns the id of a machine that will be used to host a unit
   661  // of all the given services. If one of the services still requires units to be
   662  // added, an empty string is returned, meaning that a new machine must be
   663  // created for holding the unit. If instead all units are already placed,
   664  // return the id of the machine which already holds units of the given services
   665  // and which hosts the least number of units.
   666  func (h *bundleHandler) chooseMachine(services ...string) string {
   667  	candidateMachines := make(map[string]bool, len(h.unitStatus))
   668  	numUnitsPerMachine := make(map[string]int, len(h.unitStatus))
   669  	numUnitsPerService := make(map[string]int, len(h.data.Applications))
   670  	// Collect the number of units and the corresponding machines for all
   671  	// involved services.
   672  	for unit, machine := range h.unitStatus {
   673  		// Retrieve the top level machine.
   674  		machine = strings.Split(machine, "/")[0]
   675  		numUnitsPerMachine[machine]++
   676  		svc, err := names.UnitApplication(unit)
   677  		if err != nil {
   678  			// Should never happen because the bundle logic has already checked
   679  			// that unit names are well formed.
   680  			panic(err)
   681  		}
   682  		for _, application := range services {
   683  			if application != svc {
   684  				continue
   685  			}
   686  			numUnitsPerService[application]++
   687  			candidateMachines[machine] = true
   688  		}
   689  	}
   690  	// If at least one application still requires units to be added, return an
   691  	// empty machine in order to force new machine creation.
   692  	for _, application := range services {
   693  		if numUnitsPerService[application] < h.data.Applications[application].NumUnits {
   694  			return ""
   695  		}
   696  	}
   697  	// Return the least used machine.
   698  	var result string
   699  	var min int
   700  	for machine, num := range numUnitsPerMachine {
   701  		if candidateMachines[machine] && (result == "" || num < min) {
   702  			result, min = machine, num
   703  		}
   704  	}
   705  	return result
   706  }
   707  
   708  // updateUnitStatusPeriod is the time duration used to wait for a mega-watcher
   709  // change to be available.
   710  var updateUnitStatusPeriod = watcher.Period + 5*time.Second
   711  
   712  // updateUnitStatus uses the mega-watcher to update units and machines info
   713  // (h.unitStatus) so that it reflects the current environment status.
   714  // This function must be called assuming new delta changes are available or
   715  // will be available within the watcher time period. Otherwise, the function
   716  // unblocks and an error is returned.
   717  func (h *bundleHandler) updateUnitStatus() error {
   718  	var delta []multiwatcher.Delta
   719  	var err error
   720  	ch := make(chan struct{})
   721  	go func() {
   722  		delta, err = h.watcher.Next()
   723  		close(ch)
   724  	}()
   725  	select {
   726  	case <-ch:
   727  		if err != nil {
   728  			return errors.Annotate(err, "cannot update model status")
   729  		}
   730  		for _, d := range delta {
   731  			switch entityInfo := d.Entity.(type) {
   732  			case *multiwatcher.UnitInfo:
   733  				h.unitStatus[entityInfo.Name] = entityInfo.MachineId
   734  			}
   735  		}
   736  	case <-time.After(updateUnitStatusPeriod):
   737  		// TODO(fwereade): 2016-03-17 lp:1558657
   738  		return errors.New("timeout while trying to get new changes from the watcher")
   739  	}
   740  	return nil
   741  }
   742  
   743  // numUnitsForService return the number of units belonging to the given application
   744  // currently in the environment.
   745  func (h *bundleHandler) numUnitsForService(application string) (num int) {
   746  	for unit := range h.unitStatus {
   747  		svc, err := names.UnitApplication(unit)
   748  		if err != nil {
   749  			// Should never happen.
   750  			panic(err)
   751  		}
   752  		if svc == application {
   753  			num++
   754  		}
   755  	}
   756  	return num
   757  }
   758  
   759  // resolveMachine returns the machine id resolving the given unit or machine
   760  // placeholder.
   761  func (h *bundleHandler) resolveMachine(placeholder string) (string, error) {
   762  	machineOrUnit := resolve(placeholder, h.results)
   763  	if !names.IsValidUnit(machineOrUnit) {
   764  		return machineOrUnit, nil
   765  	}
   766  	for h.unitStatus[machineOrUnit] == "" {
   767  		if err := h.updateUnitStatus(); err != nil {
   768  			return "", errors.Annotate(err, "cannot resolve machine")
   769  		}
   770  	}
   771  	return h.unitStatus[machineOrUnit], nil
   772  }
   773  
   774  // resolveRelation returns the relation name resolving the included application
   775  // placeholder.
   776  func resolveRelation(e string, results map[string]string) string {
   777  	parts := strings.SplitN(e, ":", 2)
   778  	application := resolve(parts[0], results)
   779  	if len(parts) == 1 {
   780  		return application
   781  	}
   782  	return fmt.Sprintf("%s:%s", application, parts[1])
   783  }
   784  
   785  // resolve returns the real entity name for the bundle entity (for instance a
   786  // application or a machine) with the given placeholder id.
   787  // A placeholder id is a string like "$deploy-42" or "$addCharm-2", indicating
   788  // the results of a previously applied change. It always starts with a dollar
   789  // sign, followed by the identifier of the referred change. A change id is a
   790  // string indicating the action type ("deploy", "addRelation" etc.), followed
   791  // by a unique incremental number.
   792  func resolve(placeholder string, results map[string]string) string {
   793  	if !strings.HasPrefix(placeholder, "$") {
   794  		panic(`placeholder does not start with "$"`)
   795  	}
   796  	id := placeholder[1:]
   797  	return results[id]
   798  }
   799  
   800  // upgradeCharm upgrades the charm for the given application to the given charm id.
   801  // If the application is already deployed using the given charm id, do nothing.
   802  // This function returns an error if the existing charm and the target one are
   803  // incompatible, meaning an upgrade from one to the other is not allowed.
   804  func (h *bundleHandler) upgradeCharm(
   805  	api DeployAPI,
   806  	applicationName string,
   807  	chID charmstore.CharmID,
   808  	csMac *macaroon.Macaroon,
   809  	resources map[string]string,
   810  ) error {
   811  	id := chID.URL.String()
   812  	existing, err := h.api.GetCharmURL(applicationName)
   813  	if err != nil {
   814  		return errors.Annotatef(err, "cannot retrieve info for application %q", applicationName)
   815  	}
   816  	if existing.String() == id {
   817  		h.log.Infof("reusing application %s (charm: %s)", applicationName, id)
   818  		return nil
   819  	}
   820  	url, err := charm.ParseURL(id)
   821  	if err != nil {
   822  		return errors.Annotatef(err, "cannot parse charm URL %q", id)
   823  	}
   824  	chID.URL = url
   825  	if url.WithRevision(-1).Path() != existing.WithRevision(-1).Path() {
   826  		return errors.Errorf("bundle charm %q is incompatible with existing charm %q", id, existing)
   827  	}
   828  	charmsClient := charms.NewClient(api)
   829  	resourceLister, err := resourceadapters.NewAPIClient(api)
   830  	if err != nil {
   831  		return errors.Trace(err)
   832  	}
   833  	filtered, err := getUpgradeResources(charmsClient, resourceLister, applicationName, url, resources)
   834  	if err != nil {
   835  		return errors.Trace(err)
   836  	}
   837  	var resNames2IDs map[string]string
   838  	if len(filtered) != 0 {
   839  		resNames2IDs, err = resourceadapters.DeployResources(
   840  			applicationName,
   841  			chID,
   842  			csMac,
   843  			resources,
   844  			filtered,
   845  			api,
   846  		)
   847  		if err != nil {
   848  			return errors.Trace(err)
   849  		}
   850  	}
   851  	cfg := application.SetCharmConfig{
   852  		ApplicationName: applicationName,
   853  		CharmID:         chID,
   854  		ResourceIDs:     resNames2IDs,
   855  	}
   856  	if err := h.api.SetCharm(cfg); err != nil {
   857  		return errors.Annotatef(err, "cannot upgrade charm to %q", id)
   858  	}
   859  	h.log.Infof("upgraded charm for existing application %s (from %s to %s)", applicationName, existing, id)
   860  	for resName := range resNames2IDs {
   861  		h.log.Infof("added resource %s", resName)
   862  	}
   863  	return nil
   864  }
   865  
   866  // isErrServiceExists reports whether the given error has been generated
   867  // from trying to deploy an application that already exists.
   868  func isErrServiceExists(err error) bool {
   869  	// TODO frankban (bug 1495952): do this check using the cause rather than
   870  	// the string when a specific cause is available.
   871  	return strings.HasSuffix(err.Error(), "application already exists")
   872  }
   873  
   874  // isErrRelationExists reports whether the given error has been generated
   875  // from trying to create an already established relation.
   876  func isErrRelationExists(err error) bool {
   877  	// TODO frankban (bug 1495952): do this check using the cause rather than
   878  	// the string when a specific cause is available.
   879  	return strings.HasSuffix(err.Error(), "relation already exists")
   880  }