Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP TASK: Further improvements for the 1and1 project #33

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b77c2b7
BUGFIX: Show module icon in module list
Sebobo Jan 19, 2023
fb3ee84
BUGFIX: Prevent tailwind from breaking neos backend styles
Sebobo Jan 19, 2023
5e53d53
BUGFIX: Prevent metadata from loosing its workspacename
Sebobo Jan 20, 2023
6b25d92
FEATURE: Set build lock by workspace to allow parallel releases
Sebobo Jan 20, 2023
8b5f110
TASK: Show rendering error details in render_orchestrator output
Sebobo Jan 20, 2023
d39ef93
BUGFIX: Flush content cache when the pipeline actually starts
Sebobo Feb 1, 2023
28f7d2f
FEATURE: Allow recursing through hidden documents
Sebobo Feb 14, 2023
efc5811
add configuration to enable/disable auto-start of incremental releases
erickloss Feb 22, 2023
eb2bca8
BUGFIX: Use workspace name from enumerated node during rendering
Sebobo Apr 19, 2023
b0aabd7
BUGFIX: Enable storing rendered documents in non public workspaces
Sebobo Apr 19, 2023
a0de60d
BUGFIX: Prevent exception in overview when ContentReleaseMetadata is …
Sebobo May 2, 2023
477108a
TASK: Add rendertime to render orchestrator output
Sebobo May 16, 2023
4876ca0
BUGFIX: Prevent error when exception has no referenceCode
Sebobo Jul 10, 2023
f3f7110
BUGFIX: Prevent creating a second content release with the same id
Sebobo Jul 19, 2023
7ce1709
FEATURE: Store rendertime of documents in metadata
Sebobo Oct 9, 2023
8bafbae
BUGFIX: Prevent error when viewing content releases with no Redis met…
Sebobo Nov 9, 2023
a15d22e
FEATURE: Log enumeration duration for each site
Sebobo Nov 14, 2023
8e20be9
BUGFIX: Speed up enumeration by fetching all documents with one query
Sebobo Nov 14, 2023
9e988aa
TASK: Cache redis instance for repeated calls in CacheReader
Sebobo Nov 15, 2023
3816e27
FEATURE: Allow enumeration of any nodetype
Sebobo Nov 15, 2023
a59129f
TASK: Optimise redis content cache reader
Sebobo Nov 16, 2023
b07abbc
TASK: Cleanup enumeration code
Sebobo Nov 22, 2023
ba2248f
TASK: Reformat NodeEnumerator code
Sebobo Nov 28, 2023
74157bf
BUGFIX: Add html escaping to stderr and stdout of job logs
mhsdesign Dec 7, 2023
a737e87
Merge pull request #35 from Flowpack/bugfix/error-logs-html-escaping
Sebobo Dec 7, 2023
ce64170
FEATURE: Make iteration checks in NodeRenderer configurable
Sebobo Dec 11, 2023
cb4128d
Merge branch 'task/adjustments-for-1and1' of https://github.com/Flowp…
Sebobo Dec 11, 2023
dbb3705
TASK: Append payload only in logging if set
Sebobo Dec 12, 2023
9e887c9
TASK: Add newlines to log output
Sebobo Dec 12, 2023
a639800
BUGFIX: Remove pretty formatting for payloads again
Sebobo Dec 12, 2023
f68938e
BUGFIX: Make NodeRenderer constants overridable
Sebobo Dec 12, 2023
53db941
TASK: Forgiving data handling in ui service
mhsdesign Dec 20, 2023
aab5a6d
BUGFIX: Joblogs preserve whitespace and highlight errors
mhsdesign Jan 17, 2024
11a2a2b
TASK: Reverse stdout log and improve styling
Sebobo Jan 17, 2024
71a4ac5
Merge pull request #36 from Flowpack/bugfix/joblogs-preserve-whitespa…
Sebobo Jan 17, 2024
d62e013
FEATURE: Format stdout with eel helper
Sebobo Apr 15, 2024
7d576fe
BUGFIX: Handle empty stdout when formatting logs
Sebobo Apr 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions Classes/Aspects/CacheUrlMappingAspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class CacheUrlMappingAspect
*/
protected $isActive = false;

/**
* @var null | int
*/
protected $renderTimestamp = null;

