Skip to content

Commit 555ab97

Browse files
authored
fix(apisix-standalone): duplicate upstreams when use service multiple upstreams (#334)
1 parent aa417b2 commit 555ab97

File tree

3 files changed

+200
-4
lines changed

3 files changed

+200
-4
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { Differ } from '@api7/adc-differ';
2+
import * as ADCSDK from '@api7/adc-sdk';
3+
4+
import { BackendAPISIXStandalone } from '../../src';
5+
import {
6+
config as configCache,
7+
rawConfig as rawConfigCache,
8+
} from '../../src/cache';
9+
import * as typing from '../../src/typing';
10+
import { server1, token1 } from '../support/constants';
11+
import {
12+
createEvent,
13+
deleteEvent,
14+
dumpConfiguration,
15+
restartAPISIX,
16+
syncEvents,
17+
updateEvent,
18+
} from '../support/utils';
19+
20+
const cacheKey = 'default';
21+
describe('Service-Upstreams E2E', () => {
22+
let backend: BackendAPISIXStandalone;
23+
24+
beforeAll(async () => {
25+
await restartAPISIX();
26+
backend = new BackendAPISIXStandalone({
27+
server: server1,
28+
token: token1,
29+
tlsSkipVerify: true,
30+
cacheKey,
31+
});
32+
});
33+
34+
describe('Sync and dump service with multiple upstreams', () => {
35+
const upstreamND1Name = 'nd-upstream1';
36+
const upstreamND1 = {
37+
name: upstreamND1Name,
38+
type: 'roundrobin',
39+
scheme: 'https',
40+
nodes: [
41+
{
42+
host: '1.1.1.1',
43+
port: 443,
44+
weight: 100,
45+
},
46+
],
47+
} satisfies ADCSDK.Upstream;
48+
const upstreamND2Name = 'nd-upstream2';
49+
const upstreamND2 = {
50+
//@ts-expect-error custom id
51+
id: upstreamND2Name,
52+
name: upstreamND2Name,
53+
type: 'roundrobin',
54+
scheme: 'https',
55+
nodes: [
56+
{
57+
host: '1.0.0.1',
58+
port: 443,
59+
weight: 100,
60+
},
61+
],
62+
} satisfies ADCSDK.Upstream;
63+
const serviceName = 'test';
64+
const service = {
65+
name: serviceName,
66+
upstream: {
67+
type: 'roundrobin',
68+
nodes: [
69+
{
70+
host: 'httpbin.org',
71+
port: 443,
72+
weight: 100,
73+
},
74+
],
75+
},
76+
upstreams: [upstreamND1, upstreamND2],
77+
} satisfies ADCSDK.Service;
78+
79+
it('Initialize cache', () =>
80+
expect(dumpConfiguration(backend)).resolves.not.toThrow());
81+
82+
it('Create', async () =>
83+
syncEvents(
84+
backend,
85+
Differ.diff(
86+
{
87+
services: [service],
88+
},
89+
await dumpConfiguration(backend),
90+
),
91+
));
92+
93+
const checkOriginalConfig = () => {
94+
const rawConfig = rawConfigCache.get(cacheKey);
95+
expect(rawConfig?.services?.[0].id).toEqual(
96+
ADCSDK.utils.generateId(serviceName),
97+
);
98+
expect(rawConfig?.upstreams).not.toBeUndefined();
99+
expect(rawConfig?.upstreams).toHaveLength(3);
100+
expect(rawConfig?.upstreams?.[0].name).toEqual(serviceName);
101+
expect(rawConfig?.upstreams?.[1].name).toEqual(upstreamND1Name);
102+
expect(rawConfig?.upstreams?.[2].name).toEqual(upstreamND2Name);
103+
expect(rawConfig?.upstreams?.[0].labels).toBeUndefined();
104+
expect(
105+
rawConfig?.upstreams?.[1].labels?.[
106+
typing.ADC_UPSTREAM_SERVICE_ID_LABEL
107+
],
108+
).toEqual(ADCSDK.utils.generateId(serviceName));
109+
expect(
110+
rawConfig?.upstreams?.[2].labels?.[
111+
typing.ADC_UPSTREAM_SERVICE_ID_LABEL
112+
],
113+
).toEqual(ADCSDK.utils.generateId(serviceName));
114+
115+
const config = configCache.get(cacheKey);
116+
expect(config?.services).not.toBeUndefined();
117+
expect(config?.services).toHaveLength(1);
118+
expect(config?.services?.[0].upstreams).toHaveLength(2);
119+
expect(
120+
config?.services?.[0].upstreams?.[0].labels?.[
121+
typing.ADC_UPSTREAM_SERVICE_ID_LABEL
122+
],
123+
).toBeUndefined();
124+
expect(
125+
config?.services?.[0].upstreams?.[1].labels?.[
126+
typing.ADC_UPSTREAM_SERVICE_ID_LABEL
127+
],
128+
).toBeUndefined();
129+
};
130+
it('Check cache', checkOriginalConfig);
131+
132+
it('Try update (without any change)', async () =>
133+
syncEvents(
134+
backend,
135+
Differ.diff(
136+
{
137+
services: [service],
138+
},
139+
await dumpConfiguration(backend),
140+
),
141+
));
142+
143+
it('Check cache 2', checkOriginalConfig);
144+
145+
it('Try update', async () => {
146+
const newService = structuredClone(service);
147+
newService.upstreams[0].nodes[0].host = '8.8.8.8';
148+
await syncEvents(
149+
backend,
150+
Differ.diff(
151+
{
152+
services: [newService],
153+
},
154+
await dumpConfiguration(backend),
155+
),
156+
);
157+
});
158+
159+
it('Check updated cache', () => {
160+
const rawConfig = rawConfigCache.get(cacheKey);
161+
expect(rawConfig?.services?.[0].id).toEqual(
162+
ADCSDK.utils.generateId(serviceName),
163+
);
164+
expect(rawConfig?.upstreams).not.toBeUndefined();
165+
expect(rawConfig?.upstreams).toHaveLength(3);
166+
expect(
167+
rawConfig?.upstreams?.[1].labels?.[
168+
typing.ADC_UPSTREAM_SERVICE_ID_LABEL
169+
],
170+
).toEqual(ADCSDK.utils.generateId(serviceName));
171+
expect(rawConfig?.upstreams?.[1].nodes[0].host).toEqual('8.8.8.8');
172+
173+
const config = configCache.get(cacheKey);
174+
expect(config?.services).not.toBeUndefined();
175+
expect(config?.services).toHaveLength(1);
176+
expect(config?.services?.[0].upstreams).toHaveLength(2);
177+
expect(
178+
config?.services?.[0].upstreams?.[0].labels?.[
179+
typing.ADC_UPSTREAM_SERVICE_ID_LABEL
180+
],
181+
).toBeUndefined();
182+
expect(config?.services?.[0].upstreams?.[0].nodes?.[0].host).toEqual(
183+
'8.8.8.8',
184+
);
185+
});
186+
});
187+
});

libs/backend-apisix-standalone/src/transformer.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as ADCSDK from '@api7/adc-sdk';
2-
import { isEmpty } from 'lodash';
2+
import { cloneDeep, isEmpty, unset } from 'lodash';
33

44
import * as typing from './typing';
55

@@ -12,7 +12,7 @@ export const toADC = (input: typing.APISIXStandalone) => {
1212
upstream: Omit<typing.Upstream, 'id' | 'name' | 'modifiedIndex'> & {
1313
name?: string;
1414
},
15-
) => ({
15+
): ADCSDK.Upstream => ({
1616
name: upstream.name,
1717
description: upstream.desc,
1818
labels: upstream.labels,
@@ -96,7 +96,16 @@ export const toADC = (input: typing.APISIXStandalone) => {
9696
upstream.labels?.[typing.ADC_UPSTREAM_SERVICE_ID_LABEL] ===
9797
service.id,
9898
)
99-
.map(transformUpstream)
99+
.map((upstream) => {
100+
const up = transformUpstream(
101+
cloneDeep(upstream),
102+
) as ADCSDK.Upstream & {
103+
id: string;
104+
};
105+
up.id = upstream.id;
106+
unset(up, `labels.${typing.ADC_UPSTREAM_SERVICE_ID_LABEL}`);
107+
return up;
108+
})
100109
.map(ADCSDK.utils.recursiveOmitUndefined),
101110
}))
102111
.map(ADCSDK.utils.recursiveOmitUndefined) ?? [],

libs/differ/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { DifferV3 } from './differv3.js';
1+
export { DifferV3, DifferV3 as Differ } from './differv3.js';

0 commit comments

Comments
 (0)