fix(services): сохранить доп. FQDN у IP при пуле из одного адреса
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 6s
quality / changes (push) Successful in 9s
quality / api (push) Skipped
quality / docker-check (push) Skipped
quality / web (push) Successful in 1m10s
CD / quality (push) Successful in 1m23s
CD / publish (push) Successful in 1m41s

При одном IP общий и доп. FQDN неотличимы по target_ips; после сохранения гидратация относила оба к общим доменам.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-08-31 12:19:53 +07:00
co-authored by Cursor
parent 5cf39880f8
commit a228febc27
2 changed files with 76 additions and 5 deletions
+33
View File
@@ -84,6 +84,21 @@ describe('hydrateAddressBlock', () => {
expect(state.preservedBindings[0]?.fqdn).toBe('edge.example.com')
})
it('при одном IP пула отделяет второй A в extraFqdn узла', () => {
const drafts = [
aRecord('dns.shnt.top', ['130.49.213.176']),
aRecord('ndns.shnt.top', ['130.49.213.176']),
]
const state = hydrateAddressBlock(drafts, ['130.49.213.176'])
expect(state.commonFqdns).toEqual(['dns.shnt.top'])
expect(state.nodes).toEqual([
{ ip: '130.49.213.176', extraFqdn: 'ndns.shnt.top' },
])
expect(state.preservedBindings).toEqual([])
})
it('поднимает веса и приоритеты с общего FQDN', () => {
const drafts = [
aRecord('gt.rkns.top', ['130.49.213.153', '93.115.203.183'], {
@@ -143,6 +158,24 @@ describe('toDomainsPayload', () => {
expect(second.nodes).toEqual(first.nodes)
expect(second.preservedBindings).toEqual([])
})
it('круг hydrate → payload → hydrate сохраняет extra FQDN при одном IP', () => {
const drafts = [
aRecord('dns.shnt.top', ['130.49.213.176']),
aRecord('ndns.shnt.top', ['130.49.213.176']),
]
const first = hydrateAddressBlock(drafts, ['130.49.213.176'])
expect(first.commonFqdns).toEqual(['dns.shnt.top'])
expect(first.nodes).toEqual([
{ ip: '130.49.213.176', extraFqdn: 'ndns.shnt.top' },
])
const rebound = toAddressBindings(first, primaryMeta)
const second = hydrateAddressBlock(rebound, ['130.49.213.176'])
expect(second.commonFqdns).toEqual(first.commonFqdns)
expect(second.nodes).toEqual(first.nodes)
expect(second.preservedBindings).toEqual([])
})
})
describe('removeAddressNode', () => {
+43 -5
View File
@@ -145,6 +145,23 @@ function isFullPoolA(draft: ServiceBindingDraft, pool: string[]): boolean {
return draft.record_type === 'A' && sameIpSet(draft.target_ips, pool)
}
function takeAsCommon(
draft: ServiceBindingDraft,
fqdn: string,
commonFqdns: string[],
weights: Record<string, number>,
priorities: Record<string, number>,
): { weights: Record<string, number>; priorities: Record<string, number> } {
if (fqdn) commonFqdns.push(draft.fqdn)
if (Object.keys(weights).length === 0) {
return {
weights: { ...draft.target_ip_weights },
priorities: { ...draft.target_ip_priorities },
}
}
return { weights, priorities }
}
export function hydrateAddressBlock(
drafts: ServiceBindingDraft[],
pool: string[] = [],
@@ -166,15 +183,36 @@ export function hydrateAddressBlock(
const preservedBindings: ServiceBindingDraft[] = []
let weights: Record<string, number> = {}
let priorities: Record<string, number> = {}
const splitSinglePool =
ips.length === 1 &&
drafts.filter((draft) => isFullPoolA(draft, ips)).length > 1
let assignedFirstSinglePoolCommon = false
for (const draft of drafts) {
const fqdn = draft.fqdn.trim()
if (isFullPoolA(draft, ips)) {
if (fqdn) commonFqdns.push(draft.fqdn)
if (Object.keys(weights).length === 0) {
weights = { ...draft.target_ip_weights }
priorities = { ...draft.target_ip_priorities }
if (splitSinglePool && isFullPoolA(draft, ips)) {
if (!assignedFirstSinglePoolCommon) {
assignedFirstSinglePoolCommon = true
const next = takeAsCommon(draft, fqdn, commonFqdns, weights, priorities)
weights = next.weights
priorities = next.priorities
continue
}
const ip = draft.target_ips[0]?.trim() ?? ''
if (ip && poolSet.has(ip) && fqdn && !claimed.has(ip)) {
claimed.add(ip)
extraByIp.set(ip, draft.fqdn)
continue
}
const overflow = takeAsCommon(draft, fqdn, commonFqdns, weights, priorities)
weights = overflow.weights
priorities = overflow.priorities
continue
}
if (isFullPoolA(draft, ips)) {
const next = takeAsCommon(draft, fqdn, commonFqdns, weights, priorities)
weights = next.weights
priorities = next.priorities
continue
}
if (draft.record_type === 'A' && draft.target_ips.length === 1) {