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