Skip to content

Commit

Permalink
Add security service and fake data
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Skripnick committed Dec 20, 2016
1 parent 84d13c3 commit f707005
Show file tree
Hide file tree
Showing 14 changed files with 263 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ matrix:
- docker
install:
- docker build -t ceagle .
- docker run -d -p 127.0.0.1:5000:5000 ceagle
- docker run -d -p 127.0.0.1:5000:5000 -e "CEAGLE_CONF=/app/tests/ci/fake-clients-config.json" ceagle
- git clone http://github.com/cybertk/abao
- cd abao
- git checkout 0.5.0
Expand Down
25 changes: 25 additions & 0 deletions ceagle/api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2016: Mirantis Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.


class Client(object):
"""REST client."""

def __init__(self, name, endpoint):
self.name = name
self.endpoint = endpoint

def __repr__(self):
return "<Client '%s'>" % self.name
27 changes: 16 additions & 11 deletions ceagle/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,20 @@

import requests

from ceagle.api import base
from ceagle.api_fake_data import fake_security
from ceagle import config

FAKE_CLIENT_MAP = {
"security": fake_security.Client,
}


class UnknownService(Exception):
pass


class Client(object):
"""REST client."""

def __init__(self, name, endpoint):
self.name = name
self.endpoint = endpoint

def __repr__(self):
return "<Client '%s'>" % self.name
class Client(base.Client):

def get(self, uri="/", **kwargs):
"""Make GET request and decode JSON data.
Expand All @@ -55,12 +53,19 @@ def get(self, uri="/", **kwargs):


def get_client(service_name):
"""Return client for given service anme, if possible.
"""Return client for given service name, if possible.
:param service_name: str name of microservice
:returns: Client
"""
if config.get_config().get("use_fake_api_data", True):
client_class = FAKE_CLIENT_MAP.get(service_name)
if client_class is None:
raise NotImplementedError(
"Fake client for '%s' is not implemented" % service_name)
else:
client_class = Client
endpoint = config.get_config().get("services", {}).get(service_name)
if endpoint:
return Client(name=service_name, endpoint=endpoint)
return client_class(name=service_name, endpoint=endpoint)
raise UnknownService("Unknown service '%s'" % service_name)
11 changes: 7 additions & 4 deletions ceagle/api/v1/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@

import flask

from ceagle.api import client

security = flask.Blueprint("security", __name__)


@security.route("/")
def get_security():
return flask.jsonify({"result": {"security": "dummy"}})
@security.route("/<region>/security/issues/<period>", methods=["GET"])
def get_issues(region, period):
c = client.get_client("security")
body, status = c.get("%s/security/issues/%s" % (region, period))
return flask.jsonify(body), status


def get_blueprints():
return [["/security", security]]
return [["/region", security]]
79 changes: 79 additions & 0 deletions ceagle/api_fake_data/fake_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2016: Mirantis Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import datetime

from ceagle.api import base


def _get_fake_issue(region, issue_type, days):
now = datetime.datetime.now()
discovered = now - datetime.timedelta(days=days)
confirmed = now
return {
"type": issue_type,
"description": "Sample issue " + issue_type,
"discovered_at": discovered.isoformat(),
"confirmed_at": confirmed.isoformat(),
"object_id": "test-id-%s-%d" % (issue_type, days),
"region": region,
}

FAKE_ISSUES = {
"Region1": [
_get_fake_issue("Region1", "IssueType1", 2),
_get_fake_issue("Region1", "IssueType2", 8),
],
"north-2.piedpiper.net": [
_get_fake_issue("north-2.piedpiper.net", "IssueType1", 0),
_get_fake_issue("north-2.piedpiper.net", "IssueType2", 2),
_get_fake_issue("north-2.piedpiper.net", "IssueType2", 8),
],
}

PERIOD_MAP = {
"day": 1,
"week": 7,
"month": 30,
}


class Client(base.Client):

def get(self, uri="/", **kwargs):
"""Make GET request and decode JSON data.
:param uri: resource URI
:param kwargs: query parameters
:returns: dict response data
"""
region, security, issues, period = uri.split("/")
if issues != "issues":
return {"error": "Not found"}, 404
issues = []
all_issues = FAKE_ISSUES.get(region)
if all_issues:
now = datetime.datetime.now()
try:
discovered_before = (now - datetime.timedelta(
days=PERIOD_MAP[period])).isoformat("T")
except KeyError:
return {"error": "Not found"}, 404
for issue in all_issues:
if issue["discovered_at"] >= discovered_before:
issues.append(issue)
else:
return {"error": "Region not found"}, 404
return {"issues": issues}, 200
2 changes: 1 addition & 1 deletion ceagle/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def not_found(error):

@app.errorhandler(client.UnknownService)
def handle_unknown_service(ex):
return flask.jsonify({"error": str(ex)}), 400
return flask.jsonify({"error": str(ex)}), 404


