Skip to content

Commit

Permalink
Rocket health sensors (#1546)
Browse files Browse the repository at this point in the history
* Add rocket function app names to main function env vars

* Add sensors for rockets

* Correct app settings for event hub function app

* Add rush change file

* Update pnpm-lock.yaml

* Update pnpm-lock.yaml

* Update docs and English grammar corrections

* Handle case for app that doesn't have any rockets

---------

Co-authored-by: Castro, Mario <[email protected]>
  • Loading branch information
MarcAstr0 and Castro, Mario authored Aug 20, 2024
1 parent 8adf2ee commit cb500c9
Show file tree
Hide file tree
Showing 21 changed files with 325 additions and 135 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@boostercloud/framework-core",
"comment": "Health sensors for Rockets",
"type": "minor"
}
],
"packageName": "@boostercloud/framework-core"
}
175 changes: 104 additions & 71 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
HealthIndicatorsResult,
UserEnvelope,
} from '@boostercloud/framework-types'
import { childrenHealthProviders, isEnabled, metadataFromId, rootHealthProviders } from './health-utils'
import { childHealthProviders, isEnabled, metadataFromId, rootHealthProviders } from './health-utils'
import { createInstance } from '@boostercloud/framework-common-helpers'
import { defaultBoosterHealthIndicators } from './health-indicators'
import { BoosterTokenVerifier } from '../../booster-token-verifier'
Expand Down Expand Up @@ -51,14 +51,14 @@ export class BoosterHealthService {
if (!indicatorResult) {
continue
}
const childrens = childrenHealthProviders(current, healthProviders)
const children = childHealthProviders(current, healthProviders)
const newResult: HealthIndicatorsResult = {
...indicatorResult,
name: current.healthIndicatorConfiguration.name,
id: current.healthIndicatorConfiguration.id,
}
if (childrens && childrens?.length > 0) {
newResult.components = await this.boosterHealthProviderResolver(childrens, healthProviders)
if (children && children?.length > 0) {
newResult.components = await this.boosterHealthProviderResolver(children, healthProviders)
}
result.push(newResult)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BoosterDatabaseHealthIndicator } from './booster-database-health-indica
import { BoosterDatabaseEventsHealthIndicator } from './booster-database-events-health-indicator'
import { BoosterFunctionHealthIndicator } from './booster-function-health-indicator'
import { BoosterDatabaseReadModelsHealthIndicator } from './booster-database-read-models-health-indicator'
import { RocketsHealthIndicator } from './rockets-health-indicator'

function buildMetadata(
config: BoosterConfig,
Expand Down Expand Up @@ -59,11 +60,18 @@ export function defaultBoosterHealthIndicators(config: BoosterConfig): Record<st
'Booster Database ReadModels',
BoosterDatabaseReadModelsHealthIndicator
)
const rocketFunctions = buildMetadata(
config,
BOOSTER_HEALTH_INDICATORS_IDS.ROCKETS,
'Rockets',
RocketsHealthIndicator
)
return {
[root.healthIndicatorConfiguration.id]: root,
[boosterFunction.healthIndicatorConfiguration.id]: boosterFunction,
[boosterDatabase.healthIndicatorConfiguration.id]: boosterDatabase,
[databaseEvents.healthIndicatorConfiguration.id]: databaseEvents,
[databaseReadModels.healthIndicatorConfiguration.id]: databaseReadModels,
[rocketFunctions.healthIndicatorConfiguration.id]: rocketFunctions,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
BOOSTER_HEALTH_INDICATORS_IDS,
BoosterConfig,
HealthIndicatorMetadata,
HealthIndicatorsResult,
HealthStatus,
} from '@boostercloud/framework-types'

export class RocketsHealthIndicator {
public async health(
config: BoosterConfig,
healthIndicatorMetadata: HealthIndicatorMetadata
): Promise<HealthIndicatorsResult> {
const results = await config.provider.sensor.areRocketFunctionsUp(config)
if (Object.keys(results).length === 0) {
return {
name: 'Rockets',
id: BOOSTER_HEALTH_INDICATORS_IDS.ROCKETS,
status: HealthStatus.UNKNOWN,
details: {
reason: 'No Rockets found',
},
}
}
return {
name: 'Rockets',
id: BOOSTER_HEALTH_INDICATORS_IDS.ROCKETS,
status: this.getOverAllHealthStatus(results),
components: Object.entries(results).map(([rocketFunctionApp, status]) => {
return {
name: rocketFunctionApp,
id: rocketFunctionApp, // @TODO: put the rocket's id instead of its name
status: status ? HealthStatus.UP : HealthStatus.DOWN,
}
}),
}
}

private getOverAllHealthStatus(results: { [key: string]: boolean }): HealthStatus {
const statusValues = Object.values(results)

if (statusValues.every((status) => status)) {
return HealthStatus.UP
}

if (statusValues.every((status) => !status)) {
return HealthStatus.DOWN
}

return HealthStatus.PARTIALLY_UP
}
}
2 changes: 1 addition & 1 deletion packages/framework-core/src/sensor/health/health-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function rootHealthProviders(
)
}

