Implements agent for Quantum Networking testing
This agent client plugs itself into each network. Then tries to ping each fixed_ips. Implements blueprint test-agent Change-Id: I3908e37401272e9e091ceae66e72cfcdd13b7898
This commit is contained in:
parent
fce952e8fa
commit
762cc5d830
20
bin/quantum-debug
Executable file
20
bin/quantum-debug
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2012 Openstack, LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from quantum.debug.shell import main
|
||||||
|
main()
|
33
quantum/debug/README
Normal file
33
quantum/debug/README
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
Debug Helper Script for Quantum
|
||||||
|
|
||||||
|
- Configure
|
||||||
|
export TEST_CONFIG_FILE=/etc/quantum/dhcp_agent.ini
|
||||||
|
or
|
||||||
|
export TEST_CONFIG_FILE=/etc/quantum/l3_agent.ini
|
||||||
|
|
||||||
|
you can also specify config file by --config-file option
|
||||||
|
|
||||||
|
- Usage
|
||||||
|
quantum-debug commands
|
||||||
|
|
||||||
|
probe-create <net-id> Create probe port - create port and interface, then plug it in.
|
||||||
|
This commands returns a port id of a probe port. A probe port is a port which is used to test.
|
||||||
|
The port id is probe id.
|
||||||
|
We can have multiple probe probes in a network, in order to check connectivity between ports.
|
||||||
|
|
||||||
|
quantum-debug probe-exec probe_id_1 'nc -l 192.168.100.3 22'
|
||||||
|
quantum-debug probe-exec probe_id_2 'nc -vz 192.168.100.4 22'
|
||||||
|
|
||||||
|
probe-delete <port-id> Delete probe - delete port then uplug
|
||||||
|
probe-exec <port-id> 'command' Exec commands on the namespace of the probe
|
||||||
|
`probe-exec <port-id>` 'interactive command' Exec interactive command (eg, ssh)
|
||||||
|
|
||||||
|
probe-list List probes
|
||||||
|
probe-clear Clear All probes
|
||||||
|
|
||||||
|
ping-all --id <network_id> --timeout 1 (optional)
|
||||||
|
ping-all is all-in-one command to ping all fixed ip's in all network or a specified network.
|
||||||
|
In the command probe is automatically created if needed.
|
||||||
|
|
||||||
|
quantum-debug extends the shell of quantumclient, so you can use all the commands of quantum
|
||||||
|
|
17
quantum/debug/__init__.py
Normal file
17
quantum/debug/__init__.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
156
quantum/debug/commands.py
Normal file
156
quantum/debug/commands.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
#!/bin/python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 logging
|
||||||
|
|
||||||
|
from cliff import lister
|
||||||
|
|
||||||
|
from quantumclient.common import utils
|
||||||
|
from quantumclient.quantum.v2_0 import QuantumCommand
|
||||||
|
from quantumclient.quantum.v2_0.port import _format_fixed_ips
|
||||||
|
|
||||||
|
|
||||||
|
class ProbeCommand(QuantumCommand):
|
||||||
|
log = logging.getLogger(__name__ + '.ProbeCommand')
|
||||||
|
|
||||||
|
def get_debug_agent(self):
|
||||||
|
return self.app.debug_agent
|
||||||
|
|
||||||
|
def run(self, parsed_args):
|
||||||
|
self.log.debug('run(%s)' % parsed_args)
|
||||||
|
self.app.stdout.write(_('Unimplemented commands') + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
class CreateProbe(ProbeCommand):
|
||||||
|
"""Create probe port and interface, then plug it in."""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.CreateProbe')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(CreateProbe, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id', metavar='network_id',
|
||||||
|
help='ID of network to probe')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def run(self, parsed_args):
|
||||||
|
self.log.debug('run(%s)' % parsed_args)
|
||||||
|
debug_agent = self.get_debug_agent()
|
||||||
|
port = debug_agent.create_probe(parsed_args.id)
|
||||||
|
self.app.stdout.write(_('Probe created : %s ') % port.id + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteProbe(ProbeCommand):
|
||||||
|
"""Delete probe - delete port then uplug """
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.DeleteProbe')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(DeleteProbe, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id', metavar='port_id',
|
||||||
|
help='ID of probe port to delete')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def run(self, parsed_args):
|
||||||
|
self.log.debug('run(%s)' % parsed_args)
|
||||||
|
debug_agent = self.get_debug_agent()
|
||||||
|
debug_agent.delete_probe(parsed_args.id)
|
||||||
|
self.app.stdout.write(_('Probe %s deleted') % parsed_args.id + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
class ListProbe(QuantumCommand, lister.Lister):
|
||||||
|
""" List probes """
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.ListProbe')
|
||||||
|
_formatters = {'fixed_ips': _format_fixed_ips, }
|
||||||
|
|
||||||
|
def get_debug_agent(self):
|
||||||
|
return self.app.debug_agent
|
||||||
|
|
||||||
|
def get_data(self, parsed_args):
|
||||||
|
|
||||||
|
debug_agent = self.get_debug_agent()
|
||||||
|
info = debug_agent.list_probes()
|
||||||
|
columns = len(info) > 0 and sorted(info[0].keys()) or []
|
||||||
|
return (columns, (utils.get_item_properties(
|
||||||
|
s, columns, formatters=self._formatters, )
|
||||||
|
for s in info), )
|
||||||
|
|
||||||
|
|
||||||
|
class ClearProbe(ProbeCommand):
|
||||||
|
"""Clear All probes """
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.ClearProbe')
|
||||||
|
|
||||||
|
def run(self, parsed_args):
|
||||||
|
self.log.debug('run(%s)' % parsed_args)
|
||||||
|
debug_agent = self.get_debug_agent()
|
||||||
|
debug_agent.clear_probe()
|
||||||
|
self.app.stdout.write(_('All Probes deleted ') + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
class ExecProbe(ProbeCommand):
|
||||||
|
"""Exec commands on the namespace of the probe
|
||||||
|
"""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.ExecProbe')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ExecProbe, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id', metavar='port_id',
|
||||||
|
help='ID of probe port to execute command')
|
||||||
|
parser.add_argument(
|
||||||
|
'command', metavar='command',
|
||||||
|
nargs='?',
|
||||||
|
default=None,
|
||||||
|
help='Command to execute')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def run(self, parsed_args):
|
||||||
|
self.log.debug('run(%s)' % parsed_args)
|
||||||
|
debug_agent = self.get_debug_agent()
|
||||||
|
result = debug_agent.exec_command(parsed_args.id, parsed_args.command)
|
||||||
|
self.app.stdout.write(result + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
class PingAll(ProbeCommand):
|
||||||
|
"""Ping all fixed_ip
|
||||||
|
"""
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__ + '.ExecProbe')
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(PingAll, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'--timeout', metavar='<timeout>',
|
||||||
|
default=10,
|
||||||
|
help='Ping timeout')
|
||||||
|
parser.add_argument(
|
||||||
|
'--id', metavar='network_id',
|
||||||
|
default=None,
|
||||||
|
help='ID of network')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def run(self, parsed_args):
|
||||||
|
self.log.debug('run(%s)' % parsed_args)
|
||||||
|
debug_agent = self.get_debug_agent()
|
||||||
|
result = debug_agent.ping_all(parsed_args.id,
|
||||||
|
timeout=parsed_args.timeout)
|
||||||
|
self.app.stdout.write(result + '\n')
|
189
quantum/debug/debug_agent.py
Normal file
189
quantum/debug/debug_agent.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
#!/bin/python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 datetime
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import netaddr
|
||||||
|
|
||||||
|
from quantum.agent.common import config
|
||||||
|
from quantum.agent.dhcp_agent import DictModel
|
||||||
|
from quantum.agent.linux import interface
|
||||||
|
from quantum.agent.linux import ip_lib
|
||||||
|
from quantum.agent.linux import utils
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.openstack.common import importutils
|
||||||
|
from quantumclient.v2_0 import client
|
||||||
|
|
||||||
|
LOG = logging.getLogger('test-agent')
|
||||||
|
|
||||||
|
DEVICE_OWNER_PROBE = 'network:probe'
|
||||||
|
|
||||||
|
|
||||||
|
class QuantumDebugAgent():
|
||||||
|
|
||||||
|
OPTS = [
|
||||||
|
cfg.StrOpt('root_helper', default='sudo'),
|
||||||
|
# Needed for drivers
|
||||||
|
cfg.StrOpt('admin_user'),
|
||||||
|
cfg.StrOpt('admin_password'),
|
||||||
|
cfg.StrOpt('admin_tenant_name'),
|
||||||
|
cfg.StrOpt('auth_url'),
|
||||||
|
cfg.StrOpt('auth_strategy', default='keystone'),
|
||||||
|
cfg.StrOpt('auth_region'),
|
||||||
|
cfg.BoolOpt('use_namespaces', default=True),
|
||||||
|
cfg.StrOpt('interface_driver',
|
||||||
|
help="The driver used to manage the virtual interface.")
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, conf, client, driver):
|
||||||
|
self.conf = conf
|
||||||
|
self.client = client
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def _get_namespace(self, port):
|
||||||
|
return "qprobe-%s" % port.id
|
||||||
|
|
||||||
|
def create_probe(self, network_id):
|
||||||
|
network = self._get_network(network_id)
|
||||||
|
port = self._create_port(network)
|
||||||
|
port.network = network
|
||||||
|
interface_name = self.driver.get_device_name(port)
|
||||||
|
namespace = None
|
||||||
|
if self.conf.use_namespaces:
|
||||||
|
namespace = self._get_namespace(port)
|
||||||
|
|
||||||
|
if ip_lib.device_exists(interface_name,
|
||||||
|
self.conf.root_helper, namespace):
|
||||||
|
LOG.debug(_('Reusing existing device: %s.') % interface_name)
|
||||||
|
else:
|
||||||
|
self.driver.plug(network.id,
|
||||||
|
port.id,
|
||||||
|
interface_name,
|
||||||
|
port.mac_address,
|
||||||
|
namespace=namespace)
|
||||||
|
ip_cidrs = []
|
||||||
|
for fixed_ip in port.fixed_ips:
|
||||||
|
subnet = fixed_ip.subnet
|
||||||
|
net = netaddr.IPNetwork(subnet.cidr)
|
||||||
|
ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
|
||||||
|
ip_cidrs.append(ip_cidr)
|
||||||
|
self.driver.init_l3(interface_name, ip_cidrs, namespace=namespace)
|
||||||
|
return port
|
||||||
|
|
||||||
|
def _get_subnet(self, subnet_id):
|
||||||
|
subnet_dict = self.client.show_subnet(subnet_id)['subnet']
|
||||||
|
return DictModel(subnet_dict)
|
||||||
|
|
||||||
|
def _get_network(self, network_id):
|
||||||
|
network_dict = self.client.show_network(network_id)['network']
|
||||||
|
network = DictModel(network_dict)
|
||||||
|
obj_subnet = [self._get_subnet(s_id) for s_id in network.subnets]
|
||||||
|
network.subnets = obj_subnet
|
||||||
|
return network
|
||||||
|
|
||||||
|
def clear_probe(self):
|
||||||
|
ports = self.client.list_ports(device_id=socket.gethostname(),
|
||||||
|
device_owner=DEVICE_OWNER_PROBE)
|
||||||
|
info = ports['ports']
|
||||||
|
for port in info:
|
||||||
|
self.delete_probe(port['id'])
|
||||||
|
|
||||||
|
def delete_probe(self, port_id):
|
||||||
|
port = DictModel(self.client.show_port(port_id)['port'])
|
||||||
|
ip = ip_lib.IPWrapper(self.conf.root_helper)
|
||||||
|
namespace = self._get_namespace(port)
|
||||||
|
if self.conf.use_namespaces and ip.netns.exists(namespace):
|
||||||
|
self.driver.unplug(self.driver.get_device_name(port),
|
||||||
|
namespace=namespace)
|
||||||
|
ip.netns.delete(namespace)
|
||||||
|
else:
|
||||||
|
self.driver.unplug(self.driver.get_device_name(port))
|
||||||
|
self.client.delete_port(port.id)
|
||||||
|
|
||||||
|
def list_probes(self):
|
||||||
|
ports = self.client.list_ports(device_owner=DEVICE_OWNER_PROBE)
|
||||||
|
info = ports['ports']
|
||||||
|
for port in info:
|
||||||
|
port['device_name'] = self.driver.get_device_name(DictModel(port))
|
||||||
|
return info
|
||||||
|
|
||||||
|
def exec_command(self, port_id, command=None):
|
||||||
|
port = DictModel(self.client.show_port(port_id)['port'])
|
||||||
|
ip = ip_lib.IPWrapper(self.conf.root_helper)
|
||||||
|
namespace = self._get_namespace(port)
|
||||||
|
if self.conf.use_namespaces:
|
||||||
|
if not command:
|
||||||
|
return "sudo ip netns exec %s" % self._get_namespace(port)
|
||||||
|
namespace = ip.ensure_namespace(namespace)
|
||||||
|
return namespace.netns.execute(shlex.split(command))
|
||||||
|
else:
|
||||||
|
return utils.execute(shlex.split(command))
|
||||||
|
|
||||||
|
def ensure_probe(self, network_id):
|
||||||
|
ports = self.client.list_ports(network_id=network_id,
|
||||||
|
device_id=socket.gethostname(),
|
||||||
|
device_owner=DEVICE_OWNER_PROBE)
|
||||||
|
info = ports.get('ports', [])
|
||||||
|
if info:
|
||||||
|
return DictModel(info[0])
|
||||||
|
else:
|
||||||
|
return self.create_probe(network_id)
|
||||||
|
|
||||||
|
def ping_all(self, network_id=None, timeout=1):
|
||||||
|
if network_id:
|
||||||
|
ports = self.client.list_ports(network_id=network_id)['ports']
|
||||||
|
else:
|
||||||
|
ports = self.client.list_ports()['ports']
|
||||||
|
result = ""
|
||||||
|
for port in ports:
|
||||||
|
probe = self.ensure_probe(port['network_id'])
|
||||||
|
if port['device_owner'] == DEVICE_OWNER_PROBE:
|
||||||
|
continue
|
||||||
|
for fixed_ip in port['fixed_ips']:
|
||||||
|
address = fixed_ip['ip_address']
|
||||||
|
subnet = self._get_subnet(fixed_ip['subnet_id'])
|
||||||
|
if subnet.ip_version == 4:
|
||||||
|
ping_command = 'ping'
|
||||||
|
else:
|
||||||
|
ping_command = 'ping6'
|
||||||
|
result += self.exec_command(probe.id,
|
||||||
|
'%s -c 1 -w %s %s' % (ping_command,
|
||||||
|
timeout,
|
||||||
|
address))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _create_port(self, network):
|
||||||
|
body = dict(port=dict(
|
||||||
|
admin_state_up=True,
|
||||||
|
network_id=network.id,
|
||||||
|
device_id='%s' % socket.gethostname(),
|
||||||
|
device_owner=DEVICE_OWNER_PROBE,
|
||||||
|
tenant_id=network.tenant_id,
|
||||||
|
fixed_ips=[dict(subnet_id=s.id) for s in network.subnets]))
|
||||||
|
port_dict = self.client.create_port(body)['port']
|
||||||
|
port = DictModel(port_dict)
|
||||||
|
port.network = network
|
||||||
|
for fixed_ip in port.fixed_ips:
|
||||||
|
fixed_ip.subnet = self._get_subnet(fixed_ip.subnet_id)
|
||||||
|
return port
|
86
quantum/debug/shell.py
Normal file
86
quantum/debug/shell.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 itertools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from quantum.agent.common import config
|
||||||
|
from quantum.agent.linux import interface
|
||||||
|
import quantum.debug.commands
|
||||||
|
from quantum.debug.debug_agent import QuantumDebugAgent
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.openstack.common import importutils
|
||||||
|
from quantumclient.common import exceptions as exc
|
||||||
|
from quantumclient.common import utils
|
||||||
|
from quantumclient.shell import env, QuantumShell, QUANTUM_API_VERSION
|
||||||
|
|
||||||
|
COMMAND_V2 = {
|
||||||
|
'probe-create': utils.import_class(
|
||||||
|
'quantum.debug.commands.CreateProbe'),
|
||||||
|
'probe-delete': utils.import_class(
|
||||||
|
'quantum.debug.commands.DeleteProbe'),
|
||||||
|
'probe-list': utils.import_class(
|
||||||
|
'quantum.debug.commands.ListProbe'),
|
||||||
|
'probe-clear': utils.import_class(
|
||||||
|
'quantum.debug.commands.ClearProbe'),
|
||||||
|
'probe-exec': utils.import_class(
|
||||||
|
'quantum.debug.commands.ExecProbe'),
|
||||||
|
'ping-all': utils.import_class(
|
||||||
|
'quantum.debug.commands.PingAll'),
|
||||||
|
#TODO(nati) ping, netcat , nmap, bench
|
||||||
|
}
|
||||||
|
COMMANDS = {'2.0': COMMAND_V2}
|
||||||
|
|
||||||
|
|
||||||
|
class QuantumDebugShell(QuantumShell):
|
||||||
|
def __init__(self, api_version):
|
||||||
|
super(QuantumDebugShell, self).__init__(api_version)
|
||||||
|
for k, v in COMMANDS[api_version].items():
|
||||||
|
self.command_manager.add_command(k, v)
|
||||||
|
|
||||||
|
def build_option_parser(self, description, version):
|
||||||
|
parser = super(QuantumDebugShell, self).build_option_parser(
|
||||||
|
description, version)
|
||||||
|
parser.add_argument(
|
||||||
|
'--config-file',
|
||||||
|
default=env('TEST_CONFIG_FILE'),
|
||||||
|
help='Config file for interface driver '
|
||||||
|
'(You may also use either the '
|
||||||
|
'l3_agent.ini or the dhcp_agent.ini)')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def initialize_app(self, argv):
|
||||||
|
super(QuantumDebugShell, self).initialize_app(argv)
|
||||||
|
if not self.options.config_file:
|
||||||
|
raise exc.CommandError(
|
||||||
|
"You must provide a config file for bridge -"
|
||||||
|
" either --config-file or env[TEST_CONFIG_FILE]")
|
||||||
|
client = self.client_manager.quantum
|
||||||
|
cfg.CONF.register_opts(interface.OPTS)
|
||||||
|
cfg.CONF.register_opts(QuantumDebugAgent.OPTS)
|
||||||
|
cfg.CONF(['--config-file', self.options.config_file])
|
||||||
|
config.setup_logging(cfg.CONF)
|
||||||
|
driver = importutils.import_object(cfg.CONF.interface_driver, cfg.CONF)
|
||||||
|
self.debug_agent = QuantumDebugAgent(cfg.CONF, client, driver)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
return QuantumDebugShell(QUANTUM_API_VERSION).run(argv or sys.argv[1:])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
271
quantum/tests/unit/test_debug_commands.py
Normal file
271
quantum/tests/unit/test_debug_commands.py
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 socket
|
||||||
|
import sys
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import mock
|
||||||
|
import unittest2 as unittest
|
||||||
|
|
||||||
|
from quantum.agent.common import config
|
||||||
|
from quantum.agent.linux import interface
|
||||||
|
from quantum.agent.linux import utils
|
||||||
|
from quantum.common import exceptions
|
||||||
|
from quantum.debug import commands
|
||||||
|
from quantum.debug.debug_agent import DEVICE_OWNER_PROBE, QuantumDebugAgent
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
|
||||||
|
|
||||||
|
class MyApp(object):
|
||||||
|
def __init__(self, _stdout):
|
||||||
|
self.stdout = _stdout
|
||||||
|
|
||||||
|
|
||||||
|
class TestDebugCommands(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
cfg.CONF.register_opts(interface.OPTS)
|
||||||
|
cfg.CONF.register_opts(QuantumDebugAgent.OPTS)
|
||||||
|
cfg.CONF(args=sys.argv, project='quantum')
|
||||||
|
cfg.CONF.set_override('use_namespaces', True)
|
||||||
|
cfg.CONF.root_helper = 'sudo'
|
||||||
|
|
||||||
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
device_exists_p = mock.patch(
|
||||||
|
'quantum.agent.linux.ip_lib.device_exists')
|
||||||
|
device_exists_p.start()
|
||||||
|
namespace_p = mock.patch(
|
||||||
|
'quantum.agent.linux.ip_lib.IpNetnsCommand')
|
||||||
|
namespace_p.start()
|
||||||
|
ensure_namespace_p = mock.patch(
|
||||||
|
'quantum.agent.linux.ip_lib.IPWrapper.ensure_namespace')
|
||||||
|
ensure_namespace_p.start()
|
||||||
|
dvr_cls_p = mock.patch('quantum.agent.linux.interface.NullDriver')
|
||||||
|
driver_cls = dvr_cls_p.start()
|
||||||
|
mock_driver = mock.MagicMock()
|
||||||
|
mock_driver.DEV_NAME_LEN = (
|
||||||
|
interface.LinuxInterfaceDriver.DEV_NAME_LEN)
|
||||||
|
mock_driver.get_device_name.return_value = 'tap12345678-12'
|
||||||
|
driver_cls.return_value = mock_driver
|
||||||
|
self.driver = mock_driver
|
||||||
|
|
||||||
|
client_cls_p = mock.patch('quantumclient.v2_0.client.Client')
|
||||||
|
client_cls = client_cls_p.start()
|
||||||
|
client_inst = mock.Mock()
|
||||||
|
client_cls.return_value = client_inst
|
||||||
|
|
||||||
|
fake_network = {'network': {'id': 'fake_net',
|
||||||
|
'tenant_id': 'fake_tenant',
|
||||||
|
'subnets': ['fake_subnet']}}
|
||||||
|
fake_port = {'port':
|
||||||
|
{'id': 'fake_port',
|
||||||
|
'device_owner': 'fake_device',
|
||||||
|
'mac_address': 'aa:bb:cc:dd:ee:ffa',
|
||||||
|
'network_id': 'fake_net',
|
||||||
|
'fixed_ips':
|
||||||
|
[{'subnet_id': 'fake_subnet', 'ip_address':'10.0.0.3'}]
|
||||||
|
}}
|
||||||
|
fake_ports = {'ports': [fake_port['port']]}
|
||||||
|
self.fake_ports = fake_ports
|
||||||
|
allocation_pools = [{'start': '10.0.0.2',
|
||||||
|
'end': '10.0.0.254'}]
|
||||||
|
fake_subnet_v4 = {'subnet': {'name': 'fake_subnet_v4',
|
||||||
|
'id': 'fake_subnet',
|
||||||
|
'network_id': 'fake_net',
|
||||||
|
'gateway_ip': '10.0.0.1',
|
||||||
|
'dns_nameservers': ['10.0.0.2'],
|
||||||
|
'host_routes': [],
|
||||||
|
'cidr': '10.0.0.0/24',
|
||||||
|
'allocation_pools': allocation_pools,
|
||||||
|
'enable_dhcp': True,
|
||||||
|
'ip_version': 4}}
|
||||||
|
|
||||||
|
client_inst.list_ports.return_value = fake_ports
|
||||||
|
client_inst.create_port.return_value = fake_port
|
||||||
|
client_inst.show_port.return_value = fake_port
|
||||||
|
client_inst.show_network.return_value = fake_network
|
||||||
|
client_inst.show_subnet.return_value = fake_subnet_v4
|
||||||
|
self.client = client_inst
|
||||||
|
mock_std = mock.Mock()
|
||||||
|
self.app = MyApp(mock_std)
|
||||||
|
self.app.debug_agent = QuantumDebugAgent(cfg.CONF,
|
||||||
|
client_inst,
|
||||||
|
mock_driver)
|
||||||
|
|
||||||
|
def test_create_probe(self):
|
||||||
|
cmd = commands.CreateProbe(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('create_probe')
|
||||||
|
args = ['fake_net']
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
fake_port = {'port':
|
||||||
|
{'device_owner': DEVICE_OWNER_PROBE,
|
||||||
|
'admin_state_up': True,
|
||||||
|
'network_id': 'fake_net',
|
||||||
|
'tenant_id': 'fake_tenant',
|
||||||
|
'fixed_ips': [{'subnet_id': 'fake_subnet'}],
|
||||||
|
'device_id': socket.gethostname()}}
|
||||||
|
namespace = 'qprobe-fake_port'
|
||||||
|
self.client.assert_has_calls([mock.call.show_network('fake_net'),
|
||||||
|
mock.call.show_subnet('fake_subnet'),
|
||||||
|
mock.call.create_port(fake_port),
|
||||||
|
mock.call.show_subnet('fake_subnet')])
|
||||||
|
self.driver.assert_has_calls([mock.call.init_l3('tap12345678-12',
|
||||||
|
['10.0.0.3/24'],
|
||||||
|
namespace=namespace
|
||||||
|
)])
|
||||||
|
|
||||||
|
def test_delete_probe(self):
|
||||||
|
cmd = commands.DeleteProbe(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('delete_probe')
|
||||||
|
args = ['fake_port']
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
namespace = 'qprobe-fake_port'
|
||||||
|
self.client.assert_has_calls([mock.call.show_port('fake_port'),
|
||||||
|
mock.call.delete_port('fake_port')])
|
||||||
|
self.driver.assert_has_calls([mock.call.get_device_name(mock.ANY),
|
||||||
|
mock.call.unplug('tap12345678-12',
|
||||||
|
namespace=namespace)])
|
||||||
|
|
||||||
|
def test_delete_probe_without_namespace(self):
|
||||||
|
cfg.CONF.set_override('use_namespaces', False)
|
||||||
|
cmd = commands.DeleteProbe(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('delete_probe')
|
||||||
|
args = ['fake_port']
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
self.client.assert_has_calls([mock.call.show_port('fake_port'),
|
||||||
|
mock.call.delete_port('fake_port')])
|
||||||
|
self.driver.assert_has_calls([mock.call.get_device_name(mock.ANY),
|
||||||
|
mock.call.unplug('tap12345678-12')])
|
||||||
|
|
||||||
|
def test_list_probe(self):
|
||||||
|
cmd = commands.ListProbe(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('list_probe')
|
||||||
|
args = []
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
self.client.assert_has_calls(
|
||||||
|
[mock.call.list_ports(device_owner=DEVICE_OWNER_PROBE)])
|
||||||
|
|
||||||
|
def test_exec_command(self):
|
||||||
|
cmd = commands.ExecProbe(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('exec_command')
|
||||||
|
args = ['fake_port', 'fake_command']
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
with mock.patch('quantum.agent.linux.ip_lib.IpNetnsCommand') as ns:
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
ns.assert_has_calls([mock.call.execute(mock.ANY)])
|
||||||
|
self.client.assert_has_calls([mock.call.show_port('fake_port')])
|
||||||
|
|
||||||
|
def test_exec_command_without_namespace(self):
|
||||||
|
cfg.CONF.set_override('use_namespaces', False)
|
||||||
|
cmd = commands.ExecProbe(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('exec_command')
|
||||||
|
args = ['fake_port', 'fake_command']
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
with mock.patch('quantum.agent.linux.utils.execute') as exe:
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
exe.assert_has_calls([mock.call.execute(mock.ANY)])
|
||||||
|
self.client.assert_has_calls([mock.call.show_port('fake_port')])
|
||||||
|
|
||||||
|
def test_clear_probe(self):
|
||||||
|
cmd = commands.ClearProbe(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('clear_probe')
|
||||||
|
args = []
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
namespace = 'qprobe-fake_port'
|
||||||
|
self.client.assert_has_calls([mock.call.list_ports(
|
||||||
|
device_id=socket.gethostname(),
|
||||||
|
device_owner=DEVICE_OWNER_PROBE),
|
||||||
|
mock.call.show_port('fake_port'),
|
||||||
|
mock.call.delete_port('fake_port')])
|
||||||
|
self.driver.assert_has_calls([mock.call.get_device_name(mock.ANY),
|
||||||
|
mock.call.unplug('tap12345678-12',
|
||||||
|
namespace=namespace)])
|
||||||
|
|
||||||
|
def test_ping_all_with_ensure_port(self):
|
||||||
|
fake_ports = self.fake_ports
|
||||||
|
|
||||||
|
def fake_port_list(network_id=None, device_owner=None, device_id=None):
|
||||||
|
if network_id:
|
||||||
|
# In order to test ensure_port, return []
|
||||||
|
return {'ports': []}
|
||||||
|
return fake_ports
|
||||||
|
self.client.list_ports.side_effect = fake_port_list
|
||||||
|
cmd = commands.PingAll(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('ping_all')
|
||||||
|
args = []
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
namespace = 'qprobe-fake_port'
|
||||||
|
with mock.patch('quantum.agent.linux.ip_lib.IpNetnsCommand') as ns:
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
ns.assert_has_calls([mock.call.execute(mock.ANY)])
|
||||||
|
fake_port = {'port':
|
||||||
|
{'device_owner': DEVICE_OWNER_PROBE,
|
||||||
|
'admin_state_up': True,
|
||||||
|
'network_id': 'fake_net',
|
||||||
|
'tenant_id': 'fake_tenant',
|
||||||
|
'fixed_ips': [{'subnet_id': 'fake_subnet'}],
|
||||||
|
'device_id': socket.gethostname()}}
|
||||||
|
expected = [mock.call.show_network('fake_net'),
|
||||||
|
mock.call.show_subnet('fake_subnet'),
|
||||||
|
mock.call.create_port(fake_port),
|
||||||
|
mock.call.show_subnet('fake_subnet')]
|
||||||
|
self.client.assert_has_calls(expected)
|
||||||
|
self.driver.assert_has_calls([mock.call.init_l3('tap12345678-12',
|
||||||
|
['10.0.0.3/24'],
|
||||||
|
namespace=namespace
|
||||||
|
)])
|
||||||
|
|
||||||
|
def test_ping_all(self):
|
||||||
|
cmd = commands.PingAll(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('ping_all')
|
||||||
|
args = []
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
with mock.patch('quantum.agent.linux.ip_lib.IpNetnsCommand') as ns:
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
ns.assert_has_calls([mock.call.execute(mock.ANY)])
|
||||||
|
fake_port = {'port':
|
||||||
|
{'device_owner': DEVICE_OWNER_PROBE,
|
||||||
|
'admin_state_up': True,
|
||||||
|
'network_id': 'fake_net',
|
||||||
|
'tenant_id': 'fake_tenant',
|
||||||
|
'fixed_ips': [{'subnet_id': 'fake_subnet'}],
|
||||||
|
'device_id': socket.gethostname()}}
|
||||||
|
expected = [mock.call.list_ports(),
|
||||||
|
mock.call.list_ports(network_id='fake_net',
|
||||||
|
device_owner=DEVICE_OWNER_PROBE,
|
||||||
|
device_id=socket.gethostname()),
|
||||||
|
mock.call.show_subnet('fake_subnet'),
|
||||||
|
mock.call.show_port('fake_port')]
|
||||||
|
self.client.assert_has_calls(expected)
|
||||||
|
|
||||||
|
def test_ping_all_v6(self):
|
||||||
|
fake_subnet_v6 = {'subnet': {'name': 'fake_v6',
|
||||||
|
'ip_version': 6}}
|
||||||
|
self.client.show_subnet.return_value = fake_subnet_v6
|
||||||
|
cmd = commands.PingAll(self.app, None)
|
||||||
|
cmd_parser = cmd.get_parser('ping_all')
|
||||||
|
args = []
|
||||||
|
parsed_args = cmd_parser.parse_args(args)
|
||||||
|
with mock.patch('quantum.agent.linux.ip_lib.IpNetnsCommand') as ns:
|
||||||
|
cmd.run(parsed_args)
|
||||||
|
ns.assert_has_calls([mock.call.execute(mock.ANY)])
|
||||||
|
self.client.assert_has_calls([mock.call.list_ports()])
|
1
setup.py
1
setup.py
@ -112,6 +112,7 @@ setuptools.setup(
|
|||||||
'quantum-nec-agent = '
|
'quantum-nec-agent = '
|
||||||
'quantum.plugins.nec.agent.nec_quantum_agent:main',
|
'quantum.plugins.nec.agent.nec_quantum_agent:main',
|
||||||
'quantum-server = quantum.server:main',
|
'quantum-server = quantum.server:main',
|
||||||
|
'quantum-debug = quantum.debug.shell:main',
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
distribute>=0.6.24
|
cliff
|
||||||
coverage
|
coverage
|
||||||
mock>=0.8
|
distribute>=0.6.24
|
||||||
|
mock>=1.0b1
|
||||||
mox==0.5.3
|
mox==0.5.3
|
||||||
nose
|
nose
|
||||||
|
nosehtmloutput
|
||||||
nosexcover
|
nosexcover
|
||||||
openstack.nose_plugin
|
openstack.nose_plugin
|
||||||
nosehtmloutput
|
|
||||||
pep8
|
pep8
|
||||||
sphinx>=1.1.2
|
sphinx>=1.1.2
|
||||||
unittest2
|
unittest2
|
||||||
|
Loading…
Reference in New Issue
Block a user