Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get random socket address #47

Open
wants to merge 30 commits into
base: osx_support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
811797e
Update .travis.yml
y-luis-rojo Jul 26, 2018
fd5715f
Merge pull request #37 from y-luis/patch-2
Jul 27, 2018
67370d7
Fix sipp build. Test with default Trusty.
y-luis-rojo Aug 20, 2018
2a90f5d
Merge pull request #41 from y-luis/hotfix/fix-sipp-build
vodik Aug 20, 2018
4c955a0
remote-host as last position argument
Jul 31, 2018
06bff09
Allow disable screen_file argument.
Jul 31, 2018
b2cd54d
Merge pull request #40 from y-luis/feature/support-disabling-logs
Aug 21, 2018
8d937bf
Merge pull request #38 from y-luis/hotfix/sipp-command-argument-order
Aug 21, 2018
ff6ef74
Add `auth_username` in command sipp_spec
Jun 26, 2019
5f6a05d
Fix missing space for `auth_username` in command sipp_spec
Jul 3, 2019
436431c
Add Authentication section in README
Jul 3, 2019
7ade10c
Fix Authentication link in README
Jul 3, 2019
8f8093f
Merge pull request #49 from laerus/auth_username
goodboy Jul 3, 2019
3c80ff1
Go easy on ol' `sippy_cup`
goodboy Jul 4, 2019
5d25d26
Drop pypy from CI for now
goodboy Jul 4, 2019
a3743ab
Merge pull request #52 from SIPp/drop_pypy_from_ci
goodboy Jul 4, 2019
1adf41f
Add test for #36
Oct 12, 2018
79b7534
Fix #36
Oct 12, 2018
9dddfe8
Add agent name to options merge logging
Oct 12, 2018
e2ef59b
Use pluggy 0.11.0
goodboy May 27, 2019
36a9b63
Merge pull request #46 from SIPp/proxyaddr_fix
goodboy Jul 4, 2019
1e6f97c
Support disabling logging files.
Jul 31, 2018
551042b
Log sipp stdout.
y-luis-rojo Aug 20, 2018
d06379c
Revert "Log sipp stdout."
y-luis-rojo Aug 20, 2018
650eec1
Beta release.
y-luis-rojo Aug 20, 2018
e890e23
Use a plain old select loop
Jun 17, 2016
611ead8
Tweak unit test exit code checking
Jun 17, 2016
d522d6b
Fix default host retrieving random socket from local OS.
y-luis-rojo Oct 14, 2018
00694f3
Git ignore PyCharm and pytest related folders.
y-luis-rojo Oct 14, 2018
466ffa4
Revert setup status
y-luis-rojo Jul 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ htmlcov/
nosetests.xml
coverage.xml
*,cover
.pytest_cache

# Translations
*.mo
Expand All @@ -55,3 +56,6 @@ docs/_build/

# PyBuilder
target/

# PyCharm
.idea/
13 changes: 7 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
language: python
sudo: false
dist: precise

cache:
- bundler
Expand All @@ -10,8 +8,9 @@ python:
- 2.7
- 3.5
- 3.6
# - 3.7
- nightly
- pypy
# - pypy
# - pypy3

addons:
Expand All @@ -29,9 +28,11 @@ install:
- pip list

before_script:
- git clone https://github.com/SIPp/sipp.git
- cd sipp
- ./build.sh
- wget https://github.com/SIPp/sipp/releases/download/v3.5.2/sipp-3.5.2.tar.gz
- tar -xvzf sipp-3.5.2.tar.gz
- cd sipp-3.5.2
- ./configure
- make
- export PATH="$PWD:$PATH"
- cd ..

Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Python configuring and launching the infamous
## It doesn't try to

