Driver code for CPCSubset listing feature

- Driver code has been added which can list the CpcSubsets- subset of zSystems
Central processor complex (CPC)
- Dpm configuration files based on oslo config libraries that will read dpm
specific attributes from nova.conf
- All relevant Driver functions for listing CpcSubsets is implemented
- Defaulted CPCSubset type to BAREMETAL which will be updated with HVType
is updated with PRSM hypervisor type
- Unit testcases for some of the files [WIP]

Change-Id: Id447928fee253aac87f8a1da251f99a3386f53dd
Partial-Bug: #1645999
This commit is contained in:
preethipy 2016-12-01 16:05:21 +05:30
parent 83c2926d1c
commit 3b2c0aa7ac
14 changed files with 693 additions and 0 deletions

21
nova_dpm/conf/__init__.py Normal file
View File

@ -0,0 +1,21 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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_dpm.conf import dpm
from oslo_config import cfg
CONF = cfg.CONF
dpm.register_opts(CONF)

60
nova_dpm/conf/dpm.py Normal file
View File

@ -0,0 +1,60 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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
dpm_group = cfg.OptGroup('dpm',
title='DPM options',
help="""
PR/SM2 (in DPM mode) is a hypervisor. By using PR/SM2 hypervisor we can create
partition on IBM z Systems or IBM LinuxONE system. A partition
is a virtual representation of the hardware resources of a
z Systems or LinuxONE system.
Hardware Management Console (HMC) is a component of DPM by
using which we can create partitions.
DPM options are used when the compute_driver is set to use
DPM (compute_driver=dpm.HMCDriver).
""")
ALL_DPM_OPTS = [
cfg.StrOpt('hmc', default='', required=True, help="""
Hostname or IP address for connection to HMC via zhmcclient"""),
cfg.StrOpt('hmc_username', default='', required=True, help="""
User name for connection to HMC Host."""),
cfg.StrOpt('hmc_password', default='', required=True, help="""
Password for connection to HMC Host."""),
cfg.StrOpt('host', default='', required=True, help="""
CpcSubset name"""),
cfg.StrOpt('cpc_uuid', help="""
Uuid of the CPC"""),
cfg.IntOpt('max_processors', help="""
Maximum number of shared IFL processors available on CpcSubset"""),
cfg.IntOpt('max_memory', help="""
Maximum amount of memory available on CpcSubset"""),
cfg.IntOpt('max_instances', help="""
Maximum number of instances that can be created on CpcSubset""")
]
def register_opts(conf):
conf.register_group(dpm_group)
conf.register_opts(ALL_DPM_OPTS, group=dpm_group)
def list_opts():
return {dpm_group: ALL_DPM_OPTS}

View File

@ -0,0 +1,38 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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.
"""
:mod:`nova.tests.unit` -- Nova Unittests
=====================================================
.. automodule:: nova.tests.unit
:platform: Unix
"""
import eventlet
from nova import objects
eventlet.monkey_patch(os=False)
# NOTE(alaski): Make sure this is done after eventlet monkey patching otherwise
# the threading.local() store used in oslo_messaging will be initialized to
# threadlocal storage rather than greenthread local. This will cause context
# sets and deletes in that storage to clobber each other.
# NOTE(comstud): Make sure we have all of the objects loaded. We do this
# at module import time, because we may be using mock decorators in our
# tests that run at import time.
objects.register_all()

View File

View File

View File

@ -0,0 +1,45 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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 zhmcclient._cpc import Cpc
def getFakeCPC(cpcmanager):
cpc_props = dict()
cpc_props['object-uri'] = "/api/cpc/dummy"
cpc_props['name'] = "fakecpc"
cpc_props['storage-customer'] = 100
cpc_props['processor-count-pending-ifl'] = 6
cpc_props['processor-count-ifl'] = 12
cpc_props['storage-customer-available'] = 50
cpc = Cpc(cpcmanager, cpc_props['object-uri'], cpc_props)
return cpc
def getFakeCPCconf():
conf = {'hostname': "S12subset",
'cpc_uuid': "1232132",
'max_processors': 10,
'max_memory_mb': 200,
'max_partitions': 10
}
return conf
def getFakeCPCwithProp(cpcmanager, cpc_props):
cpc = Cpc(cpcmanager, cpc_props['object-uri'], cpc_props)
return cpc

View File

