github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/environs/tools/tools.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package tools
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/loggo"
    10  
    11  	"launchpad.net/juju-core/environs"
    12  	"launchpad.net/juju-core/environs/simplestreams"
    13  	"launchpad.net/juju-core/errors"
    14  	coretools "launchpad.net/juju-core/tools"
    15  	"launchpad.net/juju-core/version"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.environs.tools")
    19  
    20  func makeToolsConstraint(cloudSpec simplestreams.CloudSpec, majorVersion, minorVersion int,
    21  	filter coretools.Filter) (*ToolsConstraint, error) {
    22  
    23  	var toolsConstraint *ToolsConstraint
    24  	if filter.Number != version.Zero {
    25  		// A specific tools version is required, however, a general match based on major/minor
    26  		// version may also have been requested. This is used to ensure any agent version currently
    27  		// recorded in the environment matches the Juju cli version.
    28  		// We can short circuit any lookup here by checking the major/minor numbers against
    29  		// the filter version and exiting early if there is a mismatch.
    30  		majorMismatch := majorVersion > 0 && majorVersion != filter.Number.Major
    31  		minorMismacth := minorVersion != -1 && minorVersion != filter.Number.Minor
    32  		if majorMismatch || minorMismacth {
    33  			return nil, coretools.ErrNoMatches
    34  		}
    35  		toolsConstraint = NewVersionedToolsConstraint(filter.Number,
    36  			simplestreams.LookupParams{CloudSpec: cloudSpec})
    37  	} else {
    38  		toolsConstraint = NewGeneralToolsConstraint(majorVersion, minorVersion, filter.Released,
    39  			simplestreams.LookupParams{CloudSpec: cloudSpec})
    40  	}
    41  	if filter.Arch != "" {
    42  		toolsConstraint.Arches = []string{filter.Arch}
    43  	} else {
    44  		logger.Debugf("no architecture specified when finding tools, looking for any")
    45  		toolsConstraint.Arches = []string{"amd64", "i386", "arm", "arm64", "ppc64"}
    46  	}
    47  	// The old tools search allowed finding tools without needing to specify a series.
    48  	// The simplestreams metadata is keyed off series, so series must be specified in
    49  	// the search constraint. If no series is specified, we gather all the series from
    50  	// lucid onwards and add those to the constraint.
    51  	var seriesToSearch []string
    52  	if filter.Series != "" {
    53  		seriesToSearch = []string{filter.Series}
    54  	} else {
    55  		logger.Debugf("no series specified when finding tools, looking for any")
    56  		seriesToSearch = simplestreams.SupportedSeries()
    57  	}
    58  	toolsConstraint.Series = seriesToSearch
    59  	return toolsConstraint, nil
    60  }
    61  
    62  // Define some boolean parameter values.
    63  const DoNotAllowRetry = false
    64  
    65  // FindTools returns a List containing all tools with a given
    66  // major.minor version number available in the cloud instance, filtered by filter.
    67  // If minorVersion = -1, then only majorVersion is considered.
    68  // If no *available* tools have the supplied major.minor version number, or match the
    69  // supplied filter, the function returns a *NotFoundError.
    70  func FindTools(cloudInst environs.ConfigGetter, majorVersion, minorVersion int,
    71  	filter coretools.Filter, allowRetry bool) (list coretools.List, err error) {
    72  
    73  	var cloudSpec simplestreams.CloudSpec
    74  	if inst, ok := cloudInst.(simplestreams.HasRegion); ok {
    75  		if cloudSpec, err = inst.Region(); err != nil {
    76  			return nil, err
    77  		}
    78  	}
    79  	// If only one of region or endpoint is provided, that is a problem.
    80  	if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
    81  		return nil, fmt.Errorf("cannot find tools without a complete cloud configuration")
    82  	}
    83  
    84  	if minorVersion >= 0 {
    85  		logger.Infof("reading tools with major.minor version %d.%d", majorVersion, minorVersion)
    86  	} else {
    87  		logger.Infof("reading tools with major version %d", majorVersion)
    88  	}
    89  	defer convertToolsError(&err)
    90  	// Construct a tools filter.
    91  	// Discard all that are known to be irrelevant.
    92  	if filter.Number != version.Zero {
    93  		logger.Infof("filtering tools by version: %s", filter.Number)
    94  	}
    95  	if filter.Series != "" {
    96  		logger.Infof("filtering tools by series: %s", filter.Series)
    97  	}
    98  	if filter.Arch != "" {
    99  		logger.Infof("filtering tools by architecture: %s", filter.Arch)
   100  	}
   101  	sources, err := GetMetadataSourcesWithRetries(cloudInst, allowRetry)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	return FindToolsForCloud(sources, cloudSpec, majorVersion, minorVersion, filter)
   106  }
   107  
   108  // FindToolsForCloud returns a List containing all tools with a given
   109  // major.minor version number and cloudSpec, filtered by filter.
   110  // If minorVersion = -1, then only majorVersion is considered.
   111  // If no *available* tools have the supplied major.minor version number, or match the
   112  // supplied filter, the function returns a *NotFoundError.
   113  func FindToolsForCloud(sources []simplestreams.DataSource, cloudSpec simplestreams.CloudSpec,
   114  	majorVersion, minorVersion int, filter coretools.Filter) (list coretools.List, err error) {
   115  
   116  	toolsConstraint, err := makeToolsConstraint(cloudSpec, majorVersion, minorVersion, filter)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	toolsMetadata, _, err := Fetch(sources, simplestreams.DefaultIndexPath, toolsConstraint, false)
   121  	if err != nil {
   122  		if errors.IsNotFoundError(err) {
   123  			err = ErrNoTools
   124  		}
   125  		return nil, err
   126  	}
   127  	if len(toolsMetadata) == 0 {
   128  		return nil, coretools.ErrNoMatches
   129  	}
   130  	list = make(coretools.List, len(toolsMetadata))
   131  	for i, metadata := range toolsMetadata {
   132  		list[i] = &coretools.Tools{
   133  			Version: metadata.binary(),
   134  			URL:     metadata.FullPath,
   135  			Size:    metadata.Size,
   136  			SHA256:  metadata.SHA256,
   137  		}
   138  	}
   139  	if filter.Series != "" {
   140  		if err := checkToolsSeries(list, filter.Series); err != nil {
   141  			return nil, err
   142  		}
   143  	}
   144  	return list, err
   145  }
   146  
   147  // The following allows FindTools, as called by FindBootstrapTools, to be patched for testing.
   148  var bootstrapFindTools = FindTools
   149  
   150  type findtoolsfunc func(environs.ConfigGetter, int, int, coretools.Filter, bool) (coretools.List, error)
   151  
   152  func TestingPatchBootstrapFindTools(stub findtoolsfunc) func() {
   153  	origFunc := bootstrapFindTools
   154  	bootstrapFindTools = stub
   155  	return func() {
   156  		bootstrapFindTools = origFunc
   157  	}
   158  }
   159  
   160  // BootstrapToolsParams contains parameters for FindBootstrapTools
   161  type BootstrapToolsParams struct {
   162  	Version    *version.Number
   163  	Arch       *string
   164  	Series     string
   165  	AllowRetry bool
   166  }
   167  
   168  // FindBootstrapTools returns a ToolsList containing only those tools with
   169  // which it would be reasonable to launch an environment's first machine, given the supplied constraints.
   170  // If a specific agent version is not requested, all tools matching the current major.minor version are chosen.
   171  func FindBootstrapTools(cloudInst environs.ConfigGetter, params BootstrapToolsParams) (list coretools.List, err error) {
   172  	// Construct a tools filter.
   173  	cfg := cloudInst.Config()
   174  	cliVersion := version.Current.Number
   175  	filter := coretools.Filter{
   176  		Series: params.Series,
   177  		Arch:   stringOrEmpty(params.Arch),
   178  	}
   179  	if params.Version != nil {
   180  		// If we already have an explicit agent version set, we're done.
   181  		filter.Number = *params.Version
   182  		return bootstrapFindTools(cloudInst, cliVersion.Major, cliVersion.Minor, filter, params.AllowRetry)
   183  	}
   184  	if dev := cliVersion.IsDev() || cfg.Development(); !dev {
   185  		logger.Infof("filtering tools by released version")
   186  		filter.Released = true
   187  	}
   188  	return bootstrapFindTools(cloudInst, cliVersion.Major, cliVersion.Minor, filter, params.AllowRetry)
   189  }
   190  
   191  func stringOrEmpty(pstr *string) string {
   192  	if pstr == nil {
   193  		return ""
   194  	}
   195  	return *pstr
   196  }
   197  
   198  // FindInstanceTools returns a ToolsList containing only those tools with which
   199  // it would be reasonable to start a new instance, given the supplied series and arch.
   200  func FindInstanceTools(cloudInst environs.ConfigGetter,
   201  	vers version.Number, series string, arch *string) (list coretools.List, err error) {
   202  
   203  	// Construct a tools filter.
   204  	// Discard all that are known to be irrelevant.
   205  	filter := coretools.Filter{
   206  		Number: vers,
   207  		Series: series,
   208  		Arch:   stringOrEmpty(arch),
   209  	}
   210  	return FindTools(cloudInst, vers.Major, vers.Minor, filter, DoNotAllowRetry)
   211  }
   212  
   213  // FindExactTools returns only the tools that match the supplied version.
   214  func FindExactTools(cloudInst environs.ConfigGetter,
   215  	vers version.Number, series string, arch string) (t *coretools.Tools, err error) {
   216  
   217  	logger.Infof("finding exact version %s", vers)
   218  	// Construct a tools filter.
   219  	// Discard all that are known to be irrelevant.
   220  	filter := coretools.Filter{
   221  		Number: vers,
   222  		Series: series,
   223  		Arch:   arch,
   224  	}
   225  	availableTools, err := FindTools(cloudInst, vers.Major, vers.Minor, filter, DoNotAllowRetry)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	if len(availableTools) != 1 {
   230  		return nil, fmt.Errorf("expected one tools, got %d tools", len(availableTools))
   231  	}
   232  	return availableTools[0], nil
   233  }
   234  
   235  // CheckToolsSeries verifies that all the given possible tools are for the
   236  // given OS series.
   237  func checkToolsSeries(toolsList coretools.List, series string) error {
   238  	toolsSeries := toolsList.AllSeries()
   239  	if len(toolsSeries) != 1 {
   240  		return fmt.Errorf("expected single series, got %v", toolsSeries)
   241  	}
   242  	if toolsSeries[0] != series {
   243  		return fmt.Errorf("tools mismatch: expected series %v, got %v", series, toolsSeries[0])
   244  	}
   245  	return nil
   246  }
   247  
   248  func isToolsError(err error) bool {
   249  	switch err {
   250  	case ErrNoTools, coretools.ErrNoMatches:
   251  		return true
   252  	}
   253  	return false
   254  }
   255  
   256  func convertToolsError(err *error) {
   257  	if isToolsError(*err) {
   258  		*err = errors.NewNotFoundError(*err, "")
   259  	}
   260  }