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