export function childrenHealthProviders(
export function childHealthProviders(
healthIndicatorMetadata: HealthIndicatorMetadata,
healthProviders: Record<string, HealthIndicatorMetadata>
): Array<HealthIndicatorMetadata> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ function defaultSensor(token?: string, url?: string) {
rawRequestToHealthEnvelope: fake(() => {
return { token: token, componentPath: url }
}),
areRocketFunctionsUp: fake(() => ''),
}
}

Expand Down Expand Up @@ -289,24 +290,12 @@ function expectDatabaseEventsWithDetails(databaseEvents: any, status: string, de
}

function expectDatabaseReadModels(databaseReadModels: any, status: string): void {
expectDefaultResult(
databaseReadModels,
status,
'booster/database/readmodels',
'Booster Database ReadModels',
0
)
expectDefaultResult(databaseReadModels, status, 'booster/database/readmodels', 'Booster Database ReadModels', 0)
expect(databaseReadModels.details).to.be.undefined
}

function expectDatabaseReadModelsWithDetails(databaseReadModels: any, status: string, details: any): void {
expectDefaultResult(
databaseReadModels,
status,
'booster/database/readmodels',
'Booster Database ReadModels',
0
)
expectDefaultResult(databaseReadModels, status, 'booster/database/readmodels', 'Booster Database ReadModels', 0)
expect(databaseReadModels.details).to.be.deep.eq(details)
}

