github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/service/bundle.go (about)

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