-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds intial consul support and unit test stub
- Loading branch information
1 parent
2a03961
commit 06c99b9
Showing
4 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import Consul from 'consul'; | ||
import { Context } from './context'; | ||
import { GetItem } from 'consul/lib/kv'; | ||
import InstanceStore, { InstanceGroup } from './instance_store'; | ||
|
||
// implments the InstanceStore interface using consul K/V API calls | ||
// uses the got library to make HTTP requests | ||
|
||
export interface ConsulOptions { | ||
host: string; | ||
port: number; | ||
secure: boolean; | ||
groupsPrefix?: string; | ||
} | ||
|
||
export default class ConsulStore { | ||
private client: Consul; | ||
private groupsPrefix = 'autoscaler/groups/'; | ||
|
||
constructor(options: ConsulOptions) { | ||
this.client = new Consul(options); | ||
if (options.groupsPrefix) { | ||
this.groupsPrefix = options.groupsPrefix; | ||
} | ||
} | ||
|
||
async getInstanceGroup(ctx: Context, group: string): Promise<InstanceGroup> { | ||
try { | ||
const { Value } = await this.fetch(ctx, `${this.groupsPrefix}${group}`); | ||
return <InstanceGroup>JSON.parse(Value); | ||
} catch (err) { | ||
ctx.logger.error(`Failed to get instance group from consul: ${err}`, { err }); | ||
throw err; | ||
} | ||
} | ||
|
||
async getAllInstanceGroups(ctx: Context): Promise<InstanceGroup[]> { | ||
try { | ||
const keys = await this.fetchInstanceGroups(ctx); | ||
const groups = await Promise.all(keys.map((key) => this.getInstanceGroup(ctx, key))); | ||
return groups; | ||
} catch (err) { | ||
ctx.logger.error(`Failed to get all instance groups from consul: ${err}`, { err }); | ||
throw err; | ||
} | ||
} | ||
|
||
async fetchInstanceGroups(ctx: Context): Promise<string[]> { | ||
ctx.logger.debug('fetching consul k/v keys'); | ||
const res = await this.client.kv.get({ key: this.groupsPrefix, recurse: true }); | ||
ctx.logger.debug('received consul k/v keys', { res }); | ||
if (!res) { | ||
return []; | ||
} | ||
return Object.entries(res).map(([_k, v]) => v.Key.replace(this.groupsPrefix, '')); | ||
} | ||
|
||
async upsertInstanceGroup(ctx: Context, group: InstanceGroup): Promise<boolean> { | ||
try { | ||
await this.write(ctx, `${this.groupsPrefix}${group.name}`, JSON.stringify(group)); | ||
return true; | ||
} catch (err) { | ||
ctx.logger.error(`Failed to upsert instance group into consul: ${err}`, { group: group.name, err }); | ||
return false; | ||
} | ||
} | ||
|
||
async deleteInstanceGroup(ctx: Context, group: string): Promise<boolean> { | ||
try { | ||
await this.delete(`${this.groupsPrefix}${group}`); | ||
return true; | ||
} catch (err) { | ||
ctx.logger.error(`Failed to delete instance group from consul: ${err}`, { group, err }); | ||
return false; | ||
} | ||
} | ||
|
||
async fetch(ctx: Context, key: string): Promise<GetItem | undefined> { | ||
ctx.logger.debug(`reading consul k/v key`, { key }); | ||
const v = await this.client.kv.get(key); | ||
ctx.logger.debug(`received consul k/v item`, { v }); | ||
return v; | ||
} | ||
|
||
async write(ctx: Context, key: string, value: string): Promise<boolean> { | ||
try { | ||
const res = await this.client.kv.set(key, value); | ||
if (!res) { | ||
ctx.logger.error(`Failed to write to consul`); | ||
} | ||
return res; | ||
} catch (err) { | ||
ctx.logger.error(`Failed to write to consul: ${err}`, { err }); | ||
return false; | ||
} | ||
} | ||
|
||
async delete(key: string): Promise<boolean> { | ||
await this.client.kv.del(key); | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
// @ts-nocheck | ||
import AutoscalerLogger from '../logger'; | ||
import assert from 'node:assert'; | ||
import test, { afterEach, describe, mock } from 'node:test'; | ||
|
||
import ConsulClient, { ConsulOptions } from '../consul'; | ||
|
||
const asLogger = new AutoscalerLogger({ logLevel: 'debug' }); | ||
const logger = asLogger.createLogger('debug'); | ||
|
||
const ctx = { logger }; | ||
ctx.logger.debug = mock.fn(); | ||
ctx.logger.error = mock.fn(); | ||
|
||
const options = <ConsulOptions>{ | ||
host: 'localhost', | ||
port: 8500, | ||
secure: false, | ||
groupsPrefix: '_test/autoscaler/groups/', | ||
}; | ||
const client = new ConsulClient(options); | ||
|
||
const group = { | ||
name: 'test', | ||
type: 'test', | ||
region: 'test', | ||
environment: 'test', | ||
enableScheduler: true, | ||
tags: { | ||
test: 'test', | ||
}, | ||
}; | ||
|
||
describe('ConsulClient', () => { | ||
afterEach(() => { | ||
mock.restoreAll(); | ||
}); | ||
|
||
describe('testListInstanceGroups', () => { | ||
test('will list all instance groups', async () => { | ||
const res = await client.fetchInstanceGroups(ctx); | ||
assert.strictEqual(res.length, 0); | ||
}); | ||
|
||
test('will upsert a test group', async () => { | ||
const res = await client.upsertInstanceGroup(ctx, group); | ||
assert.strictEqual(res, true); | ||
}); | ||
|
||
test('will find upserted group when listing all instance groups', async () => { | ||
const res = await client.fetchInstanceGroups(ctx); | ||
assert.strictEqual(res.length, 1); | ||
assert.strictEqual(res[0], group.name); | ||
|
||
const res2 = await client.getInstanceGroup(ctx, group.name); | ||
assert.deepEqual(res2, group); | ||
}); | ||
|
||
test('will delete upserted test group', async () => { | ||
const res = await client.deleteInstanceGroup(ctx, group.name); | ||
assert.strictEqual(res, true); | ||
}); | ||
}); | ||
}); |