Add a new filesystem for image conversion

Create the new host_fs CLI commands and the APIs
    system host-fs-add
    system host-fs-delete

These commands will be used only for adding/removing 'image-conversion'
filesystem dedicated only for qcow2 image conversion.
'image-conversion' filesystem is optional.
It is not allowed to add/remove any other filesystem.

Change-Id: I87c876371e123ec1ba946170258401d220260e31
Partial-bug: 1819688
Depends-On: https://review.opendev.org/#/c/714936/
Signed-off-by: Elena Taivan <stefan.dinescu@windriver.com>
This commit is contained in:
Elena Taivan 2020-03-25 12:33:42 +00:00
parent d10ddf1369
commit 8099bbbbcf
7 changed files with 246 additions and 8 deletions

View File

@ -9,6 +9,8 @@
from cgtsclient.common import base
from cgtsclient import exc
CREATION_ATTRIBUTES = ['name', 'ihost_uuid', 'size']
class HostFs(base.Resource):
def __repr__(self):
@ -41,6 +43,21 @@ class HostFsManager(base.Manager):
if body:
return self.resource_class(self, body)
def delete(self, fs_id):
path = '/v1/host_fs/%s' % fs_id
return self._delete(path)
def create(self, **kwargs):
path = '/v1/host_fs'
valid_list = []
new = {}
for (key, value) in kwargs.items():
if key in CREATION_ATTRIBUTES:
new[key] = value
else:
raise exc.InvalidAttribute('%s' % key)
return self._create(path, new)
def _find_fs(cc, ihost, host_fs):
if host_fs.isdigit():

View File

@ -96,3 +96,56 @@ def do_host_fs_modify(cc, args):
raise exc.CommandError('Failed to modify filesystems')
_print_fs_list(cc, ihost.uuid)
@utils.arg('hostnameorid',
metavar='<hostname or id>',
help="Name or ID of the host [REQUIRED]")
@utils.arg('name',
metavar='<fs name>',
help="Name of the Filesystem [REQUIRED]")
def do_host_fs_delete(cc, args):
"""Delete a host filesystem."""
# Get the ihost object
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
host_fs = fs_utils._find_fs(cc, ihost, args.name)
try:
cc.host_fs.delete(host_fs.uuid)
except exc.HTTPNotFound:
raise exc.CommandError('Filesystem delete failed: host %s: '
'name %s' % (args.hostnameorid,
args.name))
@utils.arg('hostnameorid',
metavar='<hostname or id>',
help="Name or ID of the host [REQUIRED]")
@utils.arg('name',
metavar='<fs name=size>',
nargs=1,
action='append',
help="Name of the Filesystem [REQUIRED]")
def do_host_fs_add(cc, args):
"""Add a host filesystem"""
fields = {}
# Get the ihost object
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
for attr in args.name[0]:
try:
fs_name, size = attr.split("=", 1)
fields['name'] = fs_name
fields['size'] = size
except ValueError:
raise exc.CommandError('Filesystem creation attributes must be '
'FS_NAME=SIZE not "%s"' % attr)
try:
fields['ihost_uuid'] = ihost.uuid
fs = cc.host_fs.create(**fields)
except exc.HTTPNotFound:
raise exc.CommandError('Failed to create filesystem: host %s: fields %s' %
(args.hostnameorid, fields))
_print_fs_show(fs)

View File