- Auto-generate SIPp XML scripts like [sippy_cup](https://github.com/mojolingo/sippy_cup)
* we believe this is the wrong way to work around the problem of SIPp's shitty XML control language
* `pysipp` in no way tries to work around the problem of SIPp's awful
XML control language; your current scenario scripts are compatible!


## Basic Usage
Expand All @@ -41,6 +42,15 @@ uas(block=False) # returns a `pysipp.launch.PopenRunner` instance by default
uac() # run client synchronously
```

## Authentication
When using the `[authentication]` [sipp keyword](https://sipp.readthedocs.io/en/latest/scenarios/keywords.html#authentication)
in scenarios, providing the credentials can be done with the
`auth_username` and `auth_password` arguments, for example:

```python
pysipp.client(auth_username='sipp', auth_password='sipp-pass')
```

## Multiple Agents
For multi-UA orchestrations we can use a `pysipp.scenario`.
The scenario from above is the default agent configuration:
Expand Down Expand Up @@ -73,6 +83,15 @@ scen = pysipp.scenario(dirpath='path/to/test_scenario/',
scen()
```

**pysipp** by default uses `-screen_file` SIPp argument to redirect output,
but this argument is only available in SIPp version >= [3.5.0](https://sourceforge.net/p/sipp/mailman/message/34041962/),
for lower versions to run properly, this argument must be
disable setting `enable_screen_file` to `False`:

```python
scen = pysipp.scenario(enable_screen_file=False)
```

If you've got multiple such scenario directories you can iterate over
them:

Expand Down Expand Up @@ -157,7 +176,8 @@ pysipp.utils.log_to_stderr("DEBUG")
### Applying default settings
For now see [#4](https://github.com/SIPp/pysipp/issues/4)

More to come...
## More to come?
- document attributes / flags
- writing plugins
- using a `pysipp_conf.py`
- remote execution
Expand Down
8 changes: 4 additions & 4 deletions pysipp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ def scenario(dirpath=None, proxyaddr=None, autolocalsocks=True,
scenkwargs=scenkwargs
)

if proxyaddr:
assert isinstance(
proxyaddr, tuple), 'proxyaddr must be a (addr, port) tuple'
scen.clientdefaults.proxyaddr = proxyaddr
if proxyaddr:
assert isinstance(
proxyaddr, tuple), 'proxyaddr must be a (addr, port) tuple'
scen.clientdefaults.proxyaddr = proxyaddr

return scen

Expand Down
27 changes: 17 additions & 10 deletions pysipp/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@


def tuple_property(attrs):

def getter(self):
tup = tuple(getattr(self, attr) for attr in attrs)
if all(tup):
Expand Down Expand Up @@ -77,10 +76,11 @@ def is_client(self):
def is_server(self):
return 'uas' in self.name.lower()

def iter_logfile_items(self, types_attr='_log_types'):
def iter_logfile_items(self, types_attr='_log_types', enable_screen_file=True):
for name in getattr(self, types_attr):
attr_name = name + '_file'
yield attr_name, getattr(self, attr_name)
if name != 'screen' or enable_screen_file:
attr_name = name + '_file'
yield attr_name, getattr(self, attr_name)

def iter_toconsole_items(self):
yield 'screen_file', self.screen_file
Expand Down Expand Up @@ -118,11 +118,11 @@ def enable_tracing(self):
attr_name = 'trace_' + name
setattr(self, attr_name, True)

def enable_logging(self, logdir=None, debug=False):
def enable_logging(self, logdir=None, debug=False, enable_screen_file=True):
"""Enable agent logging by appending appropriately named log file
arguments to the underlying command.
"""
logattrs = self.iter_logfile_items()
logattrs = self.iter_logfile_items(enable_screen_file=enable_screen_file)
if debug:
logattrs = itertools.chain(
logattrs,
Expand Down Expand Up @@ -233,8 +233,9 @@ class ScenarioType(object):

If called it will invoke the standard run hooks.
"""

def __init__(self, agents, defaults, clientdefaults=None,
serverdefaults=None, confpy=None):
serverdefaults=None, confpy=None, logs=True, enable_screen_file=True):
# agents iterable in launch-order
self._agents = agents
ua_attrs = UserAgent.keys()
Expand All @@ -253,6 +254,8 @@ def __init__(self, agents, defaults, clientdefaults=None,

# hook module
self.mod = confpy
self.enable_screen_file = enable_screen_file
self.logs = logs

@property
def agents(self):
Expand Down Expand Up @@ -330,6 +333,7 @@ def prepare_agent(self, agent):
"""Return a new agent with all default settings applied from this
scenario
"""

def merge(dicts):
"""Merge dicts without clobbering up to 1 level deep's worth of
sub-dicts
Expand Down Expand Up @@ -359,12 +363,15 @@ def merge(dicts):
# apply defaults
ordered = [self._defaults, secondary, agent.todict()]
for name, defs in zip(['defaults', dname, 'agent.todict()'], ordered):
log.debug("'{}' contents:\n{}".format(name, defs))
log.debug("{} '{}' contents:\n{}".format(agent.name, name, defs))

params = merge(ordered)
log.debug("merged contents:\n{}".format(params))
log.debug("{} merged contents:\n{}".format(agent.name, params))
ua = UserAgent(defaults=params)
ua.enable_logging()

ua.enable_logging(enable_screen_file=self.enable_screen_file)
if self.logs:
ua.enable_logging()

# call post defaults hook
plugin.mng.hook.pysipp_post_ua_defaults(ua=ua)
Expand Down
5 changes: 3 additions & 2 deletions pysipp/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ def todict(self):
# contact info
'{prefix} ',
'{bin_path} ',
('{remote_host}', AddrField), # NOTE: no space
':{remote_port} ',
('-i {local_host} ', AddrField),
'-p {local_port} ',
'-s {uri_username} ',
Expand All @@ -214,6 +212,7 @@ def todict(self):
# SIP vars
'-cid_str {cid_str} ',
'-base_cseq {base_cseq} ',
'-au {auth_username} ',
'-ap {auth_password} ',
# load settings
'-r {rate} ',
Expand Down Expand Up @@ -244,6 +243,8 @@ def todict(self):
('-trace_logs {trace_log}', BoolField),
('-trace_screen {trace_screen}', BoolField),
('-error_overwrite {error_overwrite}', BoolField),
('{remote_host}', AddrField), # NOTE: no space
':{remote_port}',
]


Expand Down
44 changes: 33 additions & 11 deletions pysipp/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ def __init__(
self,
subprocmod=subprocess,
osmod=os,
poller=select.epoll,
select=select.select,
):
# these could optionally be rpyc proxy objs
self.spm = subprocmod
self.osm = osmod
self.poller = poller()
self.select = select
# collector thread placeholder
self._waiter = None
# store proc results
Expand Down Expand Up @@ -67,11 +67,9 @@ def __call__(self, cmds, block=True, rate=300, **kwargs):
stderr=sp.PIPE
)
fd = proc.stderr.fileno()
log.debug("registering fd '{}' for pid '{}'".format(
fd, proc.pid))
log.debug("registering fd '{}' for pid '{}'".format(fd, proc.pid))
fds2procs[fd] = self._procs[cmd] = proc
# register for stderr hangup events
self.poller.register(proc.stderr.fileno(), select.EPOLLHUP)

# limit launch rate
time.sleep(1. / rate)

Expand All @@ -87,15 +85,39 @@ def _wait(self, fds2procs):
signalled = None
left = len(fds2procs)
collected = 0
p2fd = {p: p.stderr for p in fds2procs.values()}
stderrs = {p: [] for p in fds2procs.values()}

# wait on stderr hangup events
while collected < left:
pairs = self.poller.poll() # wait on hangup events
log.debug("received hangup for pairs '{}'".format(pairs))
for fd, status in pairs:
try:
fds, _, _ = self.select(list(p2fd.values()), [], [])
except ValueError:
# all fds are now closed
hungup = p2fd.keys()
else:
hungup = []
for proc in (fds2procs[fd.fileno()] for fd in fds):
data = proc.stderr.read()
if data != '':
stderrs[proc].append(data) # append stderr
continue

p2fd.pop(proc)
hungup.append(proc)

if not hungup:
continue

for proc in hungup:
log.debug("received hangup for pid '{}'".format(proc.pid))
collected += 1
proc = fds2procs[fd]
# attach streams so they can be read more then once
log.debug("collecting streams for {}".format(proc))
proc.streams = Streams(*proc.communicate()) # timeout=2))
proc.streams = Streams(
stdout=proc.communicate()[0],
stderr=''.join(stderrs[proc]),
) # timeout=2))
if proc.returncode != 0 and not signalled:
# stop all other agents if there is a failure
signalled = self.stop()
Expand Down
4 changes: 2 additions & 2 deletions pysipp/netplug.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ def getsockaddr(host, family=socket.AF_INET, port=0, sockmod=socket):
binding to an ip, acquiring a random port and then
closing the socket and returning that address.

..warning:: Obviously this is not guarateed to be an unused address
..warning:: Obviously this is not guaranteed to be an unused address
since we don't actually keep it bound, so there may be a race with
other processes acquiring the addr before our SIPp process re-binds.
"""
for fam, stype, proto, _, sa in socket.getaddrinfo(
host, port, family, socket.SOCK_DGRAM, 0, socket.AI_PASSIVE,
host if host else '127.0.0.1', port, family, socket.SOCK_DGRAM, 0, socket.AI_PASSIVE,
):
s = socket.socket(family, stype, proto)
s.bind(sa)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
url='https://github.com/SIPp/pysipp',
platforms=['linux'],
packages=['pysipp', 'pysipp.cli'],
install_requires=['pluggy==0.3.1'],
install_requires=['pluggy==0.11.0'],
tests_require=['pytest'],
entry_points={
'console_scripts': [
Expand Down
6 changes: 3 additions & 3 deletions tests/test_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ def test_agent_fails():
assert uasproc.streams.stderr
assert uasproc.returncode == 255, uasproc.streams.stderr

# killed by signal
# times out (can't do by signal - SIPp issue #176)
uacproc = runner.get(timeout=0)[uac.render()]
# assert not uacproc.streams.stderr # sometimes this has a log msg?
ret = uacproc.returncode
# killed by SIGUSR1 or terminates before it starts (racy)
assert ret == -10 or ret == 0
# timed out or terminates before it starts (racy)
assert ret == -10 or ret == 0 or ret == 1


def test_default_scen(default_agents):
Expand Down
19 changes: 19 additions & 0 deletions tests/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ def test_confpy_hooks(scendir):
assert agent.uri_username == 'doggy'


def test_proxyaddr_with_scendir(scendir):
"""When building a scenario from a xml file directory the
`proxyaddr` kwarg should be assigned.
"""
remoteaddr = ('9.9.9.9', 80)
scen = pysipp.scenario(
dirpath=scendir + '/default_with_confpy',
proxyaddr=remoteaddr
)

assert scen.clientdefaults.proxyaddr == remoteaddr
for name, cmd in scen.cmditems():
if name == 'uac':
assert "-rsa '{}':'{}'".format(*remoteaddr) in cmd
assert "'{}':'{}'".format(*scen.clientdefaults.destaddr) in cmd
elif name == 'uas':
assert "-rsa '{}':'{}'".format(*remoteaddr) not in cmd


def test_sync_run(scenwalk):
"""Ensure all scenarios in the test run to completion in synchronous mode
"""
Expand Down