github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/service/service.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package service contains api calls for functionality 5 // related to deploying and managing services and their 6 // related charms. 7 package service 8 9 import ( 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "gopkg.in/juju/charm.v6-unstable" 13 csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" 14 goyaml "gopkg.in/yaml.v2" 15 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/instance" 19 jjj "github.com/juju/juju/juju" 20 "github.com/juju/juju/state" 21 statestorage "github.com/juju/juju/state/storage" 22 ) 23 24 var ( 25 logger = loggo.GetLogger("juju.apiserver.service") 26 27 newStateStorage = statestorage.NewStorage 28 ) 29 30 func init() { 31 common.RegisterStandardFacade("Service", 3, NewAPI) 32 } 33 34 // Service defines the methods on the service API end point. 35 type Service interface { 36 SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error) 37 } 38 39 // API implements the service interface and is the concrete 40 // implementation of the api end point. 41 type API struct { 42 check *common.BlockChecker 43 state *state.State 44 authorizer common.Authorizer 45 } 46 47 // NewAPI returns a new service API facade. 48 func NewAPI( 49 st *state.State, 50 resources *common.Resources, 51 authorizer common.Authorizer, 52 ) (*API, error) { 53 if !authorizer.AuthClient() { 54 return nil, common.ErrPerm 55 } 56 57 return &API{ 58 state: st, 59 authorizer: authorizer, 60 check: common.NewBlockChecker(st), 61 }, nil 62 } 63 64 // SetMetricCredentials sets credentials on the service. 65 func (api *API) SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error) { 66 result := params.ErrorResults{ 67 Results: make([]params.ErrorResult, len(args.Creds)), 68 } 69 if len(args.Creds) == 0 { 70 return result, nil 71 } 72 for i, a := range args.Creds { 73 service, err := api.state.Service(a.ServiceName) 74 if err != nil { 75 result.Results[i].Error = common.ServerError(err) 76 continue 77 } 78 err = service.SetMetricCredentials(a.MetricCredentials) 79 if err != nil { 80 result.Results[i].Error = common.ServerError(err) 81 } 82 } 83 return result, nil 84 } 85 86 // Deploy fetches the charms from the charm store and deploys them 87 // using the specified placement directives. 88 func (api *API) Deploy(args params.ServicesDeploy) (params.ErrorResults, error) { 89 result := params.ErrorResults{ 90 Results: make([]params.ErrorResult, len(args.Services)), 91 } 92 if err := api.check.ChangeAllowed(); err != nil { 93 return result, errors.Trace(err) 94 } 95 owner := api.authorizer.GetAuthTag().String() 96 for i, arg := range args.Services { 97 err := deployService(api.state, owner, arg) 98 result.Results[i].Error = common.ServerError(err) 99 } 100 return result, nil 101 } 102 103 // DeployService fetches the charm from the charm store and deploys it. 104 // The logic has been factored out into a common function which is called by 105 // both the legacy API on the client facade, as well as the new service facade. 106 func deployService(st *state.State, owner string, args params.ServiceDeploy) error { 107 curl, err := charm.ParseURL(args.CharmUrl) 108 if err != nil { 109 return errors.Trace(err) 110 } 111 if curl.Revision < 0 { 112 return errors.Errorf("charm url must include revision") 113 } 114 115 // Do a quick but not complete validation check before going any further. 116 for _, p := range args.Placement { 117 if p.Scope != instance.MachineScope { 118 continue 119 } 120 _, err = st.Machine(p.Directive) 121 if err != nil { 122 return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, p.Directive) 123 } 124 } 125 126 // Try to find the charm URL in state first. 127 ch, err := st.Charm(curl) 128 // TODO(wallyworld) - remove for 2.0 beta4 129 if errors.IsNotFound(err) { 130 // Clients written to expect 1.16 compatibility require this next block. 131 if curl.Schema != "cs" { 132 return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema) 133 } 134 if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{ 135 URL: args.CharmUrl, 136 }); err == nil { 137 ch, err = st.Charm(curl) 138 } 139 } 140 if err != nil { 141 return errors.Trace(err) 142 } 143 144 if err := checkMinVersion(ch); err != nil { 145 return errors.Trace(err) 146 } 147 148 var settings charm.Settings 149 if len(args.ConfigYAML) > 0 { 150 settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) 151 } else if len(args.Config) > 0 { 152 // Parse config in a compatible way (see function comment). 153 settings, err = parseSettingsCompatible(ch, args.Config) 154 } 155 if err != nil { 156 return errors.Trace(err) 157 } 158 159 channel := csparams.Channel(args.Channel) 160 161 _, err = jjj.DeployService(st, 162 jjj.DeployServiceParams{ 163 ServiceName: args.ServiceName, 164 Series: args.Series, 165 // TODO(dfc) ServiceOwner should be a tag 166 ServiceOwner: owner, 167 Charm: ch, 168 Channel: channel, 169 NumUnits: args.NumUnits, 170 ConfigSettings: settings, 171 Constraints: args.Constraints, 172 Placement: args.Placement, 173 Storage: args.Storage, 174 EndpointBindings: args.EndpointBindings, 175 Resources: args.Resources, 176 }) 177 return errors.Trace(err) 178 } 179 180 // ServiceSetSettingsStrings updates the settings for the given service, 181 // taking the configuration from a map of strings. 182 func ServiceSetSettingsStrings(service *state.Service, settings map[string]string) error { 183 ch, _, err := service.Charm() 184 if err != nil { 185 return errors.Trace(err) 186 } 187 // Parse config in a compatible way (see function comment). 188 changes, err := parseSettingsCompatible(ch, settings) 189 if err != nil { 190 return errors.Trace(err) 191 } 192 return service.UpdateConfigSettings(changes) 193 } 194 195 // parseSettingsCompatible parses setting strings in a way that is 196 // compatible with the behavior before this CL based on the issue 197 // http://pad.lv/1194945. Until then setting an option to an empty 198 // string caused it to reset to the default value. We now allow 199 // empty strings as actual values, but we want to preserve the API 200 // behavior. 201 func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) { 202 setSettings := map[string]string{} 203 unsetSettings := charm.Settings{} 204 // Split settings into those which set and those which unset a value. 205 for name, value := range settings { 206 if value == "" { 207 unsetSettings[name] = nil 208 continue 209 } 210 setSettings[name] = value 211 } 212 // Validate the settings. 213 changes, err := ch.Config().ParseSettingsStrings(setSettings) 214 if err != nil { 215 return nil, err 216 } 217 // Validate the unsettings and merge them into the changes. 218 unsetSettings, err = ch.Config().ValidateSettings(unsetSettings) 219 if err != nil { 220 return nil, err 221 } 222 for name := range unsetSettings { 223 changes[name] = nil 224 } 225 return changes, nil 226 } 227 228 // Update updates the service attributes, including charm URL, 229 // minimum number of units, settings and constraints. 230 // All parameters in params.ServiceUpdate except the service name are optional. 231 func (api *API) Update(args params.ServiceUpdate) error { 232 if !args.ForceCharmUrl { 233 if err := api.check.ChangeAllowed(); err != nil { 234 return errors.Trace(err) 235 } 236 } 237 svc, err := api.state.Service(args.ServiceName) 238 if err != nil { 239 return errors.Trace(err) 240 } 241 // Set the charm for the given service. 242 if args.CharmUrl != "" { 243 // For now we do not support changing the channel through Update(). 244 // TODO(ericsnow) Support it? 245 channel := svc.Channel() 246 if err = api.serviceSetCharm(svc, args.CharmUrl, channel, args.ForceSeries, args.ForceCharmUrl, nil); err != nil { 247 return errors.Trace(err) 248 } 249 } 250 // Set the minimum number of units for the given service. 251 if args.MinUnits != nil { 252 if err = svc.SetMinUnits(*args.MinUnits); err != nil { 253 return errors.Trace(err) 254 } 255 } 256 // Set up service's settings. 257 if args.SettingsYAML != "" { 258 if err = serviceSetSettingsYAML(svc, args.SettingsYAML); err != nil { 259 return errors.Annotate(err, "setting configuration from YAML") 260 } 261 } else if len(args.SettingsStrings) > 0 { 262 if err = ServiceSetSettingsStrings(svc, args.SettingsStrings); err != nil { 263 return errors.Trace(err) 264 } 265 } 266 // Update service's constraints. 267 if args.Constraints != nil { 268 return svc.SetConstraints(*args.Constraints) 269 } 270 return nil 271 } 272 273 // SetCharm sets the charm for a given service. 274 func (api *API) SetCharm(args params.ServiceSetCharm) error { 275 // when forced units in error, don't block 276 if !args.ForceUnits { 277 if err := api.check.ChangeAllowed(); err != nil { 278 return errors.Trace(err) 279 } 280 } 281 service, err := api.state.Service(args.ServiceName) 282 if err != nil { 283 return errors.Trace(err) 284 } 285 channel := csparams.Channel(args.Channel) 286 return api.serviceSetCharm(service, args.CharmUrl, channel, args.ForceSeries, args.ForceUnits, args.ResourceIDs) 287 } 288 289 // serviceSetCharm sets the charm for the given service. 290 func (api *API) serviceSetCharm(service *state.Service, url string, channel csparams.Channel, forceSeries, forceUnits bool, resourceIDs map[string]string) error { 291 curl, err := charm.ParseURL(url) 292 if err != nil { 293 return errors.Trace(err) 294 } 295 sch, err := api.state.Charm(curl) 296 if err != nil { 297 return errors.Trace(err) 298 } 299 cfg := state.SetCharmConfig{ 300 Charm: sch, 301 Channel: channel, 302 ForceSeries: forceSeries, 303 ForceUnits: forceUnits, 304 ResourceIDs: resourceIDs, 305 } 306 return service.SetCharm(cfg) 307 } 308 309 // settingsYamlFromGetYaml will parse a yaml produced by juju get and generate 310 // charm.Settings from it that can then be sent to the service. 311 func settingsFromGetYaml(yamlContents map[string]interface{}) (charm.Settings, error) { 312 onlySettings := charm.Settings{} 313 settingsMap, ok := yamlContents["settings"].(map[interface{}]interface{}) 314 if !ok { 315 return nil, errors.New("unknown format for settings") 316 } 317 318 for setting := range settingsMap { 319 s, ok := settingsMap[setting].(map[interface{}]interface{}) 320 if !ok { 321 return nil, errors.Errorf("unknown format for settings section %v", setting) 322 } 323 // some keys might not have a value, we don't care about those. 324 v, ok := s["value"] 325 if !ok { 326 continue 327 } 328 stringSetting, ok := setting.(string) 329 if !ok { 330 return nil, errors.Errorf("unexpected setting key, expected string got %T", setting) 331 } 332 onlySettings[stringSetting] = v 333 } 334 return onlySettings, nil 335 } 336 337 // serviceSetSettingsYAML updates the settings for the given service, 338 // taking the configuration from a YAML string. 339 func serviceSetSettingsYAML(service *state.Service, settings string) error { 340 b := []byte(settings) 341 var all map[string]interface{} 342 if err := goyaml.Unmarshal(b, &all); err != nil { 343 return errors.Annotate(err, "parsing settings data") 344 } 345 // The file is already in the right format. 346 if _, ok := all[service.Name()]; !ok { 347 changes, err := settingsFromGetYaml(all) 348 if err != nil { 349 return errors.Annotate(err, "processing YAML generated by get") 350 } 351 return errors.Annotate(service.UpdateConfigSettings(changes), "updating settings with service YAML") 352 } 353 354 ch, _, err := service.Charm() 355 if err != nil { 356 return errors.Annotate(err, "obtaining charm for this service") 357 } 358 359 changes, err := ch.Config().ParseSettingsYAML(b, service.Name()) 360 if err != nil { 361 return errors.Annotate(err, "creating config from YAML") 362 } 363 return errors.Annotate(service.UpdateConfigSettings(changes), "updating settings") 364 } 365 366 // GetCharmURL returns the charm URL the given service is 367 // running at present. 368 func (api *API) GetCharmURL(args params.ServiceGet) (params.StringResult, error) { 369 service, err := api.state.Service(args.ServiceName) 370 if err != nil { 371 return params.StringResult{}, err 372 } 373 charmURL, _ := service.CharmURL() 374 return params.StringResult{Result: charmURL.String()}, nil 375 } 376 377 // Set implements the server side of Service.Set. 378 // It does not unset values that are set to an empty string. 379 // Unset should be used for that. 380 func (api *API) Set(p params.ServiceSet) error { 381 if err := api.check.ChangeAllowed(); err != nil { 382 return errors.Trace(err) 383 } 384 svc, err := api.state.Service(p.ServiceName) 385 if err != nil { 386 return err 387 } 388 ch, _, err := svc.Charm() 389 if err != nil { 390 return err 391 } 392 // Validate the settings. 393 changes, err := ch.Config().ParseSettingsStrings(p.Options) 394 if err != nil { 395 return err 396 } 397 398 return svc.UpdateConfigSettings(changes) 399 400 } 401 402 // Unset implements the server side of Client.Unset. 403 func (api *API) Unset(p params.ServiceUnset) error { 404 if err := api.check.ChangeAllowed(); err != nil { 405 return errors.Trace(err) 406 } 407 svc, err := api.state.Service(p.ServiceName) 408 if err != nil { 409 return err 410 } 411 settings := make(charm.Settings) 412 for _, option := range p.Options { 413 settings[option] = nil 414 } 415 return svc.UpdateConfigSettings(settings) 416 } 417 418 // CharmRelations implements the server side of Service.CharmRelations. 419 func (api *API) CharmRelations(p params.ServiceCharmRelations) (params.ServiceCharmRelationsResults, error) { 420 var results params.ServiceCharmRelationsResults 421 service, err := api.state.Service(p.ServiceName) 422 if err != nil { 423 return results, err 424 } 425 endpoints, err := service.Endpoints() 426 if err != nil { 427 return results, err 428 } 429 results.CharmRelations = make([]string, len(endpoints)) 430 for i, endpoint := range endpoints { 431 results.CharmRelations[i] = endpoint.Relation.Name 432 } 433 return results, nil 434 } 435 436 // Expose changes the juju-managed firewall to expose any ports that 437 // were also explicitly marked by units as open. 438 func (api *API) Expose(args params.ServiceExpose) error { 439 if err := api.check.ChangeAllowed(); err != nil { 440 return errors.Trace(err) 441 } 442 svc, err := api.state.Service(args.ServiceName) 443 if err != nil { 444 return err 445 } 446 return svc.SetExposed() 447 } 448 449 // Unexpose changes the juju-managed firewall to unexpose any ports that 450 // were also explicitly marked by units as open. 451 func (api *API) Unexpose(args params.ServiceUnexpose) error { 452 if err := api.check.ChangeAllowed(); err != nil { 453 return errors.Trace(err) 454 } 455 svc, err := api.state.Service(args.ServiceName) 456 if err != nil { 457 return err 458 } 459 return svc.ClearExposed() 460 } 461 462 // addServiceUnits adds a given number of units to a service. 463 func addServiceUnits(st *state.State, args params.AddServiceUnits) ([]*state.Unit, error) { 464 service, err := st.Service(args.ServiceName) 465 if err != nil { 466 return nil, err 467 } 468 if args.NumUnits < 1 { 469 return nil, errors.New("must add at least one unit") 470 } 471 return jjj.AddUnits(st, service, args.NumUnits, args.Placement) 472 } 473 474 // AddUnits adds a given number of units to a service. 475 func (api *API) AddUnits(args params.AddServiceUnits) (params.AddServiceUnitsResults, error) { 476 if err := api.check.ChangeAllowed(); err != nil { 477 return params.AddServiceUnitsResults{}, errors.Trace(err) 478 } 479 units, err := addServiceUnits(api.state, args) 480 if err != nil { 481 return params.AddServiceUnitsResults{}, err 482 } 483 unitNames := make([]string, len(units)) 484 for i, unit := range units { 485 unitNames[i] = unit.String() 486 } 487 return params.AddServiceUnitsResults{Units: unitNames}, nil 488 } 489 490 // DestroyUnits removes a given set of service units. 491 func (api *API) DestroyUnits(args params.DestroyServiceUnits) error { 492 if err := api.check.RemoveAllowed(); err != nil { 493 return errors.Trace(err) 494 } 495 var errs []string 496 for _, name := range args.UnitNames { 497 unit, err := api.state.Unit(name) 498 switch { 499 case errors.IsNotFound(err): 500 err = errors.Errorf("unit %q does not exist", name) 501 case err != nil: 502 case unit.Life() != state.Alive: 503 continue 504 case unit.IsPrincipal(): 505 err = unit.Destroy() 506 default: 507 err = errors.Errorf("unit %q is a subordinate", name) 508 } 509 if err != nil { 510 errs = append(errs, err.Error()) 511 } 512 } 513 return common.DestroyErr("units", args.UnitNames, errs) 514 } 515 516 // Destroy destroys a given service. 517 func (api *API) Destroy(args params.ServiceDestroy) error { 518 if err := api.check.RemoveAllowed(); err != nil { 519 return errors.Trace(err) 520 } 521 svc, err := api.state.Service(args.ServiceName) 522 if err != nil { 523 return err 524 } 525 return svc.Destroy() 526 } 527 528 // GetConstraints returns the constraints for a given service. 529 func (api *API) GetConstraints(args params.GetServiceConstraints) (params.GetConstraintsResults, error) { 530 svc, err := api.state.Service(args.ServiceName) 531 if err != nil { 532 return params.GetConstraintsResults{}, err 533 } 534 cons, err := svc.Constraints() 535 return params.GetConstraintsResults{cons}, err 536 } 537 538 // SetConstraints sets the constraints for a given service. 539 func (api *API) SetConstraints(args params.SetConstraints) error { 540 if err := api.check.ChangeAllowed(); err != nil { 541 return errors.Trace(err) 542 } 543 svc, err := api.state.Service(args.ServiceName) 544 if err != nil { 545 return err 546 } 547 return svc.SetConstraints(args.Constraints) 548 } 549 550 // AddRelation adds a relation between the specified endpoints and returns the relation info. 551 func (api *API) AddRelation(args params.AddRelation) (params.AddRelationResults, error) { 552 if err := api.check.ChangeAllowed(); err != nil { 553 return params.AddRelationResults{}, errors.Trace(err) 554 } 555 inEps, err := api.state.InferEndpoints(args.Endpoints...) 556 if err != nil { 557 return params.AddRelationResults{}, err 558 } 559 rel, err := api.state.AddRelation(inEps...) 560 if err != nil { 561 return params.AddRelationResults{}, err 562 } 563 outEps := make(map[string]charm.Relation) 564 for _, inEp := range inEps { 565 outEp, err := rel.Endpoint(inEp.ServiceName) 566 if err != nil { 567 return params.AddRelationResults{}, err 568 } 569 outEps[inEp.ServiceName] = outEp.Relation 570 } 571 return params.AddRelationResults{Endpoints: outEps}, nil 572 } 573 574 // DestroyRelation removes the relation between the specified endpoints. 575 func (api *API) DestroyRelation(args params.DestroyRelation) error { 576 if err := api.check.RemoveAllowed(); err != nil { 577 return errors.Trace(err) 578 } 579 eps, err := api.state.InferEndpoints(args.Endpoints...) 580 if err != nil { 581 return err 582 } 583 rel, err := api.state.EndpointsRelation(eps...) 584 if err != nil { 585 return err 586 } 587 return rel.Destroy() 588 }