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