Merge "Create new host_fs postgres table"

This commit is contained in:
Zuul 2019-07-02 19:53:46 +00:00 committed by Gerrit Code Review
commit 77b492c2e7
13 changed files with 519 additions and 16 deletions

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv"
TIS_PATCH_VER=325
TIS_PATCH_VER=326

View File

@ -17,7 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2019 Wind River Systems, Inc.
#
@ -146,12 +146,14 @@ class AgentManager(service.PeriodicService):
self._iconfig_read_config_reported = None
self._ihost_personality = None
self._ihost_uuid = ""
self._ihost_rootfs_device = ""
self._agent_throttle = 0
self._mgmt_ip = None
self._prev_disk = None
self._prev_partition = None
self._prev_lvg = None
self._prev_pv = None
self._prev_fs = None
self._subfunctions = None
self._subfunctions_configured = False
self._notify_subfunctions_alarm_clear = False
@ -745,6 +747,7 @@ class AgentManager(service.PeriodicService):
self._ihost_uuid = ihost['uuid']
self._ihost_personality = ihost['personality']
self._mgmt_ip = ihost['mgmt_ip']
self._ihost_rootfs_device = ihost['rootfs_device']
if os.path.isfile(tsc.PLATFORM_CONF_FILE):
# read the platform config file and check for UUID
@ -1157,8 +1160,8 @@ class AgentManager(service.PeriodicService):
LOG.debug("SysInv Agent Audit running.")
if force_updates:
LOG.debug("SysInv Agent Audit force updates: (%s)" %
(', '.join(force_updates)))
LOG.info("SysInv Agent Audit force updates: (%s)" %
(', '.join(force_updates)))
self._update_ttys_dcd_status(icontext, self._ihost_uuid)
if self._agent_throttle > 5:
@ -1182,7 +1185,7 @@ class AgentManager(service.PeriodicService):
self._prev_disk = None
# if this audit is requested by conductor, clear
# previous states for disk, lvg and pv to force an update
# previous states for disk, lvg, pv and fs to force an update
if force_updates:
if constants.DISK_AUDIT_REQUEST in force_updates:
self._prev_disk = None
@ -1192,6 +1195,8 @@ class AgentManager(service.PeriodicService):
self._prev_pv = None
if constants.PARTITION_AUDIT_REQUEST in force_updates:
self._prev_partition = None
if constants.FILESYSTEM_AUDIT_REQUEST in force_updates:
self._prev_fs = None
# Update disks
idisk = self._idisk_operator.idisk_get()
@ -1250,6 +1255,55 @@ class AgentManager(service.PeriodicService):
self._prev_lvg = None
pass
# Update the filesystems
# Get the supported filesystems for this host
filesystems = []
# check if the scratch fs is supported for current host
if utils.is_filesystem_supported(constants.FILESYSTEM_NAME_SCRATCH, self._ihost_personality):
scratch_lv_size = utils.get_controller_fs_scratch_size()
data = {
'name': constants.FILESYSTEM_NAME_SCRATCH,
'size': scratch_lv_size,
'logical_volume': constants.FILESYSTEM_LV_DICT[
constants.FILESYSTEM_NAME_SCRATCH]
}
filesystems.append(data)
# check if the backup fs is supported for current host
if utils.is_filesystem_supported(constants.FILESYSTEM_NAME_BACKUP, self._ihost_personality):
backup_lv_size = utils.get_controller_fs_backup_size(self._ihost_rootfs_device)
data = {
'name': constants.FILESYSTEM_NAME_BACKUP,
'size': backup_lv_size,
'logical_volume': constants.FILESYSTEM_LV_DICT[
constants.FILESYSTEM_NAME_BACKUP]
}
filesystems.append(data)
# check if the docker fs is supported for current host
if utils.is_filesystem_supported(constants.FILESYSTEM_NAME_DOCKER, self._ihost_personality):
data = {
'name': constants.FILESYSTEM_NAME_DOCKER,
'size': constants.KUBERNETES_DOCKER_STOR_SIZE,
'logical_volume': constants.FILESYSTEM_LV_DICT[
constants.FILESYSTEM_NAME_DOCKER]
}
filesystems.append(data)
if filesystems and ((self._prev_fs is None) or (self._prev_fs != filesystems)):
try:
rpcapi.create_host_filesystems(icontext,
self._ihost_uuid,
filesystems)
self._prev_fs = filesystems
except exception.SysinvException:
LOG.exception("Sysinv Agent exception updating fs"
"conductor.")
self._prev_fs = None
pass
self._report_config_applied(icontext)
if os.path.isfile(tsc.PLATFORM_CONF_FILE):

