[Kimchi-devel] [PATCH 3/4] Host's software update: Update backend.
Sheldon
shaohef at linux.vnet.ibm.com
Tue Feb 11 06:35:45 UTC 2014
On 02/10/2014 10:50 AM, Paulo Vital wrote:
> Update model and mockmodel to support backend opertions.
> Add new file implementing backend operations, with four new classes:
>
> 1) SoftwareUpdate (object): Class to represent and operate with OS software
> update system in Kimchi's perspective. It's agnostic to host's package management
> system, and can execute all operations necessary: get all packages to update,
> get information about one package and execute the update. This class will load
> in runtime the necessary classes to work with the host's package management:
> YumUpdate for YUM systems based, AptUpdate for APT systems based and ZypperUpdate
> for Zypper systems based.
>
> 2) YumUpdate (object): Class to represent and operate with YUM. Loaded only on
> those systems that supports YUM, it's responsible to connect and collect
> information of the packages to be updated. Also it's responsible to execute the
> update of the system.
>
> 3) AptUpdate (object): Class to represent and operate with APT. Loaded only on
> those systems that supports APT, it's responsible to connect and collect
> information of the packages to be updated. Also it's responsible to execute the
> update of the system.
>
> 4) ZypperUpdate (object): Class to represent and operate with Zypper. Loaded only
> on those systems that supports Zypper, it's responsible to connect and collect
> information of the packages to be updated. Also it's responsible to execute the
> update of the system.
>
> Signed-off-by: Paulo Vital <pvital at linux.vnet.ibm.com>
> Signed-off-by: Ramon Medeiros <ramonn at linux.vnet.ibm.com>
> ---
> src/kimchi/mockmodel.py | 46 +++++++++
> src/kimchi/model/host.py | 20 +++-
> src/kimchi/swupdate.py | 264 +++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 329 insertions(+), 1 deletion(-)
> create mode 100644 src/kimchi/swupdate.py
>
> diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
> index 4e276eb..235c72f 100644
> --- a/src/kimchi/mockmodel.py
> +++ b/src/kimchi/mockmodel.py
> @@ -75,6 +75,7 @@ class MockModel(object):
> self._mock_storagepools = {'default': MockStoragePool('default')}
> self._mock_networks = {'default': MockNetwork('default')}
> self._mock_interfaces = self.dummy_interfaces()
> + self._mock_swupdate = MockPackageUpdate()
> self.next_taskid = 1
> self.storagepool_activate('default')
>
> @@ -665,6 +666,16 @@ class MockModel(object):
> 'display_proxy_port':
> kconfig.get('display', 'display_proxy_port')}
>
> + def packageupdate_get_list(self):
> + return self._mock_swupdate.getUpdates()
> +
> + def packageupdate_lookup(self, pkg_name):
> + return self._mock_swupdate.getUpdate(pkg_name)
> +
> + def packageupdate_update(self, args=None):
> + kimchi_log.info('Host is going to be updated.')
> + self._mock_swupdate.doUpdate()
> +
>
> class MockVMTemplate(VMTemplate):
> def __init__(self, args, mockmodel_inst=None):
> @@ -825,6 +836,41 @@ class MockVMScreenshot(VMScreenshot):
> image.save(thumbnail)
>
>
> +class MockPackageUpdate(object):
> + def __init__(self):
> + self._packages = {'udevmountd': {'repo': 'openSUSE-13.1-Update',
> + 'version': '0.81.5-14.1',
> + 'arch': 'x86_64',
> + 'package_name': 'udevmountd'},
> + 'sysconfig-network': {'repo': 'openSUSE-13.1-Extras',
> + 'version': '0.81.5-14.1',
> + 'arch': 'x86_64',
> + 'package_name': 'sysconfig-network'},
> + 'libzypp': {'repo': 'openSUSE-13.1-Update',
> + 'version': '13.9.0-10.1',
> + 'arch': 'noarch',
> + 'package_name': 'libzypp'}}
> + self._num2update = 3
> +
> + def getUpdates(self):
> + return self._packages
> +
> + def getUpdate(self, name):
> + if not name in self._packages.keys():
> + raise NotFoundError("Package %s is not marked to be updated." %
> + name)
> + return self._packages[name]
> +
> + def getNumOfUpdates(self):
> + return self._num2update
> +
> + def doUpdate(self):
> + if self._num2update == 0:
> + kimchi_log.info("No packages marked for update")
> + raise OperationFailed("No packages marked for update")
> + sleep(60)
seems 60 is long for a test case.
if you add a test case in test_rest.py
> +
> +
> def get_mock_environment():
> model = MockModel()
> for i in xrange(5):
> diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py
> index a3d9e38..1bccd53 100644
> --- a/src/kimchi/model/host.py
> +++ b/src/kimchi/model/host.py
> @@ -30,10 +30,12 @@ from cherrypy.process.plugins import BackgroundTask
>
> from kimchi import disks
> from kimchi import netinfo
> +from kimchi import swupdate
> from kimchi.basemodel import Singleton
> from kimchi.exception import NotFoundError, OperationFailed
> +from kimchi.model.tasks import TaskModel
> from kimchi.model.vms import DOM_STATE_MAP
> -from kimchi.utils import kimchi_log
> +from kimchi.utils import add_task, kimchi_log
>
>
> HOST_STATS_INTERVAL = 1
> @@ -199,3 +201,19 @@ class PartitionModel(object):
> raise NotFoundError("Partition %s not found in the host"
> % name)
> return disks.get_partition_details(name)
> +
> +
> +class PackageUpdateModel(object):
> + def __init__(self, **kargs):
> + self.host_swupdate = swupdate.SoftwareUpdate()
> + self.objstore = kargs['objstore']
> + self.task = TaskModel(**kargs)
> +
> + def lookup(self, *name):
> + return self.host_swupdate.getUpdates()
> +
> + def update(self, **kargs):
> + kimchi_log.info('Host is going to be updated.')
> + gen_cmd = self.host_swupdate.doUpdate()
> + taskid = add_task('', gen_cmd, self.objstore, 'packageupdate')
> + return self.task.lookup(taskid)
> diff --git a/src/kimchi/swupdate.py b/src/kimchi/swupdate.py
> new file mode 100644
> index 0000000..5cda9c1
> --- /dev/null
> +++ b/src/kimchi/swupdate.py
> @@ -0,0 +1,264 @@
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM, Corp. 2014
> +#
> +# Authors:
> +# Paulo Vital <pvital at linux.vnet.ibm.com>
> +# Ramon Medeiros <ramonn at linux.vnet.ibm.com>
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> +
> +import platform
> +import subprocess
> +
> +from kimchi.exception import NotFoundError, OperationFailed
> +from kimchi.utils import kimchi_log, run_command
> +
> +YUM_DISTROS = ['fedora', 'red hat enterprise linux',
> + 'red hat enterprise linux server']
> +APT_DISTROS = ['debian', 'ubuntu']
> +ZYPPER_DISTROS = ['opensuse ', 'suse linux enterprise server ']
> +
> +
> +class SoftwareUpdate(object):
> + """
> + Class to represent and operate with OS software update.
> + """
> + def __init__(self):
> + # This stores all packages to be updated for Kimchi perspective. It's a
> + # dictionary of dictionaries, in the format {'package_name': package},
> + # where:
> + # package = {'package_name': <string>, 'version': <string>,
> + # 'arch': <string>, 'repo': <string>
> + # }
> + self._packages = {}
> +
> + # This stores the number of packages to update
> + self._num2update = 0
> +
> + # Get the distro of host machine and creates an object related to
> + # correct package management system
> + self._distro = platform.linux_distribution()[0].lower()
> + if (self._distro in YUM_DISTROS):
> + kimchi_log.info("Loading YumUpdate features.")
> + self._pkg_mnger = YumUpdate()
> + elif (self._distro in APT_DISTROS):
> + kimchi_log.info("Loading AptUpdate features.")
> + self._pkg_mnger = AptUpdate()
> + elif (self._distro in ZYPPER_DISTROS):
> + kimchi_log.info("Loading ZypperUpdate features.")
> + self._pkg_mnger = ZypperUpdate()
> + else:
> + self._pkg_mnger = None
> +
> + if not self._pkg_mnger:
> + kimchi_log.info("There is no compatible package manager for \
> + this system.")
> +
> + def _scanUpdates(self):
> + """
> + Update self._packages with packages to be updated.
> + """
> + self._packages = {}
> + self._num2update = 0
> +
> + # Call system pkg_mnger to get the packages as list of dictionaries.
> + for pkg in self._pkg_mnger.getPackagesList():
> +
> + # Check if already exist a package in self._packages
> + pkg_id = pkg.get('package_name')
> + if pkg_id in self._packages.keys():
> + # package already listed to update. do nothing
> + continue
> +
> + # Update the self._packages and self._num2update
> + self._packages[pkg_id] = pkg
> + self._num2update = self._num2update + 1
> + print self._num2update
> + print self._packages
> +
> + def getUpdates(self):
> + """
> + Return the self._packages.
> + """
> + self._scanUpdates()
> + return self._packages
> +
> + def getNumOfUpdates(self):
> + """
> + Return the number of packages to be updated.
> + """
> + self._scanUpdates()
> + return self._num2update
> +
> + def doUpdate(self):
> + """
> + Execute the update
> + """
> + if self._num2update == 0:
> + kimchi_log.info("No packages marked for update")
> + raise OperationFailed("No packages marked for update")
> + return self._pkg_mnger.update()
> +
> +
> +class YumUpdate(object):
> + """
> + Class to represent and operate with YUM software update system.
> + It's loaded only on those systems listed at YUM_DISTROS and loads necessary
> + modules in runtime.
> + """
> + def __init__(self):
> + self._pkgs = {}
> + self._yb = getattr(__import__('yum'), 'YumBase')()
> +
> + def _refreshUpdateList(self):
> + """
> + Update the list of packages to be updated in the system.
> + """
> + self._pkgs = self._yb.doPackageLists('updates')
> +
> + def getPackagesList(self):
> + """
> + Return a list of package's dictionaries. Each dictionary contains the
> + information about a package, in the format
> + package = {'package_name': <string>, 'version': <string>,
> + 'arch': <string>, 'repo': <string>
> + }
> + """
> + self._refreshUpdateList()
> + pkg_list = []
> + for pkg in self._pkgs:
> + package = {'package_name': pkg.name,
> + 'version': "%s-%s" % (pkg.version, pkg.release),
> + 'arch': pkg.arch, 'repo': pkg.ui_from_repo}
> + pkg_list.append(package)
> + return pkg_list
> +
> + def update(self):
> + """
> + Execute the update of all packages marked to be update.
> + """
> + # FIXME: Due to incompatabilities between cherrypy and yum/sqlite3
> + # threading, we need execute the YUM command line to execute the update
> + cmd = ["yum", "-y", "-d", "0", "-e", "0", "update"]
> + (stdout, stderr, returncode) = run_command(cmd)
> +
> + if len(stderr) > 0:
> + raise OperationFailed("ERROR when executing command: %s" % stderr)
> +
> +
> +class AptUpdate(object):
> + """
> + Class to represent and operate with APT software update system.
> + It's loaded only on those systems listed at APT_DISTROS and loads necessary
> + modules in runtime.
> + """
> + def __init__(self):
> + self._pkgs = {}
> + self._apt_cache = getattr(__import__('apt'), 'Cache')()
> +
> + def _refreshUpdateList(self):
> + """
> + Update the list of packages to be updated in the system.
> + """
> + self._apt_cache.update()
> + self._apt_cache.upgrade()
> + self._pkgs = self._apt_cache.get_changes()
> +
> + def getPackagesList(self):
> + """
> + Return a list of package's dictionaries. Each dictionary contains the
> + information about a package, in the format
> + package = {'package_name': <string>, 'version': <string>,
> + 'arch': <string>, 'repo': <string>
> + }
> + """
> + self._refreshUpdateList()
> + pkg_list = []
> + for pkg in self._pkgs:
> + package = {'package_name': pkg.shortname,
> + 'version': pkg.candidate.version,
> + 'arch': pkg.architecture(),
> + 'repo': pkg.candidate.origins[0].label}
> + pkg_list.append(package)
> + return pkg_list
> +
> + def update(self):
> + """
> + Execute the update of all packages marked to be update.
> + """
> + try:
> + self._apt_cache.update()
> + self._apt_cache.open(None)
> + self._apt_cache.upgrade()
> + self._apt_cache.commit()
> + except Exception, e:
> + raise OperationFailed("ERROR when executing command: %s" % e)
> +
> +
> +class ZypperUpdate(object):
> + """
> + Class to represent and operate with Zypper software update system.
> + It's loaded only on those systems listed at ZYPPER_DISTROS and loads
> + necessary modules in runtime.
> + """
> + def __init__(self):
> + self._pkgs = {}
> +
> + def _refreshUpdateList(self):
> + """
> + Update the list of packages to be updated in the system.
> + """
> + self._pkgs = {}
> + cmd = ["zypper", "list-updates"]
> + (stdout, stderr, returncode) = run_command(cmd)
> +
> + if len(stderr) > 0:
> + raise OperationFailed("ERROR when executing command: %s" % stderr)
> +
> + for line in stdout.split('\n'):
> + if line.find('v |') >= 0:
> + info = line.split(' | ')
> + package = {'package_name': info[2], 'version': info[4],
> + 'arch': info[5], 'repo': info[1]}
> + self._pkgs[info[2]] = package
> +
> + def getPackagesList(self):
> + """
> + Return a list of package's dictionaries. Each dictionary contains the
> + information about a package, in the format
> + package = {'package_name': <string>, 'version': <string>,
> + 'arch': <string>, 'repo': <string>
> + }
> + """
> + self._refreshUpdateList()
> + pkg_list = []
> + for pkg in self._pkgs:
> + pkg_list.append(pkg)
> + return pkg_list
> +
> + def update(self):
> + """
> + Execute the update of all packages marked to be update.
> + """
> + cmd = ["zypper", "--non-interactive", "update",
> + "--auto-agree-with-licenses"]
> + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
> + stderr=subprocess.PIPE)
> + stdout, stderr = proc.communicate()
> +
> + if len(stderr) > 0:
> + raise OperationFailed("ERROR when executing command: %s" % stderr)
--
Thanks and best regards!
Sheldon Feng(冯少合)<shaohef at linux.vnet.ibm.com>
IBM Linux Technology Center
More information about the Kimchi-devel
mailing list