@ -0,0 +1,90 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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_dpm.tests.unit.virt.dpm import fakecpcs
from zhmcclient._manager import BaseManager
import zhmcclient
"""Fake zhmcclient"""
def getCpcmgr(ipaddress, username, password):
session = Session(ipaddress, username, password)
client = Client(session)
cpcmgr = CpcManager(client)
return cpcmgr
def getCpcmgrForClient(client):
cpcmgr = zhmcclient.CpcManager(client)
return cpcmgr
def getzhmclientCpcmgr(ipaddress, username, password):
session = Session(ipaddress, username, password)
client = Client(session)
cpcmgr = zhmcclient.CpcManager(client)
return cpcmgr
class CpcManager(BaseManager):
"""fake cpcmanager"""
def __init__(self, client):
# This function should not go into the docs.
# Parameters:
# client (:class:`~zhmcclient.Client`):
# Client object for the HMC to be used.
super(CpcManager, self).__init__()
self._session = client.session
def list(self, full_properties=False):
cpc_list = []
zhmclientcpcmgr = getzhmclientCpcmgr("0.0.0.0",
"dummyuser", "dummypassword")
cpc_list.append(fakecpcs.getFakeCPC(zhmclientcpcmgr))
return cpc_list
class Session(object):
"""fake Session"""
def __init__(self, host, userid=None, password=None):
self._host = host
self._userid = userid
self._password = password
return None
@property
def userid(self):
return self._userid
class Client(object):
"""fake client"""
def __init__(self, session):
self._session = session
self._cpcs = CpcManager(self)
self._api_version = None
@property
def cpcs(self):
cpcmgr = getCpcmgr("0.0.0.0", "dummyuser", "dummypassword")
return cpcmgr
@property
def session(self):
return self._session

View File

@ -0,0 +1,57 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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
import nova_dpm.conf
from nova.test import TestCase
from nova_dpm.tests.unit.virt.dpm import fakezhmcclient
from nova_dpm.virt.dpm import driver
from nova_dpm.virt.dpm import driver as dpm_driver
"""
cpcsubset unit testcase
"""
dpm_driver.zhmcclient = fakezhmcclient
CONF = nova_dpm.conf.CONF
class DPMdriverTestCase(TestCase):
def setUp(self):
super(DPMdriverTestCase, self).setUp()
@mock.patch.object(driver.LOG, 'debug')
@mock.patch.object(CONF, 'dpm')
def test_host(self, mock_dpmconf, mock_warning):
mock_dpmconf.hmc_username = "dummyuser"
mock_dpmconf.hmc_password = "dummy"
mock_dpmconf.hmc = "1.1.1.1"
dummyvirtapi = None
dpmdriver = driver.DPMDriver(dummyvirtapi)
self.assertIsNotNone(dpmdriver)
expected_arg = 'HMC details 1.1.1.1 dummyuser'
assertlogs = False
for call in mock_warning.call_args_list:
if (len(call) > 0):
if (len(call[0]) > 0 and call[0][0] == expected_arg):
assertlogs = True
self.assertTrue(assertlogs)

View File

@ -0,0 +1,66 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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.test import TestCase
from nova_dpm.tests.unit.virt.dpm import fakecpcs
from nova_dpm.tests.unit.virt.dpm import fakezhmcclient
from nova_dpm.virt.dpm import host
"""
cpcsubset unit testcase
"""
class HostTestCase(TestCase):
def setUp(self):
super(HostTestCase, self).setUp()
@mock.patch.object(host.LOG, 'debug')
def test_host(self, mock_warning):
session = fakezhmcclient.Session("dummy", "dummy", "dummy")
client = fakezhmcclient.Client(session)
cpcmanager = fakezhmcclient.getCpcmgrForClient(client)
cpc = fakecpcs.getFakeCPC(cpcmanager)
conf = fakecpcs.getFakeCPCconf()
host.Host(conf, cpc, client)
expected_arg = "Host initializing done"
assertlogs = False
for call in mock_warning.call_args_list:
if (len(call) > 0):
if (len(call[0]) > 0 and call[0][0] == expected_arg):
assertlogs = True
self.assertTrue(assertlogs)
@mock.patch.object(host.LOG, 'debug')
def test_host_properties(self, mock_warning):
session = fakezhmcclient.Session("dummy", "dummy", "dummy")
client = fakezhmcclient.Client(session)
cpcmanager = fakezhmcclient.getCpcmgrForClient(client)
cpc = fakecpcs.getFakeCPC(cpcmanager)
conf = fakecpcs.getFakeCPCconf()
host1 = host.Host(conf, cpc, client)
host_properties = host1.properties
self.assertEqual(host_properties['hypervisor_hostname'], 'S12subset')
self.assertEqual(host_properties['cpc_name'], 'fakecpc')