View File

@ -526,6 +526,26 @@ FILESYSTEM_LV_DICT = {
FILESYSTEM_NAME_PATCH_VAULT: 'patch-vault-lv',
}
FILESYSTEM_CONTROLLER_SUPPORTED_LIST = [
FILESYSTEM_NAME_SCRATCH,
FILESYSTEM_NAME_BACKUP,
FILESYSTEM_NAME_DOCKER,
]
FILESYSTEM_WORKER_SUPPORTED_LIST = [
FILESYSTEM_NAME_DOCKER,
]
FILESYSTEM_STORAGE_SUPPORTED_LIST = [
FILESYSTEM_NAME_DOCKER,
]
FILESYSTEM_HOSTS_SUPPORTED_LIST_DICT = {
CONTROLLER: FILESYSTEM_CONTROLLER_SUPPORTED_LIST,
WORKER: FILESYSTEM_WORKER_SUPPORTED_LIST,
STORAGE: FILESYSTEM_STORAGE_SUPPORTED_LIST,
}
SUPPORTED_LOGICAL_VOLUME_LIST = FILESYSTEM_LV_DICT.values()
SUPPORTED_FILEYSTEM_LIST = [
@ -594,10 +614,12 @@ DISK_AUDIT_REQUEST = "audit_disk"
LVG_AUDIT_REQUEST = "audit_lvg"
PV_AUDIT_REQUEST = "audit_pv"
PARTITION_AUDIT_REQUEST = "audit_partition"
FILESYSTEM_AUDIT_REQUEST = "audit_fs"
CONTROLLER_AUDIT_REQUESTS = [DISK_AUDIT_REQUEST,
LVG_AUDIT_REQUEST,
PV_AUDIT_REQUEST,
PARTITION_AUDIT_REQUEST]
PARTITION_AUDIT_REQUEST,
FILESYSTEM_AUDIT_REQUEST]
# Interface definitions
NETWORK_TYPE_NONE = 'none'

View File

@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@ -1364,3 +1364,11 @@ class UnsupportedAssignedInterfaceDataNetworkType(Conflict):
class UnsupportedRemovedInterfaceDataNetworkType(Conflict):
message = _("Cannot remove datanetwork with type '%(network_type)s' "
"from an interface.")
class FilesystemAlreadyExists(Conflict):
message = _("A Host FS with name %(name)s already exists.")
class FilesystemNotFound(NotFound):
message = _("No Host FS with id %(fs_id)s not found")

View File

@ -1605,6 +1605,16 @@ def get_dhcp_client_iaid(mac_address):
return hwaddr[2] << 24 | hwaddr[3] << 16 | hwaddr[4] << 8 | hwaddr[5]
def is_filesystem_supported(fs, personality):
""" Check to see if a filesystem is supported for the host personality.
"""
if personality in constants.FILESYSTEM_HOSTS_SUPPORTED_LIST_DICT:
if fs in constants.FILESYSTEM_HOSTS_SUPPORTED_LIST_DICT[personality]:
return True
return False
def get_controller_fs_scratch_size():
""" Get the filesystem scratch size setup by kickstart.
"""
@ -1636,6 +1646,38 @@ def get_controller_fs_scratch_size():
return scratch_gib
def get_controller_fs_backup_size(rootfs_device):
""" Get the filesystem backup size.
"""
disk_size = get_disk_capacity_mib(rootfs_device)
disk_size = int(disk_size / 1024)
if disk_size > constants.DEFAULT_SMALL_DISK_SIZE:
LOG.debug("Disk size : %s ... large disk defaults" % disk_size)
database_storage = constants.DEFAULT_DATABASE_STOR_SIZE
cgcs_lv_size = constants.DEFAULT_CGCS_STOR_SIZE
backup_lv_size = database_storage + cgcs_lv_size + \
constants.BACKUP_OVERHEAD
elif disk_size >= constants.MINIMUM_DISK_SIZE:
LOG.debug("Disk size : %s ... small disk defaults" % disk_size)
# Due to the small size of the disk we can't provide the
# proper amount of backup space which is (database + cgcs_lv
# + BACKUP_OVERHEAD) so we are using a smaller default.
backup_lv_size = constants.DEFAULT_SMALL_BACKUP_STOR_SIZE
else:
LOG.info("Disk size : %s ... disk too small" % disk_size)
raise exception.SysinvException("Disk size requirements not met.")
return backup_lv_size
def get_cgts_vg_free_space():
"""Determine free space in cgts-vg"""

