Skip to content

Commit

Permalink
Implement regions api
Browse files Browse the repository at this point in the history
* This patch implements regions api
* Fix API client send separately response status and not affect response body
  • Loading branch information
boris-42 committed Dec 5, 2016
1 parent e8f10ec commit 99d880e
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 37 deletions.
10 changes: 4 additions & 6 deletions ceagle/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,13 @@ def get(self, uri="/", **kwargs):
except requests.exceptions.ConnectionError:
mesg = "Service '%(name)s' is not available at '%(endpoint)s'" % (
{"name": self.name, "endpoint": self.endpoint})
return {"status_code": 502,
"error": {"message": mesg}}
return {"error": {"message": mesg}}, 502
try:
result = response.json()
except ValueError:
return {"status_code": 500,
"error": {"message": "Response can not be decoded"}}
result.setdefault("status_code", response.status_code)
return result
return {"error": {"message": "Response can not be decoded"}}, 500

return result, response.status_code


def get_client(service_name):
Expand Down
6 changes: 3 additions & 3 deletions ceagle/api/v1/infrastructure.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
from ceagle.api_fake_data import fake_infra


bp_region = flask.Blueprint("infra", __name__)
bp = flask.Blueprint("infra", __name__)


@bp_region.route("/<region>/infra")
@bp.route("/<region>/infra")
@fake_infra.get_region_infra
def get_region_infra(region):
return flask.jsonify("fixme!")


def get_blueprints():
return [["/region", bp_region]]
return [["/region", bp]]
24 changes: 21 additions & 3 deletions ceagle/api/v1/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,35 @@

import flask

from ceagle.api import client
from ceagle.api_fake_data import fake_regions

from ceagle import config

bp = flask.Blueprint("regions", __name__)


@bp.route("/", defaults={"detailed": False})
@bp.route("", defaults={"detailed": False})
@bp.route("/detailed", defaults={"detailed": True})
@fake_regions.get_regions
def get_regions(detailed):
return flask.jsonify({"fixme"})
regions = {}

for service_name in config.get_config()["services"].keys():
if service_name == "infra":
continue # TODO(boris-42): This should not be checked here.
service_client = client.get_client(service_name)

resp, code = service_client.get("/api/v1/regions")
if code != 200:
# FIXME ADD LOGS HERE
continue
for r in resp:
regions.setdefault(r, {"services": []})
regions[r]["services"].append(service_name)

if not detailed:
return flask.jsonify({"regions": regions.keys()})
return flask.jsonify({"regions": regions})


def get_blueprints():
Expand Down
14 changes: 6 additions & 8 deletions ceagle/api/v1/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ def get_status_health(period):
json.dumps({"error": "No health endpoint configured"}), 404)

api_endpoint = "/api/v1/health/{}".format(period)
result = health_client.get(api_endpoint)
return flask.make_response(json.dumps(result), result["status_code"])
result, code = health_client.get(api_endpoint)
return flask.make_response(json.dumps(result), code)


@bp_status.route("/performance/<period>")
Expand All @@ -59,8 +59,7 @@ def get_status_availability(period):
return flask.jsonify(err), 404

api_endpoint = "/api/v1/availability/{period}".format(period=period)
resp = ct.get(api_endpoint)
code = resp.pop("status_code")
resp, code = ct.get(api_endpoint)
return flask.jsonify(resp), code


Expand All @@ -80,8 +79,8 @@ def get_region_status_health(region, period):

api_endpoint = "/api/v1/region/{region}/health/{period}".format(
region=region, period=period)
result = health_client.get(api_endpoint)
return flask.make_response(json.dumps(result), result["status_code"])
result, code = health_client.get(api_endpoint)
return flask.make_response(json.dumps(result), code)


@bp_region_status.route("/<region>/status/performance/<period>")
Expand All @@ -99,8 +98,7 @@ def get_region_status_availability(region, period):
return flask.jsonify(err), 404
api_endpoint = "/api/v1/region/{region}/availability/{period}".format(
region=region, period=period)
resp = ct.get(api_endpoint)
code = resp.pop("status_code")
resp, code = ct.get(api_endpoint)
return flask.jsonify(resp), code


Expand Down
17 changes: 7 additions & 10 deletions tests/unit/api/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,30 @@ def test___init__(self):
@mock.patch("ceagle.api.client.requests.get")
def test_get(self, mock_requests_get):
mock_requests_get.return_value.status_code = "foo_status"
mock_requests_get.return_value.json.return_value = (
{"foo": 42})
mock_requests_get.return_value.json.return_value = {"foo": 42}
ct = client.Client("foo", "http://foo_ep")
result = ct.get()
mock_requests_get.assert_called_once_with("http://foo_ep/")
self.assertEqual({"status_code": "foo_status", "foo": 42}, result)
self.assertEqual(({"foo": 42}, "foo_status"), result)

mock_requests_get.reset_mock()

@mock.patch("ceagle.api.client.requests.get")
def test_get_with_path(self, mock_requests_get):
mock_requests_get.return_value.json.return_value = (
{"foo": 42, "status_code": 24})
mock_requests_get.return_value.json.return_value = {"foo": 42}
mock_requests_get.return_value.status_code = 200
ct = client.Client("foo", "http://foo_ep")
result = ct.get("/bar")
mock_requests_get.assert_called_once_with("http://foo_ep/bar")
self.assertEqual({"status_code": 24, "foo": 42}, result)
self.assertEqual(({"foo": 42}, 200), result)

