Merge "XenAPI: use os-xenapi for XenAPI driver"

This commit is contained in:
Jenkins 2017-05-15 15:02:46 +00:00 committed by Gerrit Code Review
commit aefe2a8f50
4 changed files with 95 additions and 210 deletions

View File

@ -13,23 +13,18 @@
# under the License. # under the License.
"""Implementation of Inspector abstraction for XenAPI.""" """Implementation of Inspector abstraction for XenAPI."""
from os_xenapi.client import session as xenapi_session
from os_xenapi.client import XenAPI
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import units from oslo_utils import units
import six.moves.urllib.parse as urlparse
try:
import XenAPI as api
except ImportError:
api = None
try:
import cPickle as pickle
except ImportError:
import pickle
from ceilometer.compute.pollsters import util from ceilometer.compute.pollsters import util
from ceilometer.compute.virt import inspector as virt_inspector from ceilometer.compute.virt import inspector as virt_inspector
from ceilometer.i18n import _ from ceilometer.i18n import _
LOG = logging.getLogger(__name__)
opt_group = cfg.OptGroup(name='xenapi', opt_group = cfg.OptGroup(name='xenapi',
title='Options for XenAPI') title='Options for XenAPI')
@ -50,22 +45,7 @@ class XenapiException(virt_inspector.InspectorException):
pass pass
def swap_xapi_host(url, host_addr):
"""Replace the XenServer address present in 'url' with 'host_addr'."""
temp_url = urlparse.urlparse(url)
# The connection URL is served by XAPI and doesn't support having a
# path for the connection url after the port. And username/password
# will be pass separately. So the URL like "http://abc:abc@abc:433/abc"
# should not appear for XAPI case.
temp_netloc = temp_url.netloc.replace(temp_url.hostname, '%s' % host_addr)
replaced = temp_url._replace(netloc=temp_netloc)
return urlparse.urlunparse(replaced)
def get_api_session(conf): def get_api_session(conf):
if not api:
raise ImportError(_('XenAPI not installed'))
url = conf.xenapi.connection_url url = conf.xenapi.connection_url
username = conf.xenapi.connection_username username = conf.xenapi.connection_username
password = conf.xenapi.connection_password password = conf.xenapi.connection_password
@ -74,20 +54,10 @@ def get_api_session(conf):
'connection_password to use')) 'connection_password to use'))
try: try:
session = (api.xapi_local() if url == 'unix://local' session = xenapi_session.XenAPISession(url, username, password,
else api.Session(url)) originator="ceilometer")
session.login_with_password(username, password) LOG.debug("XenAPI session is created successfully, %s", session)
except api.Failure as e: except XenAPI.Failure as e:
if e.details[0] == 'HOST_IS_SLAVE':
master = e.details[1]
url = swap_xapi_host(url, master)
try:
session = api.Session(url)
session.login_with_password(username, password)
except api.Failure as es:
raise XenapiException(_('Could not connect slave host: %s') %
es.details[0])
else:
msg = _("Could not connect to XenAPI: %s") % e.details[0] msg = _("Could not connect to XenAPI: %s") % e.details[0]
raise XenapiException(msg) raise XenapiException(msg)
return session return session
@ -98,31 +68,9 @@ class XenapiInspector(virt_inspector.Inspector):
def __init__(self, conf): def __init__(self, conf):
super(XenapiInspector, self).__init__(conf) super(XenapiInspector, self).__init__(conf)
self.session = get_api_session(self.conf) self.session = get_api_session(self.conf)
self.host_ref = self._get_host_ref()
self.host_uuid = self._get_host_uuid()
def _get_host_ref(self):
"""Return the xenapi host on which nova-compute runs on."""
return self.session.xenapi.session.get_this_host(self.session.handle)
def _get_host_uuid(self):
return self.session.xenapi.host.get_uuid(self.host_ref)
def _call_xenapi(self, method, *args):
return self.session.xenapi_request(method, args)
def _call_plugin(self, plugin, fn, args):
args['host_uuid'] = self.host_uuid
return self.session.xenapi.host.call_plugin(
self.host_ref, plugin, fn, args)
def _call_plugin_serialized(self, plugin, fn, *args, **kwargs):
params = {'params': pickle.dumps(dict(args=args, kwargs=kwargs))}
rv = self._call_plugin(plugin, fn, params)
return pickle.loads(rv)
def _lookup_by_name(self, instance_name): def _lookup_by_name(self, instance_name):
vm_refs = self._call_xenapi("VM.get_by_name_label", instance_name) vm_refs = self.session.VM.get_by_name_label(instance_name)
n = len(vm_refs) n = len(vm_refs)
if n == 0: if n == 0:
raise virt_inspector.InstanceNotFoundException( raise virt_inspector.InstanceNotFoundException(
@ -138,30 +86,28 @@ class XenapiInspector(virt_inspector.Inspector):
vm_ref = self._lookup_by_name(instance_name) vm_ref = self._lookup_by_name(instance_name)
cpu_util = self._get_cpu_usage(vm_ref, instance_name) cpu_util = self._get_cpu_usage(vm_ref, instance_name)
memory_usage = self._get_memory_usage(vm_ref) memory_usage = self._get_memory_usage(vm_ref)
LOG.debug("inspect_instance, cpu_util: %(cpu)s, memory_usage: %(mem)s",
{'cpu': cpu_util, 'mem': memory_usage}, instance=instance)
return virt_inspector.InstanceStats(cpu_util=cpu_util, return virt_inspector.InstanceStats(cpu_util=cpu_util,
memory_usage=memory_usage) memory_usage=memory_usage)
def _get_cpu_usage(self, vm_ref, instance_name): def _get_cpu_usage(self, vm_ref, instance_name):
vcpus_number = int(self._call_xenapi("VM.get_VCPUs_max", vm_ref)) vcpus_number = int(self.session.VM.get_VCPUs_max(vm_ref))
if vcpus_number <= 0: if vcpus_number <= 0:
msg = _("Could not get VM %s CPU number") % instance_name msg = _("Could not get VM %s CPU number") % instance_name
raise XenapiException(msg) raise XenapiException(msg)
cpu_util = 0.0 cpu_util = 0.0
for index in range(vcpus_number): for index in range(vcpus_number):
cpu_util += float(self._call_xenapi("VM.query_data_source", cpu_util += float(self.session.VM.query_data_source(
vm_ref, vm_ref, "cpu%d" % index))
"cpu%d" % index))
return cpu_util / int(vcpus_number) * 100 return cpu_util / int(vcpus_number) * 100
def _get_memory_usage(self, vm_ref): def _get_memory_usage(self, vm_ref):
total_mem = float(self._call_xenapi("VM.query_data_source", total_mem = float(self.session.VM.query_data_source(vm_ref, "memory"))
vm_ref,
"memory"))
try: try:
free_mem = float(self._call_xenapi("VM.query_data_source", free_mem = float(self.session.VM.query_data_source(
vm_ref, vm_ref, "memory_internal_free"))
"memory_internal_free")) except XenAPI.Failure:
except api.Failure:
# If PV tools is not installed in the guest instance, it's # If PV tools is not installed in the guest instance, it's
# impossible to get free memory. So give it a default value # impossible to get free memory. So give it a default value
# as 0. # as 0.
@ -174,12 +120,15 @@ class XenapiInspector(virt_inspector.Inspector):
def inspect_vnics(self, instance, duration): def inspect_vnics(self, instance, duration):
instance_name = util.instance_name(instance) instance_name = util.instance_name(instance)
vm_ref = self._lookup_by_name(instance_name) vm_ref = self._lookup_by_name(instance_name)
dom_id = self._call_xenapi("VM.get_domid", vm_ref) dom_id = self.session.VM.get_domid(vm_ref)
vif_refs = self._call_xenapi("VM.get_VIFs", vm_ref) vif_refs = self.session.VM.get_VIFs(vm_ref)
bw_all = self._call_plugin_serialized('bandwidth', bw_all = self.session.call_plugin_serialized('bandwidth',
'fetch_all_bandwidth') 'fetch_all_bandwidth')
LOG.debug("inspect_vnics, all bandwidth: %s", bw_all,
instance=instance)
for vif_ref in vif_refs or []: for vif_ref in vif_refs or []:
vif_rec = self._call_xenapi("VIF.get_record", vif_ref) vif_rec = self.session.VIF.get_record(vif_ref)
bw_vif = bw_all[dom_id][vif_rec['device']] bw_vif = bw_all[dom_id][vif_rec['device']]
@ -197,17 +146,15 @@ class XenapiInspector(virt_inspector.Inspector):
def inspect_vnic_rates(self, instance, duration): def inspect_vnic_rates(self, instance, duration):
instance_name = util.instance_name(instance) instance_name = util.instance_name(instance)
vm_ref = self._lookup_by_name(instance_name) vm_ref = self._lookup_by_name(instance_name)
vif_refs = self._call_xenapi("VM.get_VIFs", vm_ref) vif_refs = self.session.VM.get_VIFs(vm_ref)
if vif_refs: if vif_refs:
for vif_ref in vif_refs: for vif_ref in vif_refs:
vif_rec = self._call_xenapi("VIF.get_record", vif_ref) vif_rec = self.session.VIF.get_record(vif_ref)
rx_rate = float(self._call_xenapi( rx_rate = float(self.session.VM.query_data_source(
"VM.query_data_source", vm_ref, vm_ref, "vif_%s_rx" % vif_rec['device']))
"vif_%s_rx" % vif_rec['device'])) tx_rate = float(self.session.VM.query_data_source(
tx_rate = float(self._call_xenapi( vm_ref, "vif_%s_tx" % vif_rec['device']))
"VM.query_data_source", vm_ref,
"vif_%s_tx" % vif_rec['device']))
yield virt_inspector.InterfaceRateStats( yield virt_inspector.InterfaceRateStats(
name=vif_rec['uuid'], name=vif_rec['uuid'],
@ -220,17 +167,15 @@ class XenapiInspector(virt_inspector.Inspector):
def inspect_disk_rates(self, instance, duration): def inspect_disk_rates(self, instance, duration):
instance_name = util.instance_name(instance) instance_name = util.instance_name(instance)
vm_ref = self._lookup_by_name(instance_name) vm_ref = self._lookup_by_name(instance_name)
vbd_refs = self._call_xenapi("VM.get_VBDs", vm_ref) vbd_refs = self.session.VM.get_VBDs(vm_ref)
if vbd_refs: if vbd_refs:
for vbd_ref in vbd_refs: for vbd_ref in vbd_refs:
vbd_rec = self._call_xenapi("VBD.get_record", vbd_ref) vbd_rec = self.session.VBD.get_record(vbd_ref)
read_rate = float(self._call_xenapi( read_rate = float(self.session.VM.query_data_source(
"VM.query_data_source", vm_ref, vm_ref, "vbd_%s_read" % vbd_rec['device']))
"vbd_%s_read" % vbd_rec['device'])) write_rate = float(self.session.VM.query_data_source(
write_rate = float(self._call_xenapi( vm_ref, "vbd_%s_write" % vbd_rec['device']))
"VM.query_data_source", vm_ref,
"vbd_%s_write" % vbd_rec['device']))
yield virt_inspector.DiskRateStats( yield virt_inspector.DiskRateStats(
device=vbd_rec['device'], device=vbd_rec['device'],
read_bytes_rate=read_rate, read_bytes_rate=read_rate,

View File

@ -1,19 +0,0 @@
# Copyright 2016 Citrix
#
# 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.
"""fake XenAPI for testing in case XenAPI doesn't exist in the test env"""
class Failure(Exception):
def __init__(self, details):
self.details = details

View File

@ -19,34 +19,6 @@ from oslotest import base
from ceilometer.compute.virt.xenapi import inspector as xenapi_inspector from ceilometer.compute.virt.xenapi import inspector as xenapi_inspector
from ceilometer import service from ceilometer import service
from ceilometer.tests.unit.compute.virt.xenapi import fake_XenAPI
class TestSwapXapiHost(base.BaseTestCase):
def test_swapping(self):
self.assertEqual(
"http://otherserver:8765/somepath",
xenapi_inspector.swap_xapi_host(
"http://someserver:8765/somepath", 'otherserver'))
def test_no_port(self):
self.assertEqual(
"http://otherserver/somepath",
xenapi_inspector.swap_xapi_host(
"http://someserver/somepath", 'otherserver'))
def test_no_path(self):
self.assertEqual(
"http://otherserver",
xenapi_inspector.swap_xapi_host(
"http://someserver", 'otherserver'))
def test_same_hostname_path(self):
self.assertEqual(
"http://other:80/some",
xenapi_inspector.swap_xapi_host(
"http://some:80/some", 'other'))
class TestXenapiInspection(base.BaseTestCase): class TestXenapiInspection(base.BaseTestCase):
@ -64,26 +36,14 @@ class TestXenapiInspection(base.BaseTestCase):
fake_total_mem = 134217728.0 fake_total_mem = 134217728.0
fake_free_mem = 65536.0 fake_free_mem = 65536.0
def fake_xenapi_request(method, args):
if method == 'VM.get_by_name_label':
return ['vm_ref']
elif method == 'VM.get_VCPUs_max':
return '1'
elif method == 'VM.query_data_source':
if 'memory' in args:
return fake_total_mem
elif 'memory_internal_free' in args:
return fake_free_mem
elif 'cpu0' in args:
return 0.4
else:
return None
else:
return None
session = self.inspector.session session = self.inspector.session
with mock.patch.object(session, 'xenapi_request', with mock.patch.object(session.VM, 'get_by_name_label') as mock_name, \
side_effect=fake_xenapi_request): mock.patch.object(session.VM, 'get_VCPUs_max') as mock_vcpu, \
mock.patch.object(session.VM, 'query_data_source') \
as mock_query:
mock_name.return_value = ['vm_ref']
mock_vcpu.return_value = '1'
mock_query.side_effect = [0.4, fake_total_mem, fake_free_mem]
stats = self.inspector.inspect_instance(fake_instance, None) stats = self.inspector.inspect_instance(fake_instance, None)
self.assertEqual(40, stats.cpu_util) self.assertEqual(40, stats.cpu_util)
self.assertEqual(64, stats.memory_usage) self.assertEqual(64, stats.memory_usage)
@ -91,35 +51,17 @@ class TestXenapiInspection(base.BaseTestCase):
def test_inspect_memory_usage_without_freeMem(self): def test_inspect_memory_usage_without_freeMem(self):
fake_instance = {'OS-EXT-SRV-ATTR:instance_name': 'fake_instance_name', fake_instance = {'OS-EXT-SRV-ATTR:instance_name': 'fake_instance_name',
'id': 'fake_instance_id'} 'id': 'fake_instance_id'}
def _fake_xenapi_request(method, args):
if xenapi_inspector.api is None:
# the XenAPI may not exist in the test environment.
# In that case, we use the fake XenAPI for testing.
xenapi_inspector.api = fake_XenAPI
fake_total_mem = 134217728.0 fake_total_mem = 134217728.0
fake_details = ['INTERNAL_ERROR', fake_free_mem = 0
'Rrd.Invalid_data_source("memory_internal_free")']
if method == 'VM.get_by_name_label':
return ['vm_ref']
elif method == 'VM.get_VCPUs_max':
return '1'
elif method == 'VM.query_data_source':
if 'memory' in args:
return fake_total_mem
elif 'memory_internal_free' in args:
raise xenapi_inspector.api.Failure(fake_details)
elif 'cpu0' in args:
return 0.4
else:
return None
else:
return None
session = self.inspector.session session = self.inspector.session
with mock.patch.object(session, 'xenapi_request', with mock.patch.object(session.VM, 'get_by_name_label') as mock_name, \
side_effect=_fake_xenapi_request): mock.patch.object(session.VM, 'get_VCPUs_max') as mock_vcpu, \
mock.patch.object(session.VM, 'query_data_source') \
as mock_query:
mock_name.return_value = ['vm_ref']
mock_vcpu.return_value = '1'
mock_query.side_effect = [0.4, fake_total_mem, fake_free_mem]
stats = self.inspector.inspect_instance(fake_instance, None) stats = self.inspector.inspect_instance(fake_instance, None)
self.assertEqual(128, stats.memory_usage) self.assertEqual(128, stats.memory_usage)
@ -132,7 +74,6 @@ class TestXenapiInspection(base.BaseTestCase):
'MAC': 'vif_mac', 'MAC': 'vif_mac',
'device': '0', 'device': '0',
} }
request_returns = [['vm_ref'], '10', ['vif_ref'], vif_rec]
bandwidth_returns = [{ bandwidth_returns = [{
'10': { '10': {
'0': { '0': {
@ -141,14 +82,19 @@ class TestXenapiInspection(base.BaseTestCase):
} }
}] }]
session = self.inspector.session session = self.inspector.session
with mock.patch.object(session, 'xenapi_request', with mock.patch.object(session.VM, 'get_by_name_label') as mock_name, \
side_effect=request_returns): mock.patch.object(session.VM, 'get_domid') as mock_domid, \
with mock.patch.object(self.inspector, mock.patch.object(session.VM, 'get_VIFs') as mock_vif, \
'_call_plugin_serialized', mock.patch.object(session.VIF, 'get_record') as mock_record, \
side_effect=bandwidth_returns): mock.patch.object(session, 'call_plugin_serialized') \
as mock_plugin:
interfaces = list( mock_name.return_value = ['vm_ref']
self.inspector.inspect_vnics(fake_instance, None)) mock_domid.return_value = '10'
mock_vif.return_value = ['vif_ref']
mock_record.return_value = vif_rec
mock_plugin.side_effect = bandwidth_returns
interfaces = list(self.inspector.inspect_vnics(
fake_instance, None))
self.assertEqual(1, len(interfaces)) self.assertEqual(1, len(interfaces))
vnic0 = interfaces[0] vnic0 = interfaces[0]
@ -167,11 +113,17 @@ class TestXenapiInspection(base.BaseTestCase):
'MAC': 'vif_mac', 'MAC': 'vif_mac',
'device': '0', 'device': '0',
} }
side_effects = [['vm_ref'], ['vif_ref'], vif_rec, 1024.0, 2048.0]
session = self.inspector.session session = self.inspector.session
with mock.patch.object(session, 'xenapi_request', with mock.patch.object(session.VM, 'get_by_name_label') as mock_name, \
side_effect=side_effects): mock.patch.object(session.VM, 'get_VIFs') as mock_vif, \
mock.patch.object(session.VIF, 'get_record') as mock_record, \
mock.patch.object(session.VM, 'query_data_source') \
as mock_query:
mock_name.return_value = ['vm_ref']
mock_vif.return_value = ['vif_ref']
mock_record.return_value = vif_rec
mock_query.side_effect = [1024.0, 2048.0]
interfaces = list(self.inspector.inspect_vnic_rates( interfaces = list(self.inspector.inspect_vnic_rates(
fake_instance, None)) fake_instance, None))
@ -189,11 +141,17 @@ class TestXenapiInspection(base.BaseTestCase):
vbd_rec = { vbd_rec = {
'device': 'xvdd' 'device': 'xvdd'
} }
side_effects = [['vm_ref'], ['vbd_ref'], vbd_rec, 1024.0, 2048.0]
session = self.inspector.session session = self.inspector.session
with mock.patch.object(session, 'xenapi_request', with mock.patch.object(session.VM, 'get_by_name_label') as mock_name, \
side_effect=side_effects): mock.patch.object(session.VM, 'get_VBDs') as mock_vbds, \
mock.patch.object(session.VBD, 'get_record') as mock_records, \
mock.patch.object(session.VM, 'query_data_source') \
as mock_query:
mock_name.return_value = ['vm_ref']
mock_vbds.return_value = ['vbd_ref']
mock_records.return_value = vbd_rec
mock_query.side_effect = [1024.0, 2048.0]
disks = list(self.inspector.inspect_disk_rates( disks = list(self.inspector.inspect_disk_rates(
fake_instance, None)) fake_instance, None))

View File

@ -49,3 +49,4 @@ WSME>=0.8 # MIT
# NOTE(jd) We do not import it directly, but WSME datetime string parsing # NOTE(jd) We do not import it directly, but WSME datetime string parsing
# behaviour changes when this library is installed # behaviour changes when this library is installed
python-dateutil>=2.4.2 # BSD python-dateutil>=2.4.2 # BSD
os-xenapi>=0.1.1 # Apache-2.0