@@ -30,6 +30,7 @@ import (
3030 "github.com/gorilla/websocket"
3131 . "github.com/onsi/ginkgo/v2"
3232 . "github.com/onsi/gomega"
33+ "github.com/stretchr/testify/assert"
3334 "k8s.io/apimachinery/pkg/types"
3435 "k8s.io/apimachinery/pkg/util/wait"
3536
@@ -802,6 +803,7 @@ spec:
802803 }
803804 })
804805 })
806+
805807 Context ("Test ApisixRoute sync during startup" , func () {
806808 const route = `
807809apiVersion: apisix.apache.org/v2
@@ -1041,4 +1043,263 @@ spec:
10411043 Expect (string (msg )).To (Equal (testMessage ), "message content verification" )
10421044 })
10431045 })
1046+
1047+ Context ("Test ApisixRoute with External Services" , func () {
1048+ const (
1049+ externalServiceName = "ext-httpbin"
1050+ upstreamName = "httpbin-upstream"
1051+ routeName = "httpbin-route"
1052+ )
1053+
1054+ createExternalService := func (externalName string ) {
1055+ By (fmt .Sprintf ("create ExternalName service: %s -> %s" , externalServiceName , externalName ))
1056+ svcSpec := fmt .Sprintf (`
1057+ apiVersion: v1
1058+ kind: Service
1059+ metadata:
1060+ name: %s
1061+ spec:
1062+ type: ExternalName
1063+ externalName: %s
1064+ ` , externalServiceName , externalName )
1065+ err := s .CreateResourceFromString (svcSpec )
1066+ Expect (err ).ShouldNot (HaveOccurred (), "creating ExternalName service" )
1067+ }
1068+
1069+ createApisixUpstream := func (externalType apiv2.ApisixUpstreamExternalType , name string ) {
1070+ By (fmt .Sprintf ("create ApisixUpstream: type=%s, name=%s" , externalType , name ))
1071+ upstreamSpec := fmt .Sprintf (`
1072+ apiVersion: apisix.apache.org/v2
1073+ kind: ApisixUpstream
1074+ metadata:
1075+ name: %s
1076+ spec:
1077+ externalNodes:
1078+ - type: %s
1079+ name: %s
1080+ ` , upstreamName , externalType , name )
1081+ var upstream apiv2.ApisixUpstream
1082+ applier .MustApplyAPIv2 (
1083+ types.NamespacedName {Namespace : s .Namespace (), Name : upstreamName },
1084+ & upstream ,
1085+ upstreamSpec ,
1086+ )
1087+ }
1088+
1089+ createApisixRoute := func () {
1090+ By ("create ApisixRoute referencing ApisixUpstream" )
1091+ routeSpec := fmt .Sprintf (`
1092+ apiVersion: apisix.apache.org/v2
1093+ kind: ApisixRoute
1094+ metadata:
1095+ name: %s
1096+ spec:
1097+ ingressClassName: apisix
1098+ http:
1099+ - name: rule1
1100+ match:
1101+ hosts:
1102+ - httpbin.org
1103+ paths:
1104+ - /ip
1105+ upstreams:
1106+ - name: %s
1107+ ` , routeName , upstreamName )
1108+ var route apiv2.ApisixRoute
1109+ applier .MustApplyAPIv2 (
1110+ types.NamespacedName {Namespace : s .Namespace (), Name : routeName },
1111+ & route ,
1112+ routeSpec ,
1113+ )
1114+ }
1115+
1116+ createApisixRouteWithHostRewrite := func (host string ) {
1117+ By ("create ApisixRoute with host rewrite" )
1118+ routeSpec := fmt .Sprintf (`
1119+ apiVersion: apisix.apache.org/v2
1120+ kind: ApisixRoute
1121+ metadata:
1122+ name: %s
1123+ spec:
1124+ ingressClassName: apisix
1125+ http:
1126+ - name: rule1
1127+ match:
1128+ hosts:
1129+ - httpbin.org
1130+ paths:
1131+ - /ip
1132+ upstreams:
1133+ - name: %s
1134+ plugins:
1135+ - name: proxy-rewrite
1136+ enable: true
1137+ config:
1138+ host: %s
1139+ ` , routeName , upstreamName , host )
1140+ var route apiv2.ApisixRoute
1141+ applier .MustApplyAPIv2 (
1142+ types.NamespacedName {Namespace : s .Namespace (), Name : routeName },
1143+ & route ,
1144+ routeSpec ,
1145+ )
1146+ }
1147+
1148+ verifyAccess := func () {
1149+ By ("verify access to external service" )
1150+ request := func () int {
1151+ return s .NewAPISIXClient ().GET ("/ip" ).
1152+ WithHost ("httpbin.org" ).
1153+ Expect ().Raw ().StatusCode
1154+ }
1155+ Eventually (request ).WithTimeout (30 * time .Second ).ProbeEvery (2 * time .Second ).
1156+ Should (Equal (http .StatusOK ))
1157+ }
1158+
1159+ It ("access third-party service directly" , func () {
1160+ createApisixUpstream (apiv2 .ExternalTypeDomain , "httpbin.org" )
1161+ createApisixRoute ()
1162+ verifyAccess ()
1163+ })
1164+
1165+ It ("access third-party service with host rewrite" , func () {
1166+ createApisixUpstream (apiv2 .ExternalTypeDomain , "httpbin.org" )
1167+ createApisixRouteWithHostRewrite ("httpbin.org" )
1168+ verifyAccess ()
1169+ })
1170+
1171+ It ("access external domain via ExternalName service" , func () {
1172+ createExternalService ("httpbin.org" )
1173+ createApisixUpstream (apiv2 .ExternalTypeService , externalServiceName )
1174+ createApisixRoute ()
1175+ verifyAccess ()
1176+ })
1177+
1178+ It ("access in-cluster service via ExternalName" , func () {
1179+ By ("create temporary httpbin service" )
1180+
1181+ By ("get FQDN of temporary service" )
1182+ fqdn := fmt .Sprintf ("%s.%s.svc.cluster.local" , "httpbin-service-e2e-test" , s .Namespace ())
1183+
1184+ By ("setup external service and route" )
1185+ createExternalService (fqdn )
1186+ createApisixUpstream (apiv2 .ExternalTypeService , externalServiceName )
1187+ createApisixRoute ()
1188+ verifyAccess ()
1189+ })
1190+
1191+ Context ("complex scenarios" , func () {
1192+ It ("multiple external services in one upstream" , func () {
1193+ By ("create ApisixUpstream with multiple external nodes" )
1194+ upstreamSpec := `
1195+ apiVersion: apisix.apache.org/v2
1196+ kind: ApisixUpstream
1197+ metadata:
1198+ name: httpbin-upstream
1199+ spec:
1200+ externalNodes:
1201+ - type: Domain
1202+ name: httpbin.org
1203+ - type: Domain
1204+ name: postman-echo.com
1205+ `
1206+ var upstream apiv2.ApisixUpstream
1207+ applier .MustApplyAPIv2 (
1208+ types.NamespacedName {Namespace : s .Namespace (), Name : upstreamName },
1209+ & upstream ,
1210+ upstreamSpec ,
1211+ )
1212+
1213+ createApisixRoute ()
1214+
1215+ By ("verify access to multiple services" )
1216+ time .Sleep (7 * time .Second )
1217+ hasEtag := false // postman-echo.com
1218+ hasNoEtag := false // httpbin.org
1219+ for range 20 {
1220+ headers := s .NewAPISIXClient ().GET ("/ip" ).
1221+ WithHeader ("Host" , "httpbin.org" ).
1222+ WithHeader ("X-Foo" , "bar" ).
1223+ Expect ().
1224+ Headers ().Raw ()
1225+ if _ , ok := headers ["Etag" ]; ok {
1226+ hasEtag = true
1227+ } else {
1228+ hasNoEtag = true
1229+ }
1230+ if hasEtag && hasNoEtag {
1231+ break
1232+ }
1233+ }
1234+ assert .True (GinkgoT (), hasEtag && hasNoEtag , "both httpbin and postman should be accessed at least once" )
1235+ })
1236+
1237+ It ("should be able to use backends and upstreams together" , func () {
1238+ upstreamSpec := `
1239+ apiVersion: apisix.apache.org/v2
1240+ kind: ApisixUpstream
1241+ metadata:
1242+ name: httpbin-upstream
1243+ spec:
1244+ externalNodes:
1245+ - type: Domain
1246+ name: postman-echo.com
1247+ `
1248+ var upstream apiv2.ApisixUpstream
1249+ applier .MustApplyAPIv2 (
1250+ types.NamespacedName {Namespace : s .Namespace (), Name : upstreamName },
1251+ & upstream ,
1252+ upstreamSpec ,
1253+ )
1254+ By ("create ApisixRoute with both backends and upstreams" )
1255+ routeSpec := fmt .Sprintf (`
1256+ apiVersion: apisix.apache.org/v2
1257+ kind: ApisixRoute
1258+ metadata:
1259+ name: %s
1260+ spec:
1261+ ingressClassName: apisix
1262+ http:
1263+ - name: rule1
1264+ match:
1265+ hosts:
1266+ - httpbin.org
1267+ paths:
1268+ - /ip
1269+ backends:
1270+ - serviceName: httpbin-service-e2e-test
1271+ servicePort: 80
1272+ resolveGranularity: service
1273+ upstreams:
1274+ - name: %s
1275+ ` , routeName , upstreamName )
1276+ var route apiv2.ApisixRoute
1277+ applier .MustApplyAPIv2 (
1278+ types.NamespacedName {Namespace : s .Namespace (), Name : routeName },
1279+ & route ,
1280+ routeSpec ,
1281+ )
1282+ By ("verify access to multiple services" )
1283+ time .Sleep (7 * time .Second )
1284+ hasEtag := false // postman-echo.com
1285+ hasNoEtag := false // httpbin.org
1286+ for range 20 {
1287+ headers := s .NewAPISIXClient ().GET ("/ip" ).
1288+ WithHeader ("Host" , "httpbin.org" ).
1289+ WithHeader ("X-Foo" , "bar" ).
1290+ Expect ().
1291+ Headers ().Raw ()
1292+ if _ , ok := headers ["Etag" ]; ok {
1293+ hasEtag = true
1294+ } else {
1295+ hasNoEtag = true
1296+ }
1297+ if hasEtag && hasNoEtag {
1298+ break
1299+ }
1300+ }
1301+ assert .True (GinkgoT (), hasEtag && hasNoEtag , "both httpbin and postman should be accessed at least once" )
1302+ })
1303+ })
1304+ })
10441305})
0 commit comments