github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  }