diff --git a/nova/exception.py b/nova/exception.py index 55d56419dd9b..c44a6b45e067 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2067,3 +2067,8 @@ class RealtimeMaskNotFoundOrInvalid(Invalid): msg_fmt = _("Realtime policy needs vCPU(s) mask configured with at least " "1 RT vCPU and 1 ordinary vCPU. See hw:cpu_realtime_mask " "or hw_cpu_realtime_mask") + + +class OsInfoNotFound(NotFound): + msg_fmt = _("No configuration information found for operating system " + "%(os_name)s") diff --git a/nova/tests/unit/virt/test_osinfo.py b/nova/tests/unit/virt/test_osinfo.py new file mode 100644 index 000000000000..befced3c0094 --- /dev/null +++ b/nova/tests/unit/virt/test_osinfo.py @@ -0,0 +1,53 @@ +# Copyright 2015 Red Hat, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from nova import exception +from nova import test +from nova.virt import osinfo + + +class LibvirtOsInfoTest(test.NoDBTestCase): + + def setUp(self): + super(LibvirtOsInfoTest, self).setUp() + osinfo.libosinfo = mock.Mock() + + def test_get_os(self): + filter_mock = mock.Mock() + osinfo.libosinfo = mock.Mock() + osinfo.libosinfo.Filter.new.return_value = filter_mock + osinfo_mock = mock.Mock() + filtered_list = osinfo_mock.new_filtered + filtered_list.return_value.get_length.return_value = 1 + os_info_db = osinfo._OsInfoDatabase.get_instance() + os_info_db.oslist = osinfo_mock + os_info_db.get_os('test33') + filter_mock.add_constraint.assert_called_once_with('short-id', + 'test33') + self.assertTrue(filtered_list.return_value.get_nth.called) + + def test_get_os_fails(self): + filter_mock = mock.Mock() + osinfo.libosinfo = mock.Mock() + osinfo.libosinfo.Filter.return_value.new.return_value = filter_mock + osinfo_mock = mock.Mock() + filtered = osinfo_mock.new_filtered.return_value + filtered.get_length.return_value = 0 + os_info_db = osinfo._OsInfoDatabase.get_instance() + os_info_db.oslist = osinfo_mock + self.assertRaises(exception.OsInfoNotFound, + os_info_db.get_os, + 'test33') diff --git a/nova/virt/osinfo.py b/nova/virt/osinfo.py new file mode 100644 index 000000000000..ae5ce39cd5b5 --- /dev/null +++ b/nova/virt/osinfo.py @@ -0,0 +1,113 @@ +# Copyright 2015 Red Hat, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log as logging +from oslo_utils import importutils + +from nova import exception +from nova.i18n import _, _LE + +libosinfo = None +LOG = logging.getLogger(__name__) + +# TODO(vladikr) The current implementation will serve only as a temporary +# solution, due to it's dependency on the libosinfo gobject library. +# In the future it will be replaced by a pure python library or by a direct +# parsing of the libosinfo XML files. However, it will be possible only when +# libosinfo project will declare the XML structure to be a stable ABI. + + +class _OsInfoDatabase(object): + + _instance = None + + def __init__(self): + + global libosinfo + if libosinfo is None: + try: + libosinfo = importutils.import_module( + 'gi.repository.Libosinfo') + except ImportError as exp: + raise exception.NovaException( + _("Cannot load Libosinfo: (%s)") % exp) + + self.loader = libosinfo.Loader() + self.loader.process_default_path() + + self.db = self.loader.get_db() + self.oslist = self.db.get_os_list() + + @classmethod + def get_instance(cls): + """Get libosinfo connection + """ + if cls._instance is None: + cls._instance = _OsInfoDatabase() + + return cls._instance + + def get_os(self, os_name): + """Retrieve OS object based on id, unique URI identifier of the OS + :param os_name: id - the unique operating systemidentifier + e.g. http://fedoraproject.org/fedora/21, + http://microsoft.com/win/xp, + or a + short-id - the short name of the OS + e.g. fedora21, winxp + :returns: The operation system object Libosinfo.Os + :raise exception.OsInfoNotFound: If os hasn't been found + """ + if not os_name: + raise exception.OsInfoNotFound(os_name='Empty') + fltr = libosinfo.Filter.new() + flt_field = 'id' if os_name.startswith('http') else 'short-id' + fltr.add_constraint(flt_field, os_name) + filttered = self.oslist.new_filtered(fltr) + list_len = filttered.get_length() + if not list_len: + raise exception.OsInfoNotFound(os_name=os_name) + return filttered.get_nth(0) + + +class OsInfo(object): + """OS Information Structure + """ + + def __init__(self, os_name): + self._os_obj = self._get_os_obj(os_name) + + def _get_os_obj(self, os_name): + try: + return _OsInfoDatabase.get_instance().get_os(os_name) + except exception.NovaException as e: + LOG.error(_LE("Cannot find OS information - Reason: (%s)"), e) + + @property + def network_model(self): + if self._os_obj is not None: + fltr = libosinfo.Filter() + fltr.add_constraint("class", "net") + devs = self._os_obj.get_all_devices(fltr) + if devs.get_length(): + return devs.get_nth(0).get_name() + + @property + def disk_model(self): + if self._os_obj is not None: + fltr = libosinfo.Filter() + fltr.add_constraint("class", "block") + devs = self._os_obj.get_all_devices(fltr) + if devs.get_length(): + return devs.get_nth(0).get_name()