Skip to content

Commit e7a8f78

Browse files
committed
WIP docs and tests for refactored method
1 parent 434183a commit e7a8f78

File tree

7 files changed

+132
-125
lines changed

7 files changed

+132
-125
lines changed

TAGGING.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Tagging
2+
3+
todo blurb about tagging
4+
5+
You probably don't need this unless you have a lot of objects!
6+
7+
Support level: currenly only implemented for AWS/S3
8+
9+
Note extra cost, extra api calls + tagging cost per object per month
10+
11+
## Sources
12+
13+
Constraints:
14+
- Key - max 128 chars (aws + azure)
15+
- Value - max 128 chars (actual max is 256, but reserving half for future use e.g. versioning)
16+
- Deterministic
17+
18+
Implemented:
19+
- Mimetype
20+
- Env - defined by <insert cfg here>
21+
22+
## Multi env
23+
- Turn off object override on every env except prod
24+
- TODO diagram here
25+
26+
## Reporting
27+
TODO blurb about reporting
28+
Note reporting quirk because of multi env setup and what each env knows about.
29+
30+
## Migration
31+
32+
TODO
33+
If you change any of the tags e.g. the way they work and want to re-update tags, update tag status to needs sync and queue adhoc task to re-sync.
34+
35+
## For developers
36+
37+
### Adding a new source
38+
- Add to <insert place in tag manager here>
39+
- Run migration steps (TODO link to above)

classes/local/store/object_file_system.php

+49
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
use stored_file;
3737
use file_storage;
3838
use BlobRestProxy;
39+
use coding_exception;
40+
use Throwable;
3941
use tool_objectfs\local\manager;
42+
use tool_objectfs\local\tag\tag_manager;
4043

4144
defined('MOODLE_INTERNAL') || die();
4245

@@ -1154,4 +1157,50 @@ private function update_object(array $result): array {
11541157

11551158
return $result;
11561159
}
1160+
1161+
/**
1162+
* Syncs tags post upload for a given hash.
1163+
* Note this function assumes tagging is enable and supported by the fs.
1164+
*
1165+
* @param string $contenthash file to sync tags for
1166+
*/
1167+
public function sync_object_tags(string $contenthash) {
1168+
// Get a lock before syncing, to ensure other parts of objectfs are not moving/interacting with this object.
1169+
$lock = $this->acquire_object_lock($contenthash, 5);
1170+
1171+
// No lock - just skip it.
1172+
if (!$lock) {
1173+
throw new coding_exception("Could not get object lock"); // TODO different ex type?
1174+
}
1175+
1176+
try {
1177+
$objectexists = $this->is_file_readable_externally_by_hash($contenthash);
1178+
// If object does not exist, cannot sync tags to nothing, abort.
1179+
if (!$objectexists) {
1180+
// TODO maybe this should be a failed status?
1181+
tag_manager::mark_object_tag_sync_status($contenthash, tag_manager::SYNC_STATUS_SYNC_NOT_REQUIRED);
1182+
return;
1183+
}
1184+
1185+
// Cannot override and object exists, query existing and store.
1186+
if (!manager::can_override_existing_objects() && $objectexists) {
1187+
// Query existing tags and store them.
1188+
$existingtags = $this->get_external_client()->get_object_tags($contenthash);
1189+
tag_manager::store_tags_locally($contenthash, $existingtags);
1190+
1191+
// Else can override, upload new and store.
1192+
} else {
1193+
$tags = tag_manager::gather_object_tags_for_upload($contenthash);
1194+
$this->get_external_client()->set_object_tags($contenthash, $tags);
1195+
tag_manager::store_tags_locally($contenthash, $tags);
1196+
}
1197+
1198+
// Either way, it has synced.
1199+
tag_manager::mark_object_tag_sync_status($contenthash, tag_manager::SYNC_STATUS_SYNC_NOT_REQUIRED);
1200+
} catch (Throwable $e) {
1201+
$lock->release();
1202+
throw $e;
1203+
}
1204+
$lock->release();
1205+
}
11571206
}

