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  }