Beispiel #1
0
    def connected(self, protocol):
        """Handles connection."""
        Service.connected(self, protocol)
        self.protocol = protocol

        self.send(Message(self.name, Message.TYPE_LOGIN))
        return
Beispiel #2
0
 def run(self):
     _period = 1000 * self.configreader["services"][self.name]["checkmail_interval"]
     beat = PeriodicCallback(self.cmd_processmails,
                             _period,
                             io_loop=self.ioloop)
     beat.start()
     Service.run(self)
Beispiel #3
0
    def __init__(self, camera_factory=None):
        Service.__init__(self, cfg.CAMERA_NAME)

        self.camera_factory = camera_factory
        self.camera = None

        self.handlers[Message.TYPE_PARAMS] = self.handle_params

        self.client = Client(self, cfg.SERVER_HOST, cfg.SERVER_PORT)
        self.protocol = None

        self.frame_rate = 1.0
        return
Beispiel #4
0
    def __init__(self, controller=None):
        Service.__init__(self, 'dispatcher')

        self.handlers[Message.TYPE_LOGIN] = self.handle_login
        self.handlers[Message.TYPE_IMAGE] = self.handle_image
        self.handlers[Message.TYPE_RESULT] = self.handle_result

        self.server = Server(self, cfg.SERVER_PORT)
        self.protocols = {}

        self.nodes = {}

        if cfg.DASHBOARD:
            self.dashboard = Dashboard()
            self.dashboard.start()

        else:
            self.dashboard = None

        measure_period = float(cfg.CONTROLLER_LOOP_TIME)/cfg.MEASURES_PER_LOOP
        self.monitor = Monitor(self.process_measurements, measure_period)
        self.monitor.register_item(self.ITEM_FPS, Monitor.ITEM_RATE)
        self.monitor.register_item(self.ITEM_MAKESPAN, Monitor.ITEM_AVG)
        self.monitor.register_item(self.ITEM_THROUGHPUT, Monitor.ITEM_RATE)

        self.controller = controller
        self.controller.dashboard = self.dashboard
        self.controller.dispatcher = self

        if self.dashboard:
            self.dashboard.controller = controller

        self.imagebuf = Queue.Queue()   # Buffer of encoded images
                                        # (time stamp, image data)

        # Initialize probe to blank image
        self.probe_image = self.generate_probe_image()

        self.tokens = Queue.Queue()
        self.job_id = 0

        self.job_image_cache = {}

        self.sub_loop = 0
        return
def do_packet_ping(api_client, src_id, dst_id, socket_type):
    """Perform a packet ping from src to dst using the specified binary"""
    shim = Service(CONFIG.shim())

    with open(os.devnull, 'w') as null:
        p = subprocess.Popen(packet_ping_command(api_client, src_id, dst_id, socket_type),
                             stdout=null, stderr=null,
                             env={'LD_PRELOAD': shim.config.path,
                                  'OP_BINDTODEVICE': src_id})
        p.wait()
        expect(p.returncode).to(equal(0))
Beispiel #6
0
def do_ping(api_client, ping_binary, src_id, dst_id, domain):
    """Perform a ping from src to dst using the specified binary"""
    shim = Service(CONFIG.shim())
    dst_ip = get_interface_address(api_client, dst_id, domain)

    with open(os.devnull, 'w') as null:
        p = subprocess.Popen(ping_command(ping_binary, dst_ip, domain),
                             stdout=null, stderr=null,
                             env={'LD_PRELOAD': shim.config.path,
                                  'OP_BINDTODEVICE': src_id})
        p.wait()
        expect(p.returncode).to(equal(0))
Beispiel #7
0
                            be_valid_cpu_generator,
                            be_valid_cpu_generator_result,
                            be_valid_dynamic_results)


CONFIG = Config(os.path.join(os.path.dirname(__file__),
                os.environ.get('MAMBA_CONFIG', 'config.yaml')))


def approximately_equal(a, b):
    return abs(a - b) < 0.01


