1
0
mirror of https://github.com/jcwimer/docker-swarm-autoscaler synced 2026-03-24 23:04:43 +00:00

First release with tests

This commit is contained in:
2019-12-18 13:35:37 -05:00
parent 4c4c972dbd
commit 0f2424221a
15 changed files with 797 additions and 44 deletions

18
tests/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM ruby:2.6.3
RUN apt-get update -qq && \
apt-get install -y -qq \
jq \
make \
gcc \
bash
RUN echo 'gem: --no-rdoc --no-ri' > /root/.gemrc
RUN gem install bundler
RUN gem update --system
RUN mkdir -p /gemfile
#Cache gems so they don't install on every code change
WORKDIR /gemfile
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install --jobs 4

8
tests/Gemfile Normal file
View File

@@ -0,0 +1,8 @@
source 'https://rubygems.org'
ruby '2.6.3'
gem 'awesome_print', '~> 1.8'
gem 'guard-rspec', require: false
gem 'pry-byebug', '~> 3.7'
gem 'rspec-shell-expectations'
gem 'rake'
gem 'rspec'

75
tests/Gemfile.lock Normal file
View File

@@ -0,0 +1,75 @@
GEM
remote: https://rubygems.org/
specs:
awesome_print (1.8.0)
byebug (11.0.1)
coderay (1.1.2)
diff-lcs (1.3)
ffi (1.11.3)
formatador (0.2.5)
guard (2.16.1)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.7.3)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lumberjack (1.0.13)
method_source (0.9.2)
nenv (0.3.0)
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pry-byebug (3.7.0)
byebug (~> 11.0)
pry (~> 0.10)
rake (13.0.1)
rb-fsevent (0.10.3)
rb-inotify (0.10.0)
ffi (~> 1.0)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.0)
rspec-support (~> 3.9.0)
rspec-expectations (3.9.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-shell-expectations (1.3.0)
rspec-support (3.9.0)
shellany (0.0.1)
thor (1.0.1)
PLATFORMS
ruby
DEPENDENCIES
awesome_print (~> 1.8)
guard-rspec
pry-byebug (~> 3.7)
rake
rspec
rspec-shell-expectations
RUBY VERSION
ruby 2.6.3p62
BUNDLED WITH
2.1.1

14
tests/README.md Normal file
View File

@@ -0,0 +1,14 @@
# docker-swarm-autoscaler unit tests
### Dependencies (packaged into docker with ./run-tests.sh)
#### [uses rspec-shell-expectations](https://github.com/matthijsgroen/rspec-shell-expectations)
------
1. Ruby 2.6.3
2. jq needs installed
### Gotchas
------
1. Do not use ${0} in scripts. Instead use ${BASH_SOURCE[0]}
2. Many times it is necessary to see the stdout and stderr in your test to see what you forgot to mock. For example, if your test is failing
you can do `expect(stdout).to eq ''` and rspec will fail and give you the stdout message. Same with stderr. This will help you mock the things
you might have overlooked.

15
tests/Rakefile Normal file
View File

@@ -0,0 +1,15 @@
require 'rake'
require 'rspec/core/rake_task'
task :default => :test
desc 'Run specs'
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = Dir.glob('spec/**/*_spec.rb')
t.rspec_opts = '--format documentation'
t.rspec_opts << ' --color'
end
task :test => [
:spec,
]

35
tests/run-tests.sh Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -ex
function main {
build-image
run-ruby-tests
}
function cd-to-top-of-repo {
cd "$(git rev-parse --show-toplevel)"
}
function build-image {
cd-to-top-of-repo
docker build -t docker-swarm-autoscaler-tests ./tests
}
function run-ruby-tests {
cd-to-top-of-repo
echo 'INFO: Running rspec unit tests...'
local -r container_id=$(
docker create --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
docker-swarm-autoscaler-tests \
bash -c "cd /root/tests && \
bundle exec rake spec"
)
docker cp . "${container_id}:/root/"
trap "docker rm ${container_id}" SIGHUP
docker start --attach --interactive "${container_id}"
}
[[ "${0}" == "${BASH_SOURCE[0]}" ]] && main "${@}"

View File

@@ -0,0 +1,71 @@
require 'spec_helper'
current_dir=Dir.pwd
# tests dir is current
autoscale="#{current_dir}/../docker-swarm-autoscaler/auto-scale.sh"
describe 'auto-scale.sh' do
create_standard_mocks
context 'scaling docker swarm services' do
it 'scales a service with lower than the minimum replicas' do
set_standard_mock_outputs
stdout, stderr, status = stubbed_env.execute("/bin/bash #{autoscale}", {'LOOP' => 'false'})
expect(stdout).to include("Service hello_helloworld_too_low_cpu has an autoscale label.")
expect(stdout).to include("Service hello_helloworld_too_low_cpu is below the minimum. Scaling to the minimum of 3")
expect(status.exitstatus).to eq 0
end
it 'scales a service with low cpu down by 1 replica' do
set_standard_mock_outputs
stdout, stderr, status = stubbed_env.execute("/bin/bash #{autoscale}", {'LOOP' => 'false'})
expect(stdout).to include("Service hello_helloworld_low_cpu has an autoscale label.")
expect(stdout).to include("Scaling down the service hello_helloworld_low_cpu to 3")
expect(status.exitstatus).to eq 0
end
it 'does not scale a service with low cpu when the minimum replicas is reached' do
set_standard_mock_outputs
stdout, stderr, status = stubbed_env.execute("/bin/bash #{autoscale}", {'LOOP' => 'false'})
expect(stdout).to include("Service hello_helloworld_min_replicas_low_cpu has an autoscale label.")
expect(stdout).to include("Service hello_helloworld_min_replicas_low_cpu has the minumum number of replicas.")
expect(status.exitstatus).to eq 0
end
it 'scales a service with high cpu up by 1 replica' do
set_standard_mock_outputs
stdout, stderr, status = stubbed_env.execute("/bin/bash #{autoscale}", {'LOOP' => 'false'})
expect(stdout).to include("Service hello_helloworld_high_cpu has an autoscale label.")
expect(stdout).to include("Scaling up the service hello_helloworld_high_cpu to 4")
expect(status.exitstatus).to eq 0
end
it 'does not scale a service with high cpu when the max replicas is reached' do
set_standard_mock_outputs
stdout, stderr, status = stubbed_env.execute("/bin/bash #{autoscale}", {'LOOP' => 'false'})
expect(stdout).to include("Service hello_helloworld_high_cpu_full_replicas has an autoscale label.")
expect(stdout).to include("Service hello_helloworld_high_cpu_full_replicas already has the maximum of 4 replicas")
expect(status.exitstatus).to eq 0
end
it 'scales a service with more than the maximum number of replicas' do
set_standard_mock_outputs
stdout, stderr, status = stubbed_env.execute("/bin/bash #{autoscale}", {'LOOP' => 'false'})
expect(stdout).to include("Service hello_helloworld_high_cpu_too_many_replicas has an autoscale label.")
expect(stdout).to include("Service hello_helloworld_high_cpu_too_many_replicas is above the maximum. Scaling to the maximum of 4")
expect(status.exitstatus).to eq 0
end
it 'does not scale a service without an autoscale label' do
set_standard_mock_outputs
stdout, stderr, status = stubbed_env.execute("/bin/bash #{autoscale}", {'LOOP' => 'false'})
expect(stdout).to include("Service autoscale_docker-swarm-autoscaler does not have an autoscale label.")
expect(status.exitstatus).to eq 0
end
end
end

413
tests/spec/spec_helper.rb Normal file
View File

@@ -0,0 +1,413 @@
require 'ap'
require 'pry'
require 'rspec/shell/expectations'
RSpec.configure do |c|
c.include Rspec::Shell::Expectations
end
def create_standard_mocks
let(:stubbed_env) { create_stubbed_env }
let(:curl_mock) { stubbed_env.stub_command('curl') }
let(:docker_mock) { stubbed_env.stub_command('docker') }
end
def set_standard_mock_outputs
# If you have something non standard need to be output, define your output before running this function. Outputs are stacked to stdout with new lines \n. Thus defining your non standard output first will output will be on top.
# If your non standard mock output is an exit code, define it after this function. Exit codes can be overwritten and whichever is deined last is what the test will use.
standard_prometheus_output='{
"status": "success",
"data": {
"resultType": "vector",
"result": [
{
"metric": {
"container_label_com_docker_swarm_service_name": "autoscale_docker-swarm-autoscaler",
"instance": "10.0.0.6:8080"
},
"value": [
1576602885.053,
"0.41103154419335"
]
},
{
"metric": {
"container_label_com_docker_swarm_service_name": "hello_helloworld_low_cpu",
"instance": "10.0.0.6:8080"
},
"value": [
1576602885.053,
"0.011596642816404852"
]
},
{
"metric": {
"container_label_com_docker_swarm_service_name": "hello_helloworld_too_low_cpu",
"instance": "10.0.0.6:8080"
},
"value": [
1576602885.053,
"0.011596642816404852"
]
},
{
"metric": {
"container_label_com_docker_swarm_service_name": "hello_helloworld_high_cpu",
"instance": "10.0.0.6:8080"
},
"value": [
1576602885.053,
"86.4"
]
},
{
"metric": {
"container_label_com_docker_swarm_service_name": "hello_helloworld_high_cpu_full_replicas",
"instance": "10.0.0.6:8080"
},
"value": [
1576602885.053,
"86.4"
]
},
{
"metric": {
"container_label_com_docker_swarm_service_name": "hello_helloworld_min_replicas_low_cpu",
"instance": "10.0.0.6:8080"
},
"value": [
1576602885.053,
"0.01"
]
},
{
"metric": {
"container_label_com_docker_swarm_service_name": "hello_helloworld_high_cpu_too_many_replicas",
"instance": "10.0.0.6:8080"
},
"value": [
1576602885.053,
"86.4"
]
}
]
}
}
'
helloworld_high_cpu_too_many_replicas_docker_inspect_output='[
{
"Spec": {
"Name": "hello_helloworld_high_cpu_too_many_replicas",
"Labels": {
"com.docker.stack.image": "tutum/hello-world",
"com.docker.stack.namespace": "hello",
"swarm.autoscaler": "true",
"swarm.autoscaler.maximum": "4",
"swarm.autoscaler.minimum": "3"
},
"Mode": {
"Replicated": {
"Replicas": 5
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 8080,
"PublishMode": "ingress"
}
]
}
}
}
]'
helloworld_high_cpu_docker_inspect_output='[
{
"Spec": {
"Name": "hello_helloworld_high_cpu",
"Labels": {
"com.docker.stack.image": "tutum/hello-world",
"com.docker.stack.namespace": "hello",
"swarm.autoscaler": "true",
"swarm.autoscaler.maximum": "4",
"swarm.autoscaler.minimum": "3"
},
"Mode": {
"Replicated": {
"Replicas": 3
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 8080,
"PublishMode": "ingress"
}
]
}
}
}
]'
helloworld_high_cpu_full_replicas_docker_inspect_output='[
{
"Spec": {
"Name": "hello_helloworld_high_cpu_full_replicas",
"Labels": {
"com.docker.stack.image": "tutum/hello-world",
"com.docker.stack.namespace": "hello",
"swarm.autoscaler": "true",
"swarm.autoscaler.maximum": "4",
"swarm.autoscaler.minimum": "3"
},
"Mode": {
"Replicated": {
"Replicas": 4
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 8080,
"PublishMode": "ingress"
}
]
}
}
}
]'
helloworld_low_cpu_docker_inspect_output='[
{
"Spec": {
"Name": "hello_helloworld_low_cpu",
"Labels": {
"com.docker.stack.image": "tutum/hello-world",
"com.docker.stack.namespace": "hello",
"swarm.autoscaler": "true",
"swarm.autoscaler.maximum": "4",
"swarm.autoscaler.minimum": "3"
},
"Mode": {
"Replicated": {
"Replicas": 4
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 8080,
"PublishMode": "ingress"
}
]
}
}
}
]'
helloworld_too_low_cpu_docker_inspect_output='[
{
"Spec": {
"Name": "hello_helloworld_too_low_cpu",
"Labels": {
"com.docker.stack.image": "tutum/hello-world",
"com.docker.stack.namespace": "hello",
"swarm.autoscaler": "true",
"swarm.autoscaler.maximum": "4",
"swarm.autoscaler.minimum": "3"
},
"Mode": {
"Replicated": {
"Replicas": 1
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 8080,
"PublishMode": "ingress"
}
]
}
}
}
]'
docker_swarm_autoscaler_docker_inspect_output='[
{
"Spec": {
"Name": "autoscale_docker-swarm-autoscaler",
"Labels": {
"com.docker.stack.image": "tutum/hello-world",
"com.docker.stack.namespace": "autoscale"
},
"Mode": {
"Replicated": {
"Replicas": 1
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 8080,
"PublishMode": "ingress"
}
]
}
}
}
]'
hello_helloworld_min_replicas_low_cpu_docker_inspect_output='[
{
"Spec": {
"Name": "hello_helloworld_min_replicas_low_cpu",
"Labels": {
"com.docker.stack.image": "tutum/hello-world",
"com.docker.stack.namespace": "hello",
"swarm.autoscaler": "true",
"swarm.autoscaler.maximum": "4",
"swarm.autoscaler.minimum": "3"
},
"Mode": {
"Replicated": {
"Replicas": 3
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 8080,
"PublishMode": "ingress"
}
]
}
}
}
]'
curl_mock.with_args('--silent').outputs(standard_prometheus_output, to: :stdout)
docker_mock.with_args('service','inspect','hello_helloworld_high_cpu').outputs(helloworld_high_cpu_docker_inspect_output, to: :stdout)
docker_mock.with_args('service','inspect','hello_helloworld_low_cpu').outputs(helloworld_low_cpu_docker_inspect_output, to: :stdout)
docker_mock.with_args('service','inspect','hello_helloworld_too_low_cpu').outputs(helloworld_too_low_cpu_docker_inspect_output, to: :stdout)
docker_mock.with_args('service','inspect','autoscale_docker-swarm-autoscaler').outputs(docker_swarm_autoscaler_docker_inspect_output, to: :stdout)
docker_mock.with_args('service','inspect','hello_helloworld_high_cpu_full_replicas').outputs(helloworld_high_cpu_full_replicas_docker_inspect_output, to: :stdout)
docker_mock.with_args('service','inspect','hello_helloworld_high_cpu_too_many_replicas').outputs(helloworld_high_cpu_too_many_replicas_docker_inspect_output, to: :stdout)
docker_mock.with_args('service','inspect','hello_helloworld_min_replicas_low_cpu').outputs(hello_helloworld_min_replicas_low_cpu_docker_inspect_output, to: :stdout)
docker_mock.with_args('service', 'scale').returns_exitstatus(0)
end