classes/local/tag/tag_manager.php

-34
Original file line numberDiff line numberDiff line change
@@ -160,40 +160,6 @@ public static function mark_object_tag_sync_status(string $contenthash, int $sta
160160
$DB->set_field('tool_objectfs_objects', 'tagsyncstatus', $status, ['contenthash' => $contenthash]);
161161
}
162162

163-
/**
164-
* Syncs an objects tags after they are uploaded.
165-
* Note - you should take out the object lock before calling this function to avoid race conditions.
166-
* @param string $contenthash
167-
* @param object_file_system $fs
168-
*/
169-
public static function sync_object_tags_post_upload(string $contenthash, object_file_system $fs) {
170-
$client = $fs->get_external_client();
171-
$objectexists = $fs->is_file_readable_externally_by_hash($contenthash);
172-
173-
// If object does not exist, cannot sync tags to nothing, abort.
174-
if (!$objectexists) {
175-
// TODO maybe this should be a failed status?
176-
self::mark_object_tag_sync_status($contenthash, self::SYNC_STATUS_SYNC_NOT_REQUIRED);
177-
return;
178-
}
179-
180-
// Cannot override and object exists, query existing and store.
181-
if (!manager::can_override_existing_objects() && $objectexists) {
182-
// Query existing tags and store them.
183-
$existingtags = $client->get_object_tags($contenthash);
184-
self::store_tags_locally($contenthash, $existingtags);
185-
186-
// Else can override, upload new and store.
187-
} else {
188-
$tags = self::gather_object_tags_for_upload($contenthash);
189-
$client->set_object_tags($contenthash, $tags);
190-
self::store_tags_locally($contenthash, $tags);
191-
}
192-
193-
// Either way, it has synced.
194-
self::mark_object_tag_sync_status($contenthash, self::SYNC_STATUS_SYNC_NOT_REQUIRED);
195-
}
196-
197163
/**
198164
* Returns a simple list of all the sources and their descriptions.
199165
* @return string html string

classes/task/update_object_tags.php

+2-17
Original file line numberDiff line numberDiff line change
@@ -50,29 +50,14 @@ public function execute() {
5050
// Sanity check that fs is object file system and not anything else.
5151
$fs = get_file_storage()->get_file_system();
5252

53-
if (!method_exists($fs, "acquire_object_lock")) {
53+
if (!method_exists($fs, "sync_object_tags")) {
5454
mtrace("File system is not object file system, exiting");
5555
return;
5656
}
5757

5858
// For each, try to sync their tags.
5959
foreach ($contenthashes as $contenthash) {
60-
// Get a lock before syncing, to ensure other parts of objectfs are not moving/interacting with this object.
61-
$lock = $fs->acquire_object_lock($contenthash, 5);
62-
63-
// No lock - just skip it.
64-
if (!$lock) {
65-
continue;
66-
}
67-
68-
try {
69-
mtrace("Syncing tags for " . $contenthash);
70-
tag_manager::sync_object_tags_post_upload($contenthash);
71-
} catch (Throwable $e) {
72-
$lock->release();
73-
throw $e;
74-
}
75-
$lock->release();
60+
$fs->sync_object_tags($contenthash);
7661
}
7762

7863
// Re-queue self to process more in another iteration.

classes/tests/testcase.php

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ protected function setUp(): void {
3939
global $CFG;
4040
$CFG->alternative_file_system_class = '\\tool_objectfs\\tests\\test_file_system';
4141
$CFG->forced_plugin_settings['tool_objectfs']['deleteexternal'] = false;
42+
$CFG->objectfs_environment_name = 'test';
4243
$this->filesystem = new test_file_system();
4344
$this->logger = new \tool_objectfs\log\null_logger();
4445
$this->resetAfterTest(true);

tests/local/tagging_test.php

+39-74
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,17 @@ public function test_tag_sources_identifier(tag_source $source) {
7676
* @dataProvider tag_source_provider
7777
*/
7878
public function test_tag_sources_value(tag_source $source) {
79-
// Ensure tag value is < 256 chars, to fit AWS & Azure spec.
79+
// Ensure tag value is < 128 chars, AWS & Azure spec allow for 256, but we reserve 128 for future use.
8080
$file = $this->create_duplicated_object();
8181
$value = $source->get_value_for_contenthash($file->contenthash);
82+
83+
// Null value - allowed, but means we cannot test.
84+
if (is_null($value)) {
85+
return;
86+
}
87+
8288
$count = strlen($value);
83-
$this->assertLessThan(256, $count);
89+
$this->assertLessThan(128, $count);
8490
$this->assertGreaterThan(0, $count);
8591
}
8692

