github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/apiserver/storage/storage.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storage
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names"
     9  	"github.com/juju/utils/set"
    10  
    11  	"github.com/juju/juju/apiserver/common"
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/state"
    14  	"github.com/juju/juju/storage"
    15  	"github.com/juju/juju/storage/poolmanager"
    16  	"github.com/juju/juju/storage/provider/registry"
    17  )
    18  
    19  func init() {
    20  	common.RegisterStandardFacade("Storage", 1, NewAPI)
    21  }
    22  
    23  // API implements the storage interface and is the concrete
    24  // implementation of the api end point.
    25  type API struct {
    26  	storage     storageAccess
    27  	poolManager poolmanager.PoolManager
    28  	authorizer  common.Authorizer
    29  }
    30  
    31  // createAPI returns a new storage API facade.
    32  func createAPI(
    33  	st storageAccess,
    34  	pm poolmanager.PoolManager,
    35  	resources *common.Resources,
    36  	authorizer common.Authorizer,
    37  ) (*API, error) {
    38  	if !authorizer.AuthClient() {
    39  		return nil, common.ErrPerm
    40  	}
    41  
    42  	return &API{
    43  		storage:     st,
    44  		poolManager: pm,
    45  		authorizer:  authorizer,
    46  	}, nil
    47  }
    48  
    49  // NewAPI returns a new storage API facade.
    50  func NewAPI(
    51  	st *state.State,
    52  	resources *common.Resources,
    53  	authorizer common.Authorizer,
    54  ) (*API, error) {
    55  	return createAPI(getState(st), poolManager(st), resources, authorizer)
    56  }
    57  
    58  func poolManager(st *state.State) poolmanager.PoolManager {
    59  	return poolmanager.New(state.NewStateSettings(st))
    60  }
    61  
    62  // Show retrieves and returns detailed information about desired storage
    63  // identified by supplied tags. If specified storage cannot be retrieved,
    64  // individual error is returned instead of storage information.
    65  func (api *API) Show(entities params.Entities) (params.StorageDetailsResults, error) {
    66  	var all []params.StorageDetailsResult
    67  	for _, entity := range entities.Entities {
    68  		storageTag, err := names.ParseStorageTag(entity.Tag)
    69  		if err != nil {
    70  			all = append(all, params.StorageDetailsResult{
    71  				Error: common.ServerError(err),
    72  			})
    73  			continue
    74  		}
    75  		found, instance, serverErr := api.getStorageInstance(storageTag)
    76  		if err != nil {
    77  			all = append(all, params.StorageDetailsResult{Error: serverErr})
    78  			continue
    79  		}
    80  		if found {
    81  			results := api.createStorageDetailsResult(storageTag, instance)
    82  			all = append(all, results...)
    83  		}
    84  	}
    85  	return params.StorageDetailsResults{Results: all}, nil
    86  }
    87  
    88  // List returns all currently known storage. Unlike Show(),
    89  // if errors encountered while retrieving a particular
    90  // storage, this error is treated as part of the returned storage detail.
    91  func (api *API) List() (params.StorageInfosResult, error) {
    92  	stateInstances, err := api.storage.AllStorageInstances()
    93  	if err != nil {
    94  		return params.StorageInfosResult{}, common.ServerError(err)
    95  	}
    96  	var infos []params.StorageInfo
    97  	for _, stateInstance := range stateInstances {
    98  		storageTag := stateInstance.StorageTag()
    99  		persistent, err := api.isPersistent(stateInstance)
   100  		if err != nil {
   101  			return params.StorageInfosResult{}, err
   102  		}
   103  		instance := createParamsStorageInstance(stateInstance, persistent)
   104  
   105  		// It is possible to encounter errors here related to getting individual
   106  		// storage details such as getting attachments, getting machine from the unit,
   107  		// etc.
   108  		// Current approach is to do what status command does - treat error
   109  		// as another valid property, i.e. augment storage details.
   110  		attachments := api.createStorageDetailsResult(storageTag, instance)
   111  		for _, one := range attachments {
   112  			aParam := params.StorageInfo{one.Result, one.Error}
   113  			infos = append(infos, aParam)
   114  		}
   115  	}
   116  	return params.StorageInfosResult{Results: infos}, nil
   117  }
   118  
   119  func (api *API) createStorageDetailsResult(
   120  	storageTag names.StorageTag,
   121  	instance params.StorageDetails,
   122  ) []params.StorageDetailsResult {
   123  	attachments, err := api.getStorageAttachments(storageTag, instance)
   124  	if err != nil {
   125  		return []params.StorageDetailsResult{params.StorageDetailsResult{Result: instance, Error: err}}
   126  	}
   127  	if len(attachments) > 0 {
   128  		// If any attachments were found for this storage instance,
   129  		// return them instead.
   130  		result := make([]params.StorageDetailsResult, len(attachments))
   131  		for i, attachment := range attachments {
   132  			result[i] = params.StorageDetailsResult{Result: attachment}
   133  		}
   134  		return result
   135  	}
   136  	// If we are here then this storage instance is unattached.
   137  	return []params.StorageDetailsResult{params.StorageDetailsResult{Result: instance}}
   138  }
   139  
   140  func (api *API) getStorageAttachments(
   141  	storageTag names.StorageTag,
   142  	instance params.StorageDetails,
   143  ) ([]params.StorageDetails, *params.Error) {
   144  	serverError := func(err error) *params.Error {
   145  		return common.ServerError(errors.Annotatef(err, "getting attachments for storage %v", storageTag.Id()))
   146  	}
   147  	stateAttachments, err := api.storage.StorageAttachments(storageTag)
   148  	if err != nil {
   149  		return nil, serverError(err)
   150  	}
   151  	result := make([]params.StorageDetails, len(stateAttachments))
   152  	for i, one := range stateAttachments {
   153  		paramsStorageAttachment, err := api.createParamsStorageAttachment(instance, one)
   154  		if err != nil {
   155  			return nil, serverError(err)
   156  		}
   157  		result[i] = paramsStorageAttachment
   158  	}
   159  	return result, nil
   160  }
   161  
   162  func (api *API) createParamsStorageAttachment(si params.StorageDetails, sa state.StorageAttachment) (params.StorageDetails, error) {
   163  	result := params.StorageDetails{Status: "pending"}
   164  	result.StorageTag = sa.StorageInstance().String()
   165  	if result.StorageTag != si.StorageTag {
   166  		panic("attachment does not belong to storage instance")
   167  	}
   168  	result.UnitTag = sa.Unit().String()
   169  	result.OwnerTag = si.OwnerTag
   170  	result.Kind = si.Kind
   171  	result.Persistent = si.Persistent
   172  	// TODO(axw) set status according to whether storage has been provisioned.
   173  
   174  	// This is only for provisioned attachments
   175  	machineTag, err := api.storage.UnitAssignedMachine(sa.Unit())
   176  	if err != nil {
   177  		return params.StorageDetails{}, errors.Annotate(err, "getting unit for storage attachment")
   178  	}
   179  	info, err := common.StorageAttachmentInfo(api.storage, sa, machineTag)
   180  	if err != nil {
   181  		if errors.IsNotProvisioned(err) {
   182  			// If Info returns an error, then the storage has not yet been provisioned.
   183  			return result, nil
   184  		}
   185  		return params.StorageDetails{}, errors.Annotate(err, "getting storage attachment info")
   186  	}
   187  	result.Location = info.Location
   188  	if result.Location != "" {
   189  		result.Status = "attached"
   190  	}
   191  	return result, nil
   192  }
   193  
   194  func (api *API) getStorageInstance(tag names.StorageTag) (bool, params.StorageDetails, *params.Error) {
   195  	nothing := params.StorageDetails{}
   196  	serverError := func(err error) *params.Error {
   197  		return common.ServerError(errors.Annotatef(err, "getting %v", tag))
   198  	}
   199  	stateInstance, err := api.storage.StorageInstance(tag)
   200  	if err != nil {
   201  		if errors.IsNotFound(err) {
   202  			return false, nothing, nil
   203  		}
   204  		return false, nothing, serverError(err)
   205  	}
   206  	persistent, err := api.isPersistent(stateInstance)
   207  	if err != nil {
   208  		return false, nothing, serverError(err)
   209  	}
   210  	return true, createParamsStorageInstance(stateInstance, persistent), nil
   211  }
   212  
   213  func createParamsStorageInstance(si state.StorageInstance, persistent bool) params.StorageDetails {
   214  	result := params.StorageDetails{
   215  		OwnerTag:   si.Owner().String(),
   216  		StorageTag: si.Tag().String(),
   217  		Kind:       params.StorageKind(si.Kind()),
   218  		Status:     "pending",
   219  		Persistent: persistent,
   220  	}
   221  	return result
   222  }
   223  
   224  // TODO(axw) move this and createParamsStorageInstance to
   225  // apiserver/common/storage.go, alongside StorageAttachmentInfo.
   226  func (api *API) isPersistent(si state.StorageInstance) (bool, error) {
   227  	if si.Kind() != state.StorageKindBlock {
   228  		// TODO(axw) when we support persistent filesystems,
   229  		// e.g. CephFS, we'll need to do the same thing as
   230  		// we do for volumes for filesystems.
   231  		return false, nil
   232  	}
   233  	volume, err := api.storage.StorageInstanceVolume(si.StorageTag())
   234  	if err != nil {
   235  		return false, err
   236  	}
   237  	// If the volume is not provisioned, we read its config attributes.
   238  	if params, ok := volume.Params(); ok {
   239  		_, cfg, err := common.StoragePoolConfig(params.Pool, api.poolManager)
   240  		if err != nil {
   241  			return false, err
   242  		}
   243  		return cfg.IsPersistent(), nil
   244  	}
   245  	// If the volume is provisioned, we look at its provisioning info.
   246  	info, err := volume.Info()
   247  	if err != nil {
   248  		return false, err
   249  	}
   250  	return info.Persistent, nil
   251  }
   252  
   253  // ListPools returns a list of pools.
   254  // If filter is provided, returned list only contains pools that match
   255  // the filter.
   256  // Pools can be filtered on names and provider types.
   257  // If both names and types are provided as filter,
   258  // pools that match either are returned.
   259  // This method lists union of pools and environment provider types.
   260  // If no filter is provided, all pools are returned.
   261  func (a *API) ListPools(
   262  	filter params.StoragePoolFilter,
   263  ) (params.StoragePoolsResult, error) {
   264  
   265  	if ok, err := a.isValidPoolListFilter(filter); !ok {
   266  		return params.StoragePoolsResult{}, err
   267  	}
   268  
   269  	pools, err := a.poolManager.List()
   270  	if err != nil {
   271  		return params.StoragePoolsResult{}, err
   272  	}
   273  	providers, err := a.allProviders()
   274  	if err != nil {
   275  		return params.StoragePoolsResult{}, err
   276  	}
   277  	matches := buildFilter(filter)
   278  	results := append(
   279  		filterPools(pools, matches),
   280  		filterProviders(providers, matches)...,
   281  	)
   282  	return params.StoragePoolsResult{results}, nil
   283  }
   284  
   285  func buildFilter(filter params.StoragePoolFilter) func(n, p string) bool {
   286  	providerSet := set.NewStrings(filter.Providers...)
   287  	nameSet := set.NewStrings(filter.Names...)
   288  
   289  	matches := func(n, p string) bool {
   290  		// no filters supplied = pool matches criteria
   291  		if providerSet.IsEmpty() && nameSet.IsEmpty() {
   292  			return true
   293  		}
   294  		// if at least 1 name and type are supplied, use AND to match
   295  		if !providerSet.IsEmpty() && !nameSet.IsEmpty() {
   296  			return nameSet.Contains(n) && providerSet.Contains(string(p))
   297  		}
   298  		// Otherwise, if only names or types are supplied, use OR to match
   299  		return nameSet.Contains(n) || providerSet.Contains(string(p))
   300  	}
   301  	return matches
   302  }
   303  
   304  func filterProviders(
   305  	providers []storage.ProviderType,
   306  	matches func(n, p string) bool,
   307  ) []params.StoragePool {
   308  	if len(providers) == 0 {
   309  		return nil
   310  	}
   311  	all := make([]params.StoragePool, 0, len(providers))
   312  	for _, p := range providers {
   313  		ps := string(p)
   314  		if matches(ps, ps) {
   315  			all = append(all, params.StoragePool{Name: ps, Provider: ps})
   316  		}
   317  	}
   318  	return all
   319  }
   320  
   321  func filterPools(
   322  	pools []*storage.Config,
   323  	matches func(n, p string) bool,
   324  ) []params.StoragePool {
   325  	if len(pools) == 0 {
   326  		return nil
   327  	}
   328  	all := make([]params.StoragePool, 0, len(pools))
   329  	for _, p := range pools {
   330  		if matches(p.Name(), string(p.Provider())) {
   331  			all = append(all, params.StoragePool{
   332  				Name:     p.Name(),
   333  				Provider: string(p.Provider()),
   334  				Attrs:    p.Attrs(),
   335  			})
   336  		}
   337  	}
   338  	return all
   339  }
   340  
   341  func (a *API) allProviders() ([]storage.ProviderType, error) {
   342  	envName, err := a.storage.EnvName()
   343  	if err != nil {
   344  		return nil, errors.Annotate(err, "getting env name")
   345  	}
   346  	if providers, ok := registry.EnvironStorageProviders(envName); ok {
   347  		return providers, nil
   348  	}
   349  	return nil, nil
   350  }
   351  
   352  func (a *API) isValidPoolListFilter(
   353  	filter params.StoragePoolFilter,
   354  ) (bool, error) {
   355  	if len(filter.Providers) != 0 {
   356  		if valid, err := a.isValidProviderCriteria(filter.Providers); !valid {
   357  			return false, errors.Trace(err)
   358  		}
   359  	}
   360  	if len(filter.Names) != 0 {
   361  		if valid, err := a.isValidNameCriteria(filter.Names); !valid {
   362  			return false, errors.Trace(err)
   363  		}
   364  	}
   365  	return true, nil
   366  }
   367  
   368  func (a *API) isValidNameCriteria(names []string) (bool, error) {
   369  	for _, n := range names {
   370  		if !storage.IsValidPoolName(n) {
   371  			return false, errors.NotValidf("pool name %q", n)
   372  		}
   373  	}
   374  	return true, nil
   375  }
   376  
   377  func (a *API) isValidProviderCriteria(providers []string) (bool, error) {
   378  	envName, err := a.storage.EnvName()
   379  	if err != nil {
   380  		return false, errors.Annotate(err, "getting env name")
   381  	}
   382  	for _, p := range providers {
   383  		if !registry.IsProviderSupported(envName, storage.ProviderType(p)) {
   384  			return false, errors.NotSupportedf("%q for environment %q", p, envName)
   385  		}
   386  	}
   387  	return true, nil
   388  }
   389  
   390  // CreatePool creates a new pool with specified parameters.
   391  func (a *API) CreatePool(p params.StoragePool) error {
   392  	_, err := a.poolManager.Create(
   393  		p.Name,
   394  		storage.ProviderType(p.Provider),
   395  		p.Attrs)
   396  	return err
   397  }
   398  
   399  func (a *API) ListVolumes(filter params.VolumeFilter) (params.VolumeItemsResult, error) {
   400  	if !filter.IsEmpty() {
   401  		return params.VolumeItemsResult{Results: a.filterVolumes(filter)}, nil
   402  	}
   403  	volumes, err := a.listVolumeAttachments()
   404  	if err != nil {
   405  		return params.VolumeItemsResult{}, common.ServerError(err)
   406  	}
   407  	return params.VolumeItemsResult{Results: volumes}, nil
   408  }
   409  
   410  func (a *API) listVolumeAttachments() ([]params.VolumeItem, error) {
   411  	all, err := a.storage.AllVolumes()
   412  	if err != nil {
   413  		return nil, errors.Trace(err)
   414  	}
   415  	return a.volumeAttachments(all), nil
   416  }
   417  
   418  func (a *API) volumeAttachments(all []state.Volume) []params.VolumeItem {
   419  	if all == nil || len(all) == 0 {
   420  		return nil
   421  	}
   422  
   423  	result := make([]params.VolumeItem, len(all))
   424  	for i, v := range all {
   425  		volume, err := a.convertStateVolumeToParams(v)
   426  		if err != nil {
   427  			result[i] = params.VolumeItem{
   428  				Error: common.ServerError(errors.Trace(err)),
   429  			}
   430  			continue
   431  		}
   432  		result[i] = params.VolumeItem{Volume: volume}
   433  		atts, err := a.storage.VolumeAttachments(v.VolumeTag())
   434  		if err != nil {
   435  			result[i].Error = common.ServerError(errors.Annotatef(
   436  				err, "attachments for volume %v", v.VolumeTag()))
   437  			continue
   438  		}
   439  		result[i].Attachments = convertStateVolumeAttachmentsToParams(atts)
   440  	}
   441  	return result
   442  }
   443  
   444  func (a *API) filterVolumes(f params.VolumeFilter) []params.VolumeItem {
   445  	var attachments []state.VolumeAttachment
   446  	var errs []params.VolumeItem
   447  
   448  	addErr := func(err error) {
   449  		errs = append(errs,
   450  			params.VolumeItem{Error: common.ServerError(err)})
   451  	}
   452  
   453  	for _, machine := range f.Machines {
   454  		tag, err := names.ParseMachineTag(machine)
   455  		if err != nil {
   456  			addErr(errors.Annotatef(err, "parsing machine tag %v", machine))
   457  		}
   458  		machineAttachments, err := a.storage.MachineVolumeAttachments(tag)
   459  		if err != nil {
   460  			addErr(errors.Annotatef(err,
   461  				"getting volume attachments for machine %v",
   462  				machine))
   463  		}
   464  		attachments = append(attachments, machineAttachments...)
   465  	}
   466  	return append(errs, a.getVolumeItems(attachments)...)
   467  }
   468  
   469  func (a *API) convertStateVolumeToParams(st state.Volume) (params.VolumeInstance, error) {
   470  	volume := params.VolumeInstance{VolumeTag: st.VolumeTag().String()}
   471  
   472  	if storage, err := st.StorageInstance(); err == nil {
   473  		volume.StorageTag = storage.String()
   474  		storageInstance, err := a.storage.StorageInstance(storage)
   475  		if err != nil {
   476  			err = errors.Annotatef(err,
   477  				"getting storage instance %v for volume %v",
   478  				storage, volume.VolumeTag)
   479  			return params.VolumeInstance{}, err
   480  		}
   481  		owner := storageInstance.Owner()
   482  		// only interested in Unit for now
   483  		if unitTag, ok := owner.(names.UnitTag); ok {
   484  			volume.UnitTag = unitTag.String()
   485  		}
   486  	}
   487  	if info, err := st.Info(); err == nil {
   488  		volume.HardwareId = info.HardwareId
   489  		volume.Size = info.Size
   490  		volume.Persistent = info.Persistent
   491  		volume.VolumeId = info.VolumeId
   492  	}
   493  	status, err := st.Status()
   494  	if err != nil {
   495  		return params.VolumeInstance{}, errors.Trace(err)
   496  	}
   497  	volume.Status = common.EntityStatusFromState(status)
   498  	return volume, nil
   499  }
   500  
   501  func convertStateVolumeAttachmentsToParams(all []state.VolumeAttachment) []params.VolumeAttachment {
   502  	if len(all) == 0 {
   503  		return nil
   504  	}
   505  	result := make([]params.VolumeAttachment, len(all))
   506  	for i, one := range all {
   507  		result[i] = convertStateVolumeAttachmentToParams(one)
   508  	}
   509  	return result
   510  }
   511  
   512  func convertStateVolumeAttachmentToParams(attachment state.VolumeAttachment) params.VolumeAttachment {
   513  	result := params.VolumeAttachment{
   514  		VolumeTag:  attachment.Volume().String(),
   515  		MachineTag: attachment.Machine().String()}
   516  	if info, err := attachment.Info(); err == nil {
   517  		result.Info = params.VolumeAttachmentInfo{
   518  			info.DeviceName,
   519  			info.BusAddress,
   520  			info.ReadOnly,
   521  		}
   522  	}
   523  	return result
   524  }
   525  
   526  func (a *API) getVolumeItems(all []state.VolumeAttachment) []params.VolumeItem {
   527  	group := groupAttachmentsByVolume(all)
   528  
   529  	if len(group) == 0 {
   530  		return nil
   531  	}
   532  
   533  	result := make([]params.VolumeItem, len(group))
   534  	i := 0
   535  	for volumeTag, attachments := range group {
   536  		result[i] = a.createVolumeItem(volumeTag, attachments)
   537  		i++
   538  	}
   539  	return result
   540  }
   541  
   542  func (a *API) createVolumeItem(volumeTag string, attachments []params.VolumeAttachment) params.VolumeItem {
   543  	result := params.VolumeItem{Attachments: attachments}
   544  
   545  	tag, err := names.ParseVolumeTag(volumeTag)
   546  	if err != nil {
   547  		result.Error = common.ServerError(errors.Annotatef(err, "parsing volume tag %v", volumeTag))
   548  		return result
   549  	}
   550  	st, err := a.storage.Volume(tag)
   551  	if err != nil {
   552  		result.Error = common.ServerError(errors.Annotatef(err, "getting volume for tag %v", tag))
   553  		return result
   554  	}
   555  	volume, err := a.convertStateVolumeToParams(st)
   556  	if err != nil {
   557  		result.Error = common.ServerError(errors.Trace(err))
   558  		return result
   559  	}
   560  	result.Volume = volume
   561  	return result
   562  }
   563  
   564  // groupAttachmentsByVolume constructs map of attachments grouped by volumeTag
   565  func groupAttachmentsByVolume(all []state.VolumeAttachment) map[string][]params.VolumeAttachment {
   566  	if len(all) == 0 {
   567  		return nil
   568  	}
   569  	group := make(map[string][]params.VolumeAttachment)
   570  	for _, one := range all {
   571  		attachment := convertStateVolumeAttachmentToParams(one)
   572  		group[attachment.VolumeTag] = append(
   573  			group[attachment.VolumeTag],
   574  			attachment)
   575  	}
   576  	return group
   577  }
   578  
   579  // AddToUnit validates and creates additional storage instances for units.
   580  // This method handles bulk add operations and
   581  // a failure on one individual storage instance does not block remaining
   582  // instances from being processed.
   583  // A "CHANGE" block can block this operation.
   584  func (a *API) AddToUnit(args params.StoragesAddParams) (params.ErrorResults, error) {
   585  	// Check if changes are allowed and the operation may proceed.
   586  	blockChecker := common.NewBlockChecker(a.storage)
   587  	if err := blockChecker.ChangeAllowed(); err != nil {
   588  		return params.ErrorResults{}, errors.Trace(err)
   589  	}
   590  
   591  	if len(args.Storages) == 0 {
   592  		return params.ErrorResults{}, nil
   593  	}
   594  
   595  	serverErr := func(err error) params.ErrorResult {
   596  		if errors.IsNotFound(err) {
   597  			err = common.ErrPerm
   598  		}
   599  		return params.ErrorResult{Error: common.ServerError(err)}
   600  	}
   601  
   602  	paramsToState := func(p params.StorageConstraints) state.StorageConstraints {
   603  		s := state.StorageConstraints{Pool: p.Pool}
   604  		if p.Size != nil {
   605  			s.Size = *p.Size
   606  		}
   607  		if p.Count != nil {
   608  			s.Count = *p.Count
   609  		}
   610  		return s
   611  	}
   612  
   613  	result := make([]params.ErrorResult, len(args.Storages))
   614  	for i, one := range args.Storages {
   615  		u, err := names.ParseUnitTag(one.UnitTag)
   616  		if err != nil {
   617  			result[i] = serverErr(
   618  				errors.Annotatef(err, "parsing unit tag %v", one.UnitTag))
   619  			continue
   620  		}
   621  
   622  		err = a.storage.AddStorageForUnit(u,
   623  			one.StorageName,
   624  			paramsToState(one.Constraints))
   625  		if err != nil {
   626  			result[i] = serverErr(
   627  				errors.Annotatef(err, "adding storage %v for %v", one.StorageName, one.UnitTag))
   628  		}
   629  	}
   630  	return params.ErrorResults{Results: result}, nil
   631  }