From ab7dd9a2ce27cbcb56c67773762d6ec871774803 Mon Sep 17 00:00:00 2001 From: joe <joe@incal.net> Date: Wed, 20 Feb 2013 09:39:26 +0100 Subject: [PATCH] Timestamp feature and tests. With a small change to preceding versions, now we use a "number" to store the timestamp data, as we need a little more fine-grain and there is no reason to lose information at this stage. --- configdb/client/cli.py | 24 ++++++++++++++++++++++-- configdb/client/connection.py | 15 ++++++++++++++- configdb/db/db_api.py | 2 +- configdb/db/schema.py | 4 ++-- configdb/server/wsgiapp.py | 1 + configdb/tests/db_api_test_base.py | 15 ++++++++++++++- configdb/tests/test_schema.py | 2 +- 7 files changed, 55 insertions(+), 8 deletions(-) diff --git a/configdb/client/cli.py b/configdb/client/cli.py index 70cb46d..cb30dbc 100644 --- a/configdb/client/cli.py +++ b/configdb/client/cli.py @@ -163,6 +163,18 @@ class GetAction(Action): obj = conn.get(entity.name, args._name) self.view.pprint(obj) +class TimestampAction(Action): + """Get the timestamp of last update on an entity.""" + + name = 'timestamp' + + def __init__(self, entity, parser): + pass + + def run(self, conn, entity, args): + print conn.get_timestamp(entity.name) + + class FindAction(Action): """Find instances.""" @@ -211,13 +223,18 @@ class AuditAction(object): name = 'audit' descr = 'query audit logs' - + view = JsonPlain + AUDIT_ATTRS = ('entity', 'object', 'user', 'op') + @classmethod + def set_view(cls, viewclass): + cls.view = viewclass + def __init__(self, parser): for attr in self.AUDIT_ATTRS: parser.add_argument('--' + attr) - + def run(self, conn, entity, args): query = dict((x, getattr(args, x)) for x in self.AUDIT_ATTRS @@ -262,6 +279,7 @@ class Parser(object): UpdateAction, GetAction, FindAction, + TimestampAction ) toplevel_actions = ( @@ -302,6 +320,8 @@ class Parser(object): help='use with --help for additional help', dest='_entity_name') for entity in self.schema.get_entities(): + if entity.name in self.schema.sys_schema_tables: + continue subparser = subparsers.add_parser(entity.name, help=entity.description) self._init_subparser(entity, subparser) diff --git a/configdb/client/connection.py b/configdb/client/connection.py index aa99d4b..281ca6b 100644 --- a/configdb/client/connection.py +++ b/configdb/client/connection.py @@ -204,7 +204,7 @@ class Connection(object): data = data.to_net() return [self._from_net(entity_name, x) for x in self._call(entity_name, 'find', data=data)] - + def get(self, entity_name, object_name): """Fetch a single object. @@ -251,3 +251,16 @@ class Connection(object): """ return self._request(['audit'], query) + def get_timestamp(self, entity_name): + """Fetch the timestamp of last update to a entity. + + Args: + entity_name: string, entity name + + Returns: + A String representing the unix epoch of last update + + Raises: + None + """ + return self._call(entity_name, 'timestamp') diff --git a/configdb/db/db_api.py b/configdb/db/db_api.py index 3347e64..3522c5b 100644 --- a/configdb/db/db_api.py +++ b/configdb/db/db_api.py @@ -118,7 +118,7 @@ class AdmDbApi(object): #Avoid updating timestamp for tables that are not part of the schema. return True - data = {'name': entity_name, 'ts': int(time()) } + data = {'name': entity_name, 'ts': time() } ts = self.schema.get_entity('__timestamp') data = self._unpack(ts, data) diff --git a/configdb/db/schema.py b/configdb/db/schema.py index b293322..311d745 100644 --- a/configdb/db/schema.py +++ b/configdb/db/schema.py @@ -175,7 +175,7 @@ class Schema(object): self.default_acl.set_acl(DEFAULT_ACL) def _add_timestamp(self): - ts_schema = {'name': { 'type': 'string', 'size': 32}, 'ts': {'type': 'int', 'nullable': False } } + ts_schema = {'name': { 'type': 'string', 'size': 32}, 'ts': {'type': 'number', 'nullable': False } } self.entities['__timestamp'] = Entity('__timestamp', ts_schema) def _relation_check(self): @@ -195,7 +195,7 @@ class Schema(object): return self.entities.get(name) def get_entities(self): - return self.entities.itervalues() + return self.entities.itervalues() def acl_check_fields(self, entity, fields, auth_context, op, obj): """Authorize an operation on the fields of an instance.""" diff --git a/configdb/server/wsgiapp.py b/configdb/server/wsgiapp.py index b98b079..0301015 100644 --- a/configdb/server/wsgiapp.py +++ b/configdb/server/wsgiapp.py @@ -158,6 +158,7 @@ def delete(class_name, object_name): @api_app.route('/timestamp/<class_name>') @authenticate +@json_response def ts(class_name): try: res = g.api.get_timestamp(class_name, g.auth_ctx) diff --git a/configdb/tests/db_api_test_base.py b/configdb/tests/db_api_test_base.py index 72e1712..d06a4bf 100644 --- a/configdb/tests/db_api_test_base.py +++ b/configdb/tests/db_api_test_base.py @@ -204,7 +204,7 @@ class DbApiTestBase(object): # self.api.get('host', 'utz', self.ctx).name) # self.assertRaises(exceptions.NotFound, # self.api.get, 'host', 'obz', self.ctx) - + def test_update_modify_relation(self): self.assertTrue( self.api.update('host', 'obz', {'roles': ['role2']}, self.ctx)) @@ -347,3 +347,16 @@ class DbApiTestBase(object): self.api.get_audit, {'entity': 'private', 'op': 'create'}, auth_ctx) + + def test_timestamp_is_updated(self): + result = self.api.update('host', 'obz', {'ip': '2.3.4.5'}, self.ctx) + self.assertTrue(result) + ts1 = self.api.get_timestamp('host', self.ctx).ts + self.assertTrue(ts1 != 0) + result = self.api.update('host', 'obz', {'ip': '3.3.3.5'}, self.ctx) + self.assertTrue(result) + ts2 = self.api.get_timestamp('host', self.ctx).ts + self.assertTrue(ts2 > ts1) + + def test_timestamp_for_non_updated_entity(self): + self.assertRaises(ValueError, self.api.get_timestamp('role',self.ctx)) diff --git a/configdb/tests/test_schema.py b/configdb/tests/test_schema.py index 780fd33..193ce21 100644 --- a/configdb/tests/test_schema.py +++ b/configdb/tests/test_schema.py @@ -8,7 +8,7 @@ class SchemaTest(TestBase): def test_empty_schema_ok(self): s = schema.Schema('{}') - self.assertEquals({}, s.entities) + self.assertEquals(s.sys_schema_tables, s.entities.keys()) def test_entity_without_name(self): data = """ -- GitLab