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:
parent
14a3ea6ab2
commit
cf77385929
9
.gitignore
vendored
Executable file
9
.gitignore
vendored
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
*.egg*
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
.tox
|
||||||
|
.venv
|
||||||
|
build/*
|
||||||
|
dist/*
|
0
.gitreview
Normal file → Executable file
0
.gitreview
Normal file → Executable file
5
README.rst
Normal file
5
README.rst
Normal 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
|
0
neutron-zvm-plugin/neutron/plugins/zvm/__init__.py
Executable file
0
neutron-zvm-plugin/neutron/plugins/zvm/__init__.py
Executable file
0
neutron-zvm-plugin/neutron/plugins/zvm/agent/__init__.py
Executable file
0
neutron-zvm-plugin/neutron/plugins/zvm/agent/__init__.py
Executable file
62
neutron-zvm-plugin/neutron/plugins/zvm/agent/zvm_network.py
Executable file
62
neutron-zvm-plugin/neutron/plugins/zvm/agent/zvm_network.py
Executable 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
|
350
neutron-zvm-plugin/neutron/plugins/zvm/agent/zvm_neutron_agent.py
Executable file
350
neutron-zvm-plugin/neutron/plugins/zvm/agent/zvm_neutron_agent.py
Executable 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)
|
0
neutron-zvm-plugin/neutron/plugins/zvm/common/__init__.py
Executable file
0
neutron-zvm-plugin/neutron/plugins/zvm/common/__init__.py
Executable file
66
neutron-zvm-plugin/neutron/plugins/zvm/common/config.py
Executable file
66
neutron-zvm-plugin/neutron/plugins/zvm/common/config.py
Executable 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)
|
17
neutron-zvm-plugin/neutron/plugins/zvm/common/constants.py
Executable file
17
neutron-zvm-plugin/neutron/plugins/zvm/common/constants.py
Executable 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')
|
43
neutron-zvm-plugin/neutron/plugins/zvm/common/exception.py
Executable file
43
neutron-zvm-plugin/neutron/plugins/zvm/common/exception.py
Executable 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')
|
415
neutron-zvm-plugin/neutron/plugins/zvm/common/utils.py
Executable file
415
neutron-zvm-plugin/neutron/plugins/zvm/common/utils.py
Executable 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]
|
205
neutron-zvm-plugin/neutron/plugins/zvm/common/xcatutils.py
Executable file
205
neutron-zvm-plugin/neutron/plugins/zvm/common/xcatutils.py
Executable 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
|
48
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_network.py
Executable file
48
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_network.py
Executable 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)
|
227
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_neutron_agent.py
Executable file
227
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_neutron_agent.py
Executable 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")
|
471
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_utils.py
Executable file
471
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_utils.py
Executable 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)
|
41
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_xcatutils.py
Executable file
41
neutron-zvm-plugin/neutron/tests/unit/zvm/test_zvm_xcatutils.py
Executable 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()
|
2936
nova-zvm-virt-driver/nova/tests/test_zvm.py
Executable file
2936
nova-zvm-virt-driver/nova/tests/test_zvm.py
Executable file
File diff suppressed because it is too large
Load Diff
31
nova-zvm-virt-driver/nova/virt/zvm/__init__.py
Executable file
31
nova-zvm-virt-driver/nova/virt/zvm/__init__.py
Executable 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
|
65
nova-zvm-virt-driver/nova/virt/zvm/configdrive.py
Executable file
65
nova-zvm-virt-driver/nova/virt/zvm/configdrive.py
Executable 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()
|
68
nova-zvm-virt-driver/nova/virt/zvm/const.py
Executable file
68
nova-zvm-virt-driver/nova/virt/zvm/const.py
Executable 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
|
1889
nova-zvm-virt-driver/nova/virt/zvm/driver.py
Executable file
1889
nova-zvm-virt-driver/nova/virt/zvm/driver.py
Executable file
File diff suppressed because it is too large
Load Diff
80
nova-zvm-virt-driver/nova/virt/zvm/exception.py
Executable file
80
nova-zvm-virt-driver/nova/virt/zvm/exception.py
Executable 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')
|
800
nova-zvm-virt-driver/nova/virt/zvm/imageop.py
Executable file
800
nova-zvm-virt-driver/nova/virt/zvm/imageop.py
Executable 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
|
609
nova-zvm-virt-driver/nova/virt/zvm/instance.py
Executable file
609
nova-zvm-virt-driver/nova/virt/zvm/instance.py
Executable 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
|
207
nova-zvm-virt-driver/nova/virt/zvm/networkop.py
Executable file
207
nova-zvm-virt-driver/nova/virt/zvm/networkop.py
Executable 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)
|
830
nova-zvm-virt-driver/nova/virt/zvm/utils.py
Executable file
830
nova-zvm-virt-driver/nova/virt/zvm/utils.py
Executable 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
|
45
nova-zvm-virt-driver/nova/virt/zvm/vif.py
Executable file
45
nova-zvm-virt-driver/nova/virt/zvm/vif.py
Executable 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
|
774
nova-zvm-virt-driver/nova/virt/zvm/volumeop.py
Executable file
774
nova-zvm-virt-driver/nova/virt/zvm/volumeop.py
Executable 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'
|
Loading…
x
Reference in New Issue
Block a user