Skip to content
This repository has been archived by the owner on Oct 28, 2020. It is now read-only.

Commit

Permalink
Adding tests for different sidekick functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
a5huynh committed Jun 19, 2015
1 parent b774a3c commit 356f56f
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[run]
omit=*.pyenvs*,*site-packages*,tests/*

3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
python_paths = tests

1 change: 1 addition & 0 deletions run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
py.test --cov . --cov-report html --capture=no
121 changes: 80 additions & 41 deletions sidekick.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# @Author: ahuynh
# @Date: 2015-06-10 16:51:36
# @Last Modified by: ahuynh
# @Last Modified time: 2015-06-12 10:15:05
# @Last Modified time: 2015-06-18 21:16:25
'''
The sidekick should essentially replace job of the following typical
bash script that is used to announce a service to ETCD.
Expand Down Expand Up @@ -75,6 +75,77 @@ def check_name( container, name ):
return False


def find_matching_container( containers, args ):
'''
Given the name of the container:
- Find the matching container
- Note the open ports for that container
- Generate a UUID based on the name, ip, and port
Return a dictionary of generated URIs mapped to the UUID for each open
port, using the following format:
UUID: {
'ip': IP that was passed in via args.ip,
'port': Open port,
'uri': IP:PORT
}
'''
# Find the matching container
matching = {}
for container in containers:
if not check_name( container, args.name ):
continue

ports = public_ports( container )

# TODO: Handle multiple public ports
# Right now we grab the first port in the list and announce the
# server using that IP
if len( ports ) == 0:
raise Exception( 'Container has no public ports' )

for port in ports:
port = port[ 'PublicPort' ]

# Create a UUID
m = hashlib.md5()
m.update( args.name.encode('utf-8') )
m.update( args.ip.encode('utf-8') )
m.update( str( port ).encode('utf-8') )
uuid = m.hexdigest()

# Store the details
uri = '{}:{}'.format( args.ip, port )
matching[ uuid ] = { 'ip': args.ip, 'port': port, 'uri': uri }

return matching


def health_check( service ):
'''
Check the health of `service`.
This is done using a socket to test if the specified PublicPort is
responding to requests.
'''
healthy = False

try:
s = socket.socket()
s.connect( ( service['ip'], service['port'] ) )
except ConnectionRefusedError:
logger.error( 'tcp://{ip}:{port} health check FAILED'.format(**service) )
healthy = False
else:
s.close()
logger.error( 'tcp://{ip}:{port} health check SUCCEEDED'.format(**service) )
healthy = True
s.close()

return healthy


def public_ports( container ):
''' Return a list of public ports for <container> '''
return list(filter( lambda x: 'PublicPort' in x, container['Ports'] ))
Expand All @@ -90,45 +161,23 @@ def main():
logger.info( 'Using {}'.format( args.docker ) )
kwargs['base_url'] = args.docker

docker_client = Client(**kwargs)

# Connect to ECTD
etcd_client = etcd.Client()

etcd_folder = os.path.join( args.prefix, args.domain )
logger.debug( 'Announcing to {}'.format( etcd_folder ) )

# Find the matching container
docker_client = Client(**kwargs)
try:
containers = docker_client.containers()
except Exception:
logger.error( containers )
except Exception as e:
logger.error( e )
sys.exit( 'FAILURE - Unable to connect Docker. Is it running?' )

# Find the matching container
matching = {}
for container in containers:
if check_name( container, args.name ):
ports = public_ports( container )

# TODO: Handle multiple public ports
# Right now we grab the first port in the list and announce the
# server using that IP
if len( ports ) == 0:
raise Exception( 'Container has no public ports' )

for port in ports:
port = port[ 'PublicPort' ]

# Create a UUID
m = hashlib.md5()
m.update( args.name.encode('utf-8') )
m.update( args.ip.encode('utf-8') )
m.update( str( port ).encode('utf-8') )
uuid = m.hexdigest()

# Store the details
uri = '{}:{}'.format( args.ip, port )
matching[ uuid ] = { 'ip': args.ip, 'port': port, 'uri': uri }
matching = find_matching_container( containers, args )

# Main health checking loop
while True:
Expand All @@ -138,25 +187,15 @@ def main():

full_key = os.path.join( etcd_folder, key )

try:
s = socket.socket()
s.connect( ( value['ip'], value['port'] ) )
except ConnectionRefusedError:
logger.error( 'tcp://{ip}:{port} health check FAILED'.format(**value) )
healthy = False
else:
s.close()
logger.error( 'tcp://{ip}:{port} health check SUCCEEDED'.format(**value) )
healthy = True
s.close()
healthy = health_check( value )

try:
if not healthy:
# Remove this server from ETCD if it exists
etcd_client.delete( full_key )
else:
# Announce this server to ETCD
etcd_client.set( full_key, value['uri'] )
# Announce this server to ETCD
etcd_client.set( full_key, value['uri'] )
except etcd.EtcdException as e:
logging.error( e )

Expand Down
Empty file added tests/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions tests/test_sidekick.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author: ahuynh
# @Date: 2015-06-18 20:15:30
# @Last Modified by: ahuynh
# @Last Modified time: 2015-06-18 21:07:08
import unittest

from collections import namedtuple
from sidekick import check_name, find_matching_container, health_check, public_ports

# Used to test command line arguments
Args = namedtuple('Args', ['name', 'ip'])


class TestSidekick( unittest.TestCase ):

def setUp( self ):

self.args = Args( name='test', ip='localhost' )

self.container = {
'Image': 'image:latest',
'Ports': [{
'PrivatePort': 9200,
'IP': '0.0.0.0',
'Type': 'tcp',
'PublicPort': 9200 }, {
'PrivatePort': 9300,
'IP': '0.0.0.0',
'Type': 'tcp',
'PublicPort': 9300}],
'Created': 1427906382,
'Names': ['/test'],
'Status': 'Up 2 days'}

def test_check_name( self ):
''' Test `check_name` functionality '''
self.assertTrue( check_name( self.container, 'test' ) )
self.assertFalse( check_name( self.container, '/test' ) )

def test_find_matching_container( self ):
''' Test `find_matching_container` functionality '''
# Test a successful match
results = find_matching_container( [self.container], self.args )
self.assertEqual( len( results.keys() ), 2 )

# Test an unsuccessful match
no_open_ports = dict( self.container )
no_open_ports['Ports'] = []
with self.assertRaises( Exception ):
find_matching_container( [no_open_ports], self.args )

def test_health_check( self ):
''' Test `health_check` functionality '''
results = find_matching_container( [self.container], self.args )
for value in results.values():
self.assertFalse( health_check( value ) )

def test_public_ports( self ):
''' Test `public_ports` functionality '''
self.assertEquals( len( public_ports( self.container ) ), 2 )

0 comments on commit 356f56f

Please sign in to comment.