330 lines
11 KiB
Python
Executable File
330 lines
11 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import os
|
|
from logging import debug, info, warning, DEBUG, basicConfig
|
|
from subprocess import Popen, PIPE
|
|
import yaml
|
|
from shutil import rmtree
|
|
from tempfile import mkstemp, mkdtemp
|
|
from socket import inet_ntoa
|
|
from struct import pack
|
|
import netifaces
|
|
import re
|
|
|
|
|
|
ASTUTE_PATH = '/etc/astute.yaml'
|
|
ASTUTE_SECTION = 'fuel-plugin-xenserver'
|
|
LOG_ROOT = '/var/log/fuel-plugin-xenserver'
|
|
LOG_FILE = 'compute_post_deployment.log'
|
|
HIMN_IP = '169.254.0.1'
|
|
|
|
if not os.path.exists(LOG_ROOT):
|
|
os.mkdir(LOG_ROOT)
|
|
|
|
basicConfig(filename=os.path.join(LOG_ROOT, LOG_FILE), level=DEBUG)
|
|
|
|
|
|
def reportError(err):
|
|
warning(err)
|
|
raise Exception(err)
|
|
|
|
|
|
def execute(*cmd, **kwargs):
|
|
cmd = map(str, cmd)
|
|
info(' '.join(cmd))
|
|
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
|
|
|
if 'prompt' in kwargs:
|
|
prompt = kwargs.get('prompt')
|
|
proc.stdout.flush()
|
|
(out, err) = proc.communicate(prompt)
|
|
else:
|
|
out = proc.stdout.readlines()
|
|
err = proc.stderr.readlines()
|
|
(out, err) = map(' '.join, [out, err])
|
|
(out, err) = (out.replace('\n', ''), err.replace('\n', ''))
|
|
|
|
if out:
|
|
debug(out)
|
|
|
|
if proc.returncode is not None and proc.returncode != 0:
|
|
reportError(err)
|
|
|
|
return out
|
|
|
|
|
|
def ssh(host, username, password, *cmd, **kwargs):
|
|
cmd = map(str, cmd)
|
|
|
|
return execute('sshpass', '-p', password, 'ssh',
|
|
'-o', 'StrictHostKeyChecking=no',
|
|
'%s@%s' % (username, host), *cmd,
|
|
prompt=kwargs.get('prompt'))
|
|
|
|
|
|
def scp(host, username, password, target_path, filename):
|
|
return execute('sshpass', '-p', password, 'scp',
|
|
'-o', 'StrictHostKeyChecking=no', filename,
|
|
'%s@%s:%s' % (username, host, target_path))
|
|
|
|
|
|
def get_astute(astute_path):
|
|
"""Return the root object read from astute.yaml"""
|
|
if not os.path.exists(astute_path):
|
|
reportError('%s not found' % astute_path)
|
|
|
|
astute = yaml.load(open(astute_path))
|
|
return astute
|
|
|
|
|
|
def astute_get(dct, keys, default=None, fail_if_missing=True):
|
|
"""A safe dictionary getter"""
|
|
for key in keys:
|
|
if key in dct:
|
|
dct = dct[key]
|
|
else:
|
|
if fail_if_missing:
|
|
reportError('Value of "%s" is missing' % key)
|
|
return default
|
|
return dct
|
|
|
|
|
|
def get_options(astute, astute_section):
|
|
"""Return username and password filled in plugin."""
|
|
if not astute_section in astute:
|
|
reportError('%s not found' % astute_section)
|
|
|
|
options = astute[astute_section]
|
|
info('username: {username}'.format(**options))
|
|
info('password: {password}'.format(**options))
|
|
info('install_xapi: {install_xapi}'.format(**options))
|
|
return options['username'], options['password'], \
|
|
options['install_xapi']
|
|
|
|
|
|
def get_endpoints(astute):
|
|
"""Return the IP addresses of the endpoints connected to
|
|
storage/mgmt network.
|
|
"""
|
|
endpoints = astute['network_scheme']['endpoints']
|
|
endpoints = dict([(
|
|
k.replace('br-', ''),
|
|
endpoints[k]['IP'][0]
|
|
) for k in endpoints])
|
|
|
|
info('storage network: {storage}'.format(**endpoints))
|
|
info('mgmt network: {mgmt}'.format(**endpoints))
|
|
return endpoints
|
|
|
|
|
|
def init_eth():
|
|
"""Initialize the net interface connected to HIMN
|
|
|
|
Returns:
|
|
the IP addresses of local host and XenServer.
|
|
"""
|
|
|
|
domid = execute('xenstore-read', 'domid')
|
|
himn_mac = execute(
|
|
'xenstore-read',
|
|
'/local/domain/%s/vm-data/himn_mac' % domid)
|
|
info('himn_mac: %s' % himn_mac)
|
|
|
|
_mac = lambda eth: \
|
|
netifaces.ifaddresses(eth).get(netifaces.AF_LINK)[0]['addr']
|
|
eths = [eth for eth in netifaces.interfaces() if _mac(eth) == himn_mac]
|
|
if len(eths) != 1:
|
|
reportError('Cannot find eth matches himn_mac')
|
|
|
|
eth = eths[0]
|
|
info('himn_eth: %s' % eth)
|
|
|
|
ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET)
|
|
|
|
if not ip:
|
|
execute('dhclient', eth)
|
|
fname = '/etc/network/interfaces.d/ifcfg-' + eth
|
|
s = ('auto {eth}\n'
|
|
'iface {eth} inet dhcp\n'
|
|
'post-up route del default dev {eth}').format(eth=eth)
|
|
with open(fname, 'w') as f:
|
|
f.write(s)
|
|
info('%s created' % fname)
|
|
execute('ifdown', eth)
|
|
execute('ifup', eth)
|
|
ip = netifaces.ifaddresses(eth).get(netifaces.AF_INET)
|
|
|
|
if ip:
|
|
himn_local = ip[0]['addr']
|
|
himn_xs = '.'.join(himn_local.split('.')[:-1] + ['1'])
|
|
if HIMN_IP == himn_xs:
|
|
info('himn_local: %s' % himn_local)
|
|
return eth, himn_local
|
|
|
|
reportError('HIMN failed to get IP address from XenServer')
|
|
|
|
|
|
def check_hotfix_exists(himn, username, password, hotfix):
|
|
out = ssh(HIMN_IP, username, password,
|
|
'xe patch-list name-label=%s' % hotfix)
|
|
if not out:
|
|
reportError('Hotfix %s has not been installed' % hotfix)
|
|
|
|
|
|
def install_xenapi_sdk():
|
|
"""Install XenAPI Python SDK"""
|
|
execute('cp', 'XenAPI.py', '/usr/lib/python2.7/dist-packages/')
|
|
|
|
|
|
def create_novacompute_conf(himn, username, password, public_ip):
|
|
"""Fill nova-compute.conf with HIMN IP and root password. """
|
|
template = '\n'.join([
|
|
'[DEFAULT]',
|
|
'compute_driver=xenapi.XenAPIDriver',
|
|
'force_config_drive=always',
|
|
'novncproxy_base_url=https://%s:6080/vnc_auto.html',
|
|
'vncserver_proxyclient_address=%s',
|
|
'[xenserver]',
|
|
'connection_url=http://%s',
|
|
'connection_username="%s"',
|
|
'connection_password="%s"'
|
|
])
|
|
|
|
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:
|
|
reportError('Cannot get IP Address on Management Network')
|
|
|
|
s = template % (public_ip, mgmt_ip, himn, username, password)
|
|
fname = '/etc/nova/nova-compute.conf'
|
|
with open(fname, 'w') as f:
|
|
f.write(s)
|
|
info('%s created' % fname)
|
|
|
|
|
|
def restart_nova_services():
|
|
"""Restart nova services"""
|
|
execute('stop', 'nova-compute')
|
|
execute('start', 'nova-compute')
|
|
execute('stop', 'nova-network')
|
|
execute('start', 'nova-network')
|
|
|
|
|
|
def route_to_compute(endpoints, himn_xs, himn_local, username, password):
|
|
"""Route storage/mgmt requests to compute nodes. """
|
|
out = ssh(himn_xs, username, password, 'route', '-n')
|
|
_net = lambda ip: '.'.join(ip.split('.')[:-1] + ['0'])
|
|
_mask = lambda cidr: inet_ntoa(pack(
|
|
'>I', 0xffffffff ^ (1 << 32 - int(cidr)) - 1))
|
|
_routed = lambda net, mask, gw: re.search(r'%s\s+%s\s+%s\s+' % (
|
|
net.replace('.', r'\.'),
|
|
gw.replace('.', r'\.'),
|
|
mask
|
|
), out)
|
|
|
|
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', net, 'netmask',
|
|
mask, 'gw', himn_local]
|
|
ssh(himn_xs, username, password, *params)
|
|
sh = 'echo \'%s\' >> /etc/sysconfig/static-routes' \
|
|
% ' '.join(params)
|
|
ssh(himn_xs, username, password, sh)
|
|
else:
|
|
info('%s network ip is missing' % endpoint_name)
|
|
|
|
|
|
def install_suppack(himn, username, password):
|
|
"""Install xapi driver supplemental pack. """
|
|
# TODO: check if installed
|
|
scp(himn, username, password, '/tmp/', 'novaplugins-kilo.iso')
|
|
out = ssh(
|
|
himn, username, password, 'xe-install-supplemental-pack',
|
|
'/tmp/novaplugins-kilo.iso', prompt='Y\n')
|
|
ssh(himn, username, password, 'rm', '/tmp/novaplugins-kilo.iso')
|
|
|
|
|
|
def forward_from_himn(eth):
|
|
"""Forward packets from HIMN to storage/mgmt network. """
|
|
execute('sed', '-i', 's/#net.ipv4.ip_forward/net.ipv4.ip_forward/g',
|
|
'/etc/sysctl.conf')
|
|
execute('sysctl', '-p', '/etc/sysctl.conf')
|
|
|
|
endpoint_names = ['br-storage', 'br-mgmt']
|
|
for endpoint_name in endpoint_names:
|
|
execute('iptables', '-t', 'nat', '-A', 'POSTROUTING',
|
|
'-o', endpoint_name, '-j', 'MASQUERADE')
|
|
execute('iptables', '-A', 'FORWARD',
|
|
'-i', endpoint_name, '-o', eth,
|
|
'-m', 'state', '--state', 'RELATED,ESTABLISHED',
|
|
'-j', 'ACCEPT')
|
|
execute('iptables', '-A', 'FORWARD',
|
|
'-i', eth, '-o', endpoint_name,
|
|
'-j', 'ACCEPT')
|
|
|
|
execute('iptables', '-t', 'filter', '-S', 'FORWARD')
|
|
execute('iptables', '-t', 'nat', '-S', 'POSTROUTING')
|
|
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. """
|
|
execute('iptables', '-t', 'nat', '-A', 'PREROUTING',
|
|
'-i', eth_in, '-p', 'tcp', '--dport', target_port,
|
|
'-j', 'DNAT', '--to', target_host)
|
|
execute('iptables', '-A', 'FORWARD',
|
|
'-i', eth_out, '-o', eth_in,
|
|
'-m', 'state', '--state', 'RELATED,ESTABLISHED',
|
|
'-j', 'ACCEPT')
|
|
execute('iptables', '-A', 'FORWARD',
|
|
'-i', eth_in, '-o', eth_out,
|
|
'-j', 'ACCEPT')
|
|
|
|
execute('iptables', '-t', 'filter', '-S', 'FORWARD')
|
|
execute('iptables', '-t', 'nat', '-S', 'POSTROUTING')
|
|
execute('service', 'iptables-persistent', 'save')
|
|
|
|
|
|
def install_logrotate_script(himn, username, password):
|
|
"Install console logrotate script"
|
|
scp(himn, username, password, '/root/', 'rotate_xen_guest_logs.sh')
|
|
ssh(himn, username, password, 'mkdir -p /var/log/xen/guest')
|
|
ssh(himn, username, password, '''crontab - << CRONTAB
|
|
* * * * * /root/rotate_xen_guest_logs.sh
|
|
CRONTAB''')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
install_xenapi_sdk()
|
|
astute = get_astute(ASTUTE_PATH)
|
|
if astute:
|
|
username, password, install_xapi = get_options(astute, ASTUTE_SECTION)
|
|
endpoints = get_endpoints(astute)
|
|
himn_eth, himn_local = init_eth()
|
|
|
|
public_ip = astute_get(
|
|
astute, ('network_metadata', 'vips', 'public', 'ipaddr'))
|
|
|
|
if username and password and endpoints and himn_local:
|
|
check_hotfix_exists(HIMN_IP, username, password, 'XS65ESP1013')
|
|
route_to_compute(
|
|
endpoints, HIMN_IP, himn_local, username, password)
|
|
if install_xapi:
|
|
install_suppack(HIMN_IP, username, password)
|
|
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)
|
|
restart_nova_services()
|
|
|
|
install_logrotate_script(HIMN_IP, username, password)
|