Beispiel #1
0
// DeployService takes a charm and various parameters and deploys it.
func DeployService(st ServiceDeployer, args DeployServiceParams) (*state.Service, error) {
	if args.NumUnits > 1 && len(args.Placement) == 0 && args.ToMachineSpec != "" {
		return nil, fmt.Errorf("cannot use --num-units with --to")
	}
	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
	if err != nil {
		return nil, err
	}
	if args.Charm.Meta().Subordinate {
		if args.NumUnits != 0 || args.ToMachineSpec != "" {
			return nil, fmt.Errorf("subordinate service must be deployed without units")
		}
		if !constraints.IsEmpty(&args.Constraints) {
			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
		}
	}
	if args.ServiceOwner == "" {
		env, err := st.Environment()
		if err != nil {
			return nil, errors.Trace(err)
		}
		args.ServiceOwner = env.Owner().String()
	}
	// TODO(fwereade): transactional State.AddService including settings, constraints
	// (minimumUnitCount, initialMachineIds?).

	if len(args.Networks) > 0 || args.Constraints.HaveNetworks() {
		return nil, fmt.Errorf("use of --networks is deprecated. Please use spaces")
	}

	if len(args.Placement) == 0 {
		args.Placement, err = makePlacement(args.ToMachineSpec)
		if err != nil {
			return nil, errors.Trace(err)
		}
	}

	asa := state.AddServiceArgs{
		Name:      args.ServiceName,
		Series:    args.Series,
		Owner:     args.ServiceOwner,
		Charm:     args.Charm,
		Networks:  args.Networks,
		Storage:   stateStorageConstraints(args.Storage),
		Settings:  settings,
		NumUnits:  args.NumUnits,
		Placement: args.Placement,
	}

	if !args.Charm.Meta().Subordinate {
		asa.Constraints = args.Constraints
	}

	// TODO(dimitern): In a follow-up drop Networks and use spaces
	// constraints for this when possible.
	return st.AddService(asa)
}
Beispiel #2
0
// DeployService takes a charm and various parameters and deploys it.
func DeployService(st ServiceDeployer, args DeployServiceParams) (*state.Service, error) {
	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
	if err != nil {
		return nil, err
	}
	if args.Charm.Meta().Subordinate {
		if args.NumUnits != 0 {
			return nil, fmt.Errorf("subordinate service must be deployed without units")
		}
		if !constraints.IsEmpty(&args.Constraints) {
			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
		}
	}
	if args.ServiceOwner == "" {
		env, err := st.Model()
		if err != nil {
			return nil, errors.Trace(err)
		}
		args.ServiceOwner = env.Owner().String()
	}
	// TODO(fwereade): transactional State.AddService including settings, constraints
	// (minimumUnitCount, initialMachineIds?).

	if len(args.Networks) > 0 || args.Constraints.HaveNetworks() {
		return nil, fmt.Errorf("use of --networks is deprecated. Please use spaces")
	}

	effectiveBindings, err := getEffectiveBindingsForCharmMeta(args.Charm.Meta(), args.EndpointBindings)
	if err != nil {
		return nil, errors.Annotate(err, "cannot determine effective service endpoint bindings")
	}

	asa := state.AddServiceArgs{
		Name:             args.ServiceName,
		Series:           args.Series,
		Owner:            args.ServiceOwner,
		Charm:            args.Charm,
		Networks:         args.Networks,
		Storage:          stateStorageConstraints(args.Storage),
		Settings:         settings,
		NumUnits:         args.NumUnits,
		Placement:        args.Placement,
		Resources:        args.Resources,
		EndpointBindings: effectiveBindings,
	}

	if !args.Charm.Meta().Subordinate {
		asa.Constraints = args.Constraints
	}

	// TODO(dimitern): In a follow-up drop Networks and use spaces
	// constraints for this when possible.
	return st.AddService(asa)
}
Beispiel #3
0
func (s *CommonProvisionerSuite) checkStartInstanceCustom(c *gc.C, m *state.Machine, secret string, cons constraints.Value, networks []string, networkInfo []network.Info, waitInstanceId bool) (inst instance.Instance) {
	s.BackingState.StartSync()
	for {
		select {
		case o := <-s.op:
			switch o := o.(type) {
			case dummy.OpStartInstance:
				inst = o.Instance
				if waitInstanceId {
					s.waitInstanceId(c, m, inst.Id())
				}

				// Check the instance was started with the expected params.
				c.Assert(o.MachineId, gc.Equals, m.Id())
				nonceParts := strings.SplitN(o.MachineNonce, ":", 2)
				c.Assert(nonceParts, gc.HasLen, 2)
				c.Assert(nonceParts[0], gc.Equals, names.NewMachineTag("0").String())
				c.Assert(nonceParts[1], jc.Satisfies, utils.IsValidUUIDString)
				c.Assert(o.Secret, gc.Equals, secret)
				c.Assert(o.Networks, jc.DeepEquals, networks)
				c.Assert(o.NetworkInfo, jc.DeepEquals, networkInfo)

				// All provisioned machines in this test suite have
				// their hardware characteristics attributes set to
				// the same values as the constraints due to the dummy
				// environment being used.
				if !constraints.IsEmpty(&cons) {
					c.Assert(o.Constraints, gc.DeepEquals, cons)
					hc, err := m.HardwareCharacteristics()
					c.Assert(err, gc.IsNil)
					c.Assert(*hc, gc.DeepEquals, instance.HardwareCharacteristics{
						Arch:     cons.Arch,
						Mem:      cons.Mem,
						RootDisk: cons.RootDisk,
						CpuCores: cons.CpuCores,
						CpuPower: cons.CpuPower,
						Tags:     cons.Tags,
					})
				}
				return
			default:
				c.Logf("ignoring unexpected operation %#v", o)
			}
		case <-time.After(2 * time.Second):
			c.Fatalf("provisioner did not start an instance")
			return
		}
	}
	return
}
Beispiel #4
0
// DeployService takes a charm and various parameters and deploys it.
func DeployService(st ServiceDeployer, args DeployServiceParams) (*state.Service, error) {
	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
	if err != nil {
		return nil, err
	}
	if args.Charm.Meta().Subordinate {
		if args.NumUnits != 0 {
			return nil, fmt.Errorf("subordinate service must be deployed without units")
		}
		if !constraints.IsEmpty(&args.Constraints) {
			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
		}
	}
	if args.ServiceOwner == "" {
		env, err := st.Model()
		if err != nil {
			return nil, errors.Trace(err)
		}
		args.ServiceOwner = env.Owner().String()
	}
	// TODO(fwereade): transactional State.AddService including settings, constraints
	// (minimumUnitCount, initialMachineIds?).

	effectiveBindings := getEffectiveBindingsForCharmMeta(args.Charm.Meta(), args.EndpointBindings)

	asa := state.AddServiceArgs{
		Name:             args.ServiceName,
		Series:           args.Series,
		Owner:            args.ServiceOwner,
		Charm:            args.Charm,
		Channel:          args.Channel,
		Storage:          stateStorageConstraints(args.Storage),
		Settings:         settings,
		NumUnits:         args.NumUnits,
		Placement:        args.Placement,
		Resources:        args.Resources,
		EndpointBindings: effectiveBindings,
	}

	if !args.Charm.Meta().Subordinate {
		asa.Constraints = args.Constraints
	}

	return st.AddService(asa)
}
Beispiel #5
0
// DeployApplication takes a charm and various parameters and deploys it.
func DeployApplication(st ApplicationDeployer, args DeployApplicationParams) (*state.Application, error) {
	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
	if err != nil {
		return nil, err
	}
	if args.Charm.Meta().Subordinate {
		if args.NumUnits != 0 {
			return nil, fmt.Errorf("subordinate application must be deployed without units")
		}
		if !constraints.IsEmpty(&args.Constraints) {
			return nil, fmt.Errorf("subordinate application must be deployed without constraints")
		}
	}
	// TODO(fwereade): transactional State.AddApplication including settings, constraints
	// (minimumUnitCount, initialMachineIds?).

	effectiveBindings := getEffectiveBindingsForCharmMeta(args.Charm.Meta(), args.EndpointBindings)

	asa := state.AddApplicationArgs{
		Name:             args.ApplicationName,
		Series:           args.Series,
		Charm:            args.Charm,
		Channel:          args.Channel,
		Storage:          stateStorageConstraints(args.Storage),
		Settings:         settings,
		NumUnits:         args.NumUnits,
		Placement:        args.Placement,
		Resources:        args.Resources,
		EndpointBindings: effectiveBindings,
	}

	if !args.Charm.Meta().Subordinate {
		asa.Constraints = args.Constraints
	}

	return st.AddApplication(asa)
}
Beispiel #6
0
// EnableHASingle applies a single ControllersServersSpec specification to the current environment.
// Exported so it can be called by the legacy client API in the client package.
func EnableHASingle(st *state.State, spec params.ControllersSpec) (params.ControllersChanges, error) {
	if !st.IsController() {
		return params.ControllersChanges{}, errors.New("unsupported with hosted models")
	}
	// Check if changes are allowed and the command may proceed.
	blockChecker := common.NewBlockChecker(st)
	if err := blockChecker.ChangeAllowed(); err != nil {
		return params.ControllersChanges{}, errors.Trace(err)
	}
	// Validate the environment tag if present.
	if spec.ModelTag != "" {
		tag, err := names.ParseModelTag(spec.ModelTag)
		if err != nil {
			return params.ControllersChanges{}, errors.Errorf("invalid model tag: %v", err)
		}
		if _, err := st.FindEntity(tag); err != nil {
			return params.ControllersChanges{}, err
		}
	}

	series := spec.Series
	if series == "" {
		ssi, err := st.ControllerInfo()
		if err != nil {
			return params.ControllersChanges{}, err
		}

		// We should always have at least one voting machine
		// If we *really* wanted we could just pick whatever series is
		// in the majority, but really, if we always copy the value of
		// the first one, then they'll stay in sync.
		if len(ssi.VotingMachineIds) == 0 {
			// Better than a panic()?
			return params.ControllersChanges{}, errors.Errorf("internal error, failed to find any voting machines")
		}
		templateMachine, err := st.Machine(ssi.VotingMachineIds[0])
		if err != nil {
			return params.ControllersChanges{}, err
		}
		series = templateMachine.Series()
	}
	if constraints.IsEmpty(&spec.Constraints) {
		// No constraints specified, so we'll use the constraints off
		// a running controller.
		controllerInfo, err := st.ControllerInfo()
		if err != nil {
			return params.ControllersChanges{}, err
		}
		// We'll sort the controller ids to find the smallest.
		// This will typically give the initial bootstrap machine.
		var controllerIds []int
		for _, id := range controllerInfo.MachineIds {
			idNum, err := strconv.Atoi(id)
			if err != nil {
				logger.Warningf("ignoring non numeric controller id %v", id)
				continue
			}
			controllerIds = append(controllerIds, idNum)
		}
		if len(controllerIds) == 0 {
			errors.Errorf("internal error, failed to find any controllers")
		}
		sort.Ints(controllerIds)

		// Load the controller machine and get its constraints.
		controllerId := controllerIds[0]
		controller, err := st.Machine(strconv.Itoa(controllerId))
		if err != nil {
			return params.ControllersChanges{}, errors.Annotatef(err, "reading controller id %v", controllerId)
		}
		spec.Constraints, err = controller.Constraints()
		if err != nil {
			return params.ControllersChanges{}, errors.Annotatef(err, "reading constraints for controller id %v", controllerId)
		}
	}

	changes, err := st.EnableHA(spec.NumControllers, spec.Constraints, series, spec.Placement)
	if err != nil {
		return params.ControllersChanges{}, err
	}
	return controllersChanges(changes), nil
}
Beispiel #7
0
func (s *CommonProvisionerSuite) checkStartInstanceCustom(
	c *gc.C, m *state.Machine,
	secret string, cons constraints.Value,
	networks []string, networkInfo []network.InterfaceInfo,
	subnetsToZones map[network.Id][]string,
	volumes []storage.Volume,
	secureServerConnection bool,
	checkPossibleTools coretools.List,
	waitInstanceId bool,
) (
	inst instance.Instance,
) {
	s.BackingState.StartSync()
	for {
		select {
		case o := <-s.op:
			switch o := o.(type) {
			case dummy.OpStartInstance:
				inst = o.Instance
				if waitInstanceId {
					s.waitInstanceId(c, m, inst.Id())
				}

				// Check the instance was started with the expected params.
				c.Assert(o.MachineId, gc.Equals, m.Id())
				nonceParts := strings.SplitN(o.MachineNonce, ":", 2)
				c.Assert(nonceParts, gc.HasLen, 2)
				c.Assert(nonceParts[0], gc.Equals, names.NewMachineTag("0").String())
				c.Assert(nonceParts[1], jc.Satisfies, utils.IsValidUUIDString)
				c.Assert(o.Secret, gc.Equals, secret)
				c.Assert(o.SubnetsToZones, jc.DeepEquals, subnetsToZones)
				c.Assert(o.Networks, jc.DeepEquals, networks)
				c.Assert(o.NetworkInfo, jc.DeepEquals, networkInfo)
				c.Assert(o.Volumes, jc.DeepEquals, volumes)
				c.Assert(o.AgentEnvironment["SECURE_STATESERVER_CONNECTION"], gc.Equals, strconv.FormatBool(secureServerConnection))

				var jobs []multiwatcher.MachineJob
				for _, job := range m.Jobs() {
					jobs = append(jobs, job.ToParams())
				}
				c.Assert(o.Jobs, jc.SameContents, jobs)

				if checkPossibleTools != nil {
					for _, t := range o.PossibleTools {
						url := fmt.Sprintf("https://%s/environment/%s/tools/%s",
							s.st.Addr(), coretesting.EnvironmentTag.Id(), t.Version)
						c.Check(t.URL, gc.Equals, url)
						t.URL = ""
					}
					for _, t := range checkPossibleTools {
						t.URL = ""
					}
					c.Assert(o.PossibleTools, gc.DeepEquals, checkPossibleTools)
				}

				// All provisioned machines in this test suite have
				// their hardware characteristics attributes set to
				// the same values as the constraints due to the dummy
				// environment being used.
				if !constraints.IsEmpty(&cons) {
					c.Assert(o.Constraints, gc.DeepEquals, cons)
					hc, err := m.HardwareCharacteristics()
					c.Assert(err, jc.ErrorIsNil)
					c.Assert(*hc, gc.DeepEquals, instance.HardwareCharacteristics{
						Arch:     cons.Arch,
						Mem:      cons.Mem,
						RootDisk: cons.RootDisk,
						CpuCores: cons.CpuCores,
						CpuPower: cons.CpuPower,
						Tags:     cons.Tags,
					})
				}
				return
			default:
				c.Logf("ignoring unexpected operation %#v", o)
			}
		case <-time.After(2 * time.Second):
			c.Fatalf("provisioner did not start an instance")
			return
		}
	}
}
Beispiel #8
0
func (c *DeployCommand) deployCharm(
	curl *charm.URL, series string, ctx *cmd.Context,
	client *api.Client, deployer *serviceDeployer,
) (rErr error) {
	if c.BumpRevision {
		ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
	}

	charmInfo, err := client.CharmInfo(curl.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate service")
		}
		if numUnits == 1 && c.PlacementSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate service")
		}
	}
	serviceName := c.ServiceName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(ctx)
		if err != nil {
			return err
		}
	}

	state, err := c.NewAPIRoot()
	if err != nil {
		return errors.Trace(err)
	}
	httpClient, err := c.HTTPClient()
	if err != nil {
		return errors.Trace(err)
	}

	deployInfo := DeploymentInfo{
		CharmURL:    curl,
		ServiceName: serviceName,
		ModelUUID:   client.ModelUUID(),
	}

	for _, step := range c.Steps {
		err = step.RunPre(state, httpClient, ctx, deployInfo)
		if err != nil {
			return err
		}
	}

	defer func() {
		for _, step := range c.Steps {
			err = step.RunPost(state, httpClient, ctx, deployInfo, rErr)
			if err != nil {
				rErr = err
			}
		}
	}()

	if err := deployer.serviceDeploy(serviceDeployParams{
		curl.String(),
		serviceName,
		series,
		numUnits,
		string(configYAML),
		c.Constraints,
		c.Placement,
		c.Networks,
		c.Storage,
	}); err != nil {
		return err
	}

	state, err = c.NewAPIRoot()
	if err != nil {
		return errors.Trace(err)
	}
	httpClient, err = c.HTTPClient()
	if err != nil {
		return errors.Trace(err)
	}

	return err
}
Beispiel #9
0
func (c *DeployCommand) Run(ctx *cmd.Context) error {
	client, err := c.NewAPIClient()
	if err != nil {
		return err
	}
	defer client.Close()

	conf, err := service.GetClientConfig(client)
	if err != nil {
		return err
	}

	if err := c.CheckProvider(conf); err != nil {
		return err
	}

	csClient, err := newCharmStoreClient()
	if err != nil {
		return errors.Trace(err)
	}
	defer csClient.jar.Save()
	curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf)
	if err != nil {
		return errors.Trace(err)
	}

	curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient)
	if err != nil {
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	if c.BumpRevision {
		ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
	}

	charmInfo, err := client.CharmInfo(curl.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate service")
		}
		if numUnits == 1 && c.PlacementSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate service")
		}
	}
	serviceName := c.ServiceName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(ctx)
		if err != nil {
			return err
		}
	}

	// If storage or placement is specified, we attempt to use a new API on the service facade.
	if len(c.Storage) > 0 || len(c.Placement) > 0 {
		notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server")
		serviceClient, err := c.newServiceAPIClient()
		if err != nil {
			return notSupported
		}
		defer serviceClient.Close()
		for i, p := range c.Placement {
			if p.Scope == "env-uuid" {
				p.Scope = serviceClient.EnvironmentUUID()
			}
			c.Placement[i] = p
		}
		err = serviceClient.ServiceDeploy(
			curl.String(),
			serviceName,
			numUnits,
			string(configYAML),
			c.Constraints,
			c.PlacementSpec,
			c.Placement,
			[]string{},
			c.Storage,
		)
		if params.IsCodeNotImplemented(err) {
			return notSupported
		}
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	if len(c.Networks) > 0 {
		ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks")
	}

	err = client.ServiceDeploy(
		curl.String(),
		serviceName,
		numUnits,
		string(configYAML),
		c.Constraints,
		c.PlacementSpec)

	if err != nil {
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	state, err := c.NewAPIRoot()
	if err != nil {
		return err
	}
	err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID())
	if params.IsCodeNotImplemented(err) {
		// The state server is too old to support metering.  Warn
		// the user, but don't return an error.
		logger.Warningf("current state server version does not support charm metering")
		return nil
	}

	return block.ProcessBlockedError(err, block.BlockChange)
}
Beispiel #10
0
func (c *DeployCommand) Run(ctx *cmd.Context) error {
	client, err := c.NewAPIClient()
	if err != nil {
		return err
	}
	defer client.Close()

	conf, err := service.GetClientConfig(client)
	if err != nil {
		return err
	}

	if err := c.CheckProvider(conf); err != nil {
		return err
	}

	csClient, err := newCharmStoreClient()
	if err != nil {
		return errors.Trace(err)
	}
	defer csClient.jar.Save()
	curl, repo, err := resolveCharmURL(c.CharmName, csClient.params, ctx.AbsPath(c.RepoPath), conf)
	if err != nil {
		return errors.Trace(err)
	}

	curl, err = addCharmViaAPI(client, ctx, curl, repo, csClient)
	if err != nil {
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	if c.BumpRevision {
		ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
	}

	requestedNetworks, err := networkNamesToTags(parseNetworks(c.Networks))
	if err != nil {
		return err
	}
	// We need to ensure network names are valid below, but we don't need them here.
	_, err = networkNamesToTags(c.Constraints.IncludeNetworks())
	if err != nil {
		return err
	}
	_, err = networkNamesToTags(c.Constraints.ExcludeNetworks())
	if err != nil {
		return err
	}
	haveNetworks := len(requestedNetworks) > 0 || c.Constraints.HaveNetworks()

	charmInfo, err := client.CharmInfo(curl.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate service")
		}
		if numUnits == 1 && c.ToMachineSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate service")
		}
	}
	serviceName := c.ServiceName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(ctx)
		if err != nil {
			return err
		}
	}

	// If storage is specified, we attempt to use a new API on the service facade.
	if len(c.Storage) > 0 {
		notSupported := errors.New("cannot deploy charms with storage: not supported by the API server")
		serviceClient, err := c.newServiceAPIClient()
		if err != nil {
			return notSupported
		}
		defer serviceClient.Close()
		err = serviceClient.ServiceDeploy(
			curl.String(),
			serviceName,
			numUnits,
			string(configYAML),
			c.Constraints,
			c.ToMachineSpec,
			requestedNetworks,
			c.Storage,
		)
		if params.IsCodeNotImplemented(err) {
			return notSupported
		}
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	err = client.ServiceDeployWithNetworks(
		curl.String(),
		serviceName,
		numUnits,
		string(configYAML),
		c.Constraints,
		c.ToMachineSpec,
		requestedNetworks,
	)
	if params.IsCodeNotImplemented(err) {
		if haveNetworks {
			return errors.New("cannot use --networks/--constraints networks=...: not supported by the API server")
		}
		err = client.ServiceDeploy(
			curl.String(),
			serviceName,
			numUnits,
			string(configYAML),
			c.Constraints,
			c.ToMachineSpec)
	}

	if err != nil {
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	state, err := c.NewAPIRoot()
	if err != nil {
		return err
	}
	err = registerMeteredCharm(c.RegisterURL, state, csClient.jar, curl.String(), serviceName, client.EnvironmentUUID())
	if err != nil {
		return err
	}

	return block.ProcessBlockedError(err, block.BlockChange)
}
Beispiel #11
0
// DeployService takes a charm and various parameters and deploys it.
func DeployService(st *state.State, args DeployServiceParams) (*state.Service, error) {
	if args.NumUnits > 1 && args.ToMachineSpec != "" {
		return nil, fmt.Errorf("cannot use --num-units with --to")
	}
	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
	if err != nil {
		return nil, err
	}
	if args.Charm.Meta().Subordinate {
		if args.NumUnits != 0 || args.ToMachineSpec != "" {
			return nil, fmt.Errorf("subordinate service must be deployed without units")
		}
		if !constraints.IsEmpty(&args.Constraints) {
			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
		}
	}
	if args.ServiceOwner == "" {
		args.ServiceOwner = "user-admin"
	}
	// TODO(fwereade): transactional State.AddService including settings, constraints
	// (minimumUnitCount, initialMachineIds?).
	if len(args.Networks) > 0 || args.Constraints.HaveNetworks() {
		conf, err := st.EnvironConfig()
		if err != nil {
			return nil, err
		}
		env, err := environs.New(conf)
		if err != nil {
			return nil, err
		}
		if !env.SupportNetworks() {
			return nil, fmt.Errorf("cannot deploy with networks: not suppored by the environment")
		}
	}
	service, err := st.AddService(
		args.ServiceName,
		args.ServiceOwner,
		args.Charm,
		args.Networks,
	)
	if err != nil {
		return nil, err
	}
	if len(settings) > 0 {
		if err := service.UpdateConfigSettings(settings); err != nil {
			return nil, err
		}
	}
	if args.Charm.Meta().Subordinate {
		return service, nil
	}
	if !constraints.IsEmpty(&args.Constraints) {
		if err := service.SetConstraints(args.Constraints); err != nil {
			return nil, err
		}
	}
	if args.NumUnits > 0 {
		if _, err := AddUnits(st, service, args.NumUnits, args.ToMachineSpec); err != nil {
			return nil, err
		}
	}
	return service, nil
}
Beispiel #12
0
// DeployService takes a charm and various parameters and deploys it.
func DeployService(st *state.State, args DeployServiceParams) (*state.Service, error) {
	if args.NumUnits > 1 && len(args.Placement) == 0 && args.ToMachineSpec != "" {
		return nil, fmt.Errorf("cannot use --num-units with --to")
	}
	settings, err := args.Charm.Config().ValidateSettings(args.ConfigSettings)
	if err != nil {
		return nil, err
	}
	if args.Charm.Meta().Subordinate {
		if args.NumUnits != 0 || args.ToMachineSpec != "" {
			return nil, fmt.Errorf("subordinate service must be deployed without units")
		}
		if !constraints.IsEmpty(&args.Constraints) {
			return nil, fmt.Errorf("subordinate service must be deployed without constraints")
		}
	}
	if args.ServiceOwner == "" {
		env, err := st.Environment()
		if err != nil {
			return nil, errors.Trace(err)
		}
		args.ServiceOwner = env.Owner().String()
	}
	// TODO(fwereade): transactional State.AddService including settings, constraints
	// (minimumUnitCount, initialMachineIds?).
	if len(args.Networks) > 0 || args.Constraints.HaveNetworks() {
		conf, err := st.EnvironConfig()
		if err != nil {
			return nil, err
		}
		env, err := environs.New(conf)
		if err != nil {
			return nil, err
		}
		if _, ok := environs.SupportsNetworking(env); !ok {
			return nil, fmt.Errorf("cannot deploy with networks: not suppored by the environment")
		}
	}
	service, err := st.AddService(
		args.ServiceName,
		args.ServiceOwner,
		args.Charm,
		args.Networks,
		stateStorageConstraints(args.Storage),
	)
	if err != nil {
		return nil, err
	}
	if len(settings) > 0 {
		if err := service.UpdateConfigSettings(settings); err != nil {
			return nil, err
		}
	}
	if args.Charm.Meta().Subordinate {
		return service, nil
	}
	if !constraints.IsEmpty(&args.Constraints) {
		if err := service.SetConstraints(args.Constraints); err != nil {
			return nil, err
		}
	}
	if args.NumUnits > 0 {
		var err error
		// We either have a machine spec or a placement directive.
		// Placement directives take precedence.
		if len(args.Placement) > 0 || args.ToMachineSpec == "" {
			_, err = AddUnitsWithPlacement(st, service, args.NumUnits, args.Placement)
		} else {
			_, err = AddUnits(st, service, args.NumUnits, args.ToMachineSpec)
		}
		if err != nil {
			return nil, err
		}
	}
	return service, nil
}
Beispiel #13
0
func (c *deployCommand) Run(ctx *cmd.Context) error {
	client, err := c.NewAPIClient()
	if err != nil {
		return err
	}
	defer client.Close()

	conf, err := service.GetClientConfig(client)
	if err != nil {
		return err
	}

	if err := c.CheckProvider(conf); err != nil {
		return err
	}

	httpClient, err := c.HTTPClient()
	if err != nil {
		return errors.Trace(err)
	}
	csClient := newCharmStoreClient(httpClient)
	repoPath := ctx.AbsPath(c.RepoPath)

	// Handle local bundle paths.
	f, err := os.Open(c.CharmOrBundle)
	if err == nil {
		defer f.Close()
		info, err := f.Stat()
		if err != nil {
			return block.ProcessBlockedError(err, block.BlockChange)
		}
		if info.IsDir() {
			return errors.New("deployment of bundle directories not yet supported")
		}
		bundleData, err := charm.ReadBundleData(f)
		if err != nil {
			return block.ProcessBlockedError(err, block.BlockChange)
		}
		if err := deployBundle(bundleData, client, csClient, repoPath, conf, ctx); err != nil {
			return block.ProcessBlockedError(err, block.BlockChange)
		}
		ctx.Infof("deployment of bundle %q completed", f.Name())
		return nil
	} else if !os.IsNotExist(err) {
		logger.Warningf("cannot open %q: %v; falling back to using charm repository", c.CharmOrBundle, err)
	}

	curl, repo, err := resolveCharmStoreEntityURL(c.CharmOrBundle, csClient.params, repoPath, conf)
	if err != nil {
		return errors.Trace(err)
	}

	// Handle bundle URLs.
	if curl.Series == "bundle" {
		// Deploy a bundle entity.
		bundle, err := repo.GetBundle(curl)
		if err != nil {
			return block.ProcessBlockedError(err, block.BlockChange)
		}
		if err := deployBundle(bundle.Data(), client, csClient, repoPath, conf, ctx); err != nil {
			return block.ProcessBlockedError(err, block.BlockChange)
		}
		ctx.Infof("deployment of bundle %q completed", curl)
		return nil
	}

	curl, err = addCharmViaAPI(client, curl, repo, csClient)
	if err != nil {
		return block.ProcessBlockedError(err, block.BlockChange)
	}
	ctx.Infof("Added charm %q to the environment.", curl)

	if c.BumpRevision {
		ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
	}

	charmInfo, err := client.CharmInfo(curl.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate service")
		}
		if numUnits == 1 && c.PlacementSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate service")
		}
	}
	serviceName := c.ServiceName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(ctx)
		if err != nil {
			return err
		}
	}

	// If storage or placement is specified, we attempt to use a new API on the service facade.
	if len(c.Storage) > 0 || len(c.Placement) > 0 {
		notSupported := errors.New("cannot deploy charms with storage or placement: not supported by the API server")
		serviceClient, err := c.newServiceAPIClient()
		if err != nil {
			return notSupported
		}
		defer serviceClient.Close()
		for i, p := range c.Placement {
			if p.Scope == "env-uuid" {
				p.Scope = serviceClient.EnvironmentUUID()
			}
			c.Placement[i] = p
		}
		err = serviceClient.ServiceDeploy(
			curl.String(),
			serviceName,
			numUnits,
			string(configYAML),
			c.Constraints,
			c.PlacementSpec,
			c.Placement,
			[]string{},
			c.Storage,
		)
		if params.IsCodeNotImplemented(err) {
			return notSupported
		}
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	if len(c.Networks) > 0 {
		ctx.Infof("use of --networks is deprecated and is ignored. Please use spaces to manage placement within networks")
	}

	err = client.ServiceDeploy(
		curl.String(),
		serviceName,
		numUnits,
		string(configYAML),
		c.Constraints,
		c.PlacementSpec)

	if err != nil {
		return block.ProcessBlockedError(err, block.BlockChange)
	}

	state, err := c.NewAPIRoot()
	if err != nil {
		return err
	}
	err = registerMeteredCharm(c.RegisterURL, state, httpClient, curl.String(), serviceName, client.EnvironmentUUID())
	if params.IsCodeNotImplemented(err) {
		// The state server is too old to support metering.  Warn
		// the user, but don't return an error.
		logger.Warningf("current state server version does not support charm metering")
		return nil
	}

	return block.ProcessBlockedError(err, block.BlockChange)
}
Beispiel #14
0
func (c *DeployCommand) deployCharm(args deployCharmArgs) (rErr error) {
	charmInfo, err := args.client.CharmInfo(args.id.URL.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate service")
		}
		if numUnits == 1 && c.PlacementSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate service")
		}
	}
	serviceName := c.ServiceName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(args.ctx)
		if err != nil {
			return err
		}
	}

	state, err := c.NewAPIRoot()
	if err != nil {
		return errors.Trace(err)
	}
	bakeryClient, err := c.BakeryClient()
	if err != nil {
		return errors.Trace(err)
	}

	deployInfo := DeploymentInfo{
		CharmID:     args.id,
		ServiceName: serviceName,
		ModelUUID:   args.client.ModelUUID(),
	}

	for _, step := range c.Steps {
		err = step.RunPre(state, bakeryClient, args.ctx, deployInfo)
		if err != nil {
			return err
		}
	}

	defer func() {
		for _, step := range c.Steps {
			err = step.RunPost(state, bakeryClient, args.ctx, deployInfo, rErr)
			if err != nil {
				rErr = err
			}
		}
	}()

	if args.id.URL != nil && args.id.URL.Schema != "local" && len(charmInfo.Meta.Terms) > 0 {
		args.ctx.Infof("Deployment under prior agreement to terms: %s",
			strings.Join(charmInfo.Meta.Terms, " "))
	}

	ids, err := handleResources(c, c.Resources, serviceName, args.id, args.csMac, charmInfo.Meta.Resources)
	if err != nil {
		return errors.Trace(err)
	}

	params := serviceDeployParams{
		charmID:       args.id,
		serviceName:   serviceName,
		series:        args.series,
		numUnits:      numUnits,
		configYAML:    string(configYAML),
		constraints:   c.Constraints,
		placement:     c.Placement,
		storage:       c.Storage,
		spaceBindings: c.Bindings,
		resources:     ids,
	}
	return args.deployer.serviceDeploy(params)
}
Beispiel #15
0
func (c *DeployCommand) Run(ctx *cmd.Context) error {
	client, err := juju.NewAPIClientFromName(c.EnvName)
	if err != nil {
		return err
	}
	defer client.Close()

	attrs, err := client.EnvironmentGet()
	if err != nil {
		return err
	}
	conf, err := config.New(config.NoDefaults, attrs)
	if err != nil {
		return err
	}

	curl, err := resolveCharmURL(c.CharmName, client, conf)
	if err != nil {
		return err
	}

	repo, err := charm.InferRepository(curl.Reference, ctx.AbsPath(c.RepoPath))
	if err != nil {
		return err
	}

	repo = config.SpecializeCharmRepo(repo, conf)

	curl, err = addCharmViaAPI(client, ctx, curl, repo)
	if err != nil {
		return err
	}

	if c.BumpRevision {
		ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
	}

	requestedNetworks, err := networkNamesToTags(parseNetworks(c.Networks))
	if err != nil {
		return err
	}
	// We need to ensure network names are valid below, but we don't need them here.
	_, err = networkNamesToTags(c.Constraints.IncludeNetworks())
	if err != nil {
		return err
	}
	_, err = networkNamesToTags(c.Constraints.ExcludeNetworks())
	if err != nil {
		return err
	}
	haveNetworks := len(requestedNetworks) > 0 || c.Constraints.HaveNetworks()

	charmInfo, err := client.CharmInfo(curl.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate service")
		}
		if numUnits == 1 && c.ToMachineSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate service")
		}
	}
	serviceName := c.ServiceName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(ctx)
		if err != nil {
			return err
		}
	}
	err = client.ServiceDeployWithNetworks(
		curl.String(),
		serviceName,
		numUnits,
		string(configYAML),
		c.Constraints,
		c.ToMachineSpec,
		requestedNetworks,
	)
	if params.IsCodeNotImplemented(err) {
		if haveNetworks {
			return errors.New("cannot use --networks/--constraints networks=...: not supported by the API server")
		}
		err = client.ServiceDeploy(
			curl.String(),
			serviceName,
			numUnits,
			string(configYAML),
			c.Constraints,
			c.ToMachineSpec)
	}
	return err
}
Beispiel #16
0
func (c *DeployCommand) deployCharm(
	id charmstore.CharmID,
	csMac *macaroon.Macaroon,
	series string,
	ctx *cmd.Context,
	apiRoot DeployAPI,
) (rErr error) {
	charmInfo, err := apiRoot.CharmInfo(id.URL.String())
	if err != nil {
		return err
	}

	numUnits := c.NumUnits
	if charmInfo.Meta.Subordinate {
		if !constraints.IsEmpty(&c.Constraints) {
			return errors.New("cannot use --constraints with subordinate application")
		}
		if numUnits == 1 && c.PlacementSpec == "" {
			numUnits = 0
		} else {
			return errors.New("cannot use --num-units or --to with subordinate application")
		}
	}
	serviceName := c.ApplicationName
	if serviceName == "" {
		serviceName = charmInfo.Meta.Name
	}

	var configYAML []byte
	if c.Config.Path != "" {
		configYAML, err = c.Config.Read(ctx)
		if err != nil {
			return errors.Trace(err)
		}
	}

	bakeryClient, err := c.BakeryClient()
	if err != nil {
		return errors.Trace(err)
	}

	uuid, ok := apiRoot.ModelUUID()
	if !ok {
		return errors.New("API connection is controller-only (should never happen)")
	}

	deployInfo := DeploymentInfo{
		CharmID:         id,
		ApplicationName: serviceName,
		ModelUUID:       uuid,
		CharmInfo:       charmInfo,
	}

	for _, step := range c.Steps {
		err = step.RunPre(apiRoot, bakeryClient, ctx, deployInfo)
		if err != nil {
			return errors.Trace(err)
		}
	}

	defer func() {
		for _, step := range c.Steps {
			err = errors.Trace(step.RunPost(apiRoot, bakeryClient, ctx, deployInfo, rErr))
			if err != nil {
				rErr = err
			}
		}
	}()

	if id.URL != nil && id.URL.Schema != "local" && len(charmInfo.Meta.Terms) > 0 {
		ctx.Infof("Deployment under prior agreement to terms: %s",
			strings.Join(charmInfo.Meta.Terms, " "))
	}

	ids, err := resourceadapters.DeployResources(
		serviceName,
		id,
		csMac,
		c.Resources,
		charmInfo.Meta.Resources,
		apiRoot,
	)
	if err != nil {
		return errors.Trace(err)
	}

	return errors.Trace(apiRoot.Deploy(application.DeployArgs{
		CharmID:          id,
		Cons:             c.Constraints,
		ApplicationName:  serviceName,
		Series:           series,
		NumUnits:         numUnits,
		ConfigYAML:       string(configYAML),
		Placement:        c.Placement,
		Storage:          c.Storage,
		Resources:        ids,
		EndpointBindings: c.Bindings,
	}))
}