Skip to content

Commit bf02a31

Browse files
committed
Version 1.4
1 parent 0c4d5a7 commit bf02a31

File tree

15 files changed

+357
-153
lines changed

15 files changed

+357
-153
lines changed

ChangeLog

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
- 1.4 Sep 18 2015
2+
* Cleanup a lot of code
3+
* Add additional comments
4+
* Support retries when only one worker is configured
5+
* Support configurable buffer sizes
6+
* Add timestamps to all log messages
7+
* Fix usage text
8+
* Update README to be nicer format
9+
* Add --version support
10+
* remove old install.sh script
11+
* Other Minor adjustments and cleanups

MANIFEST.in

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
recursive-include pumpkinlb *.py
2+
include ChangeLog
3+
include setup.py
4+
include MANIFEST.in
5+
include README.md
6+
include README.rst
7+
include PumpkinLB.py
8+
include LICENSE
9+
include example.cfg

PumpkinLB.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
#!/usr/bin/python2
2+
#
3+
# PumpkinLB Copyright (c) 2014-2015 Tim Savannah under GPLv3.
4+
# You should have received a copy of the license as LICENSE
5+
#
6+
# See: https://github.com/kata198/PumpkinLB
27

38
import math
49
import multiprocessing
@@ -11,9 +16,17 @@
1116
import traceback
1217
import time
1318

19+
from pumpkinlb import __version__ as pumpkin_version
20+
1421
from pumpkinlb.config import PumpkinConfig, PumpkinMapping
15-
from pumpkinlb.usage import printUsage, printConfigHelp
22+
from pumpkinlb.usage import printUsage, printConfigHelp, getVersionStr
1623
from pumpkinlb.listener import PumpkinListener
24+
from pumpkinlb.constants import GRACEFUL_SHUTDOWN_TIME
25+
26+
from pumpkinlb.log import logmsg, logerr
27+
28+
29+
1730

1831
if __name__ == '__main__':
1932

@@ -25,6 +38,9 @@
2538
elif arg == '--help-config':
2639
printConfigHelp(sys.stdout)
2740
sys.exit(0)
41+
elif arg == '--version':
42+
sys.stdout.write(getVersionStr() + '\n')
43+
sys.exit(0)
2844
elif configFilename is not None:
2945
sys.stderr.write('Too many arguments.\n\n')
3046
printUsage(sys.stderr)
@@ -45,12 +61,14 @@
4561
printConfigHelp(sys.stderr)
4662
sys.exit(1)
4763

64+
bufferSize = pumpkinConfig.getOptionValue('buffer_size')
65+
logmsg('Configured buffer size = %d bytes\n' %(bufferSize,))
66+
4867
mappings = pumpkinConfig.getMappings()
4968
listeners = []
5069
for mappingAddr, mapping in mappings.iteritems():
51-
listenerArgs = mapping.getListenerArgs()
52-
sys.stdout.write('Starting up listener: ' + str(listenerArgs) + '\n')
53-
listener = PumpkinListener(*listenerArgs)
70+
logmsg('Starting up listener on %s:%d with mappings: %s\n' %(mapping.localAddr, mapping.localPort, str(mapping.workers)))
71+
listener = PumpkinListener(mapping.localAddr, mapping.localPort, mapping.workers, bufferSize)
5472
listener.start()
5573
listeners.append(listener)
5674

@@ -60,42 +78,39 @@
6078
def handleSigTerm(*args):
6179
global listeners
6280
global globalIsTerminating
63-
sys.stderr.write('CALLED\n')
81+
# sys.stderr.write('CALLED\n')
6482
if globalIsTerminating is True:
6583
return # Already terminating
6684
globalIsTerminating = True
67-
sys.stdout.write('Caught signal, shutting down listeners...\n')
85+
logerr('Caught signal, shutting down listeners...\n')
6886
for listener in listeners:
6987
try:
7088
os.kill(listener.pid, signal.SIGTERM)
7189
except:
7290
pass
73-
sys.stderr.write('Sent signal to children, waiting up to 4 seconds then trying to clean up\n')
91+
logerr('Sent signal to children, waiting up to 4 seconds then trying to clean up\n')
7492
time.sleep(1)
7593
startTime = time.time()
7694
remainingListeners = listeners
7795
remainingListeners2 = []
7896
for listener in remainingListeners:
79-
sys.stderr.write('Waiting on %d...\n' %(listener.pid,))
80-
sys.stderr.flush()
97+
logerr('Waiting on %d...\n' %(listener.pid,))
8198
listener.join(.05)
8299
if listener.is_alive() is True:
83100
remainingListeners2.append(listener)
84101
remainingListeners = remainingListeners2
85-
sys.stderr.write('Remaining (%d) listeners are: %s\n' %(len(remainingListeners), [listener.pid for listener in remainingListeners]))
86-
sys.stderr.flush()
102+
logerr('Remaining (%d) listeners are: %s\n' %(len(remainingListeners), [listener.pid for listener in remainingListeners]))
87103

