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