@@ -20,6 +20,7 @@ package sync
2020
2121import (
2222 "context"
23+ "fmt"
2324 "testing"
2425 "time"
2526
@@ -29,6 +30,9 @@ import (
2930 syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
3031 "github.com/kcp-dev/api-syncagent/test/utils"
3132
33+ kcpdevv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
34+
35+ rbacv1 "k8s.io/api/rbac/v1"
3236 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3337 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3438 "k8s.io/apimachinery/pkg/runtime/schema"
@@ -132,3 +136,139 @@ spec:
132136 t .Fatalf ("Failed to wait for object to be synced down: %v" , err )
133137 }
134138}
139+
140+ func TestAPIExportEndpointSliceDifferentCluster (t * testing.T ) {
141+ const (
142+ apiExportName = "kcp.example.com"
143+ kcpGroupName = "kcp.example.com"
144+ orgWorkspace = "endpointslice-different-cluster"
145+ endpointWorkspace = "endpoint"
146+ )
147+
148+ ctx := t .Context ()
149+ ctrlruntime .SetLogger (logr .Discard ())
150+
151+ // setup a test environment in kcp
152+ rootCluster := logicalcluster .NewPath ("root" )
153+ utils .CreateOrganization (t , ctx , orgWorkspace , apiExportName )
154+
155+ // create a custom AEES in a different cluster than the APIExport
156+ kcpClusterClient := utils .GetKcpAdminClusterClient (t )
157+ orgClient := kcpClusterClient .Cluster (rootCluster .Join (orgWorkspace ))
158+ endpointClusterName := utils .CreateWorkspace (t , ctx , orgClient , endpointWorkspace )
159+ endpointClient := kcpClusterClient .Cluster (endpointClusterName .Path ())
160+
161+ endpointSlice := & kcpdevv1alpha1.APIExportEndpointSlice {
162+ ObjectMeta : metav1.ObjectMeta {
163+ Name : "dummy" ,
164+ },
165+ Spec : kcpdevv1alpha1.APIExportEndpointSliceSpec {
166+ APIExport : kcpdevv1alpha1.ExportBindingReference {
167+ Path : rootCluster .Join (orgWorkspace ).String (),
168+ Name : apiExportName ,
169+ },
170+ },
171+ }
172+
173+ t .Logf ("Creating APIExportEndpointSlice %q…" , endpointSlice .Name )
174+ if err := endpointClient .Create (ctx , endpointSlice ); err != nil {
175+ t .Fatalf ("Failed to create APIExportEndpointSlice: %v" , err )
176+ }
177+
178+ agent := rbacv1.Subject {
179+ Kind : "User" ,
180+ Name : "api-syncagent-e2e" ,
181+ }
182+
183+ utils .GrantWorkspaceAccess (t , ctx , endpointClient , agent , rbacv1.PolicyRule {
184+ APIGroups : []string {"core.kcp.io" },
185+ Resources : []string {"logicalclusters" },
186+ ResourceNames : []string {"cluster" },
187+ Verbs : []string {"get" },
188+ }, rbacv1.PolicyRule {
189+ APIGroups : []string {"apis.kcp.io" },
190+ Resources : []string {"apiexportendpointslices" },
191+ ResourceNames : []string {endpointSlice .Name },
192+ Verbs : []string {"get" , "list" , "watch" },
193+ })
194+
195+ endpointKubeconfig := utils .CreateKcpAgentKubeconfig (t , fmt .Sprintf ("/clusters/%s" , endpointClusterName ))
196+
197+ // start a service cluster
198+ envtestKubeconfig , envtestClient , _ := utils .RunEnvtest (t , []string {
199+ "test/crds/crontab.yaml" ,
200+ })
201+
202+ // publish Crontabs and Backups
203+ t .Logf ("Publishing CRDs…" )
204+ prCrontabs := & syncagentv1alpha1.PublishedResource {
205+ ObjectMeta : metav1.ObjectMeta {
206+ Name : "publish-crontabs" ,
207+ },
208+ Spec : syncagentv1alpha1.PublishedResourceSpec {
209+ Resource : syncagentv1alpha1.SourceResourceDescriptor {
210+ APIGroup : "example.com" ,
211+ Version : "v1" ,
212+ Kind : "CronTab" ,
213+ },
214+ // These rules make finding the local object easier, but should not be used in production.
215+ Naming : & syncagentv1alpha1.ResourceNaming {
216+ Name : "{{ .Object.metadata.name }}" ,
217+ Namespace : "synced-{{ .Object.metadata.namespace }}" ,
218+ },
219+ Projection : & syncagentv1alpha1.ResourceProjection {
220+ Group : kcpGroupName ,
221+ },
222+ },
223+ }
224+
225+ if err := envtestClient .Create (ctx , prCrontabs ); err != nil {
226+ t .Fatalf ("Failed to create PublishedResource: %v" , err )
227+ }
228+
229+ // start the agent in the background to update the APIExport with the CronTabs API
230+ utils .RunEndpointSliceAgent (ctx , t , "bob" , endpointKubeconfig , envtestKubeconfig , endpointSlice .Name )
231+
232+ // wait until the API is available
233+
234+ teamClusterPath := logicalcluster .NewPath ("root" ).Join (orgWorkspace ).Join ("team-1" )
235+ teamClient := kcpClusterClient .Cluster (teamClusterPath )
236+
237+ utils .WaitForBoundAPI (t , ctx , teamClient , schema.GroupVersionResource {
238+ Group : kcpGroupName ,
239+ Version : "v1" ,
240+ Resource : "crontabs" ,
241+ })
242+
243+ // create a Crontab object in a team workspace
244+ t .Log ("Creating CronTab in kcp…" )
245+ crontab := utils .YAMLToUnstructured (t , `
246+ apiVersion: kcp.example.com/v1
247+ kind: CronTab
248+ metadata:
249+ namespace: default
250+ name: my-crontab
251+ spec:
252+ cronSpec: '* * *'
253+ image: ubuntu:latest
254+ ` )
255+
256+ if err := teamClient .Create (ctx , crontab ); err != nil {
257+ t .Fatalf ("Failed to create CronTab in kcp: %v" , err )
258+ }
259+
260+ // wait for the agent to sync the object down into the service cluster
261+
262+ t .Logf ("Wait for CronTab to be synced…" )
263+ copy := & unstructured.Unstructured {}
264+ copy .SetAPIVersion ("example.com/v1" )
265+ copy .SetKind ("CronTab" )
266+
267+ err := wait .PollUntilContextTimeout (ctx , 500 * time .Millisecond , 30 * time .Second , false , func (ctx context.Context ) (done bool , err error ) {
268+ copyKey := types.NamespacedName {Namespace : "synced-default" , Name : "my-crontab" }
269+ return envtestClient .Get (ctx , copyKey , copy ) == nil , nil
270+ })
271+ if err != nil {
272+ t .Fatalf ("Failed to wait for object to be synced down: %v" , err )
273+ }
274+ }
0 commit comments