@mock.patch("ceagle.api.client.requests.get")
def test_get_wrong_response_fmt(self, mock_requests_get):
mock_requests_get.return_value.json.side_effect = ValueError
ct = client.Client("foo", "http://foo_ep")
result = ct.get("/bar")
self.assertEqual(
{"error": {"message": "Response can not be decoded"},
"status_code": 500},
({"error": {"message": "Response can not be decoded"}}, 500),
result)

@mock.patch("ceagle.api.client.requests.get")
Expand All @@ -68,8 +66,7 @@ def test_get_not_available(self, mock_requests_get):
ct = client.Client("foo", "http://foo_ep")
result = ct.get("/bar")
mesg = "Service 'foo' is not available at 'http://foo_ep'"
self.assertEqual({"error": {"message": mesg},
"status_code": 502},
self.assertEqual(({"error": {"message": mesg}}, 502),
result)


Expand Down
54 changes: 53 additions & 1 deletion tests/unit/api/v1/test_regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,65 @@
# License for the specific language governing permissions and limitations
# under the License.

import collections

import mock

from ceagle.api_fake_data import base as fake_api_base
from tests.unit import test


class ApiTestCase(test.TestCase):

def test_api_response_code(self):
code, resp = self.get("/api/v1/regions/")
code, resp = self.get("/api/v1/regions")
self.assertEqual(code, 200)
code, resp = self.get("/api/v1/regions/detailed")
self.assertEqual(code, 200)


class RegionsApiTestCase(test.TestCase):

def setUp(self):
super(RegionsApiTestCase, self).setUp()
self.config = {
"services": collections.OrderedDict([
("availability", "foo_endpoint"),
("health", "foo_endpoint"),
("infra", {})
])
}
self.saved_use_fake_api = fake_api_base.USE_FAKE_DATA
fake_api_base.USE_FAKE_DATA = False

def tearDown(self):
fake_api_base.USE_FAKE_DATA = self.saved_use_fake_api
super(RegionsApiTestCase, self).tearDown()

@mock.patch("ceagle.config.get_config")
@mock.patch("ceagle.api.client.Client")
def test_get_regions(self, mock_client, mock_config):
mock_config.return_value = self.config
mock_client.return_value.get.side_effect = [
(["a1", "b1", "c1"], 200),
(["b1", "d1", "e1"], 200),
(["a2", "b2", "c2"], 200),
(["b2", "d2", "e2"], 200)
]
code, resp = self.get("/api/v1/regions")
self.assertEqual(200, code)
resp["regions"].sort()
self.assertEqual({"regions": ["a1", "b1", "c1", "d1", "e1"]}, resp)

mock_client.get_client.reset_mock()
code, resp = self.get("/api/v1/regions/detailed")
self.assertEqual(200, code)
self.assertEqual({
"regions": {
"a2": {"services": ["availability"]},
"b2": {"services": ["availability", "health"]},
"c2": {"services": ["availability"]},
"d2": {"services": ["health"]},
"e2": {"services": ["health"]}
}
}, resp)
12 changes: 6 additions & 6 deletions tests/unit/api/v1/test_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,23 @@ def tearDown(self):
@mock.patch("ceagle.api.client.Client")
def test_status_health_api(self, mock_client, mock_get_config):
mock_get_config.return_value = self.health_config
mock_client.return_value.get.return_value = {"status_code": 200}
mock_client.return_value.get.return_value = ({"test": 1}, 200)
code, resp = self.get("/api/v1/status/health/day")

mock_client.return_value.get.assert_called_with("/api/v1/health/day")
self.assertEqual(200, code)
self.assertEqual({"test": 1}, resp)

@mock.patch("ceagle.api.client.Client")
def test_region_status_health_api(self, mock_client, mock_get_config):
mock_get_config.return_value = self.health_config
mock_client.return_value.get.return_value = {"status_code": 200}
mock_client.return_value.get.return_value = ({"test": 2}, 200)

code, resp = self.get("/api/v1/region/test_region/status/health/day")
mock_client.return_value.get.assert_called_with(
"/api/v1/region/test_region/health/day")
self.assertEqual(200, code)
self.assertEqual({"test": 2}, resp)

def test_health_api_no_endpoint(self, mock_get_config):
mock_get_config.return_value = {}
Expand Down Expand Up @@ -115,8 +117,7 @@ def test_get_status_availability(self, mock_client, mock_config):

# OK
mock_config.return_value = self.config
mock_get = mock.Mock(return_value={"data": "nice",
"status_code": 42})
mock_get = mock.Mock(return_value=({"data": "nice"}, 42))
mock_client.return_value.get = mock_get
code, resp = self.get(uri)
self.assertEqual(42, code)
Expand All @@ -135,8 +136,7 @@ def test_get_region_status_availability(self, mock_client, mock_config):

# OK
mock_config.return_value = self.config
mock_get = mock.Mock(return_value={"data": "nice",
"status_code": 42})
mock_get = mock.Mock(return_value=({"data": "nice"}, 42))
mock_client.return_value.get = mock_get
code, resp = self.get(uri)
self.assertEqual(42, code)
Expand Down

0 comments on commit 99d880e

Please sign in to comment.