The init commmit of stackforge/zvm-driver

Including all python modules for nova-zvm-virt-driver and
neutron-zvm-plugin.

Change-Id: I72dd9f64fc412cbf10f5e7ab6e4ac465a977e849
This commit is contained in:
Huang Rui 2014-11-03 15:20:08 +08:00
parent 14a3ea6ab2
commit cf77385929
29 changed files with 10293 additions and 0 deletions

9
.gitignore vendored Executable file
View File

@ -0,0 +1,9 @@
*.egg*
*.log
*.pyc
.project
.pydevproject
.tox
.venv
build/*
dist/*

0
.gitreview Normal file → Executable file
View File

5
README.rst Normal file
View File

@ -0,0 +1,5 @@
This project contains a set of drivers and plugins for different OpenStack
components that enables OpenStack manage z/VM hypervisor and virtual machines
running in the z/VM system.
Wiki page: https://wiki.openstack.org/wiki/ZVMDriver

View File

@ -0,0 +1,62 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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
from neutron.openstack.common import log as logging
from neutron.plugins.common import utils as plugin_utils
from neutron.plugins.zvm.common import utils
LOG = logging.getLogger(__name__)
vswitch_opts = [
cfg.StrOpt('rdev_list',
help='RDev list for vswitch uplink port')]
CONF = cfg.CONF
CONF.import_opt('flat_networks', "neutron.plugins.ml2.drivers.type_flat",
'ml2_type_flat')
CONF.import_opt('network_vlan_ranges', "neutron.plugins.ml2.drivers.type_vlan",
'ml2_type_vlan')
class zvmVswitch(object):
def __init__(self, zhcp, name, vlan):
self._utils = utils.zvmUtils()
self._utils.add_vswitch(zhcp, name,
eval("CONF." + name + ".rdev_list"), vid=vlan)
self.zhcp = zhcp
class zvmNetwork(object):
def __init__(self):
self._zhcp = CONF.AGENT.xcat_zhcp_nodename
self._vsws = []
self._maps = {}
self._creat_networks()
def _creat_networks(self):
self._maps = plugin_utils.parse_network_vlan_ranges(
CONF.ml2_type_vlan.network_vlan_ranges
+ CONF.ml2_type_flat.flat_networks)
self._vsws = []
for vsw in self._maps.keys():
CONF.register_opts(vswitch_opts, vsw)
self._vsws.append(zvmVswitch(self._zhcp, vsw, self._maps[vsw]))
def get_network_maps(self):
return self._maps

View File

@ -0,0 +1,350 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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 eventlet
import sys
import time
import zvm_network
from oslo.config import cfg
from neutron import context
from neutron.agent import rpc as agent_rpc
from neutron.common import config as common_config
from neutron.common import constants as q_const
from neutron.common import rpc as n_rpc
from neutron.common import topics
from neutron.plugins.common import constants as p_const
from neutron.openstack.common import log as logging
from neutron.openstack.common import loopingcall
from neutron.openstack.common.gettextutils import _
from neutron.plugins.zvm.common import exception
from neutron.plugins.zvm.common import utils
LOG = logging.getLogger(__name__)
def restart_wrapper(func):
def wrapper(*args, **kw):
gen = func(*args, **kw)
gen.next()
return gen
return wrapper
class zvmNeutronAgent(n_rpc.RpcCallback):
RPC_API_VERSION = '1.1'
def __init__(self):
super(zvmNeutronAgent, self).__init__()
self._utils = utils.zvmUtils()
self._polling_interval = cfg.CONF.AGENT.polling_interval
self._zhcp_node = cfg.CONF.AGENT.xcat_zhcp_nodename
self._host = cfg.CONF.AGENT.zvm_host
zvm_net = zvm_network.zvmNetwork()
self.agent_state = {
'binary': 'neutron-zvm-agent',
'host': self._host,
'topic': q_const.L2_AGENT_TOPIC,
'configurations': {'vswitch_mappings': zvm_net.get_network_maps()},
'agent_type': q_const.AGENT_TYPE_ZVM,
'start_flag': True}
self._setup_server_rpc()
self._zhcp_userid = self._utils.get_zhcp_userid(self._zhcp_node)
self._restart_handler = self._handle_restart()
def _setup_server_rpc(self):
self.agent_id = 'zvm_agent_%s' % self._zhcp_node
self.topic = topics.AGENT
self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
self.context = context.get_admin_context_without_session()
self.endpoints = [self]
consumers = [[topics.PORT, topics.UPDATE],
[topics.NETWORK, topics.DELETE]]
self.connection = agent_rpc.create_consumers(self.endpoints,
self.topic,
consumers)
report_interval = cfg.CONF.AGENT.report_interval
if report_interval:
heartbeat = loopingcall.FixedIntervalLoopingCall(
self._report_state)
heartbeat.start(interval=report_interval)
def _report_state(self):
try:
self.state_rpc.report_state(self.context, self.agent_state)
self.agent_state.pop('start_flag', None)
except Exception:
LOG.exception(_("Failed reporting state!"))
def network_delete(self, context, network_id=None):
LOG.debug(_("Network delete received. UUID: %s"), network_id)
def port_update(self, context, **kwargs):
port = kwargs.get('port')
LOG.debug(_("Port update received. UUID: %s"), port)
if not port['id'] in self._port_map.keys():
# update a port which is not coupled to any NIC, nothing
# to do for a user based vswitch
return
vswitch = self._port_map[port['id']]['vswitch']
userid = self._port_map[port['id']]['userid']
if port['admin_state_up']:
self._utils.couple_nic_to_vswitch(vswitch, port['id'],
self._zhcp_node, userid)
self.plugin_rpc.update_device_up(self.context, port['id'],
self.agent_id)
else:
self._utils.uncouple_nic_from_vswitch(vswitch, port['id'],
self._zhcp_node, userid)
self.plugin_rpc.update_device_down(self.context, port['id'],
self.agent_id)
self._utils.put_user_direct_online(self._zhcp_node,
self._zhcp_userid)
def port_bound(self, port_id, net_uuid,
network_type, physical_network, segmentation_id, userid):
LOG.debug(_("Binding port %s"), port_id)
self._utils.grant_user(self._zhcp_node, physical_network, userid)
vdev = self._utils.couple_nic_to_vswitch(physical_network, port_id,
self._zhcp_node, userid)
self._utils.put_user_direct_online(self._zhcp_node,
self._zhcp_userid)
if network_type == p_const.TYPE_VLAN:
LOG.info(_('Binding VLAN, VLAN ID: %s'), segmentation_id)
self._utils.set_vswitch_port_vlan_id(segmentation_id, port_id,
vdev, self._zhcp_node,
physical_network)
else:
LOG.info(_('Bind %s mode done'), network_type)
def port_unbound(self, port_id):
LOG.debug(_("Unbinding port %s"), port_id)
# uncouple is not necessary, because revoke user will uncouple it
# automatically.
self._utils.revoke_user(self._zhcp_node,
self._port_map[port_id]['vswitch'],
self._port_map[port_id]['userid'])
def _update_ports(self, registered_ports):
ports_info = self._utils.get_nic_ids()
ports = set()
for p in ports_info:
target_host = p.split(',')[5].strip('"')
new_port_id = p.split(',')[2].strip('"')
if target_host == self._zhcp_node:
ports.add(new_port_id)
if ports == registered_ports:
return
added = ports - registered_ports
removed = registered_ports - ports
return {'current': ports, 'added': added, 'removed': removed}
def _treat_vif_port(self, port_id, network_id, network_type,
physical_network, segmentation_id,
admin_state_up):
node = self._utils.get_node_from_port(port_id)
userid = self._utils.get_userid_from_node(node)
LOG.info(_("Update port for node:%s") % node)
if admin_state_up:
self.port_bound(port_id, network_id, network_type,
physical_network, segmentation_id,
userid)
else:
self._utils.grant_user(self._zhcp_node, physical_network, userid)
return (node, userid)
def _treat_devices_added(self, devices):
for device in devices:
LOG.info(_("Adding port %s") % device)
try:
details = self.plugin_rpc.get_device_details(self.context,
device,
self.agent_id)
except Exception:
LOG.debug(_("Unable to get port details for %s:"), device)
continue
try:
if 'port_id' in details:
LOG.info(_("Port %(device)s updated."
" Details: %(details)s"),
{'device': device, 'details': details})
(node, userid) = self._treat_vif_port(
details['port_id'],
details['network_id'],
details['network_type'],
details['physical_network'],
details['segmentation_id'],
details['admin_state_up'])
# add device done, keep port map info
self._port_map[device] = {}
self._port_map[device]['userid'] = userid
self._port_map[device]['nodename'] = node
self._port_map[device]['vswitch'] = details[
'physical_network']
self._port_map[device]['vlan_id'] = details[
'segmentation_id']
# no rollback if this fails
self._utils.update_xcat_switch(details['port_id'],
details['physical_network'],
details['segmentation_id'])
if details.get('admin_state_up'):
LOG.debug(_("Setting status for %s to UP"), device)
self.plugin_rpc.update_device_up(
self.context, device, self.agent_id, cfg.CONF.host)
else:
LOG.debug(_("Setting status for %s to DOWN"), device)
self.plugin_rpc.update_device_down(
self.context, device, self.agent_id, cfg.CONF.host)
else:
LOG.debug(_("Device %s not defined on Neutron server"),
device)
continue
except Exception as e:
LOG.exception(_("Can not add device %(device)s: %(msg)s"),
{'device': device, 'msg': e})
continue
def _treat_devices_removed(self, devices):
for device in devices:
LOG.info(_("Removing port %s"), device)
try:
if not device in self._port_map:
LOG.warn(_("Can't find port %s in zvm agent"), device)
continue
self.port_unbound(device)
self.plugin_rpc.update_device_down(self.context,
device,
self.agent_id)
del self._port_map[device]
except Exception as e:
LOG.exception(_("Removing port failed %(device)s: %(msg)s"),
{'device': device, 'msg': e})
continue
def _process_network_ports(self, port_info):
if len(port_info['added']):
self._treat_devices_added(port_info['added'])
if len(port_info['removed']):
self._treat_devices_removed(port_info['removed'])
def xcatdb_daemon_loop(self):
ports = set()
# Get all exsited ports as configured
all_ports_info = self._update_ports(ports)
if all_ports_info is not None:
ports = all_ports_info['current']
connect = True
while True:
try:
start_time = time.time()
port_info = self._update_ports(ports)
# if no exception is raised in _update_ports,
# then the connection has recovered
if connect is False:
self._restart_handler.send(None)
connect = True
if port_info:
LOG.debug(_("Devices change!"))
self._process_network_ports(port_info)
ports = port_info['current']
except exception.zVMxCatRequestFailed as e:
LOG.error(_("Lost connection to xCAT. %s"), e)
connect = False
except Exception as e:
LOG.exception(_("error in xCAT DB query loop: %s"), e)
# sleep till end of polling interval
elapsed = (time.time() - start_time)
if (elapsed < self._polling_interval):
sleep_time = self._polling_interval - elapsed
LOG.debug(_("Sleep %s"), sleep_time)
time.sleep(sleep_time)
else:
LOG.debug(_("Looping iteration exceeded interval"))
def _init_xcat_mgt(self):
'''xCAT Management Node(MN) use the first flat network to manage all
the instances. So a flat network is required.
To talk to xCAT MN, xCAT MN requires every instance has a NIC which is
in the same subnet as xCAT. The xCAT MN's IP address is xcat_mgt_ip,
mask is xcat_mgt_mask in the config file,
by default neutron_zvm_plugin.ini.
'''
if not len(cfg.CONF.ml2_type_flat.flat_networks):
raise exception.zvmException(
msg=_('Can not find xCAT management network,'
'a flat network is required by xCAT.'))
self._utils.create_xcat_mgt_network(self._zhcp_node,
cfg.CONF.AGENT.xcat_mgt_ip,
cfg.CONF.AGENT.xcat_mgt_mask,
cfg.CONF.ml2_type_flat.flat_networks[0])
@restart_wrapper
def _handle_restart(self):
xcat_uptime, zvm_uptime = (None, None)
while True:
LOG.info(_("Try to reinitialize network ... "))
try:
tmp_new_time = self._utils.query_xcat_uptime(self._zhcp_node)
if xcat_uptime != tmp_new_time:
self._init_xcat_mgt()
xcat_uptime = tmp_new_time
tmp_new_time = self._utils.query_zvm_uptime(self._zhcp_node)
if zvm_uptime != tmp_new_time:
self._port_map = self._utils.re_grant_user(self._zhcp_node)
zvm_uptime = tmp_new_time
yield
except Exception:
LOG.error(_("Failed to handle restart,"
"try again in 5 seconds"))
time.sleep(5)
def main():
eventlet.monkey_patch()
cfg.CONF(project='neutron')
common_config.init(sys.argv[1:])
common_config.setup_logging()
agent = zvmNeutronAgent()
# Start to query xCAT DB
agent.xcatdb_daemon_loop()
LOG.info(_("z/VM agent initialized, now running... "))
sys.exit(0)

View File

@ -0,0 +1,66 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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
from neutron.agent.common import config
from neutron.openstack.common.gettextutils import _
agent_opts = [
cfg.StrOpt(
'xcat_zhcp_nodename',
default='zhcp',
help=_('xCat zHCP nodename in xCAT ')),
cfg.StrOpt(
'zvm_host',
help=_('z/VM host that managed by zHCP.')),
cfg.StrOpt(
'zvm_xcat_username',
default='admin',
help=_('xCat REST API username')),
cfg.StrOpt(
'zvm_xcat_password',
default='admin',
secret=True,
help=_('Password of the xCat REST API user')),
cfg.StrOpt(
'zvm_xcat_server',
help=_("xCat MN server address")),
cfg.IntOpt(
'polling_interval',
default=2,
help=_("The number of seconds the agent will wait between "
"polling for local device changes.")),
cfg.IntOpt(
'zvm_xcat_timeout',
default=300,
help=_("The number of seconds the agent will wait for "
"xCAT MN response")),
cfg.StrOpt(
'xcat_mgt_ip',
default=None,
help=_("The IP address is used for xCAT MN to management instances.")),
cfg.StrOpt(
'xcat_mgt_mask',
default=None,
help=_("The IP mask is used for xCAT MN to management instances.")),
]
CONF = cfg.CONF
CONF.register_opts(agent_opts, "AGENT")
config.register_agent_state_opts_helper(cfg.CONF)
config.register_root_helper(cfg.CONF)

View File

@ -0,0 +1,17 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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.
XCAT_RESPONSE_KEYS = ('info', 'data', 'node', 'errorcode', 'error')

View File

@ -0,0 +1,43 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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 neutron.common import exceptions as exception
from neutron.openstack.common.gettextutils import _
class zvmException(exception.NeutronException):
message = _('zvmException: %(msg)s')
class zVMxCatConnectionFailed(exception.NeutronException):
message = _('Failed to connect xCAT server: %(xcatserver)s')
class zVMxCatRequestFailed(exception.NeutronException):
message = _('Request to xCAT server %(xcatserver)s failed: %(err)s')
class zVMJsonLoadsError(exception.NeutronException):
message = _('JSON loads error: not in JSON format')
class zVMInvalidDataError(exception.NeutronException):
message = _('Invalid data error: %(msg)s')
class zVMInvalidxCatResponseDataError(exception.NeutronException):
message = _('Invalid data returned from xCAT: %(msg)s')

View File

@ -0,0 +1,415 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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 xcatutils
from oslo.config import cfg
from neutron.openstack.common import log as logging
from neutron.openstack.common.gettextutils import _
from neutron.plugins.zvm.common import exception
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class zvmUtils(object):
_MAX_REGRANT_USER_NUMBER = 1000
def __init__(self):
self._xcat_url = xcatutils.xCatURL()
self._zhcp_userid = None
self._userid_map = {}
self._xcat_node_name = self._get_xcat_node_name()
def get_node_from_port(self, port_id):
return self._get_nic_settings(port_id, get_node=True)
def get_nic_ids(self):
addp = ''
url = self._xcat_url.tabdump("/switch", addp)
nic_settings = xcatutils.xcat_request("GET", url)
# remove table header
nic_settings['data'][0].pop(0)
# it's possible to return empty array
return nic_settings['data'][0]
def _get_nic_settings(self, port_id, field=None, get_node=False):
"""Get NIC information from xCat switch table."""
LOG.debug(_("Get nic information for port: %s"), port_id)
addp = '&col=port&value=%s' % port_id + '&attribute=%s' % (
field and field or 'node')
url = self._xcat_url.gettab("/switch", addp)
nic_settings = xcatutils.xcat_request("GET", url)
ret_value = nic_settings['data'][0][0]
if field is None and not get_node:
ret_value = self.get_userid_from_node(ret_value)
return ret_value
def get_userid_from_node(self, node):
addp = '&col=node&value=%s&attribute=userid' % node
url = self._xcat_url.gettab("/zvm", addp)
user_info = xcatutils.xcat_request("GET", url)
return user_info['data'][0][0]
def couple_nic_to_vswitch(self, vswitch_name, switch_port_name,
zhcp, userid, dm=True, immdt=True):
"""Couple nic to vswitch."""
LOG.debug(_("Connect nic to switch: %s"), vswitch_name)
vdev = self._get_nic_settings(switch_port_name, "interface")
if vdev:
self._couple_nic(zhcp, vswitch_name, userid, vdev, dm, immdt)
else:
raise exception.zVMInvalidDataError(msg=('Cannot get vdev for '
'user %s, couple to port %s') %
(userid, switch_port_name))
return vdev
def uncouple_nic_from_vswitch(self, vswitch_name, switch_port_name,
zhcp, userid, dm=True, immdt=True):
"""Uncouple nic from vswitch."""
LOG.debug(_("Disconnect nic from switch: %s"), vswitch_name)
vdev = self._get_nic_settings(switch_port_name, "interface")
self._uncouple_nic(zhcp, userid, vdev, dm, immdt)
def set_vswitch_port_vlan_id(self, vlan_id, switch_port_name, vdev, zhcp,
vswitch_name):
userid = self._get_nic_settings(switch_port_name)
if not userid:
raise exception.zVMInvalidDataError(msg=('Cannot get userid by '
'port %s') % (switch_port_name))
url = self._xcat_url.xdsh("/%s" % zhcp)
commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
commands += " -T %s" % userid
commands += ' -k grant_userid=%s' % userid
commands += " -k switch_name=%s" % vswitch_name
commands += " -k user_vlan_id=%s" % vlan_id
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
def grant_user(self, zhcp, vswitch_name, userid):
"""Set vswitch to grant user."""
url = self._xcat_url.xdsh("/%s" % zhcp)
commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
commands += " -T %s" % userid
commands += " -k switch_name=%s" % vswitch_name
commands += " -k grant_userid=%s" % userid
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
def revoke_user(self, zhcp, vswitch_name, userid):
"""Set vswitch to grant user."""
url = self._xcat_url.xdsh("/%s" % zhcp)
commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
commands += " -T %s" % userid
commands += " -k switch_name=%s" % vswitch_name
commands += " -k revoke_userid=%s" % userid
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
def _couple_nic(self, zhcp, vswitch_name, userid, vdev, dm, immdt):
"""Couple NIC to vswitch by adding vswitch into user direct."""
url = self._xcat_url.xdsh("/%s" % zhcp)
if dm:
commands = '/opt/zhcp/bin/smcli'
commands += ' Virtual_Network_Adapter_Connect_Vswitch_DM'
commands += " -T %s " % userid + "-v %s" % vdev
commands += " -n %s" % vswitch_name
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
if immdt:
# the inst must be active, or this call will failed
commands = '/opt/zhcp/bin/smcli'
commands += ' Virtual_Network_Adapter_Connect_Vswitch'
commands += " -T %s " % userid + "-v %s" % vdev
commands += " -n %s" % vswitch_name
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
def _uncouple_nic(self, zhcp, userid, vdev, dm, immdt):
"""Couple NIC to vswitch by adding vswitch into user direct."""
url = self._xcat_url.xdsh("/%s" % zhcp)
if dm:
commands = '/opt/zhcp/bin/smcli'
commands += ' Virtual_Network_Adapter_Disconnect_DM'
commands += " -T %s " % userid + "-v %s" % vdev
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
if immdt:
# the inst must be active, or this call will failed
commands = '/opt/zhcp/bin/smcli'
commands += ' Virtual_Network_Adapter_Disconnect'
commands += " -T %s " % userid + "-v %s" % vdev
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
def put_user_direct_online(self, zhcp, userid):
url = self._xcat_url.xdsh("/%s" % zhcp)
commands = '/opt/zhcp/bin/smcli Static_Image_Changes_Immediate_DM'
commands += " -T %s" % userid
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
def get_zhcp_userid(self, zhcp):
if not self._zhcp_userid:
self._zhcp_userid = self.get_userid_from_node(zhcp)
return self._zhcp_userid
def add_vswitch(self, zhcp, name, rdev,
controller='*',
connection=1, queue_mem=8, router=0, network_type=2, vid=0,
port_type=1, update=1, gvrp=2, native_vid=1):
'''
connection:0-unspecified 1-Actice 2-non-Active
router:0-unspecified 1-nonrouter 2-prirouter
type:0-unspecified 1-IP 2-ethernet
vid:1-4094 for access port defaut vlan
port_type:0-unspecified 1-access 2-trunk
update:0-unspecified 1-create 2-create and add to system
configuration file
gvrp:0-unspecified 1-gvrp 2-nogvrp
'''
if (self._does_vswitch_exist(zhcp, name)):
LOG.info(_('Vswitch %s already exists.'), name)
return
# if vid = 0, port_type, gvrp and native_vlanid are not
# allowed to specified
if not len(vid):
vid = 0
port_type = 0
gvrp = 0
native_vid = -1
else:
vid = str(vid[0][0]) + '-' + str(vid[0][1])
userid = self.get_zhcp_userid(zhcp)
url = self._xcat_url.xdsh("/%s" % zhcp)
commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Create'
commands += " -T %s" % userid
commands += ' -n %s' % name
if rdev:
commands += " -r %s" % rdev.replace(',', ' ')
#commands += " -a %s" % osa_name
if controller != '*':
commands += " -i %s" % controller
commands += " -c %s" % connection
commands += " -q %s" % queue_mem
commands += " -e %s" % router
commands += " -t %s" % network_type
commands += " -v %s" % vid
commands += " -p %s" % port_type
commands += " -u %s" % update
commands += " -G %s" % gvrp
commands += " -V %s" % native_vid
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
result = xcatutils.xcat_request("PUT", url, body)
if (result['errorcode'][0][0] != '0') or \
(not self._does_vswitch_exist(zhcp, name)):
raise exception.zvmException(
msg=("switch: %s add failed, %s") %
(name, result['data'][0][0]))
LOG.info(_('Created vswitch %s done.'), name)
def _does_vswitch_exist(self, zhcp, vsw):
userid = self.get_zhcp_userid(zhcp)
url = self._xcat_url.xdsh("/%s" % zhcp)
commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Query'
commands += " -T %s" % userid
commands += " -s %s" % vsw
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
result = xcatutils.xcat_request("PUT", url, body)
return (result['errorcode'][0][0] == '0')
def re_grant_user(self, zhcp):
"""Grant user again after z/VM is re-IPLed"""
ports_info = self._get_userid_vswitch_vlan_id_mapping(zhcp)
records_num = 0
cmd = ''
def run_command(command):
xdsh_commands = 'command=%s' % command
body = [xdsh_commands]
url = self._xcat_url.xdsh("/%s" % zhcp)
xcatutils.xcat_request("PUT", url, body)
for (port_id, port) in ports_info.items():
if port['userid'] is None or port['vswitch'] is None:
continue
if len(port['userid']) == 0 or len(port['vswitch']) == 0:
continue
cmd += '/opt/zhcp/bin/smcli '
cmd += 'Virtual_Network_Vswitch_Set_Extended '
cmd += '-T %s ' % port['userid']
cmd += '-k switch_name=%s ' % port['vswitch']
cmd += '-k grant_userid=%s' % port['userid']
try:
if int(port['vlan_id']) in range(1, 4094):
cmd += ' -k user_vlan_id=%s\n' % port['vlan_id']
else:
cmd += '\n'
except ValueError:
# just in case there are bad records of vlan info which
# could be a string
LOG.warn(_("Unknown vlan '%(vlan)s' for user %(user)s."),
{'vlan': port['vlan_id'], 'user': port['userid']})
cmd += '\n'
continue
records_num += 1
if records_num >= self._MAX_REGRANT_USER_NUMBER:
try:
commands = 'echo -e "#!/bin/sh\n%s" > grant.sh' % cmd[:-1]
run_command(commands)
commands = 'sh grant.sh;rm -f grant.sh'
run_command(commands)
records_num = 0
cmd = ''
except Exception:
LOG.warn(_("Grant user failed"))
if len(cmd) > 0:
commands = 'echo -e "#!/bin/sh\n%s" > grant.sh' % cmd[:-1]
run_command(commands)
commands = 'sh grant.sh;rm -f grant.sh'
run_command(commands)
return ports_info
def _get_userid_vswitch_vlan_id_mapping(self, zhcp):
ports_info = self.get_nic_ids()
ports = {}
for p in ports_info:
port_info = p.split(',')
target_host = port_info[5].strip('"')
port_vid = port_info[3].strip('"')
port_id = port_info[2].strip('"')
vswitch = port_info[1].strip('"')
nodename = port_info[0].strip('"')
if target_host == zhcp:
ports[port_id] = {'nodename': nodename,
'vswitch': vswitch,
'userid': None,
'vlan_id': port_vid}
def get_all_userid():
users = {}
addp = ''
url = self._xcat_url.tabdump("/zvm", addp)
all_userids = xcatutils.xcat_request("GET", url)
header = '#node,hcp,userid,nodetype,parent,comments,disable'
all_userids['data'][0].remove(header)
if len(all_userids) > 0:
for u in all_userids['data'][0]:
user_info = u.split(',')
userid = user_info[2].strip('"')
nodename = user_info[0].strip('"')
users[nodename] = {'userid': userid}
return users
users = get_all_userid()
for (port_id, port) in ports.items():
try:
ports[port_id]['userid'] = users[port['nodename']]['userid']
except Exception:
LOG.info(_("Garbage port found. port id: %s") % port_id)
return ports
def update_xcat_switch(self, port, vswitch, vlan):
"""Update information in xCAT switch table."""
commands = "port=%s" % port
commands += " switch.switch=%s" % vswitch
commands += " switch.vlan=%s" % (vlan and vlan or -1)
url = self._xcat_url.tabch("/switch")
body = [commands]
xcatutils.xcat_request("PUT", url, body)
def create_xcat_mgt_network(self, zhcp, mgt_ip, mgt_mask, mgt_vswitch):
url = self._xcat_url.xdsh("/%s" % zhcp)
xdsh_commands = ('command=smcli Virtual_Network_Adapter_Query'
' -T %s -v 0800') % self._xcat_node_name
body = [xdsh_commands]
result = xcatutils.xcat_request("PUT", url, body)['data'][0][0]
code = result.split("\n")
# return code 212: Adapter does not exist
new_nic = ''
if len(code) == 4 and code[1].split(': ')[2] == '212':
new_nic = ('vmcp define nic 0800 type qdio\n' +
'vmcp couple 0800 system %s\n' % (mgt_vswitch))
elif len(code) == 7:
status = code[4].split(': ')[2]
if status == 'Coupled and active':
# we just assign the IP/mask,
# no matter if it is assigned or not
LOG.info(_("Assign IP for NIC 800."))
else:
LOG.error(_("NIC 800 staus is unknown."))
return
else:
raise exception.zvmException(
msg="Unknown information from SMAPI")
url = self._xcat_url.xdsh("/%s") % self._xcat_node_name
cmd = new_nic + ('/usr/bin/perl /usr/sbin/sspqeth2.pl ' +
'-a %s -d 0800 0801 0802 -e eth2 -m %s -g %s'
% (mgt_ip, mgt_mask, mgt_ip))
xdsh_commands = 'command=%s' % cmd
body = [xdsh_commands]
xcatutils.xcat_request("PUT", url, body)
def _get_xcat_node_ip(self):
addp = '&col=key&value=master&attribute=value'
url = self._xcat_url.gettab("/site", addp)
return xcatutils.xcat_request("GET", url)['data'][0][0]
def _get_xcat_node_name(self):
xcat_ip = self._get_xcat_node_ip()
addp = '&col=ip&value=%s&attribute=node' % (xcat_ip)
url = self._xcat_url.gettab("/hosts", addp)
return (xcatutils.xcat_request("GET", url)['data'][0][0])
def query_xcat_uptime(self, zhcp):
url = self._xcat_url.xdsh("/%s" % zhcp)
cmd = '/opt/zhcp/bin/smcli Image_Query_Activate_Time'
cmd += " -T %s" % self.get_userid_from_node(
self._xcat_node_name)
# format 4: yyyy-mm-dd
cmd += " -f %s" % "4"
xdsh_commands = 'command=%s' % cmd
body = [xdsh_commands]
ret_str = xcatutils.xcat_request("PUT", url, body)['data'][0][0]
return ret_str.split('on ')[1]
def query_zvm_uptime(self, zhcp):
url = self._xcat_url.xdsh("/%s" % zhcp)
cmd = '/opt/zhcp/bin/smcli System_Info_Query'
xdsh_commands = 'command=%s' % cmd
body = [xdsh_commands]
ret_str = xcatutils.xcat_request("PUT", url, body)['data'][0][0]
return ret_str.split('\n')[4].split(': ', 3)[2]

View File

@ -0,0 +1,205 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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 functools
import httplib
from neutron.openstack.common import jsonutils
from neutron.openstack.common.gettextutils import _
from neutron.openstack.common import log as logging
from neutron.plugins.zvm.common import config
from neutron.plugins.zvm.common import constants
from neutron.plugins.zvm.common import exception
LOG = logging.getLogger(__name__)
CONF = config.CONF
class xCatURL(object):
"""To return xCat url for invoking xCat REST API."""
def __init__(self):
"""Set constant that used to form xCat url."""
self.PREFIX = '/xcatws'
self.SUFFIX = '?userName=' + CONF.AGENT.zvm_xcat_username + \
'&password=' + CONF.AGENT.zvm_xcat_password + \
'&format=json'
self.NODES = '/nodes'
self.TABLES = '/tables'
self.XDSH = '/dsh'
def tabdump(self, arg='', addp=None):
rurl = self.PREFIX + self.TABLES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def _append_addp(self, rurl, addp=None):
if addp is not None:
return rurl + addp
else:
return rurl
def gettab(self, arg='', addp=None):
"""Get table arg, with attribute addp."""
rurl = self.PREFIX + self.TABLES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def tabch(self, arg='', addp=None):
"""Add/update/delete row(s) in table arg, with attribute addp."""
rurl = self.PREFIX + self.TABLES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def xdsh(self, arg=''):
"""Run shell command."""
return self.PREFIX + self.NODES + arg + self.XDSH + self.SUFFIX
class xCatConnection():
"""Https requests to xCat web service."""
def __init__(self):
"""Initialize https connection to xCat service."""
self.host = CONF.AGENT.zvm_xcat_server
self.xcat_timeout = CONF.AGENT.zvm_xcat_timeout
try:
self.conn = httplib.HTTPSConnection(self.host, None, None, None,
True, self.xcat_timeout)
except Exception:
LOG.error(_("Connect to xCat server %s failed") % self.host)
raise exception.zVMxCatConnectionFailed(xcatserver=self.host)
def request(self, method, url, body=None, headers={}):
"""Do http request to xCat server
Will return (response_status, response_reason, response_body)
"""
if body is not None:
body = jsonutils.dumps(body)
headers = {'content-type': 'text/plain',
'content-length': len(body)}
try:
self.conn.request(method, url, body, headers)
except Exception as err:
LOG.error(_("Request to xCat server %(host)s failed: %(err)s") %
{'host': self.host, 'err': err})
raise exception.zVMxCatRequestFailed(xcatserver=self.host,
err=err)
res = self.conn.getresponse()
msg = res.read()
resp = {
'status': res.status,
'reason': res.reason,
'message': msg}
# NOTE(rui): Currently, only xCat returns 200 or 201 can be
# considered acceptable.
err = None
if method == "POST":
if res.status != 201:
err = str(resp)
else:
if res.status != 200:
err = str(resp)
if err is not None:
LOG.error(_("Request to xCat server %(host)s failed: %(err)s") %
{'host': self.host, 'err': err})
raise exception.zVMxCatRequestFailed(xcatserver=self.host,
err=err)
return resp
def xcat_request(method, url, body=None, headers={}):
conn = xCatConnection()
resp = conn.request(method, url, body, headers)
return load_xcat_resp(resp['message'])
def jsonloads(jsonstr):
try:
return jsonutils.loads(jsonstr)
except ValueError:
LOG.error(_("Respone is not in JSON format"))
raise exception.zVMJsonLoadsError()
def wrap_invalid_xcat_resp_data_error(function):
"""zVM driver get zVM hypervisor and virtual machine information
from xCat. xCat REST API response has its own fixed format(a JSON
stream). zVM driver abstract useful info base on the special format,
and raise exception if the data in incorrect format.
"""
@functools.wraps(function)
def decorated_function(*arg, **kwargs):
try:
return function(*arg, **kwargs)
except (ValueError, TypeError, IndexError) as err:
LOG.error(_('Invalid data returned from xCat: %s') % err)
raise exception.zVMInvalidxCatResponseDataError(msg=err)
except Exception as err:
raise
return decorated_function
@wrap_invalid_xcat_resp_data_error
def load_xcat_resp(message):
"""Abstract information from xCat REST response body.
As default, xCat response will in format of JSON and can be
converted to Python dictionary, would looks like:
{"data": [{"info": [info,]}, {"data": [data,]}, ..., {"error": [error,]}]}
Returns a Python dictionary, looks like:
{'info': [info,],
'data': [data,],
'error': [error,]}
"""
resp_list = jsonloads(message)['data']
keys = constants.XCAT_RESPONSE_KEYS
resp = {}
try:
for k in keys:
resp[k] = []
for d in resp_list:
for k in keys:
if d.get(k) is not None:
resp[k].append(d.get(k))
except Exception:
LOG.error(_("Invalid data returned from xCat: %s") % message)
raise exception.zVMInvalidxCatResponseDataError(msg=message)
if not verify_xcat_resp(resp):
LOG.error(_("Error returned from xCAT: %s") % message)
raise exception.zVMInvalidxCatResponseDataError(msg=message)
else:
return resp
@wrap_invalid_xcat_resp_data_error
def verify_xcat_resp(resp_dict):
"""Check whether xCAT REST API response contains an error."""
if resp_dict.get('error'):
if resp_dict['error'][0][0].find('Warning'):
return True
return False
else:
return True

View File

@ -0,0 +1,48 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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.
"""
Unit tests for the z/VM network.
"""
import mock
from oslo.config import cfg
from neutron.plugins.zvm.agent import zvm_network
from neutron.tests import base
FLAT_NETWORKS = ['flat_net1']
VLAN_NETWORKS = ['vlan_net1:100:500']
NETWORK_MAPS = {'vlan_net1': [(100, 500)], 'flat_net1': []}
class TestZVMNetwork(base.BaseTestCase):
_FAKE_NETWORK_VLAN_RANGES = "fakevsw1:1:4094,fakevsw2,fakevsw3:2:2999"
def setUp(self):
super(TestZVMNetwork, self).setUp()
cfg.CONF.set_override('flat_networks', FLAT_NETWORKS,
group='ml2_type_flat')
cfg.CONF.set_override('network_vlan_ranges', VLAN_NETWORKS,
group='ml2_type_vlan')
with mock.patch('neutron.plugins.zvm.common.utils.zvmUtils') as utils:
self._zvm_network = zvm_network.zvmNetwork()
def test_get_network_maps(self):
maps = self._zvm_network.get_network_maps()
self.assertEqual(maps, NETWORK_MAPS)

View File

@ -0,0 +1,227 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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.
"""
Unit tests for neutron z/VM driver
"""
import mock
from neutron.plugins.zvm.agent import zvm_neutron_agent
from neutron.tests import base
from oslo.config import cfg
FLAT_NETWORKS = ['flat_net1']
VLAN_NETWORKS = ['vlan_net1:100:500']
NET_UUID = 'zvm-net-uuid'
PORT_UUID = 'zvm-port-uuid'
class FakeLoopingCall(object):
def __init__(self, fake_time):
self.fake_time = fake_time
def start(self, interval=0):
self.fake_time()
class TestZVMNeutronAgent(base.BaseTestCase):
def setUp(self):
super(TestZVMNeutronAgent, self).setUp()
self.addCleanup(cfg.CONF.reset)
cfg.CONF.set_override('rpc_backend',
'neutron.openstack.common.rpc.impl_fake')
cfg.CONF.set_override('flat_networks', FLAT_NETWORKS,
group='ml2_type_flat')
cfg.CONF.set_override('network_vlan_ranges', VLAN_NETWORKS,
group='ml2_type_vlan')
mock.patch('neutron.openstack.common.loopingcall.'
'FixedIntervalLoopingCall',
new=FakeLoopingCall)
with mock.patch(
'neutron.plugins.zvm.common.utils.zvmUtils') as mock_Utils:
instance = mock_Utils.return_value
get_zhcp_userid = mock.MagicMock(return_value='zhcp_user')
create_xcat_mgt_network = mock.MagicMock()
instance.get_zhcp_userid = get_zhcp_userid
instance.create_xcat_mgt_network = create_xcat_mgt_network
net_attrs = {'fake_uuid1': {
'vswitch': 'fake_vsw', 'userid': 'fake_user1'}}
instance.re_grant_user = mock.MagicMock(return_value=net_attrs)
instance.query_xcat_uptime = mock.MagicMock(
return_value="xcat uptime 1")
instance.query_zvm_uptime = mock.MagicMock(
return_value="zvm uptime 1")
self.agent = zvm_neutron_agent.zvmNeutronAgent()
self.agent.plugin_rpc = mock.Mock()
self.agent.context = mock.Mock()
self.agent.agent_id = mock.Mock()
def test_port_bound_vlan(self):
vid = 100
with mock.patch.object(zvm_neutron_agent, "LOG") as log:
self._test_port_bound('vlan', vid)
log.info.assert_called_with('Binding VLAN, VLAN ID: %s', vid)
def test_port_bound_flat(self):
with mock.patch.object(zvm_neutron_agent, "LOG") as log:
self._test_port_bound('flat')
log.info.assert_called_with('Bind %s mode done', 'flat')
def _test_port_bound(self, network_type, vid=None):
port = mock.MagicMock()
net_uuid = NET_UUID
mock_enable_vlan = mock.MagicMock()
enable_vlan = False
if network_type == 'vlan':
enable_vlan = True
with mock.patch.multiple(
self.agent._utils,
couple_nic_to_vswitch=mock.MagicMock(),
put_user_direct_online=mock.MagicMock(),
set_vswitch_port_vlan_id=mock_enable_vlan):
self.agent.port_bound(port, net_uuid, network_type, None,
vid, 'fake_user')
self.assertEqual(enable_vlan, mock_enable_vlan.called)
def test_port_unbound(self):
# port_unbound just call utils.revoke_user, revoke_user is covered
# in test_zvm_utils
pass
def test_treat_devices_added_returns_true_for_missing_device(self):
attrs = {'get_device_details.side_effect': Exception()}
self.agent.plugin_rpc.configure_mock(**attrs)
# no exception should be raised
self.agent._treat_devices_added([])
def test_treat_devices_added_down_port(self):
details = dict(port_id='added_port_down', physical_network='vsw',
segmentation_id='10', network_id='fake_net',
network_type='flat', admin_state_up=False)
attrs = {'get_device_details.return_value': details}
self.agent.plugin_rpc.configure_mock(**attrs)
with mock.patch.object(self.agent, "_treat_vif_port",
mock.Mock(return_value=('fake_node', 'fake_user'))):
self.agent._treat_devices_added(['added_port_down'])
self.assertTrue(self.agent.plugin_rpc.update_device_down.called)
def test_treat_devices_added_up_port(self):
details = dict(port_id='added_port', physical_network='vsw',
segmentation_id='10', network_id='fake_net',
network_type='flat', admin_state_up=True)
attrs = {'get_device_details.return_value': details}
self.agent.plugin_rpc.configure_mock(**attrs)
with mock.patch.object(self.agent, "_treat_vif_port",
mock.Mock(return_value=('fake_node', 'fake_user'))):
self.agent._treat_devices_added(['added_port'])
self.assertTrue(self.agent.plugin_rpc.get_device_details.called)
def test_treat_devices_added_missing_port_id(self):
details = mock.MagicMock()
details.__contains__.side_effect = lambda x: False
attrs = {'get_device_details.return_value': details}
self.agent.plugin_rpc.configure_mock(**attrs)
with mock.patch.object(zvm_neutron_agent, "LOG") as log:
self.agent._treat_devices_added(['unknown_port'])
log.debug.assert_called_with(
"Device %s not defined on Neutron server", "unknown_port")
def test_treat_devices_removed_returns_true_for_missing_device(self):
attrs = {'update_device_down.side_effect': Exception()}
self.agent.plugin_rpc.configure_mock(**attrs)
devices = ['fake_uuid1']
with mock.patch.object(zvm_neutron_agent, "LOG") as log:
self.agent._treat_devices_removed(devices)
self.assertTrue(log.exception.called)
def test_treat_devices_removed(self):
devices = ['unknown_port', 'fake_uuid1']
with mock.patch.object(zvm_neutron_agent, "LOG") as log:
self.agent._treat_devices_removed(devices)
log.warn.assert_called_with('Can\'t find port %s in zvm agent',
'unknown_port')
self.assertTrue(self.agent.plugin_rpc.update_device_down.called)
def test_port_update_up(self):
with mock.patch.object(self.agent.plugin_rpc,
"update_device_up") as rpc:
with mock.patch.object(self.agent._utils,
"couple_nic_to_vswitch") as couple:
self.agent.port_update(None, port={'id': 'fake_uuid1',
'admin_state_up': True})
self.assertTrue(rpc.called)
self.assertTrue(couple.called)
def test_port_update_down(self):
with mock.patch.object(self.agent.plugin_rpc,
"update_device_down") as rpc:
with mock.patch.object(self.agent._utils,
"uncouple_nic_from_vswitch") as couple:
self.agent.port_update(None, port={'id': 'fake_uuid1',
'admin_state_up': False})
self.assertTrue(rpc.called)
self.assertTrue(couple.called)
# Test agent state report
def test_report_state(self):
with mock.patch.object(self.agent.state_rpc,
"report_state") as report_st:
self.agent._report_state()
report_st.assert_called_with(self.agent.context,
self.agent.agent_state)
self.assertNotIn("start_flag", self.agent.agent_state)
def test_treat_vif_port(self):
with mock.patch.object(self.agent, "port_bound") as bound:
self.agent._treat_vif_port('port_id', 'network_id', 'flat',
'vsw1', '10', True)
self.assertTrue(bound.called)
self.agent._treat_vif_port('port_id', 'network_id', 'flat',
'vsw1', '10', False)
self.assertTrue(self.agent._utils.grant_user.called)
def test_handle_restar_zvm(self):
q_xcat = mock.MagicMock(return_value="xcat uptime 2")
q_zvm = mock.MagicMock(return_value="zvm uptime 2")
re_grant = mock.MagicMock()
with mock.patch.multiple(
self.agent._utils,
query_xcat_uptime=q_xcat,
query_zvm_uptime=q_zvm,
re_grant_user=re_grant):
self.agent._restart_handler.send(None)
self.assertTrue(re_grant.called)
self.assertTrue(self.agent._utils.create_xcat_mgt_network.called)
def test_handle_restar_zvm_exception(self):
q_xcat = mock.MagicMock(side_effect=
Exception("xcat uptime exception"))
with mock.patch.object(zvm_neutron_agent, "LOG") as log:
with mock.patch.object(self.agent._utils,
"query_xcat_uptime", q_xcat):
self.agent._restart_handler.send(None)
log.exception.assert_called_with("Failed to handle restart")

View File

@ -0,0 +1,471 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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.
"""
Unit tests for the z/VM utils.
"""
import mock
from neutron.plugins.zvm.common import exception
from neutron.plugins.zvm.common import utils
from neutron.tests import base
from oslo.config import cfg
class TestZVMUtils(base.BaseTestCase):
_FAKE_VSWITCH_NAME = "fakevsw1"
_FAKE_PORT_NAME = "fake_port_name"
_FAKE_RET_VAL = 0
_FAKE_VM_PATH = "fake_vm_path"
_FAKE_VSWITCH = "fakevsw1"
_FAKE_VLAN_ID = "fake_vlan_id"
_FAKE_ZHCP_NODENAME = "fakezhcp"
_FAKE_ZHCP_USER = 'zhcp_user'
_FAKE_VDEV = "1000"
_FAKE_XCAT_NODENAME = "fakexcat"
_FAKE_XCAT_USER = "fake_xcat_user"
_FAKE_XCAT_PW = "fake_xcat_password"
def setUp(self):
super(TestZVMUtils, self).setUp()
self.addCleanup(cfg.CONF.reset)
cfg.CONF.set_override('zvm_xcat_username', self._FAKE_XCAT_USER,
group='AGENT')
cfg.CONF.set_override('zvm_xcat_password', self._FAKE_XCAT_PW,
group='AGENT')
with mock.patch(
'neutron.plugins.zvm.common.utils.zvmUtils._get_xcat_node_name',
mock.Mock(return_value=self._FAKE_XCAT_NODENAME)):
self._utils = utils.zvmUtils()
def test_couple_nic_to_vswitch(self):
xcat_req = mock.Mock()
xcat_req.side_effect = [{'data': [[self._FAKE_VDEV]]},
{'data': [['OK']]},
{'data': [['OK']]}]
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
ret = self._utils.couple_nic_to_vswitch(self._FAKE_VSWITCH,
self._FAKE_PORT_NAME,
self._FAKE_ZHCP_NODENAME,
"fake_user")
self.assertEqual(ret, self._FAKE_VDEV)
url_vdev = ('/xcatws/tables/switch?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json&'
'col=port&value=fake_port_name&attribute=interface')
url_couple_nic = ('/xcatws/nodes/fakezhcp/dsh?userName='
'fake_xcat_user&password=fake_xcat_password&format=json')
body_couple_nic_dm = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Adapter_Connect_Vswitch_DM -T fake_user'
' -v 1000 -n fakevsw1')]
body_couple_nic = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Adapter_Connect_Vswitch -T fake_user'
' -v 1000 -n fakevsw1')]
calls = [mock.call('GET', url_vdev),
mock.call('PUT', url_couple_nic, body_couple_nic_dm),
mock.call('PUT', url_couple_nic, body_couple_nic)]
xcat_req.assert_has_calls(calls)
def test_grant_user(self):
xcat_req = mock.Mock()
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
ret = self._utils.grant_user(self._FAKE_ZHCP_NODENAME,
self._FAKE_VSWITCH,
"fake_user")
url_grant_user = ('/xcatws/nodes/fakezhcp/dsh?userName='
'fake_xcat_user&password=fake_xcat_password&format=json')
body_grant_user = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Vswitch_Set_Extended -T fake_user'
' -k switch_name=fakevsw1 -k grant_userid=fake_user')]
xcat_req.assert_called_with('PUT', url_grant_user, body_grant_user)
def test_uncouple_nic_from_vswitch(self):
xcat_req = mock.Mock()
xcat_req.side_effect = [{'data': [[self._FAKE_VDEV]]},
{'data': [['OK']]},
{'data': [['OK']]}]
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
ret = self._utils.uncouple_nic_from_vswitch(self._FAKE_VSWITCH,
self._FAKE_PORT_NAME,
self._FAKE_ZHCP_NODENAME,
"fake_user")
url_vdev = ('/xcatws/tables/switch?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json&'
'col=port&value=fake_port_name&attribute=interface')
url_uncouple_nic = ('/xcatws/nodes/fakezhcp/dsh?userName='
'fake_xcat_user&password=fake_xcat_password&format=json')
body_uncouple_nic_dm = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Adapter_Disconnect_DM -T fake_user'
' -v 1000')]
body_uncouple_nic = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Adapter_Disconnect -T fake_user -v 1000')]
calls = [mock.call('GET', url_vdev),
mock.call('PUT', url_uncouple_nic, body_uncouple_nic_dm),
mock.call('PUT', url_uncouple_nic, body_uncouple_nic)]
xcat_req.assert_has_calls(calls)
def test_revoke_user(self):
res = {'errorcode': [['0']]}
xcat_req = mock.MagicMock()
xcat_req.return_value = res
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.revoke_user(self._FAKE_ZHCP_NODENAME,
self._FAKE_VSWITCH_NAME,
"fake_user")
url_revoke_user = ('/xcatws/nodes/fakezhcp/dsh?userName='
'fake_xcat_user&password=fake_xcat_password&format=json')
body_revoke_user = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Vswitch_Set_Extended -T fake_user'
' -k switch_name=fakevsw1 -k revoke_userid=fake_user')]
xcat_req.assert_called_with('PUT', url_revoke_user,
body_revoke_user)
def test_add_vswitch_exist(self):
res = {'errorcode': [['0']]}
xcat_req = mock.MagicMock()
xcat_req.return_value = res
self._utils.get_zhcp_userid = mock.MagicMock(
return_value=self._FAKE_ZHCP_USER)
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
with mock.patch.object(utils, "LOG") as log:
self._utils.add_vswitch(self._FAKE_ZHCP_NODENAME,
self._FAKE_VSWITCH_NAME,
self._FAKE_VDEV)
log.info.assert_called_with('Vswitch %s already exists.',
self._FAKE_VSWITCH_NAME)
def test_add_vswitch(self):
self._utils.get_zhcp_userid = mock.MagicMock()
self._utils.get_zhcp_userid.side_effect = [self._FAKE_ZHCP_USER,
self._FAKE_ZHCP_USER,
self._FAKE_ZHCP_USER]
xcat_req = mock.Mock()
res = {'errorcode': [['0']]} # vswitch does exist
res_err = {'errorcode': [['1']]} # vswitch does not exist
xcat_req.side_effect = [res_err, res, res]
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.add_vswitch(self._FAKE_ZHCP_NODENAME,
self._FAKE_VSWITCH_NAME,
self._FAKE_VDEV,
vid=[self._FAKE_VLAN_ID])
url = ('/xcatws/nodes/fakezhcp/dsh?userName=fake_xcat_user'
'&password=fake_xcat_password&format=json')
body = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Vswitch_Create -T zhcp_user -n fakevsw1'
' -r 1000 -c 1 -q 8 -e 0 -t 2 -v 1'
' -p 1 -u 1 -G 2 -V 1')]
xcat_req.assert_any_called('PUT', url, body)
def test_set_vswitch_port_vlan_id(self):
self._utils._get_nic_settings = mock.MagicMock(return_value='inst1')
xcat_req = mock.Mock()
xcat_req.return_value = "OK"
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.set_vswitch_port_vlan_id(self._FAKE_VLAN_ID,
self._FAKE_PORT_NAME,
self._FAKE_VDEV,
self._FAKE_ZHCP_NODENAME,
self._FAKE_VSWITCH)
url = ('/xcatws/nodes/fakezhcp/dsh?userName=fake_xcat_user'
'&password=fake_xcat_password&format=json')
body = [('command=/opt/zhcp/bin/smcli'
' Virtual_Network_Vswitch_Set_Extended -T inst1'
' -k grant_userid=inst1 -k switch_name=fakevsw1'
' -k user_vlan_id=fake_vlan_id')]
xcat_req.assert_called_with('PUT', url, body)
def test_get_nic_ids(self):
xcat_req = mock.Mock()
data = 'fnode,fswitch,fport,fvlan,finf,-,false'
xcat_req.return_value = {'data': [[(
'#node,switch,port,vlan,interface,comments,disable'), data]]}
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
ret = self._utils.get_nic_ids()
self.assertEqual(ret, [data])
url = ('/xcatws/tables/switch?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
xcat_req.assert_called_with('GET', url)
def test_get_node_from_port(self):
xcat_req = mock.Mock()
xcat_req.side_effect = [{'data': [[self._FAKE_ZHCP_NODENAME]]}]
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
ret = self._utils.get_node_from_port(self._FAKE_PORT_NAME)
self.assertEqual(ret, self._FAKE_ZHCP_NODENAME)
url = ('/xcatws/tables/switch?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json&'
'col=port&value=fake_port_name&attribute=node')
calls = [mock.call('GET', url)]
xcat_req.assert_has_calls(calls)
def _test_get_userid_from_node(self, node, user):
xcat_req = mock.Mock()
xcat_req.return_value = {'data': [[user]]}
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
ret = self._utils.get_zhcp_userid(self._FAKE_ZHCP_NODENAME)
url = ('/xcatws/tables/zvm?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json&col=node&'
'value=%s&attribute=userid' % node)
xcat_req.assert_called_with('GET', url)
return ret
def test_get_userid_from_node(self):
self.assertEqual(self._test_get_userid_from_node(
self._FAKE_ZHCP_NODENAME,
self._FAKE_ZHCP_USER),
self._FAKE_ZHCP_USER)
def test_get_zhcp_userid(self):
self.assertEqual(self._test_get_userid_from_node(
self._FAKE_ZHCP_NODENAME,
self._FAKE_ZHCP_USER),
self._FAKE_ZHCP_USER)
def test_put_user_direct_online(self):
xcat_req = mock.Mock()
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.put_user_direct_online(self._FAKE_ZHCP_NODENAME,
'inst1')
url = ('/xcatws/nodes/fakezhcp/dsh?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
body = [('command=/opt/zhcp/bin/smcli'
' Static_Image_Changes_Immediate_DM -T inst1')]
xcat_req.assert_called_with('PUT', url, body)
def test_update_xcat_switch(self):
xcat_req = mock.Mock()
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.update_xcat_switch(self._FAKE_PORT_NAME,
self._FAKE_VSWITCH,
self._FAKE_VLAN_ID)
url = ('/xcatws/tables/switch?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
body = ['port=fake_port_name switch.switch=fakevsw1'
' switch.vlan=fake_vlan_id']
xcat_req.assert_called_with('PUT', url, body)
def _verify_query_nic(self, result, xcat_req):
url = ('/xcatws/nodes/fakexcat/dsh?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
body = ['command=smcli Virtual_Network_Adapter_Query'
' -T fakexcat -v 0800']
xcat_req.assert_any_with('PUT', url, body)
def test_create_xcat_mgt_network_exist(self):
nic_def = ['zhcp: Adapter:\nzhcp: Address: 0800\n'
'zhcp: Device count: 3\nzhcp: Adapter type: QDIO\n'
'zhcp: Adapter status: Coupled and active\n'
'zhcp: LAN owner: SYSTEM\n'
'zhcp: LAN name: XCATVSW2']
xcat_req = mock.Mock()
xcat_req.side_effect = [{'data': [nic_def]},
{'data': [['OK']]}]
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.create_xcat_mgt_network(self._FAKE_ZHCP_NODENAME,
"10.1.1.1",
"255.255.0.0",
self._FAKE_VSWITCH)
self._verify_query_nic(nic_def, xcat_req)
url = ('/xcatws/nodes/fakexcat/dsh?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
body = ['command=/usr/bin/perl /usr/sbin/sspqeth2.pl -a 10.1.1.1'
' -d 0800 0801 0802 -e eth2 -m 255.255.0.0 -g 10.1.1.1']
xcat_req.assert_called_with('PUT', url, body)
def test_create_xcat_mgt_network_not_exist(self):
nic_undef = ['zhcp: Failed\nzhcp: Return Code: 212\n'
'zhcp: Reason Code: 8\n'
'zhcp: Description: Adapter does not exist']
xcat_req = mock.Mock()
xcat_req.side_effect = [{'data': [nic_undef]},
{'data': [['OK']]}]
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.create_xcat_mgt_network(self._FAKE_ZHCP_NODENAME,
"10.1.1.1",
"255.255.0.0",
self._FAKE_VSWITCH)
self._verify_query_nic(nic_undef, xcat_req)
url = ('/xcatws/nodes/fakexcat/dsh?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
body = ['command=vmcp define nic 0800 type qdio\n'
'vmcp couple 0800 system fakevsw1\n'
'/usr/bin/perl /usr/sbin/sspqeth2.pl -a 10.1.1.1'
' -d 0800 0801 0802 -e eth2 -m 255.255.0.0 -g 10.1.1.1']
xcat_req.assert_called_with('PUT', url, body)
def test_create_xcat_mgt_network_error(self):
nic_err = ['zhcp: Adapter:\nzhcp: Address: 0800\n'
'zhcp: Device count: 3\nzhcp: Adapter type: QDIO\n'
'zhcp: Adapter status: Not coupled\n'
'zhcp: LAN owner: \n'
'zhcp: LAN name: ']
smapi_err = ['Failed']
xcat_req = mock.Mock()
xcat_req.side_effect = [{'data': [nic_err]},
{'data': [smapi_err]}]
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
with mock.patch.object(utils, "LOG") as log:
self._utils.create_xcat_mgt_network(self._FAKE_ZHCP_NODENAME,
"10.1.1.1",
"255.255.0.0",
self._FAKE_VSWITCH)
self._verify_query_nic(nic_err, xcat_req)
log.error.assert_called_with('NIC 800 staus is unknown.')
self.assertRaises(exception.zvmException,
self._utils.create_xcat_mgt_network,
self._FAKE_ZHCP_NODENAME,
"10.1.1.1",
"255.255.0.0",
self._FAKE_VSWITCH)
self._verify_query_nic(smapi_err, xcat_req)
def test_re_grant_user(self):
'''We assume there is three nodes valid in the xCAT MN db, they are:
node1, node2, node4. We mock _MAX_REGRANT_USER_NUMBER to 2. So the
process of regrant has two steps. Fisrt grant two nodes and then
grant one node.'''
fake_port_info = ['node1,switch,port1,10,inf1,fakezhcp,false',
'node2,switch,port2,10,inf2,fakezhcp,false',
# node3's zhcp field is invalid
'node3,switch,port3,10,inf3,zhcp,false',
'node4,switch,port3,10,inf4,fakezhcp,false']
self._utils.get_nic_ids = mock.MagicMock(return_value=fake_port_info)
fake_user_id = ['#node,hcp,userid,nodetype,parent,comments,disable',
'"opnstk1","zhcp.ibm.com",,,,,', # invalid record
'"node1","fakezhcp","user01",,,,',
'"zhcp2","zhcp2.ibm.com","ZHCP",,,,', # invalid record
'"node2","fakezhcp","user02",,,,',
'"node3","zhcp","user03",,,,', # invalid record
'"node4","fakezhcp","user04",,,,']
xcat_req = mock.Mock()
xcat_req.side_effect = [{'data': [fake_user_id]},
{'data': [['OK']]}, # run_command step 1, regrant two node
{'data': [['OK']]}, # run_command remove
{'data': [['OK']]}, # run_command step 2
{'data': [['OK']]}] # run_command remove
with mock.patch.object(utils.zvmUtils, '_MAX_REGRANT_USER_NUMBER', 2):
with mock.patch(
'neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
self._utils.re_grant_user(self._FAKE_ZHCP_NODENAME)
url_command = ('/xcatws/nodes/fakezhcp/dsh?userName='
'fake_xcat_user&password=fake_xcat_password&format=json')
valid_users = [1, 2, 4]
last_user = None
# re_grant_user uses a dict to keep the ports info, so we don't
# know the order, which nodes are reganted in step 1 and which
# one is regranted in step 2. We will try to find the node
# removed in step 2 first, because this is easier. Then we
# verify the step 1.
for i in valid_users:
cmd_vsw_couple =\
('command=echo -e "#!/bin/sh\n/opt/zhcp/bin/smcli'
' Virtual_Network_Vswitch_Set_Extended -T user0%s -k'
' switch_name=switch -k grant_userid=user0%s'
' -k user_vlan_id=10" > grant.sh' % (i, i))
if mock.call('PUT', url_command, [cmd_vsw_couple]) in\
xcat_req.call_args_list:
last_user = i
break
self.assertTrue(last_user)
# remove the node from valid users, so we can verify if the
# other two nodes has been regranted via the valid_users.
del(valid_users[valid_users.index(last_user)])
body_cmd_node_1 =\
('command=echo -e "#!/bin/sh\n'
'/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
' -T user0%s -k switch_name=switch -k grant_userid=user0%s'
' -k user_vlan_id=10\n'
% (valid_users[0], valid_users[0])) +\
('/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
' -T user0%s -k switch_name=switch -k grant_userid=user0%s'
' -k user_vlan_id=10" > grant.sh'
% (valid_users[1], valid_users[1]))
body_cmd_node_2 =\
('command=echo -e "#!/bin/sh\n'
'/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
' -T user0%s -k switch_name=switch -k grant_userid=user0%s'
' -k user_vlan_id=10\n'
% (valid_users[1], valid_users[1])) +\
('/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
' -T user0%s -k switch_name=switch -k grant_userid=user0%s'
' -k user_vlan_id=10" > grant.sh'
% (valid_users[0], valid_users[0]))
self.assertTrue(
(mock.call('PUT', url_command, [body_cmd_node_1])
in xcat_req.call_args_list)
or (mock.call('PUT', url_command, [body_cmd_node_2])
in xcat_req.call_args_list))
def test_query_xcat_uptime(self):
xcat_uptime = {'data':
[['XCAT was activated on 2014-06-11 at 02:41:15']]}
xcat_req = mock.Mock(return_value=xcat_uptime)
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
with mock.patch.object(utils.zvmUtils, "get_userid_from_node",
mock.Mock(return_value='xcat')):
ret = self._utils.query_xcat_uptime(self._FAKE_ZHCP_NODENAME)
self.assertEqual(ret, '2014-06-11 at 02:41:15')
url = ('/xcatws/nodes/fakezhcp/dsh?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
body = ['command=/opt/zhcp/bin/smcli'
' Image_Query_Activate_Time -T xcat -f 4']
xcat_req.assert_called_with('PUT', url, body)
def test_query_zvm_uptime(self):
fake_ret = ('timezone\ncurrent time\nversion\nGen time\n'
'zhcp: The z/VM CP IPL time: 2014-06-11 01:38:37 EDT\n'
'storage\n')
zvm_uptime = {'data': [[fake_ret]]}
xcat_req = mock.Mock(return_value=zvm_uptime)
with mock.patch('neutron.plugins.zvm.common.xcatutils.xcat_request',
xcat_req):
ret = self._utils.query_zvm_uptime(self._FAKE_ZHCP_NODENAME)
self.assertEqual(ret, '2014-06-11 01:38:37 EDT')
url = ('/xcatws/nodes/fakezhcp/dsh?userName=fake_xcat_user&'
'password=fake_xcat_password&format=json')
body = ['command=/opt/zhcp/bin/smcli System_Info_Query']
xcat_req.assert_called_with('PUT', url, body)

View File

@ -0,0 +1,41 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 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.
"""
Unit tests for the z/VM xCAT utils.
"""
import mock
from oslo.config import cfg
from neutron.plugins.zvm.common import xcatutils
from neutron.tests import base
class TestZVMXcatUtils(base.BaseTestCase):
_FAKE_XCAT_SERVER = "127.0.0.1"
_FAKE_XCAT_TIMEOUT = 300
def setUp(self):
super(TestZVMXcatUtils, self).setUp()
cfg.CONF.set_override('zvm_xcat_server',
self._FAKE_XCAT_SERVER, 'AGENT')
cfg.CONF.set_override('zvm_xcat_timeout',
self._FAKE_XCAT_TIMEOUT, 'AGENT')
self._xcaturl = xcatutils.xCatURL()
with mock.patch.multiple(xcatutils.httplib,
HTTPSConnection=mock.MagicMock()):
self._zvm_xcat_connection = xcatutils.xCatConnection()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.
"""A connection to an IBM z/VM Virtualization system.
Generally, OpenStack z/VM virt driver will call xCat REST API to operate
to z/VM hypervisors.xCat has a control point(a virtual machine) in z/VM
system, which enables xCat management node to control the z/VM system.
OpenStack z/VM driver will communicate with xCat management node through
xCat REST API. Thus OpenStack can operate to z/VM system indirectly.
"""
from nova.virt.zvm import driver
ZVMDriver = driver.ZVMDriver

View File

@ -0,0 +1,65 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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 os
import tarfile
from oslo.config import cfg
from nova import exception
from nova import utils
from nova.virt import configdrive
CONF = cfg.CONF
class ZVMConfigDriveBuilder(configdrive.ConfigDriveBuilder):
"""Enable ConfigDrive to make tgz package."""
def __init__(self, instance_md):
super(ZVMConfigDriveBuilder, self).__init__(instance_md)
def make_drive(self, path):
"""Make the config drive.
:param path: the path to place the config drive image at
:raises ProcessExecuteError if a helper process has failed.
"""
if CONF.config_drive_format == 'tgz':
self._make_tgz(path)
else:
raise exception.ConfigDriveUnknownFormat(
format=CONF.config_drive_format)
def _make_tgz(self, path):
try:
olddir = os.getcwd()
except OSError:
olddir = CONF.state_path
with utils.tempdir() as tmpdir:
self._write_md_files(tmpdir)
tar = tarfile.open(path, "w:gz")
os.chdir(tmpdir)
tar.add("openstack")
tar.add("ec2")
try:
os.chdir(olddir)
except Exception:
pass
tar.close()

View File

@ -0,0 +1,68 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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.compute import power_state
HYPERVISOR_TYPE = 'zvm'
ARCHITECTURE = 's390x'
ALLOWED_VM_TYPE = 'zLinux'
XCAT_MGT = 'zvm'
XCAT_RINV_HOST_KEYWORDS = {
"zvm_host": "z/VM Host:",
"zhcp": "zHCP:",
"cec_vendor": "CEC Vendor:",
"cec_model": "CEC Model:",
"hypervisor_os": "Hypervisor OS:",
"hypervisor_name": "Hypervisor Name:",
"architecture": "Architecture:",
"lpar_cpu_total": "LPAR CPU Total:",
"lpar_cpu_used": "LPAR CPU Used:",
"lpar_memory_total": "LPAR Memory Total:",
"lpar_memory_used": "LPAR Memory Used:",
"lpar_memory_offline": "LPAR Memory Offline:",
"ipl_time": "IPL Time:",
}
XCAT_DISKPOOL_KEYWORDS = {
"disk_total": "Total:",
"disk_used": "Used:",
"disk_available": "Free:",
}
XCAT_RESPONSE_KEYS = ('info', 'data', 'node', 'errorcode', 'error')
ZVM_POWER_STAT = {
'on': power_state.RUNNING,
'off': power_state.SHUTDOWN,
}
ZVM_DEFAULT_ROOT_DISK = "dasda"
ZVM_DEFAULT_SECOND_DISK = "dasdb"
ZVM_DEFAULT_ROOT_VOLUME = "sda"
ZVM_DEFAULT_SECOND_VOLUME = "sdb"
ZVM_DEFAULT_THIRD_VOLUME = "sdc"
ZVM_DEFAULT_LAST_VOLUME = "sdz"
DEFAULT_EPH_DISK_FMT = "ext3"
ZVM_DEFAULT_FCP_ID = 'auto'
ZVM_DEFAULT_NIC_VDEV = '1000'
ZVM_IMAGE_SIZE_MAX = 10

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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 import exception
from nova.openstack.common.gettextutils import _
class ZVMBaseException(exception.NovaException):
"""Base z/VM exception."""
pass
class ZVMDriverError(ZVMBaseException):
msg_fmt = _('z/VM driver error: %(msg)s')
class ZVMXCATRequestFailed(ZVMBaseException):
msg_fmt = _('Request to xCAT server %(xcatserver)s failed: %(msg)s')
class ZVMInvalidXCATResponseDataError(ZVMBaseException):
msg_fmt = _('Invalid data returned from xCAT: %(msg)s')
class ZVMXCATInternalError(ZVMBaseException):
msg_fmt = _('Error returned from xCAT: %(msg)s')
class ZVMVolumeError(ZVMBaseException):
msg_fmt = _('Volume error: %(msg)s')
class ZVMImageError(ZVMBaseException):
msg_fmt = _("Image error: %(msg)s")
class ZVMGetImageFromXCATFailed(ZVMBaseException):
msg_fmt = _('Get image from xCAT failed: %(msg)s')
class ZVMNetworkError(ZVMBaseException):
msg_fmt = _("z/VM network error: %(msg)s")
class ZVMXCATXdshFailed(ZVMBaseException):
msg_fmt = _('Execute xCAT xdsh command failed: %(msg)s')
class ZVMXCATCreateNodeFailed(ZVMBaseException):
msg_fmt = _('Create xCAT node %(node)s failed: %(msg)s')
class ZVMXCATCreateUserIdFailed(ZVMBaseException):
msg_fmt = _('Create xCAT user id %(instance)s failed: %(msg)s')
class ZVMXCATUpdateNodeFailed(ZVMBaseException):
msg_fmt = _('Update node %(node)s info failed: %(msg)s')
class ZVMXCATDeployNodeFailed(ZVMBaseException):
msg_fmt = _('Deploy image on node %(node)s failed: %(msg)s')
class ZVMConfigDriveError(ZVMBaseException):
msg_fmt = _('Create configure drive failed: %(msg)s')

View File

@ -0,0 +1,800 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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 commands
import datetime
import os
import re
import shutil
import tarfile
import xml.dom.minidom as Dom
from oslo.config import cfg
from nova import exception as nova_exception
from nova.image import glance
from nova.openstack.common import excutils
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
from nova.virt import images
from nova.virt.zvm import const
from nova.virt.zvm import exception
from nova.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
QUEUE_BUFFER_SIZE = 10
class ZVMImages(object):
def __init__(self):
self._xcat_url = zvmutils.XCATUrl()
self._pathutils = zvmutils.PathUtils()
def create_zvm_image(self, instance, image_name, image_href):
"""Create z/VM image from z/VM instance by invoking xCAT REST API
imgcapture.
"""
nodename = instance['name']
profile = image_name + "_" + image_href
body = ['nodename=' + nodename,
'profile=' + profile]
if CONF.zvm_image_compression_level:
if CONF.zvm_image_compression_level.isdigit() and (
int(CONF.zvm_image_compression_level) in range(0, 10)):
body.append('compress=%s' % CONF.zvm_image_compression_level)
else:
msg = _("Invalid zvm_image_compression_level detected, please"
"specify it with a integer between 0 and 9 in your nova.conf")
raise exception.ZVMImageError(msg=msg)
url = self._xcat_url.imgcapture()
LOG.debug(_('Capturing %s start') % instance['name'])
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMImageError):
res = zvmutils.xcat_request("POST", url, body)
os_image = self._get_os_image(res)
return os_image
def _get_os_image(self, response):
"""Return the image_name by parsing the imgcapture rest api
response.
"""
image_name_xcat = ""
if len(response['info']) > 0:
for info in response['info']:
for info_element in info:
if "Completed capturing the image" in info_element:
start_index = info_element.find('(')
end_index = info_element.find(')')
image_name_xcat = info_element[start_index + 1:
end_index]
return image_name_xcat
if len(image_name_xcat) == 0:
msg = _("Capture image failed.")
LOG.error(msg)
raise exception.ZVMImageError(msg=msg)
else:
msg = _("Capture image returns bad response.")
LOG.error(msg)
raise exception.ZVMImageError(msg=msg)
def get_snapshot_time_path(self):
return self._pathutils.get_snapshot_time_path()
def get_image_from_xcat(self, image_name_xcat, image_name,
snapshot_time_path):
"""Import image from xCAT to nova, by invoking the imgexport
REST API.
"""
LOG.debug(_("Getting image from xCAT"))
destination = os.path.join(snapshot_time_path, image_name + '.tgz')
host = zvmutils.get_host()
body = ['osimage=' + image_name_xcat,
'destination=' + destination,
'remotehost=' + host]
url = self._xcat_url.imgexport()
try:
zvmutils.xcat_request("POST", url, body)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError) as err:
msg = (_("Transfer image to compute node failed: %s") % err)
raise exception.ZVMImageError(msg=msg)
return destination
def delete_image_glance(self, image_service, context, image_href):
"""
delete the image from glance database and image repository.
Can be used for a rollback step if operations to image fail.
"""
image_service.delete(context, image_href)
def clean_up_snapshot_time_path(self, snapshot_time_path):
"""
Clean up the time_path and its contents under "snapshot_tmp" after
image uploaded to glance.
Also be used for a rollback step if operations to the image file fail.
"""
if os.path.exists(snapshot_time_path):
LOG.debug(_("Cleaning up nova local image file"))
shutil.rmtree(snapshot_time_path)
def _delete_image_file_from_xcat(self, image_name_xcat):
"""
Delete image file from xCAT MN.
When capturing, image in the xCAT MN's repository will be removed after
it is imported to nova compute node.
"""
LOG.debug(_("Removing image files from xCAT MN image repository"))
url = self._xcat_url.rmimage('/' + image_name_xcat)
try:
zvmutils.xcat_request("DELETE", url)
except (exception.ZVMXCATInternalError,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATRequestFailed):
LOG.warn(_("Failed to delete image file %s from xCAT") %
image_name_xcat)
def _delete_image_object_from_xcat(self, image_name_xcat):
"""Delete image object from xCAT MN.
After capturing, image definition in the xCAT MN's table osimage and
linuximage will be removed after it is imported to nova compute node.
"""
LOG.debug(_("Deleting the image object"))
url = self._xcat_url.rmobject('/' + image_name_xcat)
try:
zvmutils.xcat_request("DELETE", url)
except (exception.ZVMXCATInternalError,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATRequestFailed):
LOG.warn(_("Failed to delete image definition %s from xCAT") %
image_name_xcat)
def delete_image_from_xcat(self, image_name_xcat):
self._delete_image_file_from_xcat(image_name_xcat)
self._delete_image_object_from_xcat(image_name_xcat)
def _getxmlnode(self, node, name):
return node.getElementsByTagName(name)[0] if node else []
def _getnode(self, node_root, tagname):
"""For parse manifest."""
nodename = node_root.getElementsByTagName(tagname)[0]
nodevalue = nodename.childNodes[0].data
return nodevalue
def parse_manifest_xml(self, image_package_path):
"""Return the image properties from manifest.xml."""
LOG.debug(_("Parsing the manifest.xml"))
manifest_xml = os.path.join(image_package_path, "manifest.xml")
manifest = {}
if os.path.exists(manifest_xml):
xml_file = Dom.parse(manifest_xml)
else:
LOG.warn(_('manifest.xml does not exist'))
manifest['imagename'] = ''
manifest['imagetype'] = ''
manifest['osarch'] = ''
manifest['osname'] = ''
manifest['osvers'] = ''
manifest['profile'] = ''
manifest['provmethod'] = ''
return manifest
node_root = xml_file.documentElement
node_root = self._getxmlnode(node_root, 'osimage')
manifest['imagename'] = self._getnode(node_root, "imagename")
manifest['imagetype'] = self._getnode(node_root, "imagetype")
manifest['osarch'] = self._getnode(node_root, "osarch")
manifest['osname'] = self._getnode(node_root, "osname")
manifest['osvers'] = self._getnode(node_root, "osvers")
manifest['profile'] = self._getnode(node_root, "profile")
manifest['provmethod'] = self._getnode(node_root, "provmethod")
return manifest
def untar_image_bundle(self, snapshot_time_path, image_bundle):
"""Untar the image bundle *.tgz from xCAT and remove the *.tgz."""
if os.path.exists(image_bundle):
LOG.debug(_("Untarring the image bundle ... "))
tarobj = tarfile.open(image_bundle, "r:gz")
for tarinfo in tarobj:
tarobj.extract(tarinfo.name, path=snapshot_time_path)
tarobj.close()
os.remove(image_bundle)
else:
self.clean_up_snapshot_time_path(snapshot_time_path)
msg = _("Image bundle does not exist")
raise exception.ZVMImageError(msg=msg)
def get_image_file_name(self, image_package_path):
file_contents = os.listdir(image_package_path)
for f in file_contents:
if f.endswith('.img'):
return f
msg = _("Can not find image file")
raise exception.ZVMImageError(msg=msg)
def image_exist_xcat(self, image_id):
"""To see if the specific image exist in xCAT MN's image
repository.
"""
LOG.debug(_("Checking if the image %s exists or not in xCAT "
"MN's image repository ") % image_id)
image_uuid = image_id.replace('-', '_')
parm = '&criteria=profile=~' + image_uuid
url = self._xcat_url.lsdef_image(addp=parm)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMImageError):
res = zvmutils.xcat_request("GET", url)
res_image = res['info']
if '_' in str(res_image):
return True
else:
return False
def fetch_image(self, context, image_id, target, user, project):
LOG.debug(_("Downloading image %s from glance image server") %
image_id)
try:
images.fetch(context, image_id, target, user, project)
except Exception as err:
msg = _("Download image file of image %(id)s failed with reason:"
" %(err)s") % {'id': image_id, 'err': err}
raise exception.ZVMImageError(msg=msg)
def generate_manifest_file(self, image_meta, image_name, disk_file,
manifest_path):
"""Generate the manifest.xml file from glance's image metadata
as a part of the image bundle.
"""
image_id = image_meta['id']
image_type = image_meta['properties']['image_type_xcat']
os_version = image_meta['properties']['os_version']
os_name = image_meta['properties']['os_name']
os_arch = image_meta['properties']['architecture']
prov_method = image_meta['properties']['provisioning_method']
image_profile = '_'.join((image_name, image_id.replace('-', '_')))
image_name_xcat = '-'.join((os_version, os_arch,
prov_method, image_profile))
rootimgdir_str = ('/install', prov_method, os_version,
os_arch, image_profile)
rootimgdir = '/'.join(rootimgdir_str)
today_date = datetime.date.today()
last_use_date_string = today_date.strftime("%Y-%m-%d")
is_deletable = "auto:last_use_date:" + last_use_date_string
doc = Dom.Document()
xcatimage = doc.createElement('xcatimage')
doc.appendChild(xcatimage)
# Add linuximage section
imagename = doc.createElement('imagename')
imagename_value = doc.createTextNode(image_name_xcat)
imagename.appendChild(imagename_value)
rootimagedir = doc.createElement('rootimgdir')
rootimagedir_value = doc.createTextNode(rootimgdir)
rootimagedir.appendChild(rootimagedir_value)
linuximage = doc.createElement('linuximage')
linuximage.appendChild(imagename)
linuximage.appendChild(rootimagedir)
xcatimage.appendChild(linuximage)
# Add osimage section
osimage = doc.createElement('osimage')
manifest = {'imagename': image_name_xcat,
'imagetype': image_type,
'isdeletable': is_deletable,
'osarch': os_arch,
'osname': os_name,
'osvers': os_version,
'profile': image_profile,
'provmethod': prov_method}
for item in manifest.keys():
itemkey = doc.createElement(item)
itemvalue = doc.createTextNode(manifest[item])
itemkey.appendChild(itemvalue)
osimage.appendChild(itemkey)
xcatimage.appendChild(osimage)
f = open(manifest_path + '/manifest.xml', 'w')
f.write(doc.toprettyxml(indent=''))
f.close()
# Add the rawimagefiles section
rawimagefiles = doc.createElement('rawimagefiles')
xcatimage.appendChild(rawimagefiles)
files = doc.createElement('files')
files_value = doc.createTextNode(rootimgdir + '/' + disk_file)
files.appendChild(files_value)
rawimagefiles.appendChild(files)
f = open(manifest_path + '/manifest.xml', 'w')
f.write(doc.toprettyxml(indent=' '))
f.close()
self._rewr(manifest_path)
return manifest_path + '/manifest.xml'
def _rewr(self, manifest_path):
f = open(manifest_path + '/manifest.xml', 'r')
lines = f.read()
f.close()
lines = lines.replace('\n', '')
lines = re.sub(r'>(\s*)<', r'>\n\1<', lines)
lines = re.sub(r'>[ \t]*(\S+)[ \t]*<', r'>\1<', lines)
f = open(manifest_path + '/manifest.xml', 'w')
f.write(lines)
f.close()
def generate_image_bundle(self, spawn_path, tmp_file_fn, image_name):
"""Generate the image bundle which is used to import to xCAT MN's
image repository.
"""
image_bundle_name = image_name + '.tgz'
tar_file = spawn_path + '/' + tmp_file_fn + '_' + image_bundle_name
LOG.debug(_("The generate the image bundle file is %s") % tar_file)
os.chdir(spawn_path)
tarFile = tarfile.open(tar_file, mode='w:gz')
try:
tarFile.add(tmp_file_fn)
tarFile.close()
except Exception as err:
msg = (_("Generate image bundle failed: %s") % err)
LOG.error(msg)
if os.path.isfile(tar_file):
os.remove(tar_file)
raise exception.ZVMImageError(msg=msg)
finally:
self._pathutils.clean_temp_folder(tmp_file_fn)
return tar_file
def check_space_imgimport_xcat(self, context, instance, tar_file,
xcat_free_space_threshold, zvm_xcat_master):
image_href = instance['image_ref']
try:
free_space_xcat = self.get_free_space_xcat(
xcat_free_space_threshold, zvm_xcat_master)
img_transfer_needed = self._get_transfer_needed_space_xcat(context,
image_href, tar_file)
larger = max(xcat_free_space_threshold, img_transfer_needed)
if img_transfer_needed > free_space_xcat:
larger = max(xcat_free_space_threshold, img_transfer_needed)
size_needed = float(larger - free_space_xcat)
self.prune_image_xcat(context, size_needed,
img_transfer_needed)
else:
LOG.debug(_("Image transfer needed space satisfied in xCAT"))
except exception.ZVMImageError:
with excutils.save_and_reraise_exception():
os.remove(tar_file)
def put_image_to_xcat(self, image_bundle_package, image_profile):
"""Import the image bundle from compute node to xCAT MN's image
repository.
"""
remote_host_info = zvmutils.get_host()
body = ['osimage=%s' % image_bundle_package,
'profile=%s' % image_profile,
'remotehost=%s' % remote_host_info]
url = self._xcat_url.imgimport()
try:
zvmutils.xcat_request("POST", url, body)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError) as err:
msg = _("Import the image bundle to xCAT MN failed: %s") % err
raise exception.ZVMImageError(msg=msg)
finally:
os.remove(image_bundle_package)
def get_imgname_xcat(self, image_id):
"""Get the xCAT deployable image name by image id."""
image_uuid = image_id.replace('-', '_')
parm = '&criteria=profile=~' + image_uuid
url = self._xcat_url.lsdef_image(addp=parm)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMImageError):
res = zvmutils.xcat_request("GET", url)
with zvmutils.expect_invalid_xcat_resp_data():
res_image = res['info'][0][0]
res_img_name = res_image.strip().split(" ")[0]
if res_img_name:
return res_img_name
else:
LOG.error(_("Fail to find the right image to deploy"))
def _get_image_list_xcat(self):
"""Get an image list from xcat osimage table.
criteria: osarch=s390x and provmethod=netboot|raw|sysclone and
isdeletable field
"""
display_field = '&field=isdeletable'
isdeletable_criteria = '&criteria=isdeletable=~^auto:last_use_date:'
osarch_criteria = '&criteria=osarch=s390x'
provmethod_criteria = '&criteria=provmethod=~netboot|raw|sysclone'
addp = ''.join([isdeletable_criteria, osarch_criteria,
provmethod_criteria, display_field])
url = self._xcat_url.lsdef_image(addp=addp)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMImageError):
output = zvmutils.xcat_request("GET", url)
image_list = []
if len(output['info']) <= 0:
return image_list
if len(output['info'][0]) > 0:
i = 0
while i < len(output['info'][0]):
if "Object name:" in output['info'][0][i]:
sub_list = []
len_objectname = len("Object name: ")
is_deletable = output['info'][0][i + 1]
last_use_date = self._validate_last_use_date(
output['info'][0][i][len_objectname:],
is_deletable)
if last_use_date is None:
i += 2
continue
sub_list.append(output['info'][0][i][len_objectname:])
sub_list.append(last_use_date)
image_list.append(sub_list)
i += 2
return image_list
def update_last_use_date(self, image_name_xcat):
"""Update the last_use_date in xCAT osimage table after a
successful deploy.
"""
LOG.debug(_("Update the last_use_date in xCAT osimage table "
"after a successful deploy"))
today_date = datetime.date.today()
last_use_date_string = today_date.strftime("%Y-%m-%d")
url = self._xcat_url.tabch('/osimage')
is_deletable = "auto:last_use_date:" + last_use_date_string
body = ["imagename=" + image_name_xcat,
"osimage.isdeletable=" + is_deletable]
try:
zvmutils.xcat_request("PUT", url, body)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError) as err:
LOG.warn(_("Illegal date for last_use_date %s") % err)
return last_use_date_string
def _validate_last_use_date(self, image_name, is_deletable):
"""Validate the isdeletable date format."""
last_use_date_string = is_deletable.split(":")[2]
timere = "^\d{4}[-]((0([1-9]{1}))|"\
"(1[0|1|2]))[-](([0-2]([0-9]{1}))|(3[0|1]))$"
if (len(last_use_date_string) == 10) and (
re.match(timere, last_use_date_string)):
LOG.debug(_("The format for last_use_date is valid "))
else:
LOG.warn(_("The format for image %s record in xcat table osimage's"
" last_used_date is not valid. The correct format is "
"auto:last_use_date:yyyy-mm-dd") % image_name)
return
try:
last_use_date_datetime = datetime.datetime.strptime(
last_use_date_string, '%Y-%m-%d')
except Exception as err:
LOG.warn(_("Illegal date for last_use_date %(msg)s") % err)
return
return last_use_date_datetime.date()
def _verify_is_deletable_periodic(self, last_use_date, clean_period):
"""Check the last_use_date of an image to determine if the image
need to be cleaned.
"""
now = datetime.date.today()
delta = (now - last_use_date).days
if (delta - clean_period) >= 0:
return True
else:
return False
def clean_image_cache_xcat(self, clean_period):
"""Clean the old image."""
image_list = self._get_image_list_xcat()
if len(image_list) <= 0:
return
else:
i = 0
while i < len(image_list):
image_name_xcat = image_list[i][0]
last_use_date = image_list[i][1]
if self._verify_is_deletable_periodic(last_use_date,
clean_period):
LOG.debug(_('Delete the image %s') % image_name_xcat)
self.delete_image_from_xcat(image_name_xcat)
else:
LOG.debug(_("Keep the image"))
i += 1
def _get_image_bundle_size(self, tar_file):
size_byte = os.path.getsize(tar_file)
return float(size_byte) / 1024 / 1024 / 1024
def _get_image_size_glance(self, context, image_href):
(image_service, image_id) = glance.get_remote_image_service(
context, image_href)
try:
image_meta = image_service.show(context, image_href)
except nova_exception.ImageNotFound:
image_meta = {}
return 0
size_byte = image_meta['size']
return float(size_byte) / 1024 / 1024 / 1024
def get_free_space_xcat(self, xcat_free_space_threshold, zvm_xcat_master):
"""Get the free space in xCAT MN /install."""
LOG.debug(_("Get the xCAT MN /install free space"))
addp = "&field=--freerepospace"
if isinstance(zvm_xcat_master, str):
url = self._xcat_url.rinv("/" + zvm_xcat_master, addp=addp)
else:
msg = _("zvm_xcat_master should be specified as a string")
LOG.error(msg)
raise exception.ZVMImageError(msg=msg)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMImageError):
result = zvmutils.xcat_request("GET", url)
with zvmutils.expect_invalid_xcat_resp_data():
if len(result['info']) == 0:
msg = _("'rinv <zvm_xcat_master> --freerepospace' returns "
"null, please check 'df -h /install', there may "
"be something wrong with the mount of /install")
raise exception.ZVMImageError(msg=msg)
free_space_line = result['info'][0][0]
free_space = free_space_line.split()[4]
if free_space.endswith("G"):
free_disk_value = free_space.rstrip("G")
return float(free_disk_value)
elif free_space.endswith("M"):
free_disk_value = free_space.rstrip("M")
return float(free_disk_value) / 1024
elif free_space == "0":
return 0
else:
return xcat_free_space_threshold
def get_imgcapture_needed(self, instance):
"""Get the space needed on xCAT MN for an image capture."""
LOG.debug(_("Getting image capture needed size for %s") %
instance['name'])
cmd = "df -h /"
result = None
result = zvmutils.xdsh(instance['name'], cmd)['data'][0]
imgcapture_needed_space = ""
try:
result_data = result[0].split()
if CONF.zvm_image_compression_level and \
int(CONF.zvm_image_compression_level) == 0:
imgcapture_needed_space = result_data[10]
else:
imgcapture_needed_space = result_data[11]
if imgcapture_needed_space.endswith("G"):
imgcapture_needed_space_value =\
imgcapture_needed_space.rstrip("G")
return float(imgcapture_needed_space_value) * 2
elif imgcapture_needed_space.endswith("M"):
imgcapture_needed_space_value =\
imgcapture_needed_space.rstrip("M")
return (float(imgcapture_needed_space_value) / 1024) * 2
else:
return const.ZVM_IMAGE_SIZE_MAX
except (IndexError, ValueError, TypeError) as err:
raise exception.ZVMImageError(msg=err)
def _get_transfer_needed_space_xcat(self, context, image_href, tar_file):
"""To transfer an image bundle from glance to xCAT, the needed size is
image_bundle_size + image_size.
"""
image_bundle_size = self._get_image_bundle_size(tar_file)
image_size = self._get_image_size_glance(context, image_href)
return image_bundle_size + image_size
def _get_image_href_by_osimage(self, image_name_xcat):
"""If we have the xCAT.osimage.imagename, we want to get the
image_href.
"""
try:
image_profile = image_name_xcat.split("-")
if len(image_profile) >= 4:
return image_profile[3]
else:
return image_name_xcat
except (TypeError, IndexError):
LOG.error(_("xCAT imagename format for %s is not as expected")
% image_name_xcat)
def _sort_image_by_use_date(self, image_list, left, right):
"""Sort the image_list by last_use_date from oldest image to latest."""
if (left < right):
i = left
j = right
x = image_list[left]
while (i < j):
while (i < j and image_list[j][1] >= x[1]):
j -= 1
if(i < j):
image_list[i] = image_list[j]
i += 1
while(i < j and image_list[i][1] < x[1]):
i += 1
if(i < j):
image_list[j] = image_list[i]
j -= 1
image_list[i] = x
self._sort_image_by_use_date(image_list, left, i - 1)
self._sort_image_by_use_date(image_list, i + 1, right)
def _get_to_be_deleted_images_xcat(self, context, size_needed,
current_needed):
"""To get a list of images which is to be removed from xCAT image
repository because it cannot provide enough space for image operations
from OpenStack.
"""
image_list = self._get_image_list_xcat()
size_sum = 0
to_be_deleted_image_profile = []
if len(image_list) <= 0:
msg = _("No image to be deleted, please create space manually "
"on xcat(%s).") % CONF.zvm_xcat_server
raise exception.ZVMImageError(msg=msg)
else:
self._sort_image_by_use_date(image_list, 0, len(image_list) - 1)
for img in image_list:
image_name_xcat = img[0]
image_profile = self._get_image_href_by_osimage(
image_name_xcat)
image_uuid = image_profile.partition('_')[2].replace("_", "-")
image_size = self._get_image_size_glance(context,
image_uuid)
if image_size > 0:
to_be_deleted_image_profile.append(image_profile)
size_sum += image_size
if size_sum >= size_needed:
return to_be_deleted_image_profile
if size_sum >= current_needed:
return to_be_deleted_image_profile
else:
msg = _("xCAT MN space not enough for the current image operation")
raise exception.ZVMImageError(msg=msg)
def prune_image_xcat(self, context, size_needed, current_needed):
"""Remove the images which meet remove criteria from xCAT."""
LOG.debug(_("Clear up space by clean images in xCAT"))
to_be_deleted_image_profile = self._get_to_be_deleted_images_xcat(
context, size_needed, current_needed)
if len(to_be_deleted_image_profile) > 0:
for image_profile in to_be_deleted_image_profile:
image_name_xcat = self.get_imgname_xcat(image_profile)
self.delete_image_from_xcat(image_name_xcat)
def zimage_check(self, image_meta):
"""Do a brief check to see if the image is a valid zVM image."""
property_ = ['image_file_name', 'image_type_xcat', 'architecture',
'os_name', 'provisioning_method', 'os_version']
for prop in property_:
if prop not in image_meta['properties'].keys():
msg = (_("The image %s is not a valid zVM image,please check "
"if the image properties match the requirements.")
% image_meta['id'])
LOG.error(msg)
raise exception.ZVMImageError(msg=msg)
def cleanup_image_after_migration(self, inst_name):
"""Cleanup osimages in xCAT image repository while confirm migration
or revert migration at source compute.
"""
image_list = self._get_image_list_xcat()
matchee = ''.join(['rsz', inst_name])
for img in image_list:
img_name = img[0]
if matchee in img_name:
self.delete_image_from_xcat(img_name)
def get_root_disk_units(self, image_file_path):
"""use 'hexdump' to get the root_disk_units"""
cmd = "hexdump -n 48 -C %s" % image_file_path
(result, output) = commands.getstatusoutput(cmd)
if result != 0:
msg = (_("Get image property failed,"
" please check whether the image file exists!"))
raise exception.ZVMImageError(msg=msg)
LOG.debug(_("hexdump result is %s") % output)
try:
root_disk_units = int(output[144:156])
except ValueError:
msg = (_("Image file at %s is missing imbeded disk size "
"metadata, it was probably not captured with xCAT")
% image_file_path)
raise exception.ZVMImageError(msg=msg)
if 'FBA' not in output and 'CKD' not in output:
msg = (_("The image's disk type is not valid. Currently we only"
" support FBA and CKD disk"))
raise exception.ZVMImageError(msg=msg)
LOG.debug(_("The image's root_disk_units is %s") % root_disk_units)
return root_disk_units
def set_image_root_disk_units(self, context, image_meta, image_file_path):
"""Set the property 'root_disk_units'to image. """
new_image_meta = image_meta
root_disk_units = self.get_root_disk_units(image_file_path)
LOG.debug(_("The image's root_disk_units is %s") % root_disk_units)
(glance_image_service, image_id) = glance.get_remote_image_service(
context, image_meta['id'])
new_image_meta = glance_image_service.show(context, image_id)
new_image_meta['properties']['root_disk_units'] = str(root_disk_units)
try:
glance_image_service.update(context, image_id,
new_image_meta, None)
except nova_exception.ImageNotAuthorized:
msg = _('Not allowed to modify attributes for image %s') % image_id
LOG.error(msg)
return new_image_meta

View File

@ -0,0 +1,609 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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 datetime
from oslo.config import cfg
from nova.compute import power_state
from nova import exception as nova_exception
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
from nova.openstack.common import loopingcall
from nova.openstack.common import timeutils
from nova.virt.zvm import const
from nova.virt.zvm import exception
from nova.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class ZVMInstance(object):
'''OpenStack instance that running on of z/VM hypervisor.'''
def __init__(self, instance={}):
"""Initialize instance attributes for database."""
self._xcat_url = zvmutils.XCATUrl()
self._xcat_conn = zvmutils.XCATConnection()
self._instance = instance
self._name = instance['name']
def power_off(self):
"""Power off z/VM instance."""
try:
self._power_state("PUT", "off")
except exception.ZVMXCATInternalError as err:
err_str = err.format_message()
if ("Return Code: 200" in err_str and
"Reason Code: 12" in err_str):
# Instance already not active
LOG.warn(_("z/VM instance %s not active") % self._name)
return
else:
msg = _("Failed to power off instance: %s") % err
LOG.error(msg)
raise nova_exception.InstancePowerOffFailure(reason=msg)
def power_on(self):
""""Power on z/VM instance."""
try:
self._power_state("PUT", "on")
except exception.ZVMXCATInternalError as err:
err_str = err.format_message()
if ("Return Code: 200" in err_str and
"Reason Code: 8" in err_str):
# Instance already not active
LOG.warn(_("z/VM instance %s already active") % self._name)
return
self._wait_for_reachable()
if not self._reachable:
LOG.error(_("Failed to power on instance %s: timeout") %
self._name)
raise nova_exception.InstancePowerOnFailure(reason="timeout")
def reset(self):
"""Hard reboot z/VM instance."""
try:
self._power_state("PUT", "reset")
except exception.ZVMXCATInternalError as err:
err_str = err.format_message()
if ("Return Code: 200" in err_str and
"Reason Code: 12" in err_str):
# Be able to reset in power state of SHUTDOWN
LOG.warn(_("Reset z/VM instance %s from SHUTDOWN state") %
self._name)
return
else:
raise err
self._wait_for_reachable()
def reboot(self):
"""Soft reboot z/VM instance."""
self._power_state("PUT", "reboot")
self._wait_for_reachable()
def pause(self):
"""Pause the z/VM instance."""
self._power_state("PUT", "pause")
def unpause(self):
"""Unpause the z/VM instance."""
self._power_state("PUT", "unpause")
self._wait_for_reachable()
def attach_volume(self, volumeop, context, connection_info, instance,
mountpoint, is_active, rollback=True):
volumeop.attach_volume_to_instance(context, connection_info,
instance, mountpoint,
is_active, rollback)
def detach_volume(self, volumeop, connection_info, instance, mountpoint,
is_active, rollback=True):
volumeop.detach_volume_from_instance(connection_info,
instance, mountpoint,
is_active, rollback)
def get_info(self):
"""Get the current status of an z/VM instance.
Returns a dict containing:
:state: the running state, one of the power_state codes
:max_mem: (int) the maximum memory in KBytes allowed
:mem: (int) the memory in KBytes used by the domain
:num_cpu: (int) the number of virtual CPUs for the domain
:cpu_time: (int) the CPU time used in nanoseconds
"""
power_stat = self._get_power_stat()
is_reachable = self.is_reachable()
max_mem_kb = int(self._instance['memory_mb']) * 1024
if is_reachable:
try:
rec_list = self._get_rinv_info()
except exception.ZVMXCATInternalError:
raise nova_exception.InstanceNotFound(instance_id=self._name)
try:
mem = self._get_current_memory(rec_list)
num_cpu = self._get_cpu_count(rec_list)
cpu_time = self._get_cpu_used_time(rec_list)
_instance_info = {'state': power_stat,
'max_mem': max_mem_kb,
'mem': mem,
'num_cpu': num_cpu,
'cpu_time': cpu_time, }
except exception.ZVMInvalidXCATResponseDataError:
LOG.warn(_("Failed to get inventory info for %s") % self._name)
_instance_info = {'state': power_stat,
'max_mem': max_mem_kb,
'mem': max_mem_kb,
'num_cpu': self._instance['vcpus'],
'cpu_time': 0, }
else:
# Since xCAT rinv can't get info from a server that in power state
# of SHUTDOWN or PAUSED
if ((power_stat == power_state.RUNNING) and
(self._instance['power_state'] == power_state.PAUSED)):
# return paused state only previous power state is paused
_instance_info = {'state': power_state.PAUSED,
'max_mem': max_mem_kb,
'mem': max_mem_kb,
'num_cpu': self._instance['vcpus'],
'cpu_time': 0, }
else:
# otherwise return xcat returned state
_instance_info = {'state': power_stat,
'max_mem': max_mem_kb,
'mem': 0,
'num_cpu': self._instance['vcpus'],
'cpu_time': 0, }
return _instance_info
def create_xcat_node(self, zhcp, userid=None):
"""Create xCAT node for z/VM instance."""
LOG.debug(_("Creating xCAT node for %s") % self._name)
user_id = userid or self._name
body = ['userid=%s' % user_id,
'hcp=%s' % zhcp,
'mgt=zvm',
'groups=%s' % CONF.zvm_xcat_group]
url = self._xcat_url.mkdef('/' + self._name)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMXCATCreateNodeFailed, node=self._name):
zvmutils.xcat_request("POST", url, body)
def create_userid(self, block_device_info, image_meta):
"""Create z/VM userid into user directory for a z/VM instance."""
# We do not support boot from volume currently
LOG.debug(_("Creating the z/VM user entry for instance %s")
% self._name)
is_volume_base = zvmutils.volume_in_mapping(
const.ZVM_DEFAULT_ROOT_VOLUME, block_device_info)
if is_volume_base:
# TODO(rui): Boot from volume
msg = _("Not support boot from volume.")
raise exception.ZVMXCATCreateUserIdFailed(instance=self._name,
msg=msg)
eph_disks = block_device_info.get('ephemerals', [])
kwprofile = 'profile=%s' % CONF.zvm_user_profile
body = [kwprofile,
'password=%s' % CONF.zvm_user_default_password,
'cpu=%i' % self._instance['vcpus'],
'memory=%im' % self._instance['memory_mb'],
'privilege=%s' % CONF.zvm_user_default_privilege]
url = self._xcat_url.mkvm('/' + self._name)
try:
zvmutils.xcat_request("POST", url, body)
if not is_volume_base:
size = '%ig' % self._instance['root_gb']
# use a flavor the disk size is 0
if size == '0g':
size = image_meta['properties']['root_disk_units']
# Add root disk and set ipl
self.add_mdisk(CONF.zvm_diskpool,
CONF.zvm_user_root_vdev,
size)
self._set_ipl(CONF.zvm_user_root_vdev)
# Add additional ephemeral disk
if self._instance['ephemeral_gb'] != 0:
if eph_disks == []:
# Create ephemeral disk according to flavor
fmt = (CONF.default_ephemeral_format or
const.DEFAULT_EPH_DISK_FMT)
self.add_mdisk(CONF.zvm_diskpool,
CONF.zvm_user_adde_vdev,
'%ig' % self._instance['ephemeral_gb'],
fmt)
else:
# Create ephemeral disks according --ephemeral option
for idx, eph in enumerate(eph_disks):
vdev = (eph.get('vdev') or
zvmutils.generate_eph_vdev(idx))
size = eph['size']
size_in_units = eph.get('size_in_units', False)
if not size_in_units:
size = '%ig' % size
fmt = (eph.get('guest_format') or
CONF.default_ephemeral_format or
const.DEFAULT_EPH_DISK_FMT)
self.add_mdisk(CONF.zvm_diskpool, vdev, size, fmt)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMDriverError) as err:
msg = _("Failed to create z/VM userid: %s") % err
LOG.error(msg)
raise exception.ZVMXCATCreateUserIdFailed(instance=self._name,
msg=msg)
def _set_ipl(self, ipl_state):
body = ["--setipl %s" % ipl_state]
url = self._xcat_url.chvm('/' + self._name)
zvmutils.xcat_request("PUT", url, body)
def is_locked(self, zhcp_node):
cmd = "smcli Image_Lock_Query_DM -T %s" % self._name
resp = zvmutils.xdsh(zhcp_node, cmd)
return "is Unlocked..." not in str(resp)
def _wait_for_unlock(self, zhcp_node, interval=10, timeout=600):
LOG.debug("Waiting for unlock instance %s" % self._name)
def _wait_unlock(expiration):
if timeutils.utcnow() > expiration:
LOG.debug("Waiting for unlock instance %s timeout" %
self._name)
raise loopingcall.LoopingCallDone()
if not self.is_locked(zhcp_node):
LOG.debug("Instance %s is unlocked" %
self._name)
raise loopingcall.LoopingCallDone()
expiration = timeutils.utcnow() + datetime.timedelta(seconds=timeout)
timer = loopingcall.FixedIntervalLoopingCall(_wait_unlock,
expiration)
timer.start(interval=interval).wait()
def delete_userid(self, zhcp_node):
"""Delete z/VM userid for the instance.This will remove xCAT node
at same time.
"""
url = self._xcat_url.rmvm('/' + self._name)
try:
zvmutils.xcat_request("DELETE", url)
except exception.ZVMXCATInternalError as err:
if (err.format_message().__contains__("Return Code: 400") and
err.format_message().__contains__("Reason Code: 4")):
# zVM user definition not found, delete xCAT node directly
self.delete_xcat_node()
elif (err.format_message().__contains__("Return Code: 400") and
(err.format_message().__contains__("Reason Code: 16") or
err.format_message().__contains__("Reason Code: 12"))):
# The vm or vm device was locked. Unlock before deleting
self._wait_for_unlock(zhcp_node)
zvmutils.xcat_request("DELETE", url)
else:
raise err
except exception.ZVMXCATRequestFailed as err:
emsg = err.format_message()
if (emsg.__contains__("Invalid nodes and/or groups") and
emsg.__contains__("Forbidden")):
# Assume neither zVM userid nor xCAT node exist in this case
return
else:
raise err
def delete_xcat_node(self):
"""Remove xCAT node for z/VM instance."""
url = self._xcat_url.rmdef('/' + self._name)
try:
zvmutils.xcat_request("DELETE", url)
except exception.ZVMXCATInternalError as err:
if err.format_message().__contains__("Could not find an object"):
# The xCAT node not exist
return
else:
raise err
def add_mdisk(self, diskpool, vdev, size, fmt=None):
"""Add a 3390 mdisk for a z/VM user.
NOTE: No read, write and multi password specified, and
access mode default as 'MR'.
"""
disk_type = CONF.zvm_diskpool_type
if (disk_type == 'ECKD'):
action = '--add3390'
elif (disk_type == 'FBA'):
action = '--add9336'
else:
errmsg = _("Disk type %s is not supported.") % disk_type
LOG.error(errmsg)
raise exception.ZVMDriverError(msg=errmsg)
if fmt:
body = [" ".join([action, diskpool, vdev, size, "MR", "''", "''",
"''", fmt])]
else:
body = [" ".join([action, diskpool, vdev, size])]
url = self._xcat_url.chvm('/' + self._name)
zvmutils.xcat_request("PUT", url, body)
def _power_state(self, method, state):
"""Invoke xCAT REST API to set/get power state for a instance."""
body = [state]
url = self._xcat_url.rpower('/' + self._name)
return zvmutils.xcat_request(method, url, body)
def _get_power_stat(self):
"""Get power status of a z/VM instance."""
LOG.debug(_('Query power stat of %s') % self._name)
res_dict = self._power_state("GET", "stat")
@zvmutils.wrap_invalid_xcat_resp_data_error
def _get_power_string(d):
tempstr = d['info'][0][0]
return tempstr[(tempstr.find(':') + 2):].strip()
power_stat = _get_power_string(res_dict)
return zvmutils.mapping_power_stat(power_stat)
def _get_rinv_info(self):
"""get rinv result and return in a list."""
url = self._xcat_url.rinv('/' + self._name, '&field=cpumem')
LOG.debug(_('Remote inventory of %s') % self._name)
res_info = zvmutils.xcat_request("GET", url)['info']
with zvmutils.expect_invalid_xcat_resp_data():
rinv_info = res_info[0][0].split('\n')
return rinv_info
@zvmutils.wrap_invalid_xcat_resp_data_error
def _modify_storage_format(self, mem):
"""modify storage from 'G' ' M' to 'K'."""
new_mem = 0
if mem.endswith('G'):
new_mem = int(mem[:-1]) * 1024 * 1024
elif mem.endswith('M'):
new_mem = int(mem[:-1]) * 1024
elif mem.endswith('K'):
new_mem = int(mem[:-1])
else:
exp = "ending with a 'G', 'M' or 'K'"
errmsg = _("Invalid memory format: %(invalid)s; Expected: "
"%(exp)s") % {'invalid': mem, 'exp': exp}
LOG.error(errmsg)
raise exception.ZVMInvalidXCATResponseDataError(msg=errmsg)
return new_mem
@zvmutils.wrap_invalid_xcat_resp_data_error
def _get_current_memory(self, rec_list):
"""Return the max memory can be used."""
_mem = None
for rec in rec_list:
if rec.__contains__("Total Memory: "):
tmp_list = rec.split()
_mem = tmp_list[3]
_mem = self._modify_storage_format(_mem)
return _mem
@zvmutils.wrap_invalid_xcat_resp_data_error
def _get_cpu_count(self, rec_list):
"""Return the virtual cpu count."""
_cpu_flag = False
num_cpu = 0
for rec in rec_list:
if (_cpu_flag is True):
tmp_list = rec.split()
if (len(tmp_list) > 1):
if (tmp_list[1] == "CPU"):
num_cpu += 1
else:
_cpu_flag = False
if rec.__contains__("Processors: "):
_cpu_flag = True
return num_cpu
@zvmutils.wrap_invalid_xcat_resp_data_error
def _get_cpu_used_time(self, rec_list):
"""Return the cpu used time in."""
cpu_time = None
for rec in rec_list:
if rec.__contains__("CPU Used Time: "):
tmp_list = rec.split()
cpu_time = tmp_list[4]
return int(cpu_time)
def is_reachable(self):
"""Return True is the instance is reachable."""
url = self._xcat_url.nodestat('/' + self._name)
LOG.debug(_('Get instance status of %s') % self._name)
res_dict = zvmutils.xcat_request("GET", url)
with zvmutils.expect_invalid_xcat_resp_data():
status = res_dict['node'][0][0]['data'][0]
if status is not None:
if status.__contains__('sshd'):
return True
return False
def _wait_for_reachable(self):
"""Called at an interval until the instance is reachable."""
self._reachable = False
def _wait_reachable(expiration):
if (CONF.zvm_reachable_timeout and
timeutils.utcnow() > expiration):
raise loopingcall.LoopingCallDone()
if self.is_reachable():
self._reachable = True
LOG.debug(_("Instance %s reachable now") %
self._name)
raise loopingcall.LoopingCallDone()
expiration = timeutils.utcnow() + datetime.timedelta(
seconds=CONF.zvm_reachable_timeout)
timer = loopingcall.FixedIntervalLoopingCall(_wait_reachable,
expiration)
timer.start(interval=5).wait()
def update_node_info(self, image_meta):
LOG.debug(_("Update the node info for instance %s") % self._name)
image_name = image_meta['name']
image_id = image_meta['id']
os_type = image_meta['properties']['os_version']
os_arch = image_meta['properties']['architecture']
prov_method = image_meta['properties']['provisioning_method']
profile_name = '_'.join((image_name, image_id.replace('-', '_')))
body = ['noderes.netboot=%s' % const.HYPERVISOR_TYPE,
'nodetype.os=%s' % os_type,
'nodetype.arch=%s' % os_arch,
'nodetype.provmethod=%s' % prov_method,
'nodetype.profile=%s' % profile_name]
url = self._xcat_url.chtab('/' + self._name)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMXCATUpdateNodeFailed, node=self._name):
zvmutils.xcat_request("PUT", url, body)
def update_node_info_resize(self, image_name_xcat):
LOG.debug(_("Update the nodetype for instance %s") % self._name)
name_section = image_name_xcat.split("-")
os_type = name_section[0]
os_arch = name_section[1]
profile_name = name_section[3]
body = ['noderes.netboot=%s' % const.HYPERVISOR_TYPE,
'nodetype.os=%s' % os_type,
'nodetype.arch=%s' % os_arch,
'nodetype.provmethod=%s' % 'sysclone',
'nodetype.profile=%s' % profile_name]
url = self._xcat_url.chtab('/' + self._name)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMXCATUpdateNodeFailed, node=self._name):
zvmutils.xcat_request("PUT", url, body)
def get_provmethod(self):
addp = "&col=node=%s&attribute=provmethod" % self._name
url = self._xcat_url.gettab('/nodetype', addp)
res_info = zvmutils.xcat_request("GET", url)
return res_info['data'][0][0]
def update_node_provmethod(self, provmethod):
LOG.debug(_("Update the nodetype for instance %s") % self._name)
body = ['nodetype.provmethod=%s' % provmethod]
url = self._xcat_url.chtab('/' + self._name)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMXCATUpdateNodeFailed, node=self._name):
zvmutils.xcat_request("PUT", url, body)
def update_node_def(self, hcp, userid):
"""Update xCAT node definition."""
body = ['zvm.hcp=%s' % hcp,
'zvm.userid=%s' % userid]
url = self._xcat_url.chtab('/' + self._name)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMXCATUpdateNodeFailed, node=self._name):
zvmutils.xcat_request("PUT", url, body)
def deploy_node(self, image_name, transportfiles=None, vdev=None):
LOG.debug(_("Begin to deploy image on instance %s") % self._name)
vdev = vdev or CONF.zvm_user_root_vdev
remote_host_info = zvmutils.get_host()
body = ['netboot',
'device=%s' % vdev,
'osimage=%s' % image_name]
if transportfiles:
body.append('transport=%s' % transportfiles)
body.append('remotehost=%s' % remote_host_info)
url = self._xcat_url.nodeset('/' + self._name)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMXCATDeployNodeFailed, node=self._name):
zvmutils.xcat_request("PUT", url, body)
def copy_xcat_node(self, source_node_name):
"""Create xCAT node from an existing z/VM instance."""
LOG.debug(_("Creating xCAT node %s from existing node") % self._name)
url = self._xcat_url.lsdef_node('/' + source_node_name)
res_info = zvmutils.xcat_request("GET", url)['info'][0]
body = []
for info in res_info:
if "=" in info and ("postbootscripts" not in info)\
and ("postscripts" not in info) \
and ("hostnames" not in info):
body.append(info.lstrip())
url = self._xcat_url.mkdef('/' + self._name)
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMXCATCreateNodeFailed, node=self._name):
zvmutils.xcat_request("POST", url, body)
def get_console_log(self, logsize):
"""get console log."""
url = self._xcat_url.rinv('/' + self._name, '&field=console'
'&field=%s') % logsize
LOG.debug(_('Get console log of %s') % self._name)
res_info = zvmutils.xcat_request("GET", url)['info']
with zvmutils.expect_invalid_xcat_resp_data():
rinv_info = res_info[0][0]
return rinv_info

View File

@ -0,0 +1,207 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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
from nova.openstack.common.gettextutils import _
from nova.openstack.common import importutils
from nova.openstack.common import log as logging
from nova.virt.zvm import exception
from nova.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
NetworkUtils = zvmutils.NetworkUtils()
class NetworkOperator(object):
"""Configuration check and manage MAC address."""
_vif_driver_class_map = {
'nova.network.neutronv2.api.API':
'nova.virt.zvm.vif.ZVMNeutronVIFDriver',
}
def __init__(self):
self._xcat_url = zvmutils.XCATUrl()
self._load_vif_driver_class()
def _load_vif_driver_class(self):
vif_driver = CONF.network_api_class
try:
class_name = self._vif_driver_class_map[vif_driver]
except KeyError:
msg = _("VIF driver %s not supported by z/VM.") % vif_driver
LOG.error(msg)
raise exception.ZVMNetworkError(msg=msg)
self._vif_driver = importutils.import_object(class_name)
def add_xcat_host(self, node, ip, host_name):
"""Add/Update hostname/ip bundle in xCAT MN nodes table."""
commands = "node=%s" % node + " hosts.ip=%s" % ip
commands += " hosts.hostnames=%s" % host_name
body = [commands]
url = self._xcat_url.tabch("/hosts")
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
result_data = zvmutils.xcat_request("PUT", url, body)['data']
return result_data
def _delete_xcat_host(self, node_name):
"""Remove xcat hosts table rows where node name is node_name."""
commands = "-d node=%s hosts" % node_name
body = [commands]
url = self._xcat_url.tabch("/hosts")
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url, body)['data']
def add_xcat_mac(self, node, interface, mac, zhcp=None):
"""Add node name, interface, mac address into xcat mac table."""
commands = "mac.node=%s" % node + " mac.mac=%s" % mac
commands += " mac.interface=%s" % interface
if zhcp is not None:
commands += " mac.comments=%s" % zhcp
url = self._xcat_url.tabch("/mac")
body = [commands]
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url, body)['data']
def add_xcat_switch(self, node, nic_name, interface, zhcp=None):
"""Add node name and nic name address into xcat switch table."""
commands = "switch.node=%s" % node
commands += " switch.port=%s" % nic_name
commands += " switch.interface=%s" % interface
if zhcp is not None:
commands += " switch.comments=%s" % zhcp
url = self._xcat_url.tabch("/switch")
body = [commands]
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url, body)['data']
def _delete_xcat_mac(self, node_name):
"""Remove node mac record from xcat mac table."""
commands = "-d node=%s mac" % node_name
url = self._xcat_url.tabch("/mac")
body = [commands]
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url, body)['data']
def _delete_xcat_switch(self, node_name):
"""Remove node switch record from xcat switch table."""
commands = "-d node=%s switch" % node_name
url = self._xcat_url.tabch("/switch")
body = [commands]
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url, body)['data']
def update_xcat_mac(self, node, interface, mac, zhcp=None):
"""Add node name, interface, mac address into xcat mac table."""
commands = "node=%s" % node + " interface=%s" % interface
commands += " mac.mac=%s" % mac
if zhcp is not None:
commands += " mac.comments=%s" % zhcp
url = self._xcat_url.tabch("/mac")
body = [commands]
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url, body)['data']
def update_xcat_switch(self, node, nic_name, interface, zhcp=None):
"""Add node name and nic name address into xcat switch table."""
commands = "node=%s" % node
commands += " interface=%s" % interface
commands += " switch.port=%s" % nic_name
if zhcp is not None:
commands += " switch.comments=%s" % zhcp
url = self._xcat_url.tabch("/switch")
body = [commands]
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url, body)['data']
def clean_mac_switch_host(self, node_name):
"""Clean node records in xCAT mac, host and switch table."""
self.clean_mac_switch(node_name)
self._delete_xcat_host(node_name)
def clean_mac_switch(self, node_name):
"""Clean node records in xCAT mac and switch table."""
self._delete_xcat_mac(node_name)
self._delete_xcat_switch(node_name)
def makehosts(self):
"""Update xCAT MN /etc/hosts file."""
url = self._xcat_url.network("/makehosts")
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url)['data']
def makeDNS(self):
"""Update xCAT MN DNS."""
url = self._xcat_url.network("/makedns")
with zvmutils.except_xcat_call_failed_and_reraise(
exception.ZVMNetworkError):
return zvmutils.xcat_request("PUT", url)['data']
def config_xcat_mac(self, instance_name):
"""Hook xCat to prevent assign MAC for instance."""
fake_mac_addr = "00:00:00:00:00:00"
nic_name = "fake"
self.add_xcat_mac(instance_name, nic_name, fake_mac_addr)
def create_nic(self, zhcpnode, inst_name, nic_name, mac_address, vdev,
userid=None):
"""Create network information in xCAT and zVM user direct."""
macid = mac_address.replace(':', '')[-6:]
self._add_instance_nic(zhcpnode, inst_name, vdev, macid, userid)
self._delete_xcat_mac(inst_name)
self.add_xcat_mac(inst_name, vdev, mac_address, zhcpnode)
self.add_xcat_switch(inst_name, nic_name, vdev, zhcpnode)
def _add_instance_nic(self, zhcpnode, inst_name, vdev, macid, userid=None):
"""Add NIC defination into user direct."""
if userid is None:
command = ("/opt/zhcp/bin/smcli Image_Definition_Update_DM -T %s" %
inst_name)
else:
command = ("/opt/zhcp/bin/smcli Image_Definition_Update_DM -T %s" %
userid)
command += " -k \'NICDEF=VDEV=%s TYPE=QDIO " % vdev
command += "MACID=%s\'" % macid
try:
zvmutils.xdsh(zhcpnode, command)
except exception.ZVMXCATXdshFailed as err:
msg = _("Adding nic error: %s") % err
raise exception.ZVMNetworkError(msg=msg)

View File

@ -0,0 +1,830 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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 contextlib
import functools
import httplib
import os
import shutil
import socket
import time
from oslo.config import cfg
from nova import block_device
from nova.compute import power_state
from nova.openstack.common import excutils
from nova.openstack.common.gettextutils import _
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.virt import driver
from nova.virt.zvm import const
from nova.virt.zvm import exception
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.import_opt('instances_path', 'nova.compute.manager')
class XCATUrl(object):
"""To return xCAT url for invoking xCAT REST API."""
def __init__(self):
"""Set constant that used to form xCAT url."""
self.PREFIX = '/xcatws'
self.SUFFIX = '?userName=' + CONF.zvm_xcat_username + \
'&password=' + CONF.zvm_xcat_password + \
'&format=json'
self.NODES = '/nodes'
self.VMS = '/vms'
self.IMAGES = '/images'
self.OBJECTS = '/objects/osimage'
self.OS = '/OS'
self.TABLES = '/tables'
self.HV = '/hypervisor'
self.NETWORK = '/networks'
self.POWER = '/power'
self.INVENTORY = '/inventory'
self.STATUS = '/status'
self.MIGRATE = '/migrate'
self.CAPTURE = '/capture'
self.EXPORT = '/export'
self.IMGIMPORT = '/import'
self.BOOTSTAT = '/bootstate'
self.XDSH = '/dsh'
def _nodes(self, arg=''):
return self.PREFIX + self.NODES + arg + self.SUFFIX
def _vms(self, arg=''):
return self.PREFIX + self.VMS + arg + self.SUFFIX
def _hv(self, arg=''):
return self.PREFIX + self.HV + arg + self.SUFFIX
def rpower(self, arg=''):
return self.PREFIX + self.NODES + arg + self.POWER + self.SUFFIX
def nodels(self, arg=''):
return self._nodes(arg)
def rinv(self, arg='', addp=None):
rurl = self.PREFIX + self.NODES + arg + self.INVENTORY + self.SUFFIX
return self._append_addp(rurl, addp)
def mkdef(self, arg=''):
return self._nodes(arg)
def rmdef(self, arg=''):
return self._nodes(arg)
def nodestat(self, arg=''):
return self.PREFIX + self.NODES + arg + self.STATUS + self.SUFFIX
def chvm(self, arg=''):
return self._vms(arg)
def lsvm(self, arg=''):
return self._vms(arg)
def chhv(self, arg=''):
return self._hv(arg)
def mkvm(self, arg=''):
return self._vms(arg)
def rmvm(self, arg=''):
return self._vms(arg)
def tabdump(self, arg='', addp=None):
rurl = self.PREFIX + self.TABLES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def _append_addp(self, rurl, addp=None):
if addp is not None:
return rurl + addp
else:
return rurl
def imgcapture(self, arg=''):
return self.PREFIX + self.IMAGES + arg + self.CAPTURE + self.SUFFIX
def imgexport(self, arg=''):
return self.PREFIX + self.IMAGES + arg + self.EXPORT + self.SUFFIX
def rmimage(self, arg=''):
return self.PREFIX + self.IMAGES + arg + self.SUFFIX
def rmobject(self, arg=''):
return self.PREFIX + self.OBJECTS + arg + self.SUFFIX
def lsdef_node(self, arg='', addp=None):
rurl = self.PREFIX + self.NODES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def lsdef_image(self, arg='', addp=None):
rurl = self.PREFIX + self.IMAGES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def imgimport(self, arg=''):
return self.PREFIX + self.IMAGES + self.IMGIMPORT + arg + self.SUFFIX
def chtab(self, arg=''):
return self.PREFIX + self.NODES + arg + self.SUFFIX
def nodeset(self, arg=''):
return self.PREFIX + self.NODES + arg + self.BOOTSTAT + self.SUFFIX
def rmigrate(self, arg=''):
return self.PREFIX + self.NODES + arg + self.MIGRATE + self.SUFFIX
def gettab(self, arg='', addp=None):
rurl = self.PREFIX + self.TABLES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def tabch(self, arg='', addp=None):
"""Add/update/delete row(s) in table arg, with attribute addp."""
rurl = self.PREFIX + self.TABLES + arg + self.SUFFIX
return self._append_addp(rurl, addp)
def xdsh(self, arg=''):
"""Run shell command."""
return self.PREFIX + self.NODES + arg + self.XDSH + self.SUFFIX
def network(self, arg='', addp=None):
rurl = self.PREFIX + self.NETWORK + arg + self.SUFFIX
if addp is not None:
return rurl + addp
else:
return rurl
class XCATConnection():
"""Https requests to xCAT web service."""
def __init__(self):
"""Initialize https connection to xCAT service."""
self.host = CONF.zvm_xcat_server
self.conn = httplib.HTTPSConnection(self.host,
timeout=CONF.zvm_xcat_connection_timeout)
def request(self, method, url, body=None, headers={}):
"""Send https request to xCAT server.
Will return a python dictionary including:
{'status': http return code,
'reason': http reason,
'message': response message}
"""
if body is not None:
body = jsonutils.dumps(body)
headers = {'content-type': 'text/plain',
'content-length': len(body)}
try:
self.conn.request(method, url, body, headers)
except socket.gaierror as err:
msg = _("Failed to find address: %s") % err
raise exception.ZVMXCATRequestFailed(xcatserver=self.host, msg=msg)
except (socket.error, socket.timeout) as err:
msg = _("Communication error: %s") % err
raise exception.ZVMXCATRequestFailed(xcatserver=self.host, msg=msg)
try:
res = self.conn.getresponse()
except Exception as err:
msg = _("Failed to get response from xCAT: %s") % err
raise exception.ZVMXCATRequestFailed(xcatserver=self.host, msg=msg)
msg = res.read()
resp = {
'status': res.status,
'reason': res.reason,
'message': msg}
# Only "200" or "201" returned from xCAT can be considered
# as good status
err = None
if method == "POST":
if res.status != 201:
err = str(resp)
else:
if res.status != 200:
err = str(resp)
if err is not None:
raise exception.ZVMXCATRequestFailed(xcatserver=self.host,
msg=err)
return resp
def xcat_request(method, url, body=None, headers={}):
conn = XCATConnection()
resp = conn.request(method, url, body, headers)
return load_xcat_resp(resp['message'])
def jsonloads(jsonstr):
try:
return jsonutils.loads(jsonstr)
except ValueError:
errmsg = _("xCAT response data is not in JSON format")
LOG.error(errmsg)
raise exception.ZVMDriverError(msg=errmsg)
@contextlib.contextmanager
def expect_invalid_xcat_resp_data():
"""Catch exceptions when using xCAT response data."""
try:
yield
except (ValueError, TypeError, IndexError, AttributeError,
KeyError) as err:
raise exception.ZVMInvalidXCATResponseDataError(msg=err)
def wrap_invalid_xcat_resp_data_error(function):
"""Catch exceptions when using xCAT response data."""
@functools.wraps(function)
def decorated_function(*arg, **kwargs):
try:
return function(*arg, **kwargs)
except (ValueError, TypeError, IndexError, AttributeError,
KeyError) as err:
raise exception.ZVMInvalidXCATResponseDataError(msg=err)
return decorated_function
@contextlib.contextmanager
def ignore_errors():
"""Only execute the clauses and ignore the results."""
try:
yield
except Exception as err:
LOG.debug(_("Ignore an error: %s") % err)
pass
@contextlib.contextmanager
def except_xcat_call_failed_and_reraise(exc, **kwargs):
"""Catch all kinds of xCAT call failure and reraise.
exc: the exception that would be raised.
"""
try:
yield
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError) as err:
kwargs['msg'] = err
raise exc(**kwargs)
def convert_to_mb(s):
"""Convert memory size from GB to MB."""
s = s.upper()
try:
if s.endswith('G'):
return float(s[:-1].strip()) * 1024
else:
return float(s[:-1].strip())
except (IndexError, ValueError, KeyError, TypeError) as e:
errmsg = _("Invalid memory format: %s") % e
raise exception.ZVMDriverError(msg=errmsg)
@wrap_invalid_xcat_resp_data_error
def translate_xcat_resp(rawdata, dirt):
"""Translate xCAT response JSON stream to a python dictionary.
xCAT response example:
node: keyword1: value1\n
node: keyword2: value2\n
...
node: keywordn: valuen\n
Will return a python dictionary:
{keyword1: value1,
keyword2: value2,
...
keywordn: valuen,}
"""
data_list = rawdata.split("\n")
data = {}
for ls in data_list:
for k in dirt.keys():
if ls.__contains__(dirt[k]):
data[k] = ls[(ls.find(dirt[k]) + len(dirt[k])):].strip()
break
if data == {}:
msg = _("No value matched with keywords. Raw Data: %(raw)s; "
"Keywords: %(kws)s") % {'raw': rawdata, 'kws': str(dirt)}
raise exception.ZVMInvalidXCATResponseDataError(msg=msg)
return data
def mapping_power_stat(power_stat):
"""Translate power state to OpenStack defined constants."""
return const.ZVM_POWER_STAT.get(power_stat, power_state.NOSTATE)
@wrap_invalid_xcat_resp_data_error
def load_xcat_resp(message):
"""Abstract information from xCAT REST response body.
As default, xCAT response will in format of JSON and can be
converted to Python dictionary, would looks like:
{"data": [{"info": [info,]}, {"data": [data,]}, ..., {"error": [error,]}]}
Returns a Python dictionary, looks like:
{'info': [info,],
'data': [data,],
...
'error': [error,]}
"""
resp_list = jsonloads(message)['data']
keys = const.XCAT_RESPONSE_KEYS
resp = {}
for k in keys:
resp[k] = []
for d in resp_list:
for k in keys:
if d.get(k) is not None:
resp[k].append(d.get(k))
err = resp.get('error')
if err != []:
for e in err:
if _is_warning(str(e)):
# ignore known warnings
continue
else:
raise exception.ZVMXCATInternalError(msg=message)
_log_warnings(resp)
return resp
def _log_warnings(resp):
for msg in (resp['info'], resp['node'], resp['data']):
msgstr = str(msg)
if 'warn' in msgstr.lower():
LOG.warn(_("Warning from xCAT: %s") % msgstr)
def _is_warning(err_str):
ignore_list = (
'Warning: the RSA host key for',
'Warning: Permanently added',
'WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED',
)
for im in ignore_list:
if im in err_str:
return True
return False
def volume_in_mapping(mount_device, block_device_info):
block_device_list = [block_device.strip_dev(vol['mount_device'])
for vol in
driver.block_device_info_get_mapping(
block_device_info)]
swap = driver.block_device_info_get_swap(block_device_info)
if driver.swap_is_usable(swap):
block_device_list.append(
block_device.strip_dev(swap['device_name']))
block_device_list += [block_device.strip_dev(ephemeral['device_name'])
for ephemeral in
driver.block_device_info_get_ephemerals(
block_device_info)]
LOG.debug(_("block_device_list %s"), block_device_list)
return block_device.strip_dev(mount_device) in block_device_list
def get_host():
return ''.join([os.environ["USER"], '@', CONF.my_ip])
def get_userid(node_name):
"""Returns z/VM userid for the xCAT node."""
url = XCATUrl().lsdef_node(''.join(['/', node_name]))
info = xcat_request('GET', url)['info']
with expect_invalid_xcat_resp_data():
for s in info[0]:
if s.__contains__('userid='):
return s.strip().rpartition('=')[2]
def xdsh(node, commands):
""""Run command on xCAT node."""
LOG.debug(_('Run command %(cmd)s on xCAT node %(node)s') %
{'cmd': commands, 'node': node})
def xdsh_execute(node, commands):
"""Invoke xCAT REST API to execute command on node."""
xdsh_commands = 'command=%s' % commands
body = [xdsh_commands]
url = XCATUrl().xdsh('/' + node)
return xcat_request("PUT", url, body)
with except_xcat_call_failed_and_reraise(
exception.ZVMXCATXdshFailed):
res_dict = xdsh_execute(node, commands)
return res_dict
def punch_file(node, fn, fclass):
body = [" ".join(['--punchfile', fn, fclass, get_host()])]
url = XCATUrl().chvm('/' + node)
try:
xcat_request("PUT", url, body)
except Exception as err:
with excutils.save_and_reraise_exception():
LOG.error(_('Punch file to %(node)s failed: %(msg)s') %
{'node': node, 'msg': err})
finally:
os.remove(fn)
def punch_adminpass_file(instance_path, instance_name, admin_password):
adminpass_fn = ''.join([instance_path, '/adminpwd.sh'])
_generate_adminpass_file(adminpass_fn, admin_password)
punch_file(instance_name, adminpass_fn, 'X')
def punch_xcat_auth_file(instance_path, instance_name):
"""Make xCAT MN authorized by virtual machines."""
mn_pub_key = get_mn_pub_key()
auth_fn = ''.join([instance_path, '/xcatauth.sh'])
_generate_auth_file(auth_fn, mn_pub_key)
punch_file(instance_name, auth_fn, 'X')
def punch_eph_info_file(instance_path, instance_name, vdev=None, fmt=None,
mntdir=None):
if not fmt:
fmt = CONF.default_ephemeral_format or const.DEFAULT_EPH_DISK_FMT
eph_fn = ''.join([instance_path, '/eph.disk'])
_generate_ephinfo_file(eph_fn, vdev, fmt, mntdir)
punch_file(instance_name, eph_fn, 'X')
def generate_vdev(base, offset=1):
"""Generate virtual device number base on base vdev.
:param base: base virtual device number, string of 4 bit hex.
:param offset: offset to base, integer.
:output: virtual device number, string of 4 bit hex.
"""
vdev = hex(int(base, 16) + offset)[2:]
return vdev.rjust(4, '0')
def generate_eph_vdev(offset=1):
"""Generate virtual device number for ephemeral disks.
:parm offset: offset to zvm_user_adde_vdev.
:output: virtual device number, string of 4 bit hex.
"""
vdev = generate_vdev(CONF.zvm_user_adde_vdev, offset + 1)
if offset >= 0 and offset < 254:
return vdev
else:
msg = _("Invalid virtual device number for ephemeral disk: %s") % vdev
LOG.error(msg)
raise exception.ZVMDriverError(msg=msg)
def _generate_ephinfo_file(fname, vdev, fmt, mntdir):
vdev = vdev or CONF.zvm_user_adde_vdev
mntdir = mntdir or CONF.zvm_default_ephemeral_mntdir
lines = [
'# xCAT Init\n',
'action=addMdisk\n',
'vaddr=' + vdev + '\n',
'filesys=' + fmt + '\n',
'mntdir=' + mntdir + '\n'
]
with open(fname, 'w') as genfile:
try:
genfile.writelines(lines)
except Exception as err:
with excutils.save_and_reraise_exception():
LOG.error(_('Generate ephemeral info file failed: %s') % err)
def _generate_auth_file(fn, pub_key):
lines = ['#!/bin/bash\n',
'echo "%s" >> /root/.ssh/authorized_keys' % pub_key]
with open(fn, 'w') as f:
f.writelines(lines)
def _generate_adminpass_file(fn, admin_password):
lines = ['#! /bin/bash\n',
'echo %s|passwd --stdin root' % admin_password]
with open(fn, 'w') as f:
f.writelines(lines)
@wrap_invalid_xcat_resp_data_error
def get_mn_pub_key():
cmd = 'cat /root/.ssh/id_rsa.pub'
resp = xdsh(CONF.zvm_xcat_master, cmd)
key = resp['data'][0][0]
start_idx = key.find('ssh-rsa')
key = key[start_idx:]
return key
def parse_os_version(os_version):
"""Separate os and version from os_version.
Possible return value are only:
('rhel', x.y) and ('sles', x.y) where x.y may not be digits
"""
supported = {'rhel': ['rhel', 'redhat', 'red hat'],
'sles': ['suse', 'sles']}
os_version = os_version.lower()
for distro, patterns in supported.items():
for i in patterns:
if os_version.startswith(i):
# Not guarrentee the version is digital
return distro, os_version.split(i, 2)[1]
else:
raise exception.ZVMImageError(msg='Unknown os_version property')
class PathUtils(object):
def open(self, path, mode):
"""Wrapper on __builin__.open used to simplify unit testing."""
import __builtin__
return __builtin__.open(path, mode)
def _get_image_tmp_path(self):
image_tmp_path = os.path.normpath(CONF.zvm_image_tmp_path)
if not os.path.exists(image_tmp_path):
LOG.debug(_('Creating folder %s for image temp files') %
image_tmp_path)
os.makedirs(image_tmp_path)
return image_tmp_path
def get_bundle_tmp_path(self, tmp_file_fn):
bundle_tmp_path = os.path.join(self._get_image_tmp_path(), "spawn_tmp",
tmp_file_fn)
if not os.path.exists(bundle_tmp_path):
LOG.debug(_('Creating folder %s for image bundle temp file') %
bundle_tmp_path)
os.makedirs(bundle_tmp_path)
return bundle_tmp_path
def get_img_path(self, bundle_file_path, image_name):
return os.path.join(bundle_file_path, image_name)
def _get_snapshot_path(self):
snapshot_folder = os.path.join(self._get_image_tmp_path(),
"snapshot_tmp")
if not os.path.exists(snapshot_folder):
LOG.debug(_("Creating the snapshot folder %s") % snapshot_folder)
os.makedirs(snapshot_folder)
return snapshot_folder
def _get_punch_path(self):
punch_folder = os.path.join(self._get_image_tmp_path(), "punch_tmp")
if not os.path.exists(punch_folder):
LOG.debug(_("Creating the punch folder %s") % punch_folder)
os.makedirs(punch_folder)
return punch_folder
def _make_short_time_stamp(self):
# tmp_file_fn = time.strftime('%d%H%M%S',
# time.localtime(time.time()))
# return tmp_file_fn
return '0'
def get_punch_time_path(self):
punch_time_path = os.path.join(self._get_punch_path(),
self._make_short_time_stamp())
if not os.path.exists(punch_time_path):
LOG.debug(_("Creating punch time folder %s") % punch_time_path)
os.makedirs(punch_time_path)
return punch_time_path
def get_spawn_folder(self):
spawn_folder = os.path.join(self._get_image_tmp_path(), "spawn_tmp")
if not os.path.exists(spawn_folder):
LOG.debug(_("Creating the spawn folder %s") % spawn_folder)
os.makedirs(spawn_folder)
return spawn_folder
def make_time_stamp(self):
tmp_file_fn = time.strftime('%Y%m%d%H%M%S',
time.localtime(time.time()))
return tmp_file_fn
def get_snapshot_time_path(self):
snapshot_time_path = os.path.join(self._get_snapshot_path(),
self.make_time_stamp())
if not os.path.exists(snapshot_time_path):
LOG.debug(_('Creating folder %s for image bundle temp file') %
snapshot_time_path)
os.makedirs(snapshot_time_path)
return snapshot_time_path
def clean_temp_folder(self, tmp_file_fn):
if os.path.isdir(tmp_file_fn):
LOG.debug(_('Removing existing folder %s '), tmp_file_fn)
shutil.rmtree(tmp_file_fn)
def _get_instances_path(self):
return os.path.normpath(CONF.instances_path)
def get_instance_path(self, os_node, instance_name):
instance_folder = os.path.join(self._get_instances_path(), os_node,
instance_name)
if not os.path.exists(instance_folder):
LOG.debug(_("Creating the instance path %s") % instance_folder)
os.makedirs(instance_folder)
return instance_folder
def get_console_log_path(self, os_node, instance_name):
return os.path.join(self.get_instance_path(os_node, instance_name),
"console.log")
class NetworkUtils(object):
"""Utilities for z/VM network operator."""
def validate_ip_address(self, ip_address):
"""Check whether ip_address is valid."""
# TODO(Leon): check IP address format
pass
def validate_network_mask(self, mask):
"""Check whether mask is valid."""
# TODO(Leon): check network mask format
pass
def create_network_configuration_files(self, file_path, network_info,
base_vdev, os_type):
"""Generate network configuration files to instance."""
device_num = 0
cfg_files = []
cmd_strings = ''
udev_cfg_str = ''
dns_cfg_str = ''
route_cfg_str = ''
cmd_str = None
cfg_str = ''
# Red Hat
file_path_rhel = '/etc/sysconfig/network-scripts/'
# SuSE
file_path_sles = '/etc/sysconfig/network/'
file_name_route = file_path_sles + 'routes'
# Check the OS type
if (os_type == 'sles'):
file_path = file_path_sles
else:
file_path = file_path_rhel
file_name_dns = '/etc/resolv.conf'
for vif in network_info:
file_name = 'ifcfg-eth' + str(device_num)
network = vif['network']
(cfg_str, cmd_str, dns_str, route_str) =\
self.generate_network_configration(network,
base_vdev, device_num, os_type)
LOG.debug(_('Network configure file content is: %s') % cfg_str)
target_net_conf_file_name = file_path + file_name
cfg_files.append((target_net_conf_file_name, cfg_str))
udev_cfg_str += self.generate_udev_configuration(device_num,
'0.0.' + str(base_vdev).zfill(4))
if cmd_str is not None:
cmd_strings += cmd_str
if len(dns_str) > 0:
dns_cfg_str += dns_str
if len(route_str) > 0:
route_cfg_str += route_str
base_vdev = str(hex(int(base_vdev, 16) + 3))[2:]
device_num += 1
if len(dns_cfg_str) > 0:
cfg_files.append((file_name_dns, dns_cfg_str))
if os_type == 'sles':
udev_file_name = '/etc/udev/rules.d/70-persistent-net.rules'
cfg_files.append((udev_file_name, udev_cfg_str))
if len(route_cfg_str) > 0:
cfg_files.append((file_name_route, route_cfg_str))
return cfg_files, cmd_strings
def generate_network_configration(self, network, vdev, device_num,
os_type):
"""Generate network configuration items."""
ip_v4 = netmask_v4 = gateway_v4 = broadcast_v4 = ''
subchannels = None
device = None
cidr_v4 = None
cmd_str = None
dns_str = ''
route_str = ''
subnets_v4 = [s for s in network['subnets'] if s['version'] == 4]
if len(subnets_v4[0]['ips']) > 0:
ip_v4 = subnets_v4[0]['ips'][0]['address']
if len(subnets_v4[0]['dns']) > 0:
for dns in subnets_v4[0]['dns']:
dns_str += 'nameserver ' + dns['address'] + '\n'
netmask_v4 = str(subnets_v4[0].as_netaddr().netmask)
gateway_v4 = subnets_v4[0]['gateway']['address']
broadcast_v4 = str(subnets_v4[0].as_netaddr().broadcast)
device = "eth" + str(device_num)
address_read = str(vdev).zfill(4)
address_write = str(hex(int(vdev, 16) + 1))[2:].zfill(4)
address_data = str(hex(int(vdev, 16) + 2))[2:].zfill(4)
subchannels = '0.0.%s' % address_read.lower()
subchannels += ',0.0.%s' % address_write.lower()
subchannels += ',0.0.%s' % address_data.lower()
cfg_str = 'DEVICE=\"' + device + '\"\n' + 'BOOTPROTO=\"static\"\n'
cfg_str += 'BROADCAST=\"' + broadcast_v4 + '\"\n'
cfg_str += 'GATEWAY=\"' + gateway_v4 + '\"\nIPADDR=\"' + ip_v4 + '\"\n'
cfg_str += 'NETMASK=\"' + netmask_v4 + '\"\n'
cfg_str += 'NETTYPE=\"qeth\"\nONBOOT=\"yes\"\n'
cfg_str += 'PORTNAME=\"PORT' + address_read + '\"\n'
cfg_str += 'OPTIONS=\"layer2=1\"\n'
cfg_str += 'SUBCHANNELS=\"' + subchannels + '\"\n'
if os_type == 'sles':
cidr_v4 = self._get_cidr_from_ip_netmask(ip_v4, netmask_v4)
cmd_str = 'qeth_configure -l 0.0.%s ' % address_read.lower()
cmd_str += '0.0.%(write)s 0.0.%(data)s 1\n' % {'write':
address_write.lower(), 'data': address_data.lower()}
cfg_str = "BOOTPROTO=\'static\'\nIPADDR=\'%s\'\n" % cidr_v4
cfg_str += "BROADCAST=\'%s\'\n" % broadcast_v4
cfg_str += "STARTMODE=\'onboot\'\n"
cfg_str += "NAME=\'OSA Express Network card (%s)\'\n" %\
address_read
route_str += 'default %s - -\n' % gateway_v4
return cfg_str, cmd_str, dns_str, route_str
def generate_udev_configuration(self, device, dev_channel):
cfg_str = 'SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"qeth\",'
cfg_str += ' KERNELS==\"%s\", ATTR{type}==\"1\",' % dev_channel
cfg_str += ' KERNEL==\"eth*\", NAME=\"eth%s\"\n' % device
return cfg_str
def _get_cidr_from_ip_netmask(self, ip, netmask):
netmask_fields = netmask.split('.')
bin_str = ''
for octet in netmask_fields:
bin_str += bin(int(octet))[2:].zfill(8)
mask = str(len(bin_str.rstrip('0')))
cidr_v4 = ip + '/' + mask
return cidr_v4

View File

@ -0,0 +1,45 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 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 abc
from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class ZVMBaseVIFDriver(object):
@abc.abstractmethod
def plug(self, instance, vif):
pass
@abc.abstractmethod
def unplug(self, instance, vif):
pass
class ZVMNeutronVIFDriver(ZVMBaseVIFDriver):
"""Neutron VIF driver."""
def plug(self, instance, vif):
# Neutron takes care of plugging the port
pass
def unplug(self, instance, vif):
# Neutron takes care of unplugging the port
pass

View File

@ -0,0 +1,774 @@
# Copyright 2013 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 contextlib
import os
from random import randint
import re
import time
from oslo.config import cfg
import nova.context
from nova.objects import block_device as block_device_obj
from nova.objects import instance as instance_obj
from nova.openstack.common.gettextutils import _
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.virt.zvm import exception
from nova.virt.zvm import utils as zvmutils
from nova import volume
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class VolumeOperator(object):
"""Volume operator on IBM z/VM platform."""
def __init__(self):
self._svc_driver = SVCDriver()
def init_host(self, host_stats):
try:
self._svc_driver.init_host(host_stats)
except (exception.ZVMDriverError, exception.ZVMVolumeError) as err:
LOG.warning(_("Initialize zhcp failed. Reason: %s") % err)
def attach_volume_to_instance(self, context, connection_info, instance,
mountpoint, is_active, rollback=True):
"""Attach a volume to an instance."""
if None in [connection_info, instance, is_active]:
errmsg = _("Missing required parameters.")
raise exception.ZVMDriverError(msg=errmsg)
LOG.debug(_("Attach a volume to an instance. conn_info: %(info)s; " +
"instance: %(name)s; mountpoint: %(point)s") %
{'info': connection_info, 'name': instance['name'],
'point': mountpoint})
if is_active:
self._svc_driver.attach_volume_active(context, connection_info,
instance, mountpoint,
rollback)
else:
self._svc_driver.attach_volume_inactive(context, connection_info,
instance, mountpoint,
rollback)
def detach_volume_from_instance(self, connection_info, instance,
mountpoint, is_active, rollback=True):
"""Detach a volume from an instance."""
if None in [connection_info, instance, is_active]:
errmsg = _("Missing required parameters.")
raise exception.ZVMDriverError(msg=errmsg)
LOG.debug(_("Detach a volume from an instance. conn_info: %(info)s; " +
"instance: %(name)s; mountpoint: %(point)s") %
{'info': connection_info, 'name': instance['name'],
'point': mountpoint})
if is_active:
self._svc_driver.detach_volume_active(connection_info, instance,
mountpoint, rollback)
else:
self._svc_driver.detach_volume_inactive(connection_info, instance,
mountpoint, rollback)
def get_volume_connector(self, instance):
if not instance:
errmsg = _("Instance must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
return self._svc_driver.get_volume_connector(instance)
def has_persistent_volume(self, instance):
if not instance:
errmsg = _("Instance must be provided.")
raise exception.ZVMDriverError(msg=errmsg)
return self._svc_driver.has_persistent_volume(instance)
@contextlib.contextmanager
def wrap_internal_errors():
"""Wrap internal exceptions to ZVMVolumeError."""
try:
yield
except exception.ZVMBaseException:
raise
except Exception as err:
raise exception.ZVMVolumeError(msg=err)
class DriverAPI(object):
"""DriverAPI for implement volume_attach on IBM z/VM platform."""
def init_host(self, host_stats):
"""Initialize host environment."""
raise NotImplementedError
def get_volume_connector(self, instance):
"""Get volume connector for current driver."""
raise NotImplementedError
def attach_volume_active(self, context, connection_info, instance,
mountpoint, rollback):
"""Attach a volume to an running instance."""
raise NotImplementedError
def detach_volume_active(self, connection_info, instance, mountpoint,
rollback):
"""Detach a volume from an running instance."""
raise NotImplementedError
def attach_volume_inactive(self, context, connection_info, instance,
mountpoint, rollback):
"""Attach a volume to an shutdown instance."""
raise NotImplementedError
def detach_volume_inactive(self, connection_info, instance, mountpoint,
rollback):
"""Detach a volume from an shutdown instance."""
raise NotImplementedError
def has_persistent_volume(self, instance):
"""Decide if the specified instance has persistent volumes attached."""
raise NotImplementedError
class SVCDriver(DriverAPI):
"""SVC volume operator on IBM z/VM platform."""
def __init__(self):
self._xcat_url = zvmutils.XCATUrl()
self._path_utils = zvmutils.PathUtils()
self._host = CONF.zvm_host
self._pool_name = CONF.zvm_scsi_pool
self._fcp_pool = set()
self._instance_fcp_map = {}
self._is_instance_fcp_map_locked = False
self._volume_api = volume.API()
self._actions = {'attach_volume': 'addScsiVolume',
'detach_volume': 'removeScsiVolume',
'create_mountpoint': 'createfilesysnode',
'remove_mountpoint': 'removefilesysnode'}
self._RESERVE = 0
self._INCREASE = 1
self._DECREASE = 2
self._REMOVE = 3
def init_host(self, host_stats):
"""Initialize host environment."""
if not host_stats:
errmsg = _("Can not obtain host stats.")
raise exception.ZVMDriverError(msg=errmsg)
zhcp_fcp_list = CONF.zvm_zhcp_fcp_list
fcp_devices = self._expand_fcp_list(zhcp_fcp_list)
hcpnode = host_stats[0]['zhcp']['nodename']
for _fcp in fcp_devices:
with zvmutils.ignore_errors():
self._attach_device(hcpnode, _fcp)
with zvmutils.ignore_errors():
self._online_device(hcpnode, _fcp)
fcp_list = CONF.zvm_fcp_list
if (fcp_list is None):
errmsg = _("At least one fcp list should be given")
LOG.error(errmsg)
raise exception.ZVMVolumeError(msg=errmsg)
self._init_fcp_pool(fcp_list)
def _init_fcp_pool(self, fcp_list):
"""Map all instances and their fcp devices, and record all free fcps.
One instance should use only one fcp device so far.
"""
self._fcp_pool = self._expand_fcp_list(fcp_list)
self._instance_fcp_map = {}
# Any other functions should not modify _instance_fcp_map during
# FCP pool initialization
self._is_instance_fcp_map_locked = True
compute_host_bdms = self._get_host_volume_bdms()
for instance_bdms in compute_host_bdms:
instance_name = instance_bdms['instance']['name']
for _bdm in instance_bdms['instance_bdms']:
connection_info = self._build_connection_info(_bdm)
try:
_fcp = connection_info['data']['zvm_fcp']
if _fcp and _fcp in self._fcp_pool:
self._update_instance_fcp_map(instance_name, _fcp,
self._INCREASE)
if _fcp and _fcp not in self._fcp_pool:
errmsg = _("FCP device %s is not configured but " +
"is used by %s.") % (_fcp, instance_name)
LOG.warning(errmsg)
except (TypeError, KeyError):
pass
for _key in self._instance_fcp_map.keys():
fcp = self._instance_fcp_map.get(_key)['fcp']
self._fcp_pool.remove(fcp)
self._is_instance_fcp_map_locked = False
def _update_instance_fcp_map_if_unlocked(self, instance_name, fcp, action):
while self._is_instance_fcp_map_locked:
time.sleep(1)
self._update_instance_fcp_map(instance_name, fcp, action)
def _update_instance_fcp_map(self, instance_name, fcp, action):
fcp = fcp.lower()
if instance_name in self._instance_fcp_map:
# One instance should use only one fcp device so far
current_fcp = self._instance_fcp_map.get(instance_name)['fcp']
if fcp != current_fcp:
errmsg = _("Instance %(ins_name)s has multiple FCP devices " +
"attached! FCP1: %(fcp1)s, FCP2: %(fcp2)s"
) % {'ins_name': instance_name, 'fcp1': fcp,
'fcp2': current_fcp}
LOG.warning(errmsg)
return
if action == self._RESERVE:
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
if count > 0:
errmsg = _("Try to reserve a fcp device which already " +
"has volumes attached on: %(ins_name)s:%(fcp)s"
) % {'ins_name': instance_name, 'fcp': fcp}
LOG.warning(errmsg)
else:
new_item = {instance_name: {'fcp': fcp, 'count': 0}}
self._instance_fcp_map.update(new_item)
elif action == self._INCREASE:
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
new_item = {instance_name: {'fcp': fcp, 'count': count + 1}}
self._instance_fcp_map.update(new_item)
else:
new_item = {instance_name: {'fcp': fcp, 'count': 1}}
self._instance_fcp_map.update(new_item)
elif action == self._DECREASE:
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
if count > 0:
new_item = {instance_name: {'fcp': fcp,
'count': count - 1}}
self._instance_fcp_map.update(new_item)
else:
fcp = self._instance_fcp_map[instance_name]['fcp']
self._instance_fcp_map.pop(instance_name)
self._fcp_pool.add(fcp)
else:
errmsg = _("Try to decrease an inexistent map item: " +
"%(ins_name)s:%(fcp)s"
) % {'ins_name': instance_name, 'fcp': fcp}
LOG.warning(errmsg)
elif action == self._REMOVE:
if instance_name in self._instance_fcp_map:
count = self._instance_fcp_map[instance_name]['count']
if count > 0:
errmsg = _("Try to remove a map item while some volumes " +
"are still attached on: %(ins_name)s:%(fcp)s"
) % {'ins_name': instance_name, 'fcp': fcp}
LOG.warning(errmsg)
else:
fcp = self._instance_fcp_map[instance_name]['fcp']
self._instance_fcp_map.pop(instance_name)
self._fcp_pool.add(fcp)
else:
errmsg = _("Try to remove an inexistent map item: " +
"%(ins_name)s:%(fcp)s"
) % {'ins_name': instance_name, 'fcp': fcp}
LOG.warning(errmsg)
else:
errmsg = _("Unrecognized option: %s") % action
LOG.warning(errmsg)
def _get_host_volume_bdms(self):
"""Return all block device mappings on a compute host."""
compute_host_bdms = []
instances = self._get_all_instances()
for instance in instances:
instance_bdms = self._get_instance_bdms(instance)
compute_host_bdms.append(dict(instance=instance,
instance_bdms=instance_bdms))
return compute_host_bdms
def _get_all_instances(self):
context = nova.context.get_admin_context()
return instance_obj.InstanceList.get_by_host(context, self._host)
def _get_instance_bdms(self, instance):
context = nova.context.get_admin_context()
instance_bdms = [bdm for bdm in
(block_device_obj.BlockDeviceMappingList.
get_by_instance_uuid(context, instance['uuid']))
if bdm.is_volume]
return instance_bdms
def has_persistent_volume(self, instance):
return bool(self._get_instance_bdms(instance))
def _build_connection_info(self, bdm):
try:
connection_info = jsonutils.loads(bdm['connection_info'])
return connection_info
except (TypeError, KeyError, ValueError):
return None
def get_volume_connector(self, instance):
try:
fcp = self._instance_fcp_map.get(instance['name'])['fcp']
except Exception:
fcp = None
if not fcp:
fcp = self._get_fcp_from_pool()
if fcp:
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp, self._RESERVE)
if not fcp:
errmsg = _("No available FCP device found.")
LOG.error(errmsg)
raise exception.ZVMVolumeError(msg=errmsg)
fcp = fcp.lower()
wwpn = self._get_wwpn(fcp)
if not wwpn:
errmsg = _("FCP device %s has no available WWPN.") % fcp
LOG.error(errmsg)
raise exception.ZVMVolumeError(msg=errmsg)
wwpn = wwpn.lower()
return {'zvm_fcp': fcp, 'wwpns': [wwpn], 'host': CONF.zvm_host}
def _get_wwpn(self, fcp):
states = ['active', 'free']
for _state in states:
fcps_info = self._list_fcp_details(_state)
if not fcps_info:
continue
wwpn = self._extract_wwpn_from_fcp_info(fcp, fcps_info)
if wwpn:
return wwpn
def _list_fcp_details(self, state):
fields = '&field=--fcpdevices&field=' + state + '&field=details'
rsp = self._xcat_rinv(fields)
try:
fcp_details = rsp['info'][0][0].splitlines()
return fcp_details
except (TypeError, KeyError):
return None
def _extract_wwpn_from_fcp_info(self, fcp, fcps_info):
"""The FCP infomation would look like this:
host: FCP device number: xxxx
host: Status: Active
host: NPIV world wide port number: xxxxxxxx
host: Channel path ID: xx
host: Physical world wide port number: xxxxxxxx
......
host: FCP device number: xxxx
host: Status: Active
host: NPIV world wide port number: xxxxxxxx
host: Channel path ID: xx
host: Physical world wide port number: xxxxxxxx
"""
lines_per_item = 5
num_fcps = len(fcps_info) / lines_per_item
fcp = fcp.upper()
for _cur in range(0, num_fcps):
# Find target FCP device
if fcp not in fcps_info[_cur * lines_per_item]:
continue
# Try to get NPIV WWPN first
wwpn_info = fcps_info[(_cur + 1) * lines_per_item - 3]
wwpn = self._get_wwpn_from_line(wwpn_info)
if not wwpn:
# Get physical WWPN if NPIV WWPN is none
wwpn_info = fcps_info[(_cur + 1) * lines_per_item - 1]
wwpn = self._get_wwpn_from_line(wwpn_info)
return wwpn
def _get_wwpn_from_line(self, info_line):
wwpn = info_line.split(':')[-1].strip()
if wwpn and wwpn.upper() != 'NONE':
return wwpn
else:
return None
def _get_fcp_from_pool(self):
if self._fcp_pool:
return self._fcp_pool.pop()
self._init_fcp_pool(CONF.zvm_fcp_list)
if self._fcp_pool:
return self._fcp_pool.pop()
def _extract_connection_info(self, context, connection_info):
with wrap_internal_errors():
LOG.debug(_("Extract connection_info: %s") % connection_info)
lun = connection_info['data']['target_lun']
lun = "%04x000000000000" % int(lun)
wwpn = connection_info['data']['target_wwn']
size = '0G'
# There is no context in detach case
if context:
volume_id = connection_info['data']['volume_id']
volume = self._get_volume_by_id(context, volume_id)
size = str(volume['size']) + 'G'
fcp = connection_info['data']['zvm_fcp']
return (lun.lower(), wwpn.lower(), size, fcp.lower())
def _get_volume_by_id(self, context, volume_id):
volume = self._volume_api.get(context, volume_id)
return volume
def attach_volume_active(self, context, connection_info, instance,
mountpoint, rollback=True):
"""Attach a volume to an running instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(context,
connection_info)
try:
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._INCREASE)
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
self._add_zfcp(instance, fcp, wwpn, lun, size)
if mountpoint:
self._create_mountpoint(instance, fcp, wwpn, lun, mountpoint)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._DECREASE)
do_detach = not self._is_fcp_in_use(instance, fcp)
if rollback:
with zvmutils.ignore_errors():
self._remove_mountpoint(instance, mountpoint)
with zvmutils.ignore_errors():
self._remove_zfcp(instance, fcp, wwpn, lun)
with zvmutils.ignore_errors():
self._remove_zfcp_from_pool(wwpn, lun)
with zvmutils.ignore_errors():
if do_detach:
self._detach_device(instance['name'], fcp)
raise
def detach_volume_active(self, connection_info, instance, mountpoint,
rollback=True):
"""Detach a volume from an running instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(None,
connection_info)
try:
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._DECREASE)
do_detach = not self._is_fcp_in_use(instance, fcp)
if mountpoint:
self._remove_mountpoint(instance, mountpoint)
self._remove_zfcp(instance, fcp, wwpn, lun)
self._remove_zfcp_from_pool(wwpn, lun)
if do_detach:
self._detach_device(instance['name'], fcp)
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp, self._REMOVE)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._INCREASE)
if rollback:
with zvmutils.ignore_errors():
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
with zvmutils.ignore_errors():
self._add_zfcp(instance, fcp, wwpn, lun, size)
if mountpoint:
with zvmutils.ignore_errors():
self._create_mountpoint(instance, fcp, wwpn, lun,
mountpoint)
raise
def attach_volume_inactive(self, context, connection_info, instance,
mountpoint, rollback=True):
"""Attach a volume to an shutdown instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(context,
connection_info)
try:
do_attach = not self._is_fcp_in_use(instance, fcp)
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._INCREASE)
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
self._allocate_zfcp(instance, fcp, size, wwpn, lun)
self._notice_attach(instance, fcp, wwpn, lun, mountpoint)
if do_attach:
self._attach_device(instance['name'], fcp)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._DECREASE)
do_detach = not self._is_fcp_in_use(instance, fcp)
if rollback:
with zvmutils.ignore_errors():
self._notice_detach(instance, fcp, wwpn, lun, mountpoint)
with zvmutils.ignore_errors():
self._remove_zfcp_from_pool(wwpn, lun)
with zvmutils.ignore_errors():
if do_detach:
self._detach_device(instance['name'], fcp)
self._update_instance_fcp_map_if_unlocked(
instance['name'], fcp, self._REMOVE)
raise
def detach_volume_inactive(self, connection_info, instance, mountpoint,
rollback=True):
"""Detach a volume from an shutdown instance."""
(lun, wwpn, size, fcp) = self._extract_connection_info(None,
connection_info)
try:
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._DECREASE)
do_detach = not self._is_fcp_in_use(instance, fcp)
self._remove_zfcp(instance, fcp, wwpn, lun)
self._remove_zfcp_from_pool(wwpn, lun)
self._notice_detach(instance, fcp, wwpn, lun, mountpoint)
if do_detach:
self._detach_device(instance['name'], fcp)
self._update_instance_fcp_map_if_unlocked(instance['name'],
fcp, self._REMOVE)
except (exception.ZVMXCATRequestFailed,
exception.ZVMInvalidXCATResponseDataError,
exception.ZVMXCATInternalError,
exception.ZVMVolumeError):
self._update_instance_fcp_map_if_unlocked(instance['name'], fcp,
self._INCREASE)
if rollback:
with zvmutils.ignore_errors():
self._attach_device(instance['name'], fcp)
with zvmutils.ignore_errors():
self._notice_attach(instance, fcp, wwpn, lun, mountpoint)
with zvmutils.ignore_errors():
self._add_zfcp_to_pool(fcp, wwpn, lun, size)
with zvmutils.ignore_errors():
self._allocate_zfcp(instance, fcp, size, wwpn, lun)
raise
def _expand_fcp_list(self, fcp_list):
"""Expand fcp list string into a python list object which contains
each fcp devices in the list string. A fcp list is composed of fcp
device addresses, range indicator '-', and split indicator ';'.
For example, if fcp_list is
"0011-0013;0015;0017-0018", expand_fcp_list(fcp_list) will return
[0011, 0012, 0013, 0015, 0017, 0018].
"""
LOG.debug(_("Expand FCP list %s") % fcp_list)
if not fcp_list:
return set()
range_pattern = '[0-9a-fA-F]{1,4}(-[0-9a-fA-F]{1,4})?'
match_pattern = "^(%(range)s)(;%(range)s)*$" % {'range': range_pattern}
if not re.match(match_pattern, fcp_list):
errmsg = _("Invalid FCP address %s") % fcp_list
raise exception.ZVMDriverError(msg=errmsg)
fcp_devices = set()
for _range in fcp_list.split(';'):
if '-' not in _range:
# single device
fcp_addr = int(_range, 16)
fcp_devices.add("%04x" % fcp_addr)
else:
# a range of address
(_min, _max) = _range.split('-')
_min = int(_min, 16)
_max = int(_max, 16)
for fcp_addr in range(_min, _max + 1):
fcp_devices.add("%04x" % fcp_addr)
# remove duplicate entries
return fcp_devices
def _attach_device(self, node, addr, mode='0'):
"""Attach a device to a node."""
body = [' '.join(['--dedicatedevice', addr, addr, mode])]
self._xcat_chvm(node, body)
def _detach_device(self, node, vdev):
"""Detach a device from a node."""
body = [' '.join(['--undedicatedevice', vdev])]
self._xcat_chvm(node, body)
def _online_device(self, node, dev):
"""After attaching a device to a node, the device should be made
online before it being in use.
"""
body = ["command=cio_ignore -r %s" % dev]
self._xcat_xdsh(node, body)
body = ["command=chccwdev -e %s" % dev]
self._xcat_xdsh(node, body)
def _is_fcp_in_use(self, instance, fcp):
if instance['name'] in self._instance_fcp_map:
count = self._instance_fcp_map.get(instance['name'])['count']
if count > 0:
return True
return False
def _notice_attach(self, instance, fcp, wwpn, lun, mountpoint):
file_path = self._get_file_for_punch(instance)
# Create and send volume file
action = self._actions['attach_volume']
lines = self._get_volume_lines(action, fcp, wwpn, lun)
self._send_notice(instance, file_path, lines)
# Create and send mount point file
action = self._actions['create_mountpoint']
lines = self._get_mountpoint_lines(action, fcp, wwpn, lun, mountpoint)
self._send_notice(instance, file_path, lines)
def _notice_detach(self, instance, fcp, wwpn, lun, mountpoint):
file_path = self._get_file_for_punch(instance)
# Create and send volume file
action = self._actions['detach_volume']
lines = self._get_volume_lines(action, fcp, wwpn, lun)
self._send_notice(instance, file_path, lines)
# Create and send mount point file
action = self._actions['remove_mountpoint']
lines = self._get_mountpoint_lines(action, fcp, wwpn, lun, mountpoint)
self._send_notice(instance, file_path, lines)
def _get_volume_lines(self, action, fcp, wwpn, lun):
comments = '# xCAT Init\n'
action_line = "action=%s\n" % action
fcp_line = "fcpAddr=%s\n" % fcp
wwpn_line = "wwpn=%s\n" % wwpn
lun_line = "lun=%s\n" % lun
return [comments, action_line, fcp_line, wwpn_line, lun_line]
def _get_mountpoint_lines(self, action, fcp, wwpn, lun, mountpoint):
comments = '# xCAT Init\n'
action_line = "action=%s\n" % action
mountpoint_line = "tgtFile=%s\n" % mountpoint
if action == self._actions['create_mountpoint']:
path = self._get_zfcp_path_pattern()
srcdev = path % {'fcp': fcp, 'wwpn': wwpn, 'lun': lun}
srcdev_line = "srcFile=%s\n" % srcdev
return [comments, action_line, mountpoint_line, srcdev_line]
else:
return [comments, action_line, mountpoint_line]
def _get_file_for_punch(self, instance):
dir_path = self._path_utils.get_punch_time_path()
file_name = "%08x.disk" % randint(0, int('FFFFFFFF', 16))
file_path = os.path.join(dir_path, file_name)
return file_path
def _send_notice(self, instance, file_path, lines):
with wrap_internal_errors():
with open(file_path, 'w') as f:
f.writelines(lines)
# zvmutils.punch_file will remove the file after punching
zvmutils.punch_file(instance['name'], file_path, 'X')
def _add_zfcp_to_pool(self, fcp, wwpn, lun, size):
body = [' '.join(['--addzfcp2pool', self._pool_name, 'free', wwpn,
lun, size, fcp])]
self._xcat_chhy(body)
def _remove_zfcp_from_pool(self, wwpn, lun):
body = [' '.join(['--removezfcpfrompool', CONF.zvm_scsi_pool, lun,
wwpn])]
self._xcat_chhy(body)
def _add_zfcp(self, instance, fcp, wwpn, lun, size):
body = [' '.join(['--addzfcp', CONF.zvm_scsi_pool, fcp, str(0), size,
str(0), wwpn, lun])]
self._xcat_chvm(instance['name'], body)
def _remove_zfcp(self, instance, fcp, wwpn, lun):
body = [' '.join(['--removezfcp', fcp, wwpn, lun, '1'])]
self._xcat_chvm(instance['name'], body)
def _create_mountpoint(self, instance, fcp, wwpn, lun, mountpoint):
path = self._get_zfcp_path_pattern()
srcdev = path % {'fcp': fcp, 'wwpn': wwpn, 'lun': lun}
body = [" ".join(['--createfilesysnode', srcdev, mountpoint])]
self._xcat_chvm(instance['name'], body)
def _remove_mountpoint(self, instance, mountpoint):
body = [' '.join(['--removefilesysnode', mountpoint])]
self._xcat_chvm(instance['name'], body)
def _allocate_zfcp(self, instance, fcp, size, wwpn, lun):
body = [" ".join(['--reservezfcp', CONF.zvm_scsi_pool, 'used',
instance['name'], fcp, size, wwpn, lun])]
self._xcat_chhy(body)
def _xcat_chvm(self, node, body):
url = self._xcat_url.chvm('/' + node)
zvmutils.xcat_request('PUT', url, body)
def _xcat_chhy(self, body):
url = self._xcat_url.chhv('/' + self._host)
zvmutils.xcat_request('PUT', url, body)
def _xcat_xdsh(self, node, body):
url = self._xcat_url.xdsh('/' + node)
zvmutils.xcat_request('PUT', url, body)
def _xcat_rinv(self, fields):
url = self._xcat_url.rinv('/' + self._host, fields)
return zvmutils.xcat_request('GET', url)
def _get_zfcp_path_pattern(self):
return '/dev/disk/by-path/ccw-0.0.%(fcp)s-zfcp-0x%(wwpn)s:0x%(lun)s'