![Huang Rui](/assets/img/avatar_default.png)
Including all python modules for nova-zvm-virt-driver and neutron-zvm-plugin. Change-Id: I72dd9f64fc412cbf10f5e7ab6e4ac465a977e849
831 lines
27 KiB
Python
Executable File
831 lines
27 KiB
Python
Executable File
# 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
|