Cerberus creates an uuid for security reports

Previously, Cerberus retrieved reports according to the report identifier the plugin
provided. The goal of this US is for Cerberus to create its own uuid and keep the
report identifier of the plugin. The changes include:

* Migration of the db
* an unique attribute UUID
* Tuple (report_id, plugin_id) must be unique
* Changes of unit & functional tests

Change-Id: I9aedb88fd5a4f66ccd5e2e831790d9655f76bb09
This commit is contained in:
Romain Ziba 2015-06-11 16:06:09 +02:00
parent 0108e949f6
commit f4559a91a4
11 changed files with 105 additions and 60 deletions

View File

@ -82,18 +82,18 @@ class SecurityReportController(base.BaseController):
'tickets': ['PUT']
}
def __init__(self, report_id):
def __init__(self, uuid):
super(SecurityReportController, self).__init__()
pecan.request.context['report_id'] = report_id
self._id = report_id
pecan.request.context['uuid'] = uuid
self._id = uuid
def get_security_report(self, report_id):
def get_security_report(self, uuid):
try:
security_report = db.security_report_get(report_id)
security_report = db.security_report_get(uuid)
except Exception as e:
LOG.exception(e)
raise errors.DbError(
"Security report %s could not be retrieved" % report_id
"Security report %s could not be retrieved" % uuid
)
return security_report

View File

