Skip to content

Commit

Permalink
Merge pull request #3943 from brauner/2017-10-13/storage_api_volume_r…
Browse files Browse the repository at this point in the history
…ename

LXD storage api: Implement storage volume renaming
  • Loading branch information
stgraber authored Oct 17, 2017
2 parents 26400c0 + 0d0b21e commit dee8b48
Show file tree
Hide file tree
Showing 32 changed files with 736 additions and 297 deletions.
1 change: 1 addition & 0 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ type ContainerServer interface {
CreateStoragePoolVolume(pool string, volume api.StorageVolumesPost) (err error)
UpdateStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePut, ETag string) (err error)
DeleteStoragePoolVolume(pool string, volType string, name string) (err error)
RenameStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePost) (err error)

// Internal functions (for internal use)
RawQuery(method string, path string, data interface{}, queryETag string) (resp *api.Response, ETag string, err error)
Expand Down
15 changes: 15 additions & 0 deletions client/lxd_storage_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,18 @@ func (r *ProtocolLXD) DeleteStoragePoolVolume(pool string, volType string, name

return nil
}

// RenameStoragePoolVolume renames a storage volume
func (r *ProtocolLXD) RenameStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePost) error {
if !r.HasExtension("storage_api_volume_rename") {
return fmt.Errorf("The server is missing the required \"storage_api_volume_rename\" API extension")
}

// Send the request
_, _, err := r.query("POST", fmt.Sprintf("/storage-pools/%s/volumes/%s/%s", pool, volType, name), volume, "")
if err != nil {
return err
}

return nil
}
3 changes: 3 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,6 @@ This adds support for querying an LXD daemon for the system resources it has
## kernel\_limits
This adds support for setting process limits such as maximum number of open
files for the container via `nofile`. The format is `limits.kernel.[limit name]`.

## storage\_api\_volume\_rename
This adds support for renaming custom storage volumes.
15 changes: 14 additions & 1 deletion doc/rest-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2288,10 +2288,23 @@ Input:
"pool": "pool1",
"name": "vol1",
"type": "custom"
l }
}


## `/1.0/storage-pools/<pool>/volumes/<type>/<name>`
### POST
* Description: rename a storage volume on a given storage pool
* Introduced: with API extension `storage_api_volume_rename`
* Authentication: trusted
* Operation: sync
* Return: standard return value or standard error

Input:

{
"name": "vol1",
}

### GET
* Description: information about a storage volume of a given type on a storage pool
* Introduced: with API extension `storage`
Expand Down
28 changes: 28 additions & 0 deletions lxc/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ lxc storage volume show [<remote>:]<pool> <volume>
lxc storage volume create [<remote>:]<pool> <volume> [key=value]...
Create a storage volume on a storage pool.
lxc storage volume rename [<remote>:]<pool> <old name> <new name>
Rename a storage volume on a storage pool.
lxc storage volume get [<remote>:]<pool> <volume> <key>
Get storage volume configuration on a storage pool.
Expand Down Expand Up @@ -239,6 +242,13 @@ func (c *storageCmd) run(conf *config.Config, args []string) error {
}
pool := sub
return c.doStoragePoolVolumesList(conf, remote, pool, args)
case "rename":
if len(args) != 5 {
return errArgs
}
pool := sub
volume := args[3]
return c.doStoragePoolVolumeRename(client, pool, volume, args)
case "set":
if len(args) < 4 {
return errArgs
Expand Down Expand Up @@ -984,3 +994,21 @@ func (c *storageCmd) doStoragePoolVolumeEdit(client lxd.ContainerServer, pool st
}
return nil
}

