github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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.v5" 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 result := params.ErrorResults{ 87 Results: make([]params.ErrorResult, len(args.Services)), 88 } 89 if err := api.check.ChangeAllowed(); err != nil { 90 return result, errors.Trace(err) 91 } 92 owner := api.authorizer.GetAuthTag().String() 93 for i, arg := range args.Services { 94 err := DeployService(api.state, owner, arg) 95 result.Results[i].Error = common.ServerError(err) 96 } 97 return result, nil 98 } 99 100 // DeployService fetches the charm from the charm store and deploys it. 101 // The logic has been factored out into a common function which is called by 102 // both the legacy API on the client facade, as well as the new service facade. 103 func DeployService(st *state.State, owner string, args params.ServiceDeploy) error { 104 curl, err := charm.ParseURL(args.CharmUrl) 105 if err != nil { 106 return errors.Trace(err) 107 } 108 if curl.Revision < 0 { 109 return errors.Errorf("charm url must include revision") 110 } 111 112 if args.ToMachineSpec != "" && names.IsValidMachine(args.ToMachineSpec) { 113 _, err = st.Machine(args.ToMachineSpec) 114 if err != nil { 115 return errors.Annotatef(err, `cannot deploy "%v" to machine %v`, args.ServiceName, args.ToMachineSpec) 116 } 117 } 118 119 // Try to find the charm URL in state first. 120 ch, err := st.Charm(curl) 121 if errors.IsNotFound(err) { 122 // Clients written to expect 1.16 compatibility require this next block. 123 if curl.Schema != "cs" { 124 return errors.Errorf(`charm url has unsupported schema %q`, curl.Schema) 125 } 126 if err = AddCharmWithAuthorization(st, params.AddCharmWithAuthorization{ 127 URL: args.CharmUrl, 128 }); err == nil { 129 ch, err = st.Charm(curl) 130 } 131 } 132 if err != nil { 133 return errors.Trace(err) 134 } 135 136 var settings charm.Settings 137 if len(args.ConfigYAML) > 0 { 138 settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) 139 } else if len(args.Config) > 0 { 140 // Parse config in a compatible way (see function comment). 141 settings, err = parseSettingsCompatible(ch, args.Config) 142 } 143 if err != nil { 144 return errors.Trace(err) 145 } 146 // Convert network tags to names for any given networks. 147 requestedNetworks, err := networkTagsToNames(args.Networks) 148 if err != nil { 149 return errors.Trace(err) 150 } 151 152 _, err = jjj.DeployService(st, 153 jjj.DeployServiceParams{ 154 ServiceName: args.ServiceName, 155 // TODO(dfc) ServiceOwner should be a tag 156 ServiceOwner: owner, 157 Charm: ch, 158 NumUnits: args.NumUnits, 159 ConfigSettings: settings, 160 Constraints: args.Constraints, 161 ToMachineSpec: args.ToMachineSpec, 162 Networks: requestedNetworks, 163 Storage: args.Storage, 164 }) 165 return err 166 } 167 168 // ServiceSetSettingsStrings updates the settings for the given service, 169 // taking the configuration from a map of strings. 170 func ServiceSetSettingsStrings(service *state.Service, settings map[string]string) error { 171 ch, _, err := service.Charm() 172 if err != nil { 173 return err 174 } 175 // Parse config in a compatible way (see function comment). 176 changes, err := parseSettingsCompatible(ch, settings) 177 if err != nil { 178 return err 179 } 180 return service.UpdateConfigSettings(changes) 181 } 182 183 func networkTagsToNames(tags []string) ([]string, error) { 184 netNames := make([]string, len(tags)) 185 for i, tag := range tags { 186 t, err := names.ParseNetworkTag(tag) 187 if err != nil { 188 return nil, err 189 } 190 netNames[i] = t.Id() 191 } 192 return netNames, nil 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 }