github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/storageprovisioner/filesystem_ops.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storageprovisioner
     5  
     6  import (
     7  	"path/filepath"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/environs/config"
    14  	"github.com/juju/juju/status"
    15  	"github.com/juju/juju/storage"
    16  )
    17  
    18  // createFilesystems creates filesystems with the specified parameters.
    19  func createFilesystems(ctx *context, ops map[names.FilesystemTag]*createFilesystemOp) error {
    20  	filesystemParams := make([]storage.FilesystemParams, 0, len(ops))
    21  	for _, op := range ops {
    22  		filesystemParams = append(filesystemParams, op.args)
    23  	}
    24  	paramsBySource, filesystemSources, err := filesystemParamsBySource(
    25  		ctx.modelConfig, ctx.config.StorageDir,
    26  		filesystemParams, ctx.managedFilesystemSource,
    27  	)
    28  	if err != nil {
    29  		return errors.Trace(err)
    30  	}
    31  	var reschedule []scheduleOp
    32  	var filesystems []storage.Filesystem
    33  	var statuses []params.EntityStatusArgs
    34  	for sourceName, filesystemParams := range paramsBySource {
    35  		logger.Debugf("creating filesystems: %v", filesystemParams)
    36  		filesystemSource := filesystemSources[sourceName]
    37  		validFilesystemParams, validationErrors := validateFilesystemParams(
    38  			filesystemSource, filesystemParams,
    39  		)
    40  		for i, err := range validationErrors {
    41  			if err == nil {
    42  				continue
    43  			}
    44  			statuses = append(statuses, params.EntityStatusArgs{
    45  				Tag:    filesystemParams[i].Tag.String(),
    46  				Status: status.StatusError,
    47  				Info:   err.Error(),
    48  			})
    49  			logger.Debugf(
    50  				"failed to validate parameters for %s: %v",
    51  				names.ReadableString(filesystemParams[i].Tag), err,
    52  			)
    53  		}
    54  		filesystemParams = validFilesystemParams
    55  		if len(filesystemParams) == 0 {
    56  			continue
    57  		}
    58  		results, err := filesystemSource.CreateFilesystems(filesystemParams)
    59  		if err != nil {
    60  			return errors.Annotatef(err, "creating filesystems from source %q", sourceName)
    61  		}
    62  		for i, result := range results {
    63  			statuses = append(statuses, params.EntityStatusArgs{
    64  				Tag:    filesystemParams[i].Tag.String(),
    65  				Status: status.StatusAttaching,
    66  			})
    67  			entityStatus := &statuses[len(statuses)-1]
    68  			if result.Error != nil {
    69  				// Reschedule the filesystem creation.
    70  				reschedule = append(reschedule, ops[filesystemParams[i].Tag])
    71  
    72  				// Note: we keep the status as "pending" to indicate
    73  				// that we will retry. When we distinguish between
    74  				// transient and permanent errors, we will set the
    75  				// status to "error" for permanent errors.
    76  				entityStatus.Status = status.StatusPending
    77  				entityStatus.Info = result.Error.Error()
    78  				logger.Debugf(
    79  					"failed to create %s: %v",
    80  					names.ReadableString(filesystemParams[i].Tag),
    81  					result.Error,
    82  				)
    83  				continue
    84  			}
    85  			filesystems = append(filesystems, *result.Filesystem)
    86  		}
    87  	}
    88  	scheduleOperations(ctx, reschedule...)
    89  	setStatus(ctx, statuses)
    90  	if len(filesystems) == 0 {
    91  		return nil
    92  	}
    93  	// TODO(axw) we need to be able to list filesystems in the provider,
    94  	// by environment, so that we can "harvest" them if they're
    95  	// unknown. This will take care of killing filesystems that we fail
    96  	// to record in state.
    97  	errorResults, err := ctx.config.Filesystems.SetFilesystemInfo(filesystemsFromStorage(filesystems))
    98  	if err != nil {
    99  		return errors.Annotate(err, "publishing filesystems to state")
   100  	}
   101  	for i, result := range errorResults {
   102  		if result.Error != nil {
   103  			logger.Errorf(
   104  				"publishing filesystem %s to state: %v",
   105  				filesystems[i].Tag.Id(),
   106  				result.Error,
   107  			)
   108  		}
   109  	}
   110  	for _, v := range filesystems {
   111  		updateFilesystem(ctx, v)
   112  	}
   113  	return nil
   114  }
   115  
   116  // attachFilesystems creates filesystem attachments with the specified parameters.
   117  func attachFilesystems(ctx *context, ops map[params.MachineStorageId]*attachFilesystemOp) error {
   118  	filesystemAttachmentParams := make([]storage.FilesystemAttachmentParams, 0, len(ops))
   119  	for _, op := range ops {
   120  		args := op.args
   121  		if args.Path == "" {
   122  			args.Path = filepath.Join(ctx.config.StorageDir, args.Filesystem.Id())
   123  		}
   124  		filesystemAttachmentParams = append(filesystemAttachmentParams, args)
   125  	}
   126  	paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource(
   127  		ctx.modelConfig,
   128  		ctx.config.StorageDir,
   129  		filesystemAttachmentParams,
   130  		ctx.filesystems,
   131  		ctx.managedFilesystemSource,
   132  	)
   133  	if err != nil {
   134  		return errors.Trace(err)
   135  	}
   136  	var reschedule []scheduleOp
   137  	var filesystemAttachments []storage.FilesystemAttachment
   138  	var statuses []params.EntityStatusArgs
   139  	for sourceName, filesystemAttachmentParams := range paramsBySource {
   140  		logger.Debugf("attaching filesystems: %+v", filesystemAttachmentParams)
   141  		filesystemSource := filesystemSources[sourceName]
   142  		results, err := filesystemSource.AttachFilesystems(filesystemAttachmentParams)
   143  		if err != nil {
   144  			return errors.Annotatef(err, "attaching filesystems from source %q", sourceName)
   145  		}
   146  		for i, result := range results {
   147  			p := filesystemAttachmentParams[i]
   148  			statuses = append(statuses, params.EntityStatusArgs{
   149  				Tag:    p.Filesystem.String(),
   150  				Status: status.StatusAttached,
   151  			})
   152  			entityStatus := &statuses[len(statuses)-1]
   153  			if result.Error != nil {
   154  				// Reschedule the filesystem attachment.
   155  				id := params.MachineStorageId{
   156  					MachineTag:    p.Machine.String(),
   157  					AttachmentTag: p.Filesystem.String(),
   158  				}
   159  				reschedule = append(reschedule, ops[id])
   160  
   161  				// Note: we keep the status as "attaching" to
   162  				// indicate that we will retry. When we distinguish
   163  				// between transient and permanent errors, we will
   164  				// set the status to "error" for permanent errors.
   165  				entityStatus.Status = status.StatusAttaching
   166  				entityStatus.Info = result.Error.Error()
   167  				logger.Debugf(
   168  					"failed to attach %s to %s: %v",
   169  					names.ReadableString(p.Filesystem),
   170  					names.ReadableString(p.Machine),
   171  					result.Error,
   172  				)
   173  				continue
   174  			}
   175  			filesystemAttachments = append(filesystemAttachments, *result.FilesystemAttachment)
   176  		}
   177  	}
   178  	scheduleOperations(ctx, reschedule...)
   179  	setStatus(ctx, statuses)
   180  	if err := setFilesystemAttachmentInfo(ctx, filesystemAttachments); err != nil {
   181  		return errors.Trace(err)
   182  	}
   183  	return nil
   184  }
   185  
   186  // destroyFilesystems destroys filesystems with the specified parameters.
   187  func destroyFilesystems(ctx *context, ops map[names.FilesystemTag]*destroyFilesystemOp) error {
   188  	tags := make([]names.FilesystemTag, 0, len(ops))
   189  	for tag := range ops {
   190  		tags = append(tags, tag)
   191  	}
   192  	filesystemParams, err := filesystemParams(ctx, tags)
   193  	if err != nil {
   194  		return errors.Trace(err)
   195  	}
   196  	paramsBySource, filesystemSources, err := filesystemParamsBySource(
   197  		ctx.modelConfig, ctx.config.StorageDir,
   198  		filesystemParams, ctx.managedFilesystemSource,
   199  	)
   200  	if err != nil {
   201  		return errors.Trace(err)
   202  	}
   203  	var remove []names.Tag
   204  	var reschedule []scheduleOp
   205  	var statuses []params.EntityStatusArgs
   206  	for sourceName, filesystemParams := range paramsBySource {
   207  		logger.Debugf("destroying filesystems from %q: %v", sourceName, filesystemParams)
   208  		filesystemSource := filesystemSources[sourceName]
   209  		validFilesystemParams, validationErrors := validateFilesystemParams(filesystemSource, filesystemParams)
   210  		for i, err := range validationErrors {
   211  			if err == nil {
   212  				continue
   213  			}
   214  			statuses = append(statuses, params.EntityStatusArgs{
   215  				Tag:    filesystemParams[i].Tag.String(),
   216  				Status: status.StatusError,
   217  				Info:   err.Error(),
   218  			})
   219  			logger.Debugf(
   220  				"failed to validate parameters for %s: %v",
   221  				names.ReadableString(filesystemParams[i].Tag), err,
   222  			)
   223  		}
   224  		filesystemParams = validFilesystemParams
   225  		if len(filesystemParams) == 0 {
   226  			continue
   227  		}
   228  		filesystemIds := make([]string, len(filesystemParams))
   229  		for i, filesystemParams := range filesystemParams {
   230  			filesystem, ok := ctx.filesystems[filesystemParams.Tag]
   231  			if !ok {
   232  				return errors.NotFoundf("filesystem %s", filesystemParams.Tag.Id())
   233  			}
   234  			filesystemIds[i] = filesystem.FilesystemId
   235  		}
   236  		errs, err := filesystemSource.DestroyFilesystems(filesystemIds)
   237  		if err != nil {
   238  			return errors.Trace(err)
   239  		}
   240  		for i, err := range errs {
   241  			tag := filesystemParams[i].Tag
   242  			if err == nil {
   243  				remove = append(remove, tag)
   244  				continue
   245  			}
   246  			// Failed to destroy filesystem; reschedule and update status.
   247  			reschedule = append(reschedule, ops[tag])
   248  			statuses = append(statuses, params.EntityStatusArgs{
   249  				Tag:    tag.String(),
   250  				Status: status.StatusDestroying,
   251  				Info:   err.Error(),
   252  			})
   253  		}
   254  	}
   255  	scheduleOperations(ctx, reschedule...)
   256  	setStatus(ctx, statuses)
   257  	if err := removeEntities(ctx, remove); err != nil {
   258  		return errors.Annotate(err, "removing filesystems from state")
   259  	}
   260  	return nil
   261  }
   262  
   263  // detachFilesystems destroys filesystem attachments with the specified parameters.
   264  func detachFilesystems(ctx *context, ops map[params.MachineStorageId]*detachFilesystemOp) error {
   265  	filesystemAttachmentParams := make([]storage.FilesystemAttachmentParams, 0, len(ops))
   266  	for _, op := range ops {
   267  		filesystemAttachmentParams = append(filesystemAttachmentParams, op.args)
   268  	}
   269  	paramsBySource, filesystemSources, err := filesystemAttachmentParamsBySource(
   270  		ctx.modelConfig, ctx.config.StorageDir,
   271  		filesystemAttachmentParams,
   272  		ctx.filesystems,
   273  		ctx.managedFilesystemSource,
   274  	)
   275  	if err != nil {
   276  		return errors.Trace(err)
   277  	}
   278  	var reschedule []scheduleOp
   279  	var statuses []params.EntityStatusArgs
   280  	var remove []params.MachineStorageId
   281  	for sourceName, filesystemAttachmentParams := range paramsBySource {
   282  		logger.Debugf("detaching filesystems: %+v", filesystemAttachmentParams)
   283  		filesystemSource := filesystemSources[sourceName]
   284  		errs, err := filesystemSource.DetachFilesystems(filesystemAttachmentParams)
   285  		if err != nil {
   286  			return errors.Annotatef(err, "detaching filesystems from source %q", sourceName)
   287  		}
   288  		for i, err := range errs {
   289  			p := filesystemAttachmentParams[i]
   290  			statuses = append(statuses, params.EntityStatusArgs{
   291  				Tag: p.Filesystem.String(),
   292  				// TODO(axw) when we support multiple
   293  				// attachment, we'll have to check if
   294  				// there are any other attachments
   295  				// before saying the status "detached".
   296  				Status: status.StatusDetached,
   297  			})
   298  			id := params.MachineStorageId{
   299  				MachineTag:    p.Machine.String(),
   300  				AttachmentTag: p.Filesystem.String(),
   301  			}
   302  			entityStatus := &statuses[len(statuses)-1]
   303  			if err != nil {
   304  				reschedule = append(reschedule, ops[id])
   305  				entityStatus.Status = status.StatusDetaching
   306  				entityStatus.Info = err.Error()
   307  				logger.Debugf(
   308  					"failed to detach %s from %s: %v",
   309  					names.ReadableString(p.Filesystem),
   310  					names.ReadableString(p.Machine),
   311  					err,
   312  				)
   313  				continue
   314  			}
   315  			remove = append(remove, id)
   316  		}
   317  	}
   318  	scheduleOperations(ctx, reschedule...)
   319  	setStatus(ctx, statuses)
   320  	if err := removeAttachments(ctx, remove); err != nil {
   321  		return errors.Annotate(err, "removing attachments from state")
   322  	}
   323  	for _, id := range remove {
   324  		delete(ctx.filesystemAttachments, id)
   325  	}
   326  	return nil
   327  }
   328  
   329  // filesystemParamsBySource separates the filesystem parameters by filesystem source.
   330  func filesystemParamsBySource(
   331  	environConfig *config.Config,
   332  	baseStorageDir string,
   333  	params []storage.FilesystemParams,
   334  	managedFilesystemSource storage.FilesystemSource,
   335  ) (map[string][]storage.FilesystemParams, map[string]storage.FilesystemSource, error) {
   336  	// TODO(axw) later we may have multiple instantiations (sources)
   337  	// for a storage provider, e.g. multiple Ceph installations. For
   338  	// now we assume a single source for each provider type, with no
   339  	// configuration.
   340  	filesystemSources := make(map[string]storage.FilesystemSource)
   341  	for _, params := range params {
   342  		sourceName := string(params.Provider)
   343  		if _, ok := filesystemSources[sourceName]; ok {
   344  			continue
   345  		}
   346  		if params.Volume != (names.VolumeTag{}) {
   347  			filesystemSources[sourceName] = managedFilesystemSource
   348  			continue
   349  		}
   350  		filesystemSource, err := filesystemSource(
   351  			environConfig, baseStorageDir, sourceName, params.Provider,
   352  		)
   353  		if errors.Cause(err) == errNonDynamic {
   354  			filesystemSource = nil
   355  		} else if err != nil {
   356  			return nil, nil, errors.Annotate(err, "getting filesystem source")
   357  		}
   358  		filesystemSources[sourceName] = filesystemSource
   359  	}
   360  	paramsBySource := make(map[string][]storage.FilesystemParams)
   361  	for _, params := range params {
   362  		sourceName := string(params.Provider)
   363  		filesystemSource := filesystemSources[sourceName]
   364  		if filesystemSource == nil {
   365  			// Ignore nil filesystem sources; this means that the
   366  			// filesystem should be created by the machine-provisioner.
   367  			continue
   368  		}
   369  		paramsBySource[sourceName] = append(paramsBySource[sourceName], params)
   370  	}
   371  	return paramsBySource, filesystemSources, nil
   372  }
   373  
   374  // validateFilesystemParams validates a collection of filesystem parameters.
   375  func validateFilesystemParams(
   376  	filesystemSource storage.FilesystemSource,
   377  	filesystemParams []storage.FilesystemParams,
   378  ) ([]storage.FilesystemParams, []error) {
   379  	valid := make([]storage.FilesystemParams, 0, len(filesystemParams))
   380  	results := make([]error, len(filesystemParams))
   381  	for i, params := range filesystemParams {
   382  		err := filesystemSource.ValidateFilesystemParams(params)
   383  		if err == nil {
   384  			valid = append(valid, params)
   385  		}
   386  		results[i] = err
   387  	}
   388  	return valid, results
   389  }
   390  
   391  // filesystemAttachmentParamsBySource separates the filesystem attachment parameters by filesystem source.
   392  func filesystemAttachmentParamsBySource(
   393  	environConfig *config.Config,
   394  	baseStorageDir string,
   395  	params []storage.FilesystemAttachmentParams,
   396  	filesystems map[names.FilesystemTag]storage.Filesystem,
   397  	managedFilesystemSource storage.FilesystemSource,
   398  ) (map[string][]storage.FilesystemAttachmentParams, map[string]storage.FilesystemSource, error) {
   399  	// TODO(axw) later we may have multiple instantiations (sources)
   400  	// for a storage provider, e.g. multiple Ceph installations. For
   401  	// now we assume a single source for each provider type, with no
   402  	// configuration.
   403  	filesystemSources := make(map[string]storage.FilesystemSource)
   404  	paramsBySource := make(map[string][]storage.FilesystemAttachmentParams)
   405  	for _, params := range params {
   406  		sourceName := string(params.Provider)
   407  		paramsBySource[sourceName] = append(paramsBySource[sourceName], params)
   408  		if _, ok := filesystemSources[sourceName]; ok {
   409  			continue
   410  		}
   411  		filesystem := filesystems[params.Filesystem]
   412  		if filesystem.Volume != (names.VolumeTag{}) {
   413  			filesystemSources[sourceName] = managedFilesystemSource
   414  			continue
   415  		}
   416  		filesystemSource, err := filesystemSource(
   417  			environConfig, baseStorageDir, sourceName, params.Provider,
   418  		)
   419  		if err != nil {
   420  			return nil, nil, errors.Annotate(err, "getting filesystem source")
   421  		}
   422  		filesystemSources[sourceName] = filesystemSource
   423  	}
   424  	return paramsBySource, filesystemSources, nil
   425  }
   426  
   427  func setFilesystemAttachmentInfo(ctx *context, filesystemAttachments []storage.FilesystemAttachment) error {
   428  	if len(filesystemAttachments) == 0 {
   429  		return nil
   430  	}
   431  	// TODO(axw) we need to be able to list filesystem attachments in the
   432  	// provider, by environment, so that we can "harvest" them if they're
   433  	// unknown. This will take care of killing filesystems that we fail to
   434  	// record in state.
   435  	errorResults, err := ctx.config.Filesystems.SetFilesystemAttachmentInfo(
   436  		filesystemAttachmentsFromStorage(filesystemAttachments),
   437  	)
   438  	if err != nil {
   439  		return errors.Annotate(err, "publishing filesystems to state")
   440  	}
   441  	for i, result := range errorResults {
   442  		if result.Error != nil {
   443  			return errors.Annotatef(
   444  				result.Error, "publishing attachment of %s to %s to state",
   445  				names.ReadableString(filesystemAttachments[i].Filesystem),
   446  				names.ReadableString(filesystemAttachments[i].Machine),
   447  			)
   448  		}
   449  		// Record the filesystem attachment in the context.
   450  		id := params.MachineStorageId{
   451  			MachineTag:    filesystemAttachments[i].Machine.String(),
   452  			AttachmentTag: filesystemAttachments[i].Filesystem.String(),
   453  		}
   454  		ctx.filesystemAttachments[id] = filesystemAttachments[i]
   455  		removePendingFilesystemAttachment(ctx, id)
   456  	}
   457  	return nil
   458  }
   459  
   460  func filesystemsFromStorage(in []storage.Filesystem) []params.Filesystem {
   461  	out := make([]params.Filesystem, len(in))
   462  	for i, f := range in {
   463  		paramsFilesystem := params.Filesystem{
   464  			f.Tag.String(),
   465  			"",
   466  			params.FilesystemInfo{
   467  				f.FilesystemId,
   468  				f.Size,
   469  			},
   470  		}
   471  		if f.Volume != (names.VolumeTag{}) {
   472  			paramsFilesystem.VolumeTag = f.Volume.String()
   473  		}
   474  		out[i] = paramsFilesystem
   475  	}
   476  	return out
   477  }
   478  
   479  func filesystemAttachmentsFromStorage(in []storage.FilesystemAttachment) []params.FilesystemAttachment {
   480  	out := make([]params.FilesystemAttachment, len(in))
   481  	for i, f := range in {
   482  		out[i] = params.FilesystemAttachment{
   483  			f.Filesystem.String(),
   484  			f.Machine.String(),
   485  			params.FilesystemAttachmentInfo{
   486  				f.Path,
   487  				f.ReadOnly,
   488  			},
   489  		}
   490  	}
   491  	return out
   492  }
   493  
   494  type createFilesystemOp struct {
   495  	exponentialBackoff
   496  	args storage.FilesystemParams
   497  }
   498  
   499  func (op *createFilesystemOp) key() interface{} {
   500  	return op.args.Tag
   501  }
   502  
   503  type destroyFilesystemOp struct {
   504  	exponentialBackoff
   505  	tag names.FilesystemTag
   506  }
   507  
   508  func (op *destroyFilesystemOp) key() interface{} {
   509  	return op.tag
   510  }
   511  
   512  type attachFilesystemOp struct {
   513  	exponentialBackoff
   514  	args storage.FilesystemAttachmentParams
   515  }
   516  
   517  func (op *attachFilesystemOp) key() interface{} {
   518  	return params.MachineStorageId{
   519  		MachineTag:    op.args.Machine.String(),
   520  		AttachmentTag: op.args.Filesystem.String(),
   521  	}
   522  }
   523  
   524  type detachFilesystemOp struct {
   525  	exponentialBackoff
   526  	args storage.FilesystemAttachmentParams
   527  }
   528  
   529  func (op *detachFilesystemOp) key() interface{} {
   530  	return params.MachineStorageId{
   531  		MachineTag:    op.args.Machine.String(),
   532  		AttachmentTag: op.args.Filesystem.String(),
   533  	}
   534  }