func (c *storageCmd) doStoragePoolVolumeRename(client lxd.ContainerServer, pool string, volume string, args []string) error {
// Parse the input
volName, volType := c.parseVolume(volume)

// Create the storage volume entry
vol := api.StorageVolumePost{}
vol.Name = args[4]

err := client.RenameStoragePoolVolume(pool, volType, volName, vol)
if err != nil {
return err
}

fmt.Printf(i18n.G(`Renamed storage volume from "%s" to "%s"`)+"\n", volName, vol.Name)

return nil
}
1 change: 1 addition & 0 deletions lxd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
"storage_block_filesystem_btrfs",
"resources",
"kernel_limits",
"storage_api_volume_rename",
},
APIStatus: "stable",
APIVersion: version.APIVersion,
Expand Down
1 change: 1 addition & 0 deletions lxd/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ type storage interface {
StoragePoolVolumeMount() (bool, error)
StoragePoolVolumeUmount() (bool, error)
StoragePoolVolumeUpdate(writable *api.StorageVolumePut, changedConfig []string) error
StoragePoolVolumeRename(newName string) error
GetStoragePoolVolumeWritable() api.StorageVolumePut
SetStoragePoolVolumeWritable(writable *api.StorageVolumePut)

Expand Down
32 changes: 32 additions & 0 deletions lxd/storage_btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,38 @@ func (s *storageBtrfs) StoragePoolVolumeUpdate(writable *api.StorageVolumePut, c
return nil
}

func (s *storageBtrfs) StoragePoolVolumeRename(newName string) error {
logger.Infof(`Renaming BTRFS storage volume on storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

_, err := s.StoragePoolMount()
if err != nil {
return err
}

usedBy, err := storagePoolVolumeUsedByContainersGet(s.s, s.volume.Name, storagePoolVolumeTypeNameCustom)
if err != nil {
return err
}
if len(usedBy) > 0 {
return fmt.Errorf(`BTRFS storage volume "%s" on storage pool "%s" is attached to containers`,
s.volume.Name, s.pool.Name)
}

oldPath := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
newPath := getStoragePoolVolumeMountPoint(s.pool.Name, newName)
err = os.Rename(oldPath, newPath)
if err != nil {
return err
}

logger.Infof(`Renamed BTRFS storage volume on storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

return db.StoragePoolVolumeRename(s.s.DB, s.volume.Name, newName,
storagePoolVolumeTypeCustom, s.poolID)
}

func (s *storageBtrfs) GetStoragePoolVolumeWritable() api.StorageVolumePut {
return s.volume.Writable()
}
Expand Down
60 changes: 60 additions & 0 deletions lxd/storage_ceph.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,66 @@ func (s *storageCeph) StoragePoolVolumeUpdate(writable *api.StorageVolumePut, ch
return nil
}

func (s *storageCeph) StoragePoolVolumeRename(newName string) error {
logger.Infof(`Renaming CEPH storage volume on OSD storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

_, err := s.StoragePoolVolumeUmount()
if err != nil {
return err
}

usedBy, err := storagePoolVolumeUsedByContainersGet(s.s, s.volume.Name, storagePoolVolumeTypeNameCustom)
if err != nil {
return err
}
if len(usedBy) > 0 {
return fmt.Errorf(`RBD storage volume "%s" on CEPH OSD storage pool "%s" is attached to containers`,
s.volume.Name, s.pool.Name)
}

// unmap
err = cephRBDVolumeUnmap(s.ClusterName, s.OSDPoolName,
s.volume.Name, storagePoolVolumeTypeNameCustom,
s.UserName, true)
if err != nil {
logger.Errorf(`Failed to unmap RBD storage volume for `+`container "%s" on storage pool "%s": %s`,
s.volume.Name, s.pool.Name, err)
return err
}
logger.Debugf(`Unmapped RBD storage volume for container "%s" on storage pool "%s"`,
s.volume.Name, s.pool.Name)

err = cephRBDVolumeRename(s.ClusterName, s.OSDPoolName,
storagePoolVolumeTypeNameCustom, s.volume.Name,
newName, s.UserName)
if err != nil {
logger.Errorf(`Failed to rename RBD storage volume for container "%s" on storage pool "%s": %s`,
s.volume.Name, s.pool.Name, err)
return err
}
logger.Debugf(`Renamed RBD storage volume for container "%s" on storage pool "%s"`,
s.volume.Name, s.pool.Name)

// map
_, err = cephRBDVolumeMap(s.ClusterName, s.OSDPoolName,
newName, storagePoolVolumeTypeNameCustom,
s.UserName)
if err != nil {
logger.Errorf(`Failed to map RBD storage volume for container "%s" on storage pool "%s": %s`,
newName, s.pool.Name, err)
return err
}
logger.Debugf(`Mapped RBD storage volume for container "%s" on storage pool "%s"`,
newName, s.pool.Name)

logger.Infof(`Renamed CEPH storage volume on OSD storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

return db.StoragePoolVolumeRename(s.s.DB, s.volume.Name, newName,
storagePoolVolumeTypeCustom, s.poolID)
}

func (s *storageCeph) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
logger.Infof(`Updating CEPH storage pool "%s"`, s.pool.Name)

Expand Down
32 changes: 32 additions & 0 deletions lxd/storage_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,38 @@ func (s *storageDir) StoragePoolVolumeUpdate(writable *api.StorageVolumePut, cha
return nil
}

func (s *storageDir) StoragePoolVolumeRename(newName string) error {
logger.Infof(`Renaming DIR storage volume on storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

_, err := s.StoragePoolMount()
if err != nil {
return err
}

usedBy, err := storagePoolVolumeUsedByContainersGet(s.s, s.volume.Name, storagePoolVolumeTypeNameCustom)
if err != nil {
return err
}
if len(usedBy) > 0 {
return fmt.Errorf(`DIR storage volume "%s" on storage pool "%s" is attached to containers`,
s.volume.Name, s.pool.Name)
}

oldPath := getStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
newPath := getStoragePoolVolumeMountPoint(s.pool.Name, newName)
err = os.Rename(oldPath, newPath)
if err != nil {
return err
}

logger.Infof(`Renamed DIR storage volume on storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

return db.StoragePoolVolumeRename(s.s.DB, s.volume.Name, newName,
storagePoolVolumeTypeCustom, s.poolID)
}

func (s *storageDir) ContainerStorageReady(name string) bool {
containerMntPoint := getContainerMountPoint(s.pool.Name, name)
ok, _ := shared.PathIsEmpty(containerMntPoint)
Expand Down
32 changes: 32 additions & 0 deletions lxd/storage_lvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,38 @@ func (s *storageLvm) StoragePoolVolumeUpdate(writable *api.StorageVolumePut,
return nil
}

func (s *storageLvm) StoragePoolVolumeRename(newName string) error {
logger.Infof(`Renaming LVM storage volume on storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

_, err := s.StoragePoolVolumeUmount()
if err != nil {
return err
}

usedBy, err := storagePoolVolumeUsedByContainersGet(s.s, s.volume.Name, storagePoolVolumeTypeNameCustom)
if err != nil {
return err
}
if len(usedBy) > 0 {
return fmt.Errorf(`LVM storage volume "%s" on storage pool "%s" is attached to containers`,
s.volume.Name, s.pool.Name)
}

err = s.renameLVByPath(s.volume.Name, newName,
storagePoolVolumeAPIEndpointCustom)
if err != nil {
return fmt.Errorf(`Failed to rename logical volume from "%s" to "%s": %s`,
s.volume.Name, newName, err)
}

logger.Infof(`Renamed ZFS storage volume on storage pool "%s" from "%s" to "%s`,
s.pool.Name, s.volume.Name, newName)

return db.StoragePoolVolumeRename(s.s.DB, s.volume.Name, newName,
storagePoolVolumeTypeCustom, s.poolID)
}

func (s *storageLvm) ContainerStorageReady(name string) bool {
containerLvmName := containerNameToLVName(name)
poolName := s.getOnDiskPoolName()
Expand Down
4 changes: 4 additions & 0 deletions lxd/storage_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ func (s *storageMock) StoragePoolVolumeUpdate(writable *api.StorageVolumePut, ch
return nil
}

func (s *storageMock) StoragePoolVolumeRename(newName string) error {
return nil
}

func (s *storageMock) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
return nil
}
Expand Down
62 changes: 61 additions & 1 deletion lxd/storage_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,66 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {

var storagePoolVolumesTypeCmd = Command{name: "storage-pools/{name}/volumes/{type}", get: storagePoolVolumesTypeGet, post: storagePoolVolumesTypePost}

// /1.0/storage-pools/{name}/volumes/{type}/{name}
// Rename a storage volume of a given volume type in a given storage pool.
func storagePoolVolumeTypePost(d *Daemon, r *http.Request) Response {
// Get the name of the storage volume.
volumeName := mux.Vars(r)["name"]

// Get the name of the storage pool the volume is supposed to be
// attached to.
poolName := mux.Vars(r)["pool"]

// Get the name of the volume type.
volumeTypeName := mux.Vars(r)["type"]

req := api.StorageVolumePost{}

// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return BadRequest(err)
}

// Sanity checks.
if req.Name == "" {
return BadRequest(fmt.Errorf("No name provided"))
}

// We currently only allow to create storage volumes of type
// storagePoolVolumeTypeCustom. So check, that nothing else was
// requested.
if volumeTypeName != storagePoolVolumeTypeNameCustom {
return BadRequest(fmt.Errorf("Renaming storage volumes of type %s is not allowed", volumeTypeName))
}

// Retrieve ID of the storage pool (and check if the storage pool
// exists).
poolID, err := db.StoragePoolGetID(d.db, poolName)
if err != nil {
return SmartError(err)
}

// Check that the name isn't already in use.
_, err = db.StoragePoolVolumeGetTypeID(d.State().DB, req.Name,
storagePoolVolumeTypeCustom, poolID)
if err == nil || err != nil && err != db.NoSuchObjectError {
return Conflict
}

s, err := storagePoolVolumeInit(d.State(), poolName, volumeName, storagePoolVolumeTypeCustom)
if err != nil {
return SmartError(err)
}

err = s.StoragePoolVolumeRename(req.Name)
if err != nil {
return InternalError(err)
}

return EmptySyncResponse
}

// /1.0/storage-pools/{pool}/volumes/{type}/{name}
// Get storage volume of a given volume type on a given storage pool.
func storagePoolVolumeTypeGet(d *Daemon, r *http.Request) Response {
Expand Down Expand Up @@ -437,4 +497,4 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request) Response {
return EmptySyncResponse
}

var storagePoolVolumeTypeCmd = Command{name: "storage-pools/{pool}/volumes/{type}/{name:.*}", get: storagePoolVolumeTypeGet, put: storagePoolVolumeTypePut, patch: storagePoolVolumeTypePatch, delete: storagePoolVolumeTypeDelete}
var storagePoolVolumeTypeCmd = Command{name: "storage-pools/{pool}/volumes/{type}/{name:.*}", post: storagePoolVolumeTypePost, get: storagePoolVolumeTypeGet, put: storagePoolVolumeTypePut, patch: storagePoolVolumeTypePatch, delete: storagePoolVolumeTypeDelete}
2 changes: 1 addition & 1 deletion lxd/storage_volumes_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func storagePoolVolumeUsedByGet(s *state.State, volumeName string, volumeTypeNam
}

if len(volumeUsedBy) == 0 && len(profiles) == 0 {
return []string{}, err
return []string{}, nil
}

for _, pName := range profiles {
Expand Down
Loading

0 comments on commit dee8b48

Please sign in to comment.