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 websockify==0.8.0
wrapt==1.10.11 wrapt==1.10.11
wsgi-intercept==1.7.0 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 wsgi
from nova.conf import xenserver from nova.conf import xenserver
from nova.conf import xvp from nova.conf import xvp
from nova.conf import zvm
CONF = cfg.CONF CONF = cfg.CONF
@ -123,5 +124,6 @@ workarounds.register_opts(CONF)
wsgi.register_opts(CONF) wsgi.register_opts(CONF)
xenserver.register_opts(CONF) xenserver.register_opts(CONF)
xvp.register_opts(CONF) xvp.register_opts(CONF)
zvm.register_opts(CONF)
remote_debug.register_cli_opts(CONF) remote_debug.register_cli_opts(CONF)

View File

@ -43,6 +43,7 @@ Possible values:
* ``vmwareapi.VMwareVCDriver`` * ``vmwareapi.VMwareVCDriver``
* ``hyperv.HyperVDriver`` * ``hyperv.HyperVDriver``
* ``powervm.PowerVMDriver`` * ``powervm.PowerVMDriver``
* ``zvm.ZVMDriver``
"""), """),
cfg.BoolOpt('allow_resize_to_same_host', cfg.BoolOpt('allow_resize_to_same_host',
default=False, 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): class IronicAPIVersionNotAvailable(NovaException):
msg_fmt = _('Ironic API version %(version)s is not available.') 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 os-service-types>=1.2.0 # Apache-2.0
taskflow>=2.16.0 # Apache-2.0 taskflow>=2.16.0 # Apache-2.0
python-dateutil>=2.5.3 # BSD python-dateutil>=2.5.3 # BSD
zVMCloudConnector>=1.1.1;sys_platform!='win32' # Apache 2.0 License