z/VM Driver: Initial change set of z/VM driver

This is the first change that implements basic virt.driver methods
to allow nova-compute process start successfully.

A set of subsequent changes will implement spawn, snapshot, destroy
and instance power actions.

Change-Id: Ica6117c2c64f7518b78b7fb02487622250638e88
blueprint: add-zvm-driver-rocky
This commit is contained in:
Huang Rui 2017-11-28 18:46:41 +08:00 committed by jichenjc
parent 6ee633a971
commit e5acf4f961
14 changed files with 489 additions and 0 deletions

View File

@ -170,3 +170,4 @@ WebOb==1.8.2
websockify==0.8.0
wrapt==1.10.11
wsgi-intercept==1.7.0
zVMCloudConnector==1.1.1

View File

@ -70,6 +70,7 @@ from nova.conf import workarounds
from nova.conf import wsgi
from nova.conf import xenserver
from nova.conf import xvp
from nova.conf import zvm
CONF = cfg.CONF
@ -123,5 +124,6 @@ workarounds.register_opts(CONF)
wsgi.register_opts(CONF)
xenserver.register_opts(CONF)
xvp.register_opts(CONF)
zvm.register_opts(CONF)
remote_debug.register_cli_opts(CONF)

View File

@ -43,6 +43,7 @@ Possible values:
* ``vmwareapi.VMwareVCDriver``
* ``hyperv.HyperVDriver``
* ``powervm.PowerVMDriver``
* ``zvm.ZVMDriver``
"""),
cfg.BoolOpt('allow_resize_to_same_host',
default=False,

51
nova/conf/zvm.py Normal file
View File

@ -0,0 +1,51 @@
# Copyright 2017,2018 IBM Corp.
#
# 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_config import cfg
zvm_opt_group = cfg.OptGroup('zvm',
title='zVM Options',
help="""
zvm options allows cloud administrator to configure related
z/VM hypervisor driver to be used within an OpenStack deployment.
zVM options are used when the compute_driver is set to use
zVM (compute_driver=zvm.ZVMDriver)
""")
zvm_opts = [
cfg.URIOpt('cloud_connector_url',
sample_default='http://zvm.example.org:8080/',
help="""
URL to be used to communicate with z/VM Cloud Connector.
"""),
cfg.StrOpt('ca_file',
default=None,
help="""
CA certificate file to be verified in httpd server with TLS enabled
A string, it must be a path to a CA bundle to use.
"""),
]
def register_opts(conf):
conf.register_group(zvm_opt_group)
conf.register_opts(zvm_opts, group=zvm_opt_group)
def list_opts():
return {zvm_opt_group: zvm_opts}

View File

@ -2312,3 +2312,24 @@ class InstanceUnRescueFailure(NovaException):
class IronicAPIVersionNotAvailable(NovaException):
msg_fmt = _('Ironic API version %(version)s is not available.')
class ZVMDriverException(NovaException):
msg_fmt = _("ZVM Driver has error: %(error)s")
class ZVMConnectorError(ZVMDriverException):
msg_fmt = _("zVM Cloud Connector request failed: %(results)s")
def __init__(self, message=None, **kwargs):
"""Exception for zVM ConnectorClient calls.
:param results: The object returned from ZVMConnector.send_request.
"""
super(ZVMConnectorError, self).__init__(message=message, **kwargs)
results = kwargs.get('results', {})
self.overallRC = results.get('overallRC')
self.rc = results.get('rc')
self.rs = results.get('rs')
self.errmsg = results.get('errmsg')

View File

View File

@ -0,0 +1,45 @@
# Copyright 2017,2018 IBM Corp.
#
# 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.zvm import driver as zvmdriver
class TestZVMDriver(test.NoDBTestCase):
def setUp(self):
super(TestZVMDriver, self).setUp()
self.flags(cloud_connector_url='https://1.1.1.1:1111', group='zvm')
with mock.patch('nova.virt.zvm.utils.'
'ConnectorClient.call') as mcall:
mcall.return_value = {'hypervisor_hostname': 'TESTHOST'}
self._driver = zvmdriver.ZVMDriver('virtapi')
def test_driver_init_no_url(self):
self.flags(cloud_connector_url=None, group='zvm')
self.assertRaises(exception.ZVMDriverException,
zvmdriver.ZVMDriver, 'virtapi')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_available_resource_err_case(self, call):
res = {'overallRC': 1, 'errmsg': 'err', 'rc': 0, 'rs': 0}
call.side_effect = exception.ZVMConnectorError(res)
results = self._driver.get_available_resource()
self.assertEqual(0, results['vcpus'])
self.assertEqual(0, results['memory_mb_used'])
self.assertEqual(0, results['disk_available_least'])
self.assertEqual('TESTHOST', results['hypervisor_hostname'])

View File

@ -0,0 +1,69 @@
# Copyright 2017,2018 IBM Corp.
#
# 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.zvm import driver as zvmdriver
class TestZVMHypervisor(test.NoDBTestCase):
def setUp(self):
super(TestZVMHypervisor, self).setUp()
self.flags(cloud_connector_url='https://1.1.1.1:1111', group='zvm')
with mock.patch('nova.virt.zvm.utils.'
'ConnectorClient.call') as mcall:
mcall.return_value = {'hypervisor_hostname': 'TESTHOST'}
driver = zvmdriver.ZVMDriver('virtapi')
self._hypervisor = driver._hypervisor
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_available_resource(self, call):
host_info = {'disk_available': 1144,
'ipl_time': 'IPL at 11/14/17 10:47:44 EST',
'vcpus_used': 4,
'hypervisor_type': 'zvm',
'disk_total': 2000,
'zvm_host': 'TESTHOST',
'memory_mb': 78192,
'cpu_info': {'cec_model': '2827',
'architecture': 's390x'},
'vcpus': 84,
'hypervisor_hostname': 'TESTHOST',
'hypervisor_version': 640,
'disk_used': 856,
'memory_mb_used': 8192}
call.return_value = host_info
results = self._hypervisor.get_available_resource()
self.assertEqual(host_info, results)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_available_resource_err_case(self, call):
res = {'overallRC': 1, 'errmsg': 'err', 'rc': 0, 'rs': 0}
call.side_effect = exception.ZVMConnectorError(res)
results = self._hypervisor.get_available_resource()
# Should return an empty dict
self.assertFalse(results)
def test_get_available_nodes(self):
nodes = self._hypervisor.get_available_nodes()
self.assertEqual(['TESTHOST'], nodes)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_list_names(self, call):
call.return_value = ['vm1', 'vm2']
inst_list = self._hypervisor.list_names()
self.assertEqual(['vm1', 'vm2'], inst_list)

View File

@ -0,0 +1,81 @@
# Copyright 2017,2018 IBM Corp.
#
# 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 zvmconnector import connector
from nova import exception
from nova import test
from nova.virt.zvm import utils as zvmutils
class TestZVMUtils(test.NoDBTestCase):
def setUp(self):
super(TestZVMUtils, self).setUp()
self.flags(cloud_connector_url='http://127.0.0.1', group='zvm')
self._url = 'http://127.0.0.1'
def test_connector_request_handler_invalid_url(self):
rh = zvmutils.ConnectorClient('http://invalid')
self.assertRaises(exception.ZVMDriverException, rh.call, 'guest_list')
@mock.patch('zvmconnector.connector.ZVMConnector.__init__',
return_value=None)
def test_connector_request_handler_https(self, mock_init):
rh = zvmutils.ConnectorClient('https://127.0.0.1:80',
ca_file='/tmp/file')
mock_init.assert_called_once_with('127.0.0.1', 80, ssl_enabled=True,
verify='/tmp/file')
self.assertIsInstance(rh._conn, connector.ZVMConnector)
@mock.patch('zvmconnector.connector.ZVMConnector.__init__',
return_value=None)
def test_connector_request_handler_https_noca(self, mock_init):
rh = zvmutils.ConnectorClient('https://127.0.0.1:80')
mock_init.assert_called_once_with('127.0.0.1', 80, ssl_enabled=True,
verify=False)
self.assertIsInstance(rh._conn, connector.ZVMConnector)
@mock.patch('zvmconnector.connector.ZVMConnector.__init__',
return_value=None)
def test_connector_request_handler_http(self, mock_init):
rh = zvmutils.ConnectorClient('http://127.0.0.1:80')
mock_init.assert_called_once_with('127.0.0.1', 80, ssl_enabled=False,
verify=False)
self.assertIsInstance(rh._conn, connector.ZVMConnector)
@mock.patch('zvmconnector.connector.ZVMConnector.send_request')
def test_connector_request_handler(self, mock_send):
mock_send.return_value = {'overallRC': 0, 'output': 'data',
'rc': 0, 'rs': 0}
rh = zvmutils.ConnectorClient(self._url)
res = rh.call('guest_list')
self.assertEqual('data', res)
@mock.patch('zvmconnector.connector.ZVMConnector.send_request')
def test_connector_request_handler_error(self, mock_send):
expected = {'overallRC': 1, 'errmsg': 'err', 'rc': 0, 'rs': 0}
mock_send.return_value = expected
rh = zvmutils.ConnectorClient(self._url)
exc = self.assertRaises(exception.ZVMConnectorError, rh.call,
'guest_list')
self.assertIn('zVM Cloud Connector request failed',
exc.format_message())
self.assertEqual(expected['overallRC'], exc.overallRC)
self.assertEqual(expected['rc'], exc.rc)
self.assertEqual(expected['rs'], exc.rs)
self.assertEqual(expected['errmsg'], exc.errmsg)

17
nova/virt/zvm/__init__.py Normal file
View File

@ -0,0 +1,17 @@
# Copyright 2017,2018 IBM Corp.
#
# 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 nova.virt.zvm import driver
ZVMDriver = driver.ZVMDriver

82
nova/virt/zvm/driver.py Normal file
View File

@ -0,0 +1,82 @@
# Copyright 2017,2018 IBM Corp.
#
# 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_serialization import jsonutils
from nova import conf
from nova import exception
from nova.i18n import _
from nova.objects import fields as obj_fields
from nova.virt import driver
from nova.virt.zvm import hypervisor
LOG = logging.getLogger(__name__)
CONF = conf.CONF
class ZVMDriver(driver.ComputeDriver):
"""z/VM implementation of ComputeDriver."""
def __init__(self, virtapi):
super(ZVMDriver, self).__init__(virtapi)
if not CONF.zvm.cloud_connector_url:
error = _('Must specify cloud_connector_url in zvm config '
'group to use compute_driver=zvm.driver.ZVMDriver')
raise exception.ZVMDriverException(error=error)
self._hypervisor = hypervisor.Hypervisor(
CONF.zvm.cloud_connector_url, ca_file=CONF.zvm.ca_file)
LOG.info("The zVM compute driver has been initialized.")
def init_host(self, host):
pass
def list_instances(self):
return self._hypervisor.list_names()
def get_available_resource(self, nodename=None):
host_stats = self._hypervisor.get_available_resource()
hypervisor_hostname = self._hypervisor.get_available_nodes()[0]
res = {
'vcpus': host_stats.get('vcpus', 0),
'memory_mb': host_stats.get('memory_mb', 0),
'local_gb': host_stats.get('disk_total', 0),
'vcpus_used': host_stats.get('vcpus_used', 0),
'memory_mb_used': host_stats.get('memory_mb_used', 0),
'local_gb_used': host_stats.get('disk_used', 0),
'hypervisor_type': host_stats.get('hypervisor_type',
obj_fields.HVType.ZVM),
'hypervisor_version': host_stats.get('hypervisor_version', ''),
'hypervisor_hostname': host_stats.get('hypervisor_hostname',
hypervisor_hostname),
'cpu_info': jsonutils.dumps(host_stats.get('cpu_info', {})),
'disk_available_least': host_stats.get('disk_available', 0),
'supported_instances': [(obj_fields.Architecture.S390X,
obj_fields.HVType.ZVM,
obj_fields.VMMode.HVM)],
'numa_topology': None,
}
LOG.debug("Getting available resource for %(host)s:%(nodename)s",
{'host': CONF.host, 'nodename': nodename})
return res
def get_available_nodes(self, refresh=False):
return self._hypervisor.get_available_nodes(refresh=refresh)

View File

@ -0,0 +1,57 @@
# Copyright 2017,2018 IBM Corp.
#
# 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 nova import exception
from nova.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
class Hypervisor(object):
"""z/VM implementation of Hypervisor."""
def __init__(self, zcc_url, ca_file=None):
super(Hypervisor, self).__init__()
self._reqh = zvmutils.ConnectorClient(zcc_url,
ca_file=ca_file)
# Very very unlikely the hostname will be changed, so when create
# hypervisor object, store the information in the cache and after
# that we can use it directly without query again from connectorclient
self._hypervisor_hostname = self._get_host_info().get(
'hypervisor_hostname')
def _get_host_info(self):
host_stats = {}
try:
host_stats = self._reqh.call('host_get_info')
except exception.ZVMConnectorError as e:
LOG.warning("Failed to get host stats: %s", e)
return host_stats
def get_available_resource(self):
return self._get_host_info()
def get_available_nodes(self, refresh=False):
# It's not expected that the hostname change, no need to take
# 'refresh' into account.
return [self._hypervisor_hostname]
def list_names(self):
"""list names of the servers in the hypervisor"""
return self._reqh.call('guest_list')

61
nova/virt/zvm/utils.py Normal file
View File

@ -0,0 +1,61 @@
# Copyright 2017,2018 IBM Corp.
#
# 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
import six
import six.moves.urllib.parse as urlparse
from zvmconnector import connector
from nova import exception
LOG = logging.getLogger(__name__)
class ConnectorClient(object):
"""Request handler to zVM cloud connector"""
def __init__(self, zcc_url, ca_file=None):
_url = urlparse.urlparse(zcc_url)
_ssl_enabled = False
if _url.scheme == 'https':
_ssl_enabled = True
elif ca_file:
LOG.warning("url is %(url) which is not https "
"but ca_file is configured to %(ca_file)s",
{'url': zcc_url, 'ca_file': ca_file})
if _ssl_enabled and ca_file:
self._conn = connector.ZVMConnector(_url.hostname, _url.port,
ssl_enabled=_ssl_enabled,
verify=ca_file)
else:
self._conn = connector.ZVMConnector(_url.hostname, _url.port,
ssl_enabled=_ssl_enabled,
verify=False)
def call(self, func_name, *args, **kwargs):
results = self._conn.send_request(func_name, *args, **kwargs)
if results['overallRC'] != 0:
LOG.error("zVM Cloud Connector request %(api)s failed with "
"parameters: %(args)s %(kwargs)s . Results: %(results)s",
{'api': func_name, 'args': six.text_type(args),
'kwargs': six.text_type(kwargs),
'results': six.text_type(results)})
raise exception.ZVMConnectorError(results=results)
return results['output']

View File

@ -66,3 +66,4 @@ retrying>=1.3.3,!=1.3.0 # Apache-2.0
os-service-types>=1.2.0 # Apache-2.0
taskflow>=2.16.0 # Apache-2.0
python-dateutil>=2.5.3 # BSD
zVMCloudConnector>=1.1.1;sys_platform!='win32' # Apache 2.0 License