github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/application/bundle.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package application 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/juju/bundlechanges" 15 "github.com/juju/errors" 16 "gopkg.in/juju/charm.v6-unstable" 17 "gopkg.in/juju/charmrepo.v2-unstable" 18 csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" 19 "gopkg.in/juju/names.v2" 20 "gopkg.in/macaroon.v1" 21 "gopkg.in/yaml.v1" 22 23 "github.com/juju/juju/api" 24 "github.com/juju/juju/api/application" 25 "github.com/juju/juju/api/charms" 26 "github.com/juju/juju/apiserver/params" 27 "github.com/juju/juju/charmstore" 28 "github.com/juju/juju/constraints" 29 "github.com/juju/juju/instance" 30 "github.com/juju/juju/resource/resourceadapters" 31 "github.com/juju/juju/state/multiwatcher" 32 "github.com/juju/juju/state/watcher" 33 "github.com/juju/juju/storage" 34 ) 35 36 var watchAll = func(c *api.Client) (allWatcher, error) { 37 return c.WatchAll() 38 } 39 40 type allWatcher interface { 41 Next() ([]multiwatcher.Delta, error) 42 Stop() error 43 } 44 45 // deploymentLogger is used to notify clients about the bundle deployment 46 // progress. 47 type deploymentLogger interface { 48 // Infof formats and logs the given message. 49 Infof(string, ...interface{}) 50 } 51 52 // deployBundle deploys the given bundle data using the given API client and 53 // charm store client. The deployment is not transactional, and its progress is 54 // notified using the given deployment logger. 55 func deployBundle( 56 bundleFilePath string, 57 data *charm.BundleData, 58 channel csparams.Channel, 59 apiRoot DeployAPI, 60 log deploymentLogger, 61 bundleStorage map[string]map[string]storage.Constraints, 62 ) (map[*charm.URL]*macaroon.Macaroon, error) { 63 verifyConstraints := func(s string) error { 64 _, err := constraints.Parse(s) 65 return err 66 } 67 verifyStorage := func(s string) error { 68 _, err := storage.ParseConstraints(s) 69 return err 70 } 71 var verifyError error 72 if bundleFilePath == "" { 73 verifyError = data.Verify(verifyConstraints, verifyStorage) 74 } else { 75 verifyError = data.VerifyLocal(bundleFilePath, verifyConstraints, verifyStorage) 76 } 77 if verifyError != nil { 78 if verr, ok := verifyError.(*charm.VerificationError); ok { 79 errs := make([]string, len(verr.Errors)) 80 for i, err := range verr.Errors { 81 errs[i] = err.Error() 82 } 83 return nil, errors.New("the provided bundle has the following errors:\n" + strings.Join(errs, "\n")) 84 } 85 return nil, errors.Annotate(verifyError, "cannot deploy bundle") 86 } 87 88 // Retrieve bundle changes. 89 changes := bundlechanges.FromData(data) 90 numChanges := len(changes) 91 92 // Initialize the unit status. 93 status, err := apiRoot.Status(nil) 94 if err != nil { 95 return nil, errors.Annotate(err, "cannot get model status") 96 } 97 unitStatus := make(map[string]string, numChanges) 98 for _, serviceData := range status.Applications { 99 for unit, unitData := range serviceData.Units { 100 unitStatus[unit] = unitData.Machine 101 } 102 } 103 104 // Instantiate a watcher used to follow the deployment progress. 105 watcher, err := apiRoot.WatchAll() 106 if err != nil { 107 return nil, errors.Annotate(err, "cannot watch model") 108 } 109 defer watcher.Stop() 110 111 // Instantiate the bundle handler. 112 h := &bundleHandler{ 113 bundleDir: bundleFilePath, 114 changes: changes, 115 results: make(map[string]string, numChanges), 116 channel: channel, 117 api: apiRoot, 118 bundleStorage: bundleStorage, 119 log: log, 120 data: data, 121 unitStatus: unitStatus, 122 ignoredMachines: make(map[string]bool, len(data.Applications)), 123 ignoredUnits: make(map[string]bool, len(data.Applications)), 124 watcher: watcher, 125 } 126 127 // Deploy the bundle. 128 csMacs := make(map[*charm.URL]*macaroon.Macaroon) 129 channels := make(map[*charm.URL]csparams.Channel) 130 for _, change := range changes { 131 switch change := change.(type) { 132 case *bundlechanges.AddCharmChange: 133 cURL, channel, csMac, err2 := h.addCharm(change.Id(), change.Params) 134 if err2 == nil { 135 csMacs[cURL] = csMac 136 channels[cURL] = channel 137 } 138 err = err2 139 case *bundlechanges.AddMachineChange: 140 err = h.addMachine(change.Id(), change.Params) 141 case *bundlechanges.AddRelationChange: 142 err = h.addRelation(change.Id(), change.Params) 143 case *bundlechanges.AddApplicationChange: 144 var cURL *charm.URL 145 cURL, err = charm.ParseURL(resolve(change.Params.Charm, h.results)) 146 if err == nil { 147 chID := charmstore.CharmID{ 148 URL: cURL, 149 Channel: channels[cURL], 150 } 151 csMac := csMacs[cURL] 152 err = h.addService(apiRoot, change.Id(), change.Params, chID, csMac) 153 } 154 case *bundlechanges.AddUnitChange: 155 err = h.addUnit(change.Id(), change.Params) 156 case *bundlechanges.ExposeChange: 157 err = h.exposeService(change.Id(), change.Params) 158 case *bundlechanges.SetAnnotationsChange: 159 err = h.setAnnotations(change.Id(), change.Params) 160 default: 161 return nil, errors.Errorf("unknown change type: %T", change) 162 } 163 if err != nil { 164 return nil, errors.Annotate(err, "cannot deploy bundle") 165 } 166 } 167 return csMacs, nil 168 } 169 170 // bundleHandler provides helpers and the state required to deploy a bundle. 171 type bundleHandler struct { 172 // bundleDir is the path where the bundle file is located for local bundles. 173 bundleDir string 174 // changes holds the changes to be applied in order to deploy the bundle. 175 changes []bundlechanges.Change 176 177 // results collects data resulting from applying changes. Keys identify 178 // changes, values result from interacting with the environment, and are 179 // stored so that they can be potentially reused later, for instance for 180 // resolving a dynamic placeholder included in a change. Specifically, the 181 // following values are stored: 182 // - when adding a charm, the fully resolved charm is stored; 183 // - when deploying an application, the application name is stored; 184 // - when adding a machine, the resulting machine id is stored; 185 // - when adding a unit, either the id of the machine holding the unit or 186 // the unit name can be stored. The latter happens when a machine is 187 // implicitly created by adding a unit without a machine spec. 188 results map[string]string 189 190 // channel identifies the default channel to use for the bundle. 191 channel csparams.Channel 192 193 // api is used to interact with the environment. 194 api DeployAPI 195 196 // bundleStorage contains a mapping of application-specific storage 197 // constraints. For each application, the storage constraints in the 198 // map will replace or augment the storage constraints specified 199 // in the bundle itself. 200 bundleStorage map[string]map[string]storage.Constraints 201 202 // log is used to output messages to the user, so that the user can keep 203 // track of the bundle deployment progress. 204 log deploymentLogger 205 206 // data is the original bundle data that we want to deploy. 207 data *charm.BundleData 208 209 // unitStatus reflects the environment status and maps unit names to their 210 // corresponding machine identifiers. This is kept updated by both change 211 // handlers (addCharm, addService etc.) and by updateUnitStatus. 212 unitStatus map[string]string 213 214 // ignoredMachines and ignoredUnits map application names to whether a machine 215 // or a unit creation has been skipped during the bundle deployment because 216 // the current status of the environment does not require them to be added. 217 ignoredMachines map[string]bool 218 ignoredUnits map[string]bool 219 220 // watcher holds an environment mega-watcher used to keep the environment 221 // status up to date. 222 watcher allWatcher 223 224 // warnedLXC indicates whether or not we have warned the user that the 225 // bundle they're deploying uses lxc containers, which will be treated as 226 // LXD. This flag keeps us from writing the warning more than once per 227 // bundle. 228 warnedLXC bool 229 } 230 231 // addCharm adds a charm to the environment. 232 func (h *bundleHandler) addCharm(id string, p bundlechanges.AddCharmParams) (*charm.URL, csparams.Channel, *macaroon.Macaroon, error) { 233 // First attempt to interpret as a local path. 234 if strings.HasPrefix(p.Charm, ".") || filepath.IsAbs(p.Charm) { 235 charmPath := p.Charm 236 if !filepath.IsAbs(charmPath) { 237 charmPath = filepath.Join(h.bundleDir, charmPath) 238 } 239 240 var noChannel csparams.Channel 241 series := p.Series 242 if series == "" { 243 series = h.data.Series 244 } 245 ch, curl, err := charmrepo.NewCharmAtPath(charmPath, series) 246 if err != nil && !os.IsNotExist(err) { 247 return nil, noChannel, nil, errors.Annotatef(err, "cannot deploy local charm at %q", charmPath) 248 } 249 if err == nil { 250 if curl, err = h.api.AddLocalCharm(curl, ch); err != nil { 251 return nil, noChannel, nil, err 252 } 253 logger.Debugf("added charm %s", curl) 254 h.results[id] = curl.String() 255 return curl, noChannel, nil, nil 256 } 257 } 258 259 // Not a local charm, so grab from the store. 260 ch, err := charm.ParseURL(p.Charm) 261 if err != nil { 262 return nil, "", nil, errors.Trace(err) 263 } 264 modelCfg, err := getModelConfig(h.api) 265 if err != nil { 266 return nil, "", nil, errors.Trace(err) 267 } 268 269 url, channel, _, err := h.api.Resolve(modelCfg, ch) 270 if err != nil { 271 return nil, channel, nil, errors.Annotatef(err, "cannot resolve URL %q", p.Charm) 272 } 273 if url.Series == "bundle" { 274 return nil, channel, nil, errors.Errorf("expected charm URL, got bundle URL %q", p.Charm) 275 } 276 var csMac *macaroon.Macaroon 277 url, csMac, err = addCharmFromURL(h.api, url, channel) 278 if err != nil { 279 return nil, channel, nil, errors.Annotatef(err, "cannot add charm %q", p.Charm) 280 } 281 logger.Debugf("added charm %s", url) 282 h.results[id] = url.String() 283 return url, channel, csMac, nil 284 } 285 286 // addService deploys or update an application with no units. Service options are 287 // also set or updated. 288 func (h *bundleHandler) addService( 289 api DeployAPI, 290 id string, 291 p bundlechanges.AddApplicationParams, 292 chID charmstore.CharmID, 293 csMac *macaroon.Macaroon, 294 ) error { 295 h.results[id] = p.Application 296 ch := chID.URL.String() 297 // Handle application configuration. 298 configYAML := "" 299 if len(p.Options) > 0 { 300 config, err := yaml.Marshal(map[string]map[string]interface{}{p.Application: p.Options}) 301 if err != nil { 302 return errors.Annotatef(err, "cannot marshal options for application %q", p.Application) 303 } 304 configYAML = string(config) 305 } 306 // Handle application constraints. 307 cons, err := constraints.Parse(p.Constraints) 308 if err != nil { 309 // This should never happen, as the bundle is already verified. 310 return errors.Annotate(err, "invalid constraints for application") 311 } 312 storageConstraints := h.bundleStorage[p.Application] 313 if len(p.Storage) > 0 { 314 if storageConstraints == nil { 315 storageConstraints = make(map[string]storage.Constraints) 316 } 317 for k, v := range p.Storage { 318 if _, ok := storageConstraints[k]; ok { 319 // Storage constraints overridden 320 // on the command line. 321 continue 322 } 323 cons, err := storage.ParseConstraints(v) 324 if err != nil { 325 return errors.Annotate(err, "invalid storage constraints") 326 } 327 storageConstraints[k] = cons 328 } 329 } 330 resources := make(map[string]string) 331 for resName, revision := range p.Resources { 332 resources[resName] = fmt.Sprint(revision) 333 } 334 charmInfo, err := h.api.CharmInfo(ch) 335 if err != nil { 336 return err 337 } 338 resNames2IDs, err := resourceadapters.DeployResources( 339 p.Application, 340 chID, 341 csMac, 342 resources, 343 charmInfo.Meta.Resources, 344 api, 345 ) 346 if err != nil { 347 return errors.Trace(err) 348 } 349 350 // Figure out what series we need to deploy with. 351 conf, err := getModelConfig(h.api) 352 if err != nil { 353 return err 354 } 355 supportedSeries := charmInfo.Meta.Series 356 if len(supportedSeries) == 0 && chID.URL.Series != "" { 357 supportedSeries = []string{chID.URL.Series} 358 } 359 selector := seriesSelector{ 360 seriesFlag: p.Series, 361 charmURLSeries: chID.URL.Series, 362 supportedSeries: supportedSeries, 363 conf: conf, 364 fromBundle: true, 365 } 366 series, err := selector.charmSeries() 367 if err != nil { 368 return errors.Trace(err) 369 } 370 371 // Deploy the application. 372 logger.Debugf("application %s is deploying (charm %s)", p.Application, ch) 373 h.log.Infof("Deploying charm %q", ch) 374 if err := api.Deploy(application.DeployArgs{ 375 CharmID: chID, 376 Cons: cons, 377 ApplicationName: p.Application, 378 Series: series, 379 ConfigYAML: configYAML, 380 Storage: storageConstraints, 381 Resources: resNames2IDs, 382 EndpointBindings: p.EndpointBindings, 383 }); err == nil { 384 for resName := range resNames2IDs { 385 h.log.Infof("added resource %s", resName) 386 } 387 return nil 388 } else if !isErrServiceExists(err) { 389 return errors.Annotatef(err, "cannot deploy application %q", p.Application) 390 } 391 // The application is already deployed in the environment: check that its 392 // charm is compatible with the one declared in the bundle. If it is, 393 // reuse the existing application or upgrade to a specified revision. 394 // Exit with an error otherwise. 395 if err := h.upgradeCharm(api, p.Application, chID, csMac, resources); err != nil { 396 return errors.Annotatef(err, "cannot upgrade application %q", p.Application) 397 } 398 // Update application configuration. 399 if configYAML != "" { 400 if err := h.api.Update(params.ApplicationUpdate{ 401 ApplicationName: p.Application, 402 SettingsYAML: configYAML, 403 }); err != nil { 404 // This should never happen as possible errors are already returned 405 // by the application Deploy call above. 406 return errors.Annotatef(err, "cannot update options for application %q", p.Application) 407 } 408 h.log.Infof("configuration updated for application %s", p.Application) 409 } 410 // Update application constraints. 411 if p.Constraints != "" { 412 if err := h.api.SetConstraints(p.Application, cons); err != nil { 413 // This should never happen, as the bundle is already verified. 414 return errors.Annotatef(err, "cannot update constraints for application %q", p.Application) 415 } 416 h.log.Infof("constraints applied for application %s", p.Application) 417 } 418 return nil 419 } 420 421 // addMachine creates a new top-level machine or container in the environment. 422 func (h *bundleHandler) addMachine(id string, p bundlechanges.AddMachineParams) error { 423 services := h.servicesForMachineChange(id) 424 // Note that we always have at least one application that justifies the 425 // creation of this machine. 426 msg := services[0] + " unit" 427 svcLen := len(services) 428 if svcLen != 1 { 429 msg = strings.Join(services[:svcLen-1], ", ") + " and " + services[svcLen-1] + " units" 430 } 431 // Check whether the desired number of units already exist in the 432 // environment, in which case avoid adding other machines to host those 433 // application units. 434 machine := h.chooseMachine(services...) 435 if machine != "" { 436 h.results[id] = machine 437 notify := make([]string, 0, svcLen) 438 for _, application := range services { 439 if !h.ignoredMachines[application] { 440 h.ignoredMachines[application] = true 441 notify = append(notify, application) 442 } 443 } 444 svcLen = len(notify) 445 switch svcLen { 446 case 0: 447 return nil 448 case 1: 449 msg = notify[0] 450 default: 451 msg = strings.Join(notify[:svcLen-1], ", ") + " and " + notify[svcLen-1] 452 } 453 h.log.Infof("avoid creating other machines to host %s units", msg) 454 return nil 455 } 456 cons, err := constraints.Parse(p.Constraints) 457 if err != nil { 458 // This should never happen, as the bundle is already verified. 459 return errors.Annotate(err, "invalid constraints for machine") 460 } 461 machineParams := params.AddMachineParams{ 462 Constraints: cons, 463 Series: p.Series, 464 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 465 } 466 if ct := p.ContainerType; ct != "" { 467 // for backwards compatibility with 1.x bundles, we treat lxc 468 // placement directives as lxd. 469 if ct == "lxc" { 470 if !h.warnedLXC { 471 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.") 472 h.warnedLXC = true 473 } 474 ct = string(instance.LXD) 475 } 476 containerType, err := instance.ParseContainerType(ct) 477 if err != nil { 478 return errors.Annotatef(err, "cannot create machine for holding %s", msg) 479 } 480 machineParams.ContainerType = containerType 481 if p.ParentId != "" { 482 machineParams.ParentId, err = h.resolveMachine(p.ParentId) 483 if err != nil { 484 return errors.Annotatef(err, "cannot retrieve parent placement for %s", msg) 485 } 486 } 487 } 488 r, err := h.api.AddMachines([]params.AddMachineParams{machineParams}) 489 if err != nil { 490 return errors.Annotatef(err, "cannot create machine for holding %s", msg) 491 } 492 if r[0].Error != nil { 493 return errors.Annotatef(r[0].Error, "cannot create machine for holding %s", msg) 494 } 495 machine = r[0].Machine 496 if p.ContainerType == "" { 497 logger.Debugf("created new machine %s for holding %s", machine, msg) 498 } else if p.ParentId == "" { 499 logger.Debugf("created %s container in new machine for holding %s", machine, msg) 500 } else { 501 logger.Debugf("created %s container in machine %s for holding %s", machine, machineParams.ParentId, msg) 502 } 503 h.results[id] = machine 504 return nil 505 } 506 507 // addRelation creates a relationship between two services. 508 func (h *bundleHandler) addRelation(id string, p bundlechanges.AddRelationParams) error { 509 ep1 := resolveRelation(p.Endpoint1, h.results) 510 ep2 := resolveRelation(p.Endpoint2, h.results) 511 _, err := h.api.AddRelation(ep1, ep2) 512 if err == nil { 513 // A new relation has been established. 514 h.log.Infof("Related %q and %q", ep1, ep2) 515 return nil 516 } 517 if isErrRelationExists(err) { 518 // The relation is already present in the environment. 519 logger.Debugf("%s and %s are already related", ep1, ep2) 520 return nil 521 } 522 return errors.Annotatef(err, "cannot add relation between %q and %q", ep1, ep2) 523 } 524 525 // addUnit adds a single unit to an application already present in the environment. 526 func (h *bundleHandler) addUnit(id string, p bundlechanges.AddUnitParams) error { 527 application := resolve(p.Application, h.results) 528 // Check whether the desired number of units already exist in the 529 // environment, in which case avoid adding other units. 530 machine := h.chooseMachine(application) 531 if machine != "" { 532 h.results[id] = machine 533 if !h.ignoredUnits[application] { 534 h.ignoredUnits[application] = true 535 num := h.numUnitsForService(application) 536 var msg string 537 if num == 1 { 538 msg = "1 unit already present" 539 } else { 540 msg = fmt.Sprintf("%d units already present", num) 541 } 542 h.log.Infof("avoid adding new units to application %s: %s", application, msg) 543 } 544 return nil 545 } 546 var machineSpec string 547 var placementArg []*instance.Placement 548 if p.To != "" { 549 var err error 550 if machineSpec, err = h.resolveMachine(p.To); err != nil { 551 // Should never happen. 552 return errors.Annotatef(err, "cannot retrieve placement for %q unit", application) 553 } 554 placement, err := parsePlacement(machineSpec) 555 if err != nil { 556 return errors.Errorf("invalid --to parameter %q", machineSpec) 557 } 558 placementArg = append(placementArg, placement) 559 } 560 r, err := h.api.AddUnits(application, 1, placementArg) 561 if err != nil { 562 return errors.Annotatef(err, "cannot add unit for application %q", application) 563 } 564 unit := r[0] 565 if machineSpec == "" { 566 logger.Debugf("added %s unit to new machine", unit) 567 // In this case, the unit name is stored in results instead of the 568 // machine id, which is lazily evaluated later only if required. 569 // This way we avoid waiting for watcher updates. 570 h.results[id] = unit 571 } else { 572 logger.Debugf("added %s unit to new machine", unit) 573 h.results[id] = machineSpec 574 } 575 // Note that the machineSpec can be empty for now, resulting in a partially 576 // incomplete unit status. That's ok as the missing info is provided later 577 // when it is required. 578 h.unitStatus[unit] = machineSpec 579 return nil 580 } 581 582 // exposeService exposes an application. 583 func (h *bundleHandler) exposeService(id string, p bundlechanges.ExposeParams) error { 584 application := resolve(p.Application, h.results) 585 if err := h.api.Expose(application); err != nil { 586 return errors.Annotatef(err, "cannot expose application %s", application) 587 } 588 h.log.Infof("application %s exposed", application) 589 return nil 590 } 591 592 // setAnnotations sets annotations for an application or a machine. 593 func (h *bundleHandler) setAnnotations(id string, p bundlechanges.SetAnnotationsParams) error { 594 eid := resolve(p.Id, h.results) 595 var tag string 596 switch p.EntityType { 597 case bundlechanges.MachineType: 598 tag = names.NewMachineTag(eid).String() 599 case bundlechanges.ApplicationType: 600 tag = names.NewApplicationTag(eid).String() 601 default: 602 return errors.Errorf("unexpected annotation entity type %q", p.EntityType) 603 } 604 result, err := h.api.SetAnnotation(map[string]map[string]string{tag: p.Annotations}) 605 if err == nil && len(result) > 0 { 606 err = result[0].Error 607 } 608 if err != nil { 609 return errors.Annotatef(err, "cannot set annotations for %s %q", p.EntityType, eid) 610 } 611 logger.Debugf("annotations set for %s %s", p.EntityType, eid) 612 return nil 613 } 614 615 // servicesForMachineChange returns the names of the services for which an 616 // "addMachine" change is required, as adding machines is required to place 617 // units, and units belong to services. 618 // Receive the id of the "addMachine" change. 619 func (h *bundleHandler) servicesForMachineChange(changeId string) []string { 620 services := make(map[string]bool, len(h.data.Applications)) 621 mainloop: 622 for _, change := range h.changes { 623 for _, required := range change.Requires() { 624 if required != changeId { 625 continue 626 } 627 switch change := change.(type) { 628 case *bundlechanges.AddMachineChange: 629 // The original machine is a container, and its parent is 630 // another "addMachines" change. Search again using the 631 // parent id. 632 for _, application := range h.servicesForMachineChange(change.Id()) { 633 services[application] = true 634 } 635 continue mainloop 636 case *bundlechanges.AddUnitChange: 637 // We have found the "addUnit" change, which refers to a 638 // application: now resolve the application holding the unit. 639 application := resolve(change.Params.Application, h.results) 640 services[application] = true 641 continue mainloop 642 case *bundlechanges.SetAnnotationsChange: 643 // A machine change is always required to set machine 644 // annotations, but this isn't the interesting change here. 645 continue mainloop 646 default: 647 // Should never happen. 648 panic(fmt.Sprintf("unexpected change %T", change)) 649 } 650 } 651 } 652 results := make([]string, 0, len(services)) 653 for application := range services { 654 results = append(results, application) 655 } 656 sort.Strings(results) 657 return results 658 } 659 660 // chooseMachine returns the id of a machine that will be used to host a unit 661 // of all the given services. If one of the services still requires units to be 662 // added, an empty string is returned, meaning that a new machine must be 663 // created for holding the unit. If instead all units are already placed, 664 // return the id of the machine which already holds units of the given services 665 // and which hosts the least number of units. 666 func (h *bundleHandler) chooseMachine(services ...string) string { 667 candidateMachines := make(map[string]bool, len(h.unitStatus)) 668 numUnitsPerMachine := make(map[string]int, len(h.unitStatus)) 669 numUnitsPerService := make(map[string]int, len(h.data.Applications)) 670 // Collect the number of units and the corresponding machines for all 671 // involved services. 672 for unit, machine := range h.unitStatus { 673 // Retrieve the top level machine. 674 machine = strings.Split(machine, "/")[0] 675 numUnitsPerMachine[machine]++ 676 svc, err := names.UnitApplication(unit) 677 if err != nil { 678 // Should never happen because the bundle logic has already checked 679 // that unit names are well formed. 680 panic(err) 681 } 682 for _, application := range services { 683 if application != svc { 684 continue 685 } 686 numUnitsPerService[application]++ 687 candidateMachines[machine] = true 688 } 689 } 690 // If at least one application still requires units to be added, return an 691 // empty machine in order to force new machine creation. 692 for _, application := range services { 693 if numUnitsPerService[application] < h.data.Applications[application].NumUnits { 694 return "" 695 } 696 } 697 // Return the least used machine. 698 var result string 699 var min int 700 for machine, num := range numUnitsPerMachine { 701 if candidateMachines[machine] && (result == "" || num < min) { 702 result, min = machine, num 703 } 704 } 705 return result 706 } 707 708 // updateUnitStatusPeriod is the time duration used to wait for a mega-watcher 709 // change to be available. 710 var updateUnitStatusPeriod = watcher.Period + 5*time.Second 711 712 // updateUnitStatus uses the mega-watcher to update units and machines info 713 // (h.unitStatus) so that it reflects the current environment status. 714 // This function must be called assuming new delta changes are available or 715 // will be available within the watcher time period. Otherwise, the function 716 // unblocks and an error is returned. 717 func (h *bundleHandler) updateUnitStatus() error { 718 var delta []multiwatcher.Delta 719 var err error 720 ch := make(chan struct{}) 721 go func() { 722 delta, err = h.watcher.Next() 723 close(ch) 724 }() 725 select { 726 case <-ch: 727 if err != nil { 728 return errors.Annotate(err, "cannot update model status") 729 } 730 for _, d := range delta { 731 switch entityInfo := d.Entity.(type) { 732 case *multiwatcher.UnitInfo: 733 h.unitStatus[entityInfo.Name] = entityInfo.MachineId 734 } 735 } 736 case <-time.After(updateUnitStatusPeriod): 737 // TODO(fwereade): 2016-03-17 lp:1558657 738 return errors.New("timeout while trying to get new changes from the watcher") 739 } 740 return nil 741 } 742 743 // numUnitsForService return the number of units belonging to the given application 744 // currently in the environment. 745 func (h *bundleHandler) numUnitsForService(application string) (num int) { 746 for unit := range h.unitStatus { 747 svc, err := names.UnitApplication(unit) 748 if err != nil { 749 // Should never happen. 750 panic(err) 751 } 752 if svc == application { 753 num++ 754 } 755 } 756 return num 757 } 758 759 // resolveMachine returns the machine id resolving the given unit or machine 760 // placeholder. 761 func (h *bundleHandler) resolveMachine(placeholder string) (string, error) { 762 machineOrUnit := resolve(placeholder, h.results) 763 if !names.IsValidUnit(machineOrUnit) { 764 return machineOrUnit, nil 765 } 766 for h.unitStatus[machineOrUnit] == "" { 767 if err := h.updateUnitStatus(); err != nil { 768 return "", errors.Annotate(err, "cannot resolve machine") 769 } 770 } 771 return h.unitStatus[machineOrUnit], nil 772 } 773 774 // resolveRelation returns the relation name resolving the included application 775 // placeholder. 776 func resolveRelation(e string, results map[string]string) string { 777 parts := strings.SplitN(e, ":", 2) 778 application := resolve(parts[0], results) 779 if len(parts) == 1 { 780 return application 781 } 782 return fmt.Sprintf("%s:%s", application, parts[1]) 783 } 784 785 // resolve returns the real entity name for the bundle entity (for instance a 786 // application or a machine) with the given placeholder id. 787 // A placeholder id is a string like "$deploy-42" or "$addCharm-2", indicating 788 // the results of a previously applied change. It always starts with a dollar 789 // sign, followed by the identifier of the referred change. A change id is a 790 // string indicating the action type ("deploy", "addRelation" etc.), followed 791 // by a unique incremental number. 792 func resolve(placeholder string, results map[string]string) string { 793 if !strings.HasPrefix(placeholder, "$") { 794 panic(`placeholder does not start with "$"`) 795 } 796 id := placeholder[1:] 797 return results[id] 798 } 799 800 // upgradeCharm upgrades the charm for the given application to the given charm id. 801 // If the application is already deployed using the given charm id, do nothing. 802 // This function returns an error if the existing charm and the target one are 803 // incompatible, meaning an upgrade from one to the other is not allowed. 804 func (h *bundleHandler) upgradeCharm( 805 api DeployAPI, 806 applicationName string, 807 chID charmstore.CharmID, 808 csMac *macaroon.Macaroon, 809 resources map[string]string, 810 ) error { 811 id := chID.URL.String() 812 existing, err := h.api.GetCharmURL(applicationName) 813 if err != nil { 814 return errors.Annotatef(err, "cannot retrieve info for application %q", applicationName) 815 } 816 if existing.String() == id { 817 h.log.Infof("reusing application %s (charm: %s)", applicationName, id) 818 return nil 819 } 820 url, err := charm.ParseURL(id) 821 if err != nil { 822 return errors.Annotatef(err, "cannot parse charm URL %q", id) 823 } 824 chID.URL = url 825 if url.WithRevision(-1).Path() != existing.WithRevision(-1).Path() { 826 return errors.Errorf("bundle charm %q is incompatible with existing charm %q", id, existing) 827 } 828 charmsClient := charms.NewClient(api) 829 resourceLister, err := resourceadapters.NewAPIClient(api) 830 if err != nil { 831 return errors.Trace(err) 832 } 833 filtered, err := getUpgradeResources(charmsClient, resourceLister, applicationName, url, resources) 834 if err != nil { 835 return errors.Trace(err) 836 } 837 var resNames2IDs map[string]string 838 if len(filtered) != 0 { 839 resNames2IDs, err = resourceadapters.DeployResources( 840 applicationName, 841 chID, 842 csMac, 843 resources, 844 filtered, 845 api, 846 ) 847 if err != nil { 848 return errors.Trace(err) 849 } 850 } 851 cfg := application.SetCharmConfig{ 852 ApplicationName: applicationName, 853 CharmID: chID, 854 ResourceIDs: resNames2IDs, 855 } 856 if err := h.api.SetCharm(cfg); err != nil { 857 return errors.Annotatef(err, "cannot upgrade charm to %q", id) 858 } 859 h.log.Infof("upgraded charm for existing application %s (from %s to %s)", applicationName, existing, id) 860 for resName := range resNames2IDs { 861 h.log.Infof("added resource %s", resName) 862 } 863 return nil 864 } 865 866 // isErrServiceExists reports whether the given error has been generated 867 // from trying to deploy an application that already exists. 868 func isErrServiceExists(err error) bool { 869 // TODO frankban (bug 1495952): do this check using the cause rather than 870 // the string when a specific cause is available. 871 return strings.HasSuffix(err.Error(), "application already exists") 872 } 873 874 // isErrRelationExists reports whether the given error has been generated 875 // from trying to create an already established relation. 876 func isErrRelationExists(err error) bool { 877 // TODO frankban (bug 1495952): do this check using the cause rather than 878 // the string when a specific cause is available. 879 return strings.HasSuffix(err.Error(), "relation already exists") 880 }