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