for bp in [status, infrastructure, intelligence, optimization, security,
Expand Down
10 changes: 10 additions & 0 deletions raml/abao_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ hooks.before('GET /api/{version}/region/{region}/infra -> 404', function (test,
done();
});

hooks.before('GET /api/{version}/region/{region}/security/issues/{period} -> 200', function (test, done) {
test.request.params = params;
done();
});

hooks.before('GET /api/{version}/region/{region}/security/issues/{period} -> 404', function (test, done) {
test.request.params = paramsBadRegion;
done();
});

hooks.before('GET /api/{version}/status/{period} -> 200', function (test, done) {
test.request.params = params;
done();
Expand Down
13 changes: 13 additions & 0 deletions raml/api.raml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ mediaType: application/json
schema: !include schemas/404.json
example: !include response_examples/404/region.json

/security/issues/{period}:
uriParameters:
period:
description: "Return issues discovered at specific period"
enum: ["day", "week", "month"]
get:
description: "Return list of Issue objects"
responses:
200:
body:
schema: !include schemas/200.json
example: !include response_examples/200/security_issues.json

/status/health/{period}:
uriParameters:
period:
Expand Down
28 changes: 28 additions & 0 deletions raml/response_examples/200/security_issues.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[
{
"issueType": "securityGroup",
"regionId": "Region1",
"description": "Security group too open",
"discoveredAt": "2016-02-28T16:41:41.090Z",
"confirmedAt": "2016-03-28T16:41:41.090Z",
"subject": {
"id": "d8b0be7c-2ad7-4083-8d5a-a7a9a56fdd14",
"type": "securityGroup",
"tenantId": "demo",
"userId": "demo"
}
}, {
"issueType": "securityGroup",
"regionId": "Region1",
"description": "Security group too open",
"discoveredAt": "2016-02-28T16:21:41.090Z",
"confirmedAt": "2016-03-28T16:31:41.090Z",
"resolvedAt": "2016-04-28T16:31:41.090Z",
"subject": {
"id": "d8b0be7c-2ad7-4083-8d5a-a7a9a56fdd15",
"type": "securityGroup",
"tenantId": "demo",
"userId": "demo"
}
}
]
4 changes: 4 additions & 0 deletions raml/response_examples/200/security_regions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"Region1",
"Region2"
]
10 changes: 10 additions & 0 deletions tests/ci/fake-clients-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"flask": {
"PORT": 5000,
"HOST": "0.0.0.0",
"DEBUG": true
},
"services": {
"security": "http://example.org/api/security"
}
}
9 changes: 6 additions & 3 deletions tests/unit/api/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,15 @@ class ModuleTestCase(test.TestCase):
@mock.patch("ceagle.api.client.config")
def test_get_config(self, mock_config):
self.assertRaises(TypeError, client.get_client)
mock_config.get_config.return_value = {"services": {"bar": {}}}
mock_config.get_config.return_value = {"use_fake_api_data": False,
"services": {"bar": {}}}
self.assertRaises(client.UnknownService, client.get_client, "foo")
mock_config.get_config.return_value = {"services": {"foo": ""}}
mock_config.get_config.return_value = {"use_fake_api_data": False,
"services": {"foo": {}}}
self.assertRaises(client.UnknownService, client.get_client, "foo")

cfg = {"services": {"foo": "http://foo_ep"}}
cfg = {"services": {"foo": "http://foo_ep"},
"use_fake_api_data": False}
mock_config.get_config.return_value = cfg
ct = client.get_client("foo")
self.assertIsInstance(ct, client.Client)
Expand Down
60 changes: 60 additions & 0 deletions tests/unit/api/v1/test_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2016: Mirantis Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from tests.unit import test

import mock


fake_config = mock.Mock()
fake_config.get_config.return_value = {
"use_fake_api_data": True,
"services": {
"security": "ok",
}
}


@mock.patch("ceagle.api.client.config", fake_config)
class SecurityTestCase(test.TestCase):

def test_get_issues_month(self):
resp = self.get("/api/v1/region/Region1/security/issues/month")
self.assertEqual(200, resp[0])
self.assertEqual(2, len(resp[1]["issues"]))

def test_get_issues_week(self):
resp = self.get(
"/api/v1/region/north-2.piedpiper.net/security/issues/week")
self.assertEqual(200, resp[0])
self.assertEqual(2, len(resp[1]["issues"]))

def test_get_issues_day(self):
resp = self.get(
"/api/v1/region/north-2.piedpiper.net/security/issues/day")
self.assertEqual(200, resp[0])
self.assertEqual(1, len(resp[1]["issues"]))

def test_get_issues_empty(self):
resp = self.get("/api/v1/region/Region1/security/issues/day")
self.assertEqual((200, {"issues": []}), resp)

def test_get_issues_unknown_region(self):
resp = self.get("/api/v1/region/Region3/security/issues/day")
self.assertEqual(404, resp[0])

def test_get_issues_404(self):
resp = self.get("/api/v1/region/Region3/day")
self.assertEqual(404, resp[0])
6 changes: 3 additions & 3 deletions tests/unit/api/v1/test_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ def test_region_status_health_api(self, mock_get_client):

@mock.patch("ceagle.config.get_config")
def test_health_api_no_endpoint(self, mock_get_config):
mock_get_config.return_value = {}
mock_get_config.return_value = {"use_fake_api_data": False}
code, resp = self.get("/api/v1/status/health/day")
self.assertEqual(400, code)
self.assertEqual(404, code)
self.assertEqual({"error": "Unknown service 'health'"}, resp)

code, resp = self.get("/api/v1/region/test_region/status/health/day")
self.assertEqual(400, code)
self.assertEqual(404, code)
self.assertEqual({"error": "Unknown service 'health'"}, resp)


Expand Down

0 comments on commit f707005

Please sign in to comment.