Initial commit
This commit is contained in:
commit
87d9e8e07d
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
Example Voting App
|
||||
==================
|
||||
|
||||
This is an example Docker app with multiple services. It is run with Docker Compose and uses Docker Networking to connect containers together.
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
Since this app makes use of Compose's experimental networking support, it must be started with:
|
||||
|
||||
$ cd vote-apps/
|
||||
$ docker-compose --x-networking up -d
|
||||
|
||||
The app will be running on port 5000 on your Docker host, and the results will be on port 5001.
|
25
vote-apps/docker-compose.yml
Normal file
25
vote-apps/docker-compose.yml
Normal file
@ -0,0 +1,25 @@
|
||||
voting-app:
|
||||
build: ./voting-app/.
|
||||
volumes:
|
||||
- ./voting-app:/app
|
||||
ports:
|
||||
- "5000:80"
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
ports: ["6379"]
|
||||
|
||||
worker:
|
||||
build: ./worker
|
||||
|
||||
db:
|
||||
image: postgres:9.4
|
||||
volumes:
|
||||
- "myvolume:/var/lib/postgresql/data"
|
||||
|
||||
result-app:
|
||||
build: ./result-app/.
|
||||
volumes:
|
||||
- ./result-app:/app
|
||||
ports:
|
||||
- "5001:80"
|
15
vote-apps/result-app/Dockerfile
Normal file
15
vote-apps/result-app/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM node:0.10
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
|
||||
ADD package.json /app/package.json
|
||||
RUN npm install && npm ls
|
||||
RUN mv /app/node_modules /node_modules
|
||||
|
||||
ADD . /app
|
||||
|
||||
ENV PORT 80
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["node", "server.js"]
|
20
vote-apps/result-app/package.json
Normal file
20
vote-apps/result-app/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "result-app",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.14.1",
|
||||
"cookie-parser": "^1.4.0",
|
||||
"express": "^4.13.3",
|
||||
"method-override": "^2.3.5",
|
||||
"async": "^1.5.0",
|
||||
"pg": "^4.4.3",
|
||||
"socket.io": "^1.3.7"
|
||||
}
|
||||
}
|
78
vote-apps/result-app/server.js
Normal file
78
vote-apps/result-app/server.js
Normal file
@ -0,0 +1,78 @@
|
||||
var express = require('express'),
|
||||
async = require('async'),
|
||||
pg = require("pg"),
|
||||
cookieParser = require('cookie-parser'),
|
||||
bodyParser = require('body-parser'),
|
||||
methodOverride = require('method-override'),
|
||||
app = express(),
|
||||
server = require('http').Server(app),
|
||||
io = require('socket.io')(server);
|
||||
|
||||
io.set('transports', ['polling']);
|
||||
|
||||
var port = process.env.PORT || 4000;
|
||||
|
||||
io.sockets.on('connection', function (socket) {
|
||||
|
||||
socket.emit('message', { text : 'Welcome!' });
|
||||
|
||||
socket.on('subscribe', function (data) {
|
||||
socket.join(data.channel);
|
||||
});
|
||||
});
|
||||
|
||||
async.retry(
|
||||
{times: 1000, interval: 1000},
|
||||
function(callback) {
|
||||
pg.connect('postgres://postgres@voteapps_db_1/postgres', function(err, client, done) {
|
||||
if (err) {
|
||||
console.error("Failed to connect to db");
|
||||
}
|
||||
callback(err, client);
|
||||
});
|
||||
},
|
||||
function(err, client) {
|
||||
if (err) {
|
||||
return console.err("Giving up");
|
||||
}
|
||||
console.log("Connected to db");
|
||||
getVotes(client);
|
||||
}
|
||||
);
|
||||
|
||||
function getVotes(client) {
|
||||
client.query('SELECT vote, COUNT(id) AS count FROM votes GROUP BY vote', [], function(err, result) {
|
||||
if (err) {
|
||||
console.error("Error performing query: " + err);
|
||||
} else {
|
||||
var data = result.rows.reduce(function(obj, row) {
|
||||
obj[row.vote] = row.count;
|
||||
return obj;
|
||||
}, {});
|
||||
io.sockets.emit("scores", JSON.stringify(data));
|
||||
}
|
||||
|
||||
setTimeout(function() {getVotes(client) }, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser());
|
||||
app.use(methodOverride('X-HTTP-Method-Override'));
|
||||
app.use(function(req, res, next) {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(express.static(__dirname + '/views'));
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
res.sendFile(path.resolve(__dirname + '/views/index.html'));
|
||||
});
|
||||
|
||||
server.listen(port, function () {
|
||||
var port = server.address().port;
|
||||
console.log('App running on port ' + port);
|
||||
});
|
45
vote-apps/result-app/views/app.js
Normal file
45
vote-apps/result-app/views/app.js
Normal file
@ -0,0 +1,45 @@
|
||||
var app = angular.module('catsvsdogs', []);
|
||||
var socket = io.connect({transports:['polling']});
|
||||
|
||||
var bg1 = document.getElementById('background-stats-1');
|
||||
var bg2 = document.getElementById('background-stats-2');
|
||||
|
||||
app.controller('statsCtrl', function($scope){
|
||||
var animateStats = function(a,b){
|
||||
if(a+b>0){
|
||||
var percentA = a/(a+b)*100;
|
||||
var percentB = 100-percentA;
|
||||
bg1.style.width= percentA+"%";
|
||||
bg2.style.width = percentB+"%";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.aPercent = 50;
|
||||
$scope.bPercent = 50;
|
||||
|
||||
var updateScores = function(){
|
||||
socket.on('scores', function (json) {
|
||||
data = JSON.parse(json);
|
||||
var a = parseInt(data.a || 0);
|
||||
var b = parseInt(data.b || 0);
|
||||
|
||||
animateStats(a, b);
|
||||
|
||||
$scope.$apply(function() {
|
||||
if(a + b > 0){
|
||||
$scope.aPercent = a/(a+b) * 100;
|
||||
$scope.bPercent = b/(a+b) * 100;
|
||||
$scope.total = a + b
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var init = function(){
|
||||
document.body.style.opacity=1;
|
||||
updateScores();
|
||||
};
|
||||
socket.on('message',function(data){
|
||||
init();
|
||||
});
|
||||
});
|
41
vote-apps/result-app/views/index.html
Normal file
41
vote-apps/result-app/views/index.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="catsvsdogs">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cats vs Dogs -- Result</title>
|
||||
<base href="/index.html">
|
||||
<meta name = "viewport" content = "width=device-width, initial-scale = 1.0">
|
||||
<meta name="keywords" content="docker-compose, docker, stack">
|
||||
<meta name="author" content="Tutum dev team">
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body ng-controller="statsCtrl" >
|
||||
<div id="background-stats">
|
||||
<div id="background-stats-1">
|
||||
</div><!--
|
||||
--><div id="background-stats-2">
|
||||
</div>
|
||||
</div>
|
||||
<div id="content-container">
|
||||
<div id="content-container-center">
|
||||
<div id="choice">
|
||||
<div class="choice cats">
|
||||
<div class="label">Cats</div>
|
||||
<div class="stat">{{aPercent | number:1}}%</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="choice dogs">
|
||||
<div class="label">Dogs</div>
|
||||
<div class="stat">{{bPercent | number:1}}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="result" ng-if="total > 100">
|
||||
{{total}} votes
|
||||
</div>
|
||||
<script src="socket.io.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
6988
vote-apps/result-app/views/socket.io.js
Normal file
6988
vote-apps/result-app/views/socket.io.js
Normal file
File diff suppressed because it is too large
Load Diff
111
vote-apps/result-app/views/stylesheets/style.css
Normal file
111
vote-apps/result-app/views/stylesheets/style.css
Normal file
@ -0,0 +1,111 @@
|
||||
@import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,600);
|
||||
|
||||
*{
|
||||
box-sizing:border-box;
|
||||
}
|
||||
html,body{
|
||||
margin:0;
|
||||
padding:0;
|
||||
height:100%;
|
||||
font-family: 'Open Sans';
|
||||
}
|
||||
body{
|
||||
opacity:0;
|
||||
transition: all 1s linear;
|
||||
}
|
||||
|
||||
.divider{
|
||||
height: 150px;
|
||||
width:2px;
|
||||
background-color: #C0C9CE;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
float: left;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#background-stats-1{
|
||||
background-color: #2196f3;
|
||||
}
|
||||
|
||||
#background-stats-2{
|
||||
background-color: #00cbca;
|
||||
}
|
||||
|
||||
#content-container{
|
||||
z-index:2;
|
||||
position:relative;
|
||||
margin:0 auto;
|
||||
display:table;
|
||||
padding:10px;
|
||||
max-width:940px;
|
||||
height:100%;
|
||||
}
|
||||
#content-container-center{
|
||||
display:table-cell;
|
||||
text-align:center;
|
||||
vertical-align:middle;
|
||||
}
|
||||
#result{
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
right: 20px;
|
||||
color: #fff;
|
||||
opacity: 0.5;
|
||||
font-size: 45px;
|
||||
font-weight: 600;
|
||||
}
|
||||
#choice{
|
||||
transition: all 300ms linear;
|
||||
line-height:1.3em;
|
||||
background:#fff;
|
||||
box-shadow: 10px 0 0 #fff, -10px 0 0 #fff;
|
||||
vertical-align:middle;
|
||||
font-size:40px;
|
||||
font-weight: 600;
|
||||
width: 450px;
|
||||
height: 200px;
|
||||
}
|
||||
#choice a{
|
||||
text-decoration:none;
|
||||
}
|
||||
#choice a:hover, #choice a:focus{
|
||||
outline:0;
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
#choice .choice{
|
||||
width: 49%;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
text-align: left;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
#choice .choice .label{
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#choice .choice.dogs{
|
||||
color: #00cbca;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#choice .choice.cats{
|
||||
color: #2196f3;
|
||||
float: left;
|
||||
}
|
||||
#background-stats{
|
||||
z-index:1;
|
||||
height:100%;
|
||||
width:100%;
|
||||
position:absolute;
|
||||
}
|
||||
#background-stats div{
|
||||
transition: width 400ms ease-in-out;
|
||||
display:inline-block;
|
||||
margin-bottom:-4px;
|
||||
width:50%;
|
||||
height:100%;
|
||||
}
|
18
vote-apps/voting-app/Dockerfile
Normal file
18
vote-apps/voting-app/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
# Using official python runtime base image
|
||||
FROM python:2.7
|
||||
|
||||
# Set the application directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install our requirements.txt
|
||||
ADD requirements.txt /app/requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Copy our code from the current folder to /app inside the container
|
||||
ADD . /app
|
||||
|
||||
# Make port 5000 available for links and/or publish
|
||||
EXPOSE 80
|
||||
|
||||
# Define our command to be run when launching the container
|
||||
CMD ["python", "app.py"]
|
44
vote-apps/voting-app/app.py
Normal file
44
vote-apps/voting-app/app.py
Normal file
@ -0,0 +1,44 @@
|
||||
from flask import Flask
|
||||
from flask import render_template
|
||||
from flask import request
|
||||
from flask import make_response
|
||||
from utils import connect_to_redis
|
||||
import os
|
||||
import socket
|
||||
import random
|
||||
import json
|
||||
|
||||
option_a = os.getenv('OPTION_A', "Cats")
|
||||
option_b = os.getenv('OPTION_B', "Dogs")
|
||||
hostname = socket.gethostname()
|
||||
|
||||
redis = connect_to_redis("voteapps_redis_1")
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/", methods=['POST','GET'])
|
||||
def hello():
|
||||
voter_id = request.cookies.get('voter_id')
|
||||
if not voter_id:
|
||||
voter_id = hex(random.getrandbits(64))[2:-1]
|
||||
|
||||
vote = None
|
||||
|
||||
if request.method == 'POST':
|
||||
vote = request.form['vote']
|
||||
data = json.dumps({'voter_id': voter_id, 'vote': vote})
|
||||
redis.rpush('votes', data)
|
||||
|
||||
resp = make_response(render_template(
|
||||
'index.html',
|
||||
option_a=option_a,
|
||||
option_b=option_b,
|
||||
hostname=hostname,
|
||||
vote=vote,
|
||||
))
|
||||
resp.set_cookie('voter_id', voter_id)
|
||||
return resp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host='0.0.0.0', port=80, debug=True)
|
2
vote-apps/voting-app/requirements.txt
Normal file
2
vote-apps/voting-app/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Flask
|
||||
Redis
|
129
vote-apps/voting-app/static/stylesheets/style.css
Normal file
129
vote-apps/voting-app/static/stylesheets/style.css
Normal file
@ -0,0 +1,129 @@
|
||||
@import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,600);
|
||||
|
||||
*{
|
||||
box-sizing:border-box;
|
||||
}
|
||||
html,body{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #F7F8F9;
|
||||
height: 100vh;
|
||||
font-family: 'Open Sans';
|
||||
}
|
||||
|
||||
button{
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
-webkit-appearance:none; -webkit-border-radius:0;
|
||||
}
|
||||
|
||||
button i{
|
||||
float: right;
|
||||
padding-right: 30px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
button.a{
|
||||
background-color: #1aaaf8;
|
||||
}
|
||||
|
||||
button.b{
|
||||
background-color: #00cbca;
|
||||
}
|
||||
|
||||
#tip{
|
||||
text-align: left;
|
||||
color: #c0c9ce;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#hostname{
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
color: #8f9ea8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#content-container{
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
display: table;
|
||||
padding: 10px;
|
||||
max-width: 940px;
|
||||
height: 100%;
|
||||
}
|
||||
#content-container-center{
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#content-container-center h3{
|
||||
color: #254356;
|
||||
}
|
||||
|
||||
#choice{
|
||||
transition: all 300ms linear;
|
||||
line-height: 1.3em;
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
font-size: 3em;
|
||||
}
|
||||
#choice a{
|
||||
text-decoration:none;
|
||||
}
|
||||
#choice a:hover, #choice a:focus{
|
||||
outline:0;
|
||||
text-decoration:underline;
|
||||
}
|
||||
|
||||
#choice button{
|
||||
display: block;
|
||||
height: 80px;
|
||||
width: 330px;
|
||||
border: none;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
font-size:18px;
|
||||
font-weight: 700;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
text-align: left;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
#choice button.a:hover{
|
||||
background-color: #1488c6;
|
||||
}
|
||||
|
||||
#choice button.b:hover{
|
||||
background-color: #00a2a1;
|
||||
}
|
||||
|
||||
#choice button.a:focus{
|
||||
background-color: #1488c6;
|
||||
}
|
||||
|
||||
#choice button.b:focus{
|
||||
background-color: #00a2a1;
|
||||
}
|
||||
|
||||
#background-stats{
|
||||
z-index:1;
|
||||
height:100%;
|
||||
width:100%;
|
||||
position:absolute;
|
||||
}
|
||||
#background-stats div{
|
||||
transition: width 400ms ease-in-out;
|
||||
display:inline-block;
|
||||
margin-bottom:-4px;
|
||||
width:50%;
|
||||
height:100%;
|
||||
}
|
49
vote-apps/voting-app/templates/index.html
Normal file
49
vote-apps/voting-app/templates/index.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{option_a}} vs {{option_b}}!</title>
|
||||
<base href="/index.html">
|
||||
<meta name = "viewport" content = "width=device-width, initial-scale = 1.0">
|
||||
<meta name="keywords" content="docker-compose, docker, stack">
|
||||
<meta name="author" content="Tutum dev team">
|
||||
<link rel='stylesheet' href="{{ url_for('static',filename='stylesheets/style.css') }}" />
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content-container">
|
||||
<div id="content-container-center">
|
||||
<h3>{{option_a}} vs {{option_b}}!</h3>
|
||||
<form id="choice" name='form' method="POST" action="/">
|
||||
<button id="a" type="submit" name="vote" class="a" value="a">{{option_a}}</button>
|
||||
<button id="b" type="submit" name="vote" class="b" value="b">{{option_b}}</button>
|
||||
</form>
|
||||
<div id="tip">
|
||||
(Tip: you can change your vote)
|
||||
</div>
|
||||
<div id="hostname">
|
||||
Vote processed by {{hostname}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
|
||||
|
||||
{% if vote %}
|
||||
<script>
|
||||
var vote = "{{vote}}";
|
||||
|
||||
if(vote == "a"){
|
||||
$(".a").prop('disabled', true);
|
||||
$(".a").html('{{option_a}} <i class="fa fa-check-circle"></i>');
|
||||
$(".b").css('opacity','0.5');
|
||||
}
|
||||
if(vote == "b"){
|
||||
$(".b").prop('disabled', true);
|
||||
$(".b").html('{{option_b}} <i class="fa fa-check-circle"></i>');
|
||||
$(".a").css('opacity','0.5');
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
17
vote-apps/voting-app/utils/__init__.py
Normal file
17
vote-apps/voting-app/utils/__init__.py
Normal file
@ -0,0 +1,17 @@
|
||||
import time
|
||||
from redis import Redis, ConnectionError
|
||||
|
||||
|
||||
def connect_to_redis(host):
|
||||
time.sleep(2)
|
||||
print "Connecting to redis"
|
||||
|
||||
while True:
|
||||
try:
|
||||
redis = Redis(host=host, db=0)
|
||||
redis.ping()
|
||||
print "Connected to redis"
|
||||
return redis
|
||||
except ConnectionError:
|
||||
print "Failed to connect to redis - retrying"
|
||||
time.sleep(1)
|
15
vote-apps/worker/Dockerfile
Normal file
15
vote-apps/worker/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM java:7
|
||||
|
||||
RUN apt-get update -qq && apt-get install -y maven && apt-get clean
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
ADD pom.xml /code/pom.xml
|
||||
RUN ["mvn", "dependency:resolve"]
|
||||
RUN ["mvn", "verify"]
|
||||
|
||||
# Adding source, compile and package into a fat jar
|
||||
ADD src /code/src
|
||||
RUN ["mvn", "package"]
|
||||
|
||||
CMD ["/usr/lib/jvm/java-7-openjdk-amd64/bin/java", "-jar", "target/worker-jar-with-dependencies.jar"]
|
84
vote-apps/worker/pom.xml
Normal file
84
vote-apps/worker/pom.xml
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>worker</groupId>
|
||||
<artifactId>worker</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20140107</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.7.2</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.4-1200-jdbc41</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<finalName>worker</finalName>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>worker.Worker</mainClass>
|
||||
<classpathPrefix>dependency-jars/</classpathPrefix>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>attached</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<finalName>worker</finalName>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>worker.Worker</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
101
vote-apps/worker/src/main/java/worker/Worker.java
Normal file
101
vote-apps/worker/src/main/java/worker/Worker.java
Normal file
@ -0,0 +1,101 @@
|
||||
package worker;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import java.sql.*;
|
||||
import org.json.JSONObject;
|
||||
|
||||
class Worker {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
Jedis redis = connectToRedis("voteapps_redis_1");
|
||||
Connection dbConn = connectToDB("voteapps_db_1");
|
||||
|
||||
System.err.println("Watching vote queue");
|
||||
|
||||
while (true) {
|
||||
String voteJSON = redis.blpop(0, "votes").get(1);
|
||||
JSONObject voteData = new JSONObject(voteJSON);
|
||||
String voterID = voteData.getString("voter_id");
|
||||
String vote = voteData.getString("vote");
|
||||
|
||||
System.err.printf("Processing vote for '%s' by '%s'\n", vote, voterID);
|
||||
updateVote(dbConn, voterID, vote);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void updateVote(Connection dbConn, String voterID, String vote) throws SQLException {
|
||||
PreparedStatement insert = dbConn.prepareStatement(
|
||||
"INSERT INTO votes (id, vote) VALUES (?, ?)");
|
||||
insert.setString(1, voterID);
|
||||
insert.setString(2, vote);
|
||||
|
||||
try {
|
||||
insert.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
PreparedStatement update = dbConn.prepareStatement(
|
||||
"UPDATE votes SET vote = ? WHERE id = ?");
|
||||
update.setString(1, vote);
|
||||
update.setString(2, voterID);
|
||||
update.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
static Jedis connectToRedis(String host) {
|
||||
Jedis conn = new Jedis(host);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
conn.keys("*");
|
||||
break;
|
||||
} catch (JedisConnectionException e) {
|
||||
System.err.println("Failed to connect to redis - retrying");
|
||||
sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
System.err.println("Connected to redis");
|
||||
return conn;
|
||||
}
|
||||
|
||||
static Connection connectToDB(String host) throws SQLException {
|
||||
Connection conn = null;
|
||||
|
||||
try {
|
||||
|
||||
Class.forName("org.postgresql.Driver");
|
||||
String url = "jdbc:postgresql://" + host + "/postgres";
|
||||
|
||||
while (conn == null) {
|
||||
try {
|
||||
conn = DriverManager.getConnection(url, "postgres", "");
|
||||
} catch (SQLException e) {
|
||||
System.err.println("Failed to connect to db - retrying");
|
||||
sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
PreparedStatement st = conn.prepareStatement(
|
||||
"CREATE TABLE IF NOT EXISTS votes (id VARCHAR(255) NOT NULL UNIQUE, vote VARCHAR(255) NOT NULL)");
|
||||
st.executeUpdate();
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void sleep(long duration) {
|
||||
try {
|
||||
Thread.sleep(duration);
|
||||
} catch (InterruptedException e) {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user