prometheus_client_spec.rb 6.77 KB
Newer Older
1 2
require 'spec_helper'

3
describe Gitlab::PrometheusClient, lib: true do
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
  include PrometheusHelpers

  subject { described_class.new(api_url: 'https://prometheus.example.com') }

  describe '#ping' do
    it 'issues a "query" request to the API endpoint' do
      req_stub = stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector'))

      expect(subject.ping).to eq({ "resultType" => "vector", "result" => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] })
      expect(req_stub).to have_been_requested
    end
  end

  # This shared examples expect:
  # - query_url: A query URL
  # - execute_query: A query call
  shared_examples 'failure response' do
    context 'when request returns 400 with an error message' do
      it 'raises a Gitlab::PrometheusError error' do
        req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'bar!' })

        expect { execute_query }
          .to raise_error(Gitlab::PrometheusError, 'bar!')
        expect(req_stub).to have_been_requested
      end
    end

    context 'when request returns 400 without an error message' do
      it 'raises a Gitlab::PrometheusError error' do
        req_stub = stub_prometheus_request(query_url, status: 400)

        expect { execute_query }
          .to raise_error(Gitlab::PrometheusError, 'Bad data received')
        expect(req_stub).to have_been_requested
      end
    end

    context 'when request returns 500' do
      it 'raises a Gitlab::PrometheusError error' do
        req_stub = stub_prometheus_request(query_url, status: 500, body: { message: 'FAIL!' })

        expect { execute_query }
          .to raise_error(Gitlab::PrometheusError, '500 - {"message":"FAIL!"}')
        expect(req_stub).to have_been_requested
      end
    end
  end

52 53
  describe 'failure to reach a provided prometheus url' do
    let(:prometheus_url) {"https://prometheus.invalid.example.com"}
Jose Ivan Vargas's avatar
Jose Ivan Vargas committed
54

55 56 57
    context 'exceptions are raised' do
      it 'raises a Gitlab::PrometheusError error when a SocketError is rescued' do
        req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError)
Jose Ivan Vargas's avatar
Jose Ivan Vargas committed
58

59 60 61 62
        expect { subject.send(:get, prometheus_url) }
          .to raise_error(Gitlab::PrometheusError, "Can't connect to #{prometheus_url}")
        expect(req_stub).to have_been_requested
      end
Jose Ivan Vargas's avatar
Jose Ivan Vargas committed
63

64 65
      it 'raises a Gitlab::PrometheusError error when a SSLError is rescued' do
        req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError)
Jose Ivan Vargas's avatar
Jose Ivan Vargas committed
66

67 68 69 70 71 72 73 74 75
        expect { subject.send(:get, prometheus_url) }
          .to raise_error(Gitlab::PrometheusError, "#{prometheus_url} contains invalid SSL data")
        expect(req_stub).to have_been_requested
      end

      it 'raises a Gitlab::PrometheusError error when a HTTParty::Error is rescued' do
        req_stub = stub_prometheus_request_with_exception(prometheus_url, HTTParty::Error)

        expect { subject.send(:get, prometheus_url) }
76
          .to raise_error(Gitlab::PrometheusError, "Network connection error")
77 78
        expect(req_stub).to have_been_requested
      end
Jose Ivan Vargas's avatar
Jose Ivan Vargas committed
79 80 81
    end
  end

82 83
  describe '#query' do
    let(:prometheus_query) { prometheus_cpu_query('env-slug') }
Fatih Acet's avatar
Fatih Acet committed
84 85 86 87 88
    let(:query_url) { prometheus_query_with_time_url(prometheus_query, Time.now.utc) }

    around do |example|
      Timecop.freeze { example.run }
    end
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

    context 'when request returns vector results' do
      it 'returns data from the API call' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector'))

        expect(subject.query(prometheus_query)).to eq [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }]
        expect(req_stub).to have_been_requested
      end
    end

    context 'when request returns matrix results' do
      it 'returns nil' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('matrix'))

        expect(subject.query(prometheus_query)).to be_nil
        expect(req_stub).to have_been_requested
      end
    end

    context 'when request returns no data' do
      it 'returns []' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('vector'))

        expect(subject.query(prometheus_query)).to be_empty
        expect(req_stub).to have_been_requested
      end
    end

    it_behaves_like 'failure response' do
      let(:execute_query) { subject.query(prometheus_query) }
    end
  end

  describe '#query_range' do
    let(:prometheus_query) { prometheus_memory_query('env-slug') }
    let(:query_url) { prometheus_query_range_url(prometheus_query) }

    around do |example|
      Timecop.freeze { example.run }
    end

Fatih Acet's avatar
Fatih Acet committed
130 131 132 133 134 135 136 137 138 139 140 141 142 143
    context 'when non utc time is passed' do
      let(:time_stop) { Time.now.in_time_zone("Warsaw") }
      let(:time_start) { time_stop - 8.hours }

      let(:query_url) { prometheus_query_range_url(prometheus_query, start: time_start.utc.to_f, stop: time_stop.utc.to_f) }

      it 'passed dates are properly converted to utc' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector'))

        subject.query_range(prometheus_query, start: time_start, stop: time_stop)
        expect(req_stub).to have_been_requested
      end
    end

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    context 'when a start time is passed' do
      let(:query_url) { prometheus_query_range_url(prometheus_query, start: 2.hours.ago) }

      it 'passed it in the requested URL' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector'))

        subject.query_range(prometheus_query, start: 2.hours.ago)
        expect(req_stub).to have_been_requested
      end
    end

    context 'when request returns vector results' do
      it 'returns nil' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector'))

        expect(subject.query_range(prometheus_query)).to be_nil
        expect(req_stub).to have_been_requested
      end
    end

    context 'when request returns matrix results' do
      it 'returns data from the API call' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('matrix'))

        expect(subject.query_range(prometheus_query)).to eq([
          {
            "metric" => {},
            "values" => [[1488758662.506, "0.00002996364761904785"], [1488758722.506, "0.00003090239047619091"]]
          }
        ])
        expect(req_stub).to have_been_requested
      end
    end

    context 'when request returns no data' do
      it 'returns []' do
        req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('matrix'))

        expect(subject.query_range(prometheus_query)).to be_empty
        expect(req_stub).to have_been_requested
      end
    end

    it_behaves_like 'failure response' do
      let(:execute_query) { subject.query_range(prometheus_query) }
    end
  end
end