View File

View File

@ -0,0 +1,17 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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_dpm.virt.dpm import driver
DPMDriver = driver.DPMDriver

137
nova_dpm/virt/dpm/driver.py Normal file
View File

@ -0,0 +1,137 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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.
"""
A connection to a z Systems through Dynamic Partition Manager( DPM) APIs.
Supports DPM APIs for virtualization in z Systems
"""
from nova_dpm.virt.dpm import host as Host
from nova_dpm.virt.dpm import utils
from nova.virt import driver
from oslo_log import log as logging
from oslo_utils import importutils
import nova_dpm.conf
import requests.packages.urllib3
LOG = logging.getLogger(__name__)
CONF = nova_dpm.conf.CONF
zhmcclient = None
class DPMDriver(driver.ComputeDriver):
def __init__(self, virtapi):
super(DPMDriver, self).__init__(virtapi)
LOG.debug("__init__")
self.virtapi = virtapi
self._compute_event_callback = None
self._host = None
self._client = None
# Retrieve zhmc ipaddress, username, password from the nova.conf
zhmc = CONF.dpm.hmc
userid = CONF.dpm.hmc_username
password = CONF.dpm.hmc_password
self._get_zhmclient(zhmc, userid, password)
LOG.debug("HMC details %(zhmc)s %(userid)s"
% {'zhmc': zhmc, 'userid': userid})
def _get_zhmclient(self, zhmc, userid, password):
LOG.debug("_get_zhmclient")
# TODO(preethipy): The below line will be removed once the warnings are
# supressed within zhmclient code
requests.packages.urllib3.disable_warnings()
global zhmcclient
if zhmcclient is None:
zhmcclient = importutils.import_module('zhmcclient')
session = zhmcclient.Session(zhmc, userid, password)
self._client = zhmcclient.Client(session)
def init_host(self, host):
"""Driver initialization of the hypervisor node"""
LOG.debug("init_host")
# retrieve from ncpu service configurationfile
conf = {'hostname': CONF.dpm.host,
'cpc_uuid': CONF.dpm.cpc_uuid,
'max_processors': CONF.dpm.max_processors,
'max_memory_mb': CONF.dpm.max_memory,
'max_partitions': CONF.dpm.max_instances
}
cpc = self._client.cpcs.find(**{"object-id": conf['cpc_uuid']})
LOG.debug("Matching hypervisor found %(host)s for UUID "
"%(uuid)s and CPC %(cpcname)s" %
{'host': conf['hostname'],
'uuid': conf['cpc_uuid'],
'cpcname': cpc.properties['name']})
utils.valide_host_conf(conf, cpc)
self._host = Host.Host(conf, cpc, self._client)
def get_available_resource(self, nodename):
"""Retrieve resource information.
This method is called when nova-compute launches, and
as part of a periodic task that records the results in the DB.
:param nodename:
node which the caller want to get resources from
a driver that manages only one node can safely ignore this
:returns: Dictionary describing resources
"""
LOG.debug("get_available_resource")
dictval = self._host.properties
return dictval
def get_available_nodes(self, refresh=False):
"""Returns nodenames of all nodes managed by the compute service.
This method is for multi compute-nodes support. If a driver supports
multi compute-nodes, this method returns a list of nodenames managed
by the service. Otherwise, this method should return
[hypervisor_hostname].
"""
# TODO(preethipy): Refresh parameter should be handled to fetch
# updated nodenames
LOG.debug("get_available_nodes return node %(hostname)s" %
{'hostname': self._host.properties["hypervisor_hostname"]})
nodenames = [self._host.properties["hypervisor_hostname"]]
return nodenames
def node_is_available(self, nodename):
"""Return whether this compute service manages a particular node."""
LOG.debug("node_is_available")
if nodename in self.get_available_nodes():
return True
# Refresh and check again.
return nodename in self.get_available_nodes(refresh=True)
def get_num_instances(self):
LOG.debug("get_num_instances")
# TODO(preethipy): Will be updated with actual number of instances
return 0

110
nova_dpm/virt/dpm/host.py Normal file
View File