with description('CPU Generator Module', 'cpu') as self:
    with before.all:
        service = Service(CONFIG.service())
        self._process = service.start()
        self._api = client.api.CpuGeneratorApi(service.client())
        if not check_modules_exists(service.client(), 'cpu'):
            self.skip()

    with after.all:
        try:
            for gen in self._api.list_cpu_generators():
                if gen.running:
                    self._api.stop_cpu_generator(gen.id)
                self._api.delete_cpu_generator(gen.id)

            self._process.terminate()
            self._process.wait()
        except AttributeError:
Beispiel #8
0
    ports = ports_api.list_ports()
    expect(ports).to(have_len(be_above(1)))

    gen1 = generator_model(api_client)
    gen1.target_id = ports[0].id
    gen2 = generator_model(api_client)
    gen2.target_id = ports[1].id

    return [gen1, gen2]


with description('Packet Generator,', 'packet_generator') as self:
    with description('REST API'):

        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.api = client.api.PacketGeneratorsApi(service.client())

        with description('invalid HTTP methods,'):
            with description('/packet/generators,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api('/packet/generators', 'PUT')).to(
                        raise_api_exception(405, headers={'Allow': "DELETE, GET, POST"}))

            with description('/packet/generator-results,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api('/packet/generator-results', 'PUT')).to(
                        raise_api_exception(405, headers={'Allow': "DELETE, GET"}))

            with description('/packet/tx-flows,'):
Beispiel #9
0
from expects import expect, be_empty
import os

import client.api
import client.models
from common import Config, Service
from common.matcher import be_valid_stack, raise_api_exception
from common.helper import check_modules_exists

CONFIG = Config(
    os.path.join(os.path.dirname(__file__),
                 os.environ.get('MAMBA_CONFIG', 'config.yaml')))