/**
* @var array
*/
Expand Down Expand Up @@ -102,9 +107,6 @@ public function storeRootCacheIdentifier(JoinPointInterface $joinPoint)

/** @var NodeInterface $node */
$node = $this->currentEvaluateContext['cacheIdentifierValues']['node'];
if (!$node->getContext()->getWorkspace()->isPublicWorkspace()) {
return;
}

$url = $this->getCurrentUrl();

Expand Down Expand Up @@ -144,8 +146,10 @@ public function storeRootCacheIdentifier(JoinPointInterface $joinPoint)
$logger->debug('Mapping URL ' . $url . ' to ' . $rootIdentifier . ' with tags ' . implode(', ', $rootTags));

$arguments = $this->getCurrentArguments($node);
// TODO: To make parallel rendering possible, we need to make sure that the cache key also includes the currently rendered workspace, as the node might originate from a base workspace (usually live). See `DocumentNodeCacheKey`.
$rootKey = DocumentNodeCacheKey::fromNodeAndArguments($node, $arguments);
$rootCacheValues = DocumentNodeCacheValues::create($rootIdentifier, $url);
$rootCacheValues = DocumentNodeCacheValues::create($rootIdentifier, $url)
->withMetadata('renderTime', (int)(microtime(true) * 1000) - $this->renderTimestamp);
// allow other document metadata generators here
$rootCacheValues = $this->nodeRenderingExtensionManager->runDocumentMetadataGenerators($node, $arguments, $this->controllerContext, $rootCacheValues);
$this->contentCacheFrontend->set($rootKey->redisKeyName(), json_encode($rootCacheValues), $rootTags);
Expand Down Expand Up @@ -202,6 +206,7 @@ public function beforeDocumentRendering(ContentReleaseLogger $contentReleaseLogg
{
$this->isActive = true;
$this->contentReleaseLogger = $contentReleaseLogger;
$this->renderTimestamp = (int)(microtime(true) * 1000);
}

public function afterDocumentRendering(): void
Expand Down
21 changes: 16 additions & 5 deletions Classes/BackendUi/BackendUiDataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Flowpack\DecoupledContentStore\NodeRendering\Dto\RenderingStatistics;
use Flowpack\DecoupledContentStore\NodeRendering\Infrastructure\RedisRenderingErrorManager;
use Flowpack\DecoupledContentStore\NodeRendering\Infrastructure\RedisRenderingStatisticsStore;
use Flowpack\DecoupledContentStore\PrepareContentRelease\Dto\ContentReleaseMetadata;
use Flowpack\DecoupledContentStore\PrepareContentRelease\Infrastructure\RedisContentReleaseService;
use Flowpack\DecoupledContentStore\ReleaseSwitch\Infrastructure\RedisReleaseSwitchService;
use Neos\Flow\Annotations as Flow;
Expand Down Expand Up @@ -81,12 +82,17 @@ public function loadBackendOverviewData(RedisInstanceIdentifier $redisInstanceId
$lastRendering = RenderingStatistics::fromJsonString($lastRenderingStatisticsEntries->getResultForContentRelease($contentReleaseId));
$firstRendering = RenderingStatistics::fromJsonString($firstRenderingStatisticsEntries->getResultForContentRelease($contentReleaseId));

$metadataForContentRelease = $metadata->getResultForContentRelease($contentReleaseId);
$countForContentRelease = $counts->getResultForContentRelease($contentReleaseId);
$iterationsCountForContentRelease = $iterationsCounts->getResultForContentRelease($contentReleaseId);
$errorCountForContentRelease = $errorCounts->getResultForContentRelease($contentReleaseId);

$result[] = new ContentReleaseOverviewRow(
$contentReleaseId,
$metadata->getResultForContentRelease($contentReleaseId),
$counts->getResultForContentRelease($contentReleaseId),
$iterationsCounts->getResultForContentRelease($contentReleaseId),
$errorCounts->getResultForContentRelease($contentReleaseId),
$metadataForContentRelease instanceof ContentReleaseMetadata ? $metadataForContentRelease : null,
is_int($countForContentRelease) ? $countForContentRelease : 0,
is_int($iterationsCountForContentRelease) ? $iterationsCountForContentRelease : 0,
is_int($errorCountForContentRelease) ? $errorCountForContentRelease : 0,
$lastRendering->getTotalJobs() > 0 ? round($lastRendering->getRenderedJobs()
/ $lastRendering->getTotalJobs() * 100) : 100,
$firstRendering->getRenderedJobs(),
Expand Down Expand Up @@ -114,9 +120,14 @@ private function calculateReleaseSize(RedisInstanceIdentifier $redisInstanceIden
return round($size / 1000000, 2);
}

public function loadDetailsData(ContentReleaseIdentifier $contentReleaseIdentifier, RedisInstanceIdentifier $redisInstanceIdentifier): ContentReleaseDetails
public function loadDetailsData(ContentReleaseIdentifier $contentReleaseIdentifier, RedisInstanceIdentifier $redisInstanceIdentifier): ?ContentReleaseDetails
{
$contentReleaseMetadata = $this->redisContentReleaseService->fetchMetadataForContentRelease($contentReleaseIdentifier, $redisInstanceIdentifier);

if (!$contentReleaseMetadata) {
return null;
}

$contentReleaseJob = $this->prunnerApiService->loadJobDetail($contentReleaseMetadata->getPrunnerJobId()->toJobId());

$manualTransferJobs = count($contentReleaseMetadata->getManualTransferJobIds()) ? array_map(function (PrunnerJobId $item) {
Expand Down
33 changes: 3 additions & 30 deletions Classes/BackendUi/Dto/ContentReleaseOverviewRow.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
class ContentReleaseOverviewRow
{
private ContentReleaseIdentifier $contentReleaseIdentifier;
private ContentReleaseMetadata $metadata;
private ?ContentReleaseMetadata $metadata;
private int $enumeratedDocumentNodesCount;
private int $iterationsCount;
private int $errorCount;
Expand All @@ -23,7 +23,7 @@ class ContentReleaseOverviewRow
private bool $isActive;
private float $releaseSize;

public function __construct(ContentReleaseIdentifier $contentReleaseIdentifier, ContentReleaseMetadata $metadata,
public function __construct(ContentReleaseIdentifier $contentReleaseIdentifier, ?ContentReleaseMetadata $metadata,
int $enumeratedDocumentNodesCount, int $iterationsCount, int $errorCount,
float $progress, int $renderedUrlCount, bool $isActive, float $releaseSize)
{
Expand All @@ -38,73 +38,46 @@ public function __construct(ContentReleaseIdentifier $contentReleaseIdentifier,
$this->releaseSize = $releaseSize;
}

/**
* @return ContentReleaseMetadata
*/
public function getMetadata(): ContentReleaseMetadata
public function getMetadata(): ?ContentReleaseMetadata
{
return $this->metadata;
}

/**
* @return ContentReleaseIdentifier
*/
public function getContentReleaseIdentifier(): ContentReleaseIdentifier
{
return $this->contentReleaseIdentifier;
}

/**
* @return int
*/
public function getEnumeratedDocumentNodesCount(): int
{
return $this->enumeratedDocumentNodesCount;
}

/**
* @return int
*/
public function getIterationsCount(): int
{
return $this->iterationsCount;
}

/**
* @return int
*/
public function getErrorCount(): int
{
return $this->errorCount;
}

/**
* @return float
*/
public function getProgress(): float
{
return $this->progress;
}

/**
* @return int
*/
public function getRenderedUrlCount(): int
{
return $this->renderedUrlCount;
}

/**
* @return bool
*/
public function isActive(): bool
{
return $this->isActive;
}

/**
* @return float
*/
public function getReleaseSize(): float
{
return $this->releaseSize;
Expand Down
18 changes: 18 additions & 0 deletions Classes/Command/ContentReleasePrepareCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Flowpack\DecoupledContentStore\Core\Domain\ValueObject\ContentReleaseIdentifier;
use Flowpack\DecoupledContentStore\Core\Infrastructure\ContentReleaseLogger;
use Neos\Flow\Cli\CommandController;
use Neos\Fusion\Core\Cache\ContentCache;

/**
* Commands for the PREPARE stage in the pipeline. Not meant to be called manually.
Expand All @@ -28,6 +29,12 @@ class ContentReleasePrepareCommandController extends CommandController
*/
protected $concurrentBuildLock;

/**
* @Flow\Inject
* @var ContentCache
*/
protected $contentCache;

public function createContentReleaseCommand(string $contentReleaseIdentifier, string $prunnerJobId, string $workspaceName = 'live'): void
{
$contentReleaseIdentifier = ContentReleaseIdentifier::fromString($contentReleaseIdentifier);
Expand All @@ -52,4 +59,15 @@ public function registerManualTransferJobCommand(string $contentReleaseIdentifie

$this->redisContentReleaseService->registerManualTransferJob($contentReleaseIdentifier, $prunnerJobId, $logger);
}

public function flushContentCacheIfRequiredCommand(string $contentReleaseIdentifier, bool $flushContentCache = false): void
{
$logger = ContentReleaseLogger::fromConsoleOutput($this->output, ContentReleaseIdentifier::fromString($contentReleaseIdentifier));
if (!$flushContentCache) {
$logger->info('Not flushing content cache');
return;
}
$logger->info('Flushing content cache');
$this->contentCache->flush();
}
}
9 changes: 2 additions & 7 deletions Classes/ContentReleaseManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,12 @@
use Neos\Flow\Annotations as Flow;
use Flowpack\Prunner\PrunnerApiService;
use Flowpack\Prunner\ValueObject\PipelineName;
use Neos\Fusion\Core\Cache\ContentCache;

/**
* @Flow\Scope("singleton")
*/
class ContentReleaseManager
{
/**
* @Flow\Inject
* @var ContentCache
*/
protected $contentCache;

/**
* @Flow\Inject
Expand Down Expand Up @@ -61,6 +55,7 @@ public function startIncrementalContentRelease(string $currentContentReleaseId =
'currentContentReleaseId' => $currentContentReleaseId ?: self::NO_PREVIOUS_RELEASE,
'validate' => true,
'workspaceName' => $workspace ? $workspace->getName() : 'live',
'flushContentCache' => false,
]));
return $contentReleaseId;
}
Expand All @@ -74,12 +69,12 @@ public function startFullContentRelease(bool $validate = true, string $currentCo
}

$contentReleaseId = ContentReleaseIdentifier::create();
$this->contentCache->flush();
$this->prunnerApiService->schedulePipeline(PipelineName::create('do_content_release'), array_merge($additionalVariables, [
'contentReleaseId' => $contentReleaseId,
'currentContentReleaseId' => $currentContentReleaseId ?: self::NO_PREVIOUS_RELEASE,
'validate' => $validate,
'workspaceName' => $workspace ? $workspace->getName() : 'live',
'flushContentCache' => true,
]));
return $contentReleaseId;
}
Expand Down
1 change: 1 addition & 0 deletions Classes/Controller/BackendController.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public function detailsAction(string $contentReleaseIdentifier, ?string $content
$contentReleaseIdentifier = ContentReleaseIdentifier::fromString($contentReleaseIdentifier);
$contentStore = $contentStore ? RedisInstanceIdentifier::fromString($contentStore) : RedisInstanceIdentifier::primary();

$this->view->assign('contentReleaseIdentifier', $contentReleaseIdentifier);
$this->view->assign('contentStore', $contentStore->getIdentifier());

$detailsData = $this->backendUiDataService->loadDetailsData($contentReleaseIdentifier, $contentStore);
Expand Down
29 changes: 19 additions & 10 deletions Classes/Core/ConcurrentBuildLockService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

use Flowpack\DecoupledContentStore\Core\Domain\ValueObject\ContentReleaseIdentifier;
use Flowpack\DecoupledContentStore\Core\Infrastructure\RedisClientManager;
use Flowpack\DecoupledContentStore\PrepareContentRelease\Infrastructure\RedisContentReleaseService;
use Neos\Flow\Annotations as Flow;

/**
* We usually rely on prunner to ensure that only one build is running at any given time.
* We usually rely on prunner to ensure that only one build per workspace is running at any given time.
*
* However, when running in a cloud environment with no shared storage, the prunner data folder is not shared between
* instances. In this case, during a deployment, two containers run concurrently, with two separate prunner instances
Expand All @@ -27,35 +28,43 @@
*/
class ConcurrentBuildLockService
{
private const CONTENT_STORE_CONCURRENT_BUILD_LOCK = 'contentStore:concurrentBuildLocks';

/**
* @Flow\Inject
* @var RedisClientManager
*/
protected $redisClientManager;

public function ensureAllOtherInProgressContentReleasesWillBeTerminated(ContentReleaseIdentifier $contentReleaseIdentifier)
/**
* @Flow\Inject
* @var RedisContentReleaseService
*/
protected $redisContentReleaseService;

public function ensureAllOtherInProgressContentReleasesWillBeTerminated(ContentReleaseIdentifier $contentReleaseIdentifier): void
{
$this->redisClientManager->getPrimaryRedis()->set('contentStore:concurrentBuildLock', $contentReleaseIdentifier->getIdentifier());
$metadata = $this->redisContentReleaseService->fetchMetadataForContentRelease($contentReleaseIdentifier);
$this->redisClientManager->getPrimaryRedis()->hSet(self::CONTENT_STORE_CONCURRENT_BUILD_LOCK, $metadata->getWorkspaceName(), (string)$contentReleaseIdentifier);
}

public function assertNoOtherContentReleaseWasStarted(ContentReleaseIdentifier $contentReleaseIdentifier)
public function assertNoOtherContentReleaseWasStarted(ContentReleaseIdentifier $contentReleaseIdentifier): void
{
$concurrentBuildLockString = $this->redisClientManager->getPrimaryRedis()->get('contentStore:concurrentBuildLock');
$metadata = $this->redisContentReleaseService->fetchMetadataForContentRelease($contentReleaseIdentifier);
$concurrentBuildLockStrings = $this->redisClientManager->getPrimaryRedis()->hGetAll(self::CONTENT_STORE_CONCURRENT_BUILD_LOCK);
$concurrentBuildLockStringForWorkspace = $concurrentBuildLockStrings[$metadata->getWorkspaceName()] ?? null;

if (empty($concurrentBuildLockString)) {
if (!$concurrentBuildLockStringForWorkspace) {
echo '!!!!! Hard-aborting the current job ' . $contentReleaseIdentifier->getIdentifier() . ' because the concurrentBuildLock does not exist.' . "\n\n";
echo "This should never happen for correctly configured jobs (that run after prepare_finished).\n\n";
exit(1);
}

$concurrentBuildLock = ContentReleaseIdentifier::fromString($concurrentBuildLockString);

$concurrentBuildLock = ContentReleaseIdentifier::fromString($concurrentBuildLockStringForWorkspace);
if (!$contentReleaseIdentifier->equals($concurrentBuildLock)) {
// the concurrent build lock is different (i.e. newer) than our currently-running content release.
// Thus, we abort the in-progress content release as quickly as we can - by DYING.

echo '!!!!! Hard-aborting the current job ' . $contentReleaseIdentifier->getIdentifier() . ' because the concurrentBuildLock contains ' . $concurrentBuildLock->getIdentifier() . "\n\n";
echo '!!!!! Hard-aborting the current job ' . $contentReleaseIdentifier->getIdentifier() . ' because the concurrentBuildLock for workspace "' . $metadata->getWorkspaceName() . '" contains ' . $concurrentBuildLock->getIdentifier() . "\n\n";
echo "This is no error during deployment, but should never happen outside a deployment.\n\n It can only happen if two prunner instances run concurrently.\n\n";
exit(1);
}
Expand Down
6 changes: 3 additions & 3 deletions Classes/Core/Domain/Dto/ContentReleaseBatchResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Flowpack\DecoupledContentStore\Core\Domain\Dto;

use Flowpack\DecoupledContentStore\Core\Domain\ValueObject\ContentReleaseIdentifier;
use Flowpack\DecoupledContentStore\PrepareContentRelease\Dto\ContentReleaseMetadata;
use Neos\Flow\Annotations as Flow;

/**
Expand All @@ -20,15 +21,14 @@ private function __construct(array $results)
$this->results = $results;
}


public static function createFromArray(array $in): self
{
return new self($in);
}

public function getResultForContentRelease(ContentReleaseIdentifier $contentReleaseIdentifier)
{
return $this->results[$contentReleaseIdentifier->jsonSerialize()];
return $this->results[(string)$contentReleaseIdentifier] ?? null;
}

}
}
Loading