Expand Down
12 changes: 6 additions & 6 deletions packages/framework-core/test/sensor/health/health-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HealthIndicatorMetadata } from '@boostercloud/framework-types'
import 'mocha'
import {
childrenHealthProviders,
childHealthProviders,
isEnabled,
metadataFromId,
parentId,
Expand Down Expand Up @@ -165,13 +165,13 @@ describe('Health utils', () => {
})

it('childrenHealthProviders', () => {
expect(childrenHealthProviders(root, healthProviders)).to.be.deep.equal([rootChildren1, rootChildren2])
expect(childrenHealthProviders(rootChildren1, healthProviders)).to.be.deep.equal([
expect(childHealthProviders(root, healthProviders)).to.be.deep.equal([rootChildren1, rootChildren2])
expect(childHealthProviders(rootChildren1, healthProviders)).to.be.deep.equal([
rootChildren1Children1,
rootChildren1Children2,
])
expect(childrenHealthProviders(rootChildren1Children1, healthProviders)).to.be.deep.equal([])
expect(childrenHealthProviders(rootChildren1Children2, healthProviders)).to.be.deep.equal([])
expect(childrenHealthProviders(rootChildren2, healthProviders)).to.be.deep.equal([])
expect(childHealthProviders(rootChildren1Children1, healthProviders)).to.be.deep.equal([])
expect(childHealthProviders(rootChildren1Children2, healthProviders)).to.be.deep.equal([])
expect(childHealthProviders(rootChildren2, healthProviders)).to.be.deep.equal([])
})
})
2 changes: 2 additions & 0 deletions packages/framework-provider-aws/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export const Provider = (rockets?: RocketDescriptor[]): ProviderLibrary => {
rawRequestToHealthEnvelope: (rawRequest: unknown): HealthEnvelope => {
throw new Error('Not implemented')
},
areRocketFunctionsUp: async (config: BoosterConfig): Promise<{ [key: string]: boolean }> =>
notImplementedResult(),
},
// ProviderInfrastructureGetter
infrastructure: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export class ApplicationBuilder {
const azureStack = await this.synthApplication(app, webPubSubBaseFile)
const rocketBuilder = new RocketBuilder(this.config, azureStack.applicationStack, this.rockets)
await rocketBuilder.synthRocket()
// add rocket-related env vars to main function app settings
azureStack.addAppSettingsToFunctionApp(this.rockets)
app.synth()

azureStack.applicationStack.functionDefinitions = FunctionZip.buildAzureFunctions(this.config)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
import { Construct } from 'constructs'
import { TerraformStack } from 'cdktf'
import { Fn, TerraformStack } from 'cdktf'
import { ApplicationSynth } from './synth/application-synth'
import { ApplicationSynthStack } from './types/application-synth-stack'
import { InfrastructureRocket } from './rockets/infrastructure-rocket'
import { environmentVarNames } from '@boostercloud/framework-provider-azure'

export class AzureStack extends TerraformStack {
readonly applicationStack: ApplicationSynthStack
readonly defaultApplicationSettings: { [key: string]: string }

constructor(scope: Construct, name: string, zipFile?: string) {
super(scope, name)

const applicationSynth = new ApplicationSynth(this)
this.applicationStack = applicationSynth.synth(zipFile)
this.defaultApplicationSettings = applicationSynth.buildDefaultAppSettings(
this.applicationStack,
this.applicationStack.storageAccount!,
'func'
)
}

public addAppSettingsToFunctionApp(rockets?: InfrastructureRocket[]): void {
if (!this.applicationStack.functionApp) {
throw new Error('Function app not defined')
}

const functionAppNames = rockets
? rockets
.map((rocket: InfrastructureRocket) =>
rocket.getFunctionAppName ? rocket.getFunctionAppName(this.applicationStack) : ''
)
.join(',')
: ''

this.applicationStack.functionApp.appSettings = Fn.merge([
this.defaultApplicationSettings,
{ [environmentVarNames.rocketFunctionAppNames]: functionAppNames },
])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ import { TerraformWebPubsubHub } from './terraform-web-pubsub-hub'
import { TerraformWebPubSubExtensionKey } from './terraform-web-pub-sub-extension-key'
import { TerraformEventHubNamespace } from './terraform-event-hub-namespace'
import { TerraformEventHub } from './terraform-event-hub'
import { windowsFunctionApp } from '@cdktf/provider-azurerm'
import { storageAccount, windowsFunctionApp } from '@cdktf/provider-azurerm'
import { TerraformNetworkSecurityGroup } from './gateway/terraform-network-security-group'
import { TerraformVirtualNetwork } from './gateway/terraform-virtual-network'
import { TerraformPublicIp } from './gateway/terraform-public-ip'
import { TerraformPublicIpData } from './gateway/terraform-public-ip-data'
import { TerraformSubnet } from './gateway/terraform-subnet'
import { TerraformSubnetSecurity } from './gateway/terraform-subnet-security'
import { BASIC_SERVICE_PLAN } from '../constants'
import { TerraformFunctionAppSettings } from './terraform-function-app-settings'

export class ApplicationSynth {
readonly config: BoosterConfig
Expand Down Expand Up @@ -121,7 +122,6 @@ export class ApplicationSynth {
): windowsFunctionApp.WindowsFunctionApp {
return TerraformFunctionApp.build(
stack,
this.config,
stack.applicationServicePlan!,
stack.storageAccount!,
'func',
Expand All @@ -139,11 +139,12 @@ export class ApplicationSynth {
stack.eventConsumerStorageAccount = TerraformStorageAccount.build(stack, 'sc')
stack.eventConsumerFunctionApp = TerraformFunctionApp.build(
stack,
this.config,
stack.eventConsumerServicePlan,
stack.eventConsumerStorageAccount,
'fhub',
stack.streamFunctionAppName
stack.streamFunctionAppName,
undefined,
this.buildDefaultAppSettings(stack, stack.eventConsumerStorageAccount, 'fhub')
)
if (!stack.containers) {
stack.containers = []
Expand All @@ -164,4 +165,12 @@ export class ApplicationSynth {
stack.webPubSubHub = TerraformWebPubsubHub.build(stack)
}
}

public buildDefaultAppSettings(
stack: ApplicationSynthStack,
storageAccount: storageAccount.StorageAccount,
suffixName: string
) {
return TerraformFunctionAppSettings.build(stack, this.config, storageAccount!, suffixName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { environmentVarNames } from '@boostercloud/framework-provider-azure'
import { ApplicationSynthStack } from '../types/application-synth-stack'
import { toTerraformName } from '../helper/utils'
import { BoosterConfig } from '@boostercloud/framework-types'
import { storageAccount } from '@cdktf/provider-azurerm'

export class TerraformFunctionAppSettings {
static build(
{ appPrefix, cosmosdbDatabase, domainNameLabel, eventHubNamespace, eventHub, webPubSub }: ApplicationSynthStack,
config: BoosterConfig,
storageAccount: storageAccount.StorageAccount,
suffixName: string
): { [key: string]: string } {
if (!cosmosdbDatabase) {
throw new Error('Undefined cosmosdbDatabase resource')
}
const id = toTerraformName(appPrefix, suffixName)
const eventHubConnectionString =
eventHubNamespace?.defaultPrimaryConnectionString && eventHub?.name
? `${eventHubNamespace.defaultPrimaryConnectionString};EntityPath=${eventHub.name}`
: ''
const region = (process.env['REGION'] ?? '').toLowerCase().replace(/ /g, '')
return {
WEBSITE_RUN_FROM_PACKAGE: '1',
WEBSITE_CONTENTSHARE: id,
...config.env,
WebPubSubConnectionString: webPubSub?.primaryConnectionString || '',
BOOSTER_ENV: config.environmentName,
[environmentVarNames.restAPIURL]: `http://${domainNameLabel}.${region}.cloudapp.azure.com/${config.environmentName}`,
[environmentVarNames.eventHubConnectionString]: eventHubConnectionString,
[environmentVarNames.eventHubName]: config.resourceNames.streamTopic,
[environmentVarNames.eventHubMaxRetries]:
config.eventStreamConfiguration.parameters?.maxRetries?.toString() || '5',
[environmentVarNames.eventHubMode]: config.eventStreamConfiguration.parameters?.mode || 'exponential',
COSMOSDB_CONNECTION_STRING: `AccountEndpoint=https://${cosmosdbDatabase.name}.documents.azure.com:443/;AccountKey=${cosmosdbDatabase.primaryKey};`,
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING: storageAccount.primaryConnectionString, // Terraform bug: https://github.com/hashicorp/terraform-provider-azurerm/issues/16650
BOOSTER_APP_NAME: process.env['BOOSTER_APP_NAME'] ?? '',
}
}
}
Loading

0 comments on commit cb500c9

Please sign in to comment.