[QENG-4181] Add per-pool stats to /status API

Prior to this the only per-pool statistics that could be extracted from the API
were a list of empty pools in the "status" section of the returned results of
the `/status` endpoint.

This adds a new "pools" section to the '/status' results which lists, for each
pool, the following results:

 - The number of ready vms in the pool
 - The number of running vms in the pool
 - The number of pending vms in the pool
 - The maximum size of the pool (as specified in the vmpooler configuration)

Example:

```
{
  "boot": {
  "duration": {
    "average": 163.6,
    "min": 65.49,
    "max": 830.07,
    "total": 247744.71000000002
  },
  "count": {
    "total": 1514
  }
  # ...
  "pools": {
    "pool1": {
      "ready":   5,
      "running": 2,
      "pending": 1,
      "max":     15
    },
    "pool2": {
      "ready":   0,
      "running": 10,
      "pending": 0,
      "max:      10
    }
  }
}
```

This includes spec coverage for this change (we could use more specs on `/status` in general); as well as a couple of general spec improvements.
This commit is contained in:
Rick Bradley 2016-09-09 17:00:47 -05:00
parent 1bf0af2ba5
commit 30dc060731
3 changed files with 134 additions and 3 deletions

View file

@ -123,6 +123,7 @@ module Vmpooler
content_type :json content_type :json
result = { result = {
pools: {},
status: { status: {
ok: true, ok: true,
message: 'Battle station fully armed and operational.' message: 'Battle station fully armed and operational.'
@ -136,7 +137,21 @@ module Vmpooler
# Check for empty pools # Check for empty pools
pools.each do |pool| pools.each do |pool|
if backend.scard('vmpooler__ready__' + pool['name']).to_i == 0 # REMIND: move this out of the API and into the back-end
ready = backend.scard('vmpooler__ready__' + pool['name']).to_i
running = backend.scard('vmpooler__running__' + pool['name']).to_i
pending = backend.scard('vmpooler__pending__' + pool['name']).to_i
max = pool['size']
result[:pools][pool['name']] = {
ready: ready,
running: running,
pending: pending,
max: max
}
# for backwards compatibility, include separate "empty" stats in "status" block
if ready == 0
result[:status][:empty] ||= [] result[:status][:empty] ||= []
result[:status][:empty].push(pool['name']) result[:status][:empty].push(pool['name'])

View file

@ -35,8 +35,7 @@ end
def create_ready_vm(template, name, token = nil) def create_ready_vm(template, name, token = nil)
create_vm(name, token) create_vm(name, token)
redis.sadd("vmpooler__ready__#{template}", name) redis.sadd("vmpooler__ready__#{template}", name)
# REMIND: should be __vm__? redis.hset("vmpooler__vm__#{name}", "template", template)
redis.hset("vmpooler_vm_#{name}", "template", template)
end end
def create_running_vm(template, name, token = nil) def create_running_vm(template, name, token = nil)
@ -45,6 +44,12 @@ def create_running_vm(template, name, token = nil)
redis.hset("vmpooler__vm__#{name}", "template", template) redis.hset("vmpooler__vm__#{name}", "template", template)
end end
def create_pending_vm(template, name, token = nil)
create_vm(name, token)
redis.sadd("vmpooler__pending__#{template}", name)
redis.hset("vmpooler__vm__#{name}", "template", template)
end
def create_vm(name, token = nil) def create_vm(name, token = nil)
redis.hset("vmpooler__vm__#{name}", 'checkout', Time.now) redis.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
if token if token

View file

@ -0,0 +1,111 @@
require 'spec_helper'
require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
def has_set_tag?(vm, tag, value)
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
end
describe Vmpooler::API::V1 do
include Rack::Test::Methods
def app()
Vmpooler::API
end
describe '/status' do
let(:prefix) { '/api/v1' }
let(:config) {
{
config: {
'site_name' => 'test pooler',
'vm_lifetime_auth' => 2,
},
pools: [
{'name' => 'pool1', 'size' => 5},
{'name' => 'pool2', 'size' => 10}
],
alias: { 'poolone' => 'pool1' },
}
}
let(:current_time) { Time.now }
before(:each) do
redis.flushdb
app.settings.set :config, config
app.settings.set :redis, redis
app.settings.set :config, auth: false
create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time)
end
describe 'GET /status' do
it 'returns the configured maximum size for each pool' do
get "#{prefix}/status/"
# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["max"]).to be(5)
expect(result["pools"]["pool2"]["max"]).to be(10)
end
it 'returns the number of ready vms for each pool' do
3.times {|i| create_ready_vm("pool1", "vm-#{i}") }
get "#{prefix}/status/"
# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["ready"]).to be(3)
expect(result["pools"]["pool2"]["ready"]).to be(0)
end
it 'returns the number of running vms for each pool' do
3.times {|i| create_running_vm("pool1", "vm-#{i}") }
4.times {|i| create_running_vm("pool2", "vm-#{i}") }
get "#{prefix}/status/"
# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["running"]).to be(3)
expect(result["pools"]["pool2"]["running"]).to be(4)
end
it 'returns the number of pending vms for each pool' do
3.times {|i| create_pending_vm("pool1", "vm-#{i}") }
4.times {|i| create_pending_vm("pool2", "vm-#{i}") }
get "#{prefix}/status/"
# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["pending"]).to be(3)
expect(result["pools"]["pool2"]["pending"]).to be(4)
end
it '(for v1 backwards compatibility) lists any empty pools in the status section' do
get "#{prefix}/status/"
# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["status"]["empty"].sort).to eq(["pool1", "pool2"])
end
end
end
end