Skip to content

Commit cabb72a

Browse files
committed
tests and docs
1 parent 1b781ac commit cabb72a

File tree

6 files changed

+190
-27
lines changed

6 files changed

+190
-27
lines changed

TAGGING.md

+59-22
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,76 @@
11
# Tagging
2+
Tagging allows extra metadata about your files to be send to the external object store. These sources are defined in code, and currently cannot be configured on/off from the UI.
23

3-
todo blurb about tagging
4+
Currently, this is only implemented for the S3 file system client. [See the S3 docs for more information](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-tagging.html).
45

5-
You probably don't need this unless you have a lot of objects!
6+
**Tagging vs metadata**
7+
Note object tags are different from object metadata.
68

7-
Support level: currenly only implemented for AWS/S3
9+
Object metadata is immutable, and attached to the object on upload. With metadata, if you wish to update it (for example during a migration, or the sources changed), you have to copy the object with the new metadata, and delete the old object. This is problematic, since deletion is optional in objectfs.
810

9-
Note extra cost, extra api calls + tagging cost per object per month
11+
Object tags are more suitable, since their permissions can be managed separately (e.g. a client can be allowed to modify tags, but not delete objects).
1012

1113
## Sources
14+
The following sources are implemented currently:
15+
### Environment
16+
What environment the file was uploaded in. Configure the environment using `$CFG->objectfs_environment_name`
1217

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
18+
### Mimetype
19+
What mimetype the file is stored as under the `mdl_files` table.
1720

18-
Implemented:
19-
- Mimetype
20-
- Env - defined by <insert cfg here>
21+
## Multi environment bucket
22+
It is possible you are using objectfs with multiple environments (e.g. prod, staging) that both point to the same bucket. Since files are referenced by contenthash, it generally does not matter where they come from s
23+
If this is the case, for tagging to work as expected, you must ensure object override is turned off for every environment except production.
2124

22-
## Multi env
23-
- Turn off object override on every env except prod
24-
- TODO diagram here
25+
This means that staging is unable to overwrite objects (thus overwriting tags) for files uploaded elsewhere. However, files uploaded elsewhere can be overwitten (hence overwriting tags) by production. This allows
2526

26-
## Reporting
27-
TODO blurb about reporting
28-
Note reporting quirk because of multi env setup and what each env knows about.
27+
See this diagram for a visual explanation:
2928

30-
## Migration
29+
```mermaid
30+
graph LR
31+
subgraph S3
32+
Object("`**Object**
33+
contenthash: xyz
34+
tags: env=prod`")
35+
end
36+
subgraph Prod
37+
UploadObjectProd["`**Upload object**
38+
contenthash: xyz
39+
tags: env=prod`"] --> Object
40+
end
41+
subgraph Staging
42+
UploadObjectStaging["`**Upload object**
43+
contenthash: xyz
44+
tags: env=staging`"]
45+
end
46+
Blocked["Blocked - does not have permissions\nto overwrite existing objects"]
47+
UploadObjectStaging --- Blocked
48+
Blocked -.-> Object
49+
50+
style Object fill:#ffcd75,stroke:#ffa812
51+
style S3 fill:#ffd68f,stroke:#ffa812
52+
style Prod fill:#c2ffcc,stroke:#26ff4a
53+
style UploadObjectProd fill:#c2ffcc,stroke:#26ff4a
54+
style Staging fill:#ddd9ff,stroke:#978aff
55+
style UploadObjectStaging fill:#ddd9ff,stroke:#978aff
56+
style Blocked fill:#ff6161,stroke:#ff0000
57+
```
3158

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.
59+
## Reporting
60+
// TODO.
3461

62+
## Migration
63+
If the way a tag was calculated has changed, or new tags are added (or removed) or this feature was turned on for the first time (or turned on after being off), you must do the following:
64+
- Manually run `trigger_update_object_tags` scheduled task from the UI, or call the CLI to queue a `update_object_tags` adhoc task.
3565
## For developers
3666