@ -332,11 +332,151 @@ class HostFsController(rest.RestController):
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, host_fs_uuid):
"""Delete a host_fs."""
raise exception.OperationNotPermitted
"""Delete a host filesystem."""
host_fs = objects.host_fs.get_by_uuid(pecan.request.context,
host_fs_uuid).as_dict()
ihost_uuid = host_fs['ihost_uuid']
host = pecan.request.dbapi.ihost_get(ihost_uuid)
_delete(host_fs)
try:
# Host must be available to add/remove fs at runtime
if host.availability in [constants.AVAILABILITY_AVAILABLE,
constants.AVAILABILITY_DEGRADED]:
# perform rpc to conductor to perform config apply
pecan.request.rpcapi.update_host_filesystem_config(
pecan.request.context,
host=host,
filesystem_list=[host_fs['name']],)
except Exception as e:
msg = _("Failed to delete filesystem %s" % host_fs['name'])
LOG.error("%s with exception %s" % (msg, e))
pecan.request.dbapi.host_fs_create(host.id, host_fs)
raise wsme.exc.ClientSideError(msg)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(HostFs, body=HostFs)
def post(self, host_fs):
"""Create a new host_fs."""
raise exception.OperationNotPermitted
"""Create a host filesystem."""
try:
host_fs = host_fs.as_dict()
host_fs = _create(host_fs)
ihost_uuid = host_fs['ihost_uuid']
ihost_uuid.strip()
host = pecan.request.dbapi.ihost_get(ihost_uuid)
except exception.SysinvException as e:
LOG.exception(e)
raise wsme.exc.ClientSideError(_("Invalid data: failed to create a"
" filesystem"))
try:
# Host must be available to add/remove fs at runtime
if host.availability in [constants.AVAILABILITY_AVAILABLE,
constants.AVAILABILITY_DEGRADED]:
# perform rpc to conductor to perform config apply
pecan.request.rpcapi.update_host_filesystem_config(
pecan.request.context,
host=host,
filesystem_list=[host_fs['name']],)
except Exception as e:
msg = _("Failed to add filesystem name for %s" % host.hostname)
LOG.error("%s with exception %s" % (msg, e))
pecan.request.dbapi.host_fs_destroy(host_fs['id'])
raise wsme.exc.ClientSideError(msg)
return HostFs.convert_with_links(host_fs)
def _check_host_fs(host_fs):
"""Check host state"""
if host_fs['name'] not in constants.FS_CREATION_ALLOWED:
raise wsme.exc.ClientSideError(
_("Unsupported filesystem. Only the following filesystems are supported\
for creation or deletion: %s" % str(constants.FS_CREATION_ALLOWED)))
ihost_uuid = host_fs['ihost_uuid']
ihost_uuid.strip()
try:
ihost = pecan.request.dbapi.ihost_get(ihost_uuid)
except exception.ServerNotFound:
raise wsme.exc.ClientSideError(_("Invalid ihost_uuid %s"
% ihost_uuid))
if ihost.personality != constants.CONTROLLER:
raise wsme.exc.ClientSideError(_("Filesystem can only be added "
"on controller nodes"))
# Host must be online/available/degraded to add/remove
# any filesystem specified in FS_CREATION_ALLOWED
if ihost.availability not in [constants.AVAILABILITY_AVAILABLE,
constants.AVAILABILITY_ONLINE,
constants.AVAILABILITY_DEGRADED]:
raise wsme.exc.ClientSideError(_("Filesystem can only be added when "
"controller node is in available/online/degraded"))
def _create(host_fs):
"""Create a host filesystem"""
_check_host_fs(host_fs)
ihost_uuid = host_fs['ihost_uuid']
ihost_uuid.strip()
ihost = pecan.request.dbapi.ihost_get(ihost_uuid)
# See if this filesystem name already exists
current_host_fs_list = pecan.request.dbapi.host_fs_get_by_ihost(ihost_uuid)
for fs in current_host_fs_list:
if fs['name'] == host_fs['name']:
raise wsme.exc.ClientSideError(_("Filesystem name (%s) "
"already present" %
fs['name']))
requested_growth_gib = int(float(host_fs['size']))
LOG.info("Requested growth in GiB: %s for fs %s on host %s" %
(requested_growth_gib, host_fs['name'], ihost_uuid))
cgtsvg_free_space_gib = utils.get_node_cgtsvg_limit(ihost)
if requested_growth_gib > cgtsvg_free_space_gib:
msg = _("HostFs update failed: Not enough free space on %s. "
"Current free space %s GiB, "
"requested total increase %s GiB" %
(constants.LVG_CGTS_VG, cgtsvg_free_space_gib, requested_growth_gib))
LOG.warning(msg)
raise wsme.exc.ClientSideError(msg)
data = {
'name': constants.FILESYSTEM_NAME_IMAGE_CONVERSION,
'size': host_fs['size'],
'logical_volume': constants.FILESYSTEM_LV_DICT[
constants.FILESYSTEM_NAME_IMAGE_CONVERSION]
}
forihostid = ihost['id']
host_fs = pecan.request.dbapi.host_fs_create(forihostid, data)
return host_fs
def _delete(host_fs):
"""Delete a host filesystem."""
_check_host_fs(host_fs)
ihost = pecan.request.dbapi.ihost_get(host_fs['forihostid'])
try:
pecan.request.dbapi.host_fs_destroy(host_fs['id'])
except exception.HTTPNotFound:
msg = _("Deleting Filesystem failed: host %s filesystem %s"
% (ihost.hostname, host_fs['name']))
raise wsme.exc.ClientSideError(msg)

