fuel-plugin-xenserver/plugin_source/deployment_scripts/compute_post_deployment.py

497 lines
19 KiB
Python
Executable File

#!/usr/bin/env python
import ConfigParser
from distutils.version import LooseVersion
import netifaces
import os
import re
import shutil
from socket import inet_ntoa
import stat
from struct import pack
import utils
from utils import HIMN_IP
INT_BRIDGE = 'br-int'
XS_PLUGIN_ISO = 'xenapi-plugins-mitaka.iso'
CONNTRACK_CONF_SAMPLE =\
'/usr/share/doc/conntrack-tools-1.4.2/doc/stats/conntrackd.conf'
utils.setup_logging('compute_post_deployment.log')
LOG = utils.LOG
def get_endpoints(astute):
"""Return the IP addresses of the storage/mgmt endpoints."""
endpoints = astute['network_scheme']['endpoints']
endpoints = dict([(
k.replace('br-', ''),
endpoints[k]['IP'][0]
) for k in endpoints])
LOG.info('storage network: {storage}'.format(**endpoints))
LOG.info('mgmt network: {mgmt}'.format(**endpoints))
return endpoints
def install_xenapi_sdk():
"""Install XenAPI Python SDK"""
utils.execute('cp', 'XenAPI.py', utils.DIST_PACKAGES_DIR)
def create_novacompute_conf(himn, username, password, public_ip, services_ssl):
"""Fill nova-compute.conf with HIMN IP and root password. """
mgmt_if = netifaces.ifaddresses('br-mgmt')
if mgmt_if and mgmt_if.get(netifaces.AF_INET) \
and mgmt_if.get(netifaces.AF_INET)[0]['addr']:
mgmt_ip = mgmt_if.get(netifaces.AF_INET)[0]['addr']
else:
utils.reportError('Cannot get IP Address on Management Network')
filename = '/etc/nova/nova-compute.conf'
cf = ConfigParser.ConfigParser()
try:
cf.read(filename)
cf.set('DEFAULT', 'compute_driver', 'xenapi.XenAPIDriver')
cf.set('DEFAULT', 'force_config_drive', 'True')
if not cf.has_section('vnc'):
cf.add_section('vnc')
scheme = "https" if services_ssl else "http"
cf.set('vnc', 'novncproxy_base_url',
'%s://%s:6080/vnc_auto.html' % (scheme, public_ip))
cf.set('vnc', 'vncserver_proxyclient_address', mgmt_ip)
if not cf.has_section('xenserver'):
cf.add_section('xenserver')
cf.set('xenserver', 'connection_url', 'http://%s' % himn)
cf.set('xenserver', 'connection_username', username)
cf.set('xenserver', 'connection_password', password)
cf.set('xenserver', 'vif_driver',
'nova.virt.xenapi.vif.XenAPIOpenVswitchDriver')
cf.set('xenserver', 'ovs_integration_bridge', INT_BRIDGE)
cf.set('xenserver', 'cache_images', 'all')
with open(filename, 'w') as configfile:
cf.write(configfile)
except Exception:
utils.reportError('Cannot set configurations to %s' % filename)
LOG.info('%s created' % filename)
def route_to_compute(endpoints, himn_xs, himn_local, username):
"""Route storage/mgmt requests to compute nodes. """
def _net(ip):
return '.'.join(ip.split('.')[:-1] + ['0'])
def _mask(cidr):
return inet_ntoa(pack('>I', 0xffffffff ^ (1 << 32 - int(cidr)) - 1))
def _routed(net, mask, gw):
return re.search(r'%s\s+%s\s+%s\s+' % (
net.replace('.', r'\.'),
gw.replace('.', r'\.'),
mask
), out)
out = utils.ssh(himn_xs, username, 'route', '-n')
utils.ssh(himn_xs, username,
('printf "#!/bin/bash\nsleep 5\n" >'
'/etc/udev/scripts/reroute.sh'))
endpoint_names = ['storage', 'mgmt']
for endpoint_name in endpoint_names:
endpoint = endpoints.get(endpoint_name)
if endpoint:
ip, cidr = endpoint.split('/')
net, mask = _net(ip), _mask(cidr)
if not _routed(net, mask, himn_local):
params = ['route', 'add', '-net', '"%s"' % net, 'netmask',
'"%s"' % mask, 'gw', himn_local]
utils.ssh(himn_xs, username, *params)
# Always add the route to the udev, even if it's currently active
cmd = (
"printf 'if !(/sbin/route -n | /bin/grep -q -F \"{net}\"); "
"then\n"
"/sbin/route add -net \"{net}\" netmask "
"\"{mask}\" gw {himn_local};\n"
"fi\n' >> /etc/udev/scripts/reroute.sh"
)
cmd = cmd.format(net=net, mask=mask, himn_local=himn_local)
utils.ssh(himn_xs, username, cmd)
else:
LOG.info('%s network ip is missing' % endpoint_name)
utils.ssh(himn_xs, username, 'chmod +x /etc/udev/scripts/reroute.sh')
utils.ssh(himn_xs, username,
('echo \'SUBSYSTEM=="net" ACTION=="add" '
'KERNEL=="xenapi" RUN+="/etc/udev/scripts/reroute.sh"\' '
'> /etc/udev/rules.d/90-reroute.rules'))
def install_suppack(himn, username, package, xcp_version):
"""Install xapi driver supplemental pack. """
tmp = utils.ssh(himn, username, 'mktemp', '-d')
real_pack = "xcp_%s/%s" % (xcp_version, package)
if not os.path.exists(real_pack):
utils.reportError('Package folder %s not exist' % real_pack)
utils.scp(himn, username, tmp, real_pack)
utils.ssh(himn, username, 'xe-install-supplemental-pack',
tmp + '/' + package, prompt='Y\n')
utils.ssh(himn, username, 'rm', tmp, '-rf')
def forward_from_himn(eth):
"""Forward packets from HIMN to storage/mgmt network. """
# make change to be persistent
utils.execute('sed', '-i',
's/.*net\.ipv4\.ip_forward.*=.*/net.ipv4.ip_forward=1/g',
'/etc/sysctl.conf')
# make it to take effective now.
utils.execute('sysctl', 'net.ipv4.ip_forward=1')
endpoint_names = ['br-storage', 'br-mgmt']
for endpoint_name in endpoint_names:
utils.execute('iptables', '-t', 'nat', '-A', 'POSTROUTING',
'-o', endpoint_name, '-j', 'MASQUERADE')
utils.execute('iptables', '-A', 'FORWARD',
'-i', endpoint_name, '-o', eth,
'-m', 'state', '--state', 'RELATED,ESTABLISHED',
'-j', 'ACCEPT')
utils.execute('iptables', '-A', 'FORWARD',
'-i', eth, '-o', endpoint_name,
'-j', 'ACCEPT')
utils.execute('iptables', '-A', 'INPUT', '-i', eth, '-j', 'ACCEPT')
utils.execute('iptables', '-t', 'filter', '-S', 'FORWARD')
utils.execute('iptables', '-t', 'nat', '-S', 'POSTROUTING')
utils.execute('service', 'iptables-persistent', 'save')
def forward_port(eth_in, eth_out, target_host, target_port):
"""Forward packets from eth_in to eth_out on target_host:target_port. """
utils.execute('iptables', '-t', 'nat', '-A', 'PREROUTING',
'-i', eth_in, '-p', 'tcp', '--dport', target_port,
'-j', 'DNAT', '--to', target_host)
utils.execute('iptables', '-A', 'FORWARD',
'-i', eth_out, '-o', eth_in,
'-m', 'state', '--state', 'RELATED,ESTABLISHED',
'-j', 'ACCEPT')
utils.execute('iptables', '-A', 'FORWARD',
'-i', eth_in, '-o', eth_out,
'-j', 'ACCEPT')
utils.execute('iptables', '-t', 'filter', '-S', 'FORWARD')
utils.execute('iptables', '-t', 'nat', '-S', 'POSTROUTING')
utils.execute('service', 'iptables-persistent', 'save')
def install_logrotate_script(himn, username):
"Install console logrotate script"
utils.scp(himn, username, '/root/', 'rotate_xen_guest_logs.sh')
utils.ssh(himn, username, 'mkdir -p /var/log/xen/guest')
utils.ssh(himn, username, '''crontab - << CRONTAB
* * * * * /root/rotate_xen_guest_logs.sh >/dev/null 2>&1
CRONTAB''')
def install_image_cache_cleanup():
tool_path = '/usr/bin/destroy_cached_images'
tool_conf = '/etc/nova/nova-compute.conf'
# install this tool.
try:
src_file = 'tools/destroy_cached_images.py'
target_file = tool_path
shutil.copy(src_file, target_file)
os.chown(target_file, 0, 0)
os.chmod(target_file, stat.S_IRWXU)
except Exception:
utils.reportError("Failed to install file %s" % target_file)
# create a daily clean-up cron job
cron_entry = '5 3 * * * {} --config-file={} >/dev/null 2>&1'.format(
tool_path,
tool_conf)
user = 'root'
utils.add_cron_job(user, cron_entry)
LOG.info('Added crontab successfully: %s' % cron_entry)
def modify_neutron_rootwrap_conf(himn, username, password):
"""Set xenapi configurations"""
filename = '/etc/neutron/rootwrap.conf'
cf = ConfigParser.ConfigParser()
try:
cf.read(filename)
cf.set('xenapi', 'xenapi_connection_url', 'http://%s' % himn)
cf.set('xenapi', 'xenapi_connection_username', username)
cf.set('xenapi', 'xenapi_connection_password', password)
with open(filename, 'w') as configfile:
cf.write(configfile)
except Exception:
utils.reportError("Fail to modify file %s", filename)
LOG.info('Modify file %s successfully', filename)
def modify_neutron_ovs_agent_conf(int_br, br_mappings):
filename = '/etc/neutron/plugins/ml2/openvswitch_agent.ini'
cf = ConfigParser.ConfigParser()
try:
cf.read(filename)
cf.set('agent', 'root_helper',
'neutron-rootwrap-xen-dom0 /etc/neutron/rootwrap.conf')
cf.set('agent', 'root_helper_daemon', '')
cf.set('agent', 'minimize_polling', False)
cf.set('ovs', 'integration_bridge', int_br)
cf.set('ovs', 'bridge_mappings', br_mappings)
with open(filename, 'w') as configfile:
cf.write(configfile)
except Exception:
utils.reportError("Fail to modify %s", filename)
LOG.info('Modify %s successfully', filename)
def get_private_network_ethX():
# find out ethX in DomU which connect to private network
# br-aux is the auxiliary bridge and in normal case there will be a patch
# between br-prv and br-aux
values = astute['network_scheme']['transformations']
for item in values:
if item['action'] == 'add-port' and item['bridge'] == 'br-aux':
return item['name']
# If cannot find br-aux, the network topo should be public and private
# connect to the same network and "Assign public network to all nodes"
# is checked, we need to use br-ex to find ethX in domU
for item in values:
if item['action'] == 'add-port' and item['bridge'] == 'br-ex':
return item['name']
def find_bridge_mappings(astute, himn, username):
ethX = get_private_network_ethX()
if not ethX:
utils.reportError("Cannot find eth used for private network")
# find the ethX mac in /sys/class/net/ethX/address
with open('/sys/class/net/%s/address' % ethX, 'r') as fo:
mac = fo.readline()
network_uuid = utils.ssh(himn, username,
('xe vif-list params=network-uuid '
'minimal=true MAC=%s') % mac)
bridge = utils.ssh(himn, username,
('xe network-param-get param-name=bridge '
'uuid=%s') % network_uuid)
# find physical network name
phynet_setting = astute['quantum_settings']['L2']['phys_nets']
physnet = phynet_setting.keys()[0]
return physnet + ':' + bridge
def restart_services(service_name):
utils.execute('stop', service_name)
utils.execute('start', service_name)
def enable_linux_bridge(himn, username):
# When using OVS under XS6.5, it will prevent use of Linux bridge in
# Dom0, but neutron-openvswitch-agent in compute node will use Linux
# bridge, so we remove this restriction here
utils.ssh(himn, username, 'rm -f /etc/modprobe.d/blacklist-bridge*')
def patch_ceilometer():
"""Add patches which are not merged to upstream
Order of patches applied:
ceilometer-poll-cpu-util.patch
ceilometer-rates-always-zero.patch
ceilometer-support-network-bytes.patch
ceilometer-add-purge_inspection_cache.patch
"""
patchfile_list = [
'ceilometer-poll-cpu-util.patch',
'ceilometer-rates-always-zero.patch',
'ceilometer-support-network-bytes.patch',
'ceilometer-add-purge_inspection_cache.patch',
]
for patch_file in patchfile_list:
utils.patch(utils.DIST_PACKAGES_DIR, patch_file, 1)
def patch_compute_xenapi():
"""Add patches which are not merged to upstream
Order of patches applied:
support-disable-image-cache.patch
speed-up-config-drive.patch
ovs-interim-bridge.patch
neutron-security-group.patch
live-migration-iscsi.patch
support-vif-hotplug.patch
fix-rescue-vm.patch
live-migration-vifmapping.patch
"""
patchfile_list = [
# Change-Id: I5ebff2c1f7534b06233a4d41d7f5f2e5e3b60b5a
'support-disable-image-cache.patch',
# Change-Id: I359e17d6d5838f4028df0bd47e4825de420eb383
'speed-up-config-drive.patch',
# Change-Id: I0cfc0284e1fcd1a6169d31a7ad410716037e5cc2
'ovs-interim-bridge.patch',
# Change-Id: Id9b39aa86558a9f7099caedabd2d517bf8ad3d68
'neutron-security-group.patch',
# Change-Id: I88d1d384ab7587c428e517d184258bb517dfb4ab
'live-migration-iscsi.patch',
# Change-Id: I22f3fe52d07100592015007653c7f8c47c25d22c
'support-vif-hotplug.patch',
# Change-Id: I32c66733330bc9877caea7e2a2290c02b3906708
'fix-rescue-vm.patch',
# Change-Id: If0fb5d764011521916fbbe15224f524a220052f3
'live-migration-vifmapping.patch',
# TODO(huanxie): below patch isn't merged into upstream yet,
# it only affects XS7.1 and later
# Change-Id: I31850b25e2f32eb65a00fbb824b08646c9ed340a
'assert_can_migrated.patch',
]
for patch_file in patchfile_list:
utils.patch(utils.DIST_PACKAGES_DIR, patch_file, 1)
def patch_neutron_ovs_agent():
"""Apply neutron patch
Add conntrack-tools patch to support conntrack in Dom0
"""
utils.patch('/usr/bin', 'fix-xenapi-returncode.patch', 2)
def reconfig_multipath():
"""Ignore local disks for multipathd
Change devnode rule from "^hd[a-z]" to "^(hd|xvd)[a-z]"
"""
multipath_conf = '/etc/multipath.conf'
if os.path.exists(multipath_conf):
utils.execute('sed', '-i', r's/"\^hd\[a-z\]"/"^(hd|xvd)[a-z]"/',
multipath_conf)
else:
with open(multipath_conf, "w") as f:
f.write('# Generated by %s:\n' % utils.PLUGIN_NAME)
f.write('blacklist {\ndevnode "^(hd|xvd)[a-z]"\n}')
utils.execute('service', 'multipath-tools', 'restart')
def check_and_setup_ceilometer(himn, username, password):
"""Set xenapi configuration for ceilometer service"""
filename = '/etc/ceilometer/ceilometer.conf'
if not os.path.exists(filename):
utils.reportError("The file: %s doesn't exist" % filename)
return
patch_ceilometer()
cf = ConfigParser.ConfigParser()
try:
cf.read(filename)
cf.set('DEFAULT', 'hypervisor_inspector', 'xenapi')
cf.set('xenapi', 'connection_url', 'http://%s' % himn)
cf.set('xenapi', 'connection_username', username)
cf.set('xenapi', 'connection_password', password)
with open(filename, 'w') as configfile:
cf.write(configfile)
LOG.info('Modify file %s successfully', filename)
except Exception:
utils.reportError("Fail to modify file %s", filename)
return
restart_services('ceilometer-polling')
def enable_conntrack_service(himn, username, xcp_version):
if LooseVersion(xcp_version) < LooseVersion('2.1.0'):
# Only support conntrack-tools since XS7.0(XCP2.1.0) and above
LOG.info('No need to enable conntrack-tools with XCP %s' % xcp_version)
return
# use conntrack statistic mode, so change conntrackd.conf
if not os.path.exists('/etc/conntrackd/conntrackd.conf.back'):
utils.ssh(himn, username,
'mv',
'/etc/conntrackd/conntrackd.conf',
'/etc/conntrackd/conntrackd.conf.back')
utils.ssh(himn, username,
'cp',
CONNTRACK_CONF_SAMPLE,
'/etc/conntrackd/conntrackd.conf')
# Rotate log file for conntrack
utils.scp(himn, username,
'/etc/logrotate.d', 'etc/logrotate.d/conntrackd')
# Restart conntrackd service
utils.ssh(himn, username, 'service', 'conntrackd', 'restart')
def get_xcp_version(himn, username):
xcp_ver = utils.ssh(himn, username,
('xe host-param-get uuid=$(xe host-list --minimal) '
'param-name=software-version '
'param-key=platform_version'))
return xcp_ver
if __name__ == '__main__':
install_xenapi_sdk()
astute = utils.get_astute()
if astute:
username, password, install_xapi = utils.get_options(astute)
endpoints = get_endpoints(astute)
himn_eth, himn_local = utils.init_eth()
public_ip = utils.astute_get(
astute, ('network_metadata', 'vips', 'public', 'ipaddr'))
services_ssl = utils.astute_get(
astute, ('public_ssl', 'services'))
if username and password and endpoints and himn_local:
route_to_compute(endpoints, HIMN_IP, himn_local, username)
xcp_version = get_xcp_version(HIMN_IP, username)
if install_xapi:
install_suppack(HIMN_IP, username, XS_PLUGIN_ISO, xcp_version)
enable_linux_bridge(HIMN_IP, username)
forward_from_himn(himn_eth)
# port forwarding for novnc
forward_port('br-mgmt', himn_eth, HIMN_IP, '80')
create_novacompute_conf(HIMN_IP, username, password, public_ip,
services_ssl)
patch_compute_xenapi()
restart_services('nova-compute')
install_logrotate_script(HIMN_IP, username)
# enable conntrackd service in Dom0
enable_conntrack_service(HIMN_IP, username, xcp_version)
# neutron-l2-agent in compute node
modify_neutron_rootwrap_conf(HIMN_IP, username, password)
br_mappings = find_bridge_mappings(astute, HIMN_IP, username)
modify_neutron_ovs_agent_conf(INT_BRIDGE, br_mappings)
patch_neutron_ovs_agent()
restart_services('neutron-openvswitch-agent')
reconfig_multipath()
# Add xenapi specific setup for ceilometer if service is enabled.
is_ceilometer_enabled = utils.astute_get(astute,
('ceilometer', 'enabled'))
if is_ceilometer_enabled:
check_and_setup_ceilometer(HIMN_IP, username, password)
else:
LOG.info('Skip ceilomter setup as this service is '
'disabled.')
install_image_cache_cleanup()