88104
afterJoinTime = time.time()
89105

90106
if remainingListeners:
91107
delta = afterJoinTime - startTime
92-
remainingSleep = int(6 - math.floor(afterJoinTime - startTime))
108+
remainingSleep = int(GRACEFUL_SHUTDOWN_TIME - math.floor(afterJoinTime - startTime))
93109
if remainingSleep > 0:
94110
anyAlive = False
95111
# If we still have time left, see if we are just done or if there are children to clean up using remaining time allotment
96112
if threading.activeCount() > 1 or len(multiprocessing.active_children()) > 0:
97-
sys.stderr.write('Listener closed in %1.2f seconds. Waiting up to %d seconds before terminating.\n' %(delta, remainingSleep))
98-
sys.stderr.flush()
113+
logerr('Listener closed in %1.2f seconds. Waiting up to %d seconds before terminating.\n' %(delta, remainingSleep))
99114
thisThread = threading.current_thread()
100115
for i in range(remainingSleep):
101116
allThreads = threading.enumerate()
@@ -117,18 +132,15 @@ def handleSigTerm(*args):
117132
time.sleep(1)
118133

119134
if anyAlive is True:
120-
sys.stderr.write('Could not kill in time.\n')
135+
logerr('Could not kill in time.\n')
121136
else:
122-
sys.stderr.write('Shutdown successful after %1.2f seconds.\n' %( time.time() - startTime))
123-
sys.stderr.flush()
137+
logerr('Shutdown successful after %1.2f seconds.\n' %( time.time() - startTime))
124138

125139
else:
126-
sys.stderr.write('Listener timed out in closing, exiting uncleanly.\n')
127-
sys.stderr.flush()
140+
logerr('Listener timed out in closing, exiting uncleanly.\n')
128141
time.sleep(.05) # Why not? :P
129142

130-
sys.stdout.write('exiting...\n')
131-
sys.stdout.flush()
143+
logmsg('exiting...\n')
132144
signal.signal(signal.SIGTERM, signal.SIG_DFL)
133145
signal.signal(signal.SIGINT, signal.SIG_DFL)
134146
sys.exit(0)

README.md

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,67 @@
1-
# PumpkinLB
2-
A simple, fast, pure-python load balancer
1+
PumpkinLB
2+
=========
33

4-
Description
5-
===========
64

7-
PumpkinLB is a fast multi-process TCP load balancer, compatible with: Linux, Cygwin, and Windows environments. It listens for requests on local ports, and farms them out to any number of workers. You can use it to very quickly setup a load balancer, e.x. from 1 front point to 5 different apache servers.
5+
PumpkinLB is a fast multi-process TCP load balancer / port forwarder, compatible with: Linux, Cygwin, and Windows environments.
6+
7+
8+
**Processing**
9+
10+
PumpkinLB listens for requests on ports local to the machine on which it is running, and farms them out to any number of workers.
11+
12+
You can use it to very quickly setup a load balancer, e.x. from 1 entry-point to 5 different apache workers on various servers.
813

914
Each incoming port is waited-on by a distinct process, and each connection is yet another process, thus it performs very well even under heavy load.
1015

11-
Usage
12-
=====
16+
Requests are generally handled round-robin between the various workers.
17+
If a request fails on a backend worker, it will be retried on another random worker until it succeeds, and a message will be logged.
18+
19+
**Usage**
20+
1321

1422
Execute by running PumpkinLB.py [cfgFile]
1523

16-
Where cfgFile is the path to your config file. There is a sample "example.cfg" included.
24+
Where [cfgFile] is the path to your config file. There is a sample "example.cfg" included.
1725

18-
Config file is broken up into sections, definable by [$SectionName], followed by variables in format of key=value.
1926

20-
Sections:
27+
**Config**
2128

22-
[options]
23-
pre_resolve_workers=0/1 [Default 1] Any workers defined with a hostname will be evaluated at the time the config is read.
24-
This is preferable as it saves a DNS trip for every request, and should be enabled
25-
unless your DNS is likely to change and you want the workers to match the change.
29+
The Config file is broken up into sections, definable by [$SectionName], followed by variables in format of key=value.
2630

27-
[mapping]
28-
localaddr:inport=worker1:port,worker2:port... Listen on interface defined by "localaddr" on port "inport". Farm out to worker addresses and ports. Ex: 192.168.1.100:80=10.10.0.1:5900,10.10.0.2:5900
29-
or
30-
inport=worker1:port,worker2:port... Listen on all interfaces on port "inport", and farm out to worker addresses with given ports. Ex: 80=10.10.0.1:5900,10.10.0.2:5900
31+
Sections:
3132

3233

