github.com/oam-dev/kubevela@v1.9.11/pkg/velaql/providers/query/handler_test.go (about)

     1  /*
     2   Copyright 2021. The KubeVela Authors.
     3  
     4   Licensed under the Apache License, Version 2.0 (the "License");
     5   you may not use this file except in compliance with the License.
     6   You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10   Unless required by applicable law or agreed to in writing, software
    11   distributed under the License is distributed on an "AS IS" BASIS,
    12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   See the License for the specific language governing permissions and
    14   limitations under the License.
    15  */
    16  
    17  package query
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"time"
    24  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  	v1 "k8s.io/api/apps/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	networkv1 "k8s.io/api/networking/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	"k8s.io/apimachinery/pkg/util/intstr"
    34  	"k8s.io/klog/v2"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	"sigs.k8s.io/yaml"
    37  
    38  	monitorContext "github.com/kubevela/pkg/monitor/context"
    39  	"github.com/kubevela/workflow/pkg/cue/model/value"
    40  	"github.com/kubevela/workflow/pkg/providers"
    41  
    42  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    43  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    44  	helmapi "github.com/oam-dev/kubevela/pkg/appfile/helm/flux2apis"
    45  	"github.com/oam-dev/kubevela/pkg/oam"
    46  	"github.com/oam-dev/kubevela/pkg/oam/util"
    47  	verrors "github.com/oam-dev/kubevela/pkg/utils/errors"
    48  	querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
    49  )
    50  
    51  type AppResourcesList struct {
    52  	List []Resource  `json:"list,omitempty"`
    53  	App  interface{} `json:"app"`
    54  	Err  string      `json:"err,omitempty"`
    55  }
    56  
    57  type PodList struct {
    58  	List    []*unstructured.Unstructured `json:"list"`
    59  	Value   interface{}                  `json:"value"`
    60  	Cluster string                       `json:"cluster"`
    61  }
    62  
    63  var _ = Describe("Test Query Provider", func() {
    64  	var baseDeploy *v1.Deployment
    65  	var baseService *corev1.Service
    66  	var basePod *corev1.Pod
    67  
    68  	BeforeEach(func() {
    69  		baseDeploy = new(v1.Deployment)
    70  		Expect(yaml.Unmarshal([]byte(deploymentYaml), baseDeploy)).Should(BeNil())
    71  
    72  		baseService = new(corev1.Service)
    73  		Expect(yaml.Unmarshal([]byte(serviceYaml), baseService)).Should(BeNil())
    74  
    75  		basePod = new(corev1.Pod)
    76  		Expect(yaml.Unmarshal([]byte(podYaml), basePod)).Should(BeNil())
    77  	})
    78  
    79  	Context("Test ListResourcesInApp", func() {
    80  		It("Test list latest resources created by application", func() {
    81  			namespace := "test"
    82  			ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
    83  			Expect(k8sClient.Create(ctx, &ns)).Should(BeNil())
    84  
    85  			app := v1beta1.Application{
    86  				ObjectMeta: metav1.ObjectMeta{
    87  					Name:      "test",
    88  					Namespace: "test",
    89  					Annotations: map[string]string{
    90  						oam.AnnotationKubeVelaVersion: "v1.3.1",
    91  						oam.AnnotationPublishVersion:  "v1",
    92  					},
    93  				},
    94  				Spec: v1beta1.ApplicationSpec{
    95  					Components: []common.ApplicationComponent{{
    96  						Name: "web",
    97  						Type: "webservice",
    98  						Properties: util.Object2RawExtension(map[string]string{
    99  							"image": "busybox",
   100  						}),
   101  						Traits: []common.ApplicationTrait{{
   102  							Type: "expose",
   103  							Properties: util.Object2RawExtension(map[string]interface{}{
   104  								"ports": []int{8000},
   105  							}),
   106  						}},
   107  					}},
   108  				},
   109  			}
   110  
   111  			Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
   112  			oldApp := new(v1beta1.Application)
   113  			Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&app), oldApp)).Should(BeNil())
   114  			oldApp.Status.LatestRevision = &common.Revision{
   115  				Revision: 1,
   116  			}
   117  			oldApp.Status.AppliedResources = []common.ClusterObjectReference{{
   118  				Cluster: "",
   119  				Creator: "workflow",
   120  				ObjectReference: corev1.ObjectReference{
   121  					APIVersion: "v1",
   122  					Kind:       "Service",
   123  					Namespace:  namespace,
   124  					Name:       "web",
   125  				},
   126  			}, {
   127  				Cluster: "",
   128  				Creator: "workflow",
   129  				ObjectReference: corev1.ObjectReference{
   130  					APIVersion: "apps/v1",
   131  					Kind:       "Deployment",
   132  					Namespace:  namespace,
   133  					Name:       "web",
   134  				},
   135  			}}
   136  			Eventually(func() error {
   137  				err := k8sClient.Status().Update(ctx, oldApp)
   138  				if err != nil {
   139  					return err
   140  				}
   141  				return nil
   142  			}, 300*time.Microsecond, 3*time.Second).Should(BeNil())
   143  
   144  			appDeploy := baseDeploy.DeepCopy()
   145  			appDeploy.SetName("web")
   146  			appDeploy.SetNamespace(namespace)
   147  			appDeploy.SetLabels(map[string]string{
   148  				oam.LabelAppComponent: "web",
   149  				oam.LabelAppRevision:  "test-v1",
   150  			})
   151  			Expect(k8sClient.Create(ctx, appDeploy)).Should(BeNil())
   152  
   153  			appService := baseService.DeepCopy()
   154  			appService.SetName("web")
   155  			appService.SetNamespace(namespace)
   156  			appService.SetLabels(map[string]string{
   157  				oam.LabelAppComponent: "web",
   158  				oam.LabelAppRevision:  "test-v1",
   159  			})
   160  			Expect(k8sClient.Create(ctx, appService)).Should(BeNil())
   161  
   162  			rt := &v1beta1.ResourceTracker{
   163  				ObjectMeta: metav1.ObjectMeta{
   164  					Name: fmt.Sprintf("%s-v1-%s", oldApp.Name, oldApp.Namespace),
   165  					Labels: map[string]string{
   166  						oam.LabelAppName:      oldApp.Name,
   167  						oam.LabelAppNamespace: oldApp.Namespace,
   168  					},
   169  					Annotations: map[string]string{
   170  						oam.AnnotationPublishVersion: "v1",
   171  					},
   172  				},
   173  				Spec: v1beta1.ResourceTrackerSpec{
   174  					ManagedResources: []v1beta1.ManagedResource{
   175  						{
   176  							ClusterObjectReference: common.ClusterObjectReference{
   177  								Cluster: "",
   178  								ObjectReference: corev1.ObjectReference{
   179  									APIVersion: "v1",
   180  									Kind:       "Service",
   181  									Namespace:  namespace,
   182  									Name:       "web",
   183  								},
   184  							},
   185  							OAMObjectReference: common.OAMObjectReference{
   186  								Component: "web",
   187  							},
   188  						},
   189  						{
   190  							ClusterObjectReference: common.ClusterObjectReference{
   191  								Cluster: "",
   192  								ObjectReference: corev1.ObjectReference{
   193  									APIVersion: "apps/v1",
   194  									Kind:       "Deployment",
   195  									Namespace:  namespace,
   196  									Name:       "web",
   197  								},
   198  							},
   199  							OAMObjectReference: common.OAMObjectReference{
   200  								Component: "web",
   201  							},
   202  						},
   203  					},
   204  					Type: v1beta1.ResourceTrackerTypeVersioned,
   205  				},
   206  			}
   207  			Expect(k8sClient.Create(ctx, rt)).Should(BeNil())
   208  
   209  			prd := provider{cli: k8sClient}
   210  			opt := `app: {
   211  				name: "test"
   212  				namespace: "test"
   213  				filter: {
   214  					cluster: "",
   215  					clusterNamespace: "test",
   216  					components: ["web"]
   217  				}
   218  			}`
   219  			v, err := value.NewValue(opt, nil, "")
   220  			Expect(err).Should(BeNil())
   221  			logCtx := monitorContext.NewTraceContext(ctx, "")
   222  			Expect(prd.ListResourcesInApp(logCtx, nil, v, nil)).Should(BeNil())
   223  
   224  			appResList := new(AppResourcesList)
   225  			Expect(v.UnmarshalTo(appResList)).Should(BeNil())
   226  			if appResList.Err != "" {
   227  				klog.Error(appResList.Err)
   228  			}
   229  
   230  			Expect(len(appResList.List)).Should(Equal(2))
   231  
   232  			Expect(appResList.List[0].Object.GroupVersionKind()).Should(Equal(oldApp.Status.AppliedResources[0].GroupVersionKind()))
   233  			Expect(appResList.List[1].Object.GroupVersionKind()).Should(Equal(oldApp.Status.AppliedResources[1].GroupVersionKind()))
   234  
   235  			updateApp := new(v1beta1.Application)
   236  			Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(&app), updateApp)).Should(BeNil())
   237  
   238  			updateApp.ObjectMeta.Annotations = map[string]string{
   239  				oam.AnnotationKubeVelaVersion: "v1.1.0",
   240  			}
   241  			Expect(k8sClient.Update(ctx, updateApp)).Should(BeNil())
   242  			newValue, err := value.NewValue(opt, nil, "")
   243  			Expect(err).Should(BeNil())
   244  			Expect(prd.ListResourcesInApp(logCtx, nil, newValue, nil)).Should(BeNil())
   245  			newAppResList := new(AppResourcesList)
   246  			Expect(v.UnmarshalTo(newAppResList)).Should(BeNil())
   247  			Expect(len(newAppResList.List)).Should(Equal(2))
   248  			Expect(newAppResList.List[0].Object.GroupVersionKind()).Should(Equal(updateApp.Status.AppliedResources[0].GroupVersionKind()))
   249  			Expect(newAppResList.List[1].Object.GroupVersionKind()).Should(Equal(updateApp.Status.AppliedResources[1].GroupVersionKind()))
   250  		})
   251  
   252  		It("Test list resource with incomplete parameter", func() {
   253  			optWithoutApp := ""
   254  			prd := provider{cli: k8sClient}
   255  			newV, err := value.NewValue(optWithoutApp, nil, "")
   256  			Expect(err).Should(BeNil())
   257  			logCtx := monitorContext.NewTraceContext(ctx, "")
   258  			err = prd.ListResourcesInApp(logCtx, nil, newV, nil)
   259  			Expect(err).ShouldNot(BeNil())
   260  			Expect(verrors.IsCuePathNotFound(err)).Should(BeTrue())
   261  		})
   262  	})
   263  
   264  	Context("Test ListAppliedResources", func() {
   265  		It("Test list applied resources created by application", func() {
   266  			// create test app
   267  			app := v1beta1.Application{
   268  				ObjectMeta: metav1.ObjectMeta{
   269  					Name:      "test-applied",
   270  					Namespace: "default",
   271  				},
   272  				Spec: v1beta1.ApplicationSpec{
   273  					Components: []common.ApplicationComponent{{
   274  						Name: "web",
   275  						Type: "webservice",
   276  						Properties: util.Object2RawExtension(map[string]string{
   277  							"image": "busybox",
   278  						}),
   279  						Traits: []common.ApplicationTrait{{
   280  							Type: "expose",
   281  							Properties: util.Object2RawExtension(map[string]interface{}{
   282  								"ports": []int{8000},
   283  							}),
   284  						}},
   285  					}},
   286  				},
   287  			}
   288  			Expect(k8sClient.Create(ctx, &app)).Should(BeNil())
   289  			// create RT
   290  			rt := &v1beta1.ResourceTracker{
   291  				ObjectMeta: metav1.ObjectMeta{
   292  					Name:      "test-applied",
   293  					Namespace: "default",
   294  					Labels: map[string]string{
   295  						oam.LabelAppName:      app.Name,
   296  						oam.LabelAppNamespace: app.Namespace,
   297  					},
   298  				},
   299  				Spec: v1beta1.ResourceTrackerSpec{
   300  					Type: v1beta1.ResourceTrackerTypeRoot,
   301  					ManagedResources: []v1beta1.ManagedResource{
   302  						{
   303  							ClusterObjectReference: common.ClusterObjectReference{
   304  								Cluster: "",
   305  								ObjectReference: corev1.ObjectReference{
   306  									Kind:       "Deployment",
   307  									APIVersion: "apps/v1",
   308  									Namespace:  "default",
   309  									Name:       "web",
   310  								},
   311  							},
   312  							OAMObjectReference: common.OAMObjectReference{
   313  								Component: "web",
   314  							},
   315  						},
   316  						{
   317  							ClusterObjectReference: common.ClusterObjectReference{
   318  								Cluster: "",
   319  								ObjectReference: corev1.ObjectReference{
   320  									Kind:       "Service",
   321  									APIVersion: "v1",
   322  									Namespace:  "default",
   323  									Name:       "web",
   324  								},
   325  							},
   326  							OAMObjectReference: common.OAMObjectReference{
   327  								Trait:     "expose",
   328  								Component: "web",
   329  							},
   330  						},
   331  					},
   332  				},
   333  			}
   334  			err := k8sClient.Create(context.TODO(), rt)
   335  			Expect(err).Should(BeNil())
   336  			prd := provider{cli: k8sClient}
   337  			opt := `app: {
   338  				name: "test-applied"
   339  				namespace: "default"
   340  				filter: {
   341  					components: ["web"]
   342  				}
   343  			}`
   344  			v, err := value.NewValue(opt, nil, "")
   345  			Expect(err).Should(BeNil())
   346  			logCtx := monitorContext.NewTraceContext(ctx, "")
   347  			Expect(prd.ListAppliedResources(logCtx, nil, v, nil)).Should(BeNil())
   348  			type Res struct {
   349  				List []v1beta1.ManagedResource `json:"list"`
   350  			}
   351  			var res Res
   352  			err = v.UnmarshalTo(&res)
   353  			Expect(err).Should(BeNil())
   354  			Expect(len(res.List)).Should(Equal(2))
   355  
   356  			By("test filter with the apiVersion and kind")
   357  			optWithVersion := `app: {
   358  				name: "test-applied"
   359  				namespace: "default"
   360  				filter: {
   361  					components: ["web"]
   362  					apiVersion: "apps/v1"
   363  				}
   364  			}`
   365  			valueWithVersion, err := value.NewValue(optWithVersion, nil, "")
   366  			Expect(err).Should(BeNil())
   367  			Expect(prd.ListAppliedResources(logCtx, nil, valueWithVersion, nil)).Should(BeNil())
   368  			var res2 Res
   369  			err = valueWithVersion.UnmarshalTo(&res2)
   370  			Expect(err).Should(BeNil())
   371  			Expect(len(res2.List)).Should(Equal(1))
   372  			Expect(res2.List[0].Kind).Should(Equal("Deployment"))
   373  
   374  			optWithKind := `app: {
   375  				name: "test-applied"
   376  				namespace: "default"
   377  				filter: {
   378  					components: ["web"]
   379  					kind: "Service"
   380  				}
   381  			}`
   382  			valueWithKind, err := value.NewValue(optWithKind, nil, "")
   383  			Expect(err).Should(BeNil())
   384  			Expect(prd.ListAppliedResources(logCtx, nil, valueWithKind, nil)).Should(BeNil())
   385  			var res3 Res
   386  			err = valueWithKind.UnmarshalTo(&res3)
   387  			Expect(err).Should(BeNil())
   388  			Expect(len(res3.List)).Should(Equal(1))
   389  			Expect(res3.List[0].Kind).Should(Equal("Service"))
   390  		})
   391  	})
   392  
   393  	Context("Test search event from k8s object", func() {
   394  		It("Test search event with incomplete parameter", func() {
   395  			emptyOpt := ""
   396  			prd := provider{cli: k8sClient}
   397  			v, err := value.NewValue(emptyOpt, nil, "")
   398  			Expect(err).Should(BeNil())
   399  			logCtx := monitorContext.NewTraceContext(ctx, "")
   400  			err = prd.SearchEvents(logCtx, nil, v, nil)
   401  			Expect(err).ShouldNot(BeNil())
   402  			Expect(verrors.IsCuePathNotFound(err)).Should(BeTrue())
   403  
   404  			optWithoutCluster := `value: {}`
   405  			v, err = value.NewValue(optWithoutCluster, nil, "")
   406  			Expect(err).Should(BeNil())
   407  			err = prd.SearchEvents(logCtx, nil, v, nil)
   408  			Expect(err).ShouldNot(BeNil())
   409  			Expect(verrors.IsCuePathNotFound(err)).Should(BeTrue())
   410  
   411  			optWithWrongValue := `value: {}
   412  cluster: "test"`
   413  			v, err = value.NewValue(optWithWrongValue, nil, "")
   414  			Expect(err).Should(BeNil())
   415  			err = prd.SearchEvents(logCtx, nil, v, nil)
   416  			Expect(err).ShouldNot(BeNil())
   417  		})
   418  	})
   419  
   420  	Context("Test CollectLogsInPod", func() {
   421  		It("Test CollectLogsInPod with specified container", func() {
   422  			prd := provider{cli: k8sClient, cfg: cfg}
   423  			pod := &corev1.Pod{
   424  				ObjectMeta: metav1.ObjectMeta{Name: "hello-world", Namespace: "default"},
   425  				Spec: corev1.PodSpec{
   426  					Containers: []corev1.Container{{Name: "main", Image: "busybox"}},
   427  				}}
   428  			Expect(k8sClient.Create(ctx, pod)).Should(Succeed())
   429  			logCtx := monitorContext.NewTraceContext(ctx, "")
   430  
   431  			v, err := value.NewValue(``, nil, "")
   432  			Expect(err).Should(Succeed())
   433  			err = prd.CollectLogsInPod(logCtx, nil, v, nil)
   434  			Expect(err).ShouldNot(BeNil())
   435  			Expect(verrors.IsCuePathNotFound(err)).Should(BeTrue())
   436  
   437  			v, err = value.NewValue(`cluster: "local"`, nil, "")
   438  			Expect(err).Should(Succeed())
   439  			err = prd.CollectLogsInPod(logCtx, nil, v, nil)
   440  			Expect(err).ShouldNot(BeNil())
   441  			Expect(verrors.IsCuePathNotFound(err)).Should(BeTrue())
   442  
   443  			v, err = value.NewValue(`cluster: "local"
   444  namespace: "default"`, nil, "")
   445  			Expect(err).Should(Succeed())
   446  			err = prd.CollectLogsInPod(logCtx, nil, v, nil)
   447  			Expect(err).ShouldNot(BeNil())
   448  			Expect(verrors.IsCuePathNotFound(err)).Should(BeTrue())
   449  
   450  			v, err = value.NewValue(`cluster: "local"
   451  namespace: "default"
   452  pod: "hello-world"`, nil, "")
   453  			Expect(err).Should(Succeed())
   454  			err = prd.CollectLogsInPod(logCtx, nil, v, nil)
   455  			Expect(err).ShouldNot(BeNil())
   456  			Expect(verrors.IsCuePathNotFound(err)).Should(BeTrue())
   457  
   458  			v, err = value.NewValue(`cluster: "local"
   459  namespace: "default"
   460  pod: "hello-world"
   461  options: {
   462    container: 1
   463  }`, nil, "")
   464  			Expect(err).Should(Succeed())
   465  			err = prd.CollectLogsInPod(logCtx, nil, v, nil)
   466  			Expect(err).ShouldNot(BeNil())
   467  			Expect(err.Error()).Should(ContainSubstring("invalid log options content"))
   468  
   469  			v, err = value.NewValue(`cluster: "local"
   470  namespace: "default"
   471  pod: "hello-world"
   472  options: {
   473    container: "main"
   474    previous: true
   475    sinceSeconds: 100
   476    tailLines: 50
   477  }`, nil, "")
   478  			Expect(err).Should(Succeed())
   479  			Expect(prd.CollectLogsInPod(logCtx, nil, v, nil)).Should(Succeed())
   480  			_, err = v.GetString("outputs", "logs")
   481  			Expect(err).Should(Succeed())
   482  		})
   483  	})
   484  
   485  	It("Test install provider", func() {
   486  		p := providers.NewProviders()
   487  		Install(p, k8sClient, cfg)
   488  		h, ok := p.GetHandler("query", "listResourcesInApp")
   489  		Expect(h).ShouldNot(BeNil())
   490  		Expect(ok).Should(Equal(true))
   491  		h, ok = p.GetHandler("query", "collectResources")
   492  		Expect(h).ShouldNot(BeNil())
   493  		Expect(ok).Should(Equal(true))
   494  		l, ok := p.GetHandler("query", "listAppliedResources")
   495  		Expect(l).ShouldNot(BeNil())
   496  		Expect(ok).Should(Equal(true))
   497  		h, ok = p.GetHandler("query", "searchEvents")
   498  		Expect(ok).Should(Equal(true))
   499  		Expect(h).ShouldNot(BeNil())
   500  		h, ok = p.GetHandler("query", "collectLogsInPod")
   501  		Expect(ok).Should(Equal(true))
   502  		Expect(h).ShouldNot(BeNil())
   503  		h, ok = p.GetHandler("query", "collectServiceEndpoints")
   504  		Expect(ok).Should(Equal(true))
   505  		Expect(h).ShouldNot(BeNil())
   506  	})
   507  
   508  	It("Test generator service endpoints", func() {
   509  		appsts := common.AppStatus{
   510  			AppliedResources: []common.ClusterObjectReference{
   511  				{
   512  					Cluster: "",
   513  					ObjectReference: corev1.ObjectReference{
   514  						Kind:       "Ingress",
   515  						Namespace:  "default",
   516  						Name:       "ingress-http",
   517  						APIVersion: "networking.k8s.io/v1",
   518  					},
   519  				},
   520  				{
   521  					Cluster: "",
   522  					ObjectReference: corev1.ObjectReference{
   523  						Kind:       "Ingress",
   524  						Namespace:  "default",
   525  						Name:       "ingress-https",
   526  						APIVersion: "networking.k8s.io/v1",
   527  					},
   528  				},
   529  				{
   530  					Cluster: "",
   531  					ObjectReference: corev1.ObjectReference{
   532  						Kind:       "Ingress",
   533  						Namespace:  "default",
   534  						Name:       "ingress-paths",
   535  						APIVersion: "networking.k8s.io/v1",
   536  					},
   537  				},
   538  				{
   539  					Cluster: "",
   540  					ObjectReference: corev1.ObjectReference{
   541  						APIVersion: "v1",
   542  						Kind:       "Service",
   543  						Namespace:  "default",
   544  						Name:       "nodeport",
   545  					},
   546  				},
   547  				{
   548  					Cluster: "",
   549  					ObjectReference: corev1.ObjectReference{
   550  						APIVersion: "v1",
   551  						Kind:       "Service",
   552  						Namespace:  "default",
   553  						Name:       "loadbalancer",
   554  					},
   555  				},
   556  				{
   557  					Cluster: "",
   558  					ObjectReference: corev1.ObjectReference{
   559  						APIVersion: "helm.toolkit.fluxcd.io/v2beta1",
   560  						Kind:       helmapi.HelmReleaseGVK.Kind,
   561  						Namespace:  "default",
   562  						Name:       "helm-release",
   563  					},
   564  				},
   565  				{
   566  					Cluster: "",
   567  					ObjectReference: corev1.ObjectReference{
   568  						APIVersion: "machinelearning.seldon.io/v1",
   569  						Kind:       "SeldonDeployment",
   570  						Namespace:  "default",
   571  						Name:       "sdep",
   572  					},
   573  				},
   574  				{
   575  					Cluster: "",
   576  					ObjectReference: corev1.ObjectReference{
   577  						APIVersion: "gateway.networking.k8s.io/v1beta1",
   578  						Kind:       "HTTPRoute",
   579  						Namespace:  "default",
   580  						Name:       "http-test-route",
   581  					},
   582  				},
   583  				{
   584  					Cluster: "",
   585  					ObjectReference: corev1.ObjectReference{
   586  						APIVersion: "gateway.networking.k8s.io/v1beta1",
   587  						Kind:       "HTTPRoute",
   588  						Namespace:  "default",
   589  						Name:       "velaux-ssl",
   590  					},
   591  				},
   592  			},
   593  		}
   594  		testApp := &v1beta1.Application{
   595  			ObjectMeta: metav1.ObjectMeta{
   596  				Name:      "endpoints-app",
   597  				Namespace: "default",
   598  			},
   599  			Spec: v1beta1.ApplicationSpec{
   600  				Components: []common.ApplicationComponent{
   601  					{
   602  						Name: "endpoints-test",
   603  						Type: "webservice",
   604  					},
   605  				},
   606  			},
   607  			Status: appsts,
   608  		}
   609  		err := k8sClient.Create(context.TODO(), testApp)
   610  		Expect(err).Should(BeNil())
   611  
   612  		var gtapp v1beta1.Application
   613  		Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: "endpoints-app", Namespace: "default"}, &gtapp)).Should(BeNil())
   614  		gtapp.Status = appsts
   615  		Expect(k8sClient.Status().Update(ctx, &gtapp)).Should(BeNil())
   616  		var mr []v1beta1.ManagedResource
   617  		for _, ar := range appsts.AppliedResources {
   618  			smr := v1beta1.ManagedResource{
   619  				ClusterObjectReference: ar,
   620  			}
   621  			smr.Component = "endpoints-test"
   622  			mr = append(mr, smr)
   623  		}
   624  		rt := &v1beta1.ResourceTracker{
   625  			ObjectMeta: metav1.ObjectMeta{
   626  				Name:      "endpoints-app",
   627  				Namespace: "default",
   628  				Labels: map[string]string{
   629  					oam.LabelAppName:      testApp.Name,
   630  					oam.LabelAppNamespace: testApp.Namespace,
   631  				},
   632  			},
   633  			Spec: v1beta1.ResourceTrackerSpec{
   634  				Type:             v1beta1.ResourceTrackerTypeRoot,
   635  				ManagedResources: mr,
   636  			},
   637  		}
   638  		err = k8sClient.Create(context.TODO(), rt)
   639  		Expect(err).Should(BeNil())
   640  
   641  		helmRelease := &unstructured.Unstructured{}
   642  		helmRelease.SetName("helm-release")
   643  		helmRelease.SetNamespace("default")
   644  		helmRelease.SetGroupVersionKind(helmapi.HelmReleaseGVK)
   645  		err = k8sClient.Create(context.TODO(), helmRelease)
   646  		Expect(err).Should(BeNil())
   647  
   648  		testServiceList := []map[string]interface{}{
   649  			{
   650  				"name": "clusterip",
   651  				"ports": []corev1.ServicePort{
   652  					{Port: 80, TargetPort: intstr.FromInt(80), Name: "80port"},
   653  					{Port: 81, TargetPort: intstr.FromInt(81), Name: "81port"},
   654  				},
   655  				"type": corev1.ServiceTypeClusterIP,
   656  			},
   657  			{
   658  				"name": "nodeport",
   659  				"ports": []corev1.ServicePort{
   660  					{Port: 80, TargetPort: intstr.FromInt(80), NodePort: 30229},
   661  				},
   662  				"type": corev1.ServiceTypeNodePort,
   663  			},
   664  			{
   665  				"name": "loadbalancer",
   666  				"ports": []corev1.ServicePort{
   667  					{Port: 80, TargetPort: intstr.FromInt(80), Name: "80port", NodePort: 30080},
   668  					{Port: 81, TargetPort: intstr.FromInt(81), Name: "81port", NodePort: 30081},
   669  				},
   670  				"type": corev1.ServiceTypeLoadBalancer,
   671  				"status": corev1.ServiceStatus{
   672  					LoadBalancer: corev1.LoadBalancerStatus{
   673  						Ingress: []corev1.LoadBalancerIngress{
   674  							{
   675  								IP: "10.10.10.10",
   676  							},
   677  							{
   678  								Hostname: "text.example.com",
   679  							},
   680  						},
   681  					},
   682  				},
   683  			},
   684  			{
   685  				"name": "helm1",
   686  				"ports": []corev1.ServicePort{
   687  					{Port: 80, NodePort: 30002, TargetPort: intstr.FromInt(80)},
   688  				},
   689  				"type": corev1.ServiceTypeNodePort,
   690  				"labels": map[string]string{
   691  					"helm.toolkit.fluxcd.io/name":      "helm-release",
   692  					"helm.toolkit.fluxcd.io/namespace": "default",
   693  				},
   694  			},
   695  			{
   696  				"name": "seldon-ambassador",
   697  				"ports": []corev1.ServicePort{
   698  					{Port: 80, TargetPort: intstr.FromInt(80), Name: "80port", NodePort: 30011},
   699  				},
   700  				"type": corev1.ServiceTypeLoadBalancer,
   701  				"status": corev1.ServiceStatus{
   702  					LoadBalancer: corev1.LoadBalancerStatus{
   703  						Ingress: []corev1.LoadBalancerIngress{
   704  							{
   705  								IP: "1.1.1.1",
   706  							},
   707  						},
   708  					},
   709  				},
   710  			},
   711  		}
   712  		err = k8sClient.Create(context.TODO(), &corev1.Namespace{
   713  			ObjectMeta: metav1.ObjectMeta{
   714  				Name: "vela-system",
   715  			},
   716  		})
   717  		Expect(err).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
   718  		for _, s := range testServiceList {
   719  			ns := "default"
   720  			if s["namespace"] != nil {
   721  				ns = s["namespace"].(string)
   722  			}
   723  			service := &corev1.Service{
   724  				ObjectMeta: metav1.ObjectMeta{
   725  					Name:      s["name"].(string),
   726  					Namespace: ns,
   727  				},
   728  				Spec: corev1.ServiceSpec{
   729  					Ports: s["ports"].([]corev1.ServicePort),
   730  					Type:  s["type"].(corev1.ServiceType),
   731  				},
   732  			}
   733  
   734  			if s["labels"] != nil {
   735  				service.Labels = s["labels"].(map[string]string)
   736  			}
   737  			err := k8sClient.Create(context.TODO(), service)
   738  			Expect(err).Should(BeNil())
   739  			if s["status"] != nil {
   740  				service.Status = s["status"].(corev1.ServiceStatus)
   741  				err := k8sClient.Status().Update(context.TODO(), service)
   742  				Expect(err).Should(BeNil())
   743  			}
   744  		}
   745  
   746  		var prefixbeta = networkv1.PathTypePrefix
   747  		testIngress := []client.Object{
   748  			&networkv1.Ingress{
   749  				ObjectMeta: metav1.ObjectMeta{
   750  					Name:      "ingress-http",
   751  					Namespace: "default",
   752  				},
   753  				Spec: networkv1.IngressSpec{
   754  					Rules: []networkv1.IngressRule{
   755  						{
   756  							Host: "ingress.domain",
   757  							IngressRuleValue: networkv1.IngressRuleValue{
   758  								HTTP: &networkv1.HTTPIngressRuleValue{
   759  									Paths: []networkv1.HTTPIngressPath{
   760  										{
   761  											Path: "/",
   762  											Backend: networkv1.IngressBackend{
   763  												Service: &networkv1.IngressServiceBackend{
   764  													Name: "clusterip",
   765  													Port: networkv1.ServiceBackendPort{
   766  														Number: 80,
   767  													},
   768  												},
   769  											},
   770  											PathType: &prefixbeta,
   771  										},
   772  									},
   773  								},
   774  							},
   775  						},
   776  					},
   777  				},
   778  			},
   779  			&networkv1.Ingress{
   780  				ObjectMeta: metav1.ObjectMeta{
   781  					Name:      "ingress-https",
   782  					Namespace: "default",
   783  				},
   784  				Spec: networkv1.IngressSpec{
   785  					TLS: []networkv1.IngressTLS{
   786  						{
   787  							SecretName: "https-secret",
   788  						},
   789  					},
   790  					Rules: []networkv1.IngressRule{
   791  						{
   792  							Host: "ingress.domain.https",
   793  							IngressRuleValue: networkv1.IngressRuleValue{
   794  								HTTP: &networkv1.HTTPIngressRuleValue{
   795  									Paths: []networkv1.HTTPIngressPath{
   796  										{
   797  											Path: "/",
   798  											Backend: networkv1.IngressBackend{
   799  												Service: &networkv1.IngressServiceBackend{
   800  													Name: "clusterip",
   801  													Port: networkv1.ServiceBackendPort{
   802  														Number: 80,
   803  													},
   804  												},
   805  											},
   806  											PathType: &prefixbeta,
   807  										},
   808  									},
   809  								},
   810  							},
   811  						},
   812  					},
   813  				},
   814  			},
   815  			&networkv1.Ingress{
   816  				ObjectMeta: metav1.ObjectMeta{
   817  					Name:      "ingress-paths",
   818  					Namespace: "default",
   819  				},
   820  				Spec: networkv1.IngressSpec{
   821  					TLS: []networkv1.IngressTLS{
   822  						{
   823  							SecretName: "https-secret",
   824  						},
   825  					},
   826  					Rules: []networkv1.IngressRule{
   827  						{
   828  							Host: "ingress.domain.path",
   829  							IngressRuleValue: networkv1.IngressRuleValue{
   830  								HTTP: &networkv1.HTTPIngressRuleValue{
   831  									Paths: []networkv1.HTTPIngressPath{
   832  										{
   833  											Path: "/test",
   834  											Backend: networkv1.IngressBackend{
   835  												Service: &networkv1.IngressServiceBackend{
   836  													Name: "clusterip",
   837  													Port: networkv1.ServiceBackendPort{
   838  														Number: 80,
   839  													},
   840  												},
   841  											},
   842  											PathType: &prefixbeta,
   843  										},
   844  										{
   845  											Path: "/test2",
   846  											Backend: networkv1.IngressBackend{
   847  												Service: &networkv1.IngressServiceBackend{
   848  													Name: "clusterip",
   849  													Port: networkv1.ServiceBackendPort{
   850  														Number: 80,
   851  													},
   852  												},
   853  											},
   854  											PathType: &prefixbeta,
   855  										},
   856  									},
   857  								},
   858  							},
   859  						},
   860  					},
   861  				},
   862  			},
   863  			&networkv1.Ingress{
   864  				TypeMeta: metav1.TypeMeta{
   865  					APIVersion: "networking.k8s.io/v1beta1",
   866  				},
   867  				ObjectMeta: metav1.ObjectMeta{
   868  					Name:      "ingress-helm",
   869  					Namespace: "default",
   870  					Labels: map[string]string{
   871  						"helm.toolkit.fluxcd.io/name":      "helm-release",
   872  						"helm.toolkit.fluxcd.io/namespace": "default",
   873  					},
   874  				},
   875  				Spec: networkv1.IngressSpec{
   876  					Rules: []networkv1.IngressRule{
   877  						{
   878  							Host: "ingress.domain.helm",
   879  							IngressRuleValue: networkv1.IngressRuleValue{
   880  								HTTP: &networkv1.HTTPIngressRuleValue{
   881  									Paths: []networkv1.HTTPIngressPath{
   882  										{
   883  											Path: "/",
   884  											Backend: networkv1.IngressBackend{
   885  												Service: &networkv1.IngressServiceBackend{
   886  													Name: "clusterip",
   887  													Port: networkv1.ServiceBackendPort{
   888  														Number: 80,
   889  													},
   890  												},
   891  											},
   892  											PathType: &prefixbeta,
   893  										},
   894  									},
   895  								},
   896  							},
   897  						},
   898  					},
   899  				},
   900  			},
   901  		}
   902  
   903  		for _, ing := range testIngress {
   904  			err := k8sClient.Create(context.TODO(), ing)
   905  			Expect(err).Should(BeNil())
   906  		}
   907  
   908  		obj := &unstructured.Unstructured{}
   909  		obj.SetName("sdep")
   910  		obj.SetNamespace("default")
   911  		obj.SetAnnotations(map[string]string{
   912  			annoAmbassadorServiceName:      "seldon-ambassador",
   913  			annoAmbassadorServiceNamespace: "default",
   914  		})
   915  		obj.SetGroupVersionKind(schema.GroupVersionKind{
   916  			Group:   "machinelearning.seldon.io",
   917  			Version: "v1",
   918  			Kind:    "SeldonDeployment",
   919  		})
   920  		err = k8sClient.Create(context.TODO(), obj)
   921  		Expect(err).Should(BeNil())
   922  
   923  		// Create the HTTPRoute for test
   924  		resources := []string{
   925  			"./testdata/gateway/http-route.yaml",
   926  			"./testdata/gateway/gateway.yaml",
   927  			"./testdata/gateway/gateway-tls.yaml",
   928  			"./testdata/gateway/https-route.yaml",
   929  		}
   930  		var objects []client.Object
   931  		for _, resource := range resources {
   932  			data, err := os.ReadFile(resource)
   933  			Expect(err).Should(BeNil())
   934  			var route unstructured.Unstructured
   935  			err = yaml.Unmarshal(data, &route)
   936  			Expect(err).Should(BeNil())
   937  			objects = append(objects, &route)
   938  		}
   939  
   940  		for _, res := range objects {
   941  			err := k8sClient.Create(context.TODO(), res)
   942  			Expect(err).Should(BeNil())
   943  		}
   944  
   945  		// Prepare nodes in test environment
   946  		masterNode := &corev1.Node{
   947  			ObjectMeta: metav1.ObjectMeta{
   948  				Name: "node-1",
   949  				Labels: map[string]string{
   950  					"node-role.kubernetes.io/master": "true",
   951  				},
   952  			},
   953  			Status: corev1.NodeStatus{
   954  				Addresses: []corev1.NodeAddress{
   955  					{
   956  						Type:    corev1.NodeInternalIP,
   957  						Address: "internal-ip-1",
   958  					},
   959  				},
   960  			},
   961  		}
   962  		workerNode := &corev1.Node{
   963  			ObjectMeta: metav1.ObjectMeta{
   964  				Name: "node-2",
   965  				Labels: map[string]string{
   966  					"node-role.kubernetes.io/worker": "true",
   967  				},
   968  			},
   969  			Status: corev1.NodeStatus{
   970  				Addresses: []corev1.NodeAddress{
   971  					{
   972  						Type:    corev1.NodeInternalIP,
   973  						Address: "internal-ip-2",
   974  					},
   975  					{
   976  						Type:    corev1.NodeExternalIP,
   977  						Address: "external-ip-2",
   978  					},
   979  				},
   980  			},
   981  		}
   982  		Expect(k8sClient.Create(ctx, masterNode)).Should(BeNil())
   983  		Expect(k8sClient.Create(ctx, workerNode)).Should(BeNil())
   984  
   985  		opt := `app: {
   986  			name: "endpoints-app"
   987  			namespace: "default"
   988  			filter: {
   989  				cluster: "",
   990  				clusterNamespace: "default",
   991  			}
   992  			withTree: true
   993  		}`
   994  		v, err := value.NewValue(opt, nil, "")
   995  		Expect(err).Should(BeNil())
   996  		pr := &provider{
   997  			cli: k8sClient,
   998  		}
   999  		logCtx := monitorContext.NewTraceContext(ctx, "")
  1000  		err = pr.CollectServiceEndpoints(logCtx, nil, v, nil)
  1001  		Expect(err).Should(BeNil())
  1002  		gatewayIP := selectorNodeIP(ctx, "", k8sClient)
  1003  		Expect(gatewayIP).Should(Equal("external-ip-2"))
  1004  		urls := []string{
  1005  			"http://ingress.domain",
  1006  			"https://ingress.domain.https",
  1007  			"https://ingress.domain.path/test",
  1008  			"https://ingress.domain.path/test2",
  1009  			fmt.Sprintf("http://%s:30229", gatewayIP),
  1010  			"http://10.10.10.10",
  1011  			"http://text.example.com",
  1012  			"10.10.10.10:81",
  1013  			"text.example.com:81",
  1014  			fmt.Sprintf("http://%s:30002", gatewayIP),
  1015  			"http://ingress.domain.helm",
  1016  			"http://1.1.1.1/seldon/default/sdep",
  1017  			"http://gateway.domain",
  1018  			"http://gateway.domain/api",
  1019  			"https://demo.kubevela.net",
  1020  		}
  1021  		endValue, err := v.Field("list")
  1022  		Expect(err).Should(BeNil())
  1023  		var endpoints []querytypes.ServiceEndpoint
  1024  		err = endValue.Decode(&endpoints)
  1025  		Expect(err).Should(BeNil())
  1026  		Expect(len(urls)).Should(Equal(len(endpoints)))
  1027  		for i, e := range endpoints {
  1028  			fmt.Println(e.String())
  1029  			Expect(urls[i]).Should(Equal(e.String()))
  1030  		}
  1031  	})
  1032  })
  1033  
  1034  var deploymentYaml = `
  1035  apiVersion: apps/v1
  1036  kind: Deployment
  1037  metadata:
  1038    labels:
  1039      app.oam.dev/app-revision-hash: ee69f7ed168cd8fa
  1040      app.oam.dev/appRevision: first-vela-app-v1
  1041      app.oam.dev/component: express-server
  1042      app.oam.dev/name: first-vela-app
  1043      app.oam.dev/resourceType: WORKLOAD
  1044      app.oam.dev/revision: express-server-v1
  1045      oam.dev/render-hash: ee2d39b553b6ef03
  1046      workload.oam.dev/type: webservice
  1047    name: express-server
  1048    namespace: default
  1049  spec:
  1050    replicas: 2
  1051    selector:
  1052      matchLabels:
  1053        app.oam.dev/component: express-server
  1054    template:
  1055      metadata:
  1056        labels:
  1057          app.oam.dev/component: express-server
  1058      spec:
  1059        containers:
  1060        - image: crccheck/hello-world
  1061          imagePullPolicy: Always
  1062          name: express-server
  1063          ports:
  1064          - containerPort: 8000
  1065            protocol: TCP
  1066  `
  1067  
  1068  var serviceYaml = `
  1069  apiVersion: v1
  1070  kind: Service
  1071  metadata:
  1072    labels:
  1073      app.oam.dev/app-revision-hash: ee69f7ed168cd8fa
  1074      app.oam.dev/appRevision: first-vela-app-v1
  1075      app.oam.dev/component: express-server
  1076      app.oam.dev/name: first-vela-app
  1077      app.oam.dev/resourceType: TRAIT
  1078      app.oam.dev/revision: express-server-v1
  1079      oam.dev/render-hash: bebe99ac3e9607d0
  1080      trait.oam.dev/resource: service
  1081      trait.oam.dev/type: ingress-1-20
  1082    name: express-server
  1083    namespace: default
  1084  spec:
  1085    ports:
  1086    - port: 8000
  1087      protocol: TCP
  1088      targetPort: 8000
  1089    selector:
  1090      app.oam.dev/component: express-server
  1091  `
  1092  
  1093  var podYaml = `
  1094  apiVersion: v1
  1095  kind: Pod
  1096  metadata:
  1097    labels:
  1098      app.oam.dev/component: express-server
  1099    name: express-server-b77f4476b-4mt5m
  1100    namespace: default
  1101  spec:
  1102    containers:
  1103    - image: crccheck/hello-world
  1104      imagePullPolicy: Always
  1105      name: express-server-1
  1106      ports:
  1107      - containerPort: 8000
  1108        protocol: TCP
  1109  `