3767
### Adding a new source
38-
- Add to <insert place in tag manager here>
39-
- Run migration steps (TODO link to above)
68+
Note the rules about sources:
69+
- Identifier must be < 128 chars long.
70+
- Value must be < 128 chars long. Note we intentionally reserve 128 chars of extra space for later use.
71+
72+
To add a new source:
73+
- Implement `tag_source`
74+
- Add to the `tag_manager` class
75+
- As part of an upgrade step, mark all objects as needing tag sync (using `tag_manager` class)
76+
- As part of an upgrade step, queue a `update_object_tags` adhoc task to process the tag migration.

classes/local/manager.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,10 @@ public static function get_filename_from_header($header) {
389389
* @return bool
390390
*/
391391
public static function can_override_existing_objects(): bool {
392+
// This is either "1" or "0"
392393
$config = get_config('tool_objectfs', 'canoverrideobjects');
393394

394-
// Default true for backwards compatibility.
395-
return empty($config) ? true : $config;
395+
// Default to true if not set.
396+
return is_string($config) ? (bool) $config : true;
396397
}
397398
}

classes/local/store/object_file_system.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -1165,12 +1165,12 @@ private function update_object(array $result): array {
11651165
* @param string $contenthash file to sync tags for
11661166
*/
11671167
public function sync_object_tags(string $contenthash) {
1168-
if (!$this->get_external_client()->is_tagging_supported()) {
1168+
if (!$this->get_external_client()->supports_object_tagging()) {
11691169
throw new coding_exception("Cannot sync tags, external client does not support tagging.");
11701170
}
11711171

11721172
// Get a lock before syncing, to ensure other parts of objectfs are not moving/interacting with this object.
1173-
$lock = $this->acquire_object_lock($contenthash, 5);
1173+
$lock = $this->acquire_object_lock($contenthash, 10);
11741174

11751175
// No lock - just skip it.
11761176
if (!$lock) {

classes/tests/test_client.php

+19
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class test_client extends object_client_base {
3434
*/
3535
private $bucketpath;
3636

37+
/**
38+
* @var array in-memory tags used for unit tests
39+
*/
40+
public $tags;
41+
3742
/**
3843
* string
3944
* @param \stdClass $config
@@ -158,6 +163,20 @@ public function get_maximum_upload_size() {
158163
return $this->maxupload;
159164
}
160165

166+
/**
167+
* Sets object tags - uses in-memory store for unit tests
168+
*/
169+
public function set_object_tags(string $contenthash, array $tags) {
170+
$this->tags[$contenthash] = $tags;
171+
}
172+
173+
/**
174+
* Gets object tags - uses in-memory store for unit tests
175+
*/
176+
public function get_object_tags(string $contenthash): array {
177+
return $this->tags[$contenthash] ?? [];
178+
}
179+
161180
/**
162181
* Object tagging support, for unit testing
163182
* @return bool

classes/tests/testcase.php

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
* @package tool_objectfs
3131
*/
3232
abstract class testcase extends \advanced_testcase {
33+
/**
34+
* @var test_file_system test file system
35+
*/
36+
protected $filesystem;
3337

3438
/**
3539
* setUp
@@ -42,6 +46,7 @@ protected function setUp(): void {
4246
$CFG->objectfs_environment_name = 'test';
4347
$this->filesystem = new test_file_system();
4448
$this->logger = new \tool_objectfs\log\null_logger();
49+
4550
$this->resetAfterTest(true);
4651
}
4752

tests/object_file_system_test.php

+102-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
namespace tool_objectfs;
1818

19+
use coding_exception;
1920
use tool_objectfs\local\store\object_file_system;
2021
use tool_objectfs\local\manager;
22+
use tool_objectfs\local\tag\tag_manager;
2123
use tool_objectfs\tests\test_file_system;
2224

2325
/**
@@ -1017,5 +1019,104 @@ public function test_add_file_from_string_update_object_fail() {
10171019
$this->assertTrue($result[2]);
10181020
}
10191021

1020-
// TODO test tag syncing somehow ? Using a test FS ? Maybe test FS can store them in memory ?
1022+
/**
1023+
* Test syncing tags throws exception when client does not support tagging.
1024+
*/
1025+
public function test_sync_object_tags_not_supported() {
1026+
global $CFG;
1027+
$CFG->phpunit_objectfs_supports_object_tagging = false;
1028+
$this->expectException(coding_exception::class);
1029+
$this->expectExceptionMessage('Cannot sync tags, external client does not support tagging');
1030+
$this->filesystem->sync_object_tags('123');
1031+
}
1032+
1033+
/**
1034+
* Tests syncing object tags where the file is not replicated.
1035+
*/
1036+
public function test_sync_object_tags_object_not_replicated() {
1037+
global $CFG, $DB;
1038+
$CFG->phpunit_objectfs_supports_object_tagging = true;
1039+
1040+
// Create object - not replicated to 'external' store yet.
1041+
$object = $this->create_local_object('test syncing local');
1042+
1043+
// Sync, this should do nothing but change sync status - cannot sync object tags
1044+
// where the object is not replicated.
1045+
$this->filesystem->sync_object_tags($object->contenthash);
1046+
$object = $DB->get_record('tool_objectfs_objects', ['contenthash' => $object->contenthash]);
1047+
$this->assertEquals($object->tagsyncstatus, tag_manager::SYNC_STATUS_SYNC_NOT_REQUIRED);
1048+
}
1049+
1050+
/**
1051+
* Provides values to sync_object_tags_replicated
1052+
* @return array
1053+
*/
1054+
public static function sync_object_tags_replicated_provider(): array {
1055+
return [
1056+
'can override' => [
1057+
'can override' => true,
1058+
],
1059+
'cannot override' => [
1060+
'cannot override' => false,
1061+
],
1062+
];
1063+
}
1064+
1065+
/**
1066+
* Tests sync_object_tags when the object is replicated.
1067+
* @dataProvider sync_object_tags_replicated_provider
1068+
*/
1069+
public function test_sync_object_tags_replicated(bool $canoverride) {
1070+
global $CFG, $DB;
1071+
$CFG->phpunit_objectfs_supports_object_tagging = true;
1072+
1073+
set_config('canoverrideobjects', $canoverride, 'tool_objectfs');
1074+
$this->assertEquals($canoverride, manager::can_override_existing_objects());
1075+
1076+
$object = $this->create_duplicated_object('test syncing replicated');
1077+
1078+
$testtags = [
1079+
'test' => 123,
1080+
'test2' => 123,
1081+
'test3' => 123,
1082+
'test4' => 123,
1083+
];
1084+
1085+
// Fake set the tags in the external store.
1086+
$this->filesystem->get_external_client()->tags[$object->contenthash] = $testtags;
1087+
1088+
// Ensure tags are set 'externally'.
1089+
$tags = $this->filesystem->get_external_client()->get_object_tags($object->contenthash);
1090+
$this->assertCount(count($testtags), $tags);
1091+
1092+
// But tags will not be stored locally (yet).
1093+
$localtags = $DB->get_records('tool_objectfs_object_tags', ['contenthash' => $object->contenthash]);
1094+
$this->assertCount(0, $localtags);
1095+
1096+
// Sync the file.
1097+
$this->filesystem->sync_object_tags($object->contenthash);
1098+
1099+
// Tags should now be replicated locally.
1100+
$localtags = $DB->get_records('tool_objectfs_object_tags', ['contenthash' => $object->contenthash]);
1101+
$externaltags = $this->filesystem->get_external_client()->get_object_tags($object->contenthash);
1102+
1103+
if ($canoverride) {
1104+
// If can override, we expect it to be overwritten by the tags defined in the sources.
1105+
$expectednum = count(tag_manager::get_defined_tag_sources());
1106+
$this->assertCount($expectednum, $localtags);
1107+
1108+
// Also expect the external store to be updated.
1109+
$this->assertCount($expectednum, $externaltags);
1110+
} else {
1111+
// If cannot override, we expect the tags to match what was in the 'external' store.
1112+
$this->assertCount(count($testtags), $localtags);
1113+
1114+
// External store should not be updated.
1115+
$this->assertCount(count($testtags), $externaltags);
1116+
}
1117+
1118+
// Ensure status changed to not needing sync.
1119+
$object = $DB->get_record('tool_objectfs_objects', ['contenthash' => $object->contenthash]);
1120+
$this->assertEquals($object->tagsyncstatus, tag_manager::SYNC_STATUS_SYNC_NOT_REQUIRED);
1121+
}
10211122
}

0 commit comments

Comments
 (0)