github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/common/tools.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/juju/errors" 11 "github.com/juju/names" 12 "github.com/juju/version" 13 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/environs" 16 "github.com/juju/juju/environs/config" 17 envtools "github.com/juju/juju/environs/tools" 18 "github.com/juju/juju/network" 19 "github.com/juju/juju/state" 20 "github.com/juju/juju/state/binarystorage" 21 coretools "github.com/juju/juju/tools" 22 ) 23 24 var envtoolsFindTools = envtools.FindTools 25 26 // ToolsURLGetter is an interface providing the ToolsURL method. 27 type ToolsURLGetter interface { 28 // ToolsURLs returns URLs for the tools with 29 // the specified binary version. 30 ToolsURLs(v version.Binary) ([]string, error) 31 } 32 33 type ModelConfigGetter interface { 34 ModelConfig() (*config.Config, error) 35 } 36 37 // APIHostPortsGetter is an interface providing the APIHostPorts method. 38 type APIHostPortsGetter interface { 39 // APIHostPorst returns the HostPorts for each API server. 40 APIHostPorts() ([][]network.HostPort, error) 41 } 42 43 // ToolsStorageGetter is an interface providing the ToolsStorage method. 44 type ToolsStorageGetter interface { 45 // ToolsStorage returns a binarystorage.StorageCloser. 46 ToolsStorage() (binarystorage.StorageCloser, error) 47 } 48 49 // ToolsGetter implements a common Tools method for use by various 50 // facades. 51 type ToolsGetter struct { 52 entityFinder state.EntityFinder 53 configGetter ModelConfigGetter 54 toolsStorageGetter ToolsStorageGetter 55 urlGetter ToolsURLGetter 56 getCanRead GetAuthFunc 57 } 58 59 // NewToolsGetter returns a new ToolsGetter. The GetAuthFunc will be 60 // used on each invocation of Tools to determine current permissions. 61 func NewToolsGetter(f state.EntityFinder, c ModelConfigGetter, s ToolsStorageGetter, t ToolsURLGetter, getCanRead GetAuthFunc) *ToolsGetter { 62 return &ToolsGetter{f, c, s, t, getCanRead} 63 } 64 65 // Tools finds the tools necessary for the given agents. 66 func (t *ToolsGetter) Tools(args params.Entities) (params.ToolsResults, error) { 67 result := params.ToolsResults{ 68 Results: make([]params.ToolsResult, len(args.Entities)), 69 } 70 canRead, err := t.getCanRead() 71 if err != nil { 72 return result, err 73 } 74 agentVersion, err := t.getGlobalAgentVersion() 75 if err != nil { 76 return result, err 77 } 78 toolsStorage, err := t.toolsStorageGetter.ToolsStorage() 79 if err != nil { 80 return result, err 81 } 82 defer toolsStorage.Close() 83 84 for i, entity := range args.Entities { 85 tag, err := names.ParseTag(entity.Tag) 86 if err != nil { 87 result.Results[i].Error = ServerError(ErrPerm) 88 continue 89 } 90 agentToolsList, err := t.oneAgentTools(canRead, tag, agentVersion, toolsStorage) 91 if err == nil { 92 result.Results[i].ToolsList = agentToolsList 93 // TODO(axw) Get rid of this in 1.22, when all upgraders 94 // are known to ignore the flag. 95 result.Results[i].DisableSSLHostnameVerification = true 96 } 97 result.Results[i].Error = ServerError(err) 98 } 99 return result, nil 100 } 101 102 func (t *ToolsGetter) getGlobalAgentVersion() (version.Number, error) { 103 // Get the Agent Version requested in the Environment Config 104 nothing := version.Number{} 105 cfg, err := t.configGetter.ModelConfig() 106 if err != nil { 107 return nothing, err 108 } 109 agentVersion, ok := cfg.AgentVersion() 110 if !ok { 111 return nothing, errors.New("agent version not set in model config") 112 } 113 return agentVersion, nil 114 } 115 116 func (t *ToolsGetter) oneAgentTools(canRead AuthFunc, tag names.Tag, agentVersion version.Number, storage binarystorage.Storage) (coretools.List, error) { 117 if !canRead(tag) { 118 return nil, ErrPerm 119 } 120 entity, err := t.entityFinder.FindEntity(tag) 121 if err != nil { 122 return nil, err 123 } 124 tooler, ok := entity.(state.AgentTooler) 125 if !ok { 126 return nil, NotSupportedError(tag, "agent tools") 127 } 128 existingTools, err := tooler.AgentTools() 129 if err != nil { 130 return nil, err 131 } 132 toolsFinder := NewToolsFinder(t.configGetter, t.toolsStorageGetter, t.urlGetter) 133 list, err := toolsFinder.findTools(params.FindToolsParams{ 134 Number: agentVersion, 135 MajorVersion: -1, 136 MinorVersion: -1, 137 Series: existingTools.Version.Series, 138 Arch: existingTools.Version.Arch, 139 }) 140 if err != nil { 141 return nil, err 142 } 143 return list, nil 144 } 145 146 // ToolsSetter implements a common Tools method for use by various 147 // facades. 148 type ToolsSetter struct { 149 st state.EntityFinder 150 getCanWrite GetAuthFunc 151 } 152 153 // NewToolsSetter returns a new ToolsGetter. The GetAuthFunc will be 154 // used on each invocation of Tools to determine current permissions. 155 func NewToolsSetter(st state.EntityFinder, getCanWrite GetAuthFunc) *ToolsSetter { 156 return &ToolsSetter{ 157 st: st, 158 getCanWrite: getCanWrite, 159 } 160 } 161 162 // SetTools updates the recorded tools version for the agents. 163 func (t *ToolsSetter) SetTools(args params.EntitiesVersion) (params.ErrorResults, error) { 164 results := params.ErrorResults{ 165 Results: make([]params.ErrorResult, len(args.AgentTools)), 166 } 167 canWrite, err := t.getCanWrite() 168 if err != nil { 169 return results, errors.Trace(err) 170 } 171 for i, agentTools := range args.AgentTools { 172 tag, err := names.ParseTag(agentTools.Tag) 173 if err != nil { 174 results.Results[i].Error = ServerError(ErrPerm) 175 continue 176 } 177 err = t.setOneAgentVersion(tag, agentTools.Tools.Version, canWrite) 178 results.Results[i].Error = ServerError(err) 179 } 180 return results, nil 181 } 182 183 func (t *ToolsSetter) setOneAgentVersion(tag names.Tag, vers version.Binary, canWrite AuthFunc) error { 184 if !canWrite(tag) { 185 return ErrPerm 186 } 187 entity0, err := t.st.FindEntity(tag) 188 if err != nil { 189 return err 190 } 191 entity, ok := entity0.(state.AgentTooler) 192 if !ok { 193 return NotSupportedError(tag, "agent tools") 194 } 195 return entity.SetAgentVersion(vers) 196 } 197 198 type ToolsFinder struct { 199 configGetter ModelConfigGetter 200 toolsStorageGetter ToolsStorageGetter 201 urlGetter ToolsURLGetter 202 } 203 204 // NewToolsFinder returns a new ToolsFinder, returning tools 205 // with their URLs pointing at the API server. 206 func NewToolsFinder(c ModelConfigGetter, s ToolsStorageGetter, t ToolsURLGetter) *ToolsFinder { 207 return &ToolsFinder{c, s, t} 208 } 209 210 // FindTools returns a List containing all tools matching the given parameters. 211 func (f *ToolsFinder) FindTools(args params.FindToolsParams) (params.FindToolsResult, error) { 212 result := params.FindToolsResult{} 213 list, err := f.findTools(args) 214 if err != nil { 215 result.Error = ServerError(err) 216 } else { 217 result.List = list 218 } 219 return result, nil 220 } 221 222 // findTools calls findMatchingTools and then rewrites the URLs 223 // using the provided ToolsURLGetter. 224 func (f *ToolsFinder) findTools(args params.FindToolsParams) (coretools.List, error) { 225 list, err := f.findMatchingTools(args) 226 if err != nil { 227 return nil, err 228 } 229 // Rewrite the URLs so they point at the API servers. If the 230 // tools are not in tools storage, then the API server will 231 // download and cache them if the client requests that version. 232 var fullList coretools.List 233 for _, baseTools := range list { 234 urls, err := f.urlGetter.ToolsURLs(baseTools.Version) 235 if err != nil { 236 return nil, err 237 } 238 for _, url := range urls { 239 tools := *baseTools 240 tools.URL = url 241 fullList = append(fullList, &tools) 242 } 243 } 244 return fullList, nil 245 } 246 247 // findMatchingTools searches tools storage and simplestreams for tools matching the 248 // given parameters. If an exact match is specified (number, series and arch) 249 // and is found in tools storage, then simplestreams will not be searched. 250 func (f *ToolsFinder) findMatchingTools(args params.FindToolsParams) (coretools.List, error) { 251 exactMatch := args.Number != version.Zero && args.Series != "" && args.Arch != "" 252 storageList, err := f.matchingStorageTools(args) 253 if err == nil && exactMatch { 254 return storageList, nil 255 } else if err != nil && err != coretools.ErrNoMatches { 256 return nil, err 257 } 258 259 // Look for tools in simplestreams too, but don't replace 260 // any versions found in storage. 261 cfg, err := f.configGetter.ModelConfig() 262 if err != nil { 263 return nil, err 264 } 265 env, err := environs.New(cfg) 266 if err != nil { 267 return nil, err 268 } 269 filter := toolsFilter(args) 270 stream := envtools.PreferredStream(&args.Number, cfg.Development(), cfg.AgentStream()) 271 simplestreamsList, err := envtoolsFindTools( 272 env, args.MajorVersion, args.MinorVersion, stream, filter, 273 ) 274 if len(storageList) == 0 && err != nil { 275 return nil, err 276 } 277 278 list := storageList 279 found := make(map[version.Binary]bool) 280 for _, tools := range storageList { 281 found[tools.Version] = true 282 } 283 for _, tools := range simplestreamsList { 284 if !found[tools.Version] { 285 list = append(list, tools) 286 } 287 } 288 sort.Sort(list) 289 return list, nil 290 } 291 292 // matchingStorageTools returns a coretools.List, with an entry for each 293 // metadata entry in the tools storage that matches the given parameters. 294 func (f *ToolsFinder) matchingStorageTools(args params.FindToolsParams) (coretools.List, error) { 295 storage, err := f.toolsStorageGetter.ToolsStorage() 296 if err != nil { 297 return nil, err 298 } 299 defer storage.Close() 300 allMetadata, err := storage.AllMetadata() 301 if err != nil { 302 return nil, err 303 } 304 list := make(coretools.List, len(allMetadata)) 305 for i, m := range allMetadata { 306 vers, err := version.ParseBinary(m.Version) 307 if err != nil { 308 return nil, errors.Annotatef(err, "unexpectedly bad version %q in tools storage", m.Version) 309 } 310 list[i] = &coretools.Tools{ 311 Version: vers, 312 Size: m.Size, 313 SHA256: m.SHA256, 314 } 315 } 316 list, err = list.Match(toolsFilter(args)) 317 if err != nil { 318 return nil, err 319 } 320 var matching coretools.List 321 for _, tools := range list { 322 if args.MajorVersion != -1 && tools.Version.Major != args.MajorVersion { 323 continue 324 } 325 if args.MinorVersion != -1 && tools.Version.Minor != args.MinorVersion { 326 continue 327 } 328 matching = append(matching, tools) 329 } 330 if len(matching) == 0 { 331 return nil, coretools.ErrNoMatches 332 } 333 return matching, nil 334 } 335 336 func toolsFilter(args params.FindToolsParams) coretools.Filter { 337 return coretools.Filter{ 338 Number: args.Number, 339 Arch: args.Arch, 340 Series: args.Series, 341 } 342 } 343 344 type toolsURLGetter struct { 345 modelUUID string 346 apiHostPortsGetter APIHostPortsGetter 347 } 348 349 // NewToolsURLGetter creates a new ToolsURLGetter that 350 // returns tools URLs pointing at an API server. 351 func NewToolsURLGetter(modelUUID string, a APIHostPortsGetter) *toolsURLGetter { 352 return &toolsURLGetter{modelUUID, a} 353 } 354 355 func (t *toolsURLGetter) ToolsURLs(v version.Binary) ([]string, error) { 356 addrs, err := apiAddresses(t.apiHostPortsGetter) 357 if err != nil { 358 return nil, err 359 } 360 if len(addrs) == 0 { 361 return nil, errors.Errorf("no suitable API server address to pick from") 362 } 363 var urls []string 364 for _, addr := range addrs { 365 serverRoot := fmt.Sprintf("https://%s/model/%s", addr, t.modelUUID) 366 url := ToolsURL(serverRoot, v) 367 urls = append(urls, url) 368 } 369 return urls, nil 370 } 371 372 // ToolsURL returns a tools URL pointing the API server 373 // specified by the "serverRoot". 374 func ToolsURL(serverRoot string, v version.Binary) string { 375 return fmt.Sprintf("%s/tools/%s", serverRoot, v.String()) 376 }