Skip to content

Commit

Permalink
Merge pull request #3 from Financial-Times/addConfigurableHealthChecks
Browse files Browse the repository at this point in the history
Add configurable healthchecks
  • Loading branch information
ryanolee authored Dec 2, 2019
2 parents 50b97ec + 68a0f15 commit 118c23e
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 43 deletions.
33 changes: 33 additions & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,36 @@ parameters:

## Useful links
- [FT health check formatter for viewing healthchecks in chrome ](https://github.com/Financial-Times/health-status-formatter)

## Configurable health checks
Configurable health checks can be used in place of regular health checks. These healthchecks can be modified through parameter values loaded during a cache clear.
To make a healthcheck configurable define the health check as you normally would but use the `health_check.configurable` tag instead.

```yml
services:
# Health Checks
app.placeholder.health_check:
class: YourBundle\HealthCheck\PlaceholderHealthCheck
tags: [{ name: health_check.configurable, priority: 20 }]
```

Configurable health checks can have various parts of themselves overridden by using parameters. Parameters are given in the form `${serviceId}.${config}`.
For instance:
```yml
parameters:
# Override the name of the healthcheck
app.placeholder.health_check.name: "A new name"
# Override the healthcheck severity
app.placeholder.health_check.severity: 2
```
Would configure the `app.placeholder.health_check` to have a new name and priority.

| Config attribute | Description | Default (If applicable) |
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
| priority | The priority of the healthcheck (in the order in which they run) | N/A |
| run | If the health check should be run or not. In the event false is given the service definition for the health check is removed and the health check is not run. | true |
| name | Gives the option to override the health check name (Equivalent to calling `withName` on health check). | N/A |
| severity | Gives the option to override the health check severity (Equivalent of calling `withSeverity` on health check). | N/A |
| business_impact | Gives the option to override the health check business impact entry (Equivalent of calling `withBusinessImpact` on health check). | N/A |
| panic_guide | Gives the option to override the health check panic guide (Equivalent of calling `withPanicGuide` on health check). | N/A |
| technical_summary | Gives the option to override the health check technical summary (Equivalent of calling `withTechnicalSummary` on health check). | N/A |
2 changes: 1 addition & 1 deletion src/HealthCheckBundle/Controller/HealthCheckController.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function healthCheckAction()
->withBusinessImpact("A healthcheck failed to run. It is unknown what effects this would have for users or the editorial team.")
->withPanicGuide("Read the output of the check to find where the fatal error was thrown. Note that this healthcheck failing might be a symptom of a larger problem and more serious health check failures should be looked into first.")
->withTechnicalSummary("This is a placeholder for the ". get_class($healthCheckHandle) ." heath check that failed to run successfully.")
->withCheckOutput(get_class($e).": {$e->getMessage()} in {$e->getFile()}:{$e->getLine()} ");
->withCheckOutputException($e);
}
return $healthCheck;
}, $this->healthCheckHandles);
Expand Down
47 changes: 44 additions & 3 deletions src/HealthCheckBundle/DependencyInjection/HealthCheckPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace FT\HealthCheckBundle\DependencyInjection;

use FT\HealthCheckBundle\EventListener\HealthCheckListener;
use FT\HealthCheckBundle\HealthCheck\ConfigurableHealthCheckHandler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
Expand All @@ -16,20 +17,60 @@ public function process(ContainerBuilder $container)
return;
}
$healthCheckController = $container->getDefinition('health_check.controller');

// Get health checks.
$healthChecks = $container->findTaggedServiceIds('health_check');

// Get Configurable healthchecks
$configurableHealthChecks = $container->findTaggedServiceIds('health_check.configurable');

