shared/common/aws-service.ts
AWS related service
Properties |
|
Methods |
|
constructor(debugLogsAllowed)
|
||||
Defined in shared/common/aws-service.ts:30
|
||||
Constructor @param debugLogAllower {boolean = false} - enable or disable additional logs
Parameters :
|
Private Readonly debugLogsAllowed |
Type : boolean
|
Default value : true
|
Defined in shared/common/aws-service.ts:28
|
flag for additional debug logs |
Private Readonly logger |
Default value : new Logger(AwsService.name)
|
Defined in shared/common/aws-service.ts:30
|
logger |
Async allowCronRun | ||||||||
allowCronRun(forceCronRun)
|
||||||||
Defined in shared/common/aws-service.ts:252
|
||||||||
Checks if cron run is allowed @param forceCronRun - allow some methods to still be run on non cron running environments. @return {Promise
Parameters :
Returns :
unknown
|
Async allowLeadRoutines |
allowLeadRoutines()
|
Defined in shared/common/aws-service.ts:272
|
Checks if lead tasks are allowed @return {Promise
Returns :
Promise<boolean>
|
Private Async getLeadInstanceIdFromBeanstalk |
getLeadInstanceIdFromBeanstalk()
|
Defined in shared/common/aws-service.ts:183
|
Gets the current lead instance using the Elastic Beanstalk API. But this API has a very low rate limiting, with an unknown specific limit. I (Ain) was unable to figure out what the rate limit was, but we were throttled using this way of querying. See getLeadInstanceIdFromEc2()
Returns :
Promise<string>
|
Private Async getLeadInstanceIdFromEc2 |
getLeadInstanceIdFromEc2()
|
Defined in shared/common/aws-service.ts:129
|
Gets the lead instance from the EC2 API. This API 100 requests per second limit for our bucket (all servers in our region) See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/throttling.html#throttling-limits See getLeadInstanceIdFromBeanstalk()
Returns :
Promise<string>
|
Async getRunningInstanceId |
getRunningInstanceId()
|
Defined in shared/common/aws-service.ts:100
|
Gets running ec2 instance id @return {Promise
Returns :
Promise<string>
|
Private Async isCurrentServerTheLeadInstance |
isCurrentServerTheLeadInstance()
|
Defined in shared/common/aws-service.ts:217
|
Checks if current ec2 instance is lead (can do cron/gameSave tasks) @return {Promise
Returns :
Promise<boolean>
|
Async publishMetrics | ||||||||
publishMetrics(metrics: AwsCustomMetrics)
|
||||||||
Defined in shared/common/aws-service.ts:55
|
||||||||
Publish metrics data @param {AwsCustomMetrics} metrics - metrics to publish
Parameters :
Returns :
Promise<void>
|
ready |
ready()
|
Defined in shared/common/aws-service.ts:44
|
ready checker
Returns :
boolean
|
import { Logger } from '@nestjs/common';
import * as AWS from 'aws-sdk';
import { AwsCustomMetrics } from '../interfaces/aws';
/**
* Method to check if environment configured to run cron tasks (env.RUN_CRON !== 'no')
*
* @return {boolean}
*/
export const isCronRunningEnvironment = (): boolean => {
if (!!process.env.WEBSOCKET_NO_REDIS) return true
return process.env.RUN_CRON !== 'no';
};
AWS.config.update({
region: process.env.AWS_REGION,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
});
const MATH99_APP = process.env.AWS_ENV_NAME || 'math99';
/**
* AWS related service
*/
export class AwsService {
/** flag for additional debug logs */
private readonly debugLogsAllowed: boolean = true;
/** logger */
private readonly logger = new Logger(AwsService.name);
/**
* Constructor
*
* @param debugLogAllower {boolean = false} - enable or disable additional logs
*/
constructor(debugLogsAllowed = false) {
this.debugLogsAllowed = debugLogsAllowed;
}
/**
* ready checker
*/
ready(): boolean {
const { AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY }
= process.env
return !!AWS_REGION && !!AWS_ACCESS_KEY_ID && !!AWS_SECRET_ACCESS_KEY;
}
/**
* Publish metrics data
*
* @param {AwsCustomMetrics} metrics - metrics to publish
*/
async publishMetrics(metrics: AwsCustomMetrics): Promise<void> {
if (!this.ready()) return;
const cloudwatch = new AWS.CloudWatch();
const now = new Date();
const data = Object.keys(metrics)
.filter(m => m !== 'instanceId')
.map(m => ({
MetricName: m,
Value: metrics[m],
Dimensions: [{
Name: 'AWS_REGION',
Value: process.env.AWS_REGION,
}, {
Name: 'AWS_INSTANCE_ID',
Value: metrics.instanceId,
}, {
Name: 'MATH99_REGION',
Value: process.env.MATH99_REGION,
}, {
Name: 'MATH99_APP',
Value: MATH99_APP,
}],
StorageResolution: 60,
Timestamp: now,
Unit: 'Count',
}))
const params = {
Namespace: 'MATH99',
MetricData: data,
};
return new Promise((resolve) => {
cloudwatch.putMetricData(params, (err, data) => {
if (!err) {
this.logger.log(`AWS METRICS published ${JSON.stringify(metrics)}`);
return
}
this.logger.error(`AWS METRICS error ${JSON.stringify(metrics)}: ${err}`);
});
});
}
/**
* Gets running ec2 instance id
*
* @return {Promise<string>} - ec2 instance id
*/
async getRunningInstanceId(): Promise<string> {
if (!this.ready()) return '';
const meta = new AWS.MetadataService();
return new Promise((resolve, reject) => {
meta.request('/latest/meta-data/instance-id', (err, data) => {
if (err) {
this.logger.error(
`Error requesting instance meta data: ${err.stack}`,
);
reject(err);
} else {
if (this.debugLogsAllowed) {
this.logger.log(`Instance meta data: ${data}`);
}
resolve(data);
}
});
});
}
/**
* Gets the lead instance from the EC2 API.
*
* This API 100 requests per second limit for our bucket (all servers in our region)
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/APIReference/throttling.html#throttling-limits
* @see getLeadInstanceIdFromBeanstalk()
* @private
*/
private async getLeadInstanceIdFromEc2(): Promise<string> {
const ec2 = new AWS.EC2();
return new Promise((resolve, reject) => {
try {
ec2.describeInstances(
{
Filters: [
{
Name: 'tag:elasticbeanstalk:environment-name',
Values: [process.env.AWS_ENV_NAME],
},
{
Name: 'instance-state-name',
Values: ['running']
},
],
MaxResults: 100,
},
(err, data) => {
if (err) {
this.logger.error(
`Error describing environment resources: ${err.stack}`,
);
reject(err);
} else {
const leadId = data.Reservations[0].Instances[0].InstanceId;
if (this.debugLogsAllowed) {
const allIds = data.Reservations.map(
(reservation) => reservation.Instances[0].InstanceId,
).join(', ');
this.logger.log(`All instances: ${allIds}`);
this.logger.log(`Lead instance ID: ${leadId}`);
}
resolve(leadId);
}
},
);
} catch (error) {
reject(error);
}
});
}
/**
* Gets the current lead instance using the Elastic Beanstalk API.
*
* But this API has a very low rate limiting, with an unknown specific limit.
* I (Ain) was unable to figure out what the rate limit was, but we were
* throttled using this way of querying.
*
* @see getLeadInstanceIdFromEc2()
* @deprecated
* @private
*/
private async getLeadInstanceIdFromBeanstalk(): Promise<string> {
const newClient = new AWS.ElasticBeanstalk();
try {
return new Promise((resolve, reject) => {
// eslint-disable-next-line
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ElasticBeanstalk.html#describeEnvironmentResources-property
newClient.describeEnvironmentResources(
{
EnvironmentName: process.env.AWS_ENV_NAME,
},
(err, data) => {
if (err) {
this.logger.error(
'Error describing environment resources:',
err.stack,
);
reject(err);
} else {
resolve(data.EnvironmentResources.Instances[0].Id);
}
},
);
});
} catch (error) {
this.logger.log('Error getting lead instance ID:', error);
return null;
}
}
/**
* Checks if current ec2 instance is lead (can do cron/gameSave tasks)
*
* @return {Promise<boolean>} - ec2 instance is lead
*/
private async isCurrentServerTheLeadInstance(): Promise<boolean> {
if (process.env.NODE_ENV === 'development') return true;
if (!!process.env.WEBSOCKET_NO_REDIS) return true
let runningInstanceId;
let leadInstanceId;
try {
runningInstanceId = await this.getRunningInstanceId();
} catch (e) {
this.logger.error(`Error getting running instance ID: ${e}`);
return null;
}
try {
leadInstanceId = await this.getLeadInstanceIdFromEc2();
} catch (e) {
this.logger.error(`Error getting lead instance ID: ${e}`);
return null;
}
if (this.debugLogsAllowed) {
this.logger.log(
`Is current server lead? ${runningInstanceId === leadInstanceId}`,
);
}
return `${runningInstanceId}` === `${leadInstanceId}`;
}
/**
* Checks if cron run is allowed
*
* @param forceCronRun - allow some methods to still be run on non cron running environments.
*
* @return {Promise<boolean>} - ec2 instance is lead
*/
async allowCronRun(forceCronRun = false) {
if (process.env.NODE_ENV === 'development') {
return true;
}
if (!forceCronRun && isCronRunningEnvironment() === false) {
return false;
}
// Check it everytime because the list of servers changes constantly,
// eg the previous lead instance might be killed and now this server
// is the new lead.
return await this.isCurrentServerTheLeadInstance();
}
/**
* Checks if lead tasks are allowed
*
* @return {Promise<boolean>} - ec2 instance is lead
*/
async allowLeadRoutines(): Promise<boolean> {
if (process.env.NODE_ENV === 'development') return true;
if (!!process.env.WEBSOCKET_NO_REDIS) return true
// allow if aws not configured
if (!this.ready()) return true;
return await this.isCurrentServerTheLeadInstance();
}
}