launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #10148
[Merge] lp:~stevenk/launchpad/auditor-for-packageupload into lp:launchpad
Steve Kowalik has proposed merging lp:~stevenk/launchpad/auditor-for-packageupload into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~stevenk/launchpad/auditor-for-packageupload/+merge/116180
Write an AuditorClient class, which matches up to the AuditorServer test fixture. It will connect to the configured auditor instance and send or receive data. I have also added a feature flag 'auditor.enabled' which model/browser code can use to check if they should call into the auditor service.
--
https://code.launchpad.net/~stevenk/launchpad/auditor-for-packageupload/+merge/116180
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/auditor-for-packageupload into lp:launchpad.
=== added file 'lib/lp/services/auditor/client.py'
--- lib/lp/services/auditor/client.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/auditor/client.py 2012-07-23 01:41:19 +0000
@@ -0,0 +1,61 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Client that will send and recieve audit logs to an auditor instance."""
+
+__metaclass__ = type
+__all__ = [
+ 'AuditorClient',
+ ]
+
+from datetime import datetime
+import json
+from urllib import urlencode
+from urllib2 import urlopen
+
+from lp.services.config import config
+from lp.services.enterpriseid import (
+ enterpriseid_to_object,
+ object_to_enterpriseid,
+ )
+
+
+class AuditorClient:
+ def __init__(self):
+ self.auditor = "http://%s:%s" % (
+ config.auditor.host, config.auditor.port)
+
+ def send(self, obj, operation, actorobj, comment=None, details=None):
+ unencoded_data = (
+ ('object', object_to_enterpriseid(obj)),
+ ('operation', operation),
+ ('actor', object_to_enterpriseid(actorobj)),
+ ('date', datetime.now()))
+ if comment:
+ unencoded_data.append(('comment', comment))
+ if details:
+ unencoded_data.append(('details', details))
+ f = urlopen('%s/log/' % self.auditor, urlencode(unencoded_data))
+ return f.read()
+
+ def recieve(self, obj=None, operation=None, actorobj=None, limit=None):
+ if not obj and not operation and not actorobj:
+ raise AttributeError
+ params = []
+ if obj:
+ params.append('object=%s' % object_to_enterpriseid(obj))
+ if operation:
+ params.append('operation=%s' % operation)
+ if actorobj:
+ params.append('actor=%s' % object_to_enterpriseid(actorobj))
+ if limit:
+ params.append('limit=%s' % limit)
+ f = urlopen('%s/fetch/?%s' % (self.auditor, '&'.join(params)))
+ logs = json.loads(f.read())
+ # Process the actors and objects back from enterprise ids.
+ for entry in logs['log-entries']:
+ actorstr = entry['actor']
+ objstr = entry['object']
+ entry['actor'] = enterpriseid_to_object(actorstr)
+ entry['object'] = enterpriseid_to_object(objstr)
+ return logs['log-entries']
=== added file 'lib/lp/services/auditor/tests/test_client.py'
--- lib/lp/services/auditor/tests/test_client.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/auditor/tests/test_client.py 2012-07-23 01:41:19 +0000
@@ -0,0 +1,27 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+from lp.services.auditor.client import AuditorClient
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import AuditorLayer
+
+
+class TestAuditorClient(TestCaseWithFactory):
+
+ layer = AuditorLayer
+
+ def test_send_and_recieve(self):
+ # We can use .send() and .recieve() on AuditorClient to log.
+ actor = self.factory.makePerson()
+ pu = self.factory.makePackageUpload()
+ client = AuditorClient()
+ result = client.send(pu, 'packageupload-accepted', actor)
+ self.assertEqual('Operation recorded.', result)
+ result = client.recieve(obj=pu)
+ del result[0]['date'] # Ignore the date.
+ expected = [{
+ u'comment': u'', u'details': u'', u'actor': actor,
+ u'operation': u'packageupload-accepted', u'object': pu}]
+ self.assertContentEqual(expected, result)
=== modified file 'lib/lp/services/config/schema-lazr.conf'
--- lib/lp/services/config/schema-lazr.conf 2012-07-19 13:55:15 +0000
+++ lib/lp/services/config/schema-lazr.conf 2012-07-23 01:41:19 +0000
@@ -33,6 +33,11 @@
run_parts_location: none
+[auditor]
+host: localhost
+port: none
+
+
[binaryfile_expire]
dbuser: binaryfile-expire
=== modified file 'lib/lp/services/enterpriseid.py'
--- lib/lp/services/enterpriseid.py 2012-03-22 23:21:24 +0000
+++ lib/lp/services/enterpriseid.py 2012-07-23 01:41:19 +0000
@@ -11,15 +11,9 @@
import os
-from lp.registry.model.person import Person
from lp.services.config import config
-known_types = {
- 'Person': Person,
- }
-
-
def object_to_enterpriseid(obj):
"""Given an object, convert it to SOA Enterprise ID."""
otype = obj.__class__.__name__
@@ -33,8 +27,14 @@
def enterpriseid_to_object(eid):
"""Given an SOA Enterprise ID, return the object that it references."""
+ from lp.registry.model.person import Person
+ from lp.soyuz.model.queue import PackageUpload
scheme = eid.split(':')
if not scheme[0].startswith('lp'):
raise TypeError
+ known_types = {
+ 'PackageUpload': PackageUpload,
+ 'Person': Person,
+ }
klass = known_types[scheme[1]]
return klass.get(scheme[2])
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2012-07-17 21:41:42 +0000
+++ lib/lp/services/features/flags.py 2012-07-23 01:41:19 +0000
@@ -313,6 +313,12 @@
'',
'',
''),
+ ('auditor.enabled',
+ 'boolean',
+ 'If true, send audit data to an auditor instance.',
+ '',
+ '',
+ ''),
])
# The set of all flag names that are documented.
=== modified file 'lib/lp/soyuz/browser/queue.py'
--- lib/lp/soyuz/browser/queue.py 2012-07-09 12:32:23 +0000
+++ lib/lp/soyuz/browser/queue.py 2012-07-23 01:41:19 +0000
@@ -443,7 +443,7 @@
def queue_action_accept(self, queue_item):
"""Reject the queue item passed."""
- queue_item.acceptFromQueue()
+ queue_item.acceptFromQueue(user=self.user)
def queue_action_reject(self, queue_item):
"""Accept the queue item passed."""
=== modified file 'lib/lp/soyuz/interfaces/queue.py'
--- lib/lp/soyuz/interfaces/queue.py 2012-07-09 12:32:23 +0000
+++ lib/lp/soyuz/interfaces/queue.py 2012-07-23 01:41:19 +0000
@@ -358,8 +358,9 @@
"""
@export_write_operation()
+ @call_with(user=REQUEST_USER)
@operation_for_version("devel")
- def acceptFromQueue(logger=None, dry_run=False):
+ def acceptFromQueue(logger=None, dry_run=False, user=None):
"""Call setAccepted, do a syncUpdate, and send notification email.
* Grant karma to people involved with the upload.
=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py 2012-07-11 13:43:32 +0000
+++ lib/lp/soyuz/model/queue.py 2012-07-23 01:41:19 +0000
@@ -50,6 +50,7 @@
from lp.archiveuploader.tagfiles import parse_tagfile_content
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.model.sourcepackagename import SourcePackageName
+from lp.services.auditor.client import AuditorClient
from lp.services.config import config
from lp.services.database.bulk import load_referencing
from lp.services.database.constants import UTC_NOW
@@ -64,6 +65,7 @@
SQLBase,
sqlvalues,
)
+from lp.services.features import getFeatureFlag
from lp.services.librarian.browser import ProxiedLibraryFileAlias
from lp.services.librarian.interfaces.client import DownloadFailed
from lp.services.librarian.model import LibraryFileAlias
@@ -625,7 +627,7 @@
# Give some karma!
self._giveKarma()
- def acceptFromQueue(self, logger=None, dry_run=False):
+ def acceptFromQueue(self, logger=None, dry_run=False, user=None):
"""See `IPackageUpload`."""
assert not self.is_delayed_copy, 'Cannot process delayed copies.'
@@ -633,6 +635,9 @@
self._acceptNonSyncFromQueue(logger, dry_run)
else:
self._acceptSyncFromQueue()
+ if bool(getFeatureFlag('auditor.enabled')):
+ client = AuditorClient()
+ client.send(self, 'packageupload-accepted', user)
def acceptFromCopy(self):
"""See `IPackageUpload`."""
=== modified file 'lib/lp/testing/layers.py'
--- lib/lp/testing/layers.py 2012-07-05 10:38:03 +0000
+++ lib/lp/testing/layers.py 2012-07-23 01:41:19 +0000
@@ -707,44 +707,6 @@
pass
-class AuditorLayer(BaseLayer):
-
- auditor = AuditorServer()
-
- _is_setup = False
-
- @classmethod
- @profiled
- def setUp(cls):
- cls.auditor.setUp()
- cls.config_fixture.add_section(
- cls.auditor.config.service_config)
- cls.appserver_config_fixture.add_section(
- cls.auditor.config.service_config)
- cls._is_setup = True
-
- @classmethod
- @profiled
- def tearDown(cls):
- if not cls._is_setup:
- return
- cls.auditor.cleanUp()
- cls._is_setup = False
- # Can't pop the config above, so bail here and let the test runner
- # start a sub-process.
- raise NotImplementedError
-
- @classmethod
- @profiled
- def testSetUp(cls):
- pass
-
- @classmethod
- @profiled
- def testTearDown(cls):
- pass
-
-
# We store a reference to the DB-API connect method here when we
# put a proxy in its place.
_org_connect = None
@@ -1389,6 +1351,42 @@
disconnect_stores()
+class AuditorLayer(LaunchpadFunctionalLayer):
+
+ auditor = AuditorServer()
+
+ _is_setup = False
+
+ @classmethod
+ @profiled
+ def setUp(cls):
+ cls.auditor.setUp()
+ cls.config_fixture.add_section(cls.auditor.service_config)
+ cls.appserver_config_fixture.add_section(cls.auditor.service_config)
+ cls._is_setup = True
+
+ @classmethod
+ @profiled
+ def tearDown(cls):
+ if not cls._is_setup:
+ return
+ cls.auditor.cleanUp()
+ cls._is_setup = False
+ # Can't pop the config above, so bail here and let the test runner
+ # start a sub-process.
+ raise NotImplementedError
+
+ @classmethod
+ @profiled
+ def testSetUp(cls):
+ pass
+
+ @classmethod
+ @profiled
+ def testTearDown(cls):
+ pass
+
+
class GoogleLaunchpadFunctionalLayer(LaunchpadFunctionalLayer,
GoogleServiceLayer):
"""Provides Google service in addition to LaunchpadFunctionalLayer."""
=== modified file 'versions.cfg'
--- versions.cfg 2012-07-13 01:55:27 +0000
+++ versions.cfg 2012-07-23 01:41:19 +0000
@@ -9,7 +9,7 @@
anyjson = 0.3.1
argparse = 1.2.1
auditor = 0.0.1
-auditorfixture = 0.0.1
+auditorfixture = 0.0.2
BeautifulSoup = 3.1.0.1
bson = 0.3.2
# The source for this version of bzr is at lp:~benji/bzr/bug-998040
Follow ups