Add size validations for volume clones

Adds the same validations as a volume add or resize to volume clones, to
ensure there is enough free space for them.
This commit is contained in:
Joshua Boniface 2024-02-02 10:29:34 -05:00
parent efc7434143
commit a95e72008e
5 changed files with 63 additions and 13 deletions

View File

@ -5972,7 +5972,11 @@ class API_Storage_Ceph_Volume_Element_Clone(Resource):
"name": "new_volume", "name": "new_volume",
"required": True, "required": True,
"helptext": "A new volume name must be specified.", "helptext": "A new volume name must be specified.",
} },
{
"name": "force",
"required": False,
},
] ]
) )
@Authenticator @Authenticator
@ -5988,6 +5992,12 @@ class API_Storage_Ceph_Volume_Element_Clone(Resource):
type: string type: string
required: true required: true
description: The name of the new cloned volume description: The name of the new cloned volume
- in: query
name: force
type: boolean
required: false
default: flase
description: Force action if clone volume size would violate 80% full soft cap on the pool
responses: responses:
200: 200:
description: OK description: OK
@ -6006,7 +6016,7 @@ class API_Storage_Ceph_Volume_Element_Clone(Resource):
id: Message id: Message
""" """
return api_helper.ceph_volume_clone( return api_helper.ceph_volume_clone(
pool, reqargs.get("new_volume", None), volume pool, reqargs.get("new_volume", None), volume, reqargs.get("force", None)
) )

View File

@ -1887,11 +1887,13 @@ def ceph_volume_add(zkhandler, pool, name, size, force_flag):
@ZKConnection(config) @ZKConnection(config)
def ceph_volume_clone(zkhandler, pool, name, source_volume): def ceph_volume_clone(zkhandler, pool, name, source_volume, force_flag):
""" """
Clone a Ceph RBD volume to a new volume on the PVC Ceph storage cluster. Clone a Ceph RBD volume to a new volume on the PVC Ceph storage cluster.
""" """
retflag, retdata = pvc_ceph.clone_volume(zkhandler, pool, source_volume, name) retflag, retdata = pvc_ceph.clone_volume(
zkhandler, pool, source_volume, name, force_flag=force_flag
)
if retflag: if retflag:
retcode = 200 retcode = 200

View File

@ -4237,13 +4237,25 @@ def cli_storage_volume_rename(pool, name, new_name):
@click.argument("pool") @click.argument("pool")
@click.argument("name") @click.argument("name")
@click.argument("new_name") @click.argument("new_name")
def cli_storage_volume_clone(pool, name, new_name): @click.option(
"-f",
"--force",
"force_flag",
is_flag=True,
default=False,
help="Force clone even if volume would violate 80% full safe free space.",
)
def cli_storage_volume_clone(pool, name, new_name, force_flag):
""" """
Clone a Ceph RBD volume with name NAME in pool POOL to name NEW_NAME in pool POOL. Clone a Ceph RBD volume with name NAME in pool POOL to name NEW_NAME in pool POOL.
PVC will prevent the clone of a volume who's new size is greater than the available free space on the pool. This cannot be overridden.
PVC will prevent the clone of a volume who's new size is greater than the 80% full safe free space on the pool. This can be overridden with the "-f"/"--force" option but this may be dangerous!
""" """
retcode, retmsg = pvc.lib.storage.ceph_volume_clone( retcode, retmsg = pvc.lib.storage.ceph_volume_clone(
CLI_CONFIG, pool, name, new_name CLI_CONFIG, pool, name, new_name, force_flag=force_flag
) )
finish(retcode, retmsg) finish(retcode, retmsg)

View File

@ -1294,15 +1294,15 @@ def ceph_volume_modify(
return retstatus, response.json().get("message", "") return retstatus, response.json().get("message", "")
def ceph_volume_clone(config, pool, volume, new_volume): def ceph_volume_clone(config, pool, volume, new_volume, force_flag=False):
""" """
Clone Ceph volume Clone Ceph volume
API endpoint: POST /api/v1/storage/ceph/volume/{pool}/{volume} API endpoint: POST /api/v1/storage/ceph/volume/{pool}/{volume}
API arguments: new_volume={new_volume API arguments: new_volume={new_volume, force_flag={force_flag}
API schema: {"message":"{data}"} API schema: {"message":"{data}"}
""" """
params = {"new_volume": new_volume} params = {"new_volume": new_volume, "force_flag": force_flag}
response = call_api( response = call_api(
config, config,
"post", "post",

View File

@ -611,13 +611,39 @@ def add_volume(zkhandler, pool, name, size, force_flag=False):
) )
def clone_volume(zkhandler, pool, name_src, name_new): def clone_volume(zkhandler, pool, name_src, name_new, force_flag=False):
# 1. Verify the volume
if not verifyVolume(zkhandler, pool, name_src): if not verifyVolume(zkhandler, pool, name_src):
return False, 'ERROR: No volume with name "{}" is present in pool "{}".'.format( return False, 'ERROR: No volume with name "{}" is present in pool "{}".'.format(
name_src, pool name_src, pool
) )
# 1. Clone the volume volume_stats_raw = zkhandler.read(("volume.stats", f"{pool}/{name_src}"))
volume_stats = dict(json.loads(volume_stats_raw))
size_bytes = volume_stats["size"]
pool_information = getPoolInformation(zkhandler, pool)
pool_total_free_bytes = int(pool_information["stats"]["free_bytes"])
if size_bytes >= pool_total_free_bytes:
return (
False,
f"ERROR: Clone volume size '{format_bytes_tohuman(size_bytes)}' is greater than the available free space in the pool ('{format_bytes_tohuman(pool_information['stats']['free_bytes'])}')",
)
# Check if we're greater than 80% utilization after the create; error if so unless we have the force flag
pool_total_bytes = (
int(pool_information["stats"]["used_bytes"]) + pool_total_free_bytes
)
pool_safe_total_bytes = int(pool_total_bytes * 0.80)
pool_safe_free_bytes = pool_safe_total_bytes - int(
pool_information["stats"]["used_bytes"]
)
if size_bytes >= pool_safe_free_bytes and not force_flag:
return (
False,
f"ERROR: Clone volume size '{format_bytes_tohuman(size_bytes)}' is greater than the safe free space in the pool ('{format_bytes_tohuman(pool_safe_free_bytes)}' for 80% full); retry with force to ignore this error",
)
# 2. Clone the volume
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"rbd copy {}/{} {}/{}".format(pool, name_src, pool, name_new) "rbd copy {}/{} {}/{}".format(pool, name_src, pool, name_new)
) )
@ -629,13 +655,13 @@ def clone_volume(zkhandler, pool, name_src, name_new):
), ),
) )
# 2. Get volume stats # 3. Get volume stats
retcode, stdout, stderr = common.run_os_command( retcode, stdout, stderr = common.run_os_command(
"rbd info --format json {}/{}".format(pool, name_new) "rbd info --format json {}/{}".format(pool, name_new)
) )
volstats = stdout volstats = stdout
# 3. Add the new volume to Zookeeper # 4. Add the new volume to Zookeeper
zkhandler.write( zkhandler.write(
[ [
(("volume", f"{pool}/{name_new}"), ""), (("volume", f"{pool}/{name_new}"), ""),