fix(services): снимать Down только с общего FQDN

Персональные A не крутить и не трогать при падении. Панель называется по режиму балансировки.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-08-20 17:51:49 +07:00
co-authored by Cursor
parent 3d0ea33baf
commit 2b150fa3a6
8 changed files with 221 additions and 48 deletions
+22 -5
View File
@@ -137,6 +137,7 @@ describe('toFailoverEvents', () => {
},
])
expect(failoverEventCopy(events[0]!)).toBe('Снята с gt.rkns.top')
expect(events[0]?.fqdns).not.toContain('nsgt.rkns.top')
})
it('Down только last-resort на своём FQDN', () => {
@@ -151,10 +152,8 @@ describe('toFailoverEvents', () => {
],
)
expect(events[0]?.kind).toBe('last-resort')
expect(events[0]?.fqdns).toEqual(['nsgt.rkns.top'])
expect(failoverEventCopy(events[0]!)).toBe(
'Down, в A-записях last-resort на nsgt.rkns.top',
)
expect(events[0]?.fqdns).toEqual([])
expect(failoverEventCopy(events[0]!)).toBe('Down, в A-записях last-resort')
})
it('down без A-записи на configured FQDN — снятие', () => {
@@ -279,7 +278,9 @@ describe('mergeFailoverHistory', () => {
'removed:probe',
])
expect(history[0]?.copy).toBe('130.49.213.153 добавлена на gt.rkns.top')
expect(history[1]?.copy).toContain('вышла из пула')
expect(history[1]?.copy).toBe(
'130.49.213.153 вышла из пула (gt.rkns.top)',
)
})
it('keeps DNS over a probe return in the same 2-minute window', () => {
@@ -291,4 +292,20 @@ describe('mergeFailoverHistory', () => {
expect(history).toHaveLength(1)
expect(history[0]?.source).toBe('dns')
})
it('drops dedicated extra-FQDN DNS rows', () => {
const history = mergeFailoverHistory(
[
dns({
id: 11,
fqdn: 'nsgt.rkns.top',
action: 'added',
created_at: '2026-08-20 09:26:00',
}),
],
[],
mskHip,
)
expect(history).toEqual([])
})
})
+21 -2
View File
@@ -44,6 +44,11 @@ export interface FailoverBindingPool {
active: readonly string[]
}
/** Shared pool FQDN — two or more A targets. Dedicated extra-FQDN has one IP. */
export function isSharedPoolBinding(binding: FailoverBindingPool): boolean {
return binding.configured.length >= 2
}
/** Инцидент только при down. up / degraded / unknown — не вывод из пула. */
export function isFailoverEventStatus(status: string): boolean {
return status === 'down'
@@ -72,6 +77,7 @@ export function toFailoverEvents(
const removedFqdns: string[] = []
const lastResortFqdns: string[] = []
for (const binding of bindings) {
if (!isSharedPoolBinding(binding)) continue
const configured = binding.configured.includes(row.ip)
const active = binding.active.includes(row.ip)
if (configured && !active) removedFqdns.push(binding.fqdn)
@@ -98,7 +104,18 @@ function fqdnsForIp(
ip: string,
bindings: readonly FailoverBindingPool[],
): string[] {
return bindings.filter((binding) => binding.configured.includes(ip)).map((binding) => binding.fqdn)
return bindings
.filter((binding) => isSharedPoolBinding(binding) && binding.configured.includes(ip))
.map((binding) => binding.fqdn)
}
function isPoolFqdn(
fqdn: string,
bindings: readonly FailoverBindingPool[],
): boolean {
const binding = bindings.find((item) => item.fqdn === fqdn)
if (!binding) return true
return isSharedPoolBinding(binding)
}
function fqdnLabel(fqdns: string[]): string {
@@ -201,7 +218,9 @@ export function mergeFailoverHistory(
bindings: readonly FailoverBindingPool[] = [],
): FailoverHistoryItem[] {
const merged = [
...dns.map(dnsHistoryItem),
...dns
.filter((item) => isPoolFqdn(item.fqdn, bindings))
.map(dnsHistoryItem),
...toIpAliveTransitions(probes).map((transition) =>
probeHistoryItem(transition, bindings),
),