blob: e179d29054710edb26f5b672c29b9b80cfb16c06 [file] [log] [blame]
# Copyright 2017-present Open Networking Foundation
#
# 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 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
'''
@method: get_nova_credentials_v2
@Description: Get nova credentials
@params:
returns credential from env
'''
@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
'''
@method: get_compute_nodes
@Description: Get the list of compute nodes
@params:
returns node list
'''
@classmethod
def get_compute_nodes(cls):
credentials = cls.get_nova_credentials_v2()
nvclient = nova_client.Client('2', **credentials)
return nvclient.hypervisors.list()
'''
@method: get_vsgs
@Description: Get list of vsg's running in compute node
@params: status of vsg
returns vsg wrappers
'''
@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
'''
@method: open_mgmt
@Description: Bringing up Interface for access to management
@params: intf = "Interface to open"
returns Gateway
'''
@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
'''
@method: close_mgmt
@Description: Bringing up gateway deleting default
@params: intf = "Interface to open"
dict2 = retrieved data from GET method
returns: NA
'''
@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)
'''
@method: health_check
@Description: Check if vsgs are reachable
@params:
returns True
'''
@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
'''
@method: get_vcpe_vsg
@Description: Getting vsg vm instance info from given vcpe
@params: vcpe = "vcpe name"
returns vsg
'''
@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
'''
@method: save_vcpe_config
@Description: Saving vcpe config with lan & wan side info
@params: vsg
vcpe
returns True
'''
@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
'''
@method: restore_vcpe_config
@Description: Restoring saved config for lan & wan
@params: vcpe
gw
wan
lan
returns True/False
'''
@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
'''
@method: get_vcpe_gw
@Description: Get gw of vcpe from created map
@params: vcpe
returns gw
'''
@classmethod
def get_vcpe_gw(cls, vcpe):
if vcpe in cls.vcpe_map:
return cls.vcpe_map[vcpe]['gw']
return None
'''
@method: get_vcpe_wan
@Description:
@params:
return wan side of vcpe
'''
@classmethod
def get_vcpe_wan(cls, vcpe):
if vcpe in cls.vcpe_map:
return cls.vcpe_map[vcpe]['wan']
return None
'''
@method: get_vcpe_lan
@Description:
@params:
returns True if contents of dict1 exists in dict2
'''
@classmethod
def get_vcpe_lan(cls, vcpe):
if vcpe in cls.vcpe_map:
return cls.vcpe_map[vcpe]['lan']
return None
'''
@method: vcpe_wan_up
@Description:
@params:
returns status
'''
@classmethod
def vcpe_wan_up(cls, vcpe):
return cls.restore_vcpe_config(vcpe)
'''
@method: vcpe_lan_up
@Description:
@params:
returns status
'''
@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
'''
@method: vcpe_port_down
@Description:
@params:
returns status
'''
#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
'''
@method: vcpe_wan_down
@Description:
@params:
returns status
'''
@classmethod
def vcpe_wan_down(cls, vcpe, vsg = None):
return cls.vcpe_port_down(vcpe, 'eth0', vsg = vsg)
'''
@method: vcpe_lan_down
@Description:
@params:
returns status
'''
@classmethod
def vcpe_lan_down(cls, vcpe, vsg = None):
return cls.vcpe_port_down(vcpe, 'eth1', vsg = vsg)
'''
@method: save_interface_config
@Description:
@params:
returns NA
'''
@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)
'''
@method: restore_interface_config
@Description:
@params:
returns NA
'''
#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)
'''
@method: vcpe_get_dhcp
@Description: Get DHCP from vcpe dhcp interface.
@params:
returns vcpe ip
'''
@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()
'''
@method: get_compute_node
@Description:
@params:
returns compute node name
'''
def get_compute_node(self):
return self.vsg._info['OS-EXT-SRV-ATTR:hypervisor_hostname']
'''
@method: get_ip
@Description:
@params:
returns ip of network
'''
def get_ip(self):
if 'management' in self.vsg.networks:
ips = self.vsg.networks['management']
if len(ips) > 0:
return ips[0]
return None
'''
@method: run_cmd_compute
@Description:
@params:
returns Status & output
'''
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
'''
@method: run_cmd
@Description:
@params:
returns status & 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
'''
@method: get_health
@Description:
@params:
returns Status
'''
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 through compute node %s' %(self.name, self.ip, 'reachable' if st == True else 'unreachable', self.compute_node))
return st
'''
@method: check_access
@Description: validates access
@params:
returns Status
'''
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