Beispiel #1
0
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)
	}
}
Beispiel #2
0
// 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] = &params.BundleChangesChange{
			Id:       c.Id(),
			Method:   c.Method(),
			Args:     c.GUIArgs(),
			Requires: c.Requires(),
		}
	}
	return results, nil
}
Beispiel #3
0
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
}
Beispiel #4
0
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)
}
Beispiel #5
0
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)
	}
}
Beispiel #6
0
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)
	}
}
Beispiel #7
0
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)
}
Beispiel #8
0
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)
}
Beispiel #9
0
// 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
}
Beispiel #10
0
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
}
Beispiel #11
0
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
}
Beispiel #12
0
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{})
	}
}
Beispiel #13
0
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{})
	}
}
Beispiel #14
0
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"})
}
Beispiel #15
0
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)
}
Beispiel #16
0
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)
}
Beispiel #17
0
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"})
}
Beispiel #18
0
func (c *envSetConstraintsCommand) Init(args []string) (err error) {
	c.Constraints, err = constraints.Parse(args...)
	return err
}
Beispiel #19
0
func verifyConstraint(c string) error {
	_, err := constraints.Parse(c)
	return err
}
Beispiel #20
0
// 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
}
Beispiel #21
0
// 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
}
Beispiel #22
0
// 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
}
Beispiel #23
0
// 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
}
Beispiel #24
0
// 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
}
Beispiel #25
0
// 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
}
Beispiel #26
0
// 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
}