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  }