if(empty($healthChecks)){
// Terminate compiler pass if there are no healthchecks to register
if(empty($healthChecks) && empty($configurableHealthChecks)){
return;
}

$converterIdsByPriority = array();
$converterIdsByPriority = [];

foreach ($configurableHealthChecks as $id => $tags) {
if($container->hasParameter($id.'.run') && $container->getParameter($id.'.run') === false ){
//In the event a health check should not be run entirely remove it's service definition from the container and don't register it
$container->removeDefinition($id);
continue;
}

//Create and inject a decorated service definition each definition tagged with a configurable health check
$container
->register($id.'.decorated', ConfigurableHealthCheckHandler::class)
->addArgument(new Reference('service_container'))
->addArgument(new Reference($id.".decorated.inner"))
->addArgument($id)
->setDecoratedService($id);

//Try to pull service priority from a user set priority
$priority = $container->hasParameter($id.'.priority') ? $container->getParameter($id.'.priority') : null;

//In the event that that fails or the priority is invalid use the original services priority
if (!is_int($priority)) {
foreach ($tags as $tag) {
$priority = isset($tag['priority']) ? (int) $tag['priority'] : 0;
}
}

$converterIdsByPriority[$priority][] = $id;
}

foreach ($healthChecks as $id => $tags) {
foreach ($tags as $tag) {
$priority = isset($tag['priority']) ? (int) $tag['priority'] : 0;
$converterIdsByPriority[$priority][] = $id;
}
}

if(empty($converterIdsByPriority)){
//Abort in the case that we have no health checks
return;
}

$converterIdsByPriority = $this->sortConverterIds($converterIdsByPriority);
foreach ($converterIdsByPriority as $referenceId) {
$healthCheckController->addMethodCall('addHealthCheck', array(new Reference($referenceId)));
Expand All @@ -42,7 +83,7 @@ public function process(ContainerBuilder $container)
*
* @param array $converterIdsByPriority
*
* @return \Symfony\Component\DependencyInjection\Reference[]
* @return string[]
*/
protected function sortConverterIds(array $converterIdsByPriority)
{
Expand Down
113 changes: 113 additions & 0 deletions src/HealthCheckBundle/HealthCheck/ConfigurableHealthCheckHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

namespace FT\HealthCheckBundle\HealthCheck;

use Symfony\Component\DependencyInjection\Container;

/**
* This class is used to wrap health check handles that expose themeselves as being be configurable.
*/
class ConfigurableHealthCheckHandler implements HealthCheckHandlerInterface
{
const CONFIG_OVERRIDE_MAPPINGS = [
'name' => 'withName',
'severity' => 'withSeverity',
'business_impact' => 'withBusinessImpact',
'panic_guide' => 'withPanicGuide',
'technical_summary' => 'withTechnicalSummary',
];

/**
* @var HealthCheckHandlerInterface
*/
protected $healthCheckHandle;

/**
* @var Container
*/
protected $container;

/**
* @var string
*/
private $serviceId;

/**
* @param Container $container
* @param HealthCheckHandlerInterface $healthCheckHandle
* @param string $serviceId
*/
public function __construct(Container $container, HealthCheckHandlerInterface $healthCheckHandle, string $serviceId)
{
$this->container = $container;
$this->healthCheckHandle = $healthCheckHandle;
$this->serviceId = $serviceId;
}

/**
* Decorates the {@see \FT\HealthCheckBundle\HealthCheck\HealthCheck::runHealthCheck()} method by accepting parameters to override health check values.
*
* @return HealthCheck
*/
public function runHealthCheck(): HealthCheck
{
$healthCheck = $this->healthCheckHandle->runHealthCheck();

//Override health check parts that can be overidden with there given props
foreach (self::CONFIG_OVERRIDE_MAPPINGS as $parameterName => $methodName) {
if ($this->hasParameter($parameterName)) {
$healthCheck->{$methodName}($this->getParameter($parameterName));
}
}

return $healthCheck;
}

/**
* Proxies the internal instance of the healthCheckHandle checking for if configuration overrides the interval set in the healthcheck.
*
* @return int|null
*/
public function getHealthCheckInterval(): ?int
{
$interval = $this->hasParameter('interval') ?
$this->getParameter('interval') :
false;

return (\is_int($interval) || null === $interval) ?
$interval :
$this->healthCheckHandle->getHealthCheckInterval();
}

/**
* Retrieves the health check from config if possible, returning the id from the internal healthCheckHandle instance if not.
*
* @return string
*/
public function getHealthCheckId(): string
{
return $this->hasParameter('id') ?
$this->getParameter('interval') :
$this->healthCheckHandle->getHealthCheckId();
}

/**
* Gets if parameter is defined in the context of healthcheck override.
*
* @param string $paramName
* @return bool
*/
protected function hasParameter(string $paramName): bool
{
return $this->container->hasParameter($this->serviceId. '.' . $paramName);
}

/**
* @param string $paramName
* @return string
*/
protected function getParameter(string $paramName): string
{
return $this->container->getParameter($this->serviceId . '.' . $paramName);
}
}
Loading

0 comments on commit 118c23e

Please sign in to comment.