View File

@ -3299,6 +3299,61 @@ class ConductorManager(service.PeriodicService):
return
def create_host_filesystems(self, context,
ihost_uuid, fs_dict_array):
"""Create a filesystems for an ihost with the supplied data.
This method allows records for filesystems for ihost to be
created.
:param context: an admin context
:param ihost_uuid: ihost uuid unique id
:param fs_dict_array: initial values for filesystems group objects
:returns: pass or fail
"""
ihost_uuid.strip()
try:
ihost = self.dbapi.ihost_get(ihost_uuid)
except exception.ServerNotFound:
LOG.exception("Invalid ihost_uuid %s" % ihost_uuid)
return
host_fs_list = self.dbapi.host_fs_get_by_ihost(ihost_uuid)
forihostid = ihost['id']
for fs in fs_dict_array:
fs_dict = {
'forihostid': forihostid,
}
fs_dict.update(fs)
found = False
for host_fs in host_fs_list:
if host_fs.name == fs['name']:
found = True
LOG.debug("Host FS '%s' already exists" % fs['name'])
if host_fs.size != fs['size']:
LOG.info("Host FS uuid: %s changed size from %s to %s",
host_fs.uuid, host_fs.size, fs['size'])
# Update the database
try:
self.dbapi.host_fs_update(host_fs.id, fs_dict)
except Exception:
LOG.exception("Host FS Update failed")
break
if not found:
try:
LOG.info("Creating Host FS:%s:%s %d for host id %d" %
(fs_dict['name'], fs_dict['logical_volume'],
fs_dict['size'], fs_dict['forihostid']))
self.dbapi.host_fs_create(forihostid, fs_dict)
except Exception:
LOG.exception("Host FS Creation failed")
return
def _fill_partition_info(self, db_part, ipart):
db_part_dict = db_part.as_dict()
keys = ['start_mib', 'end_mib', 'size_mib', 'type_name', 'type_guid']
@ -4157,7 +4212,7 @@ class ConductorManager(service.PeriodicService):
@periodic_task.periodic_task(spacing=CONF.conductor.audit_interval)
def _agent_update_request(self, context):
"""
Check DB for inventory objects with an inconsistent state and
Check DB for inventory objects with an inconsistent state and
request an update from sysinv agent.
Currently requesting updates for:
- ipv: if state is not 'provisioned'
@ -4209,6 +4264,9 @@ class ConductorManager(service.PeriodicService):
ilvgs = self.dbapi.ilvg_get_by_ihost(host.uuid)
if not ilvgs:
update_hosts_dict(host.id, constants.LVG_AUDIT_REQUEST)
host_fs = self.dbapi.host_fs_get_by_ihost(host.uuid)
if not host_fs:
update_hosts_dict(host.id, constants.FILESYSTEM_AUDIT_REQUEST)
# Check partitions.
partitions = self.dbapi.partition_get_all()

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2019 Wind River Systems, Inc.
#
"""
@ -176,6 +176,24 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
self.make_msg('create_controller_filesystems',
rootfs_device=rootfs_device))
def create_host_filesystems(self, context, ihost_uuid, fs_dict_array):
"""Create or update the filesystem for an ihost with the supplied
data.
This method allows records for a filesystem for ihost to be
created, or updated.
:param context: an admin context
:param ihost_uuid: ihost uuid unique id
:param fs_dict_array: initial values for the filesystems
:returns: pass or fail
"""
return self.call(context,
self.make_msg('create_host_filesystems',
ihost_uuid=ihost_uuid,
fs_dict_array=fs_dict_array))
def get_ihost_by_macs(self, context, ihost_macs):
"""Finds ihost db entry based upon the mac list

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2019 Wind River Systems, Inc.
#
@ -2228,9 +2228,10 @@ class Connection(object):
:param values: A dict containing several items used to identify
and track the controller_fs.
Example:
values = {'name': constants.DEFAULT_DOCKER_STOR_SIZE,
values = {'name': constants.FILESYSTEM_NAME_DOCKER,
'size': 30,
'logical_volume': constants.FILESYSTEM_NAME_LV_DICT,
'logical_volume': constants.FILESYSTEM_LV_DICT[
constants.FILESYSTEM_NAME_DOCKER],
'replicated': False}
:returns: A controller_fs.
"""
@ -4339,3 +4340,86 @@ class Connection(object):
:param uuid: The uuid of an interface network association.
"""
@abc.abstractmethod
def host_fs_create(self, forihostid, values):
"""Create a new filesystem for a host.
:param forihostid: uuid or id of an ihost
:param values: A dict containing several items used to identify
and track the filesystem.
Example:
values = {'name': constants.FILESYSTEM_NAME_DOCKER,
'size': 30,
'logical_volume': constants.FILESYSTEM_LV_DICT[
constants.FILESYSTEM_NAME_DOCKER],
'forihostid': 1}
:returns: A filesystem.
"""
@abc.abstractmethod
def host_fs_get(self, fs_id):
"""Return a filesystem.
:param fs_id: The id or uuid of a filesystem.
:returns: A filesystem.
"""
@abc.abstractmethod
def host_fs_get_all(self, forihostid=None):
"""Return filesystems.
:param forihostid: The id or uuid of an ihost.
:returns: filesystem.
"""
@abc.abstractmethod
def host_fs_get_list(self, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of filesystems.
:param limit: Maximum number of filesystems to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
"""
@abc.abstractmethod
def host_fs_get_by_ihost(self, ihost, limit=None,
marker=None, sort_key=None,
sort_dir=None):
"""List all the filesystems for a given ihost.
:param ihost: The id or uuid of an ihost.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted
:param sort_dir: direction in which results should be sorted
(asc, desc)
:returns: A list of filesystems.
"""
@abc.abstractmethod
def host_fs_update(self, fs_id, values):
"""Update properties of a filesystem.
:param fs_id: The id or uuid of an filesystem.
:param values: Dict of values to update. May be a partial list.
Example:
values = {'name': constants.FILESYSTEM_NAME_DOCKER,
'size': 30,
'logical_volume': constants.FILESYSTEM_LV_DICT[
constants.FILESYSTEM_NAME_DOCKER
],
'forihostid': 1}
:returns: A filesystem.
"""
@abc.abstractmethod
def host_fs_destroy(self, fs_id):
"""Destroy a filesystem.
:param fs_id: The id or uuid of a filesystem.
"""

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2019 Wind River Systems, Inc.
#
"""SQLAlchemy storage backend."""
@ -148,7 +148,8 @@ def add_identity_filter(query, value,
use_sensorgroupname=False,
use_sensorname=False,
use_cluster_uuid=False,
use_pciaddr=False):
use_pciaddr=False,
use_fsname=False):
"""Adds an identity filter to a query.
Filters results by ID, if supplied value is a valid integer.
@ -189,6 +190,8 @@ def add_identity_filter(query, value,
return query.filter_by(sensorname=value)
elif use_pciaddr:
return query.filter_by(pciaddr=value)
elif use_fsname:
return query.filter_by(name=value)
else:
return query.filter_by(hostname=value)
@ -1121,6 +1124,25 @@ def add_label_filter_by_host(query, hostid):
return query.filter(models.ihost.uuid == hostid)
def add_host_fs_filter(query, value):
"""Adds an fs-specific filter to a query.
:param query: Initial query to add filter to.
:param value: Value for filtering results by.
:return: Modified query.
"""
return add_identity_filter(query, value, use_fsname=True)
def add_host_fs_filter_by_ihost(query, value):
if utils.is_int_like(value):
return query.filter_by(forihostid=value)
else:
query = query.join(models.ihost,
models.HostFs.forihostid == models.ihost.id)
return query.filter(models.ihost.uuid == value)
class Connection(api.Connection):
"""SqlAlchemy connection."""
@ -7934,3 +7956,82 @@ class Connection(api.Connection):
@objects.objectify(objects.interface_datanetwork)
def interface_datanetwork_query(self, values):
return self._interface_datanetwork_query(values)
def _host_fs_get(self, fs_id):
query = model_query(models.HostFs)
query = add_identity_filter(query, fs_id)
try:
result = query.one()
except NoResultFound:
raise exception.FilesystemNotFound(fs_id=fs_id)
return result
@objects.objectify(objects.host_fs)
def host_fs_create(self, forihostid, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
values['forihostid'] = int(forihostid)
fs = models.HostFs()
fs.update(values)
with _session_for_write() as session:
try:
session.add(fs)
session.flush()
except db_exc.DBDuplicateEntry:
raise exception.FilesystemAlreadyExists(
name=values['name'], host=forihostid)
return self._host_fs_get(values['uuid'])
@objects.objectify(objects.host_fs)
def host_fs_get_all(self, forihostid=None):
query = model_query(models.HostFs, read_deleted="no")
if forihostid:
query = query.filter_by(forihostid=forihostid)
return query.all()
@objects.objectify(objects.host_fs)
def host_fs_get(self, fs_id):
return self._host_fs_get(fs_id)
@objects.objectify(objects.host_fs)
def host_fs_get_list(self, limit=None, marker=None,
sort_key=None, sort_dir=None):
return _paginate_query(models.HostFs, limit, marker,
sort_key, sort_dir)
@objects.objectify(objects.host_fs)
def host_fs_get_by_ihost(self, ihost, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.HostFs)
query = add_host_fs_filter_by_ihost(query, ihost)
return _paginate_query(models.HostFs, limit, marker,
sort_key, sort_dir, query)
@objects.objectify(objects.host_fs)
def host_fs_update(self, fs_id, values):
with _session_for_write() as session:
query = model_query(models.HostFs, read_deleted="no",
session=session)
query = add_host_fs_filter(query, fs_id)
count = query.update(values, synchronize_session='fetch')
if count != 1:
raise exception.FilesystemNotFound(fs_id=fs_id)
return query.one()
def host_fs_destroy(self, fs_id):
with _session_for_write() as session:
# Delete physically since it has unique columns
if uuidutils.is_uuid_like(fs_id):
model_query(models.HostFs, read_deleted="no",
session=session).\
filter_by(uuid=fs_id).\
delete()
else:
model_query(models.HostFs, read_deleted="no").\
filter_by(id=fs_id).\
delete()

View File

@ -0,0 +1,61 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sqlalchemy import DateTime, String, Integer
from sqlalchemy import Column, MetaData, Table, ForeignKey
from sysinv.openstack.common import log
ENGINE = 'InnoDB'
CHARSET = 'utf8'
LOG = log.getLogger(__name__)
def upgrade(migrate_engine):
"""
This database upgrade creates a new host_fs table for storing
filesystem info for a host.
"""
meta = MetaData()
meta.bind = migrate_engine
Table('i_host',
meta,
Column('id', Integer, primary_key=True, nullable=False),
mysql_engine=ENGINE, mysql_charset=CHARSET, autoload=True)
# Define and create the host_fs table.
fs_app = Table(
'host_fs',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('name', String(255)),
Column('size', Integer),
Column('logical_volume', String(64)),
Column('forihostid', Integer,
ForeignKey('i_host.id', ondelete='CASCADE')),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
fs_app.create()
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
# Downgrade is unsupported in this release.
raise NotImplementedError('SysInv database downgrade is unsupported.')

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -1721,3 +1721,17 @@ class KubeAppReleases(Base):
app_id = Column(Integer, ForeignKey('kube_app.id', ondelete='CASCADE'))
kube_app = relationship("KubeApp", lazy="joined", join_depth=1)
UniqueConstraint('release', 'namespace', 'app_id', name='u_app_release_namespace')
class HostFs(Base):
__tablename__ = 'host_fs'
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
name = Column(String(64))
size = Column(Integer)
logical_volume = Column(String(64))
forihostid = Column(Integer, ForeignKey('i_host.id', ondelete='CASCADE'))
host = relationship("ihost", lazy="joined", join_depth=1)

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2018 Wind River Systems, Inc.
# Copyright (c) 2013-2019 Wind River Systems, Inc.
#
@ -88,6 +88,7 @@ from sysinv.objects import storage_file
from sysinv.objects import storage_external
from sysinv.objects import storage_tier
from sysinv.objects import storage_ceph_external
from sysinv.objects import host_fs
def objectify(klass):
@ -185,6 +186,7 @@ label = label.Label
kube_app = kube_app.KubeApp
kube_app_releases = kube_app_releases.KubeAppReleases
datanetwork = datanetwork.DataNetwork
host_fs = host_fs.HostFS
__all__ = (system,
cluster,
@ -254,6 +256,7 @@ __all__ = (system,
kube_app_releases,
datanetwork,
interface_network,
host_fs,
# alias objects for RPC compatibility
ihost,
ilvg,

View File

@ -0,0 +1,38 @@
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
#
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
class HostFS(base.SysinvObject):
dbapi = db_api.get_instance()
fields = {
'id': int,
'uuid': utils.str_or_none,
'name': utils.str_or_none,
'size': utils.int_or_none,
'logical_volume': utils.str_or_none,
'forihostid': int,
'ihost_uuid': utils.str_or_none,
}
_foreign_fields = {'ihost_uuid': 'host:uuid'}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
return cls.dbapi.host_fs_get(uuid)
def save_changes(self, context, updates):
self.dbapi.host_fs_update(self.uuid, updates)