@@ -135,66 +141,43 @@ public function test_is_tagging_enabled_and_supported(bool $enabledinconfig, boo
135141
$this->assertEquals($expected, tag_manager::is_tagging_enabled_and_supported());
136142
}
137143

138-
/**
139-
* Tests query_local_tags
140-
* @covers \tool_objectfs\local\tag_manager::query_local_tags
141-
*/
142-
public function test_query_local_tags() {
143-
global $DB;
144+
public function test_gather_object_tags_for_upload() {
145+
$object = $this->create_duplicated_object();
146+
$tags = tag_manager::gather_object_tags_for_upload($object->contenthash);
144147

145-
// Setup some fake tag data.
146-
$DB->insert_records('tool_objectfs_object_tags', [
147-
[
148-
'contenthash' => 'abc123',
149-
'tagkey' => 'test',
150-
'tagvalue' => 'test',
151-
'timemodified' => time()
152-
],
153-
[
154-
'contenthash' => 'abc123',
155-
'tagkey' => 'test2',
156-
'tagvalue' => 'test2',
157-
'timemodified' => time()
158-
]
159-
]);
160-
161-
$this->assertCount(2, tag_manager::query_local_tags('abc123'));
162-
$this->assertCount(0, tag_manager::query_local_tags('doesnotexist'));
148+
$this->assertArrayHasKey('mimetype', $tags);
149+
$this->assertArrayHasKey('environment', $tags);
150+
$this->assertEquals('text', $tags['mimetype']);
151+
$this->assertEquals('test', $tags['environment']);
163152
}
164153

165-
/**
166-
* Tests update_tags_if_necessary
167-
* @covers \tool_objectfs\local\tag_manager::update_tags_if_necessary
168-
*/
169-
public function test_update_tags_if_necessary() {
154+
public function test_store_tags_locally() {
170155
global $DB;
171156

172-
// There should be no existing tags to begin with.
173-
$this->assertEmpty(tag_manager::query_local_tags('test'));
174-
175-
// Update tags if necessary - should update the tags.
176-
tag_manager::update_tags_if_necessary('test');
157+
$tags = [
158+
'test1' => 'abc',
159+
'test2' => 'xyz'
160+
];
161+
$hash = 'thisisatest';
177162

178-
// Query tags - should be equal to number defined by the manager.
179-
$expectedcount = count(tag_manager::get_defined_tag_sources());
180-
$this->assertCount($expectedcount, tag_manager::query_local_tags('test'), 'Tags created match the number defined by manager');
163+
// Ensure no tags for hash intially.
164+
$this->assertEmpty($DB->get_records('tool_objectfs_object_tags', ['contenthash' => $hash]));
181165

182-
// Note the timemodified of one of the tags.
183-
$timemodified = $DB->get_field('tool_objectfs_object_tags', 'timemodified', ['contenthash' => 'test', 'tagkey' => tag_manager::get_defined_tag_sources()[0]->get_identifier()]);
184-
185-
// Running again, timemodified should not change.
186-
$this->waitForSecond();
187-
tag_manager::update_tags_if_necessary('test');
166+
// Store.
167+
tag_manager::store_tags_locally($hash, $tags);
188168

189-
$newtimemodified = $DB->get_field('tool_objectfs_object_tags', 'timemodified', ['contenthash' => 'test', 'tagkey' => tag_manager::get_defined_tag_sources()[0]->get_identifier()]);
190-
$this->assertEquals($timemodified, $newtimemodified, 'Tags timemodified must not change if not forced and values are not different');
169+
// Confirm they are stored.
170+
$queriedtags = $DB->get_records('tool_objectfs_object_tags', ['contenthash' => $hash]);
171+
$this->assertCount(2, $queriedtags);
172+
$tagtimebefore = current($queriedtags)->timemodified;
191173

192-
// Except if it is forced.
174+
// Re-store, confirm times changed.
193175
$this->waitForSecond();
194-
tag_manager::update_tags_if_necessary('test', true);
195-
196-
$timemodifiedafterforce = $DB->get_field('tool_objectfs_object_tags', 'timemodified', ['contenthash' => 'test', 'tagkey' => tag_manager::get_defined_tag_sources()[0]->get_identifier()]);
197-
$this->assertNotEquals($timemodified, $timemodifiedafterforce, 'Forced tag update changed time modified');
176+
tag_manager::store_tags_locally($hash, $tags);
177+
$queriedtags = $DB->get_records('tool_objectfs_object_tags', ['contenthash' => $hash]);
178+
$tagtimeafter = current($queriedtags)->timemodified;
179+
180+
$this->assertNotSame($tagtimebefore, $tagtimeafter);
198181
}
199182

200183
/**
@@ -288,27 +271,9 @@ public function test_get_objects_needing_sync_limit() {
288271
$this->assertCount(1, tag_manager::get_objects_needing_sync(1));
289272
}
290273

291-
/**
292-
* Tests replicate_local_to_external_tags_for_object
293-
* @covers \tool_objectfs\local\tag_manager::replicate_local_to_external_tags_for_object
294-
*/
295-
public function test_replicate_local_to_external_tags_for_object() {
296-
global $DB;
297-
298-
// Enable tagging, setup test fs.
299-
$config = manager::get_objectfs_config();
300-
$config->taggingenabled = true;
301-
$config->enabletasks = true;
302-
$config->filesystem = '\\tool_objectfs\\tests\\test_file_system';
303-
manager::set_objectfs_config($config);
304-
305-
// Setup object, mark as needing sync.
306-
$object = $this->create_remote_object();
307-
$DB->set_field('tool_objectfs_objects', 'tagsyncstatus', tag_manager::SYNC_STATUS_NEEDS_SYNC, ['id' => $object->id]);
308-
tag_manager::replicate_local_to_external_tags_for_object($object->contenthash);
309-
310-
// Ensure status is now set as not needing sync.
311-
$status = $DB->get_field('tool_objectfs_objects', 'tagsyncstatus', ['id' => $object->id]);
312-
$this->assertEquals(tag_manager::SYNC_STATUS_SYNC_NOT_REQUIRED, $status);
274+
public function test_get_tag_summary_html() {
275+
// Quick test just to ensure it generates and nothing explodes.
276+
$html = tag_manager::get_tag_summary_html();
277+
$this->assertIsString($html);
313278
}
314279
}

tests/object_file_system_test.php

+2
Original file line numberDiff line numberDiff line change
@@ -1016,4 +1016,6 @@ public function test_add_file_from_string_update_object_fail() {
10161016
$this->assertEquals(\core_text::strlen($content), $result[1]);
10171017
$this->assertTrue($result[2]);
10181018
}
1019+
1020+
// TODO test tag syncing somehow ? Using a test FS ? Maybe test FS can store them in memory ?
10191021
}

0 commit comments

Comments
 (0)