github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "github.com/juju/names" 13 "gopkg.in/juju/charm.v6-unstable" 14 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/apiserver/params" 17 jjj "github.com/juju/juju/juju" 18 "github.com/juju/juju/state" 19 statestorage "github.com/juju/juju/state/storage" 20 ) 21 22 var ( 23 logger = loggo.GetLogger("juju.apiserver.service") 24 25 newStateStorage = statestorage.NewStorage 26 ) 27 28 func init() { 29 common.RegisterStandardFacade("Service", 1, NewAPI) 30 } 31 32 // Service defines the methods on the service API end point. 33 type Service interface { 34 SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error) 35 } 36 37 // API implements the service interface and is the concrete 38 // implementation of the api end point. 39 type API struct { 40 check *common.BlockChecker 41 state *state.State 42 authorizer common.Authorizer 43 } 44 45 // NewAPI returns a new service API facade. 46 func NewAPI( 47 st *state.State, 48 resources *common.Resources, 49 authorizer common.Authorizer, 50 ) (*API, error) { 51 if !authorizer.AuthClient() { 52 return nil, common.ErrPerm 53 } 54 55 return &API{ 56 state: st, 57 authorizer: authorizer, 58 check: common.NewBlockChecker(st), 59 }, nil 60 } 61 62 // SetMetricCredentials sets credentials on the service. 63 func (api *API) SetMetricCredentials(args params.ServiceMetricCredentials) (params.ErrorResults, error) { 64 result := params.ErrorResults{ 65 Results: make([]params.ErrorResult, len(args.Creds)), 66 } 67 if len(args.Creds) == 0 { 68 return result, nil 69 } 70 for i, a := range args.Creds { 71 service, err := api.state.Service(a.ServiceName) 72 if err != nil { 73 result.Results[i].Error = common.ServerError(err) 74 continue 75 } 76 err = service.SetMetricCredentials(a.MetricCredentials) 77 if err != nil { 78 result.Results[i].Error = common.ServerError(err) 79 } 80 } 81 return result, nil 82 } 83 84 // ServicesDeploy fetches the charms from the charm store and deploys them. 85 func (api *API) ServicesDeploy(args params.ServicesDeploy) (params.ErrorResults, error) { 86 return api.ServicesDeployWithPlacement(args) 87 } 88 89 // ServicesDeployWithPlacement fetches the charms from the charm store and deploys them 90 // using the specified placement directives. 91 func (api *API) ServicesDeployWithPlacement(args params.ServicesDeploy) (params.ErrorResults, error) { 92 result := params.ErrorResults{ 93 Results: make([]params.ErrorResult, len(args.Services)), 94 } 95 if err := api.check.ChangeAllowed(); err != nil { 96 return result, errors.Trace(err) 97 } 98 owner := api.authorizer.GetAuthTag().String() 99 for i, arg := range args.Services { 100 err := DeployService(api.state, owner, arg) 101 result.Results[i].Error = common.ServerError(err) 102 } 103 return result, nil 104 } 105 106 // DeployService fetches the charm from the charm store and deploys it. 107 // The logic has been factored out into a common function which is called by 108 // both the legacy API on the client facade, as well as the new service facade. 109 func DeployService(st *state.State, owner string, args params.ServiceDeploy) error { 110 curl, err := charm.ParseURL(args.CharmUrl) 111 if err != nil { 112 return errors.Trace(err) 113 } 114 if curl.Revision < 0 { 115 return errors.Errorf("charm url must include revision") 116 } 117 118 // Do a quick but not complete validation check before going any further. 119 if len(args.Placement) == 0 && args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) { 120 _, err = st.Machine(args.ToMachineSpec) 121 if err != nil { 122 return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, args.ToMachineSpec) 123 } 124 } 125 126 // Try to find the charm URL in state first. 127 ch, err := st.Charm(curl) 128 if errors.IsNotFound(err) { 129 // Clients written to expect 1.16 compatibility require this next block. 130 if curl.Schema != "cs" { 131 return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema) 132 } 133 if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{ 134 URL: args.CharmUrl, 135 }); err == nil { 136 ch, err = st.Charm(curl) 137 } 138 } 139 if err != nil { 140 return errors.Trace(err) 141 } 142 143 var settings charm.Settings 144 if len(args.ConfigYAML) > 0 { 145 settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) 146 } else if len(args.Config) > 0 { 147 // Parse config in a compatible way (see function comment). 148 settings, err = parseSettingsCompatible(ch, args.Config) 149 } 150 if err != nil { 151 return errors.Trace(err) 152 } 153 // Convert network tags to names for any given networks. 154 requestedNetworks, err := networkTagsToNames(args.Networks) 155 if err != nil { 156 return errors.Trace(err) 157 } 158 159 _, err = jjj.DeployService(st, 160 jjj.DeployServiceParams{ 161 ServiceName: args.ServiceName, 162 // TODO(dfc) ServiceOwner should be a tag 163 ServiceOwner: owner, 164 Charm: ch, 165 NumUnits: args.NumUnits, 166 ConfigSettings: settings, 167 Constraints: args.Constraints, 168 ToMachineSpec: args.ToMachineSpec, 169 Placement: args.Placement, 170 Networks: requestedNetworks, 171 Storage: args.Storage, 172 }) 173 return err 174 } 175 176 // ServiceSetSettingsStrings updates the settings for the given service, 177 // taking the configuration from a map of strings. 178 func ServiceSetSettingsStrings(service *state.Service, settings map[string]string) error { 179 ch, _, err := service.Charm() 180 if err != nil { 181 return err 182 } 183 // Parse config in a compatible way (see function comment). 184 changes, err := parseSettingsCompatible(ch, settings) 185 if err != nil { 186 return err 187 } 188 return service.UpdateConfigSettings(changes) 189 } 190 191 func networkTagsToNames(tags []string) ([]string, error) { 192 netNames := make([]string, len(tags)) 193 for i, tag := range tags { 194 t, err := names.ParseNetworkTag(tag) 195 if err != nil { 196 return nil, err 197 } 198 netNames[i] = t.Id() 199 } 200 return netNames, nil 201 } 202 203 // parseSettingsCompatible parses setting strings in a way that is 204 // compatible with the behavior before this CL based on the issue 205 // http://pad.lv/1194945. Until then setting an option to an empty 206 // string caused it to reset to the default value. We now allow 207 // empty strings as actual values, but we want to preserve the API 208 // behavior. 209 func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) { 210 setSettings := map[string]string{} 211 unsetSettings := charm.Settings{} 212 // Split settings into those which set and those which unset a value. 213 for name, value := range settings { 214 if value == "" { 215 unsetSettings[name] = nil 216 continue 217 } 218 setSettings[name] = value 219 } 220 // Validate the settings. 221 changes, err := ch.Config().ParseSettingsStrings(setSettings) 222 if err != nil { 223 return nil, err 224 } 225 // Validate the unsettings and merge them into the changes. 226 unsetSettings, err = ch.Config().ValidateSettings(unsetSettings) 227 if err != nil { 228 return nil, err 229 } 230 for name := range unsetSettings { 231 changes[name] = nil 232 } 233 return changes, nil 234 }