@ -15,7 +15,7 @@
#
import datetime
import decimal
import uuid
from cerberus.api.v1.datamodels import base
from wsme import types as wtypes
@ -25,14 +25,14 @@ class SecurityReportResource(base.Base):
""" Representation of a security report.
"""
id = wtypes.IntegerType()
uuid = wtypes.wsattr(wtypes.text)
"""Security report id."""
plugin_id = wtypes.wsattr(wtypes.text)
"""Associated plugin id."""
report_id = wtypes.wsattr(wtypes.text)
"""Associated report id."""
"""Associated report id provided by plugin."""
component_id = wtypes.wsattr(wtypes.text)
"""Associated component id."""
@ -56,7 +56,7 @@ class SecurityReportResource(base.Base):
"""Security rating."""
vulnerabilities = wtypes.wsattr(wtypes.text)
"""Associated report id."""
"""Vulnerabilities."""
vulnerabilities_number = wtypes.IntegerType()
"""Total of Vulnerabilities."""
@ -69,7 +69,7 @@ class SecurityReportResource(base.Base):
def as_dict(self):
return self.as_dict_from_keys(
['id', 'plugin_id', 'report_id', 'component_id',
['uuid', 'plugin_id', 'report_id', 'component_id',
'component_type', 'component_name', 'project_id',
'title', 'description', 'security_rating',
'vulnerabilities', 'vulnerabilities_number',
@ -85,7 +85,7 @@ class SecurityReportResource(base.Base):
@classmethod
def sample(cls):
sample = cls(initial_data={
'id': decimal.Decimal(1),
'uuid': str(uuid.uuid4()),
'security_rating': float(7.4),
'component_name': 'openstack-server',
'component_id': 'a1d869a1-6ab0-4f02-9e56-f83034bacfcb',

View File

@ -147,7 +147,8 @@ class DBException(CerberusException):
class ReportExists(DBException):
msg_fmt = _("Report %(report_id)s already exists.")
msg_fmt = _("Report %(report_id)s already exists for plugin "
"%(plugin_id)s.")
class PluginInfoExists(DBException):

View File

@ -56,14 +56,14 @@ def security_report_create(values):
return IMPL.security_report_create(values)
def security_report_update_last_report_date(id, date):
def security_report_update_last_report_date(uuid, date):
"""Create an instance from the values dictionary."""
return IMPL.security_report_update_last_report_date(id, date)
return IMPL.security_report_update_last_report_date(uuid, date)
def security_report_update_ticket_id(id, ticket_id):
def security_report_update_ticket_id(uuid, ticket_id):
"""Create an instance from the values dictionary."""
return IMPL.security_report_update_ticket_id(id, ticket_id)
return IMPL.security_report_update_ticket_id(uuid, ticket_id)
def security_report_get_all(project_id=None):
@ -71,9 +71,9 @@ def security_report_get_all(project_id=None):
return IMPL.security_report_get_all(project_id=project_id)
def security_report_get(id):
def security_report_get(uuid):
"""Get security report from its id in database"""
return IMPL.security_report_get(id)
return IMPL.security_report_get(uuid)
def security_report_get_from_report_id(report_id):

View File

@ -90,7 +90,8 @@ def _security_report_create(values):
security_report_ref.save()
except db_exc.DBDuplicateEntry as e:
LOG.exception(e)
raise exception.ReportExists(report_id=values['report_id'])
raise exception.ReportExists(report_id=values['report_id'],
plugin_id=values['plugin_id'])
except Exception as e:
LOG.exception(e)
raise exception.DBException()
@ -101,12 +102,12 @@ def security_report_create(values):
return _security_report_create(values)
def _security_report_update_last_report_date(report_id, date):
def _security_report_update_last_report_date(uuid, date):
try:
session = get_session()
report = model_query(models.SecurityReport, read_deleted="no",
session=session).filter(models.SecurityReport.id
== report_id).first()
session=session).filter(models.SecurityReport.uuid
== uuid).first()
report.last_report_date = date
report.save(session)
except Exception as e:
@ -114,16 +115,16 @@ def _security_report_update_last_report_date(report_id, date):
raise exception.DBException()
def security_report_update_last_report_date(report_id, date):
_security_report_update_last_report_date(report_id, date)
def security_report_update_last_report_date(uuid, date):
_security_report_update_last_report_date(uuid, date)
def _security_report_update_ticket_id(report_id, ticket_id):
def _security_report_update_ticket_id(uuid, ticket_id):
try:
session = get_session()
report = model_query(models.SecurityReport, read_deleted="no",
session=session).filter(models.SecurityReport.id
== report_id).first()
session=session).filter(models.SecurityReport.uuid
== uuid).first()
report.ticket_id = ticket_id
report.save(session)
except Exception as e:
@ -131,8 +132,8 @@ def _security_report_update_ticket_id(report_id, ticket_id):
raise exception.DBException()
def security_report_update_ticket_id(report_id, ticket_id):
_security_report_update_ticket_id(report_id, ticket_id)
def security_report_update_ticket_id(uuid, ticket_id):
_security_report_update_ticket_id(uuid, ticket_id)
def _security_report_get_all(project_id=None):
@ -154,19 +155,19 @@ def security_report_get_all(project_id=None):
return _security_report_get_all(project_id=project_id)
def _security_report_get(report_id):
def _security_report_get(uuid):
try:
session = get_session()
return model_query(
models.SecurityReport, read_deleted="no", session=session).filter(
models.SecurityReport.report_id == report_id).first()
models.SecurityReport.uuid == uuid).first()
except Exception as e:
LOG.exception(e)
raise exception.DBException()
def security_report_get(id):
return _security_report_get(id)
def security_report_get(uuid):
return _security_report_get(uuid)
def _security_report_get_from_report_id(report_id):
@ -185,19 +186,19 @@ def security_report_get_from_report_id(report_id):
return _security_report_get_from_report_id(report_id)
def _security_report_delete(report_id):
def _security_report_delete(uuid):
try:
session = get_session()
report = model_query(models.SecurityReport, read_deleted="no",
session=session).filter_by(report_id=report_id)
session=session).filter_by(uuid=uuid)
report.delete()
except Exception as e:
LOG.exception(e)
raise exception.DBException()
def security_report_delete(report_id):
return _security_report_delete(report_id)
def security_report_delete(uuid):
return _security_report_delete(uuid)
def _plugin_info_create(values):

View File

@ -0,0 +1,30 @@
#
# 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.
def upgrade(migrate_engine):
if migrate_engine.name == "mysql":
security_report = 'security_report'
sql = "ALTER TABLE %s ADD uuid VARCHAR(255)" \
" NOT NULL AFTER id," \
"ADD UNIQUE(uuid);" % security_report
sql += "ALTER TABLE %s DROP INDEX report_id;" % security_report
sql += "ALTER TABLE %s ADD UNIQUE (report_id, plugin_id);" \
% security_report
migrate_engine.execute(sql)
def downgrade(migrate_engine):
raise NotImplementedError('Database downgrade not supported - '
'would drop all tables')

View File

@ -80,8 +80,9 @@ class SecurityReport(BASE, CerberusBase):
__table_args__ = ()
id = Column(Integer, primary_key=True)
uuid = Column(String(255), unique=True)
plugin_id = Column(String(255))
report_id = Column(String(255), unique=True)
report_id = Column(String(255))
component_id = Column(String(255))
component_type = Column(String(255))
component_name = Column(String(255))
@ -98,13 +99,13 @@ class SecurityReport(BASE, CerberusBase):
class SecurityReportJsonSerializer(serialize.JsonSerializer):
"""Security report serializer"""
__attributes__ = ['id', 'title', 'description', 'plugin_id', 'report_id',
'component_id', 'component_type', 'component_name',
'project_id', 'security_rating', 'vulnerabilities',
'vulnerabilities_number', 'last_report_date',
'ticket_id', 'deleted', 'created_at', 'deleted_at',
'updated_at']
__required__ = ['id', 'title', 'component_id']
__attributes__ = ['id', 'uuid', 'title', 'description', 'plugin_id',
'report_id', 'component_id', 'component_type',
'component_name', 'project_id', 'security_rating',
'vulnerabilities', 'vulnerabilities_number',
'last_report_date', 'ticket_id', 'deleted', 'created_at',
'deleted_at', 'updated_at']
__required__ = ['uuid', 'title', 'component_id']
__attribute_serializer__ = dict(created_at='date', deleted_at='date',
acknowledged_at='date')
__object_class__ = SecurityReport

View File

@ -51,8 +51,10 @@ def store_report_and_notify(title, plugin_id, report_id, component_id,
component_name, component_type, project_id,
description, security_rating, vulnerabilities,
vulnerabilities_number, last_report_date):
report_uuid = uuid.uuid4()
report = {'title': title,
'plugin_id': plugin_id,
'uuid': str(report_uuid),
'report_id': report_id,
'component_id': component_id,
'component_type': component_type,
@ -64,10 +66,8 @@ def store_report_and_notify(title, plugin_id, report_id, component_id,
'vulnerabilities_number': vulnerabilities_number}
try:
db_api.security_report_create(report)
db_report_id = db_api.security_report_get_from_report_id(
report_id).id
db_api.security_report_update_last_report_date(
db_report_id, last_report_date)
report_uuid, last_report_date)
notifications.send_notification('store', 'security_report', report)
except cerberus_exception.DBException:
raise

View File

@ -18,6 +18,8 @@ import json
from cerberus.tests.functional import base
TEST_REPORT_ID = 'test_plugin_report_id'
class AlarmTestsV1(base.TestCase):
@ -67,10 +69,20 @@ class ReportTestsV1(base.TestCase):
headers=headers)
self.assertEqual(200, resp.status)
# Check if security report has been stored in db and delete it
report_id = 'test_plugin_report_id'
# Get uuid of the security report
resp, body = self.security_client.get(
self.security_client._version + '/security_reports/' + report_id)
self.security_client._version + '/security_reports/')
report_uuid = ''
security_reports = json.loads(body).get('security_reports', [])
for security_report in security_reports:
if security_report['report_id'] == TEST_REPORT_ID:
report_uuid = security_report['uuid']
# Check if security report has been stored in db and delete it
resp, body = self.security_client.get(
self.security_client._version + '/security_reports/' + report_uuid)
report = json.loads(body)
self.assertEqual('a1d869a1-6ab0-4f02-9e56-f83034bacfcb',
report['component_id'])
@ -78,7 +90,7 @@ class ReportTestsV1(base.TestCase):
# Delete security report
resp, body = self.security_client.delete(
self.security_client._version + '/security_reports/' + report_id)
self.security_client._version + '/security_reports/' + report_uuid)
self.assertEqual(204, resp.status)