33-
So an example to listen on port 80 localhost and farm out to 3 apache servers on your local subnet:
34+
*[options]*
35+
36+
* pre\_resolve\_workers=0/1 - Default 1
37+
38+
Any workers defined with a hostname will be evaluated at the time the config is read.
39+
40+
This is preferable as it saves a DNS trip for every request, and should be enabled
3441

35-
80=192.168.1.100:80,192.168.1.101:80,192.168.1.102:80
42+
unless your DNS is likely to change and you want the workers to match the change.
3643

3744

45+
*[mapping]*
46+
47+
* localaddr:inport=worker1:port,worker2:port...
48+
49+
Listen on interface defined by "localaddr" on port "inport". Farm out to worker addresses and ports.
50+
Ex: 192.168.1.100:80=10.10.0.1:5900,10.10.0.2:5900
51+
52+
* inport=worker1:port,worker2:port...
53+
54+
Listen on all interfaces on port "inport", and farm out to worker addresses with given ports.
55+
Ex: 80=10.10.0.1:5900,10.10.0.2:5900
56+
57+
58+
59+
So an example to listen on port 80 localhost and farm out to 3 apache servers on your local subnet:
60+
61+
80=192.168.1.100:80,192.168.1.101:80,192.168.1.102:80
3862

39-
Sending SIGTERM, SIGINT, or pressing control+c will do a graceful shutdown (it will wait for up to 4 seconds to finish any active requests, and then terminate).
4063

64+
**Graceful Shutdown**
4165

42-
Requests are generally handled round-robin between the various workers. If a request fails on a backend worker, it will be retried on another random worker until it succeeds, and a message will be logged.
66+
Sending SIGTERM, SIGINT, or pressing control+c will do a graceful shutdown (it will wait for up to 6 seconds to finish any active requests, and then terminate).
4367

README.rst

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
PumpkinLB
2+
=========
3+
4+
5+
PumpkinLB is a fast multi-process TCP load balancer / port forwarder, compatible with: Linux, Cygwin, and Windows environments.
6+
7+
8+
**Processing**
9+
10+
PumpkinLB listens for requests on ports local to the machine on which it is running, and farms them out to any number of workers.
11+
12+
You can use it to very quickly setup a load balancer, e.x. from 1 entry-point to 5 different apache workers on various servers.
13+
14+
Each incoming port is waited-on by a distinct process, and each connection is yet another process, thus it performs very well even under heavy load.
15+
16+
Requests are generally handled round-robin between the various workers.
17+
If a request fails on a backend worker, it will be retried on another random worker until it succeeds, and a message will be logged.
18+
19+
**Usage**
20+
21+
22+
Execute by running PumpkinLB.py [cfgFile]
23+
24+
Where [cfgFile] is the path to your config file. There is a sample "example.cfg" included.
25+
26+
27+
**Config**
28+
29+
The Config file is broken up into sections, definable by [$SectionName], followed by variables in format of key=value.
30+
31+
Sections:
32+
33+
34+
*[options]*
35+
36+
* pre_resolve_workers=0/1 - Default 1
37+
38+
Any workers defined with a hostname will be evaluated at the time the config is read.
39+
40+
This is preferable as it saves a DNS trip for every request, and should be enabled
41+
42+
unless your DNS is likely to change and you want the workers to match the change.
43+
44+
45+
*[mapping]*
46+
47+
* localaddr:inport=worker1:port,worker2:port...
48+
49+
Listen on interface defined by "localaddr" on port "inport". Farm out to worker addresses and ports.
50+
51+
Ex: 192.168.1.100:80=10.10.0.1:5900,10.10.0.2:5900
52+
53+
* inport=worker1:port,worker2:port...
54+
55+
Listen on all interfaces on port "inport", and farm out to worker addresses with given ports.
56+
57+
Ex: 80=10.10.0.1:5900,10.10.0.2:5900
58+
59+
60+
61+
So an example to listen on port 80 localhost and farm out to 3 apache servers on your local subnet:
62+
63+
80=192.168.1.100:80,192.168.1.101:80,192.168.1.102:80
64+
65+
66+
**Graceful Shutdown**
67+
68+
Sending SIGTERM, SIGINT, or pressing control+c will do a graceful shutdown (it will wait for up to 6 seconds to finish any active requests, and then terminate).
69+

example.cfg

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
[options]
2+
buffer_size=4096
3+
14
[mappings]
25
24003=192.168.1.100:80,192.168.1.101:80,192.168.1.102:80,192.168.1.103:80
3-
24004=google.com:80
6+
24004=example.com:80

install.sh

Lines changed: 0 additions & 15 deletions
This file was deleted.

pumpkinlb/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# PumpkinLB Copyright (c) 2014-2015 Tim Savannah under GPLv3.
2+
# You should have received a copy of the license as LICENSE
3+
#
4+
# See: https://github.com/kata198/PumpkinLB
5+
6+
__version__ = '1.4'
7+
__version_tuple__ = (1, 4, 0)

0 commit comments

Comments
 (0)