blob: 3d138df801032641fd60dad80203ef1e4105da1a [file] [log] [blame]
import os
import shutil
import re
from novaclient import client as nova_client
from SSHTestAgent import SSHTestAgent
from CordTestUtils import *
from CordTestUtils import log_test as log
log.setLevel('INFO')
class VSGAccess(object):
vcpe_map = {}
interface_map = {}
ip_addr_pattern = re.compile('[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$')
@classmethod
def setUp(cls):
try:
shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.orig')
except:
pass
@classmethod
def tearDown(cls):
try:
shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
except:
pass
@classmethod
def get_nova_credentials_v2(cls):
credential = {}
credential['username'] = os.environ['OS_USERNAME']
credential['api_key'] = os.environ['OS_PASSWORD']
credential['auth_url'] = os.environ['OS_AUTH_URL']
credential['project_id'] = os.environ['OS_TENANT_NAME']
return credential
@classmethod
def get_compute_nodes(cls):
credentials = cls.get_nova_credentials_v2()
nvclient = nova_client.Client('2', **credentials)
return nvclient.hypervisors.list()
@classmethod
def get_vsgs(cls, active = True):
credentials = cls.get_nova_credentials_v2()
nvclient = nova_client.Client('2', **credentials)
vsgs = nvclient.servers.list(search_opts = {'all_tenants': 1})
if active is True:
vsgs = filter(lambda vsg: vsg.status == 'ACTIVE', vsgs)
vsg_wrappers = []
for vsg in vsgs:
vsg_wrappers.append(VSGWrapper(vsg))
return vsg_wrappers
@classmethod
def open_mgmt(cls, intf = 'eth0'):
if intf in cls.interface_map:
gw = cls.interface_map[intf]['gw']
ip = cls.interface_map[intf]['ip']
if gw != '0.0.0.0':
current_gw, _ = get_default_gw()
cmds = [ 'route del default gw {}'.format(current_gw),
'ifconfig {} {} up'.format(intf, ip),
'route add default gw {}'.format(gw) ]
for cmd in cmds:
os.system(cmd)
shutil.copy('/etc/resolv.conf', '/etc/resolv.conf.lastdhcp')
shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
return current_gw
return None
@classmethod
def close_mgmt(cls, restore_gw, intf = 'eth0'):
if restore_gw:
cmds = [ 'route del default gw 0.0.0.0',
'route add default gw {}'.format(restore_gw),
'cp /etc/resolv.conf.lastdhcp /etc/resolv.conf',
'rm -f /etc/resolv.conf.lastdhcp'
]
for cmd in cmds:
os.system(cmd)
@classmethod
def health_check(cls):
'''Returns 0 if all active vsgs are reachable through the compute node'''
vsgs = cls.get_vsgs()
vsg_status = []
for vsg in vsgs:
vsg_status.append(vsg.get_health())
unreachable = filter(lambda st: st == False, vsg_status)
return len(unreachable) == 0
@classmethod
def get_vcpe_vsg(cls, vcpe):
'''Find the vsg hosting the vcpe service'''
if vcpe in cls.vcpe_map:
return cls.vcpe_map[vcpe]['vsg']
vsgs = cls.get_vsgs()
for vsg in vsgs:
cmd = 'sudo docker exec {} ls 2>/dev/null'.format(vcpe)
st, _ = vsg.run_cmd(cmd, timeout = 30)
if st == True:
return vsg
return None
@classmethod
def save_vcpe_config(cls, vsg, vcpe):
if vcpe not in cls.vcpe_map:
cmd_gw = "sudo docker exec %s ip route show | grep default | head -1 | awk '{print $3}'" %(vcpe)
vsg_ip = vsg.ip
if vsg_ip is None:
return False
st, output = vsg.run_cmd(cmd_gw, timeout = 30)
if st == False or not output:
return False
gw = output
cmd_wan = "sudo docker exec %s ip addr show eth0 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe)
cmd_lan = "sudo docker exec %s ip addr show eth1 |grep inet |head -1 | tr -s ' ' | awk '{print $2}' | awk '{print $1}'" %(vcpe)
st, output = vsg.run_cmd(cmd_wan, timeout = 30)
ip_wan = '0.0.0.0/24'
ip_lan = '0.0.0.0/24'
if st and output:
if cls.ip_addr_pattern.match(output):
ip_wan = output
st, output = vsg.run_cmd(cmd_lan, timeout = 30)
if st and output:
if cls.ip_addr_pattern.match(output):
ip_lan = output
cls.vcpe_map[vcpe] = { 'vsg': vsg, 'gw': gw, 'wan': ip_wan, 'lan': ip_lan }
return True
@classmethod
def restore_vcpe_config(cls, vcpe, gw = True, wan = False, lan = False):
if vcpe in cls.vcpe_map:
vsg = cls.vcpe_map[vcpe]['vsg']
cmds = []
if gw is True:
#restore default gw
gw = cls.vcpe_map[vcpe]['gw']
cmds.append('sudo docker exec {} ip link set eth0 up'.format(vcpe))
cmds.append('sudo docker exec {} route add default gw {} dev eth0'.format(vcpe, gw))
if wan is True:
ip_wan = cls.vcpe_map[vcpe]['wan']
cmds.append('sudo docker exec {} ip addr set {} dev eth0'.format(vcpe, ip_wan))
if lan is True:
ip_lan = cls.vcpe_map[vcpe]['lan']
cmds.append('sudo docker exec {} ip addr set {} dev eth1'.format(vcpe, ip_lan))
ret_status = True
for cmd in cmds:
st, _ = vsg.run_cmd(cmd, timeout = 30)
if st == False:
ret_status = False
return ret_status
return False
@classmethod
def get_vcpe_gw(cls, vcpe):
if vcpe in cls.vcpe_map:
return cls.vcpe_map[vcpe]['gw']
return None
@classmethod
def get_vcpe_wan(cls, vcpe):
if vcpe in cls.vcpe_map:
return cls.vcpe_map[vcpe]['wan']
return None
@classmethod
def get_vcpe_lan(cls, vcpe):
if vcpe in cls.vcpe_map:
return cls.vcpe_map[vcpe]['lan']
return None
@classmethod
def vcpe_wan_up(cls, vcpe):
return cls.restore_vcpe_config(vcpe)
@classmethod
def vcpe_lan_up(cls, vcpe, vsg = None):
if vsg is None:
vsg = cls.get_vcpe_vsg(vcpe)
if vsg is None:
return False
cmd = 'sudo docker exec {} ip link set eth1 up'.format(vcpe)
st, _ = vsg.run_cmd(cmd, timeout = 30)
return st
#we cannot access compute node if the vcpe port gets dhcp as default would be through fabric
@classmethod
def vcpe_port_down(cls, vcpe, port, vsg = None):
if vsg is None:
vsg = cls.get_vcpe_vsg(vcpe)
if vsg is None:
return False
if not cls.save_vcpe_config(vsg, vcpe):
return False
cmd = 'sudo docker exec {} ip link set {} down'.format(vcpe, port)
st, _ = vsg.run_cmd(cmd, timeout = 30)
if st is False:
cls.restore_vcpe_config(vcpe)
return False
return st
@classmethod
def vcpe_wan_down(cls, vcpe, vsg = None):
return cls.vcpe_port_down(vcpe, 'eth0', vsg = vsg)
@classmethod
def vcpe_lan_down(cls, vcpe, vsg = None):
return cls.vcpe_port_down(vcpe, 'eth1', vsg = vsg)
@classmethod
def save_interface_config(cls, intf):
if intf not in cls.interface_map:
ip = get_ip(intf)
if ip is None:
ip = '0.0.0.0'
default_gw, default_gw_device = get_default_gw()
if default_gw_device != intf:
default_gw = '0.0.0.0'
cls.interface_map[intf] = { 'ip' : ip, 'gw': default_gw }
#bounce the interface to remove default gw
cmds = ['ifconfig {} 0 down'.format(intf),
'ifconfig {} 0 up'.format(intf)
]
for cmd in cmds:
os.system(cmd)
#open up access to compute node
@classmethod
def restore_interface_config(cls, intf, vcpe = None):
if intf in cls.interface_map:
ip = cls.interface_map[intf]['ip']
gw = cls.interface_map[intf]['gw']
del cls.interface_map[intf]
cmds = []
if vcpe is not None:
shutil.copy('/etc/resolv.conf.orig', '/etc/resolv.conf')
#bounce the vcpes to clear default gw
cmds.append('ifconfig {} 0 down'.format(vcpe))
cmds.append('ifconfig {} 0 up'.format(vcpe))
cmds.append('ifconfig {} {} up'.format(intf, ip))
if gw and gw != '0.0.0.0':
cmds.append('route add default gw {} dev {}'.format(gw, intf))
for cmd in cmds:
os.system(cmd)
@classmethod
def vcpe_get_dhcp(cls, vcpe, mgmt = 'eth0'):
'''Get DHCP from vcpe dhcp interface.'''
'''We have to also save the management interface config for restoration'''
cls.save_interface_config(mgmt)
getstatusoutput('pkill -9 dhclient')
st, output = getstatusoutput('dhclient -q {}'.format(vcpe))
getstatusoutput('pkill -9 dhclient')
vcpe_ip = get_ip(vcpe)
if vcpe_ip is None:
cls.restore_interface_config(mgmt)
return None
if output:
#workaround for docker container apparmor that prevents moving dhclient resolv.conf
start = output.find('/etc/resolv.conf')
if start >= 0:
end = output.find("'", start)
dns_file = output[start:end]
if os.access(dns_file, os.F_OK):
shutil.copy(dns_file, '/etc/resolv.conf')
default_gw, default_gw_device = get_default_gw()
if default_gw and default_gw_device == vcpe:
return vcpe_ip
cls.restore_interface_config(mgmt, vcpe = vcpe)
return None
class VSGWrapper(object):
def __init__(self, vsg):
self.vsg = vsg
self.name = self.vsg.name
self.compute_node = self.get_compute_node()
self.ip = self.get_ip()
def get_compute_node(self):
return self.vsg._info['OS-EXT-SRV-ATTR:hypervisor_hostname']
def get_ip(self):
if 'management' in self.vsg.networks:
ips = self.vsg.networks['management']
if len(ips) > 0:
return ips[0]
return None
def run_cmd_compute(self, cmd, timeout = 5):
ssh_agent = SSHTestAgent(self.compute_node)
st, output = ssh_agent.run_cmd(cmd, timeout = timeout)
if st == True and output:
output = output.strip()
else:
output = None
return st, output
def run_cmd(self, cmd, timeout = 5, mgmt = 'eth0'):
last_gw = VSGAccess.open_mgmt(mgmt)
ssh_agent = SSHTestAgent(self.compute_node)
ssh_cmd = 'ssh {} {}'.format(self.ip, cmd)
st, output = ssh_agent.run_cmd(ssh_cmd, timeout = timeout)
if st == True and output:
output = output.strip()
else:
output = None
VSGAccess.close_mgmt(last_gw, mgmt)
return st, output
def get_health(self):
if self.ip is None:
return True
cmd = 'ping -c 1 {}'.format(self.ip)
log.info('Pinging VSG %s at IP %s' %(self.name, self.ip))
st, _ = self.run_cmd_compute(cmd)
log.info('VSG %s at IP %s is %s' %(self.name, self.ip, 'reachable' if st == True else 'unreachable'))
return st
def check_access(self):
if self.ip is None:
return True
ssh_agent = SSHTestAgent(self.compute_node)
st, _ = ssh_agent.run_cmd('ls', timeout=10)
if st == False:
log.error('Compute node at %s is not accessible' %(self.compute_node))
return st
log.info('Checking if VSG at %s is accessible from compute node %s' %(self.ip, self.compute_node))
st, _ = ssh_agent.run_cmd('ssh {} ls'.format(self.ip), timeout=30)
if st == True:
log.info('OK')
return st