@ -0,0 +1,110 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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.
"""
Host will have the handle to the CPCSubsetMgr which will retrieve cpcsubsets
"""
from nova.objects import fields as obj_fields
from oslo_log import log as logging
from oslo_serialization import jsonutils
LOG = logging.getLogger(__name__)
zhmcclient = None
class Host(object):
def __init__(self, conf, cpc, client):
LOG.debug('Host initializing for host %(hostname)s'
% {'hostname': cpc.properties['name']})
self._conf = conf
self._client = client
self._cpc = cpc
self._instances = [] # TODO(preethipy): Instance details
# to be populated
self._properties = self._get_host_poperties()
LOG.debug('Host initializing done')
@property
def properties(self):
LOG.debug('properties')
self._properties = self._get_host_poperties()
return self._properties
def _get_host_poperties(self):
LOG.debug('_get_host_properties')
dict_mo = {
"memory_mb": self._conf["max_memory_mb"],
"vcpus": self._conf["max_processors"],
'vcpus_used': self._get_proc_used(),
"local_gb": 1024, # TODO(preethipy): Update with relevant value
"memory_mb_used": self._get_mem_used(),
"local_gb_used": 0, # TODO(preethipy): Update with relevant value
"cpu_info": self._get_cpu_info(self._conf["max_processors"]),
"hypervisor_type": "PRSM",
"hypervisor_version": 1000, # TODO(preethipy): Update with
# relevant value
"numa_topology": "", # TODO(preethipy): Update with relevant value
"hypervisor_hostname": self._conf['hostname'],
"cpc_name": self._cpc.properties['name'],
"disk_available_least": 1024, # TODO(preethipy): Update with
# relevant value
'supported_instances':
[("s390", obj_fields.HVType.BAREMETAL, obj_fields.VMMode.HVM)]}
# TODO(preethipy): BareMETAL will be updated with PRSM after HVType
# updated in upstream code
LOG.debug(dict_mo)
return dict_mo
def _get_cpu_info(self, cores):
"""Get cpuinfo information.
Obtains cpu feature from virConnect.getCapabilities,
and returns as a json string.
:return: see above description
"""
cpu_info = dict()
cpu_info['arch'] = 's390x'
cpu_info['model'] = 'host'
cpu_info['vendor'] = 'IBM'
topology = dict()
topology['sockets'] = 1 # TODO(preethipy): Update with relevant value
topology['cores'] = cores
topology['threads'] = 1 # TODO(preethipy): Update with relevant value
cpu_info['topology'] = topology
features = list() # TODO(preethipy): Update with featureset required
cpu_info['features'] = features
return jsonutils.dumps(cpu_info)
def _get_proc_used(self):
# TODO(preethipy): should return processor used once the
# instances created
return 0
def _get_mem_used(self):
# TODO(preethipy): should return memory used once the
# instances created
return 0

View File

@ -0,0 +1,52 @@
# Copyright 2016 IBM Corp. All Rights Reserved.
#
# 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.i18n import _
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
def valide_host_conf(conf, cpc):
LOG.debug('valide_host_conf')
if (cpc.dpm_enabled):
cpc.pull_full_properties()
else:
# TODO(preethipy): Exception infrastructure to be finalized
raise Exception("Host not in DPM mode")
if (conf['max_processors'] > cpc.properties['processor-count-ifl']):
# TODO(preethipy): Exception infrastructure to be finalized
errormsg = (_("max_processors %(config_proc)d configured for "
"CpcSubset %(subsetname)s is greater than the "
"available amount of processors %(max_proc)d on "
"CPC uuid %(cpcuuid)s and CPC name %(cpcname)s")
% {'config_proc': conf['max_processors'],
'subsetname': conf['host'],
'max_proc': cpc.properties['processor-count-ifl'],
'cpcuuid': conf['cpc_uuid'],
'cpcname': conf['hostname']})
raise Exception(errormsg)
if (conf['max_memory_mb'] > cpc.properties['storage-customer']):
# TODO(preethipy): Exception infrastructure to be finalized
errormsg = (_("max_memory_mb %(config_mem)d configured for "
"CpcSubset %(subsetname)s is greater than the "
"available amount of memory %(max_mem)d on CPC "
"uuid %(cpcuuid)s and CPC name %(cpcname)s")
% {'config_mem': conf['max_processors'],
'subsetname': conf['host'],
'max_proc': cpc.properties['processor-count-ifl'],
'max_mem': conf['cpc_uuid'],
'cpcname': conf['hostname']})
raise Exception(errormsg)