Skip to content

Commit 9718193

Browse files
authored
Merge pull request #10 from Flowpack/add-config-epoch
FEATURE: add config epoch to settings and redis
2 parents 66aa17e + 3f3f31f commit 9718193

6 files changed

Lines changed: 104 additions & 3 deletions

File tree

Classes/ContentReleaseManager.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Flowpack\DecoupledContentStore;
66

7+
use Flowpack\DecoupledContentStore\Core\Domain\ValueObject\RedisInstanceIdentifier;
78
use Flowpack\DecoupledContentStore\Core\Infrastructure\RedisClientManager;
89
use Neos\Flow\Annotations as Flow;
910
use Flowpack\Prunner\PrunnerApiService;
@@ -33,6 +34,12 @@ class ContentReleaseManager
3334
*/
3435
protected $redisClientManager;
3536

37+
/**
38+
* @Flow\InjectConfiguration("configEpoch")
39+
* @var array
40+
*/
41+
protected $configEpochSettings;
42+
3643
const REDIS_CURRENT_RELEASE_KEY = 'contentStore:current';
3744
const NO_PREVIOUS_RELEASE = 'NO_PREVIOUS_RELEASE';
3845

@@ -63,4 +70,18 @@ public function cancelAllRunningContentReleases()
6370
$this->prunnerApiService->cancelJob($job);
6471
}
6572
}
73+
74+
public function toggleConfigEpoch(RedisInstanceIdentifier $redisInstanceIdentifier)
75+
{
76+
$currentConfigEpochConfig = $this->configEpochSettings['current'] ?? null;
77+
$previousConfigEpochConfig = $this->configEpochSettings['previous'] ?? null;
78+
$redis = $this->redisClientManager->getRedis($redisInstanceIdentifier);
79+
$configEpochRedis = $redis->get('contentStore:configEpoch');
80+
81+
if ($configEpochRedis === $currentConfigEpochConfig) {
82+
$redis->set('contentStore:configEpoch', $previousConfigEpochConfig);
83+
} else {
84+
$redis->set('contentStore:configEpoch', $currentConfigEpochConfig);
85+
}
86+
}
6687
}

Classes/Controller/BackendController.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,31 @@ class BackendController extends \Neos\Flow\Mvc\Controller\ActionController
8282
*/
8383
protected $redisContentStores;
8484

85+
/**
86+
* @Flow\InjectConfiguration("configEpoch")
87+
* @var array
88+
*/
89+
protected $configEpochSettings;
90+
8591
protected $defaultViewObjectName = FusionView::class;
8692

8793
public function indexAction(?string $contentStore = null)
8894
{
8995
$contentStore = $contentStore ? RedisInstanceIdentifier::fromString($contentStore) : RedisInstanceIdentifier::primary();
90-
$storeSize = $this->redisClientManager->getRedis($contentStore)->info('memory')['used_memory_human'];
96+
$redis = $this->redisClientManager->getRedis($contentStore);
97+
$storeSize = $redis->info('memory')['used_memory_human'];
98+
$currentConfigEpoch = $this->configEpochSettings['current'] ?? null;
99+
$previousConfigEpoch = $this->configEpochSettings['previous'] ?? null;
100+
$configEpochRedis = $redis->get('contentStore:configEpoch');
101+
$showToggleConfigEpochButton = $previousConfigEpoch !== null && !$contentStore->isPrimary();
91102

92103
$this->view->assign('contentStore', $contentStore->getIdentifier());
93104
$this->view->assign('overviewData', $this->backendUiDataService->loadBackendOverviewData($contentStore));
94105
$this->view->assign('redisContentStores', array_keys($this->redisContentStores));
95106
$this->view->assign('storeSize', $storeSize);
107+
$this->view->assign('toggleFromConfigEpoch', $configEpochRedis);
108+
$this->view->assign('toggleToConfigEpoch', $configEpochRedis === $currentConfigEpoch ? $previousConfigEpoch : $currentConfigEpoch);
109+
$this->view->assign('showToggleConfigEpochButton', $showToggleConfigEpochButton);
96110
}
97111

98112
public function detailsAction(string $contentReleaseIdentifier, ?string $contentStore = null, ?string $detailTaskName = '', ?string $prunnerJobId = '')
@@ -183,4 +197,12 @@ public function cancelRunningReleaseAction(string $redisInstanceIdentifier)
183197

184198
$this->redirect('index', null, null, ['contentStore' => $redisInstanceIdentifier->getIdentifier()]);
185199
}
200+
201+
public function toggleConfigEpochAction(string $redisInstanceIdentifier)
202+
{
203+
$redisInstanceIdentifier = RedisInstanceIdentifier::fromString($redisInstanceIdentifier);
204+
$this->contentReleaseManager->toggleConfigEpoch($redisInstanceIdentifier);
205+
206+
$this->redirect('index', null, null, ['contentStore' => $redisInstanceIdentifier->getIdentifier()]);
207+
}
186208
}

Classes/Transfer/ContentReleaseSynchronizer.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ class ContentReleaseSynchronizer
2828
*/
2929
protected $redisKeyPostfixesForEachReleaseConfiguration;
3030

