コード例 #1
0
func (s *storageService) initStorageDriver(ctx types.Context) error {
	driverName := s.config.GetString("driver")
	if driverName == "" {
		driverName = s.config.GetString("libstorage.driver")
		if driverName == "" {
			driverName = s.config.GetString("libstorage.storage.driver")
			if driverName == "" {
				return goof.WithField(
					"service", s.name, "error getting driver name")
			}
		}
	}

	ctx.WithField("driverName", driverName).Debug("got driver name")
	driver, err := registry.NewStorageDriver(driverName)
	if err != nil {
		return err
	}

	ctx = ctx.WithValue(context.DriverKey, driver)

	if err := driver.Init(ctx, s.config); err != nil {
		return err
	}

	s.driver = driver
	return nil
}
コード例 #2
0
ファイル: util_std_controller.go プロジェクト: akutz/rexray
func waitUntilLibStorageStopped(ctx apitypes.Context, errs <-chan error) {
	ctx.Debug("waiting until libStorage is stopped")

	// if there is no err channel then do not wait until libStorage is stopped
	// as the absence of the err channel means libStorage was not started in
	// embedded mode
	if errs == nil {
		ctx.Debug("done waiting on err chan; err chan is nil")
		return
	}

	// in a goroutine, range over the apiserver.Close channel until it's closed
	for range apiserver.Close() {
	}
	ctx.Debug("done sending close signals to libStorage")

	// block until the err channel is closed
	for err := range errs {
		if err == nil {
			continue
		}
		ctx.WithError(err).Error("error on closing libStorage server")
	}
	ctx.Debug("done waiting on err chan")
}
コード例 #3
0
// 	// VolumeDetach detaches a volume.
func (d *driver) VolumeDetach(
	ctx types.Context,
	volumeID string,
	opts *types.VolumeDetachOpts) (*types.Volume, error) {
	fields := eff(map[string]interface{}{
		"moduleName": ctx,
		"volumeId":   volumeID,
	})

	if volumeID == "" {
		return nil, goof.WithFields(fields, "volumeId is required for VolumeDetach")
	}
	vols, err := d.getVolume(ctx, volumeID, "", types.VolAttReqTrue)
	if err != nil {
		return nil, err
	}

	resp := volumeattach.Delete(
		d.client, vols[0].Attachments[0].InstanceID.ID, volumeID)
	if resp.Err != nil {
		return nil, goof.WithFieldsE(fields, "error detaching volume", resp.Err)
	}
	ctx.WithFields(fields).Debug("waiting for volume to detach")
	volume, err := d.waitVolumeAttachStatus(ctx, volumeID, false)
	if err == nil {
		return volume, nil
	}
	log.WithFields(fields).Debug("volume detached")
	return nil, nil
}
コード例 #4
0
func (d *idm) Mount(
	ctx types.Context,
	volumeID, volumeName string,
	opts *types.VolumeMountOpts) (string, *types.Volume, error) {

	opts.Preempt = d.preempt()

	fields := log.Fields{
		"volumeName": volumeName,
		"volumeID":   volumeID,
		"opts":       opts}
	ctx.WithFields(fields).Debug("mounting volume")

	mp, vol, err := d.IntegrationDriver.Mount(
		ctx.Join(d.ctx), volumeID, volumeName, opts)
	if err != nil {
		return "", nil, err
	}

	// if the volume has attachments assign the new mount point to the
	// MountPoint field of the first attachment element
	if len(vol.Attachments) > 0 {
		vol.Attachments[0].MountPoint = mp
	}

	d.incCount(volumeName)
	return mp, vol, err
}
コード例 #5
0
ファイル: schema.go プロジェクト: emccode/libstorage
// Validate validates the provided data (d) against the provided schema (s).
func Validate(ctx types.Context, s, d []byte) error {

	if ctx == nil {
		log.StandardLogger().WithFields(log.Fields{
			"schema": string(s),
			"body":   string(d),
		}).Debug("validating schema")
	} else {
		ctx.WithFields(log.Fields{
			"schema": string(s),
			"body":   string(d),
		}).Debug("validating schema")
	}

	validator, err := getSchemaValidator(s)
	if err != nil {
		return err
	}

	if len(d) == 0 {
		d = []byte("{}")
	}

	data, err := ucl.Parse(bytes.NewReader(d))
	if err != nil {
		return err
	}
	return validator.Validate(data)
}
コード例 #6
0
func (c *client) NextDevice(
	ctx types.Context,
	opts types.Store) (string, error) {

	if c.isController() {
		return "", utils.NewUnsupportedForClientTypeError(
			c.clientType, "NextDevice")
	}

	if supported, _ := c.Supported(ctx, opts); !supported {
		return "", errExecutorNotSupported
	}

	ctx = context.RequireTX(ctx.Join(c.ctx))

	serviceName, ok := context.ServiceName(ctx)
	if !ok {
		return "", goof.New("missing service name")
	}

	si, err := c.getServiceInfo(serviceName)
	if err != nil {
		return "", err
	}
	driverName := si.Driver.Name

	out, err := c.runExecutor(ctx, driverName, types.LSXCmdNextDevice)
	if err != nil {
		return "", err
	}

	ctx.Debug("xli nextdevice success")
	return gotil.Trim(string(out)), nil
}
コード例 #7
0
ファイル: registry_os.go プロジェクト: emccode/libstorage
func (d *odm) IsMounted(
	ctx types.Context,
	mountPoint string,
	opts types.Store) (bool, error) {

	return d.OSDriver.IsMounted(ctx.Join(d.Context), mountPoint, opts)
}
コード例 #8
0
ファイル: vfs_storage.go プロジェクト: emccode/libstorage
func (d *driver) Init(ctx types.Context, config gofig.Config) error {
	d.ctx = ctx
	d.config = config

	d.volPath = vfs.VolumesDirPath(config)
	d.snapPath = vfs.SnapshotsDirPath(config)

	ctx.WithField("vfs.root.path", vfs.RootDir(config)).Info("vfs.root")

	os.MkdirAll(d.volPath, 0755)
	os.MkdirAll(d.snapPath, 0755)

	d.volJSONGlobPatt = fmt.Sprintf("%s/*.json", d.volPath)
	d.snapJSONGlobPatt = fmt.Sprintf("%s/*.json", d.snapPath)

	volJSONPaths, err := d.getVolJSONs()
	if err != nil {
		return nil
	}
	d.volCount = int64(len(volJSONPaths)) - 1

	snapJSONPaths, err := d.getSnapJSONs()
	if err != nil {
		return nil
	}
	d.snapCount = int64(len(snapJSONPaths)) - 1

	return nil
}
コード例 #9
0
ファイル: mock_driver.go プロジェクト: emccode/libstorage
func (d *driver) VolumeRemove(
	ctx types.Context,
	volumeID string,
	opts types.Store) error {

	ctx.WithFields(log.Fields{
		"volumeID": volumeID,
	}).Debug("mockDriver.VolumeRemove")

	var xToRemove int
	var volume *types.Volume
	for x, v := range d.volumes {
		if strings.ToLower(v.ID) == strings.ToLower(volumeID) {
			volume = v
			xToRemove = x
			break
		}
	}

	if volume == nil {
		return utils.NewNotFoundError(volumeID)
	}

	d.volumes = append(d.volumes[:xToRemove], d.volumes[xToRemove+1:]...)

	return nil
}
コード例 #10
0
func (c *client) downloadExecutor(ctx types.Context) error {

	if c.isController() {
		return utils.NewUnsupportedForClientTypeError(
			c.clientType, "downloadExecutor")
	}

	ctx.Debug("downloading executor")

	f, err := os.OpenFile(
		types.LSX.String(),
		os.O_CREATE|os.O_RDWR|os.O_TRUNC,
		0755)
	if err != nil {
		return err
	}

	defer f.Close()

	rdr, err := c.APIClient.ExecutorGet(ctx, types.LSX.Name())
	n, err := io.Copy(f, rdr)
	if err != nil {
		return err
	}

	if err := f.Sync(); err != nil {
		return err
	}

	ctx.WithField("bytes", n).Debug("downloaded executor")
	return nil
}
コード例 #11
0
func (d *idm) initPathCache(ctx types.Context) {
	if !d.pathCacheEnabled() {
		ctx.Info("path cache initializion disabled")
		return
	}

	if name, ok := context.ServiceName(ctx); !ok || name == "" {
		ctx.Info("path cache initializion disabled; no service name in ctx")
		return
	}

	f := func(async bool) {
		ctx.WithField("async", async).Info("initializing the path cache")
		_, err := d.List(ctx, apiutils.NewStoreWithData(initPathCacheMap))
		if err != nil {
			ctx.WithField("async", async).WithError(err).Error(
				"error initializing the path cache")
		} else {
			ctx.WithField("async", async).Debug("initialized the path cache")
		}
	}

	if d.pathCacheAsync() {
		go f(true)
	} else {
		f(false)
	}
}
コード例 #12
0
ファイル: vbox_storage.go プロジェクト: emccode/libstorage
// Init initializes the driver.
func (d *driver) Init(ctx types.Context, config gofig.Config) error {
	d.config = config

	fields := map[string]interface{}{
		"provider":        vbox.Name,
		"moduleName":      vbox.Name,
		"endpoint":        d.endpoint(),
		"userName":        d.username(),
		"tls":             d.tls(),
		"volumePath":      d.volumePath(),
		"controllerName":  d.controllerName(),
		"machineNameOrId": d.machineNameID(""),
	}

	ctx.Info("initializing driver: ", fields)
	d.vbox = vboxc.New(d.username(), d.password(),
		d.endpoint(), d.tls(), d.controllerName())

	if err := d.vbox.Logon(); err != nil {
		return goof.WithFieldsE(fields,
			"error logging in", err)
	}

	ctx.WithFields(fields).Info("storage driver initialized")
	return nil
}
コード例 #13
0
func (c *client) getExecutorChecksum(ctx types.Context) (string, error) {

	if c.isController() {
		return "", utils.NewUnsupportedForClientTypeError(
			c.clientType, "getExecutorChecksum")
	}

	ctx.Debug("getting executor checksum")

	f, err := os.Open(types.LSX.String())
	if err != nil {
		return "", err
	}
	defer f.Close()

	h := md5.New()
	buf := make([]byte, 1024)
	for {
		n, err := f.Read(buf)
		if err == io.EOF {
			break
		}
		if err != nil {
			return "", err
		}
		if _, err := h.Write(buf[:n]); err != nil {
			return "", err
		}
	}

	sum := fmt.Sprintf("%x", h.Sum(nil))
	ctx.WithField("localChecksum", sum).Debug("got local executor checksum")
	return sum, nil
}
コード例 #14
0
ファイル: vbox_storage.go プロジェクト: emccode/libstorage
func (d *driver) findMachineByNameOrID(
	ctx types.Context, nameOrID string) (*vboxc.Machine, error) {

	ctx.WithField("nameOrID", nameOrID).Debug("finding local machine")

	m, err := d.vbox.FindMachine(nameOrID)
	if err != nil {
		return nil, err
	}
	if m == nil {
		return nil, goof.New("could not find machine")
	}

	if id, err := m.GetID(); err == nil {
		m.ID = id
	} else {
		return nil, err
	}

	if name, err := m.GetName(); err == nil {
		m.Name = name
	} else {
		return nil, err
	}

	return m, nil
}
コード例 #15
0
func (d *idm) List(
	ctx types.Context,
	opts types.Store) ([]types.VolumeMapping, error) {

	fields := log.Fields{
		"opts": opts}
	ctx.WithFields(fields).Debug("listing volumes")

	volMaps, err := d.IntegrationDriver.List(ctx.Join(d.ctx), opts)
	if err != nil {
		return nil, err
	}

	volMapsWithNames := []types.VolumeMapping{}
	for _, vm := range volMaps {
		if vm.VolumeName() != "" {
			volMapsWithNames = append(volMapsWithNames, vm)
		}
	}

	if !d.pathCacheEnabled() {
		return volMapsWithNames, nil
	}

	for _, vm := range volMapsWithNames {
		vmn := vm.VolumeName()
		if !d.isCounted(vmn) && vm.MountPoint() != "" {
			d.initCount(vmn)
		}
	}

	return volMapsWithNames, nil
}
コード例 #16
0
ファイル: registry_os.go プロジェクト: emccode/libstorage
func (d *odm) Unmount(
	ctx types.Context,
	mountPoint string,
	opts types.Store) error {

	return d.OSDriver.Unmount(ctx.Join(d.Context), mountPoint, opts)
}
コード例 #17
0
// Handle is the type's Handler function.
func (h *postArgsHandler) Handle(
	ctx types.Context,
	w http.ResponseWriter,
	req *http.Request,
	store types.Store) error {

	reqObj := ctx.Value("reqObj")
	if reqObj == nil {
		return fmt.Errorf("missing request object")
	}

	v := reflect.ValueOf(reqObj).Elem()
	t := v.Type()

	for i := 0; i < v.NumField(); i++ {
		ft := t.Field(i)
		fv := v.Field(i).Interface()

		switch tfv := fv.(type) {
		case nil:
			// do nothing
		case map[string]interface{}:
			store.Set(getFieldName(ft), utils.NewStoreWithData(tfv))
		default:
			// add it to the store
			store.Set(getFieldName(ft), fv)
		}
	}

	return h.handler(ctx, w, req, store)
}
コード例 #18
0
ファイル: mock_driver.go プロジェクト: emccode/libstorage
func (d *driver) SnapshotRemove(
	ctx types.Context,
	snapshotID string,
	opts types.Store) error {

	ctx.WithFields(log.Fields{
		"snapshotID": snapshotID,
	}).Debug("mockDriver.SnapshotRemove")

	var xToRemove int
	var snapshot *types.Snapshot
	for x, s := range d.snapshots {
		if strings.ToLower(s.ID) == strings.ToLower(snapshotID) {
			snapshot = s
			xToRemove = x
			break
		}
	}

	if snapshot == nil {
		return utils.NewNotFoundError(snapshotID)
	}

	d.snapshots = append(d.snapshots[:xToRemove], d.snapshots[xToRemove+1:]...)

	return nil
}
コード例 #19
0
ファイル: docker.go プロジェクト: emccode/libstorage
// Inspect returns a specific volume as identified by the provided
// volume name.
func (d *driver) Inspect(
	ctx types.Context,
	volumeName string,
	opts types.Store) (types.VolumeMapping, error) {

	fields := log.Fields{
		"volumeName": volumeName,
		"opts":       opts}
	ctx.WithFields(fields).Info("inspecting volume")

	objs, err := d.List(ctx, opts)
	if err != nil {
		return nil, err
	}

	var obj types.VolumeMapping
	for _, o := range objs {
		if strings.ToLower(volumeName) == strings.ToLower(o.VolumeName()) {
			obj = o
			break
		}
	}

	if obj == nil {
		return nil, utils.NewNotFoundError(volumeName)
	}

	fields = log.Fields{
		"volumeName": volumeName,
		"volume":     obj}
	ctx.WithFields(fields).Info("volume inspected")

	return obj, nil
}
コード例 #20
0
// Handle is the type's Handler function.
func (h *queryParamsHandler) Handle(
	ctx types.Context,
	w http.ResponseWriter,
	req *http.Request,
	store types.Store) error {

	for k, v := range req.URL.Query() {
		ctx.WithFields(log.Fields{
			"key":        k,
			"value":      v,
			"len(value)": len(v),
		}).Debug("query param")
		switch len(v) {
		case 0:
			store.Set(k, true)
		case 1:
			if len(v[0]) == 0 {
				store.Set(k, true)
			} else {
				if i, err := strconv.ParseInt(v[0], 10, 64); err == nil {
					store.Set(k, i)
				} else if b, err := strconv.ParseBool(v[0]); err == nil {
					store.Set(k, b)
				} else {
					store.Set(k, v[0])
				}
			}
		default:
			store.Set(k, v)
		}
	}
	return h.handler(ctx, w, req, store)
}
コード例 #21
0
ファイル: docker.go プロジェクト: emccode/libstorage
// Create will create a new volume with the volumeName and opts.
func (d *driver) Create(
	ctx types.Context,
	volumeName string,
	opts *types.VolumeCreateOpts) (*types.Volume, error) {

	if volumeName == "" {
		return nil, goof.New("missing volume name or ID")
	}

	optsNew := &types.VolumeCreateOpts{}
	az := d.availabilityZone()
	optsNew.AvailabilityZone = &az
	i, _ := strconv.Atoi(d.size())
	size := int64(i)
	optsNew.Size = &size
	volumeType := d.volumeType()
	optsNew.Type = &volumeType
	io, _ := strconv.Atoi(d.iops())
	IOPS := int64(io)
	optsNew.IOPS = &IOPS

	if opts.Opts.IsSet("availabilityZone") {
		az = opts.Opts.GetString("availabilityZone")
	}
	if opts.Opts.IsSet("size") {
		size = opts.Opts.GetInt64("size")
	}
	if opts.Opts.IsSet("volumeType") {
		volumeType = opts.Opts.GetString("volumeType")
	}
	if opts.Opts.IsSet("type") {
		volumeType = opts.Opts.GetString("type")
	}
	if opts.Opts.IsSet("iops") {
		IOPS = opts.Opts.GetInt64("iops")
	}

	optsNew.Opts = opts.Opts

	ctx.WithFields(log.Fields{
		"volumeName":       volumeName,
		"availabilityZone": az,
		"size":             size,
		"volumeType":       volumeType,
		"IOPS":             IOPS,
		"opts":             opts}).Info("creating volume")

	client := context.MustClient(ctx)
	vol, err := client.Storage().VolumeCreate(ctx, volumeName, optsNew)
	if err != nil {
		return nil, err
	}

	ctx.WithFields(log.Fields{
		"volumeName": volumeName,
		"vol":        vol}).Info("volume created")

	return vol, nil
}
コード例 #22
0
ファイル: vfs_storage.go プロジェクト: emccode/libstorage
func (d *driver) Login(ctx types.Context) (interface{}, error) {
	sess := &session{}
	if iid, ok := context.InstanceID(ctx); ok {
		sess.id = iid.String()
	}
	ctx.Debugf("vfs login=%s", sess.id)
	return sess, nil
}
コード例 #23
0
ファイル: registry_os.go プロジェクト: emccode/libstorage
func (d *odm) Mounts(
	ctx types.Context,
	deviceName, mountPoint string,
	opts types.Store) ([]*types.MountInfo, error) {
	ctx = ctx.Join(d.Context)

	return d.OSDriver.Mounts(ctx.Join(d.Context), deviceName, mountPoint, opts)
}
コード例 #24
0
ファイル: registry_os.go プロジェクト: emccode/libstorage
func (d *odm) Mount(
	ctx types.Context,
	deviceName, mountPoint string,
	opts *types.DeviceMountOpts) error {
	ctx = ctx.Join(d.Context)

	return d.OSDriver.Mount(ctx.Join(d.Context), deviceName, mountPoint, opts)
}
コード例 #25
0
func (c *client) requireCtx(ctx types.Context) types.Context {
	if ctx == nil {
		ctx = c.ctx
	} else {
		ctx = ctx.Join(c.ctx)
	}
	return context.RequireTX(ctx)
}
コード例 #26
0
ファイル: ebs_storage.go プロジェクト: emccode/libstorage
// VolumeDetach detaches a volume.
func (d *driver) VolumeDetach(
	ctx types.Context,
	volumeID string,
	opts *types.VolumeDetachOpts) (*types.Volume, error) {
	// review volume with attachments to any host
	ec2vols, err := d.getVolume(ctx, volumeID, "")
	if err != nil {
		return nil, goof.WithError("error getting volume", err)
	}
	volumes, convErr := d.toTypesVolume(
		ctx, ec2vols, types.VolAttReqTrue)
	if convErr != nil {
		return nil, goof.WithError("error converting to types.Volume", convErr)
	}

	// no volumes to detach
	if len(volumes) == 0 {
		return nil, errNoVolReturned
	}

	// volume has no attachments
	if len(volumes[0].Attachments) == 0 {
		return nil, errVolAlreadyDetached
	}

	dvInput := &awsec2.DetachVolumeInput{
		VolumeId: &volumeID,
		Force:    &opts.Force,
	}

	// Detach volume using EC2 API call
	if _, err = mustSession(ctx).DetachVolume(dvInput); err != nil {
		return nil, goof.WithFieldsE(
			log.Fields{
				"provider": d.Name(),
				"volumeID": volumeID}, "error detaching volume", err)
	}

	if err = d.waitVolumeComplete(ctx, volumeID, waitVolumeDetach); err != nil {
		return nil, goof.WithError("error waiting for volume detach", err)
	}

	ctx.Info("detached volume", volumeID)

	// check if successful detach
	detachedVol, err := d.VolumeInspect(
		ctx, volumeID, &types.VolumeInspectOpts{
			Attachments: types.VolAttReqTrue,
			Opts:        opts.Opts,
		})
	if err != nil {
		return nil, goof.WithError("error getting volume", err)
	}

	return detachedVol, nil
}
コード例 #27
0
func (c *client) InstanceInspect(
	ctx types.Context, service string) (*types.Instance, error) {

	si, err := c.ServiceInspect(
		ctx.WithValue(ctxInstanceForSvc, &ctxInstanceForSvcT{}), service)
	if err != nil {
		return nil, err
	}
	return si.Instance, nil
}
コード例 #28
0
ファイル: vbox_storage.go プロジェクト: emccode/libstorage
func (d *driver) createVolume(
	ctx types.Context, name string, size int64) (*vboxc.Medium, error) {

	if name == "" {
		return nil, goof.New("name is empty")
	}
	path := filepath.Join(d.volumePath(), name)
	ctx.WithField("path", path).Debug("creating vmdk")
	return d.vbox.CreateMedium("vmdk", path, size)
}
コード例 #29
0
ファイル: efs_storage.go プロジェクト: emccode/libstorage
// VolumeAttach attaches a volume and provides a token clients can use
// to validate that device has appeared locally.
func (d *driver) VolumeAttach(
	ctx types.Context,
	volumeID string,
	opts *types.VolumeAttachOpts) (*types.Volume, string, error) {

	svc := mustSession(ctx)

	vol, err := d.VolumeInspect(ctx, volumeID,
		&types.VolumeInspectOpts{Attachments: types.VolAttReqTrue})
	if err != nil {
		return nil, "", err
	}

	iid := context.MustInstanceID(ctx)

	var ma *types.VolumeAttachment
	for _, att := range vol.Attachments {
		if att.InstanceID.ID == iid.ID {
			ma = att
			break
		}
	}

	// No mount targets were found
	if ma == nil {

		secGrpIDs := d.secGroups
		if v, ok := iid.Fields[efs.InstanceIDFieldSecurityGroups]; ok {
			iSecGrpIDs := strings.Split(v, ";")
			ctx.WithField("secGrpIDs", iSecGrpIDs).Debug(
				"using instance security group IDs")
			secGrpIDs = iSecGrpIDs
		}

		if len(secGrpIDs) == 0 {
			return nil, "", errInvalidSecGroups
		}

		request := &awsefs.CreateMountTargetInput{
			FileSystemId:   aws.String(vol.ID),
			SubnetId:       aws.String(iid.ID),
			SecurityGroups: aws.StringSlice(secGrpIDs),
		}
		// TODO(mhrabovcin): Should we block here until MountTarget is in
		// "available" LifeCycleState? Otherwise mount could fail until creation
		//  is completed.
		_, err = svc.CreateMountTarget(request)
		// Failed to create mount target
		if err != nil {
			return nil, "", err
		}
	}

	return vol, "", err
}
コード例 #30
0
ファイル: efs_storage.go プロジェクト: emccode/libstorage
// Volumes returns all volumes or a filtered list of volumes.
func (d *driver) Volumes(
	ctx types.Context,
	opts *types.VolumesOpts) ([]*types.Volume, error) {

	svc := mustSession(ctx)

	fileSystems, err := d.getAllFileSystems(svc)
	if err != nil {
		return nil, err
	}

	var volumesSD []*types.Volume
	for _, fileSystem := range fileSystems {
		// Make sure that name is popullated
		if fileSystem.Name == nil {
			ctx.WithFields(log.Fields{
				"filesystemid": *fileSystem.FileSystemId,
			}).Warn("missing EFS filesystem name")
			continue
		}

		// Only volumes with partition prefix
		if !strings.HasPrefix(*fileSystem.Name, d.tag+tagDelimiter) {
			continue
		}

		// Only volumes in "available" state
		if *fileSystem.LifeCycleState != awsefs.LifeCycleStateAvailable {
			continue
		}

		volumeSD := &types.Volume{
			Name:        d.getPrintableName(*fileSystem.Name),
			ID:          *fileSystem.FileSystemId,
			Size:        *fileSystem.SizeInBytes.Value,
			Attachments: nil,
		}

		var atts []*types.VolumeAttachment
		if opts.Attachments.Requested() {
			atts, err = d.getVolumeAttachments(
				ctx, *fileSystem.FileSystemId, opts.Attachments)
			if err != nil {
				return nil, err
			}
		}
		if len(atts) > 0 {
			volumeSD.Attachments = atts
		}
		volumesSD = append(volumesSD, volumeSD)
	}

	return volumesSD, nil
}