View File

@ -333,6 +333,7 @@ DEFAULT_SMALL_DISK_SIZE = 240
MINIMUM_DISK_SIZE = 154
KUBERNETES_DOCKER_STOR_SIZE = 30
IMAGE_CONVERSION_SIZE = 1
DOCKER_DISTRIBUTION_STOR_SIZE = 16
ETCD_STOR_SIZE = 5
KUBELET_STOR_SIZE = 10
@ -523,11 +524,13 @@ FILESYSTEM_NAME_EXTENSION = 'extension'
FILESYSTEM_NAME_ETCD = 'etcd'
FILESYSTEM_NAME_PATCH_VAULT = 'patch-vault'
FILESYSTEM_NAME_KUBELET = 'kubelet'
FILESYSTEM_NAME_IMAGE_CONVERSION = 'image-conversion'
FILESYSTEM_LV_DICT = {
FILESYSTEM_NAME_PLATFORM: 'platform-lv',
FILESYSTEM_NAME_BACKUP: 'backup-lv',
FILESYSTEM_NAME_SCRATCH: 'scratch-lv',
FILESYSTEM_NAME_IMAGE_CONVERSION: 'conversion-lv',
FILESYSTEM_NAME_DOCKER: 'docker-lv',
FILESYSTEM_NAME_DOCKER_DISTRIBUTION: 'dockerdistribution-lv',
FILESYSTEM_NAME_DATABASE: 'pgsql-lv',
@ -537,11 +540,13 @@ FILESYSTEM_LV_DICT = {
FILESYSTEM_NAME_KUBELET: 'kubelet-lv',
}
FS_CREATION_ALLOWED = [FILESYSTEM_NAME_IMAGE_CONVERSION]
FILESYSTEM_CONTROLLER_SUPPORTED_LIST = [
FILESYSTEM_NAME_SCRATCH,
FILESYSTEM_NAME_BACKUP,
FILESYSTEM_NAME_DOCKER,
FILESYSTEM_NAME_KUBELET,
FILESYSTEM_NAME_IMAGE_CONVERSION,
]
FILESYSTEM_WORKER_SUPPORTED_LIST = [

View File

@ -5966,6 +5966,8 @@ class ConductorManager(service.PeriodicService):
'platform::filesystem::docker::runtime',
constants.FILESYSTEM_NAME_KUBELET:
'platform::filesystem::kubelet::runtime',
constants.FILESYSTEM_NAME_IMAGE_CONVERSION:
'platform::filesystem::conversion::runtime',
}
puppet_class = [classmap.get(fs) for fs in filesystem_list]

View File

@ -233,8 +233,27 @@ class StoragePuppet(base.BasePuppet):
filters = ['"a|%s|"' % f for f in devices] + ['"r|.*|"']
return '[ %s ]' % ', '.join(filters)
def _is_image_conversion_filesystem_enabled(self, host):
filesystems = self.dbapi.host_fs_get_by_ihost(host.id)
config = {}
for fs in filesystems:
if fs.name == constants.FILESYSTEM_NAME_IMAGE_CONVERSION:
config.update({
'platform::filesystem::conversion::params::conversion_enabled': True,
'platform::filesystem::conversion::params::lv_size': fs.size,
})
return config
config.update({
'platform::filesystem::conversion::params::conversion_enabled': False,
})
return config
def _get_host_fs_config(self, host):
config = {}
conversion_config = self._is_image_conversion_filesystem_enabled(host)
config.update(conversion_config)
filesystems = self.dbapi.host_fs_get_by_ihost(host.id)
for fs in filesystems:

View File

@ -500,8 +500,9 @@ class ApiHostFSDeleteTestSuiteMixin(ApiHostFSTestCaseMixin):
# Verify appropriate exception is raised
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.FORBIDDEN)
self.assertIn("Operation not permitted", response.json['error_message'])
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
self.assertIn("Unsupported filesystem",
response.json['error_message'])
class ApiHostFSPostTestSuiteMixin(ApiHostFSTestCaseMixin):
@ -522,5 +523,6 @@ class ApiHostFSPostTestSuiteMixin(ApiHostFSTestCaseMixin):
# Verify appropriate exception is raised
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.FORBIDDEN)
self.assertIn("Operation not permitted", response.json['error_message'])
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
self.assertIn("Unsupported filesystem",
response.json['error_message'])