31+
/**
32+
* @Flow\InjectConfiguration("configEpoch")
33+
* @var array
34+
*/
35+
protected $configEpochSettings;
36+
3137
/**
3238
* @Flow\Inject
3339
* @var RedisKeyService
@@ -64,6 +70,7 @@ public function syncToTarget(RedisInstanceIdentifier $targetRedisIdentifier, Con
6470
}
6571

6672
$targetRedis->zAdd('contentStore:registeredReleases', 0, $contentReleaseIdentifier->getIdentifier());
73+
$targetRedis->set('contentStore:configEpoch', $this->configEpochSettings['current']);
6774
}
6875

6976
/**

Configuration/Settings.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ Flowpack:
120120
transferMode: 'dump'
121121
isRequired: true
122122

123+
# can be used on the consuming site to ensure non-breaking deployments for changes in the config
124+
configEpoch:
125+
current: v1
126+
previous: ~
123127

124128

125129
resourceSync:

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,39 @@ and what data to delete if a release is removed.
365365
// ActionController
366366
$this->addFlashMessage('sth important you have to say');
367367
```
368+
369+
### Using different sets of config
370+
371+
In some cases it might be necessary to make fundamental adjustments to some configuration properties that would be
372+
really hard to handle (safely, non-breaking) on the consuming site of the content store. Therefore we added the config
373+
property `configEpoch` that can contain a current and previous config version. The `current` value (that should be used
374+
on the consuming site) gets published to the content store.
375+
376+
We decided to save the configEpoch on content store level instead of content release level for simplicity reasons on the
377+
consuming site. If you need to switch back to an older release that was rendered with the previous config epoch version
378+
and would not match the currently published one, you may manually toggle between current and previous config epoch.
379+
There is a button for this in the backend module for each target content store. Obviously this button should be used
380+
with extra care as the config epoch needs to fit the current release at all times.
381+
382+
Example:
383+
384+
- We need to make a bigger change to the contentDimensions config, let's say we need to add uriPrefixes that weren't
385+
there before. We adjust the config accordingly and in the same deployment we configure the config epoch as follows:
386+
387+
```yml
388+
Flowpack:
389+
DecoupledContentStore:
390+
configEpoch:
391+
current: v2
392+
previous: v1
393+
```
394+
395+
- Now on the consuming site we can take action to handle both the old and new config and decide based on the value in
396+
redis which case is executed.
397+
398+
```php
399+
'contentStoreUrl' => 'https://www.vendor.de/' . ($configEpoch === 'v2' ? 'de-de/' : '')
400+
```
368401

369402
## Development
370403

Resources/Private/BackendFusion/Integration/Backend.Index.fusion

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Flowpack.DecoupledContentStore.BackendController.index = Neos.Fusion:Component {
55
// - contentStore: contains string content store identifier
66
// - redisContentStores: array of all configured content store identifiers
77
// - storeSize: string of content store size
8+
// - toggleFromConfigEpoch: string, e.g. "v2"
9+
// - toggleToConfigEpoch: string, e.g. "v1"
10+
// - showToggleConfigEpochButton: boolean
811

912
renderer = Neos.Fusion:Component {
1013
_pruneContentStoreUri = Neos.Fusion:UriBuilder {
@@ -20,6 +23,13 @@ Flowpack.DecoupledContentStore.BackendController.index = Neos.Fusion:Component {
2023
}
2124
}
2225

26+
_toggleConfigEpoch = Neos.Fusion:UriBuilder {
27+
action = 'toggleConfigEpoch'
28+
arguments = Neos.Fusion:DataStructure {
29+
redisInstanceIdentifier = ${contentStore}
30+
}
31+
}
32+
2333
_renderedTableBody = Neos.Fusion:Loop {
2434
items = ${overviewData}
2535
itemRenderer = Neos.Fusion:Component {
@@ -95,13 +105,17 @@ Flowpack.DecoupledContentStore.BackendController.index = Neos.Fusion:Component {
95105
<span>Store size: </span>
96106
<span class="neos-badge neos-badge-info">{storeSize}</span>
97107
<br />
98-
<button form="postHelper" formaction={props._pruneContentStoreUri} type="submit" class="neos-button neos-button-danger" style="margin-top: 300px;">
108+
<button form="postHelper" formaction={props._pruneContentStoreUri} type="submit" class="neos-button neos-button-warning" style="margin-top: 300px;">
99109
Prune content store
100110
</button>
101111
<span> </span>
102-
<button form="postHelper" formaction={props._cancelRunningReleaseUri} type="submit" class="neos-button neos-button-danger" style="margin-top: 300px;">
112+
<button form="postHelper" formaction={props._cancelRunningReleaseUri} type="submit" class="neos-button neos-button-warning" style="margin-top: 300px;">
103113
Cancel running release
104114
</button>
115+
<span> </span>
116+
<button @if.showToggleConfigEpochButton={showToggleConfigEpochButton} form="postHelper" formaction={props._toggleConfigEpoch} type="submit" class="neos-button neos-button-danger" style="margin-top: 300px;">
117+
{'Toggle config epoch: ' + toggleFromConfigEpoch + ' to ' + toggleToConfigEpoch}
118+
</button>
105119

106120
<div class="neos-footer !h-full">
107121
<Flowpack.DecoupledContentStore:ListFooter />

0 commit comments

Comments
 (0)