View File

@ -32,27 +32,27 @@ class TestSecurityReports(base.TestApiBase):
def setUp(self):
super(TestSecurityReports, self).setUp()
self.fake_security_report = db_utils.get_test_security_report(
id=SECURITY_REPORT_ID
uuid=SECURITY_REPORT_ID
)
self.fake_security_reports = []
self.fake_security_reports.append(self.fake_security_report)
self.fake_security_reports.append(db_utils.get_test_security_report(
id=SECURITY_REPORT_ID_2
uuid=SECURITY_REPORT_ID_2
))
self.fake_security_report_model = db_utils.get_security_report_model(
id=SECURITY_REPORT_ID
uuid=SECURITY_REPORT_ID
)
self.fake_security_reports_model = []
self.fake_security_reports_model.append(
self.fake_security_report_model)
self.fake_security_reports_model.append(
db_utils.get_security_report_model(
id=SECURITY_REPORT_ID_2
uuid=SECURITY_REPORT_ID_2
)
)
self.security_reports_path = '/security_reports'
self.security_report_path = '/security_reports/%s' \
% self.fake_security_report['report_id']
% self.fake_security_report['uuid']
def test_get(self):

View File

@ -26,7 +26,7 @@ def fake_function():
def get_test_security_report(**kwargs):
return {
'id': kwargs.get('id', 1),
'uuid': kwargs.get('uuid', 1),
'plugin_id': kwargs.get('plugin_id',
'228df8e8-d5f4-4eb9-a547-dfc649dd1017'),
'report_id': kwargs.get('report_id', '1234'),
@ -49,7 +49,7 @@ def get_test_security_report(**kwargs):
def get_security_report_model(**kwargs):
security_report = models.SecurityReport()
security_report.id = kwargs.get('id', 1)
security_report.uuid = kwargs.get('uuid', 1)
security_report.plugin_id = kwargs.get(
'plugin_id',
'228df8e8-d5f4-4eb9-a547-dfc649dd1017'