github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/application/deploy.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	"gopkg.in/juju/charm.v6"
    14  	csparams "gopkg.in/juju/charmrepo.v3/csclient/params"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/core/application"
    18  	"github.com/juju/juju/core/constraints"
    19  	"github.com/juju/juju/core/devices"
    20  	"github.com/juju/juju/core/instance"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/storage"
    23  )
    24  
    25  // DeployApplicationParams contains the arguments required to deploy the referenced
    26  // charm.
    27  type DeployApplicationParams struct {
    28  	ApplicationName   string
    29  	Series            string
    30  	Charm             *state.Charm
    31  	Channel           csparams.Channel
    32  	ApplicationConfig *application.Config
    33  	CharmConfig       charm.Settings
    34  	Constraints       constraints.Value
    35  	NumUnits          int
    36  	// Placement is a list of placement directives which may be used
    37  	// instead of a machine spec.
    38  	Placement        []*instance.Placement
    39  	Storage          map[string]storage.Constraints
    40  	Devices          map[string]devices.Constraints
    41  	AttachStorage    []names.StorageTag
    42  	EndpointBindings map[string]string
    43  	// Resources is a map of resource name to IDs of pending resources.
    44  	Resources map[string]string
    45  }
    46  
    47  type ApplicationDeployer interface {
    48  	AddApplication(state.AddApplicationArgs) (Application, error)
    49  }
    50  
    51  type UnitAdder interface {
    52  	AddUnit(state.AddUnitParams) (Unit, error)
    53  }
    54  
    55  // DeployApplication takes a charm and various parameters and deploys it.
    56  func DeployApplication(st ApplicationDeployer, args DeployApplicationParams) (Application, error) {
    57  	charmConfig, err := args.Charm.Config().ValidateSettings(args.CharmConfig)
    58  	if err != nil {
    59  		return nil, errors.Trace(err)
    60  	}
    61  	if args.Charm.Meta().Subordinate {
    62  		if args.NumUnits != 0 {
    63  			return nil, fmt.Errorf("subordinate application must be deployed without units")
    64  		}
    65  		if !constraints.IsEmpty(&args.Constraints) {
    66  			return nil, fmt.Errorf("subordinate application must be deployed without constraints")
    67  		}
    68  	}
    69  	// TODO(fwereade): transactional State.AddApplication including settings, constraints
    70  	// (minimumUnitCount, initialMachineIds?).
    71  
    72  	effectiveBindings, err := getEffectiveBindingsForCharmMeta(args.Charm.Meta(), args.EndpointBindings)
    73  	if err != nil {
    74  		return nil, errors.Trace(err)
    75  	}
    76  
    77  	asa := state.AddApplicationArgs{
    78  		Name:              args.ApplicationName,
    79  		Series:            args.Series,
    80  		Charm:             args.Charm,
    81  		Channel:           args.Channel,
    82  		Storage:           stateStorageConstraints(args.Storage),
    83  		Devices:           stateDeviceConstraints(args.Devices),
    84  		AttachStorage:     args.AttachStorage,
    85  		ApplicationConfig: args.ApplicationConfig,
    86  		CharmConfig:       charmConfig,
    87  		NumUnits:          args.NumUnits,
    88  		Placement:         args.Placement,
    89  		Resources:         args.Resources,
    90  		EndpointBindings:  effectiveBindings,
    91  	}
    92  
    93  	if !args.Charm.Meta().Subordinate {
    94  		asa.Constraints = args.Constraints
    95  	}
    96  	return st.AddApplication(asa)
    97  }
    98  
    99  func quoteStrings(vals []string) string {
   100  	out := make([]string, len(vals))
   101  	for i, val := range vals {
   102  		out[i] = strconv.Quote(val)
   103  	}
   104  	return strings.Join(out, ", ")
   105  }
   106  
   107  func validateGivenBindings(givenBindings map[string]string, defaultBindings map[string]string) error {
   108  	invalidBindings := make([]string, 0)
   109  	for name := range givenBindings {
   110  		if name == "" {
   111  			continue
   112  		}
   113  		if _, ok := defaultBindings[name]; !ok {
   114  			invalidBindings = append(invalidBindings, name)
   115  		}
   116  	}
   117  	if len(invalidBindings) == 0 {
   118  		return nil
   119  	}
   120  	possibleBindings := make([]string, 0)
   121  	for name := range defaultBindings {
   122  		if name == "" {
   123  			continue
   124  		}
   125  		possibleBindings = append(possibleBindings, name)
   126  	}
   127  	sort.Strings(invalidBindings)
   128  	sort.Strings(possibleBindings)
   129  	return errors.Errorf("invalid binding(s) supplied %s, valid binding names are %s",
   130  		quoteStrings(invalidBindings), quoteStrings(possibleBindings))
   131  }
   132  
   133  func getEffectiveBindingsForCharmMeta(charmMeta *charm.Meta, givenBindings map[string]string) (map[string]string, error) {
   134  	// defaultBindings contains all bindable endpoints for charmMeta as keys and
   135  	// empty space names as values, so we use defaultBindings as fallback.
   136  	defaultBindings := state.DefaultEndpointBindingsForCharm(charmMeta)
   137  	if givenBindings == nil {
   138  		givenBindings = make(map[string]string, len(defaultBindings))
   139  	}
   140  	if err := validateGivenBindings(givenBindings, defaultBindings); err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	// Get the application-level default binding for all unspecified endpoints, if
   145  	// set. Otherwise use the empty default.
   146  	applicationDefaultSpace, defaultSupplied := givenBindings[""]
   147  	if defaultSupplied {
   148  		// Record that a default binding was requested
   149  		defaultBindings[""] = applicationDefaultSpace
   150  	}
   151  
   152  	effectiveBindings := make(map[string]string, len(defaultBindings))
   153  	for endpoint := range defaultBindings {
   154  		if givenSpace, isGiven := givenBindings[endpoint]; isGiven {
   155  			effectiveBindings[endpoint] = givenSpace
   156  		} else {
   157  			effectiveBindings[endpoint] = applicationDefaultSpace
   158  		}
   159  	}
   160  	return effectiveBindings, nil
   161  }
   162  
   163  // addUnits starts n units of the given application using the specified placement
   164  // directives to allocate the machines.
   165  func addUnits(
   166  	unitAdder UnitAdder,
   167  	appName string,
   168  	n int,
   169  	placement []*instance.Placement,
   170  	attachStorage []names.StorageTag,
   171  	assignUnits bool,
   172  ) ([]Unit, error) {
   173  	units := make([]Unit, n)
   174  	// Hard code for now till we implement a different approach.
   175  	policy := state.AssignCleanEmpty
   176  	// TODO what do we do if we fail half-way through this process?
   177  	for i := 0; i < n; i++ {
   178  		unit, err := unitAdder.AddUnit(state.AddUnitParams{
   179  			AttachStorage: attachStorage,
   180  		})
   181  		if err != nil {
   182  			return nil, errors.Annotatef(err, "cannot add unit %d/%d to application %q", i+1, n, appName)
   183  		}
   184  		units[i] = unit
   185  		if !assignUnits {
   186  			continue
   187  		}
   188  
   189  		// Are there still placement directives to use?
   190  		if i > len(placement)-1 {
   191  			if err := unit.AssignWithPolicy(policy); err != nil {
   192  				return nil, errors.Trace(err)
   193  			}
   194  			continue
   195  		}
   196  		if err := unit.AssignWithPlacement(placement[i]); err != nil {
   197  			return nil, errors.Annotatef(err, "acquiring machine to host unit %q", unit.UnitTag().Id())
   198  		}
   199  	}
   200  	return units, nil
   201  }
   202  
   203  func stateStorageConstraints(cons map[string]storage.Constraints) map[string]state.StorageConstraints {
   204  	result := make(map[string]state.StorageConstraints)
   205  	for name, cons := range cons {
   206  		result[name] = state.StorageConstraints{
   207  			Pool:  cons.Pool,
   208  			Size:  cons.Size,
   209  			Count: cons.Count,
   210  		}
   211  	}
   212  	return result
   213  }
   214  
   215  func stateDeviceConstraints(cons map[string]devices.Constraints) map[string]state.DeviceConstraints {
   216  	result := make(map[string]state.DeviceConstraints)
   217  	for name, cons := range cons {
   218  		result[name] = state.DeviceConstraints{
   219  			Type:       state.DeviceType(cons.Type),
   220  			Count:      cons.Count,
   221  			Attributes: cons.Attributes,
   222  		}
   223  	}
   224  	return result
   225  }