with description('Stacks,', 'stacks') as self:
    with before.all:
        service = Service(CONFIG.service())
        self.process = service.start()
        self.api = client.api.StacksApi(service.client())
        if not check_modules_exists(service.client(), 'packet-stack'):
            self.skip()

    with description('list,'):
        with it('returns valid stacks'):
            stacks = self.api.list_stacks()
            expect(stacks).not_to(be_empty)
            for stack in stacks:
                expect(stack).to(be_valid_stack)

        with description('unsupported method'):
            with it('returns 405'):
                expect(
Beispiel #10
0
 def __init__(self, id, name="bfs", configfile=None):
     Service.__init__(self, id, name, configfile)
     self.reserved_dbs = self.configreader["services"][self.name]["reserved_dbs"]
     self.collection = self.configreader["services"][self.name]["content_collection"]
     self.counter_collection = self.configreader["services"][self.name]["counter_collection"]
Beispiel #11
0

def pcap_icmp_echo_request_lengths(pcap_file):
    lengths = []
    icmp_type = scapy.all.ICMP(type='echo-request').type
    for packet in scapy.all.rdpcap(pcap_file):
        if 'ICMP' in packet and packet['ICMP'].type == icmp_type:
            lengths.append(len(packet))
    return lengths


with description('Packet Capture,', 'packet_capture') as self:
    with description('REST API,'):

        with before.all:
            service = Service(CONFIG.service('dataplane'))
            self.process = service.start()
            self.api = client.api.PacketCapturesApi(service.client())
            self.intf_api = client.api.InterfacesApi(self.api.api_client)

            # By default, ping is a privileged process.  We need it unprivileged
            # to use LD_PRELOAD, so just make a copy as a regular user.
            self.temp_dir = tempfile.mkdtemp()
            shutil.copy(PING, self.temp_dir)
            self.temp_ping = os.path.join(self.temp_dir,
                                          os.path.basename(PING))
            expect(os.path.isfile(self.temp_ping))

        with description('invalid HTTP methods,'):
            with description('/packet/captures,'):
                with it('returns 405'):
Beispiel #12
0
    return bsbgr


class has_location(Matcher):
    def __init__(self, expected):
        self._expected = CONFIG.service().base_url + expected

    def _match(self, subject):
        expect(subject).to(have_key('Location'))
        return subject['Location'] == self._expected, []


with description('Block,', 'block') as self:
    with description('REST API,'):
        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.api = client.api.BlockGeneratorApi(service.client())

        with description('invalid HTTP methods,'):
            with description('/block-devices,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                        '/block-devices', 'PUT')).to(
                            raise_api_exception(405, headers={'Allow': "GET"}))

            with description('/block-files,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                        '/block-files', 'PUT')).to(
                            raise_api_exception(405,
Beispiel #13
0
 def __init__(self, id, name="parser", configfile=None):
     Service.__init__(self, id, name, configfile)
     self.parser_cmdline = CommandLineParser()
     self.parser_jsoncmdline = JSONCommandLineParser()
     self.parser_bql = BQLParser()
Beispiel #14
0
                           get_block_dynamic_results_fields,
                           block_generator_model, file_model, bulk_start_model,
                           bulk_stop_model, wait_for_file_initialization_done)
from common.matcher import (be_valid_block_device, be_valid_block_file,
                            be_valid_block_generator,
                            be_valid_block_generator_result, has_location,
                            raise_api_exception, be_valid_dynamic_results)

CONFIG = Config(
    os.path.join(os.path.dirname(__file__),
                 os.environ.get('MAMBA_CONFIG', 'config.yaml')))

with description('Block,', 'block') as self:
    with description('REST API,'):
        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.api = client.api.BlockGeneratorApi(service.client())
            if not check_modules_exists(service.client(), 'block'):
                self.skip()

        with description('invalid HTTP methods,'):
            with description('/block-devices,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                        '/block-devices', 'PUT')).to(
                            raise_api_exception(405, headers={'Allow': "GET"}))

            with description('/block-files,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                           example_ipv6_interface, example_ipv4andv6_interface,
                           ipv4_interface, ipv6_interface,
                           make_interface_protocols)
from common.matcher import be_valid_interface, raise_api_exception
from common.helper import check_modules_exists

CONFIG = Config(
    os.path.join(os.path.dirname(__file__),
                 os.environ.get('MAMBA_CONFIG', 'config.yaml')))
BULK_OP_SIZE = 4

with description('Interfaces,', 'interfaces') as self:
    with description('REST API,'):

        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.api = client.api.InterfacesApi(service.client())
            if not check_modules_exists(service.client(), 'packet-stack'):
                self.skip()

        with description('list,'):
            with description('Eth,'):
                with before.each:
                    intf = self.api.create_interface(
                        example_ipv4_interface(self.api.api_client))
                    expect(intf).to(be_valid_interface)
                    self.intf, self.cleanup = intf, intf

                with description('unfiltered,'):
                    with it('succeeds'):
Beispiel #16
0
 def __init__(self, id, name="emailcheck", configfile=None):
     Service.__init__(self, id, name, configfile)
Beispiel #17
0
    for flow_id in gen_result.flows:
        flow = gen_api.get_tx_flow(flow_id)
        expect(flow).to(be_valid_transmit_flow)

    expect([f.id for f in gen_api.list_tx_flows() if f.id in gen_result.flows]).not_to(be_empty)


###
# Begin test proper
###
with description('Packet back to back', 'packet_b2b') as self:
    with description('generation and analysis,'):

        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.client = service.client()
            self.analyzer_api = client.api.PacketAnalyzersApi(service.client())
            self.generator_api = client.api.PacketGeneratorsApi(service.client())

        with description('with single traffic definition,'):
            with description('without signatures,'):
                with it('succeeds'):
                    ana_result, gen_result = configure_and_run_test(self.client,
                                                                    ANALYZER_CONFIG_NO_SIGS,
                                                                    GENERATOR_CONFIG_NO_SIGS)

                    # Validate results
                    exp_flow_count = 1
                    expect(len(ana_result.flows)).to(equal(exp_flow_count))
Beispiel #18
0
from mamba import description, before, after, it
from expects import expect
import os

import client.api
from common import Config, Service
from common.helper import check_modules_exists

CONFIG = Config(
    os.path.join(os.path.dirname(__file__),
                 os.environ.get('MAMBA_CONFIG', 'config.yaml')))

with description('PacketIO arguments,', 'packetio_args'):
    with description('Empty core mask,'):
        with before.all:
            service = Service(CONFIG.service('packetio-args-1'))
            self.process = service.start()
            self.api = client.api.PortsApi(service.client())
            if not check_modules_exists(service.client(), 'packetio'):
                self.skip()

        with description('binary started,'):
            with it('has no port handlers'):
                expect(
                    lambda: self.api.list_ports().to(raise_api_exception(405)))

        with after.all:
            try:
                self.process.terminate()
                self.process.wait()
            except AttributeError:
Beispiel #19
0
    return ta


def analyzer_models(api_client, protocol_counters=None, flow_counters=None):
    ana1 = analyzer_model(api_client, protocol_counters, flow_counters)
    ana2 = analyzer_model(api_client, protocol_counters, flow_counters)
    ana2.source_id = get_second_port_id(api_client)
    return [ana1, ana2]


with description('Packet Analyzer,', 'packet_analyzer') as self:
    with description('REST API,'):

        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.api = client.api.PacketAnalyzersApi(service.client())

        with description('invalid HTTP methods,'):
            with description('/packet/analyzers,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                        '/packet/analyzers', 'PUT')).to(
                            raise_api_exception(
                                405, headers={'Allow': "DELETE, GET, POST"}))

            with description('/packet/analyzer-results,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                        '/packet/analyzer-results', 'PUT')).to(
Beispiel #20
0
 def disconnected(self, protocol):
     """Handles disconnection."""
     Service.disconnected(self, protocol)
     self.protocol = None
     self.stop_camera()
     return
    s.config = config

    return s


def get_system_timesource(id=None):
    s = client.models.TimeSource()
    s.kind = 'system'
    s.id = 'test-source-' + str(id)

    return s


with description('Timesync,', 'timesync') as self:
    with before.all:
        service = Service(CONFIG.service())
        self.process = service.start()
        self.api = client.api.TimeSyncApi(service.client())

    with description('counters,'):
        with description('unsupported method,'):
            with it('returns 405'):
                expect(lambda: self.api.api_client.call_api(
                    '/time-counters', 'PUT')).to(
                        raise_api_exception(405, headers={'Allow': "GET"}))

        with description('GET,'):
            with description('list,'):
                with it('succeeds'):
                    counters = self.api.list_time_counters()
                    expect(counters).not_to(be_empty)
Beispiel #22
0
    shim = Service(CONFIG.shim())
    dst_ip = get_interface_address(api_client, dst_id, domain)

    with open(os.devnull, 'w') as null:
        p = subprocess.Popen(ping_command(ping_binary, dst_ip, domain),
                             stdout=null, stderr=null,
                             env={'LD_PRELOAD': shim.config.path,
                                  'OP_BINDTODEVICE': src_id})
        p.wait()
        expect(p.returncode).to(equal(0))


with description('Dataplane,', 'dataplane') as self:

    with before.all:
        service = Service(CONFIG.service('dataplane'))
        self.process = service.start()
        self.api = client.api.InterfacesApi(service.client())
        if not check_modules_exists(service.client(), 'packetio'):
            self.skip()

    with description('ipv4,', 'dataplane:ipv4'):
        with description('ping,', 'dataplane:ipv4:ping'):
            with before.all:
                # By default, ping is a privileged process.  We need it unprivileged
                # to use LD_PRELOAD, so just make a copy as a regular user.
                self.temp_dir = tempfile.mkdtemp()
                shutil.copy(PING, self.temp_dir)
                self.temp_ping = os.path.join(self.temp_dir, os.path.basename(PING))
                expect(os.path.isfile(self.temp_ping))
Beispiel #23
0
    ports = ports_api.list_ports()
    expect(ports).to(have_len(be_above(1)))

    gen1 = generator_model(api_client)
    gen1.target_id = ports[0].id
    gen2 = generator_model(api_client)
    gen2.target_id = ports[1].id

    return [gen1, gen2]


with description('Packet Generator,', 'packet_generator') as self:
    with description('REST API'):

        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.api = client.api.PacketGeneratorsApi(service.client())
            if not check_modules_exists(service.client(), 'packet-generator'):
                self.skip()

        with description('invalid HTTP methods,'):
            with description('/packet/generators,'):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                        '/packet/generators', 'PUT')).to(
                            raise_api_exception(
                                405, headers={'Allow': "DELETE, GET, POST"}))

            with description('/packet/generator-results,'):
                with it('returns 405'):
Beispiel #24
0

class has_location(Matcher):
    def __init__(self, expected):
        self._expected = expected

    def _match(self, subject):
        expect(subject).to(have_key('Location'))
        return subject['Location'] == self._expected, []


has_json_content_type = _has_json_content_type()

with description('Memory Generator Module', 'memory') as self:
    with before.all:
        service = Service(CONFIG.service())
        self._process = service.start()
        self._api = client.api.MemoryGeneratorApi(service.client())

    with after.all:
        try:
            self._process.terminate()
            self._process.wait()
        except AttributeError:
            pass

    with description('/memory-info'):
        with context('GET'):
            with before.all:
                self._result = self._api.memory_info_with_http_info(
                    _return_http_data_only=False)
Beispiel #25
0
from mamba import description, before, after, it
from expects import *
import os

import client.api
import client.models
from common import Config, Service
from common.matcher import be_valid_module, raise_api_exception

CONFIG = Config(
    os.path.join(os.path.dirname(__file__),
                 os.environ.get('MAMBA_CONFIG', 'config.yaml')))

with description('Modules, ', 'modules') as self:
    with before.all:
        service = Service(CONFIG.service())
        self.process = service.start()
        self.api = client.api.ModulesApi(service.client())

    with description('list, '):
        with description('all, '):
            with it('returns list of modules'):
                modules = self.api.list_modules()
                expect(modules).not_to(be_empty)
                for module in modules:
                    expect(module).to(be_valid_module)

            with description('unsupported method, '):
                with it('returns 405'):
                    expect(lambda: self.api.api_client.call_api(
                        '/modules', 'PUT')).to(
Beispiel #26
0
 def __init__(self, id, name="email", configfile=None):
     Service.__init__(self, id, name, configfile)
     self.pending_emails_key = "pending.mails"
Beispiel #27
0
def create_connected_endpoints(api_client, reader_id, writer_id, domain, protocol, null):
    """
    We need to create the server endpoint before the client endpoint, otherwise we
    run the risk of the client failing to connect before the server is started
    This function juggles the order as appropriate and returns the reader/writer
    subprocesses running the nc instances.  It also waits for verification that
    the client process has connected before returning.
    """

    shim = Service(CONFIG.shim())
    reader = None
    writer = None

    server_id = reader_id if is_server_interface(reader_id) else writer_id
    server_ip_addr = get_interface_address(api_client, server_id, domain)

    server_input = None
    if is_server_interface(writer_id):
        writer = subprocess.Popen(nc_command(server_ip_addr, version=domain, protocol=protocol, listen=True),
                                  stdin=subprocess.PIPE,
                                  stdout=null,
                                  stderr=subprocess.PIPE,
                                  close_fds=True,
                                  env={'LD_PRELOAD': shim.config.path,
                                       'OP_BINDTODEVICE': writer_id})
        server_input = writer.stdin

    if is_server_interface(reader_id):
        reader = subprocess.Popen(nc_command(server_ip_addr, version=domain, protocol=protocol, listen=True),
                                  stdin=subprocess.PIPE,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  close_fds=True,
                                  env={'LD_PRELOAD': shim.config.path,
                                       'OP_BINDTODEVICE': reader_id})
        server_input = reader.stdin

    try:
        # Use a writable stdin as a proxy for a listening server.  Using
        # the verbose option to nc can trigger a getnameinfo() call, which
        # can fail inside a container.
        wait_until_writable(server_input)
    except Exception as e:
        if reader: reader.kill()
        if writer: writer.kill()
        raise e

    client_output = None
    if not is_server_interface(writer_id):
        writer = subprocess.Popen(nc_command(server_ip_addr, version=domain, protocol=protocol, verbose=True),
                                  stdin=subprocess.PIPE,
                                  stdout=null,
                                  stderr=subprocess.PIPE,
                                  close_fds=True,
                                  env={'LD_PRELOAD': shim.config.path,
                                       'OP_BINDTODEVICE': writer_id})
        client_output = writer.stderr

    if not is_server_interface(reader_id):
        reader = subprocess.Popen(nc_command(server_ip_addr, version=domain, protocol=protocol, verbose=True),
                                  stdin=null,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  close_fds=True,
                                  env={'LD_PRELOAD': shim.config.path,
                                       'OP_BINDTODEVICE': reader_id})
        client_output = reader.stderr

    try:
        wait_for_keyword(client_output, "succeeded")
    except Exception as e:
        reader.kill()
        writer.kill()
        raise e

    return reader, writer
Beispiel #28
0
    with open(os.devnull, 'w') as null:
        p = subprocess.Popen(ping_command(ping_binary, dst_ip),
                             stdout=null,
                             stderr=null,
                             env={
                                 'LD_PRELOAD': shim.config.path,
                                 'OP_BINDTODEVICE': src_id
                             })
        p.wait()
        expect(p.returncode).to(equal(0))


with description('Dataplane,', 'dataplane') as self:

    with before.all:
        service = Service(CONFIG.service('dataplane'))
        self.process = service.start()
        self.api = client.api.InterfacesApi(service.client())

    with description('ipv4,'):
        with description('ping,'):
            with before.all:
                # By default, ping is a privileged process.  We need it unprivileged
                # to use LD_PRELOAD, so just make a copy as a regular user.
                self.temp_dir = tempfile.mkdtemp()
                shutil.copy(PING, self.temp_dir)
                self.temp_ping = os.path.join(self.temp_dir,
                                              os.path.basename(PING))
                expect(os.path.isfile(self.temp_ping))

            with description('client interface,'):
Beispiel #29
0
import os

import client.api
import client.models
from common import Config, Service
from common.matcher import be_valid_port, raise_api_exception


CONFIG = Config(os.path.join(os.path.dirname(__file__), os.environ.get('MAMBA_CONFIG', 'config.yaml')))


with description('Ports,', 'ports') as self:
    with description('REST API,'):

        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.api = client.api.PortsApi(service.client())

        with description('list,'):
            with description('unfiltered,'):
                with it('returns valid ports'):
                    ports = self.api.list_ports()
                    expect(ports).not_to(be_empty)
                    for port in ports:
                        expect(port).to(be_valid_port)

            with description('filtered,'):
                with description('known existing kind,'):
                    with it('returns valid ports of that kind'):
                        ports = self.api.list_ports(kind='dpdk')
Beispiel #30
0
    ta.id = id
    ana = ana_api_client.create_packet_analyzer(ta)
    expect(ana).to(be_valid_packet_analyzer)

    return ana


CONFIG = Config(
    os.path.join(os.path.dirname(__file__),
                 os.environ.get('MAMBA_CONFIG', 'config.yaml')))

with description('MAC Learning,', 'learning') as self:
    with description('REST API'):

        with before.all:
            service = Service(CONFIG.service())
            self.process = service.start()
            self.ana_api = client.api.PacketAnalyzersApi(service.client())
            self.gen_api = client.api.PacketGeneratorsApi(service.client())
            self.intf_api = client.api.InterfacesApi(service.client())
            if not check_modules_exists(service.client(), 'packet-generator',
                                        'packet-analyzer'):
                self.skip()

        with description('packet-generator, '):
            with description('IPv4, '):
                with before.each:
                    self.source_ip = "192.168.22.10"
                    self.source_mac = "aa:bb:cc:dd:ee:01"
                    self.target_ip = "192.168.22.1"
                    self.intf1 = ipv4_interface(self.intf_api.api_client,