func (s *ConstraintsSuite) TestParseConstraints(c *gc.C) { for i, t := range parseConstraintsTests { c.Logf("test %d: %s", i, t.summary) cons0, err := constraints.Parse(t.args...) if t.err == "" { c.Assert(err, jc.ErrorIsNil) } else { c.Assert(err, gc.ErrorMatches, t.err) continue } cons1, err := constraints.Parse(cons0.String()) c.Check(err, jc.ErrorIsNil) c.Check(cons1, gc.DeepEquals, cons0) } }
// GetBundleChanges returns the list of changes required to deploy the given // bundle data. The changes are sorted by requirements, so that they can be // applied in order. func (c *Client) GetBundleChanges(args params.GetBundleChangesParams) (params.GetBundleChangesResults, error) { var results params.GetBundleChangesResults data, err := charm.ReadBundleData(strings.NewReader(args.BundleDataYAML)) if err != nil { return results, errors.Annotate(err, "cannot read bundle YAML") } if err := data.Verify(func(s string) error { _, err := constraints.Parse(s) return err }); err != nil { if err, ok := err.(*charm.VerificationError); ok { results.Errors = make([]string, len(err.Errors)) for i, e := range err.Errors { results.Errors[i] = e.Error() } return results, nil } // This should never happen as Verify only returns verification errors. return results, errors.Annotate(err, "cannot verify bundle") } changes := bundlechanges.FromData(data) results.Changes = make([]*params.BundleChangesChange, len(changes)) for i, c := range changes { results.Changes[i] = ¶ms.BundleChangesChange{ Id: c.Id(), Method: c.Method(), Args: c.GUIArgs(), Requires: c.Requires(), } } return results, nil }
func (c *SetConstraintsCommand) Init(args []string) (err error) { if c.ServiceName != "" && !names.IsValidService(c.ServiceName) { return fmt.Errorf("invalid service name %q", c.ServiceName) } c.Constraints, err = constraints.Parse(args...) return err }
func (s *clientSuite) assertSetModelConstraintsBlocked(c *gc.C, msg string) { // Set constraints for the model. cons, err := constraints.Parse("mem=4096", "cores=2") c.Assert(err, jc.ErrorIsNil) err = s.APIState.Client().SetModelConstraints(cons) s.AssertBlocked(c, err, msg) }
func (s *ConstraintsSuite) TestRoundtripString(c *gc.C) { for _, t := range constraintsRoundtripTests { c.Logf("test %s", t.Name) cons, err := constraints.Parse(t.Value.String()) c.Check(err, jc.ErrorIsNil) c.Check(cons, jc.DeepEquals, t.Value) } }
func (s *ConstraintsSuite) TestParseConstraints(c *gc.C) { // TODO(dimitern): This test is inadequate and needs to check for // more than just the reparsed output of String() matches the // expected. for i, t := range parseConstraintsTests { c.Logf("test %d: %s", i, t.summary) cons0, err := constraints.Parse(t.args...) if t.err == "" { c.Assert(err, jc.ErrorIsNil) } else { c.Assert(err, gc.ErrorMatches, t.err) continue } cons1, err := constraints.Parse(cons0.String()) c.Check(err, jc.ErrorIsNil) c.Check(cons1, gc.DeepEquals, cons0) } }
func (s *clientSuite) assertSetModelConstraints(c *gc.C) { // Set constraints for the model. cons, err := constraints.Parse("mem=4096", "cores=2") c.Assert(err, jc.ErrorIsNil) err = s.APIState.Client().SetModelConstraints(cons) c.Assert(err, jc.ErrorIsNil) // Ensure the constraints have been correctly updated. obtained, err := s.State.ModelConstraints() c.Assert(err, jc.ErrorIsNil) c.Assert(obtained, gc.DeepEquals, cons) }
func (s *clientSuite) TestClientGetModelConstraints(c *gc.C) { // Set constraints for the model. cons, err := constraints.Parse("mem=4096", "cores=2") c.Assert(err, jc.ErrorIsNil) err = s.State.SetModelConstraints(cons) c.Assert(err, jc.ErrorIsNil) // Check we can get the constraints. obtained, err := s.APIState.Client().GetModelConstraints() c.Assert(err, jc.ErrorIsNil) c.Assert(obtained, gc.DeepEquals, cons) }
// addService deploys or update a service with no units. Service options are // also set or updated. func (h *bundleHandler) addService(id string, p bundlechanges.AddServiceParams) error { h.results[id] = p.Service ch := resolve(p.Charm, h.results) // Handle service configuration. configYAML := "" if len(p.Options) > 0 { config, err := yaml.Marshal(map[string]map[string]interface{}{p.Service: p.Options}) if err != nil { return errors.Annotatef(err, "cannot marshal options for service %q", p.Service) } configYAML = string(config) } // Handle service constraints. cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for service") } // Deploy the service. numUnits, toMachineSpec := 0, "" if err := h.client.ServiceDeploy(ch, p.Service, numUnits, configYAML, cons, toMachineSpec); err == nil { h.log.Infof("service %s deployed (charm: %s)", p.Service, ch) return nil } else if !isErrServiceExists(err) { return errors.Annotatef(err, "cannot deploy service %q", p.Service) } // The service is already deployed in the environment: check that its // charm is compatible with the one declared in the bundle. If it is, // reuse the existing service or upgrade to a specified revision. // Exit with an error otherwise. if err := upgradeCharm(h.client, h.log, p.Service, ch); err != nil { return errors.Annotatef(err, "cannot upgrade service %q", p.Service) } // Update service configuration. if configYAML != "" { if err := h.client.ServiceSetYAML(p.Service, configYAML); err != nil { // This should never happen as possible errors are already returned // by the ServiceDeploy call above. return errors.Annotatef(err, "cannot update options for service %q", p.Service) } h.log.Infof("configuration updated for service %s", p.Service) } // Update service constraints. if p.Constraints != "" { if err := h.client.SetServiceConstraints(p.Service, cons); err != nil { // This should never happen, as the bundle is already verified. return errors.Annotatef(err, "cannot update constraints for service %q", p.Service) } h.log.Infof("constraints applied for service %s", p.Service) } return nil }
func (c *serviceSetConstraintsCommand) Init(args []string) (err error) { if len(args) == 0 { return errors.Errorf("no application name specified") } if !names.IsValidApplication(args[0]) { return errors.Errorf("invalid application name %q", args[0]) } c.ApplicationName, args = args[0], args[1:] c.Constraints, err = constraints.Parse(args...) return err }
func (c *serviceSetConstraintsCommand) Init(args []string) (err error) { if len(args) == 0 { return fmt.Errorf("no service name specified") } if !names.IsValidService(args[0]) { return fmt.Errorf("invalid service name %q", args[0]) } c.ServiceName, args = args[0], args[1:] c.Constraints, err = constraints.Parse(args...) return err }
func (s *ConstraintsSuite) TestInvalidSpaces(c *gc.C) { invalidNames := []string{ "%$pace", "^foo#2", "+", "tcp:ip", "^^myspace", "^^^^^^^^", "space^x", "&-foo", "space/3", "^bar=4", "&#!", } for _, name := range invalidNames { con, err := constraints.Parse("spaces=" + name) expectName := strings.TrimPrefix(name, "^") expectErr := fmt.Sprintf(`bad "spaces" constraint: %q is not a valid space name`, expectName) c.Check(err, gc.NotNil) c.Check(err.Error(), gc.Equals, expectErr) c.Check(con, jc.DeepEquals, constraints.Value{}) } }
func (s *ConstraintsSuite) TestInvalidNetworks(c *gc.C) { invalidNames := []string{ "%ne$t", "^net#2", "+", "tcp:ip", "^^mynet", "^^^^^^^^", "net^x", "&-foo", "net/3", "^net=4", "&#!", } for _, name := range invalidNames { con, err := constraints.Parse("networks=" + name) expectName := strings.TrimPrefix(name, "^") expectErr := fmt.Sprintf(`bad "networks" constraint: %q is not a valid network name`, expectName) c.Check(err, gc.NotNil) c.Check(err.Error(), gc.Equals, expectErr) c.Check(con, jc.DeepEquals, constraints.Value{}) } }
func (s *deployRepoCharmStoreSuite) TestDeployBundleMachineAttributes(c *gc.C) { testcharms.UploadCharm(c, s.client, "trusty/django-42", "dummy") output, err := s.deployBundleYAML(c, ` services: django: charm: cs:trusty/django-42 num_units: 2 to: - 1 - new machines: 1: series: trusty constraints: "cpu-cores=4 mem=4G" annotations: foo: bar `) c.Assert(err, jc.ErrorIsNil) expectedOutput := ` added charm cs:trusty/django-42 service django deployed (charm: cs:trusty/django-42) created new machine 0 for holding django unit annotations set for machine 0 added django/0 unit to machine 0 created new machine 1 for holding django unit added django/1 unit to machine 1 deployment of bundle "local:bundle/example-0" completed` c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput)) s.assertServicesDeployed(c, map[string]serviceInfo{ "django": {charm: "cs:trusty/django-42"}, }) s.assertRelationsEstablished(c) s.assertUnitsCreated(c, map[string]string{ "django/0": "0", "django/1": "1", }) m, err := s.State.Machine("0") c.Assert(err, jc.ErrorIsNil) c.Assert(m.Series(), gc.Equals, "trusty") cons, err := m.Constraints() c.Assert(err, jc.ErrorIsNil) expectedCons, err := constraints.Parse("cpu-cores=4 mem=4G") c.Assert(err, jc.ErrorIsNil) c.Assert(cons, jc.DeepEquals, expectedCons) ann, err := s.State.Annotations(m) c.Assert(err, jc.ErrorIsNil) c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"}) }
func (s *serviceSuite) TestClientServiceUpdateAllParams(c *gc.C) { s.deployServiceForUpdateTests(c) curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{URL: curl.String()}) c.Assert(err, jc.ErrorIsNil) // Update all the service attributes. minUnits := 3 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") c.Assert(err, jc.ErrorIsNil) args := params.ServiceUpdate{ ServiceName: "service", CharmUrl: curl.String(), ForceCharmUrl: true, MinUnits: &minUnits, SettingsStrings: map[string]string{"blog-title": "string-title"}, SettingsYAML: "service:\n blog-title: yaml-title\n", Constraints: &cons, } err = s.serviceApi.ServiceUpdate(args) c.Assert(err, jc.ErrorIsNil) // Ensure the service has been correctly updated. service, err := s.State.Service("service") c.Assert(err, jc.ErrorIsNil) // Check the charm. ch, force, err := service.Charm() c.Assert(err, jc.ErrorIsNil) c.Assert(ch.URL().String(), gc.Equals, curl.String()) c.Assert(force, jc.IsTrue) // Check the minimum number of units. c.Assert(service.MinUnits(), gc.Equals, minUnits) // Check the settings: also ensure the YAML settings take precedence // over strings ones. expectedSettings := charm.Settings{"blog-title": "yaml-title"} obtainedSettings, err := service.ConfigSettings() c.Assert(err, jc.ErrorIsNil) c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings) // Check the constraints. obtainedConstraints, err := service.Constraints() c.Assert(err, jc.ErrorIsNil) c.Assert(obtainedConstraints, gc.DeepEquals, cons) }
func (s *serviceSuite) TestClientServiceUpdateSetConstraints(c *gc.C) { service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) // Update constraints for the service. cons, err := constraints.Parse("mem=4096", "cpu-cores=2") c.Assert(err, jc.ErrorIsNil) args := params.ServiceUpdate{ ServiceName: "dummy", Constraints: &cons, } err = s.serviceApi.ServiceUpdate(args) c.Assert(err, jc.ErrorIsNil) // Ensure the constraints have been correctly updated. obtained, err := service.Constraints() c.Assert(err, jc.ErrorIsNil) c.Assert(obtained, gc.DeepEquals, cons) }
func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachineAttributes(c *gc.C) { testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") _, err := s.DeployBundleYAML(c, ` applications: django: charm: cs:xenial/django-42 num_units: 2 to: - 1 - new machines: 1: series: xenial constraints: "cores=4 mem=4G" annotations: foo: bar `) c.Assert(err, jc.ErrorIsNil) s.assertApplicationsDeployed(c, map[string]serviceInfo{ "django": {charm: "cs:xenial/django-42"}, }) s.assertRelationsEstablished(c) s.assertUnitsCreated(c, map[string]string{ "django/0": "0", "django/1": "1", }) m, err := s.State.Machine("0") c.Assert(err, jc.ErrorIsNil) c.Assert(m.Series(), gc.Equals, "xenial") cons, err := m.Constraints() c.Assert(err, jc.ErrorIsNil) expectedCons, err := constraints.Parse("cores=4 mem=4G") c.Assert(err, jc.ErrorIsNil) c.Assert(cons, jc.DeepEquals, expectedCons) ann, err := s.State.Annotations(m) c.Assert(err, jc.ErrorIsNil) c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"}) }
func (c *envSetConstraintsCommand) Init(args []string) (err error) { c.Constraints, err = constraints.Parse(args...) return err }
func verifyConstraint(c string) error { _, err := constraints.Parse(c) return err }
// addMachine creates a new top-level machine or container in the environment. func (h *bundleHandler) addMachine(id string, p bundlechanges.AddMachineParams) error { services := h.servicesForMachineChange(id) // Note that we always have at least one application that justifies the // creation of this machine. msg := services[0] + " unit" svcLen := len(services) if svcLen != 1 { msg = strings.Join(services[:svcLen-1], ", ") + " and " + services[svcLen-1] + " units" } // Check whether the desired number of units already exist in the // environment, in which case avoid adding other machines to host those // application units. machine := h.chooseMachine(services...) if machine != "" { h.results[id] = machine notify := make([]string, 0, svcLen) for _, application := range services { if !h.ignoredMachines[application] { h.ignoredMachines[application] = true notify = append(notify, application) } } svcLen = len(notify) switch svcLen { case 0: return nil case 1: msg = notify[0] default: msg = strings.Join(notify[:svcLen-1], ", ") + " and " + notify[svcLen-1] } h.log.Infof("avoid creating other machines to host %s units", msg) return nil } cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for machine") } machineParams := params.AddMachineParams{ Constraints: cons, Series: p.Series, Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, } if ct := p.ContainerType; ct != "" { // for backwards compatibility with 1.x bundles, we treat lxc // placement directives as lxd. if ct == "lxc" { if !h.warnedLXC { 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.") h.warnedLXC = true } ct = string(instance.LXD) } containerType, err := instance.ParseContainerType(ct) if err != nil { return errors.Annotatef(err, "cannot create machine for holding %s", msg) } machineParams.ContainerType = containerType if p.ParentId != "" { machineParams.ParentId, err = h.resolveMachine(p.ParentId) if err != nil { return errors.Annotatef(err, "cannot retrieve parent placement for %s", msg) } } } r, err := h.api.AddMachines([]params.AddMachineParams{machineParams}) if err != nil { return errors.Annotatef(err, "cannot create machine for holding %s", msg) } if r[0].Error != nil { return errors.Annotatef(r[0].Error, "cannot create machine for holding %s", msg) } machine = r[0].Machine if p.ContainerType == "" { logger.Debugf("created new machine %s for holding %s", machine, msg) } else if p.ParentId == "" { logger.Debugf("created %s container in new machine for holding %s", machine, msg) } else { logger.Debugf("created %s container in machine %s for holding %s", machine, machineParams.ParentId, msg) } h.results[id] = machine return nil }
// addService deploys or update an application with no units. Service options are // also set or updated. func (h *bundleHandler) addService( api DeployAPI, id string, p bundlechanges.AddApplicationParams, chID charmstore.CharmID, csMac *macaroon.Macaroon, ) error { h.results[id] = p.Application ch := chID.URL.String() // Handle application configuration. configYAML := "" if len(p.Options) > 0 { config, err := yaml.Marshal(map[string]map[string]interface{}{p.Application: p.Options}) if err != nil { return errors.Annotatef(err, "cannot marshal options for application %q", p.Application) } configYAML = string(config) } // Handle application constraints. cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for application") } storageConstraints := h.bundleStorage[p.Application] if len(p.Storage) > 0 { if storageConstraints == nil { storageConstraints = make(map[string]storage.Constraints) } for k, v := range p.Storage { if _, ok := storageConstraints[k]; ok { // Storage constraints overridden // on the command line. continue } cons, err := storage.ParseConstraints(v) if err != nil { return errors.Annotate(err, "invalid storage constraints") } storageConstraints[k] = cons } } resources := make(map[string]string) for resName, revision := range p.Resources { resources[resName] = fmt.Sprint(revision) } charmInfo, err := h.api.CharmInfo(ch) if err != nil { return err } resNames2IDs, err := resourceadapters.DeployResources( p.Application, chID, csMac, resources, charmInfo.Meta.Resources, api, ) if err != nil { return errors.Trace(err) } // Figure out what series we need to deploy with. conf, err := getModelConfig(h.api) if err != nil { return err } supportedSeries := charmInfo.Meta.Series if len(supportedSeries) == 0 && chID.URL.Series != "" { supportedSeries = []string{chID.URL.Series} } selector := seriesSelector{ seriesFlag: p.Series, charmURLSeries: chID.URL.Series, supportedSeries: supportedSeries, conf: conf, fromBundle: true, } series, err := selector.charmSeries() if err != nil { return errors.Trace(err) } // Deploy the application. logger.Debugf("application %s is deploying (charm %s)", p.Application, ch) h.log.Infof("Deploying charm %q", ch) if err := api.Deploy(application.DeployArgs{ CharmID: chID, Cons: cons, ApplicationName: p.Application, Series: series, ConfigYAML: configYAML, Storage: storageConstraints, Resources: resNames2IDs, EndpointBindings: p.EndpointBindings, }); err == nil { for resName := range resNames2IDs { h.log.Infof("added resource %s", resName) } return nil } else if !isErrServiceExists(err) { return errors.Annotatef(err, "cannot deploy application %q", p.Application) } // The application is already deployed in the environment: check that its // charm is compatible with the one declared in the bundle. If it is, // reuse the existing application or upgrade to a specified revision. // Exit with an error otherwise. if err := h.upgradeCharm(api, p.Application, chID, csMac, resources); err != nil { return errors.Annotatef(err, "cannot upgrade application %q", p.Application) } // Update application configuration. if configYAML != "" { if err := h.api.Update(params.ApplicationUpdate{ ApplicationName: p.Application, SettingsYAML: configYAML, }); err != nil { // This should never happen as possible errors are already returned // by the application Deploy call above. return errors.Annotatef(err, "cannot update options for application %q", p.Application) } h.log.Infof("configuration updated for application %s", p.Application) } // Update application constraints. if p.Constraints != "" { if err := h.api.SetConstraints(p.Application, cons); err != nil { // This should never happen, as the bundle is already verified. return errors.Annotatef(err, "cannot update constraints for application %q", p.Application) } h.log.Infof("constraints applied for application %s", p.Application) } return nil }
// addService deploys or update a service with no units. Service options are // also set or updated. func (h *bundleHandler) addService(id string, p bundlechanges.AddServiceParams) error { h.results[id] = p.Service ch := resolve(p.Charm, h.results) // Handle service configuration. configYAML := "" if len(p.Options) > 0 { config, err := yaml.Marshal(map[string]map[string]interface{}{p.Service: p.Options}) if err != nil { return errors.Annotatef(err, "cannot marshal options for service %q", p.Service) } configYAML = string(config) } // Handle service constraints. cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for service") } storageConstraints := h.bundleStorage[p.Service] if len(p.Storage) > 0 { if storageConstraints == nil { storageConstraints = make(map[string]storage.Constraints) } for k, v := range p.Storage { if _, ok := storageConstraints[k]; ok { // Storage constraints overridden // on the command line. continue } cons, err := storage.ParseConstraints(v) if err != nil { return errors.Annotate(err, "invalid storage constraints") } storageConstraints[k] = cons } } // Deploy the service. if err := h.serviceDeployer.serviceDeploy(serviceDeployParams{ charmURL: ch, serviceName: p.Service, configYAML: configYAML, constraints: cons, storage: storageConstraints, spaceBindings: p.EndpointBindings, }); err == nil { h.log.Infof("service %s deployed (charm: %s)", p.Service, ch) return nil } else if !isErrServiceExists(err) { return errors.Annotatef(err, "cannot deploy service %q", p.Service) } // The service is already deployed in the environment: check that its // charm is compatible with the one declared in the bundle. If it is, // reuse the existing service or upgrade to a specified revision. // Exit with an error otherwise. if err := upgradeCharm(h.serviceClient, h.log, p.Service, ch); err != nil { return errors.Annotatef(err, "cannot upgrade service %q", p.Service) } // Update service configuration. if configYAML != "" { if err := h.serviceClient.Update(params.ServiceUpdate{ ServiceName: p.Service, SettingsYAML: configYAML, }); err != nil { // This should never happen as possible errors are already returned // by the service Deploy call above. return errors.Annotatef(err, "cannot update options for service %q", p.Service) } h.log.Infof("configuration updated for service %s", p.Service) } // Update service constraints. if p.Constraints != "" { if err := h.serviceClient.SetConstraints(p.Service, cons); err != nil { // This should never happen, as the bundle is already verified. return errors.Annotatef(err, "cannot update constraints for service %q", p.Service) } h.log.Infof("constraints applied for service %s", p.Service) } return nil }
// deployBundle deploys the given bundle data using the given API client and // charm store client. The deployment is not transactional, and its progress is // notified using the given deployment logger. func deployBundle( data *charm.BundleData, client *api.Client, serviceDeployer *serviceDeployer, csclient *csClient, repoPath string, conf *config.Config, log deploymentLogger, bundleStorage map[string]map[string]storage.Constraints, ) error { verifyConstraints := func(s string) error { _, err := constraints.Parse(s) return err } verifyStorage := func(s string) error { _, err := storage.ParseConstraints(s) return err } if err := data.Verify(verifyConstraints, verifyStorage); err != nil { return errors.Annotate(err, "cannot deploy bundle") } // Retrieve bundle changes. changes := bundlechanges.FromData(data) numChanges := len(changes) // Initialize the unit status. status, err := client.Status(nil) if err != nil { return errors.Annotate(err, "cannot get model status") } unitStatus := make(map[string]string, numChanges) for _, serviceData := range status.Services { for unit, unitData := range serviceData.Units { unitStatus[unit] = unitData.Machine } } // Instantiate a watcher used to follow the deployment progress. watcher, err := watchAll(client) if err != nil { return errors.Annotate(err, "cannot watch model") } defer watcher.Stop() serviceClient, err := serviceDeployer.newServiceAPIClient() if err != nil { return errors.Annotate(err, "cannot get service client") } annotationsClient, err := serviceDeployer.newAnnotationsAPIClient() if err != nil { return errors.Annotate(err, "cannot get annotations client") } // Instantiate the bundle handler. h := &bundleHandler{ changes: changes, results: make(map[string]string, numChanges), client: client, serviceClient: serviceClient, annotationsClient: annotationsClient, serviceDeployer: serviceDeployer, bundleStorage: bundleStorage, csclient: csclient, repoPath: repoPath, conf: conf, log: log, data: data, unitStatus: unitStatus, ignoredMachines: make(map[string]bool, len(data.Services)), ignoredUnits: make(map[string]bool, len(data.Services)), watcher: watcher, } // Deploy the bundle. for _, change := range changes { switch change := change.(type) { case *bundlechanges.AddCharmChange: err = h.addCharm(change.Id(), change.Params) case *bundlechanges.AddMachineChange: err = h.addMachine(change.Id(), change.Params) case *bundlechanges.AddRelationChange: err = h.addRelation(change.Id(), change.Params) case *bundlechanges.AddServiceChange: err = h.addService(change.Id(), change.Params) case *bundlechanges.AddUnitChange: err = h.addUnit(change.Id(), change.Params) case *bundlechanges.ExposeChange: err = h.exposeService(change.Id(), change.Params) case *bundlechanges.SetAnnotationsChange: err = h.setAnnotations(change.Id(), change.Params) default: return errors.Errorf("unknown change type: %T", change) } if err != nil { return errors.Annotate(err, "cannot deploy bundle") } } return nil }
// addService deploys or update a service with no units. Service options are // also set or updated. func (h *bundleHandler) addService(id string, p bundlechanges.AddServiceParams, chID charmstore.CharmID, csMac *macaroon.Macaroon) error { h.results[id] = p.Service ch := chID.URL.String() // Handle service configuration. configYAML := "" if len(p.Options) > 0 { config, err := yaml.Marshal(map[string]map[string]interface{}{p.Service: p.Options}) if err != nil { return errors.Annotatef(err, "cannot marshal options for service %q", p.Service) } configYAML = string(config) } // Handle service constraints. cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for service") } storageConstraints := h.bundleStorage[p.Service] if len(p.Storage) > 0 { if storageConstraints == nil { storageConstraints = make(map[string]storage.Constraints) } for k, v := range p.Storage { if _, ok := storageConstraints[k]; ok { // Storage constraints overridden // on the command line. continue } cons, err := storage.ParseConstraints(v) if err != nil { return errors.Annotate(err, "invalid storage constraints") } storageConstraints[k] = cons } } resources := make(map[string]string) for resName, revision := range p.Resources { resources[resName] = fmt.Sprint(revision) } charmInfo, err := h.client.CharmInfo(ch) if err != nil { return err } resNames2IDs, err := handleResources(h.serviceDeployer.api, resources, p.Service, chID, csMac, charmInfo.Meta.Resources) if err != nil { return errors.Trace(err) } // Figure out what series we need to deploy with. conf, err := getClientConfig(h.client) if err != nil { return err } supportedSeries := charmInfo.Meta.Series series, message, err := charmSeries(p.Series, chID.URL.Series, supportedSeries, false, conf, deployFromBundle) if err != nil { return errors.Trace(err) } // Deploy the service. if err := h.serviceDeployer.serviceDeploy(serviceDeployParams{ charmID: chID, serviceName: p.Service, series: series, configYAML: configYAML, constraints: cons, storage: storageConstraints, spaceBindings: p.EndpointBindings, resources: resNames2IDs, }); err == nil { h.log.Infof("service %s deployed (charm %s %v)", p.Service, ch, fmt.Sprintf(message, series)) for resName := range resNames2IDs { h.log.Infof("added resource %s", resName) } return nil } else if !isErrServiceExists(err) { return errors.Annotatef(err, "cannot deploy service %q", p.Service) } // The service is already deployed in the environment: check that its // charm is compatible with the one declared in the bundle. If it is, // reuse the existing service or upgrade to a specified revision. // Exit with an error otherwise. if err := h.upgradeCharm(p.Service, chID, csMac, resources); err != nil { return errors.Annotatef(err, "cannot upgrade service %q", p.Service) } // Update service configuration. if configYAML != "" { if err := h.serviceClient.Update(params.ServiceUpdate{ ServiceName: p.Service, SettingsYAML: configYAML, }); err != nil { // This should never happen as possible errors are already returned // by the service Deploy call above. return errors.Annotatef(err, "cannot update options for service %q", p.Service) } h.log.Infof("configuration updated for service %s", p.Service) } // Update service constraints. if p.Constraints != "" { if err := h.serviceClient.SetConstraints(p.Service, cons); err != nil { // This should never happen, as the bundle is already verified. return errors.Annotatef(err, "cannot update constraints for service %q", p.Service) } h.log.Infof("constraints applied for service %s", p.Service) } return nil }
// deployBundle deploys the given bundle data using the given API client and // charm store client. The deployment is not transactional, and its progress is // notified using the given deployment logger. func deployBundle( bundleFilePath string, data *charm.BundleData, channel csparams.Channel, client *api.Client, serviceDeployer *serviceDeployer, resolver *charmURLResolver, log deploymentLogger, bundleStorage map[string]map[string]storage.Constraints, ) (map[*charm.URL]*macaroon.Macaroon, error) { verifyConstraints := func(s string) error { _, err := constraints.Parse(s) return err } verifyStorage := func(s string) error { _, err := storage.ParseConstraints(s) return err } var verifyError error if bundleFilePath == "" { verifyError = data.Verify(verifyConstraints, verifyStorage) } else { verifyError = data.VerifyLocal(bundleFilePath, verifyConstraints, verifyStorage) } if verifyError != nil { if verr, ok := verifyError.(*charm.VerificationError); ok { errs := make([]string, len(verr.Errors)) for i, err := range verr.Errors { errs[i] = err.Error() } return nil, errors.New("the provided bundle has the following errors:\n" + strings.Join(errs, "\n")) } return nil, errors.Annotate(verifyError, "cannot deploy bundle") } // Retrieve bundle changes. changes := bundlechanges.FromData(data) numChanges := len(changes) // Initialize the unit status. status, err := client.Status(nil) if err != nil { return nil, errors.Annotate(err, "cannot get model status") } unitStatus := make(map[string]string, numChanges) for _, serviceData := range status.Services { for unit, unitData := range serviceData.Units { unitStatus[unit] = unitData.Machine } } // Instantiate a watcher used to follow the deployment progress. watcher, err := watchAll(client) if err != nil { return nil, errors.Annotate(err, "cannot watch model") } defer watcher.Stop() serviceClient, err := serviceDeployer.newServiceAPIClient() if err != nil { return nil, errors.Annotate(err, "cannot get service client") } annotationsClient, err := serviceDeployer.newAnnotationsAPIClient() if err != nil { return nil, errors.Annotate(err, "cannot get annotations client") } // Instantiate the bundle handler. h := &bundleHandler{ bundleDir: bundleFilePath, changes: changes, results: make(map[string]string, numChanges), channel: channel, client: client, serviceClient: serviceClient, annotationsClient: annotationsClient, serviceDeployer: serviceDeployer, bundleStorage: bundleStorage, resolver: resolver, log: log, data: data, unitStatus: unitStatus, ignoredMachines: make(map[string]bool, len(data.Services)), ignoredUnits: make(map[string]bool, len(data.Services)), watcher: watcher, } // Deploy the bundle. csMacs := make(map[*charm.URL]*macaroon.Macaroon) channels := make(map[*charm.URL]csparams.Channel) for _, change := range changes { switch change := change.(type) { case *bundlechanges.AddCharmChange: cURL, channel, csMac, err2 := h.addCharm(change.Id(), change.Params) if err2 == nil { csMacs[cURL] = csMac channels[cURL] = channel } err = err2 case *bundlechanges.AddMachineChange: err = h.addMachine(change.Id(), change.Params) case *bundlechanges.AddRelationChange: err = h.addRelation(change.Id(), change.Params) case *bundlechanges.AddServiceChange: var cURL *charm.URL cURL, err = charm.ParseURL(resolve(change.Params.Charm, h.results)) if err == nil { chID := charmstore.CharmID{ URL: cURL, Channel: channels[cURL], } csMac := csMacs[cURL] err = h.addService(change.Id(), change.Params, chID, csMac) } case *bundlechanges.AddUnitChange: err = h.addUnit(change.Id(), change.Params) case *bundlechanges.ExposeChange: err = h.exposeService(change.Id(), change.Params) case *bundlechanges.SetAnnotationsChange: err = h.setAnnotations(change.Id(), change.Params) default: return nil, errors.Errorf("unknown change type: %T", change) } if err != nil { return nil, errors.Annotate(err, "cannot deploy bundle") } } return csMacs, nil }
// addMachine creates a new top-level machine or container in the environment. func (h *bundleHandler) addMachine(id string, p bundlechanges.AddMachineParams) error { services := h.servicesForMachineChange(id) // Note that we always have at least one service that justifies the // creation of this machine. msg := services[0] + " unit" svcLen := len(services) if svcLen != 1 { msg = strings.Join(services[:svcLen-1], ", ") + " and " + services[svcLen-1] + " units" } // Check whether the desired number of units already exist in the // environment, in which case avoid adding other machines to host those // service units. machine := h.chooseMachine(services...) if machine != "" { h.results[id] = machine notify := make([]string, 0, svcLen) for _, service := range services { if !h.ignoredMachines[service] { h.ignoredMachines[service] = true notify = append(notify, service) } } svcLen = len(notify) switch svcLen { case 0: return nil case 1: msg = notify[0] default: msg = strings.Join(notify[:svcLen-1], ", ") + " and " + notify[svcLen-1] } h.log.Infof("avoid creating other machines to host %s units", msg) return nil } cons, err := constraints.Parse(p.Constraints) if err != nil { // This should never happen, as the bundle is already verified. return errors.Annotate(err, "invalid constraints for machine") } machineParams := params.AddMachineParams{ Constraints: cons, Series: p.Series, Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, } if p.ContainerType != "" { containerType, err := instance.ParseContainerType(p.ContainerType) if err != nil { return errors.Annotatef(err, "cannot create machine for holding %s", msg) } machineParams.ContainerType = containerType if p.ParentId != "" { machineParams.ParentId, err = h.resolveMachine(p.ParentId) if err != nil { return errors.Annotatef(err, "cannot retrieve parent placement for %s", msg) } } } r, err := h.client.AddMachines([]params.AddMachineParams{machineParams}) if err != nil { return errors.Annotatef(err, "cannot create machine for holding %s", msg) } if r[0].Error != nil { return errors.Annotatef(r[0].Error, "cannot create machine for holding %s", msg) } machine = r[0].Machine if p.ContainerType == "" { h.log.Infof("created new machine %s for holding %s", machine, msg) } else if p.ParentId == "" { h.log.Infof("created %s container in new machine for holding %s", machine, msg) } else { h.log.Infof("created %s container in machine %s for holding %s", machine, machineParams.ParentId, msg) } h.results[id] = machine return nil }