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 }