github.com/kiali/kiali@v1.84.0/graph/api/api_test.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"os"
    12  	"runtime"
    13  	"testing"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"github.com/gorilla/mux"
    17  	"github.com/prometheus/common/model"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/mock"
    20  	core_v1 "k8s.io/api/core/v1"
    21  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  	"k8s.io/client-go/tools/clientcmd/api"
    23  
    24  	"github.com/kiali/kiali/business"
    25  	"github.com/kiali/kiali/business/authentication"
    26  	"github.com/kiali/kiali/config"
    27  	"github.com/kiali/kiali/graph"
    28  	"github.com/kiali/kiali/kubernetes"
    29  	"github.com/kiali/kiali/kubernetes/cache"
    30  	"github.com/kiali/kiali/kubernetes/kubetest"
    31  	"github.com/kiali/kiali/prometheus"
    32  	"github.com/kiali/kiali/prometheus/prometheustest"
    33  )
    34  
    35  // Setup mock
    36  
    37  func setupMocked(t *testing.T) (*prometheus.Client, *prometheustest.PromAPIMock) {
    38  	conf := config.NewConfig()
    39  	conf.KubernetesConfig.ClusterName = "east"
    40  	config.Set(conf)
    41  
    42  	k8s := kubetest.NewFakeK8sClient(
    43  		&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "bookinfo"}},
    44  		&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "tutorial"}},
    45  	)
    46  
    47  	api := new(prometheustest.PromAPIMock)
    48  	client, err := prometheus.NewClient()
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	client.Inject(api)
    53  
    54  	mockClientFactory := kubetest.NewK8SClientFactoryMock(k8s)
    55  	business.SetWithBackends(mockClientFactory, nil)
    56  	cache := cache.NewTestingCache(t, k8s, *conf)
    57  	business.WithKialiCache(cache)
    58  
    59  	return client, api
    60  }
    61  
    62  // firstKey returns the first key from the map.
    63  // Useful when you don't care about ordering.
    64  // Empty map returns empty K value.
    65  func firstKey[K comparable, V any](m map[K]V) K {
    66  	var k K
    67  	for k = range m {
    68  		break
    69  	}
    70  	return k
    71  }
    72  
    73  func setupMockedWithIstioComponentNamespaces(t *testing.T, meshId string, userClients map[string]kubernetes.ClientInterface) (*prometheus.Client, *prometheustest.PromAPIMock, error) {
    74  	testConfig := config.NewConfig()
    75  	testConfig.KubernetesConfig.ClusterName = firstKey(userClients)
    76  	if meshId != "" {
    77  		testConfig.ExternalServices.Prometheus.QueryScope = map[string]string{"mesh_id": meshId}
    78  	}
    79  	config.Set(testConfig)
    80  	fmt.Println("!!! Set up complex mock")
    81  
    82  	api := new(prometheustest.PromAPIMock)
    83  	client, err := prometheus.NewClient()
    84  	if err != nil {
    85  		return nil, nil, err
    86  	}
    87  	client.Inject(api)
    88  
    89  	mockClientFactory := kubetest.NewK8SClientFactoryMock(nil)
    90  	mockClientFactory.SetClients(userClients)
    91  
    92  	cache := cache.NewTestingCacheWithFactory(t, mockClientFactory, *testConfig)
    93  
    94  	business.WithKialiCache(cache)
    95  	business.SetWithBackends(mockClientFactory, nil)
    96  
    97  	return client, api, nil
    98  }
    99  
   100  func mockQuery(api *prometheustest.PromAPIMock, query string, ret *model.Vector) {
   101  	api.On(
   102  		"Query",
   103  		mock.AnythingOfType("*context.emptyCtx"),
   104  		query,
   105  		mock.AnythingOfType("time.Time"),
   106  	).Return(*ret, nil)
   107  	api.On(
   108  		"Query",
   109  		mock.AnythingOfType("*context.cancelCtx"),
   110  		query,
   111  		mock.AnythingOfType("time.Time"),
   112  	).Return(*ret, nil)
   113  }
   114  
   115  // mockNamespaceGraph provides the same single-namespace mocks to be used for different graph types
   116  func mockNamespaceGraph(t *testing.T) (*prometheus.Client, *prometheustest.PromAPIMock, error) {
   117  	q0 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
   118  	v0 := model.Vector{}
   119  
   120  	q1 := `round(sum(rate(istio_requests_total{reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
   121  	q1m0 := model.Metric{
   122  		"source_workload_namespace":      "istio-system",
   123  		"source_workload":                "ingressgateway-unknown",
   124  		"source_canonical_service":       "ingressgateway",
   125  		"source_canonical_revision":      "latest",
   126  		"source_cluster":                 "east",
   127  		"destination_cluster":            "east",
   128  		"destination_service_namespace":  "bookinfo",
   129  		"destination_service":            "productpage:9080",
   130  		"destination_service_name":       "productpage",
   131  		"destination_workload_namespace": "bookinfo",
   132  		"destination_workload":           "productpage-v1",
   133  		"destination_canonical_service":  "productpage",
   134  		"destination_canonical_revision": "v1",
   135  		"request_protocol":               "http",
   136  		"response_code":                  "200",
   137  		"grpc_response_status":           "0",
   138  		"response_flags":                 "-",
   139  	}
   140  	q1m1 := model.Metric{
   141  		"source_workload_namespace":      "unknown",
   142  		"source_workload":                "unknown",
   143  		"source_canonical_service":       "unknown",
   144  		"source_canonical_revision":      "unknown",
   145  		"source_cluster":                 "unknown",
   146  		"destination_cluster":            "east",
   147  		"destination_service_namespace":  "bookinfo",
   148  		"destination_service":            "productpage:9080",
   149  		"destination_service_name":       "productpage",
   150  		"destination_workload_namespace": "bookinfo",
   151  		"destination_workload":           "productpage-v1",
   152  		"destination_canonical_service":  "productpage",
   153  		"destination_canonical_revision": "v1",
   154  		"request_protocol":               "http",
   155  		"response_code":                  "200",
   156  		"grpc_response_status":           "0",
   157  		"response_flags":                 "-",
   158  	}
   159  	q1m2 := model.Metric{
   160  		"source_workload_namespace":      "unknown",
   161  		"source_workload":                "unknown",
   162  		"source_canonical_service":       "unknown",
   163  		"source_canonical_revision":      "unknown",
   164  		"source_cluster":                 "unknown",
   165  		"destination_cluster":            "east",
   166  		"destination_service_namespace":  "bookinfo",
   167  		"destination_service":            "",
   168  		"destination_service_name":       "",
   169  		"destination_workload_namespace": "bookinfo",
   170  		"destination_workload":           "kiali-2412", // test case when there is no destination_service_name
   171  		"destination_canonical_service":  "",
   172  		"destination_canonical_revision": "",
   173  		"request_protocol":               "http",
   174  		"response_code":                  "200",
   175  		"grpc_response_status":           "0",
   176  		"response_flags":                 "-",
   177  	}
   178  	q1m3 := model.Metric{
   179  		"source_workload_namespace":      "bookinfo",
   180  		"source_workload":                "productpage-v1",
   181  		"source_canonical_service":       "productpage",
   182  		"source_canonical_revision":      "v1",
   183  		"source_cluster":                 "east",
   184  		"destination_cluster":            "east",
   185  		"destination_service_namespace":  "bookinfo",
   186  		"destination_service":            "reviews:9080",
   187  		"destination_service_name":       "reviews",
   188  		"destination_workload_namespace": "bookinfo",
   189  		"destination_workload":           "reviews-v1",
   190  		"destination_canonical_service":  "reviews",
   191  		"destination_canonical_revision": "v1",
   192  		"request_protocol":               "http",
   193  		"response_code":                  "200",
   194  		"grpc_response_status":           "0",
   195  		"response_flags":                 "-",
   196  	}
   197  	q1m4 := model.Metric{
   198  		"source_workload_namespace":      "bookinfo",
   199  		"source_workload":                "productpage-v1",
   200  		"source_canonical_service":       "productpage",
   201  		"source_canonical_revision":      "v1",
   202  		"source_cluster":                 "east",
   203  		"destination_cluster":            "east",
   204  		"destination_service_namespace":  "bookinfo",
   205  		"destination_service":            "reviews:9080",
   206  		"destination_service_name":       "reviews",
   207  		"destination_workload_namespace": "bookinfo",
   208  		"destination_workload":           "reviews-v2",
   209  		"destination_canonical_service":  "reviews",
   210  		"destination_canonical_revision": "v2",
   211  		"request_protocol":               "http",
   212  		"response_code":                  "200",
   213  		"grpc_response_status":           "0",
   214  		"response_flags":                 "-",
   215  	}
   216  	q1m5 := model.Metric{
   217  		"source_workload_namespace":      "bookinfo",
   218  		"source_workload":                "productpage-v1",
   219  		"source_canonical_service":       "productpage",
   220  		"source_canonical_revision":      "v1",
   221  		"source_cluster":                 "east",
   222  		"destination_cluster":            "east",
   223  		"destination_service_namespace":  "bookinfo",
   224  		"destination_service":            "reviews:9080",
   225  		"destination_service_name":       "reviews",
   226  		"destination_workload_namespace": "bookinfo",
   227  		"destination_workload":           "reviews-v3",
   228  		"destination_canonical_service":  "reviews",
   229  		"destination_canonical_revision": "v3",
   230  		"request_protocol":               "http",
   231  		"response_code":                  "200",
   232  		"grpc_response_status":           "0",
   233  		"response_flags":                 "-",
   234  	}
   235  	q1m6 := model.Metric{
   236  		"source_workload_namespace":      "bookinfo",
   237  		"source_workload":                "productpage-v1",
   238  		"source_canonical_service":       "productpage",
   239  		"source_canonical_revision":      "v1",
   240  		"source_cluster":                 "east",
   241  		"destination_cluster":            "east",
   242  		"destination_service_namespace":  "bookinfo",
   243  		"destination_service":            "details:9080",
   244  		"destination_service_name":       "details",
   245  		"destination_workload_namespace": "bookinfo",
   246  		"destination_workload":           "details-v1",
   247  		"destination_canonical_service":  "details",
   248  		"destination_canonical_revision": "v1",
   249  		"request_protocol":               "http",
   250  		"response_code":                  "300",
   251  		"response_flags":                 "-",
   252  	}
   253  	q1m7 := model.Metric{
   254  		"source_workload_namespace":      "bookinfo",
   255  		"source_workload":                "productpage-v1",
   256  		"source_canonical_service":       "productpage",
   257  		"source_canonical_revision":      "v1",
   258  		"source_cluster":                 "east",
   259  		"destination_cluster":            "east",
   260  		"destination_service_namespace":  "bookinfo",
   261  		"destination_service":            "details:9080",
   262  		"destination_service_name":       "details",
   263  		"destination_workload_namespace": "bookinfo",
   264  		"destination_workload":           "details-v1",
   265  		"destination_canonical_service":  "details",
   266  		"destination_canonical_revision": "v1",
   267  		"request_protocol":               "http",
   268  		"response_code":                  "400",
   269  		"response_flags":                 "-",
   270  	}
   271  	q1m8 := model.Metric{
   272  		"source_workload_namespace":      "bookinfo",
   273  		"source_workload":                "productpage-v1",
   274  		"source_canonical_service":       "productpage",
   275  		"source_canonical_revision":      "v1",
   276  		"source_cluster":                 "east",
   277  		"destination_cluster":            "east",
   278  		"destination_service_namespace":  "bookinfo",
   279  		"destination_service":            "details:9080",
   280  		"destination_service_name":       "details",
   281  		"destination_workload_namespace": "bookinfo",
   282  		"destination_workload":           "details-v1",
   283  		"destination_canonical_service":  "details",
   284  		"destination_canonical_revision": "v1",
   285  		"request_protocol":               "http",
   286  		"response_code":                  "500",
   287  		"response_flags":                 "-",
   288  	}
   289  	q1m9 := model.Metric{
   290  		"source_workload_namespace":      "bookinfo",
   291  		"source_workload":                "productpage-v1",
   292  		"source_canonical_service":       "productpage",
   293  		"source_canonical_revision":      "v1",
   294  		"source_cluster":                 "east",
   295  		"destination_cluster":            "east",
   296  		"destination_service_namespace":  "bookinfo",
   297  		"destination_service":            "details:9080",
   298  		"destination_service_name":       "details",
   299  		"destination_workload_namespace": "bookinfo",
   300  		"destination_workload":           "details-v1",
   301  		"destination_canonical_service":  "details",
   302  		"destination_canonical_revision": "v1",
   303  		"request_protocol":               "http",
   304  		"response_code":                  "200",
   305  		"grpc_response_status":           "0",
   306  		"response_flags":                 "-",
   307  	}
   308  	q1m10 := model.Metric{
   309  		"source_workload_namespace":      "bookinfo",
   310  		"source_workload":                "productpage-v1",
   311  		"source_canonical_service":       "productpage",
   312  		"source_canonical_revision":      "v1",
   313  		"source_cluster":                 "east",
   314  		"destination_cluster":            "east",
   315  		"destination_service_namespace":  "bookinfo",
   316  		"destination_service":            "productpage:9080",
   317  		"destination_service_name":       "productpage",
   318  		"destination_workload_namespace": "bookinfo",
   319  		"destination_workload":           "productpage-v1",
   320  		"destination_canonical_service":  "productpage",
   321  		"destination_canonical_revision": "v1",
   322  		"request_protocol":               "http",
   323  		"response_code":                  "200",
   324  		"grpc_response_status":           "0",
   325  		"response_flags":                 "-",
   326  	}
   327  	q1m11 := model.Metric{
   328  		"source_workload_namespace":      "bookinfo",
   329  		"source_workload":                "reviews-v2",
   330  		"source_canonical_service":       "reviews",
   331  		"source_canonical_revision":      "v2",
   332  		"source_cluster":                 "east",
   333  		"destination_cluster":            "east",
   334  		"destination_service_namespace":  "bookinfo",
   335  		"destination_service":            "ratings:9080",
   336  		"destination_service_name":       "ratings",
   337  		"destination_workload_namespace": "bookinfo",
   338  		"destination_workload":           "ratings-v1",
   339  		"destination_canonical_service":  "ratings",
   340  		"destination_canonical_revision": "v1",
   341  		"request_protocol":               "http",
   342  		"response_code":                  "200",
   343  		"grpc_response_status":           "0",
   344  		"response_flags":                 "-",
   345  	}
   346  	q1m12 := model.Metric{
   347  		"source_workload_namespace":      "bookinfo",
   348  		"source_workload":                "reviews-v2",
   349  		"source_canonical_service":       "reviews",
   350  		"source_canonical_revision":      "v2",
   351  		"source_cluster":                 "east",
   352  		"destination_cluster":            "east",
   353  		"destination_service_namespace":  "bookinfo",
   354  		"destination_service":            "ratings:9080",
   355  		"destination_service_name":       "ratings",
   356  		"destination_workload_namespace": "bookinfo",
   357  		"destination_workload":           "ratings-v1",
   358  		"destination_canonical_service":  "ratings",
   359  		"destination_canonical_revision": "v1",
   360  		"request_protocol":               "http",
   361  		"response_code":                  "500",
   362  		"response_flags":                 "-",
   363  	}
   364  	q1m13 := model.Metric{
   365  		"source_workload_namespace":      "bookinfo",
   366  		"source_workload":                "reviews-v2",
   367  		"source_canonical_service":       "reviews",
   368  		"source_canonical_revision":      "v2",
   369  		"source_cluster":                 "east",
   370  		"destination_cluster":            "east",
   371  		"destination_service_namespace":  "bookinfo",
   372  		"destination_service":            "reviews:9080",
   373  		"destination_service_name":       "reviews",
   374  		"destination_workload_namespace": "bookinfo",
   375  		"destination_workload":           "reviews-v2",
   376  		"destination_canonical_service":  "reviews",
   377  		"destination_canonical_revision": "v2",
   378  		"request_protocol":               "http",
   379  		"response_code":                  "200",
   380  		"grpc_response_status":           "0",
   381  		"response_flags":                 "-",
   382  	}
   383  	q1m14 := model.Metric{
   384  		"source_workload_namespace":      "bookinfo",
   385  		"source_workload":                "reviews-v3",
   386  		"source_canonical_service":       "reviews",
   387  		"source_canonical_revision":      "v3",
   388  		"source_cluster":                 "east",
   389  		"destination_cluster":            "east",
   390  		"destination_service_namespace":  "bookinfo",
   391  		"destination_service":            "ratings:9080",
   392  		"destination_service_name":       "ratings",
   393  		"destination_workload_namespace": "bookinfo",
   394  		"destination_workload":           "ratings-v1",
   395  		"destination_canonical_service":  "ratings",
   396  		"destination_canonical_revision": "v1",
   397  		"request_protocol":               "http",
   398  		"response_code":                  "200",
   399  		"grpc_response_status":           "0",
   400  		"response_flags":                 "-",
   401  	}
   402  	q1m15 := model.Metric{
   403  		"source_workload_namespace":      "bookinfo",
   404  		"source_workload":                "reviews-v3",
   405  		"source_canonical_service":       "reviews",
   406  		"source_canonical_revision":      "v3",
   407  		"source_cluster":                 "east",
   408  		"destination_cluster":            "east",
   409  		"destination_service_namespace":  "bookinfo",
   410  		"destination_service":            "ratings:9080",
   411  		"destination_service_name":       "ratings",
   412  		"destination_workload_namespace": "bookinfo",
   413  		"destination_workload":           "ratings-v1",
   414  		"destination_canonical_service":  "ratings",
   415  		"destination_canonical_revision": "v1",
   416  		"request_protocol":               "http",
   417  		"response_code":                  "500",
   418  		"response_flags":                 "-",
   419  	}
   420  	q1m16 := model.Metric{
   421  		"source_workload_namespace":      "bookinfo",
   422  		"source_workload":                "reviews-v3",
   423  		"source_canonical_service":       "reviews",
   424  		"source_canonical_revision":      "v3",
   425  		"source_cluster":                 "east",
   426  		"destination_cluster":            "east",
   427  		"destination_service_namespace":  "bookinfo",
   428  		"destination_service":            "reviews:9080",
   429  		"destination_service_name":       "reviews",
   430  		"destination_workload_namespace": "bookinfo",
   431  		"destination_workload":           "reviews-v3",
   432  		"destination_canonical_service":  "reviews",
   433  		"destination_canonical_revision": "v3",
   434  		"request_protocol":               "http",
   435  		"response_code":                  "200",
   436  		"grpc_response_status":           "0",
   437  		"response_flags":                 "-",
   438  	}
   439  	v1 := model.Vector{
   440  		&model.Sample{
   441  			Metric: q1m0,
   442  			Value:  100,
   443  		},
   444  		&model.Sample{
   445  			Metric: q1m1,
   446  			Value:  50,
   447  		},
   448  		&model.Sample{
   449  			Metric: q1m2,
   450  			Value:  50,
   451  		},
   452  		&model.Sample{
   453  			Metric: q1m3,
   454  			Value:  20,
   455  		},
   456  		&model.Sample{
   457  			Metric: q1m4,
   458  			Value:  20,
   459  		},
   460  		&model.Sample{
   461  			Metric: q1m5,
   462  			Value:  20,
   463  		},
   464  		&model.Sample{
   465  			Metric: q1m6,
   466  			Value:  20,
   467  		},
   468  		&model.Sample{
   469  			Metric: q1m7,
   470  			Value:  20,
   471  		},
   472  		&model.Sample{
   473  			Metric: q1m8,
   474  			Value:  20,
   475  		},
   476  		&model.Sample{
   477  			Metric: q1m9,
   478  			Value:  20,
   479  		},
   480  		&model.Sample{
   481  			Metric: q1m10,
   482  			Value:  20,
   483  		},
   484  		&model.Sample{
   485  			Metric: q1m11,
   486  			Value:  20,
   487  		},
   488  		&model.Sample{
   489  			Metric: q1m12,
   490  			Value:  10,
   491  		},
   492  		&model.Sample{
   493  			Metric: q1m13,
   494  			Value:  20,
   495  		},
   496  		&model.Sample{
   497  			Metric: q1m14,
   498  			Value:  20,
   499  		},
   500  		&model.Sample{
   501  			Metric: q1m15,
   502  			Value:  10,
   503  		},
   504  		&model.Sample{
   505  			Metric: q1m16,
   506  			Value:  20,
   507  		},
   508  	}
   509  
   510  	q2 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
   511  	q2m0 := model.Metric{
   512  		"source_workload_namespace":      "bookinfo",
   513  		"source_workload":                "productpage-v1",
   514  		"source_canonical_service":       "productpage",
   515  		"source_canonical_revision":      "v1",
   516  		"source_cluster":                 "east",
   517  		"destination_cluster":            "east",
   518  		"destination_service_namespace":  "bookinfo",
   519  		"destination_service":            "reviews:9080",
   520  		"destination_service_name":       "reviews",
   521  		"destination_workload_namespace": "bookinfo",
   522  		"destination_workload":           "reviews-v1",
   523  		"destination_canonical_service":  "reviews",
   524  		"destination_canonical_revision": "v1",
   525  		"request_protocol":               "http",
   526  		"response_code":                  "200",
   527  		"grpc_response_status":           "0",
   528  		"response_flags":                 "-",
   529  	}
   530  	q2m1 := model.Metric{
   531  		"source_workload_namespace":      "bookinfo",
   532  		"source_workload":                "productpage-v1",
   533  		"source_canonical_service":       "productpage",
   534  		"source_canonical_revision":      "v1",
   535  		"source_cluster":                 "east",
   536  		"destination_cluster":            "east",
   537  		"destination_service_namespace":  "bookinfo",
   538  		"destination_service":            "reviews:9080",
   539  		"destination_service_name":       "reviews",
   540  		"destination_workload_namespace": "bookinfo",
   541  		"destination_workload":           "reviews-v2",
   542  		"destination_canonical_service":  "reviews",
   543  		"destination_canonical_revision": "v2",
   544  		"request_protocol":               "http",
   545  		"response_code":                  "200",
   546  		"grpc_response_status":           "0",
   547  		"response_flags":                 "-",
   548  	}
   549  	q2m2 := model.Metric{
   550  		"source_workload_namespace":      "bookinfo",
   551  		"source_workload":                "productpage-v1",
   552  		"source_canonical_service":       "productpage",
   553  		"source_canonical_revision":      "v1",
   554  		"source_cluster":                 "east",
   555  		"destination_cluster":            "east",
   556  		"destination_service_namespace":  "bookinfo",
   557  		"destination_service":            "reviews:9080",
   558  		"destination_service_name":       "reviews",
   559  		"destination_workload_namespace": "bookinfo",
   560  		"destination_workload":           "reviews-v3",
   561  		"destination_canonical_service":  "reviews",
   562  		"destination_canonical_revision": "v3",
   563  		"request_protocol":               "http",
   564  		"response_code":                  "200",
   565  		"grpc_response_status":           "0",
   566  		"response_flags":                 "-",
   567  	}
   568  	q2m3 := model.Metric{
   569  		"source_workload_namespace":      "bookinfo",
   570  		"source_workload":                "productpage-v1",
   571  		"source_canonical_service":       "productpage",
   572  		"source_canonical_revision":      "v1",
   573  		"source_cluster":                 "east",
   574  		"destination_cluster":            "east",
   575  		"destination_service_namespace":  "bookinfo",
   576  		"destination_service":            "details:9080",
   577  		"destination_service_name":       "details",
   578  		"destination_workload_namespace": "bookinfo",
   579  		"destination_workload":           "details-v1",
   580  		"destination_canonical_service":  "details",
   581  		"destination_canonical_revision": "v1",
   582  		"request_protocol":               "http",
   583  		"response_code":                  "300",
   584  		"response_flags":                 "-",
   585  	}
   586  	q2m4 := model.Metric{
   587  		"source_workload_namespace":      "bookinfo",
   588  		"source_workload":                "productpage-v1",
   589  		"source_canonical_service":       "productpage",
   590  		"source_canonical_revision":      "v1",
   591  		"source_cluster":                 "east",
   592  		"destination_cluster":            "east",
   593  		"destination_service_namespace":  "bookinfo",
   594  		"destination_service":            "details:9080",
   595  		"destination_service_name":       "details",
   596  		"destination_workload_namespace": "bookinfo",
   597  		"destination_workload":           "details-v1",
   598  		"destination_canonical_service":  "details",
   599  		"destination_canonical_revision": "v1",
   600  		"request_protocol":               "http",
   601  		"response_code":                  "400",
   602  		"response_flags":                 "-",
   603  	}
   604  	q2m5 := model.Metric{
   605  		"source_workload_namespace":      "bookinfo",
   606  		"source_workload":                "productpage-v1",
   607  		"source_canonical_service":       "productpage",
   608  		"source_canonical_revision":      "v1",
   609  		"source_cluster":                 "east",
   610  		"destination_cluster":            "east",
   611  		"destination_service_namespace":  "bookinfo",
   612  		"destination_service":            "details:9080",
   613  		"destination_service_name":       "details",
   614  		"destination_workload_namespace": "bookinfo",
   615  		"destination_workload":           "details-v1",
   616  		"destination_canonical_service":  "details",
   617  		"destination_canonical_revision": "v1",
   618  		"request_protocol":               "http",
   619  		"response_code":                  "500",
   620  		"response_flags":                 "-",
   621  	}
   622  	q2m6 := model.Metric{
   623  		"source_workload_namespace":      "bookinfo",
   624  		"source_workload":                "productpage-v1",
   625  		"source_canonical_service":       "productpage",
   626  		"source_canonical_revision":      "v1",
   627  		"source_cluster":                 "east",
   628  		"destination_cluster":            "east",
   629  		"destination_service_namespace":  "bookinfo",
   630  		"destination_service":            "details:9080",
   631  		"destination_service_name":       "details",
   632  		"destination_workload_namespace": "bookinfo",
   633  		"destination_workload":           "details-v1",
   634  		"destination_canonical_service":  "details",
   635  		"destination_canonical_revision": "v1",
   636  		"request_protocol":               "http",
   637  		"response_code":                  "200",
   638  		"grpc_response_status":           "0",
   639  		"response_flags":                 "-",
   640  	}
   641  	q2m7 := model.Metric{
   642  		"source_workload_namespace":      "bookinfo",
   643  		"source_workload":                "productpage-v1",
   644  		"source_canonical_service":       "productpage",
   645  		"source_canonical_revision":      "v1",
   646  		"source_cluster":                 "east",
   647  		"destination_cluster":            "east",
   648  		"destination_service_namespace":  "bookinfo",
   649  		"destination_service":            "productpage:9080",
   650  		"destination_service_name":       "productpage",
   651  		"destination_workload_namespace": "bookinfo",
   652  		"destination_workload":           "productpage-v1",
   653  		"destination_canonical_service":  "productpage",
   654  		"destination_canonical_revision": "v1",
   655  		"request_protocol":               "http",
   656  		"response_code":                  "200",
   657  		"grpc_response_status":           "0",
   658  		"response_flags":                 "-",
   659  	}
   660  	q2m8 := model.Metric{
   661  		"source_workload_namespace":      "bookinfo",
   662  		"source_workload":                "reviews-v2",
   663  		"source_canonical_service":       "reviews",
   664  		"source_canonical_revision":      "v2",
   665  		"source_cluster":                 "east",
   666  		"destination_cluster":            "east",
   667  		"destination_service_namespace":  "bookinfo",
   668  		"destination_service":            "ratings:9080",
   669  		"destination_service_name":       "ratings",
   670  		"destination_workload_namespace": "bookinfo",
   671  		"destination_workload":           "ratings-v1",
   672  		"destination_canonical_service":  "ratings",
   673  		"destination_canonical_revision": "v1",
   674  		"request_protocol":               "http",
   675  		"response_code":                  "200",
   676  		"grpc_response_status":           "0",
   677  		"response_flags":                 "-",
   678  	}
   679  	q2m9 := model.Metric{
   680  		"source_workload_namespace":      "bookinfo",
   681  		"source_workload":                "reviews-v2",
   682  		"source_canonical_service":       "reviews",
   683  		"source_canonical_revision":      "v2",
   684  		"source_cluster":                 "east",
   685  		"destination_cluster":            "east",
   686  		"destination_service_namespace":  "bookinfo",
   687  		"destination_service":            "ratings:9080",
   688  		"destination_service_name":       "ratings",
   689  		"destination_workload_namespace": "bookinfo",
   690  		"destination_workload":           "ratings-v1",
   691  		"destination_canonical_service":  "ratings",
   692  		"destination_canonical_revision": "v1",
   693  		"request_protocol":               "http",
   694  		"response_code":                  "500",
   695  		"response_flags":                 "-",
   696  	}
   697  	q2m10 := model.Metric{
   698  		"source_workload_namespace":      "bookinfo",
   699  		"source_workload":                "reviews-v2",
   700  		"source_canonical_service":       "reviews",
   701  		"source_canonical_revision":      "v2",
   702  		"source_cluster":                 "east",
   703  		"destination_cluster":            "east",
   704  		"destination_service_namespace":  "bookinfo",
   705  		"destination_service":            "reviews:9080",
   706  		"destination_service_name":       "reviews",
   707  		"destination_workload_namespace": "bookinfo",
   708  		"destination_workload":           "reviews-v2",
   709  		"destination_canonical_service":  "reviews",
   710  		"destination_canonical_revision": "v2",
   711  		"request_protocol":               "http",
   712  		"response_code":                  "200",
   713  		"grpc_response_status":           "0",
   714  		"response_flags":                 "-",
   715  	}
   716  	q2m11 := model.Metric{
   717  		"source_workload_namespace":      "bookinfo",
   718  		"source_workload":                "reviews-v3",
   719  		"source_canonical_service":       "reviews",
   720  		"source_canonical_revision":      "v3",
   721  		"source_cluster":                 "east",
   722  		"destination_cluster":            "east",
   723  		"destination_service_namespace":  "bookinfo",
   724  		"destination_service":            "ratings:9080",
   725  		"destination_service_name":       "ratings",
   726  		"destination_workload_namespace": "bookinfo",
   727  		"destination_workload":           "ratings-v1",
   728  		"destination_canonical_service":  "ratings",
   729  		"destination_canonical_revision": "v1",
   730  		"request_protocol":               "http",
   731  		"response_code":                  "200",
   732  		"grpc_response_status":           "0",
   733  		"response_flags":                 "-",
   734  	}
   735  	q2m12 := model.Metric{
   736  		"source_workload_namespace":      "bookinfo",
   737  		"source_workload":                "reviews-v3",
   738  		"source_canonical_service":       "reviews",
   739  		"source_canonical_revision":      "v3",
   740  		"source_cluster":                 "east",
   741  		"destination_cluster":            "east",
   742  		"destination_service_namespace":  "bookinfo",
   743  		"destination_service":            "ratings:9080",
   744  		"destination_service_name":       "ratings",
   745  		"destination_workload_namespace": "bookinfo",
   746  		"destination_workload":           "ratings-v1",
   747  		"destination_canonical_service":  "ratings",
   748  		"destination_canonical_revision": "v1",
   749  		"request_protocol":               "http",
   750  		"response_code":                  "500",
   751  		"response_flags":                 "-",
   752  	}
   753  	q2m13 := model.Metric{
   754  		"source_workload_namespace":      "bookinfo",
   755  		"source_workload":                "reviews-v3",
   756  		"source_canonical_service":       "reviews",
   757  		"source_canonical_revision":      "v3",
   758  		"source_cluster":                 "east",
   759  		"destination_cluster":            "east",
   760  		"destination_service_namespace":  "bookinfo",
   761  		"destination_service":            "reviews:9080",
   762  		"destination_service_name":       "reviews",
   763  		"destination_workload_namespace": "bookinfo",
   764  		"destination_workload":           "reviews-v3",
   765  		"destination_canonical_service":  "reviews",
   766  		"destination_canonical_revision": "v3",
   767  		"request_protocol":               "http",
   768  		"response_code":                  "200",
   769  		"grpc_response_status":           "0",
   770  		"response_flags":                 "-",
   771  	}
   772  	q2m14 := model.Metric{
   773  		"source_workload_namespace":      "bookinfo",
   774  		"source_workload":                "reviews-v3",
   775  		"source_canonical_service":       "reviews",
   776  		"source_canonical_revision":      "v3",
   777  		"source_cluster":                 "east",
   778  		"destination_cluster":            "east",
   779  		"destination_service_namespace":  "bankapp",
   780  		"destination_service":            "pricing:9080",
   781  		"destination_service_name":       "pricing",
   782  		"destination_workload_namespace": "bankapp",
   783  		"destination_workload":           "pricing-v1",
   784  		"destination_canonical_service":  "pricing",
   785  		"destination_canonical_revision": "v1",
   786  		"request_protocol":               "http",
   787  		"response_code":                  "200",
   788  		"grpc_response_status":           "0",
   789  		"response_flags":                 "-",
   790  	}
   791  	q2m15 := model.Metric{
   792  		"source_workload_namespace":      "bookinfo",
   793  		"source_workload":                "reviews-v3",
   794  		"source_canonical_service":       "reviews",
   795  		"source_canonical_revision":      "v3",
   796  		"source_cluster":                 "east",
   797  		"destination_cluster":            "unknown",
   798  		"destination_service_namespace":  "unknown",
   799  		"destination_service":            "unknown",
   800  		"destination_service_name":       "unknown",
   801  		"destination_workload_namespace": "unknown",
   802  		"destination_workload":           "unknown",
   803  		"destination_canonical_service":  "unknown",
   804  		"destination_canonical_revision": "unknown",
   805  		"request_protocol":               "http",
   806  		"response_code":                  "404",
   807  		"response_flags":                 "NR",
   808  	}
   809  	q2m16 := model.Metric{
   810  		"source_workload_namespace":      "bookinfo",
   811  		"source_workload":                "reviews-v3",
   812  		"source_canonical_service":       "reviews",
   813  		"source_canonical_revision":      "v3",
   814  		"source_cluster":                 "east",
   815  		"destination_cluster":            "east",
   816  		"destination_service_namespace":  "bankapp",
   817  		"destination_service":            "deposit:9080",
   818  		"destination_service_name":       "deposit",
   819  		"destination_workload_namespace": "bankapp",
   820  		"destination_workload":           "deposit-v1",
   821  		"destination_canonical_service":  "deposit",
   822  		"destination_canonical_revision": "v1",
   823  		"request_protocol":               "grpc",
   824  		"response_code":                  "200",
   825  		"grpc_response_status":           "0",
   826  		"response_flags":                 "-",
   827  	}
   828  	v2 := model.Vector{
   829  		&model.Sample{
   830  			Metric: q2m0,
   831  			Value:  20,
   832  		},
   833  		&model.Sample{
   834  			Metric: q2m1,
   835  			Value:  20,
   836  		},
   837  		&model.Sample{
   838  			Metric: q2m2,
   839  			Value:  20,
   840  		},
   841  		&model.Sample{
   842  			Metric: q2m3,
   843  			Value:  20,
   844  		},
   845  		&model.Sample{
   846  			Metric: q2m4,
   847  			Value:  20,
   848  		},
   849  		&model.Sample{
   850  			Metric: q2m5,
   851  			Value:  20,
   852  		},
   853  		&model.Sample{
   854  			Metric: q2m6,
   855  			Value:  20,
   856  		},
   857  		&model.Sample{
   858  			Metric: q2m7,
   859  			Value:  20,
   860  		},
   861  		&model.Sample{
   862  			Metric: q2m8,
   863  			Value:  20,
   864  		},
   865  		&model.Sample{
   866  			Metric: q2m9,
   867  			Value:  10,
   868  		},
   869  		&model.Sample{
   870  			Metric: q2m10,
   871  			Value:  20,
   872  		},
   873  		&model.Sample{
   874  			Metric: q2m11,
   875  			Value:  20,
   876  		},
   877  		&model.Sample{
   878  			Metric: q2m12,
   879  			Value:  10,
   880  		},
   881  		&model.Sample{
   882  			Metric: q2m13,
   883  			Value:  20,
   884  		},
   885  		&model.Sample{
   886  			Metric: q2m14,
   887  			Value:  20,
   888  		},
   889  		&model.Sample{
   890  			Metric: q2m15,
   891  			Value:  4,
   892  		},
   893  		&model.Sample{
   894  			Metric: q2m16,
   895  			Value:  50,
   896  		},
   897  	}
   898  
   899  	q3 := `round(sum(rate(istio_tcp_sent_bytes_total{reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
   900  	v3 := model.Vector{}
   901  
   902  	q4 := `round(sum(rate(istio_tcp_sent_bytes_total{reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
   903  	q4m0 := model.Metric{
   904  		"source_workload_namespace":      "istio-system",
   905  		"source_workload":                "ingressgateway-unknown",
   906  		"source_canonical_service":       "ingressgateway",
   907  		"source_canonical_revision":      "latest",
   908  		"source_cluster":                 "east",
   909  		"destination_cluster":            "east",
   910  		"destination_service_namespace":  "bookinfo",
   911  		"destination_service":            "tcp:9080",
   912  		"destination_service_name":       "tcp",
   913  		"destination_workload_namespace": "bookinfo",
   914  		"destination_workload":           "tcp-v1",
   915  		"destination_canonical_service":  "tcp",
   916  		"destination_canonical_revision": "v1",
   917  		"response_flags":                 "-",
   918  	}
   919  	q4m1 := model.Metric{
   920  		"source_workload_namespace":      "unknown",
   921  		"source_workload":                "unknown",
   922  		"source_canonical_service":       "unknown",
   923  		"source_canonical_revision":      "unknown",
   924  		"source_cluster":                 "unknown",
   925  		"destination_cluster":            "east",
   926  		"destination_service_namespace":  "bookinfo",
   927  		"destination_service":            "tcp:9080",
   928  		"destination_service_name":       "tcp",
   929  		"destination_workload_namespace": "bookinfo",
   930  		"destination_workload":           "tcp-v1",
   931  		"destination_canonical_service":  "tcp",
   932  		"destination_canonical_revision": "v1",
   933  		"response_flags":                 "-",
   934  	}
   935  	q4m2 := model.Metric{
   936  		"source_workload_namespace":      "bookinfo",
   937  		"source_workload":                "productpage-v1",
   938  		"source_canonical_service":       "productpage",
   939  		"source_canonical_revision":      "v1",
   940  		"source_cluster":                 "east",
   941  		"destination_cluster":            "east",
   942  		"destination_service_namespace":  "bookinfo",
   943  		"destination_service":            "tcp:9080",
   944  		"destination_service_name":       "tcp",
   945  		"destination_workload_namespace": "bookinfo",
   946  		"destination_workload":           "tcp-v1",
   947  		"destination_canonical_service":  "tcp",
   948  		"destination_canonical_revision": "v1",
   949  		"response_flags":                 "-",
   950  	}
   951  	v4 := model.Vector{
   952  		&model.Sample{
   953  			Metric: q4m0,
   954  			Value:  150,
   955  		},
   956  		&model.Sample{
   957  			Metric: q4m1,
   958  			Value:  400,
   959  		},
   960  		&model.Sample{
   961  			Metric: q4m2,
   962  			Value:  31,
   963  		},
   964  	}
   965  
   966  	q5 := `round(sum(rate(istio_tcp_sent_bytes_total{reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
   967  	q5m0 := model.Metric{
   968  		"source_workload_namespace":      "bookinfo",
   969  		"source_workload":                "productpage-v1",
   970  		"source_canonical_service":       "productpage",
   971  		"source_canonical_revision":      "v1",
   972  		"source_cluster":                 "east",
   973  		"destination_cluster":            "east",
   974  		"destination_service_namespace":  "bookinfo",
   975  		"destination_service":            "tcp:9080",
   976  		"destination_service_name":       "tcp",
   977  		"destination_workload_namespace": "bookinfo",
   978  		"destination_workload":           "tcp-v1",
   979  		"destination_canonical_service":  "tcp",
   980  		"destination_canonical_revision": "v1",
   981  		"response_flags":                 "-",
   982  	}
   983  	v5 := model.Vector{
   984  		&model.Sample{
   985  			Metric: q5m0,
   986  			Value:  31,
   987  		},
   988  	}
   989  
   990  	client, api := setupMocked(t)
   991  
   992  	mockQuery(api, q0, &v0)
   993  	mockQuery(api, q1, &v1)
   994  	mockQuery(api, q2, &v2)
   995  	mockQuery(api, q3, &v3)
   996  	mockQuery(api, q4, &v4)
   997  	mockQuery(api, q5, &v5)
   998  
   999  	return client, api, nil
  1000  }
  1001  
  1002  // mockNamespaceRatesGraph adds additional queries to mockNamespaceGraph to test non-default rates for graph-gen. Basic approach
  1003  // is for "sent" to use the same traffic/rates as is done for the default traffic.  This produces the same rates (and nearly the
  1004  // same graph as for the defaults). Use double the rates for "received".  And so "total" should be triple the "sent" rates.
  1005  func mockNamespaceRatesGraph(t *testing.T) (*prometheus.Client, *prometheustest.PromAPIMock, error) {
  1006  	client, api, err := mockNamespaceGraph(t)
  1007  	if err != nil {
  1008  		return client, api, err
  1009  	}
  1010  
  1011  	q6 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  1012  	v6 := model.Vector{}
  1013  
  1014  	q7 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  1015  	q7m0 := model.Metric{
  1016  		"source_workload_namespace":      "istio-system",
  1017  		"source_workload":                "ingressgateway-unknown",
  1018  		"source_canonical_service":       "ingressgateway",
  1019  		"source_canonical_revision":      "latest",
  1020  		"source_cluster":                 "east",
  1021  		"destination_cluster":            "east",
  1022  		"destination_service_namespace":  "bookinfo",
  1023  		"destination_service":            "tcp:9080",
  1024  		"destination_service_name":       "tcp",
  1025  		"destination_workload_namespace": "bookinfo",
  1026  		"destination_workload":           "tcp-v1",
  1027  		"destination_canonical_service":  "tcp",
  1028  		"destination_canonical_revision": "v1",
  1029  		"response_flags":                 "-",
  1030  	}
  1031  	q7m1 := model.Metric{
  1032  		"source_workload_namespace":      "unknown",
  1033  		"source_workload":                "unknown",
  1034  		"source_canonical_service":       "unknown",
  1035  		"source_canonical_revision":      "unknown",
  1036  		"source_cluster":                 "unknown",
  1037  		"destination_cluster":            "east",
  1038  		"destination_service_namespace":  "bookinfo",
  1039  		"destination_service":            "tcp:9080",
  1040  		"destination_service_name":       "tcp",
  1041  		"destination_workload_namespace": "bookinfo",
  1042  		"destination_workload":           "tcp-v1",
  1043  		"destination_canonical_service":  "tcp",
  1044  		"destination_canonical_revision": "v1",
  1045  		"response_flags":                 "-",
  1046  	}
  1047  	q7m2 := model.Metric{
  1048  		"source_workload_namespace":      "bookinfo",
  1049  		"source_workload":                "productpage-v1",
  1050  		"source_canonical_service":       "productpage",
  1051  		"source_canonical_revision":      "v1",
  1052  		"source_cluster":                 "east",
  1053  		"destination_cluster":            "east",
  1054  		"destination_service_namespace":  "bookinfo",
  1055  		"destination_service":            "tcp:9080",
  1056  		"destination_service_name":       "tcp",
  1057  		"destination_workload_namespace": "bookinfo",
  1058  		"destination_workload":           "tcp-v1",
  1059  		"destination_canonical_service":  "tcp",
  1060  		"destination_canonical_revision": "v1",
  1061  		"response_flags":                 "-",
  1062  	}
  1063  	v7 := model.Vector{
  1064  		&model.Sample{
  1065  			Metric: q7m0,
  1066  			Value:  300,
  1067  		},
  1068  		&model.Sample{
  1069  			Metric: q7m1,
  1070  			Value:  800,
  1071  		},
  1072  		&model.Sample{
  1073  			Metric: q7m2,
  1074  			Value:  62,
  1075  		},
  1076  	}
  1077  
  1078  	q8 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  1079  	q8m0 := model.Metric{
  1080  		"source_workload_namespace":      "bookinfo",
  1081  		"source_workload":                "productpage-v1",
  1082  		"source_canonical_service":       "productpage",
  1083  		"source_canonical_revision":      "v1",
  1084  		"source_cluster":                 "east",
  1085  		"destination_cluster":            "east",
  1086  		"destination_service_namespace":  "bookinfo",
  1087  		"destination_service":            "tcp:9080",
  1088  		"destination_service_name":       "tcp",
  1089  		"destination_workload_namespace": "bookinfo",
  1090  		"destination_workload":           "tcp-v1",
  1091  		"destination_canonical_service":  "tcp",
  1092  		"destination_canonical_revision": "v1",
  1093  		"response_flags":                 "-",
  1094  	}
  1095  	v8 := model.Vector{
  1096  		&model.Sample{
  1097  			Metric: q8m0,
  1098  			Value:  62,
  1099  		},
  1100  	}
  1101  
  1102  	q9 := `round(sum(rate(istio_request_messages_total{reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  1103  	v9 := model.Vector{}
  1104  
  1105  	q10 := `round(sum(rate(istio_request_messages_total{reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  1106  	v10 := model.Vector{}
  1107  
  1108  	q11 := `round(sum(rate(istio_request_messages_total{reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  1109  	q11m0 := model.Metric{
  1110  		"source_workload_namespace":      "bookinfo",
  1111  		"source_workload":                "reviews-v3",
  1112  		"source_canonical_service":       "reviews",
  1113  		"source_canonical_revision":      "v3",
  1114  		"source_cluster":                 "east",
  1115  		"destination_cluster":            "east",
  1116  		"destination_service_namespace":  "bankapp",
  1117  		"destination_service":            "deposit:9080",
  1118  		"destination_service_name":       "deposit",
  1119  		"destination_workload_namespace": "bankapp",
  1120  		"destination_workload":           "deposit-v1",
  1121  		"destination_canonical_service":  "deposit",
  1122  		"destination_canonical_revision": "v1",
  1123  	}
  1124  	v11 := model.Vector{
  1125  		&model.Sample{
  1126  			Metric: q11m0,
  1127  			Value:  50,
  1128  		},
  1129  	}
  1130  
  1131  	q12 := `round(sum(rate(istio_response_messages_total{reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  1132  	v12 := model.Vector{}
  1133  
  1134  	q13 := `round(sum(rate(istio_response_messages_total{reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  1135  	v13 := model.Vector{}
  1136  
  1137  	q14 := `round(sum(rate(istio_response_messages_total{reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  1138  	q14m0 := model.Metric{
  1139  		"source_workload_namespace":      "bookinfo",
  1140  		"source_workload":                "reviews-v3",
  1141  		"source_canonical_service":       "reviews",
  1142  		"source_canonical_revision":      "v3",
  1143  		"source_cluster":                 "east",
  1144  		"destination_cluster":            "east",
  1145  		"destination_service_namespace":  "bankapp",
  1146  		"destination_service":            "deposit:9080",
  1147  		"destination_service_name":       "deposit",
  1148  		"destination_workload_namespace": "bankapp",
  1149  		"destination_workload":           "deposit-v1",
  1150  		"destination_canonical_service":  "deposit",
  1151  		"destination_canonical_revision": "v1",
  1152  	}
  1153  	v14 := model.Vector{
  1154  		&model.Sample{
  1155  			Metric: q14m0,
  1156  			Value:  100,
  1157  		},
  1158  	}
  1159  
  1160  	mockQuery(api, q6, &v6)
  1161  	mockQuery(api, q7, &v7)
  1162  	mockQuery(api, q8, &v8)
  1163  	mockQuery(api, q9, &v9)
  1164  	mockQuery(api, q10, &v10)
  1165  	mockQuery(api, q11, &v11)
  1166  	mockQuery(api, q12, &v12)
  1167  	mockQuery(api, q13, &v13)
  1168  	mockQuery(api, q14, &v14)
  1169  
  1170  	return client, api, nil
  1171  }
  1172  
  1173  func respond(w http.ResponseWriter, code int, payload interface{}) {
  1174  	response, err := json.MarshalIndent(payload, "", "  ")
  1175  	if err != nil {
  1176  		response = []byte(err.Error())
  1177  		code = http.StatusInternalServerError
  1178  	}
  1179  
  1180  	w.Header().Set("Content-Type", "application/json")
  1181  	w.WriteHeader(code)
  1182  	_, _ = w.Write(response)
  1183  }
  1184  
  1185  // Helper method that tests the objects are equal and if they aren't will
  1186  // unmarshal them into a json object and diff them. This way the output of the failure
  1187  // is actually useful. Otherwise printing the byte slice results is incomprehensible.
  1188  func assertObjectsEqual(t *testing.T, expected, actual []byte) {
  1189  	if !assert.ObjectsAreEqual(expected, actual) {
  1190  		t.Log("Actual response does not equal expected golden copy. If you've updated the golden copy, ensure it ends with a newline.")
  1191  		t.Fail()
  1192  
  1193  		var (
  1194  			ev any
  1195  			av any
  1196  		)
  1197  		err := func() error {
  1198  			if err := json.Unmarshal(expected, &ev); err != nil {
  1199  				t.Logf("Failed to unmarshal expected value: %s", err)
  1200  				return err
  1201  			}
  1202  
  1203  			if err := json.Unmarshal(actual, &av); err != nil {
  1204  				t.Logf("Failed to unmarshal actual value: %s", err)
  1205  				return err
  1206  			}
  1207  
  1208  			return nil
  1209  		}()
  1210  		if err != nil {
  1211  			t.Logf("Failed to unmarshal expected or actual value. Falling back to string comparison.\nExpected: %s\nActual: %s", string(expected), string(actual))
  1212  			return
  1213  		}
  1214  
  1215  		t.Logf("Diff: %s", cmp.Diff(ev, av))
  1216  	}
  1217  }
  1218  
  1219  func TestAppGraph(t *testing.T) {
  1220  	client, _, err := mockNamespaceRatesGraph(t)
  1221  	if err != nil {
  1222  		t.Error(err)
  1223  		return
  1224  	}
  1225  
  1226  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1227  
  1228  	mr := mux.NewRouter()
  1229  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1230  		func(w http.ResponseWriter, r *http.Request) {
  1231  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1232  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1233  			respond(w, code, config)
  1234  		}))
  1235  
  1236  	ts := httptest.NewServer(mr)
  1237  	defer ts.Close()
  1238  
  1239  	fut = graphNamespacesIstio
  1240  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=app&appenders&queryTime=1523364075"
  1241  	resp, err := http.Get(url)
  1242  	if err != nil {
  1243  		t.Fatal(err)
  1244  	}
  1245  	actual, _ := io.ReadAll(resp.Body)
  1246  	expected, _ := os.ReadFile("testdata/test_app_graph.expected")
  1247  	if runtime.GOOS == "windows" {
  1248  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1249  	}
  1250  	expected = expected[:len(expected)-1] // remove EOF byte
  1251  
  1252  	assertObjectsEqual(t, expected, actual)
  1253  	assert.Equal(t, 200, resp.StatusCode)
  1254  }
  1255  
  1256  func TestVersionedAppGraph(t *testing.T) {
  1257  	client, _, err := mockNamespaceRatesGraph(t)
  1258  	if err != nil {
  1259  		t.Error(err)
  1260  		return
  1261  	}
  1262  
  1263  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1264  
  1265  	mr := mux.NewRouter()
  1266  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1267  		func(w http.ResponseWriter, r *http.Request) {
  1268  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1269  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1270  			respond(w, code, config)
  1271  		}))
  1272  
  1273  	ts := httptest.NewServer(mr)
  1274  	defer ts.Close()
  1275  
  1276  	fut = graphNamespacesIstio
  1277  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=versionedApp&appenders&queryTime=1523364075"
  1278  	resp, err := http.Get(url)
  1279  	if err != nil {
  1280  		t.Fatal(err)
  1281  	}
  1282  	actual, _ := io.ReadAll(resp.Body)
  1283  	expected, _ := os.ReadFile("testdata/test_versioned_app_graph.expected")
  1284  	if runtime.GOOS == "windows" {
  1285  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1286  	}
  1287  	expected = expected[:len(expected)-1] // remove EOF byte
  1288  
  1289  	assertObjectsEqual(t, expected, actual)
  1290  	assert.Equal(t, 200, resp.StatusCode)
  1291  }
  1292  
  1293  func TestServiceGraph(t *testing.T) {
  1294  	client, _, err := mockNamespaceRatesGraph(t)
  1295  	if err != nil {
  1296  		t.Error(err)
  1297  		return
  1298  	}
  1299  
  1300  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1301  
  1302  	mr := mux.NewRouter()
  1303  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1304  		func(w http.ResponseWriter, r *http.Request) {
  1305  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1306  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1307  			respond(w, code, config)
  1308  		}))
  1309  
  1310  	ts := httptest.NewServer(mr)
  1311  	defer ts.Close()
  1312  
  1313  	fut = graphNamespacesIstio
  1314  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=service&appenders&queryTime=1523364075"
  1315  	resp, err := http.Get(url)
  1316  	if err != nil {
  1317  		t.Fatal(err)
  1318  	}
  1319  	actual, _ := io.ReadAll(resp.Body)
  1320  	expected, _ := os.ReadFile("testdata/test_service_graph.expected")
  1321  	if runtime.GOOS == "windows" {
  1322  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1323  	}
  1324  	expected = expected[:len(expected)-1] // remove EOF byte
  1325  
  1326  	assertObjectsEqual(t, expected, actual)
  1327  	assert.Equal(t, 200, resp.StatusCode)
  1328  }
  1329  
  1330  func TestWorkloadGraph(t *testing.T) {
  1331  	client, _, err := mockNamespaceRatesGraph(t)
  1332  	if err != nil {
  1333  		t.Error(err)
  1334  		return
  1335  	}
  1336  
  1337  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1338  
  1339  	mr := mux.NewRouter()
  1340  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1341  		func(w http.ResponseWriter, r *http.Request) {
  1342  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1343  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1344  			respond(w, code, config)
  1345  		}))
  1346  
  1347  	ts := httptest.NewServer(mr)
  1348  	defer ts.Close()
  1349  
  1350  	fut = graphNamespacesIstio
  1351  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=workload&appenders&queryTime=1523364075"
  1352  	resp, err := http.Get(url)
  1353  	if err != nil {
  1354  		t.Fatal(err)
  1355  	}
  1356  	actual, _ := io.ReadAll(resp.Body)
  1357  	expected, _ := os.ReadFile("testdata/test_workload_graph.expected")
  1358  	if runtime.GOOS == "windows" {
  1359  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1360  	}
  1361  	expected = expected[:len(expected)-1] // remove EOF byte
  1362  
  1363  	assertObjectsEqual(t, expected, actual)
  1364  	assert.Equal(t, 200, resp.StatusCode)
  1365  }
  1366  
  1367  func TestRatesGraphSent(t *testing.T) {
  1368  	client, _, err := mockNamespaceRatesGraph(t)
  1369  	if err != nil {
  1370  		t.Error(err)
  1371  		return
  1372  	}
  1373  
  1374  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1375  
  1376  	mr := mux.NewRouter()
  1377  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1378  		func(w http.ResponseWriter, r *http.Request) {
  1379  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1380  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1381  			respond(w, code, config)
  1382  		}))
  1383  
  1384  	ts := httptest.NewServer(mr)
  1385  	defer ts.Close()
  1386  
  1387  	fut = graphNamespacesIstio
  1388  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=workload&appenders&queryTime=1523364075&rateGrpc=sent&rateHttp=requests&rateTcp=sent"
  1389  	resp, err := http.Get(url)
  1390  	if err != nil {
  1391  		t.Fatal(err)
  1392  	}
  1393  	actual, _ := io.ReadAll(resp.Body)
  1394  	expected, _ := os.ReadFile("testdata/test_rates_sent_graph.expected")
  1395  	if runtime.GOOS == "windows" {
  1396  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1397  	}
  1398  	expected = expected[:len(expected)-1] // remove EOF byte
  1399  
  1400  	assertObjectsEqual(t, expected, actual)
  1401  	assert.Equal(t, 200, resp.StatusCode)
  1402  }
  1403  
  1404  func TestRatesGraphReceived(t *testing.T) {
  1405  	client, _, err := mockNamespaceRatesGraph(t)
  1406  	if err != nil {
  1407  		t.Error(err)
  1408  		return
  1409  	}
  1410  
  1411  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1412  
  1413  	mr := mux.NewRouter()
  1414  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1415  		func(w http.ResponseWriter, r *http.Request) {
  1416  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1417  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1418  			respond(w, code, config)
  1419  		}))
  1420  
  1421  	ts := httptest.NewServer(mr)
  1422  	defer ts.Close()
  1423  
  1424  	fut = graphNamespacesIstio
  1425  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=workload&appenders&queryTime=1523364075&rateGrpc=received&rateHttp=requests&rateTcp=received"
  1426  	resp, err := http.Get(url)
  1427  	if err != nil {
  1428  		t.Fatal(err)
  1429  	}
  1430  	actual, _ := io.ReadAll(resp.Body)
  1431  	expected, _ := os.ReadFile("testdata/test_rates_received_graph.expected")
  1432  	if runtime.GOOS == "windows" {
  1433  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1434  	}
  1435  	expected = expected[:len(expected)-1] // remove EOF byte
  1436  
  1437  	assertObjectsEqual(t, expected, actual)
  1438  	assert.Equal(t, 200, resp.StatusCode)
  1439  }
  1440  
  1441  func TestRatesGraphTotal(t *testing.T) {
  1442  	client, _, err := mockNamespaceRatesGraph(t)
  1443  	if err != nil {
  1444  		t.Error(err)
  1445  		return
  1446  	}
  1447  
  1448  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1449  
  1450  	mr := mux.NewRouter()
  1451  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1452  		func(w http.ResponseWriter, r *http.Request) {
  1453  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1454  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1455  			respond(w, code, config)
  1456  		}))
  1457  
  1458  	ts := httptest.NewServer(mr)
  1459  	defer ts.Close()
  1460  
  1461  	fut = graphNamespacesIstio
  1462  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=workload&appenders&queryTime=1523364075&rateGrpc=total&rateHttp=requests&rateTcp=total"
  1463  	resp, err := http.Get(url)
  1464  	if err != nil {
  1465  		t.Fatal(err)
  1466  	}
  1467  	actual, _ := io.ReadAll(resp.Body)
  1468  	expected, _ := os.ReadFile("testdata/test_rates_total_graph.expected")
  1469  	if runtime.GOOS == "windows" {
  1470  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1471  	}
  1472  	expected = expected[:len(expected)-1] // remove EOF byte
  1473  
  1474  	assertObjectsEqual(t, expected, actual)
  1475  	assert.Equal(t, 200, resp.StatusCode)
  1476  }
  1477  
  1478  func TestRatesGraphNone(t *testing.T) {
  1479  	client, _, err := mockNamespaceRatesGraph(t)
  1480  	if err != nil {
  1481  		t.Error(err)
  1482  		return
  1483  	}
  1484  
  1485  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1486  
  1487  	mr := mux.NewRouter()
  1488  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  1489  		func(w http.ResponseWriter, r *http.Request) {
  1490  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1491  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1492  			respond(w, code, config)
  1493  		}))
  1494  
  1495  	ts := httptest.NewServer(mr)
  1496  	defer ts.Close()
  1497  
  1498  	fut = graphNamespacesIstio
  1499  	url := ts.URL + "/api/namespaces/graph?namespaces=bookinfo&graphType=workload&appenders&queryTime=1523364075&rateGrpc=total&rateHttp=none&rateTcp=total"
  1500  	resp, err := http.Get(url)
  1501  	if err != nil {
  1502  		t.Fatal(err)
  1503  	}
  1504  	actual, _ := io.ReadAll(resp.Body)
  1505  	expected, _ := os.ReadFile("testdata/test_rates_none_graph.expected")
  1506  	if runtime.GOOS == "windows" {
  1507  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1508  	}
  1509  	expected = expected[:len(expected)-1] // remove EOF byte
  1510  
  1511  	assertObjectsEqual(t, expected, actual)
  1512  	assert.Equal(t, 200, resp.StatusCode)
  1513  }
  1514  
  1515  func TestWorkloadNodeGraph(t *testing.T) {
  1516  	q0 := `round(sum(rate(istio_requests_total{reporter="destination",destination_workload_namespace="bookinfo",destination_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  1517  	q0m0 := model.Metric{
  1518  		"source_workload_namespace":      "unknown",
  1519  		"source_workload":                "unknown",
  1520  		"source_canonical_service":       "unknown",
  1521  		"source_canonical_revision":      "unknown",
  1522  		"source_cluster":                 "unknown",
  1523  		"destination_cluster":            "east",
  1524  		"destination_service_namespace":  "bookinfo",
  1525  		"destination_service":            "productpage:9080",
  1526  		"destination_service_name":       "productpage",
  1527  		"destination_workload_namespace": "bookinfo",
  1528  		"destination_workload":           "productpage-v1",
  1529  		"destination_canonical_service":  "productpage",
  1530  		"destination_canonical_revision": "v1",
  1531  		"request_protocol":               "http",
  1532  		"response_code":                  "200",
  1533  		"grpc_response_status":           "0",
  1534  		"response_flags":                 "-",
  1535  	}
  1536  	q0m1 := model.Metric{
  1537  		"source_workload_namespace":      "istio-system",
  1538  		"source_workload":                "ingressgateway-unknown",
  1539  		"source_canonical_service":       "ingressgateway",
  1540  		"source_canonical_revision":      "latest",
  1541  		"source_cluster":                 "east",
  1542  		"destination_cluster":            "east",
  1543  		"destination_service_namespace":  "bookinfo",
  1544  		"destination_service":            "productpage:9080",
  1545  		"destination_service_name":       "productpage",
  1546  		"destination_workload_namespace": "bookinfo",
  1547  		"destination_workload":           "productpage-v1",
  1548  		"destination_canonical_service":  "productpage",
  1549  		"destination_canonical_revision": "v1",
  1550  		"request_protocol":               "http",
  1551  		"response_code":                  "200",
  1552  		"grpc_response_status":           "0",
  1553  		"response_flags":                 "-",
  1554  	}
  1555  	v0 := model.Vector{
  1556  		&model.Sample{
  1557  			Metric: q0m0,
  1558  			Value:  50,
  1559  		},
  1560  		&model.Sample{
  1561  			Metric: q0m1,
  1562  			Value:  100,
  1563  		},
  1564  	}
  1565  
  1566  	q1 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo",source_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  1567  	q1m0 := model.Metric{
  1568  		"source_workload_namespace":      "bookinfo",
  1569  		"source_workload":                "productpage-v1",
  1570  		"source_canonical_service":       "productpage",
  1571  		"source_canonical_revision":      "v1",
  1572  		"source_cluster":                 "east",
  1573  		"destination_cluster":            "east",
  1574  		"destination_service_namespace":  "bookinfo",
  1575  		"destination_service":            "reviews:9080",
  1576  		"destination_service_name":       "reviews",
  1577  		"destination_workload_namespace": "bookinfo",
  1578  		"destination_workload":           "reviews-v1",
  1579  		"destination_canonical_service":  "reviews",
  1580  		"destination_canonical_revision": "v1",
  1581  		"request_protocol":               "http",
  1582  		"response_code":                  "200",
  1583  		"grpc_response_status":           "0",
  1584  		"response_flags":                 "-",
  1585  	}
  1586  	q1m1 := model.Metric{
  1587  		"source_workload_namespace":      "bookinfo",
  1588  		"source_workload":                "productpage-v1",
  1589  		"source_canonical_service":       "productpage",
  1590  		"source_canonical_revision":      "v1",
  1591  		"source_cluster":                 "east",
  1592  		"destination_cluster":            "east",
  1593  		"destination_service_namespace":  "bookinfo",
  1594  		"destination_service":            "reviews:9080",
  1595  		"destination_service_name":       "reviews",
  1596  		"destination_workload_namespace": "bookinfo",
  1597  		"destination_workload":           "reviews-v2",
  1598  		"destination_canonical_service":  "reviews",
  1599  		"destination_canonical_revision": "v2",
  1600  		"request_protocol":               "http",
  1601  		"response_code":                  "200",
  1602  		"grpc_response_status":           "0",
  1603  		"response_flags":                 "-",
  1604  	}
  1605  	q1m2 := model.Metric{
  1606  		"source_workload_namespace":      "bookinfo",
  1607  		"source_workload":                "productpage-v1",
  1608  		"source_canonical_service":       "productpage",
  1609  		"source_canonical_revision":      "v1",
  1610  		"source_cluster":                 "east",
  1611  		"destination_cluster":            "east",
  1612  		"destination_service_namespace":  "bookinfo",
  1613  		"destination_service":            "reviews:9080",
  1614  		"destination_service_name":       "reviews",
  1615  		"destination_workload_namespace": "bookinfo",
  1616  		"destination_workload":           "reviews-v3",
  1617  		"destination_canonical_service":  "reviews",
  1618  		"destination_canonical_revision": "v3",
  1619  		"request_protocol":               "http",
  1620  		"response_code":                  "200",
  1621  		"grpc_response_status":           "0",
  1622  		"response_flags":                 "-",
  1623  	}
  1624  	q1m3 := model.Metric{
  1625  		"source_workload_namespace":      "bookinfo",
  1626  		"source_workload":                "productpage-v1",
  1627  		"source_canonical_service":       "productpage",
  1628  		"source_canonical_revision":      "v1",
  1629  		"source_cluster":                 "east",
  1630  		"destination_cluster":            "east",
  1631  		"destination_service_namespace":  "bookinfo",
  1632  		"destination_service":            "details:9080",
  1633  		"destination_service_name":       "details",
  1634  		"destination_workload_namespace": "bookinfo",
  1635  		"destination_workload":           "details-v1",
  1636  		"destination_canonical_service":  "details",
  1637  		"destination_canonical_revision": "v1",
  1638  		"request_protocol":               "http",
  1639  		"response_code":                  "300",
  1640  		"response_flags":                 "-",
  1641  	}
  1642  	q1m4 := model.Metric{
  1643  		"source_workload_namespace":      "bookinfo",
  1644  		"source_workload":                "productpage-v1",
  1645  		"source_canonical_service":       "productpage",
  1646  		"source_canonical_revision":      "v1",
  1647  		"source_cluster":                 "east",
  1648  		"destination_cluster":            "east",
  1649  		"destination_service_namespace":  "bookinfo",
  1650  		"destination_service":            "details:9080",
  1651  		"destination_service_name":       "details",
  1652  		"destination_workload_namespace": "bookinfo",
  1653  		"destination_workload":           "details-v1",
  1654  		"destination_canonical_service":  "details",
  1655  		"destination_canonical_revision": "v1",
  1656  		"request_protocol":               "http",
  1657  		"response_code":                  "400",
  1658  		"response_flags":                 "-",
  1659  	}
  1660  	q1m5 := model.Metric{
  1661  		"source_workload_namespace":      "bookinfo",
  1662  		"source_workload":                "productpage-v1",
  1663  		"source_canonical_service":       "productpage",
  1664  		"source_canonical_revision":      "v1",
  1665  		"source_cluster":                 "east",
  1666  		"destination_cluster":            "east",
  1667  		"destination_service_namespace":  "bookinfo",
  1668  		"destination_service":            "details:9080",
  1669  		"destination_service_name":       "details",
  1670  		"destination_workload_namespace": "bookinfo",
  1671  		"destination_workload":           "details-v1",
  1672  		"destination_canonical_service":  "details",
  1673  		"destination_canonical_revision": "v1",
  1674  		"request_protocol":               "http",
  1675  		"response_code":                  "500",
  1676  		"response_flags":                 "-",
  1677  	}
  1678  	q1m6 := model.Metric{
  1679  		"source_workload_namespace":      "bookinfo",
  1680  		"source_workload":                "productpage-v1",
  1681  		"source_canonical_service":       "productpage",
  1682  		"source_canonical_revision":      "v1",
  1683  		"source_cluster":                 "east",
  1684  		"destination_cluster":            "east",
  1685  		"destination_service_namespace":  "bookinfo",
  1686  		"destination_service":            "details:9080",
  1687  		"destination_service_name":       "details",
  1688  		"destination_workload_namespace": "bookinfo",
  1689  		"destination_workload":           "details-v1",
  1690  		"destination_canonical_service":  "details",
  1691  		"destination_canonical_revision": "v1",
  1692  		"request_protocol":               "http",
  1693  		"response_code":                  "200",
  1694  		"grpc_response_status":           "0",
  1695  		"response_flags":                 "-",
  1696  	}
  1697  	q1m7 := model.Metric{
  1698  		"source_workload_namespace":      "bookinfo",
  1699  		"source_workload":                "productpage-v1",
  1700  		"source_canonical_service":       "productpage",
  1701  		"source_canonical_revision":      "v1",
  1702  		"source_cluster":                 "east",
  1703  		"destination_cluster":            "east",
  1704  		"destination_service_namespace":  "bookinfo",
  1705  		"destination_service":            "productpage:9080",
  1706  		"destination_service_name":       "productpage",
  1707  		"destination_workload_namespace": "bookinfo",
  1708  		"destination_workload":           "productpage-v1",
  1709  		"destination_canonical_service":  "productpage",
  1710  		"destination_canonical_revision": "v1",
  1711  		"request_protocol":               "http",
  1712  		"response_code":                  "200",
  1713  		"grpc_response_status":           "0",
  1714  		"response_flags":                 "-",
  1715  	}
  1716  	q1m8 := model.Metric{
  1717  		"source_workload_namespace":      "bookinfo",
  1718  		"source_workload":                "productpage-v1",
  1719  		"source_canonical_service":       "productpage",
  1720  		"source_canonical_revision":      "v1",
  1721  		"source_cluster":                 "east",
  1722  		"destination_cluster":            "unknown",
  1723  		"destination_service_namespace":  "unknown",
  1724  		"destination_service":            "unknown",
  1725  		"destination_service_name":       "unknown",
  1726  		"destination_workload_namespace": "unknown",
  1727  		"destination_workload":           "unknown",
  1728  		"destination_canonical_service":  "unknown",
  1729  		"destination_canonical_revision": "unknown",
  1730  		"request_protocol":               "http",
  1731  		"response_code":                  "404",
  1732  		"response_flags":                 "NR",
  1733  	}
  1734  	v1 := model.Vector{
  1735  		&model.Sample{
  1736  			Metric: q1m0,
  1737  			Value:  20,
  1738  		},
  1739  		&model.Sample{
  1740  			Metric: q1m1,
  1741  			Value:  20,
  1742  		},
  1743  		&model.Sample{
  1744  			Metric: q1m2,
  1745  			Value:  20,
  1746  		},
  1747  		&model.Sample{
  1748  			Metric: q1m3,
  1749  			Value:  20,
  1750  		},
  1751  		&model.Sample{
  1752  			Metric: q1m4,
  1753  			Value:  20,
  1754  		},
  1755  		&model.Sample{
  1756  			Metric: q1m5,
  1757  			Value:  20,
  1758  		},
  1759  		&model.Sample{
  1760  			Metric: q1m6,
  1761  			Value:  20,
  1762  		},
  1763  		&model.Sample{
  1764  			Metric: q1m7,
  1765  			Value:  20,
  1766  		},
  1767  		&model.Sample{
  1768  			Metric: q1m8,
  1769  			Value:  4,
  1770  		},
  1771  	}
  1772  
  1773  	q2 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="destination",destination_workload_namespace="bookinfo",destination_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  1774  	v2 := model.Vector{}
  1775  
  1776  	q3 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace="bookinfo",source_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  1777  	q3m0 := model.Metric{
  1778  		"source_workload_namespace":      "bookinfo",
  1779  		"source_workload":                "productpage-v1",
  1780  		"source_canonical_service":       "productpage",
  1781  		"source_canonical_revision":      "v1",
  1782  		"source_cluster":                 "east",
  1783  		"destination_cluster":            "east",
  1784  		"destination_service_namespace":  "bookinfo",
  1785  		"destination_service":            "tcp:9080",
  1786  		"destination_service_name":       "tcp",
  1787  		"destination_workload_namespace": "bookinfo",
  1788  		"destination_workload":           "tcp-v1",
  1789  		"destination_canonical_service":  "tcp",
  1790  		"destination_canonical_revision": "v1",
  1791  		"response_flags":                 "-",
  1792  	}
  1793  	v3 := model.Vector{
  1794  		&model.Sample{
  1795  			Metric: q3m0,
  1796  			Value:  31,
  1797  		},
  1798  	}
  1799  
  1800  	client, xapi := setupMocked(t)
  1801  
  1802  	mockQuery(xapi, q0, &v0)
  1803  	mockQuery(xapi, q1, &v1)
  1804  	mockQuery(xapi, q2, &v2)
  1805  	mockQuery(xapi, q3, &v3)
  1806  
  1807  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  1808  
  1809  	mr := mux.NewRouter()
  1810  	mr.HandleFunc("/api/namespaces/{namespace}/workloads/{workload}/graph", http.HandlerFunc(
  1811  		func(w http.ResponseWriter, r *http.Request) {
  1812  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  1813  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  1814  			respond(w, code, config)
  1815  		}))
  1816  
  1817  	ts := httptest.NewServer(mr)
  1818  	defer ts.Close()
  1819  
  1820  	fut = graphNodeIstio
  1821  	url := ts.URL + "/api/namespaces/bookinfo/workloads/productpage-v1/graph?graphType=workload&appenders&queryTime=1523364075"
  1822  	resp, err := http.Get(url)
  1823  	if err != nil {
  1824  		t.Fatal(err)
  1825  	}
  1826  	actual, _ := io.ReadAll(resp.Body)
  1827  	expected, _ := os.ReadFile("testdata/test_workload_node_graph.expected")
  1828  	if runtime.GOOS == "windows" {
  1829  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  1830  	}
  1831  	expected = expected[:len(expected)-1] // remove EOF byte
  1832  
  1833  	assertObjectsEqual(t, expected, actual)
  1834  	assert.Equal(t, 200, resp.StatusCode)
  1835  }
  1836  
  1837  func TestAppNodeGraph(t *testing.T) {
  1838  	q0 := `round(sum(rate(istio_requests_total{reporter="destination",destination_service_namespace="bookinfo",destination_canonical_service="productpage"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  1839  	q0m0 := model.Metric{
  1840  		"source_workload_namespace":      "unknown",
  1841  		"source_workload":                "unknown",
  1842  		"source_canonical_service":       "unknown",
  1843  		"source_canonical_revision":      "unknown",
  1844  		"source_cluster":                 "unknown",
  1845  		"destination_cluster":            "east",
  1846  		"destination_service_namespace":  "bookinfo",
  1847  		"destination_service":            "productpage:9080",
  1848  		"destination_service_name":       "productpage",
  1849  		"destination_workload_namespace": "bookinfo",
  1850  		"destination_workload":           "productpage-v1",
  1851  		"destination_canonical_service":  "productpage",
  1852  		"destination_canonical_revision": "v1",
  1853  		"request_protocol":               "http",
  1854  		"response_code":                  "200",
  1855  		"grpc_response_status":           "0",
  1856  		"response_flags":                 "-",
  1857  	}
  1858  	q0m1 := model.Metric{
  1859  		"source_workload_namespace":      "istio-system",
  1860  		"source_workload":                "ingressgateway-unknown",
  1861  		"source_canonical_service":       "ingressgateway",
  1862  		"source_canonical_revision":      "latest",
  1863  		"source_cluster":                 "east",
  1864  		"destination_cluster":            "east",
  1865  		"destination_service_namespace":  "bookinfo",
  1866  		"destination_service":            "productpage:9080",
  1867  		"destination_service_name":       "productpage",
  1868  		"destination_workload_namespace": "bookinfo",
  1869  		"destination_workload":           "productpage-v1",
  1870  		"destination_canonical_service":  "productpage",
  1871  		"destination_canonical_revision": "v1",
  1872  		"request_protocol":               "http",
  1873  		"response_code":                  "200",
  1874  		"grpc_response_status":           "0",
  1875  		"response_flags":                 "-",
  1876  	}
  1877  	v0 := model.Vector{
  1878  		&model.Sample{
  1879  			Metric: q0m0,
  1880  			Value:  50,
  1881  		},
  1882  		&model.Sample{
  1883  			Metric: q0m1,
  1884  			Value:  100,
  1885  		},
  1886  	}
  1887  
  1888  	q1 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  1889  	q1m0 := model.Metric{
  1890  		"source_workload_namespace":      "bookinfo",
  1891  		"source_workload":                "productpage-v1",
  1892  		"source_canonical_service":       "productpage",
  1893  		"source_canonical_revision":      "v1",
  1894  		"source_cluster":                 "east",
  1895  		"destination_cluster":            "east",
  1896  		"destination_service_namespace":  "bookinfo",
  1897  		"destination_service":            "reviews:9080",
  1898  		"destination_service_name":       "reviews",
  1899  		"destination_workload_namespace": "bookinfo",
  1900  		"destination_workload":           "reviews-v1",
  1901  		"destination_canonical_service":  "reviews",
  1902  		"destination_canonical_revision": "v1",
  1903  		"request_protocol":               "http",
  1904  		"response_code":                  "200",
  1905  		"grpc_response_status":           "0",
  1906  		"response_flags":                 "-",
  1907  	}
  1908  	q1m1 := model.Metric{
  1909  		"source_workload_namespace":      "bookinfo",
  1910  		"source_workload":                "productpage-v1",
  1911  		"source_canonical_service":       "productpage",
  1912  		"source_canonical_revision":      "v1",
  1913  		"source_cluster":                 "east",
  1914  		"destination_cluster":            "east",
  1915  		"destination_service_namespace":  "bookinfo",
  1916  		"destination_service":            "reviews:9080",
  1917  		"destination_service_name":       "reviews",
  1918  		"destination_workload_namespace": "bookinfo",
  1919  		"destination_workload":           "reviews-v2",
  1920  		"destination_canonical_service":  "reviews",
  1921  		"destination_canonical_revision": "v2",
  1922  		"request_protocol":               "http",
  1923  		"response_code":                  "200",
  1924  		"grpc_response_status":           "0",
  1925  		"response_flags":                 "-",
  1926  	}
  1927  	q1m2 := model.Metric{
  1928  		"source_workload_namespace":      "bookinfo",
  1929  		"source_workload":                "productpage-v1",
  1930  		"source_canonical_service":       "productpage",
  1931  		"source_canonical_revision":      "v1",
  1932  		"source_cluster":                 "east",
  1933  		"destination_cluster":            "east",
  1934  		"destination_service_namespace":  "bookinfo",
  1935  		"destination_service":            "reviews:9080",
  1936  		"destination_service_name":       "reviews",
  1937  		"destination_workload_namespace": "bookinfo",
  1938  		"destination_workload":           "reviews-v3",
  1939  		"destination_canonical_service":  "reviews",
  1940  		"destination_canonical_revision": "v3",
  1941  		"request_protocol":               "http",
  1942  		"response_code":                  "200",
  1943  		"grpc_response_status":           "0",
  1944  		"response_flags":                 "-",
  1945  	}
  1946  	q1m3 := model.Metric{
  1947  		"source_workload_namespace":      "bookinfo",
  1948  		"source_workload":                "productpage-v1",
  1949  		"source_canonical_service":       "productpage",
  1950  		"source_canonical_revision":      "v1",
  1951  		"source_cluster":                 "east",
  1952  		"destination_cluster":            "east",
  1953  		"destination_service_namespace":  "bookinfo",
  1954  		"destination_service":            "details:9080",
  1955  		"destination_service_name":       "details",
  1956  		"destination_workload_namespace": "bookinfo",
  1957  		"destination_workload":           "details-v1",
  1958  		"destination_canonical_service":  "details",
  1959  		"destination_canonical_revision": "v1",
  1960  		"request_protocol":               "http",
  1961  		"response_code":                  "300",
  1962  		"response_flags":                 "-",
  1963  	}
  1964  	q1m4 := model.Metric{
  1965  		"source_workload_namespace":      "bookinfo",
  1966  		"source_workload":                "productpage-v1",
  1967  		"source_canonical_service":       "productpage",
  1968  		"source_canonical_revision":      "v1",
  1969  		"source_cluster":                 "east",
  1970  		"destination_cluster":            "east",
  1971  		"destination_service_namespace":  "bookinfo",
  1972  		"destination_service":            "details:9080",
  1973  		"destination_service_name":       "details",
  1974  		"destination_workload_namespace": "bookinfo",
  1975  		"destination_workload":           "details-v1",
  1976  		"destination_canonical_service":  "details",
  1977  		"destination_canonical_revision": "v1",
  1978  		"request_protocol":               "http",
  1979  		"response_code":                  "400",
  1980  		"response_flags":                 "-",
  1981  	}
  1982  	q1m5 := model.Metric{
  1983  		"source_workload_namespace":      "bookinfo",
  1984  		"source_workload":                "productpage-v1",
  1985  		"source_canonical_service":       "productpage",
  1986  		"source_canonical_revision":      "v1",
  1987  		"source_cluster":                 "east",
  1988  		"destination_cluster":            "east",
  1989  		"destination_service_namespace":  "bookinfo",
  1990  		"destination_service":            "details:9080",
  1991  		"destination_service_name":       "details",
  1992  		"destination_workload_namespace": "bookinfo",
  1993  		"destination_workload":           "details-v1",
  1994  		"destination_canonical_service":  "details",
  1995  		"destination_canonical_revision": "v1",
  1996  		"request_protocol":               "http",
  1997  		"response_code":                  "500",
  1998  		"response_flags":                 "-",
  1999  	}
  2000  	q1m6 := model.Metric{
  2001  		"source_workload_namespace":      "bookinfo",
  2002  		"source_workload":                "productpage-v1",
  2003  		"source_canonical_service":       "productpage",
  2004  		"source_canonical_revision":      "v1",
  2005  		"source_cluster":                 "east",
  2006  		"destination_cluster":            "east",
  2007  		"destination_service_namespace":  "bookinfo",
  2008  		"destination_service":            "details:9080",
  2009  		"destination_service_name":       "details",
  2010  		"destination_workload_namespace": "bookinfo",
  2011  		"destination_workload":           "details-v1",
  2012  		"destination_canonical_service":  "details",
  2013  		"destination_canonical_revision": "v1",
  2014  		"request_protocol":               "http",
  2015  		"response_code":                  "200",
  2016  		"grpc_response_status":           "0",
  2017  		"response_flags":                 "-",
  2018  	}
  2019  	q1m7 := model.Metric{
  2020  		"source_workload_namespace":      "bookinfo",
  2021  		"source_workload":                "productpage-v1",
  2022  		"source_canonical_service":       "productpage",
  2023  		"source_canonical_revision":      "v1",
  2024  		"source_cluster":                 "east",
  2025  		"destination_cluster":            "east",
  2026  		"destination_service_namespace":  "bookinfo",
  2027  		"destination_service":            "productpage:9080",
  2028  		"destination_service_name":       "productpage",
  2029  		"destination_workload_namespace": "bookinfo",
  2030  		"destination_workload":           "productpage-v1",
  2031  		"destination_canonical_service":  "productpage",
  2032  		"destination_canonical_revision": "v1",
  2033  		"request_protocol":               "http",
  2034  		"response_code":                  "200",
  2035  		"grpc_response_status":           "0",
  2036  		"response_flags":                 "-",
  2037  	}
  2038  	q1m8 := model.Metric{
  2039  		"source_workload_namespace":      "bookinfo",
  2040  		"source_workload":                "productpage-v1",
  2041  		"source_canonical_service":       "productpage",
  2042  		"source_canonical_revision":      "v1",
  2043  		"source_cluster":                 "east",
  2044  		"destination_cluster":            "unknown",
  2045  		"destination_service_namespace":  "unknown",
  2046  		"destination_service":            "unknown",
  2047  		"destination_service_name":       "unknown",
  2048  		"destination_workload_namespace": "unknown",
  2049  		"destination_workload":           "unknown",
  2050  		"destination_canonical_service":  "unknown",
  2051  		"destination_canonical_revision": "unknown",
  2052  		"request_protocol":               "http",
  2053  		"response_code":                  "404",
  2054  		"response_flags":                 "NR",
  2055  	}
  2056  	v1 := model.Vector{
  2057  		&model.Sample{
  2058  			Metric: q1m0,
  2059  			Value:  20,
  2060  		},
  2061  		&model.Sample{
  2062  			Metric: q1m1,
  2063  			Value:  20,
  2064  		},
  2065  		&model.Sample{
  2066  			Metric: q1m2,
  2067  			Value:  20,
  2068  		},
  2069  		&model.Sample{
  2070  			Metric: q1m3,
  2071  			Value:  20,
  2072  		},
  2073  		&model.Sample{
  2074  			Metric: q1m4,
  2075  			Value:  20,
  2076  		},
  2077  		&model.Sample{
  2078  			Metric: q1m5,
  2079  			Value:  20,
  2080  		},
  2081  		&model.Sample{
  2082  			Metric: q1m6,
  2083  			Value:  20,
  2084  		},
  2085  		&model.Sample{
  2086  			Metric: q1m7,
  2087  			Value:  20,
  2088  		},
  2089  		&model.Sample{
  2090  			Metric: q1m8,
  2091  			Value:  4,
  2092  		},
  2093  	}
  2094  
  2095  	q2 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="destination",destination_service_namespace="bookinfo",destination_canonical_service="productpage"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2096  	v2 := model.Vector{}
  2097  
  2098  	q3 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2099  	q3m0 := model.Metric{
  2100  		"source_workload_namespace":      "bookinfo",
  2101  		"source_workload":                "productpage-v1",
  2102  		"source_canonical_service":       "productpage",
  2103  		"source_canonical_revision":      "v1",
  2104  		"source_cluster":                 "east",
  2105  		"destination_cluster":            "east",
  2106  		"destination_service_namespace":  "bookinfo",
  2107  		"destination_service":            "tcp:9080",
  2108  		"destination_service_name":       "tcp",
  2109  		"destination_workload_namespace": "bookinfo",
  2110  		"destination_workload":           "tcp-v1",
  2111  		"destination_canonical_service":  "tcp",
  2112  		"destination_canonical_revision": "v1",
  2113  		"response_flags":                 "-",
  2114  	}
  2115  	v3 := model.Vector{
  2116  		&model.Sample{
  2117  			Metric: q3m0,
  2118  			Value:  31,
  2119  		},
  2120  	}
  2121  
  2122  	client, xapi := setupMocked(t)
  2123  
  2124  	mockQuery(xapi, q0, &v0)
  2125  	mockQuery(xapi, q1, &v1)
  2126  	mockQuery(xapi, q2, &v2)
  2127  	mockQuery(xapi, q3, &v3)
  2128  
  2129  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  2130  
  2131  	mr := mux.NewRouter()
  2132  	mr.HandleFunc("/api/namespaces/{namespace}/applications/{app}/graph", http.HandlerFunc(
  2133  		func(w http.ResponseWriter, r *http.Request) {
  2134  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  2135  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  2136  			respond(w, code, config)
  2137  		}))
  2138  
  2139  	ts := httptest.NewServer(mr)
  2140  	defer ts.Close()
  2141  
  2142  	fut = graphNodeIstio
  2143  	url := ts.URL + "/api/namespaces/bookinfo/applications/productpage/graph?graphType=versionedApp&appenders&queryTime=1523364075"
  2144  	resp, err := http.Get(url)
  2145  	if err != nil {
  2146  		t.Fatal(err)
  2147  	}
  2148  	actual, _ := io.ReadAll(resp.Body)
  2149  	expected, _ := os.ReadFile("testdata/test_app_node_graph.expected")
  2150  	if runtime.GOOS == "windows" {
  2151  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  2152  	}
  2153  	expected = expected[:len(expected)-1] // remove EOF byte
  2154  
  2155  	assertObjectsEqual(t, expected, actual)
  2156  	assert.Equal(t, 200, resp.StatusCode)
  2157  }
  2158  
  2159  func TestVersionedAppNodeGraph(t *testing.T) {
  2160  	q0 := `round(sum(rate(istio_requests_total{reporter="destination",destination_service_namespace="bookinfo",destination_canonical_service="productpage",destination_canonical_revision="v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  2161  	q0m0 := model.Metric{
  2162  		"source_workload_namespace":      "unknown",
  2163  		"source_workload":                "unknown",
  2164  		"source_canonical_service":       "unknown",
  2165  		"source_canonical_revision":      "unknown",
  2166  		"source_cluster":                 "unknown",
  2167  		"destination_cluster":            "east",
  2168  		"destination_service_namespace":  "bookinfo",
  2169  		"destination_service":            "productpage:9080",
  2170  		"destination_service_name":       "productpage",
  2171  		"destination_workload_namespace": "bookinfo",
  2172  		"destination_workload":           "productpage-v1",
  2173  		"destination_canonical_service":  "productpage",
  2174  		"destination_canonical_revision": "v1",
  2175  		"request_protocol":               "http",
  2176  		"response_code":                  "200",
  2177  		"grpc_response_status":           "0",
  2178  		"response_flags":                 "-",
  2179  	}
  2180  	q0m1 := model.Metric{
  2181  		"source_workload_namespace":      "istio-system",
  2182  		"source_workload":                "ingressgateway-unknown",
  2183  		"source_canonical_service":       "ingressgateway",
  2184  		"source_canonical_revision":      "latest",
  2185  		"source_cluster":                 "east",
  2186  		"destination_cluster":            "east",
  2187  		"destination_service_namespace":  "bookinfo",
  2188  		"destination_service":            "productpage:9080",
  2189  		"destination_service_name":       "productpage",
  2190  		"destination_workload_namespace": "bookinfo",
  2191  		"destination_workload":           "productpage-v1",
  2192  		"destination_canonical_service":  "productpage",
  2193  		"destination_canonical_revision": "v1",
  2194  		"request_protocol":               "http",
  2195  		"response_code":                  "200",
  2196  		"grpc_response_status":           "0",
  2197  		"response_flags":                 "-",
  2198  	}
  2199  	v0 := model.Vector{
  2200  		&model.Sample{
  2201  			Metric: q0m0,
  2202  			Value:  50,
  2203  		},
  2204  		&model.Sample{
  2205  			Metric: q0m1,
  2206  			Value:  100,
  2207  		},
  2208  	}
  2209  
  2210  	q1 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage",source_canonical_revision="v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  2211  	q1m0 := model.Metric{
  2212  		"source_workload_namespace":      "bookinfo",
  2213  		"source_workload":                "productpage-v1",
  2214  		"source_canonical_service":       "productpage",
  2215  		"source_canonical_revision":      "v1",
  2216  		"source_cluster":                 "east",
  2217  		"destination_cluster":            "east",
  2218  		"destination_service_namespace":  "bookinfo",
  2219  		"destination_service":            "reviews:9080",
  2220  		"destination_service_name":       "reviews",
  2221  		"destination_workload_namespace": "bookinfo",
  2222  		"destination_workload":           "reviews-v1",
  2223  		"destination_canonical_service":  "reviews",
  2224  		"destination_canonical_revision": "v1",
  2225  		"request_protocol":               "http",
  2226  		"response_code":                  "200",
  2227  		"grpc_response_status":           "0",
  2228  		"response_flags":                 "-",
  2229  	}
  2230  	q1m1 := model.Metric{
  2231  		"source_workload_namespace":      "bookinfo",
  2232  		"source_workload":                "productpage-v1",
  2233  		"source_canonical_service":       "productpage",
  2234  		"source_canonical_revision":      "v1",
  2235  		"source_cluster":                 "east",
  2236  		"destination_cluster":            "east",
  2237  		"destination_service_namespace":  "bookinfo",
  2238  		"destination_service":            "reviews:9080",
  2239  		"destination_service_name":       "reviews",
  2240  		"destination_workload_namespace": "bookinfo",
  2241  		"destination_workload":           "reviews-v2",
  2242  		"destination_canonical_service":  "reviews",
  2243  		"destination_canonical_revision": "v2",
  2244  		"request_protocol":               "http",
  2245  		"response_code":                  "200",
  2246  		"grpc_response_status":           "0",
  2247  		"response_flags":                 "-",
  2248  	}
  2249  	q1m2 := model.Metric{
  2250  		"source_workload_namespace":      "bookinfo",
  2251  		"source_workload":                "productpage-v1",
  2252  		"source_canonical_service":       "productpage",
  2253  		"source_canonical_revision":      "v1",
  2254  		"source_cluster":                 "east",
  2255  		"destination_cluster":            "east",
  2256  		"destination_service_namespace":  "bookinfo",
  2257  		"destination_service":            "reviews:9080",
  2258  		"destination_service_name":       "reviews",
  2259  		"destination_workload_namespace": "bookinfo",
  2260  		"destination_workload":           "reviews-v3",
  2261  		"destination_canonical_service":  "reviews",
  2262  		"destination_canonical_revision": "v3",
  2263  		"request_protocol":               "http",
  2264  		"response_code":                  "200",
  2265  		"grpc_response_status":           "0",
  2266  		"response_flags":                 "-",
  2267  	}
  2268  	q1m3 := model.Metric{
  2269  		"source_workload_namespace":      "bookinfo",
  2270  		"source_workload":                "productpage-v1",
  2271  		"source_canonical_service":       "productpage",
  2272  		"source_canonical_revision":      "v1",
  2273  		"source_cluster":                 "east",
  2274  		"destination_cluster":            "east",
  2275  		"destination_service_namespace":  "bookinfo",
  2276  		"destination_service":            "details:9080",
  2277  		"destination_service_name":       "details",
  2278  		"destination_workload_namespace": "bookinfo",
  2279  		"destination_workload":           "details-v1",
  2280  		"destination_canonical_service":  "details",
  2281  		"destination_canonical_revision": "v1",
  2282  		"request_protocol":               "http",
  2283  		"response_code":                  "300",
  2284  		"response_flags":                 "-",
  2285  	}
  2286  	q1m4 := model.Metric{
  2287  		"source_workload_namespace":      "bookinfo",
  2288  		"source_workload":                "productpage-v1",
  2289  		"source_canonical_service":       "productpage",
  2290  		"source_canonical_revision":      "v1",
  2291  		"source_cluster":                 "east",
  2292  		"destination_cluster":            "east",
  2293  		"destination_service_namespace":  "bookinfo",
  2294  		"destination_service":            "details:9080",
  2295  		"destination_service_name":       "details",
  2296  		"destination_workload_namespace": "bookinfo",
  2297  		"destination_workload":           "details-v1",
  2298  		"destination_canonical_service":  "details",
  2299  		"destination_canonical_revision": "v1",
  2300  		"request_protocol":               "http",
  2301  		"response_code":                  "400",
  2302  		"response_flags":                 "-",
  2303  	}
  2304  	q1m5 := model.Metric{
  2305  		"source_workload_namespace":      "bookinfo",
  2306  		"source_workload":                "productpage-v1",
  2307  		"source_canonical_service":       "productpage",
  2308  		"source_canonical_revision":      "v1",
  2309  		"source_cluster":                 "east",
  2310  		"destination_cluster":            "east",
  2311  		"destination_service_namespace":  "bookinfo",
  2312  		"destination_service":            "details:9080",
  2313  		"destination_service_name":       "details",
  2314  		"destination_workload_namespace": "bookinfo",
  2315  		"destination_workload":           "details-v1",
  2316  		"destination_canonical_service":  "details",
  2317  		"destination_canonical_revision": "v1",
  2318  		"request_protocol":               "http",
  2319  		"response_code":                  "500",
  2320  		"response_flags":                 "-",
  2321  	}
  2322  	q1m6 := model.Metric{
  2323  		"source_workload_namespace":      "bookinfo",
  2324  		"source_workload":                "productpage-v1",
  2325  		"source_canonical_service":       "productpage",
  2326  		"source_canonical_revision":      "v1",
  2327  		"source_cluster":                 "east",
  2328  		"destination_cluster":            "east",
  2329  		"destination_service_namespace":  "bookinfo",
  2330  		"destination_service":            "details:9080",
  2331  		"destination_service_name":       "details",
  2332  		"destination_workload_namespace": "bookinfo",
  2333  		"destination_workload":           "details-v1",
  2334  		"destination_canonical_service":  "details",
  2335  		"destination_canonical_revision": "v1",
  2336  		"request_protocol":               "http",
  2337  		"response_code":                  "200",
  2338  		"grpc_response_status":           "0",
  2339  		"response_flags":                 "-",
  2340  	}
  2341  	q1m7 := model.Metric{
  2342  		"source_workload_namespace":      "bookinfo",
  2343  		"source_workload":                "productpage-v1",
  2344  		"source_canonical_service":       "productpage",
  2345  		"source_canonical_revision":      "v1",
  2346  		"source_cluster":                 "east",
  2347  		"destination_cluster":            "east",
  2348  		"destination_service_namespace":  "bookinfo",
  2349  		"destination_service":            "productpage:9080",
  2350  		"destination_service_name":       "productpage",
  2351  		"destination_workload_namespace": "bookinfo",
  2352  		"destination_workload":           "productpage-v1",
  2353  		"destination_canonical_service":  "productpage",
  2354  		"destination_canonical_revision": "v1",
  2355  		"request_protocol":               "http",
  2356  		"response_code":                  "200",
  2357  		"grpc_response_status":           "0",
  2358  		"response_flags":                 "-",
  2359  	}
  2360  	q1m8 := model.Metric{
  2361  		"source_workload_namespace":      "bookinfo",
  2362  		"source_workload":                "productpage-v1",
  2363  		"source_canonical_service":       "productpage",
  2364  		"source_canonical_revision":      "v1",
  2365  		"source_cluster":                 "east",
  2366  		"destination_cluster":            "unknown",
  2367  		"destination_service_namespace":  "unknown",
  2368  		"destination_service":            "unknown",
  2369  		"destination_service_name":       "unknown",
  2370  		"destination_workload_namespace": "unknown",
  2371  		"destination_workload":           "unknown",
  2372  		"destination_canonical_service":  "unknown",
  2373  		"destination_canonical_revision": "unknown",
  2374  		"request_protocol":               "http",
  2375  		"response_code":                  "404",
  2376  		"response_flags":                 "NR",
  2377  	}
  2378  	v1 := model.Vector{
  2379  		&model.Sample{
  2380  			Metric: q1m0,
  2381  			Value:  20,
  2382  		},
  2383  		&model.Sample{
  2384  			Metric: q1m1,
  2385  			Value:  20,
  2386  		},
  2387  		&model.Sample{
  2388  			Metric: q1m2,
  2389  			Value:  20,
  2390  		},
  2391  		&model.Sample{
  2392  			Metric: q1m3,
  2393  			Value:  20,
  2394  		},
  2395  		&model.Sample{
  2396  			Metric: q1m4,
  2397  			Value:  20,
  2398  		},
  2399  		&model.Sample{
  2400  			Metric: q1m5,
  2401  			Value:  20,
  2402  		},
  2403  		&model.Sample{
  2404  			Metric: q1m6,
  2405  			Value:  20,
  2406  		},
  2407  		&model.Sample{
  2408  			Metric: q1m7,
  2409  			Value:  20,
  2410  		},
  2411  		&model.Sample{
  2412  			Metric: q1m8,
  2413  			Value:  4,
  2414  		},
  2415  	}
  2416  
  2417  	q2 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="destination",destination_service_namespace="bookinfo",destination_canonical_service="productpage",destination_canonical_revision="v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2418  	v2 := model.Vector{}
  2419  
  2420  	q3 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage",source_canonical_revision="v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2421  	q3m0 := model.Metric{
  2422  		"source_workload_namespace":      "bookinfo",
  2423  		"source_workload":                "productpage-v1",
  2424  		"source_canonical_service":       "productpage",
  2425  		"source_canonical_revision":      "v1",
  2426  		"source_cluster":                 "east",
  2427  		"destination_cluster":            "east",
  2428  		"destination_service_namespace":  "bookinfo",
  2429  		"destination_service":            "tcp:9080",
  2430  		"destination_service_name":       "tcp",
  2431  		"destination_workload_namespace": "bookinfo",
  2432  		"destination_workload":           "tcp-v1",
  2433  		"destination_canonical_service":  "tcp",
  2434  		"destination_canonical_revision": "v1",
  2435  		"response_flags":                 "-",
  2436  	}
  2437  	v3 := model.Vector{
  2438  		&model.Sample{
  2439  			Metric: q3m0,
  2440  			Value:  31,
  2441  		},
  2442  	}
  2443  
  2444  	client, xapi := setupMocked(t)
  2445  
  2446  	mockQuery(xapi, q0, &v0)
  2447  	mockQuery(xapi, q1, &v1)
  2448  	mockQuery(xapi, q2, &v2)
  2449  	mockQuery(xapi, q3, &v3)
  2450  
  2451  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  2452  
  2453  	mr := mux.NewRouter()
  2454  	mr.HandleFunc("/api/namespaces/{namespace}/applications/{app}/versions/{version}/graph", http.HandlerFunc(
  2455  		func(w http.ResponseWriter, r *http.Request) {
  2456  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  2457  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  2458  			respond(w, code, config)
  2459  		}))
  2460  
  2461  	ts := httptest.NewServer(mr)
  2462  	defer ts.Close()
  2463  
  2464  	fut = graphNodeIstio
  2465  	url := ts.URL + "/api/namespaces/bookinfo/applications/productpage/versions/v1/graph?graphType=versionedApp&appenders&queryTime=1523364075"
  2466  	resp, err := http.Get(url)
  2467  	if err != nil {
  2468  		t.Fatal(err)
  2469  	}
  2470  	actual, _ := io.ReadAll(resp.Body)
  2471  	expected, _ := os.ReadFile("testdata/test_versioned_app_node_graph.expected")
  2472  	if runtime.GOOS == "windows" {
  2473  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  2474  	}
  2475  	expected = expected[:len(expected)-1] // remove EOF byte
  2476  
  2477  	assertObjectsEqual(t, expected, actual)
  2478  	assert.Equal(t, 200, resp.StatusCode)
  2479  }
  2480  
  2481  func TestServiceNodeGraph(t *testing.T) {
  2482  	q0 := `round(sum(rate(istio_requests_total{reporter="source",destination_workload="unknown",destination_service=~"^productpage\\.bookinfo\\..*$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  2483  	v0 := model.Vector{}
  2484  
  2485  	q1 := `round(sum(rate(istio_requests_total{reporter="destination",destination_service_namespace="bookinfo",destination_service=~"^productpage\\.bookinfo\\..*$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  2486  	q1m0 := model.Metric{
  2487  		"source_workload_namespace":      "istio-system",
  2488  		"source_workload":                "ingressgateway-unknown",
  2489  		"source_canonical_service":       "ingressgateway",
  2490  		"source_canonical_revision":      "latest",
  2491  		"source_cluster":                 "east",
  2492  		"destination_cluster":            "east",
  2493  		"destination_service_namespace":  "bookinfo",
  2494  		"destination_service":            "productpage:9080",
  2495  		"destination_service_name":       "productpage",
  2496  		"destination_workload_namespace": "bookinfo",
  2497  		"destination_workload":           "productpage-v1",
  2498  		"destination_canonical_service":  "productpage",
  2499  		"destination_canonical_revision": "v1",
  2500  		"request_protocol":               "http",
  2501  		"response_code":                  "200",
  2502  		"grpc_response_status":           "0",
  2503  		"response_flags":                 "-",
  2504  	}
  2505  	v1 := model.Vector{
  2506  		&model.Sample{
  2507  			Metric: q1m0,
  2508  			Value:  100,
  2509  		},
  2510  	}
  2511  
  2512  	q2 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="destination",destination_service_namespace="bookinfo",destination_service=~"^productpage\\.bookinfo\\..*$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2513  	q2m0 := model.Metric{
  2514  		"source_workload_namespace":      "istio-system",
  2515  		"source_workload":                "ingressgateway-unknown",
  2516  		"source_canonical_service":       "ingressgateway",
  2517  		"source_canonical_revision":      "latest",
  2518  		"source_cluster":                 "east",
  2519  		"destination_cluster":            "east",
  2520  		"destination_service_namespace":  "bookinfo",
  2521  		"destination_service":            "productpage:9080",
  2522  		"destination_service_name":       "productpage",
  2523  		"destination_workload_namespace": "bookinfo",
  2524  		"destination_workload":           "productpage-v1",
  2525  		"destination_canonical_service":  "productpage",
  2526  		"destination_canonical_revision": "v1",
  2527  		"response_flags":                 "-",
  2528  	}
  2529  	v2 := model.Vector{
  2530  		&model.Sample{
  2531  			Metric: q2m0,
  2532  			Value:  31,
  2533  		},
  2534  	}
  2535  
  2536  	client, xapi := setupMocked(t)
  2537  
  2538  	mockQuery(xapi, q0, &v0)
  2539  	mockQuery(xapi, q1, &v1)
  2540  	mockQuery(xapi, q2, &v2)
  2541  
  2542  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  2543  
  2544  	mr := mux.NewRouter()
  2545  	mr.HandleFunc("/api/namespaces/{namespace}/services/{service}/graph", http.HandlerFunc(
  2546  		func(w http.ResponseWriter, r *http.Request) {
  2547  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  2548  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  2549  			respond(w, code, config)
  2550  		}))
  2551  
  2552  	ts := httptest.NewServer(mr)
  2553  	defer ts.Close()
  2554  
  2555  	fut = graphNodeIstio
  2556  	url := ts.URL + "/api/namespaces/bookinfo/services/productpage/graph?graphType=workload&appenders&queryTime=1523364075"
  2557  	resp, err := http.Get(url)
  2558  	if err != nil {
  2559  		t.Fatal(err)
  2560  	}
  2561  	actual, _ := io.ReadAll(resp.Body)
  2562  	expected, _ := os.ReadFile("testdata/test_service_node_graph.expected")
  2563  	if runtime.GOOS == "windows" {
  2564  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  2565  	}
  2566  	expected = expected[:len(expected)-1] // remove EOF byte
  2567  
  2568  	assertObjectsEqual(t, expected, actual)
  2569  	assert.Equal(t, 200, resp.StatusCode)
  2570  }
  2571  
  2572  func TestRatesNodeGraphTotal(t *testing.T) {
  2573  	q0 := `round(sum(rate(istio_requests_total{reporter="destination",destination_workload_namespace="bookinfo",destination_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  2574  	q0m0 := model.Metric{
  2575  		"source_workload_namespace":      "unknown",
  2576  		"source_workload":                "unknown",
  2577  		"source_canonical_service":       "unknown",
  2578  		"source_canonical_revision":      "unknown",
  2579  		"source_cluster":                 "unknown",
  2580  		"destination_cluster":            "east",
  2581  		"destination_service_namespace":  "bookinfo",
  2582  		"destination_service":            "productpage:9080",
  2583  		"destination_service_name":       "productpage",
  2584  		"destination_workload_namespace": "bookinfo",
  2585  		"destination_workload":           "productpage-v1",
  2586  		"destination_canonical_service":  "productpage",
  2587  		"destination_canonical_revision": "v1",
  2588  		"request_protocol":               "http",
  2589  		"response_code":                  "200",
  2590  		"grpc_response_status":           "0",
  2591  		"response_flags":                 "-",
  2592  	}
  2593  	q0m1 := model.Metric{
  2594  		"source_workload_namespace":      "istio-system",
  2595  		"source_workload":                "ingressgateway-unknown",
  2596  		"source_canonical_service":       "ingressgateway",
  2597  		"source_canonical_revision":      "latest",
  2598  		"source_cluster":                 "east",
  2599  		"destination_cluster":            "east",
  2600  		"destination_service_namespace":  "bookinfo",
  2601  		"destination_service":            "productpage:9080",
  2602  		"destination_service_name":       "productpage",
  2603  		"destination_workload_namespace": "bookinfo",
  2604  		"destination_workload":           "productpage-v1",
  2605  		"destination_canonical_service":  "productpage",
  2606  		"destination_canonical_revision": "v1",
  2607  		"request_protocol":               "http",
  2608  		"response_code":                  "200",
  2609  		"grpc_response_status":           "0",
  2610  		"response_flags":                 "-",
  2611  	}
  2612  	v0 := model.Vector{
  2613  		&model.Sample{
  2614  			Metric: q0m0,
  2615  			Value:  50,
  2616  		},
  2617  		&model.Sample{
  2618  			Metric: q0m1,
  2619  			Value:  100,
  2620  		},
  2621  	}
  2622  
  2623  	q1 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo",source_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  2624  	q1m0 := model.Metric{
  2625  		"source_workload_namespace":      "bookinfo",
  2626  		"source_workload":                "productpage-v1",
  2627  		"source_canonical_service":       "productpage",
  2628  		"source_canonical_revision":      "v1",
  2629  		"source_cluster":                 "east",
  2630  		"destination_cluster":            "east",
  2631  		"destination_service_namespace":  "bookinfo",
  2632  		"destination_service":            "reviews:9080",
  2633  		"destination_service_name":       "reviews",
  2634  		"destination_workload_namespace": "bookinfo",
  2635  		"destination_workload":           "reviews-v1",
  2636  		"destination_canonical_service":  "reviews",
  2637  		"destination_canonical_revision": "v1",
  2638  		"request_protocol":               "http",
  2639  		"response_code":                  "200",
  2640  		"grpc_response_status":           "0",
  2641  		"response_flags":                 "-",
  2642  	}
  2643  	q1m1 := model.Metric{
  2644  		"source_workload_namespace":      "bookinfo",
  2645  		"source_workload":                "productpage-v1",
  2646  		"source_canonical_service":       "productpage",
  2647  		"source_canonical_revision":      "v1",
  2648  		"source_cluster":                 "east",
  2649  		"destination_cluster":            "east",
  2650  		"destination_service_namespace":  "bookinfo",
  2651  		"destination_service":            "reviews:9080",
  2652  		"destination_service_name":       "reviews",
  2653  		"destination_workload_namespace": "bookinfo",
  2654  		"destination_workload":           "reviews-v2",
  2655  		"destination_canonical_service":  "reviews",
  2656  		"destination_canonical_revision": "v2",
  2657  		"request_protocol":               "http",
  2658  		"response_code":                  "200",
  2659  		"grpc_response_status":           "0",
  2660  		"response_flags":                 "-",
  2661  	}
  2662  	q1m2 := model.Metric{
  2663  		"source_workload_namespace":      "bookinfo",
  2664  		"source_workload":                "productpage-v1",
  2665  		"source_canonical_service":       "productpage",
  2666  		"source_canonical_revision":      "v1",
  2667  		"source_cluster":                 "east",
  2668  		"destination_cluster":            "east",
  2669  		"destination_service_namespace":  "bookinfo",
  2670  		"destination_service":            "reviews:9080",
  2671  		"destination_service_name":       "reviews",
  2672  		"destination_workload_namespace": "bookinfo",
  2673  		"destination_workload":           "reviews-v3",
  2674  		"destination_canonical_service":  "reviews",
  2675  		"destination_canonical_revision": "v3",
  2676  		"request_protocol":               "http",
  2677  		"response_code":                  "200",
  2678  		"grpc_response_status":           "0",
  2679  		"response_flags":                 "-",
  2680  	}
  2681  	q1m3 := model.Metric{
  2682  		"source_workload_namespace":      "bookinfo",
  2683  		"source_workload":                "productpage-v1",
  2684  		"source_canonical_service":       "productpage",
  2685  		"source_canonical_revision":      "v1",
  2686  		"source_cluster":                 "east",
  2687  		"destination_cluster":            "east",
  2688  		"destination_service_namespace":  "bookinfo",
  2689  		"destination_service":            "details:9080",
  2690  		"destination_service_name":       "details",
  2691  		"destination_workload_namespace": "bookinfo",
  2692  		"destination_workload":           "details-v1",
  2693  		"destination_canonical_service":  "details",
  2694  		"destination_canonical_revision": "v1",
  2695  		"request_protocol":               "http",
  2696  		"response_code":                  "300",
  2697  		"response_flags":                 "-",
  2698  	}
  2699  	q1m4 := model.Metric{
  2700  		"source_workload_namespace":      "bookinfo",
  2701  		"source_workload":                "productpage-v1",
  2702  		"source_canonical_service":       "productpage",
  2703  		"source_canonical_revision":      "v1",
  2704  		"source_cluster":                 "east",
  2705  		"destination_cluster":            "east",
  2706  		"destination_service_namespace":  "bookinfo",
  2707  		"destination_service":            "details:9080",
  2708  		"destination_service_name":       "details",
  2709  		"destination_workload_namespace": "bookinfo",
  2710  		"destination_workload":           "details-v1",
  2711  		"destination_canonical_service":  "details",
  2712  		"destination_canonical_revision": "v1",
  2713  		"request_protocol":               "http",
  2714  		"response_code":                  "400",
  2715  		"response_flags":                 "-",
  2716  	}
  2717  	q1m5 := model.Metric{
  2718  		"source_workload_namespace":      "bookinfo",
  2719  		"source_workload":                "productpage-v1",
  2720  		"source_canonical_service":       "productpage",
  2721  		"source_canonical_revision":      "v1",
  2722  		"source_cluster":                 "east",
  2723  		"destination_cluster":            "east",
  2724  		"destination_service_namespace":  "bookinfo",
  2725  		"destination_service":            "details:9080",
  2726  		"destination_service_name":       "details",
  2727  		"destination_workload_namespace": "bookinfo",
  2728  		"destination_workload":           "details-v1",
  2729  		"destination_canonical_service":  "details",
  2730  		"destination_canonical_revision": "v1",
  2731  		"request_protocol":               "http",
  2732  		"response_code":                  "500",
  2733  		"response_flags":                 "-",
  2734  	}
  2735  	q1m6 := model.Metric{
  2736  		"source_workload_namespace":      "bookinfo",
  2737  		"source_workload":                "productpage-v1",
  2738  		"source_canonical_service":       "productpage",
  2739  		"source_canonical_revision":      "v1",
  2740  		"source_cluster":                 "east",
  2741  		"destination_cluster":            "east",
  2742  		"destination_service_namespace":  "bookinfo",
  2743  		"destination_service":            "details:9080",
  2744  		"destination_service_name":       "details",
  2745  		"destination_workload_namespace": "bookinfo",
  2746  		"destination_workload":           "details-v1",
  2747  		"destination_canonical_service":  "details",
  2748  		"destination_canonical_revision": "v1",
  2749  		"request_protocol":               "http",
  2750  		"response_code":                  "200",
  2751  		"grpc_response_status":           "0",
  2752  		"response_flags":                 "-",
  2753  	}
  2754  	q1m7 := model.Metric{
  2755  		"source_workload_namespace":      "bookinfo",
  2756  		"source_workload":                "productpage-v1",
  2757  		"source_canonical_service":       "productpage",
  2758  		"source_canonical_revision":      "v1",
  2759  		"source_cluster":                 "east",
  2760  		"destination_cluster":            "east",
  2761  		"destination_service_namespace":  "bookinfo",
  2762  		"destination_service":            "productpage:9080",
  2763  		"destination_service_name":       "productpage",
  2764  		"destination_workload_namespace": "bookinfo",
  2765  		"destination_workload":           "productpage-v1",
  2766  		"destination_canonical_service":  "productpage",
  2767  		"destination_canonical_revision": "v1",
  2768  		"request_protocol":               "http",
  2769  		"response_code":                  "200",
  2770  		"grpc_response_status":           "0",
  2771  		"response_flags":                 "-",
  2772  	}
  2773  	q1m8 := model.Metric{
  2774  		"source_workload_namespace":      "bookinfo",
  2775  		"source_workload":                "productpage-v1",
  2776  		"source_canonical_service":       "productpage",
  2777  		"source_canonical_revision":      "v1",
  2778  		"source_cluster":                 "east",
  2779  		"destination_cluster":            "unknown",
  2780  		"destination_service_namespace":  "unknown",
  2781  		"destination_service":            "unknown",
  2782  		"destination_service_name":       "unknown",
  2783  		"destination_workload_namespace": "unknown",
  2784  		"destination_workload":           "unknown",
  2785  		"destination_canonical_service":  "unknown",
  2786  		"destination_canonical_revision": "unknown",
  2787  		"request_protocol":               "http",
  2788  		"response_code":                  "404",
  2789  		"response_flags":                 "NR",
  2790  	}
  2791  	q1m9 := model.Metric{
  2792  		"source_workload_namespace":      "bookinfo",
  2793  		"source_workload":                "productpage-v1",
  2794  		"source_canonical_service":       "productpage",
  2795  		"source_canonical_revision":      "v1",
  2796  		"source_cluster":                 "east",
  2797  		"destination_cluster":            "east",
  2798  		"destination_service_namespace":  "bankapp",
  2799  		"destination_service":            "deposit:9080",
  2800  		"destination_service_name":       "deposit",
  2801  		"destination_workload_namespace": "bankapp",
  2802  		"destination_workload":           "deposit-v1",
  2803  		"destination_canonical_service":  "deposit",
  2804  		"destination_canonical_revision": "v1",
  2805  		"request_protocol":               "grpc",
  2806  		"response_code":                  "200",
  2807  		"grpc_response_status":           "0",
  2808  		"response_flags":                 "-",
  2809  	}
  2810  	v1 := model.Vector{
  2811  		&model.Sample{
  2812  			Metric: q1m0,
  2813  			Value:  20,
  2814  		},
  2815  		&model.Sample{
  2816  			Metric: q1m1,
  2817  			Value:  20,
  2818  		},
  2819  		&model.Sample{
  2820  			Metric: q1m2,
  2821  			Value:  20,
  2822  		},
  2823  		&model.Sample{
  2824  			Metric: q1m3,
  2825  			Value:  20,
  2826  		},
  2827  		&model.Sample{
  2828  			Metric: q1m4,
  2829  			Value:  20,
  2830  		},
  2831  		&model.Sample{
  2832  			Metric: q1m5,
  2833  			Value:  20,
  2834  		},
  2835  		&model.Sample{
  2836  			Metric: q1m6,
  2837  			Value:  20,
  2838  		},
  2839  		&model.Sample{
  2840  			Metric: q1m7,
  2841  			Value:  20,
  2842  		},
  2843  		&model.Sample{
  2844  			Metric: q1m8,
  2845  			Value:  4,
  2846  		},
  2847  		&model.Sample{
  2848  			Metric: q1m9,
  2849  			Value:  50,
  2850  		},
  2851  	}
  2852  
  2853  	q2 := `round(sum(rate(istio_request_messages_total{reporter="destination",destination_workload_namespace="bookinfo",destination_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  2854  	v2 := model.Vector{}
  2855  
  2856  	q3 := `round(sum(rate(istio_request_messages_total{reporter="source",source_workload_namespace="bookinfo",source_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  2857  	q3m0 := model.Metric{
  2858  		"source_workload_namespace":      "bookinfo",
  2859  		"source_workload":                "productpage-v1",
  2860  		"source_canonical_service":       "productpage",
  2861  		"source_canonical_revision":      "v1",
  2862  		"source_cluster":                 "east",
  2863  		"destination_cluster":            "east",
  2864  		"destination_service_namespace":  "bookinfo",
  2865  		"destination_service":            "tcp:9080",
  2866  		"destination_service_name":       "tcp",
  2867  		"destination_workload_namespace": "bookinfo",
  2868  		"destination_workload":           "tcp-v1",
  2869  		"destination_canonical_service":  "tcp",
  2870  		"destination_canonical_revision": "v1",
  2871  		"response_flags":                 "-",
  2872  	}
  2873  	v3 := model.Vector{
  2874  		&model.Sample{
  2875  			Metric: q3m0,
  2876  			Value:  31,
  2877  		},
  2878  	}
  2879  
  2880  	q4 := `round(sum(rate(istio_response_messages_total{reporter="destination",destination_workload_namespace="bookinfo",destination_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  2881  	v4 := model.Vector{}
  2882  
  2883  	q5 := `round(sum(rate(istio_response_messages_total{reporter="source",source_workload_namespace="bookinfo",source_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision) > 0,0.001)`
  2884  	q5m0 := model.Metric{
  2885  		"source_workload_namespace":      "bookinfo",
  2886  		"source_workload":                "productpage-v1",
  2887  		"source_canonical_service":       "productpage",
  2888  		"source_canonical_revision":      "v1",
  2889  		"source_cluster":                 "east",
  2890  		"destination_cluster":            "east",
  2891  		"destination_service_namespace":  "bookinfo",
  2892  		"destination_service":            "tcp:9080",
  2893  		"destination_service_name":       "tcp",
  2894  		"destination_workload_namespace": "bookinfo",
  2895  		"destination_workload":           "tcp-v1",
  2896  		"destination_canonical_service":  "tcp",
  2897  		"destination_canonical_revision": "v1",
  2898  		"response_flags":                 "-",
  2899  	}
  2900  	v5 := model.Vector{
  2901  		&model.Sample{
  2902  			Metric: q5m0,
  2903  			Value:  62,
  2904  		},
  2905  	}
  2906  
  2907  	q6 := `round(sum(rate(istio_tcp_sent_bytes_total{reporter="destination",destination_workload_namespace="bookinfo",destination_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2908  	v6 := model.Vector{}
  2909  
  2910  	q7 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace="bookinfo",source_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2911  	q7m0 := model.Metric{
  2912  		"source_workload_namespace":      "bookinfo",
  2913  		"source_workload":                "productpage-v1",
  2914  		"source_canonical_service":       "productpage",
  2915  		"source_canonical_revision":      "v1",
  2916  		"source_cluster":                 "east",
  2917  		"destination_cluster":            "east",
  2918  		"destination_service_namespace":  "bookinfo",
  2919  		"destination_service":            "tcp:9080",
  2920  		"destination_service_name":       "tcp",
  2921  		"destination_workload_namespace": "bookinfo",
  2922  		"destination_workload":           "tcp-v1",
  2923  		"destination_canonical_service":  "tcp",
  2924  		"destination_canonical_revision": "v1",
  2925  		"response_flags":                 "-",
  2926  	}
  2927  	v7 := model.Vector{
  2928  		&model.Sample{
  2929  			Metric: q7m0,
  2930  			Value:  31,
  2931  		},
  2932  	}
  2933  
  2934  	q8 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="destination",destination_workload_namespace="bookinfo",destination_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2935  	v8 := model.Vector{}
  2936  
  2937  	q9 := `round(sum(rate(istio_tcp_sent_bytes_total{reporter="source",source_workload_namespace="bookinfo",source_workload="productpage-v1"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  2938  	q9m0 := model.Metric{
  2939  		"source_workload_namespace":      "bookinfo",
  2940  		"source_workload":                "productpage-v1",
  2941  		"source_canonical_service":       "productpage",
  2942  		"source_canonical_revision":      "v1",
  2943  		"source_cluster":                 "east",
  2944  		"destination_cluster":            "east",
  2945  		"destination_service_namespace":  "bookinfo",
  2946  		"destination_service":            "tcp:9080",
  2947  		"destination_service_name":       "tcp",
  2948  		"destination_workload_namespace": "bookinfo",
  2949  		"destination_workload":           "tcp-v1",
  2950  		"destination_canonical_service":  "tcp",
  2951  		"destination_canonical_revision": "v1",
  2952  		"response_flags":                 "-",
  2953  	}
  2954  	v9 := model.Vector{
  2955  		&model.Sample{
  2956  			Metric: q9m0,
  2957  			Value:  62,
  2958  		},
  2959  	}
  2960  
  2961  	client, xapi := setupMocked(t)
  2962  
  2963  	mockQuery(xapi, q0, &v0)
  2964  	mockQuery(xapi, q1, &v1)
  2965  	mockQuery(xapi, q2, &v2)
  2966  	mockQuery(xapi, q3, &v3)
  2967  	mockQuery(xapi, q4, &v4)
  2968  	mockQuery(xapi, q5, &v5)
  2969  	mockQuery(xapi, q6, &v6)
  2970  	mockQuery(xapi, q7, &v7)
  2971  	mockQuery(xapi, q8, &v8)
  2972  	mockQuery(xapi, q9, &v9)
  2973  
  2974  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  2975  
  2976  	mr := mux.NewRouter()
  2977  	mr.HandleFunc("/api/namespaces/{namespace}/workloads/{workload}/graph", http.HandlerFunc(
  2978  		func(w http.ResponseWriter, r *http.Request) {
  2979  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  2980  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  2981  			respond(w, code, config)
  2982  		}))
  2983  
  2984  	ts := httptest.NewServer(mr)
  2985  	defer ts.Close()
  2986  
  2987  	fut = graphNodeIstio
  2988  	url := ts.URL + "/api/namespaces/bookinfo/workloads/productpage-v1/graph?rateGrpc=total&rateHttp=requests&rateTcp=total&graphType=workload&appenders&queryTime=1523364075"
  2989  	resp, err := http.Get(url)
  2990  	if err != nil {
  2991  		t.Fatal(err)
  2992  	}
  2993  	actual, _ := io.ReadAll(resp.Body)
  2994  	expected, _ := os.ReadFile("testdata/test_rates_node_graph_total.expected")
  2995  	if runtime.GOOS == "windows" {
  2996  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  2997  	}
  2998  	expected = expected[:len(expected)-1] // remove EOF byte
  2999  
  3000  	assertObjectsEqual(t, expected, actual)
  3001  	assert.Equal(t, 200, resp.StatusCode)
  3002  }
  3003  
  3004  // TestComplexGraph aims to provide test coverage for a more robust graph and specific corner cases. Listed below are coverage cases
  3005  // - multi-cluster graph
  3006  // - multi-namespace graph
  3007  // - istio namespace
  3008  // - a "shared" node (internal in ns-1, outsider in ns-2)
  3009  // - request.host
  3010  // - bad dest telemetry filtering
  3011  // - bad source telemetry filtering
  3012  // - workload -> egress -> service-entry traffic
  3013  // - 0 response code (no response)
  3014  // - queryScope
  3015  // note: appenders still tested in separate unit tests given that they create their own new business/kube clients
  3016  func TestComplexGraph(t *testing.T) {
  3017  	// bookinfo
  3018  	q0 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3019  	q0m0 := model.Metric{ // outsider request that fails to reach workload
  3020  		"source_cluster":                 "cluster-tutorial",
  3021  		"source_workload_namespace":      "outsider",
  3022  		"source_workload":                "outsider-ingress",
  3023  		"source_canonical_service":       "outsider-ingress",
  3024  		"source_canonical_revision":      "latest",
  3025  		"destination_cluster":            "unknown", // this reflects real ingress reporting at this time,
  3026  		"destination_service_namespace":  "unknown", // although it seems like a possible bug to me
  3027  		"destination_service":            "reviews.bookinfo.svc.cluster.local",
  3028  		"destination_service_name":       "reviews",
  3029  		"destination_workload_namespace": "unknown",
  3030  		"destination_workload":           "unknown",
  3031  		"destination_canonical_service":  "unknown",
  3032  		"destination_canonical_revision": "latest",
  3033  		"request_protocol":               "http",
  3034  		"response_code":                  "503",
  3035  		"response_flags":                 "-",
  3036  	}
  3037  	v0 := model.Vector{
  3038  		&model.Sample{
  3039  			Metric: q0m0,
  3040  			Value:  50,
  3041  		},
  3042  	}
  3043  
  3044  	q1 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3045  	q1m0 := model.Metric{
  3046  		"source_cluster":                 "unknown",
  3047  		"source_workload_namespace":      "unknown",
  3048  		"source_workload":                "unknown",
  3049  		"source_canonical_service":       "unknown",
  3050  		"source_canonical_revision":      "unknown",
  3051  		"destination_cluster":            "cluster-bookinfo",
  3052  		"destination_service_namespace":  "bookinfo",
  3053  		"destination_service":            "productpage:9080",
  3054  		"destination_service_name":       "productpage",
  3055  		"destination_workload_namespace": "bookinfo",
  3056  		"destination_workload":           "productpage-v1",
  3057  		"destination_canonical_service":  "productpage",
  3058  		"destination_canonical_revision": "v1",
  3059  		"request_protocol":               "http",
  3060  		"response_code":                  "200",
  3061  		"grpc_response_status":           "0",
  3062  		"response_flags":                 "-",
  3063  	}
  3064  	q1m1 := model.Metric{
  3065  		"source_cluster":                 "cluster-tutorial",
  3066  		"source_workload_namespace":      "tutorial",
  3067  		"source_workload":                "customer-v1",
  3068  		"source_canonical_service":       "customer",
  3069  		"source_canonical_revision":      "v1",
  3070  		"destination_cluster":            "cluster-bookinfo",
  3071  		"destination_service_namespace":  "bookinfo",
  3072  		"destination_service":            "productpage:9080",
  3073  		"destination_service_name":       "productpage",
  3074  		"destination_workload_namespace": "bookinfo",
  3075  		"destination_workload":           "productpage-v1",
  3076  		"destination_canonical_service":  "productpage",
  3077  		"destination_canonical_revision": "v1",
  3078  		"request_protocol":               "http",
  3079  		"response_code":                  "200",
  3080  		"grpc_response_status":           "0",
  3081  		"response_flags":                 "-",
  3082  	}
  3083  	q1m2 := model.Metric{ // bad dest telem (variant 1.0)
  3084  		"source_cluster":                 "cluster-tutorial",
  3085  		"source_workload_namespace":      "tutorial",
  3086  		"source_workload":                "customer-v1",
  3087  		"source_canonical_service":       "customer",
  3088  		"source_canonical_revision":      "v1",
  3089  		"destination_cluster":            "cluster-bookinfo",
  3090  		"destination_service_namespace":  "bookinfo",
  3091  		"destination_service":            "10.20.30.40:9080",
  3092  		"destination_service_name":       "10.20.30.40:9080",
  3093  		"destination_workload_namespace": "unknown",
  3094  		"destination_workload":           "unknown",
  3095  		"destination_canonical_service":  "unknown",
  3096  		"destination_canonical_revision": "unknown",
  3097  		"request_protocol":               "http",
  3098  		"response_code":                  "200",
  3099  		"response_flags":                 "-",
  3100  	}
  3101  	q1m3 := model.Metric{ // bad dest telem (variant 1.2)
  3102  		"source_cluster":                 "cluster-tutorial",
  3103  		"source_workload_namespace":      "tutorial",
  3104  		"source_workload":                "customer-v1",
  3105  		"source_canonical_service":       "customer",
  3106  		"source_canonical_revision":      "v1",
  3107  		"destination_cluster":            "cluster-bookinfo",
  3108  		"destination_service_namespace":  "bookinfo",
  3109  		"destination_service":            "10.20.30.40",
  3110  		"destination_service_name":       "10.20.30.40",
  3111  		"destination_workload_namespace": "unknown",
  3112  		"destination_workload":           "unknown",
  3113  		"destination_canonical_service":  "unknown",
  3114  		"destination_canonical_revision": "unknown",
  3115  		"request_protocol":               "http",
  3116  		"response_code":                  "200",
  3117  		"response_flags":                 "-",
  3118  	}
  3119  	q1m4 := model.Metric{ // good telem (mock service entry)
  3120  		"source_cluster":                 "cluster-tutorial",
  3121  		"source_workload_namespace":      "tutorial",
  3122  		"source_workload":                "customer-v1",
  3123  		"source_canonical_service":       "customer",
  3124  		"source_canonical_revision":      "v1",
  3125  		"destination_cluster":            "cluster-bookinfo",
  3126  		"destination_service_namespace":  "bookinfo",
  3127  		"destination_service":            "app.example.com",
  3128  		"destination_service_name":       "app.example.com",
  3129  		"destination_workload_namespace": "unknown",
  3130  		"destination_workload":           "unknown",
  3131  		"destination_canonical_service":  "unknown",
  3132  		"destination_canonical_revision": "unknown",
  3133  		"request_protocol":               "http",
  3134  		"response_code":                  "200",
  3135  		"response_flags":                 "-",
  3136  	}
  3137  	// TODO: This is bad telemetry that should normally be filtered.  But due to https://github.com/istio/istio/issues/29373
  3138  	// we are currently not filtering but rather setting dest_cluster = source_cluster.  When 29373 is fixed for all
  3139  	// supported versions and we remove the workaround, results of the test will change.
  3140  	q1m5 := model.Metric{ // bad dest telem (variant 2.1)
  3141  		"source_cluster":                 "cluster-tutorial",
  3142  		"source_workload_namespace":      "tutorial",
  3143  		"source_workload":                "customer-v1",
  3144  		"source_canonical_service":       "customer",
  3145  		"source_canonical_revision":      "v1",
  3146  		"destination_cluster":            "unknown",
  3147  		"destination_service_namespace":  "bookinfo",
  3148  		"destination_service":            "reviews",
  3149  		"destination_service_name":       "reviews",
  3150  		"destination_workload_namespace": "bookinfo",
  3151  		"destination_workload":           "reviews-v1",
  3152  		"destination_canonical_service":  "reviews",
  3153  		"destination_canonical_revision": "v1",
  3154  		"request_protocol":               "http",
  3155  		"response_code":                  "200",
  3156  		"response_flags":                 "-",
  3157  	}
  3158  	v1 := model.Vector{
  3159  		&model.Sample{
  3160  			Metric: q1m0,
  3161  			Value:  50,
  3162  		},
  3163  		&model.Sample{
  3164  			Metric: q1m1,
  3165  			Value:  50,
  3166  		},
  3167  		&model.Sample{
  3168  			Metric: q1m2,
  3169  			Value:  100,
  3170  		},
  3171  		&model.Sample{
  3172  			Metric: q1m3,
  3173  			Value:  200,
  3174  		},
  3175  		&model.Sample{
  3176  			Metric: q1m4,
  3177  			Value:  300,
  3178  		},
  3179  		// see above
  3180  		&model.Sample{
  3181  			Metric: q1m5,
  3182  			Value:  700,
  3183  		},
  3184  	}
  3185  
  3186  	q2 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3187  	v2 := model.Vector{}
  3188  
  3189  	q3 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3190  	v3 := model.Vector{}
  3191  
  3192  	q4 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3193  	v4 := model.Vector{}
  3194  
  3195  	q5 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3196  	v5 := model.Vector{}
  3197  
  3198  	// tutorial
  3199  	q6 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="source",source_workload_namespace!="tutorial",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.tutorial\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3200  	v6 := model.Vector{}
  3201  
  3202  	q7 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="destination",destination_workload_namespace="tutorial"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3203  	q7m0 := model.Metric{
  3204  		"source_cluster":                 "unknown",
  3205  		"source_workload_namespace":      "unknown",
  3206  		"source_workload":                "unknown",
  3207  		"source_canonical_service":       "unknown",
  3208  		"source_canonical_revision":      "unknown",
  3209  		"destination_cluster":            "cluster-tutorial",
  3210  		"destination_service_namespace":  "tutorial",
  3211  		"destination_service":            "customer:9080",
  3212  		"destination_service_name":       "customer",
  3213  		"destination_workload_namespace": "tutorial",
  3214  		"destination_workload":           "customer-v1",
  3215  		"destination_canonical_service":  "customer",
  3216  		"destination_canonical_revision": "v1",
  3217  		"request_protocol":               "grpc",
  3218  		"response_code":                  "200",
  3219  		"grpc_response_status":           "0",
  3220  		"response_flags":                 "-",
  3221  	}
  3222  	q7m1 := model.Metric{
  3223  		"source_cluster":                 "cluster-tutorial",
  3224  		"source_workload_namespace":      "bad-source-telemetry-case-1",
  3225  		"source_workload":                "unknown",
  3226  		"source_canonical_service":       "unknown",
  3227  		"source_canonical_revision":      "unknown",
  3228  		"destination_cluster":            "cluster-tutorial",
  3229  		"destination_service_namespace":  "tutorial",
  3230  		"destination_service":            "customer:9080",
  3231  		"destination_service_name":       "customer",
  3232  		"destination_workload_namespace": "tutorial",
  3233  		"destination_workload":           "customer-v1",
  3234  		"destination_canonical_service":  "customer",
  3235  		"destination_canonical_revision": "v1",
  3236  		"request_protocol":               "grpc",
  3237  		"response_code":                  "200",
  3238  		"grpc_response_status":           "0",
  3239  		"response_flags":                 "-",
  3240  	}
  3241  	v7 := model.Vector{
  3242  		&model.Sample{
  3243  			Metric: q7m0,
  3244  			Value:  50,
  3245  		},
  3246  		&model.Sample{
  3247  			Metric: q7m1,
  3248  			Value:  50,
  3249  		},
  3250  	}
  3251  
  3252  	q8 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="source",source_workload_namespace="tutorial"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3253  	q8m0 := model.Metric{
  3254  		"source_cluster":                 "cluster-tutorial",
  3255  		"source_workload_namespace":      "tutorial",
  3256  		"source_workload":                "customer-v1",
  3257  		"source_canonical_service":       "customer",
  3258  		"source_canonical_revision":      "v1",
  3259  		"destination_cluster":            "cluster-bookinfo",
  3260  		"destination_service_namespace":  "bookinfo",
  3261  		"destination_service":            "productpage:9080",
  3262  		"destination_service_name":       "productpage",
  3263  		"destination_workload_namespace": "bookinfo",
  3264  		"destination_workload":           "productpage-v1",
  3265  		"destination_canonical_service":  "productpage",
  3266  		"destination_canonical_revision": "v1",
  3267  		"request_protocol":               "http",
  3268  		"response_code":                  "200",
  3269  		"grpc_response_status":           "0",
  3270  		"response_flags":                 "-",
  3271  	}
  3272  	q8m1 := model.Metric{ // bad dest telem (variant 1.0)
  3273  		"source_cluster":                 "cluster-tutorial",
  3274  		"source_workload_namespace":      "tutorial",
  3275  		"source_workload":                "customer-v1",
  3276  		"source_canonical_service":       "customer",
  3277  		"source_canonical_revision":      "v1",
  3278  		"destination_cluster":            "cluster-bookinfo",
  3279  		"destination_service_namespace":  "bookinfo",
  3280  		"destination_service":            "10.20.30.40:9080",
  3281  		"destination_service_name":       "10.20.30.40:9080",
  3282  		"destination_workload_namespace": "unknown",
  3283  		"destination_workload":           "unknown",
  3284  		"destination_canonical_service":  "unknown",
  3285  		"destination_canonical_revision": "unknown",
  3286  		"request_protocol":               "http",
  3287  		"response_code":                  "200",
  3288  		"response_flags":                 "-",
  3289  	}
  3290  	q8m2 := model.Metric{ // bad dest telem (variant 1.2)
  3291  		"source_cluster":                 "cluster-tutorial",
  3292  		"source_workload_namespace":      "tutorial",
  3293  		"source_workload":                "customer-v1",
  3294  		"source_canonical_service":       "customer",
  3295  		"source_canonical_revision":      "v1",
  3296  		"destination_cluster":            "cluster-bookinfo",
  3297  		"destination_service_namespace":  "bookinfo",
  3298  		"destination_service":            "10.20.30.40",
  3299  		"destination_service_name":       "10.20.30.40",
  3300  		"destination_workload_namespace": "unknown",
  3301  		"destination_workload":           "unknown",
  3302  		"destination_canonical_service":  "unknown",
  3303  		"destination_canonical_revision": "unknown",
  3304  		"request_protocol":               "http",
  3305  		"response_code":                  "200",
  3306  		"response_flags":                 "-",
  3307  	}
  3308  	q8m3 := model.Metric{ // good telem (mock service entry)
  3309  		"source_cluster":                 "cluster-tutorial",
  3310  		"source_workload_namespace":      "tutorial",
  3311  		"source_workload":                "customer-v1",
  3312  		"source_canonical_service":       "customer",
  3313  		"source_canonical_revision":      "v1",
  3314  		"destination_cluster":            "cluster-bookinfo",
  3315  		"destination_service_namespace":  "bookinfo",
  3316  		"destination_service":            "app.example.com",
  3317  		"destination_service_name":       "app.example.com",
  3318  		"destination_workload_namespace": "unknown",
  3319  		"destination_workload":           "unknown",
  3320  		"destination_canonical_service":  "unknown",
  3321  		"destination_canonical_revision": "unknown",
  3322  		"request_protocol":               "http",
  3323  		"response_code":                  "200",
  3324  		"response_flags":                 "-",
  3325  	}
  3326  	q8m4 := model.Metric{ // good telem (service entry via egressgateway, see the second hop below)
  3327  		"source_cluster":                 "cluster-tutorial",
  3328  		"source_workload_namespace":      "tutorial",
  3329  		"source_workload":                "customer-v1",
  3330  		"source_canonical_service":       "customer",
  3331  		"source_canonical_revision":      "v1",
  3332  		"destination_cluster":            "unknown",
  3333  		"destination_service_namespace":  "unknown",
  3334  		"destination_service":            "istio-egressgateway.istio-system.svc.cluster.local",
  3335  		"destination_service_name":       "istio-egressgateway.istio-system.svc.cluster.local",
  3336  		"destination_workload_namespace": "unknown",
  3337  		"destination_workload":           "unknown",
  3338  		"destination_canonical_service":  "unknown",
  3339  		"destination_canonical_revision": "unknown",
  3340  		"request_protocol":               "http",
  3341  		"response_code":                  "200",
  3342  		"response_flags":                 "-",
  3343  	}
  3344  	q8m5 := model.Metric{ // no response http
  3345  		"source_cluster":                 "cluster-tutorial",
  3346  		"source_workload_namespace":      "tutorial",
  3347  		"source_workload":                "customer-v1",
  3348  		"source_canonical_service":       "customer",
  3349  		"source_canonical_revision":      "v1",
  3350  		"destination_cluster":            "unknown",
  3351  		"destination_service_namespace":  "unknown",
  3352  		"destination_service":            "istio-egressgateway.istio-system.svc.cluster.local",
  3353  		"destination_service_name":       "istio-egressgateway.istio-system.svc.cluster.local",
  3354  		"destination_workload_namespace": "unknown",
  3355  		"destination_workload":           "unknown",
  3356  		"destination_canonical_service":  "unknown",
  3357  		"destination_canonical_revision": "unknown",
  3358  		"request_protocol":               "http",
  3359  		"response_code":                  "0",
  3360  		"response_flags":                 "DC",
  3361  	}
  3362  	q8m6 := model.Metric{ // no response grpc
  3363  		"source_cluster":                 "cluster-tutorial",
  3364  		"source_workload_namespace":      "tutorial",
  3365  		"source_workload":                "customer-v1",
  3366  		"source_canonical_service":       "customer",
  3367  		"source_canonical_revision":      "v1",
  3368  		"destination_cluster":            "unknown",
  3369  		"destination_service_namespace":  "unknown",
  3370  		"destination_service":            "istio-egressgateway.istio-system.svc.cluster.local",
  3371  		"destination_service_name":       "istio-egressgateway.istio-system.svc.cluster.local",
  3372  		"destination_workload_namespace": "unknown",
  3373  		"destination_workload":           "unknown",
  3374  		"destination_canonical_service":  "unknown",
  3375  		"destination_canonical_revision": "unknown",
  3376  		"request_protocol":               "grpc",
  3377  		"response_code":                  "0", // note, grpc_response_status is not reported for grpc with no response
  3378  		"response_flags":                 "DC",
  3379  	}
  3380  	// TODO: This is bad telemetry that should normally be filtered.  But due to https://github.com/istio/istio/issues/29373
  3381  	// we are currently not filtering but rather setting dest_cluster = source_cluster.  When 29373 is fixed for all
  3382  	// supported versions and we remove the workaround, results of the test will change.
  3383  	q8m7 := model.Metric{ // bad dest telem (variant 2.1)
  3384  		"source_cluster":                 "cluster-tutorial",
  3385  		"source_workload_namespace":      "tutorial",
  3386  		"source_workload":                "customer-v1",
  3387  		"source_canonical_service":       "customer",
  3388  		"source_canonical_revision":      "v1",
  3389  		"destination_cluster":            "unknown",
  3390  		"destination_service_namespace":  "bookinfo",
  3391  		"destination_service":            "reviews",
  3392  		"destination_service_name":       "reviews",
  3393  		"destination_workload_namespace": "bookinfo",
  3394  		"destination_workload":           "reviews-v1",
  3395  		"destination_canonical_service":  "reviews",
  3396  		"destination_canonical_revision": "v1",
  3397  		"request_protocol":               "http",
  3398  		"response_code":                  "200",
  3399  		"response_flags":                 "-",
  3400  	}
  3401  	v8 := model.Vector{
  3402  		&model.Sample{
  3403  			Metric: q8m0,
  3404  			Value:  50,
  3405  		},
  3406  		&model.Sample{
  3407  			Metric: q8m1,
  3408  			Value:  100,
  3409  		},
  3410  		&model.Sample{
  3411  			Metric: q8m2,
  3412  			Value:  200,
  3413  		},
  3414  		&model.Sample{
  3415  			Metric: q8m3,
  3416  			Value:  300,
  3417  		},
  3418  		&model.Sample{
  3419  			Metric: q8m4,
  3420  			Value:  400,
  3421  		},
  3422  		&model.Sample{
  3423  			Metric: q8m5,
  3424  			Value:  500,
  3425  		},
  3426  		&model.Sample{
  3427  			Metric: q8m6,
  3428  			Value:  600,
  3429  		},
  3430  		// see above
  3431  		&model.Sample{
  3432  			Metric: q8m7,
  3433  			Value:  700,
  3434  		},
  3435  	}
  3436  
  3437  	q9 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="source",source_workload_namespace!="tutorial",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.tutorial\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3438  	v9 := model.Vector{}
  3439  
  3440  	q10 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="destination",destination_workload_namespace="tutorial"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3441  	v10 := model.Vector{}
  3442  
  3443  	q11 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="source",source_workload_namespace="tutorial"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3444  	v11 := model.Vector{}
  3445  
  3446  	// istio-system
  3447  	q12 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="source",source_workload_namespace!="istio-system",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.istio-system\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3448  	v12 := model.Vector{}
  3449  
  3450  	q13 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="destination",destination_workload_namespace="istio-system"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3451  	v13 := model.Vector{}
  3452  
  3453  	q14 := `round(sum(rate(istio_requests_total{mesh_id="mesh1",reporter="source",source_workload_namespace="istio-system"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) > 0,0.001)`
  3454  	q14m0 := model.Metric{ // good telem (service entry via egressgateway, the second hop)
  3455  		"source_cluster":                 "cluster-cp",
  3456  		"source_workload_namespace":      "istio-system",
  3457  		"source_workload":                "istio-egressgateway",
  3458  		"source_canonical_service":       "istio-egressgateway",
  3459  		"source_canonical_revision":      "latest",
  3460  		"destination_cluster":            "unknown",
  3461  		"destination_service_namespace":  "unknown",
  3462  		"destination_service":            "app.example-2.com",
  3463  		"destination_service_name":       "app.example-2.com",
  3464  		"destination_workload_namespace": "unknown",
  3465  		"destination_workload":           "unknown",
  3466  		"destination_canonical_service":  "unknown",
  3467  		"destination_canonical_revision": "unknown",
  3468  		"request_protocol":               "http",
  3469  		"response_code":                  "200",
  3470  		"response_flags":                 "-",
  3471  	}
  3472  	v14 := model.Vector{
  3473  		&model.Sample{
  3474  			Metric: q14m0,
  3475  			Value:  400,
  3476  		},
  3477  	}
  3478  
  3479  	q15 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="source",source_workload_namespace!="istio-system",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.istio-system\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3480  	v15 := model.Vector{}
  3481  
  3482  	q16 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="destination",destination_workload_namespace="istio-system"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3483  	v16 := model.Vector{}
  3484  
  3485  	q17 := `round(sum(rate(istio_tcp_received_bytes_total{mesh_id="mesh1",reporter="source",source_workload_namespace="istio-system"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) > 0,0.001)`
  3486  	v17 := model.Vector{}
  3487  
  3488  	clients := map[string]kubernetes.ClientInterface{
  3489  		"cluster-tutorial": kubetest.NewFakeK8sClient(
  3490  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "bookinfo"}},
  3491  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
  3492  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "tutorial"}},
  3493  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-telemetry"}},
  3494  		),
  3495  		"cluster-cp": kubetest.NewFakeK8sClient(
  3496  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "bookinfo"}},
  3497  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
  3498  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "tutorial"}},
  3499  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-telemetry"}},
  3500  		),
  3501  		"cluster-bookinfo": kubetest.NewFakeK8sClient(
  3502  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "bookinfo"}},
  3503  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
  3504  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "tutorial"}},
  3505  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-telemetry"}},
  3506  		),
  3507  	}
  3508  	client, xapi, err := setupMockedWithIstioComponentNamespaces(t, "mesh1", clients)
  3509  	if err != nil {
  3510  		t.Error(err)
  3511  		return
  3512  	}
  3513  	mockQuery(xapi, q0, &v0)
  3514  	mockQuery(xapi, q1, &v1)
  3515  	mockQuery(xapi, q2, &v2)
  3516  	mockQuery(xapi, q3, &v3)
  3517  	mockQuery(xapi, q4, &v4)
  3518  	mockQuery(xapi, q5, &v5)
  3519  	mockQuery(xapi, q6, &v6)
  3520  	mockQuery(xapi, q7, &v7)
  3521  	mockQuery(xapi, q8, &v8)
  3522  	mockQuery(xapi, q9, &v9)
  3523  	mockQuery(xapi, q10, &v10)
  3524  	mockQuery(xapi, q11, &v11)
  3525  	mockQuery(xapi, q12, &v12)
  3526  	mockQuery(xapi, q13, &v13)
  3527  	mockQuery(xapi, q14, &v14)
  3528  	mockQuery(xapi, q15, &v15)
  3529  	mockQuery(xapi, q16, &v16)
  3530  	mockQuery(xapi, q17, &v17)
  3531  
  3532  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  3533  
  3534  	mr := mux.NewRouter()
  3535  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  3536  		func(w http.ResponseWriter, r *http.Request) {
  3537  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  3538  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  3539  			respond(w, code, config)
  3540  		}))
  3541  
  3542  	ts := httptest.NewServer(mr)
  3543  	defer ts.Close()
  3544  
  3545  	fut = graphNamespacesIstio
  3546  	url := ts.URL + "/api/namespaces/graph?graphType=versionedApp&appenders=&queryTime=1523364075&namespaces=bookinfo,tutorial,istio-system"
  3547  	resp, err := http.Get(url)
  3548  	if err != nil {
  3549  		t.Fatal(err)
  3550  	}
  3551  	actual, _ := io.ReadAll(resp.Body)
  3552  	expected, _ := os.ReadFile("testdata/test_complex_graph.expected")
  3553  	if runtime.GOOS == "windows" {
  3554  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  3555  	}
  3556  	expected = expected[:len(expected)-1] // remove EOF byte
  3557  
  3558  	assertObjectsEqual(t, expected, actual)
  3559  	assert.Equal(t, 200, resp.StatusCode)
  3560  }
  3561  
  3562  func TestMultiClusterSourceGraph(t *testing.T) {
  3563  	// bookinfo
  3564  	q0 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) ,0.001)`
  3565  	v0 := model.Vector{}
  3566  
  3567  	q1 := `round(sum(rate(istio_requests_total{reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) ,0.001)`
  3568  	q1m0 := model.Metric{
  3569  		"destination_canonical_revision": "v2",
  3570  		"destination_canonical_service":  "reviews",
  3571  		"destination_cluster":            "kukulcan",
  3572  		"destination_service":            "reviews.bookinfo.svc.cluster.local",
  3573  		"destination_service_name":       "reviews",
  3574  		"destination_service_namespace":  "bookinfo",
  3575  		"destination_workload":           "reviews-v2",
  3576  		"destination_workload_namespace": "bookinfo",
  3577  		"request_protocol":               "http",
  3578  		"response_code":                  "200",
  3579  		"response_flags":                 "-",
  3580  		"source_canonical_revision":      "v1",
  3581  		"source_canonical_service":       "productpage",
  3582  		"source_cluster":                 "kukulcan",
  3583  		"source_workload":                "productpage-v1",
  3584  		"source_workload_namespace":      "bookinfo",
  3585  	}
  3586  	q1m1 := model.Metric{
  3587  		"destination_canonical_revision": "v1",
  3588  		"destination_canonical_service":  "details",
  3589  		"destination_cluster":            "kukulcan",
  3590  		"destination_service":            "details.bookinfo.svc.cluster.local",
  3591  		"destination_service_name":       "details",
  3592  		"destination_service_namespace":  "bookinfo",
  3593  		"destination_workload":           "details-v1",
  3594  		"destination_workload_namespace": "bookinfo",
  3595  		"request_protocol":               "http",
  3596  		"response_code":                  "200",
  3597  		"response_flags":                 "-",
  3598  		"source_canonical_revision":      "v1",
  3599  		"source_canonical_service":       "productpage",
  3600  		"source_cluster":                 "kukulcan",
  3601  		"source_workload":                "productpage-v1",
  3602  		"source_workload_namespace":      "bookinfo",
  3603  	}
  3604  	q1m2 := model.Metric{
  3605  		"destination_canonical_revision": "v1",
  3606  		"destination_canonical_service":  "productpage",
  3607  		"destination_cluster":            "kukulcan",
  3608  		"destination_service":            "productpage.bookinfo.svc.cluster.local",
  3609  		"destination_service_name":       "productpage",
  3610  		"destination_service_namespace":  "bookinfo",
  3611  		"destination_workload":           "productpage-v1",
  3612  		"destination_workload_namespace": "bookinfo",
  3613  		"request_protocol":               "http",
  3614  		"response_code":                  "200",
  3615  		"response_flags":                 "-",
  3616  		"source_canonical_revision":      "latest",
  3617  		"source_canonical_service":       "istio-ingressgateway",
  3618  		"source_cluster":                 "kukulcan",
  3619  		"source_workload":                "istio-ingressgateway",
  3620  		"source_workload_namespace":      "istio-system",
  3621  	}
  3622  	q1m3 := model.Metric{
  3623  		"destination_canonical_revision": "v1",
  3624  		"destination_canonical_service":  "reviews",
  3625  		"destination_cluster":            "kukulcan",
  3626  		"destination_service":            "reviews.bookinfo.svc.cluster.local",
  3627  		"destination_service_name":       "reviews",
  3628  		"destination_service_namespace":  "bookinfo",
  3629  		"destination_workload":           "reviews-v1",
  3630  		"destination_workload_namespace": "bookinfo",
  3631  		"request_protocol":               "http",
  3632  		"response_code":                  "200",
  3633  		"response_flags":                 "-",
  3634  		"source_canonical_revision":      "v1",
  3635  		"source_canonical_service":       "productpage",
  3636  		"source_cluster":                 "kukulcan",
  3637  		"source_workload":                "productpage-v1",
  3638  		"source_workload_namespace":      "bookinfo",
  3639  	}
  3640  	v1 := model.Vector{
  3641  		&model.Sample{
  3642  			Metric: q1m0,
  3643  			Value:  100,
  3644  		},
  3645  		&model.Sample{
  3646  			Metric: q1m1,
  3647  			Value:  100,
  3648  		},
  3649  		&model.Sample{
  3650  			Metric: q1m2,
  3651  			Value:  100,
  3652  		},
  3653  		&model.Sample{
  3654  			Metric: q1m3,
  3655  			Value:  100,
  3656  		},
  3657  	}
  3658  
  3659  	q2 := `round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,request_protocol,response_code,grpc_response_status,response_flags) ,0.001)`
  3660  	q2m0 := model.Metric{
  3661  		"destination_canonical_revision": "v3",
  3662  		"destination_canonical_service":  "reviews",
  3663  		"destination_cluster":            "tzotz",
  3664  		"destination_service":            "reviews.bookinfo.svc.cluster.local",
  3665  		"destination_service_name":       "reviews",
  3666  		"destination_service_namespace":  "bookinfo",
  3667  		"destination_workload":           "reviews-v3",
  3668  		"destination_workload_namespace": "bookinfo",
  3669  		"request_protocol":               "http",
  3670  		"response_code":                  "200",
  3671  		"response_flags":                 "-",
  3672  		"source_canonical_revision":      "v1",
  3673  		"source_canonical_service":       "productpage",
  3674  		"source_cluster":                 "kukulcan",
  3675  		"source_workload":                "productpage-v1",
  3676  		"source_workload_namespace":      "bookinfo",
  3677  	}
  3678  	q2m1 := model.Metric{
  3679  		"destination_canonical_revision": "v1",
  3680  		"destination_canonical_service":  "ratings",
  3681  		"destination_cluster":            "tzotz",
  3682  		"destination_service":            "ratings.bookinfo.svc.cluster.local",
  3683  		"destination_service_name":       "ratings",
  3684  		"destination_service_namespace":  "bookinfo",
  3685  		"destination_workload":           "ratings-v1",
  3686  		"destination_workload_namespace": "bookinfo",
  3687  		"request_protocol":               "http",
  3688  		"response_code":                  "200",
  3689  		"response_flags":                 "-",
  3690  		"source_canonical_revision":      "v2",
  3691  		"source_canonical_service":       "reviews",
  3692  		"source_cluster":                 "kukulcan",
  3693  		"source_workload":                "reviews-v2",
  3694  		"source_workload_namespace":      "bookinfo",
  3695  	}
  3696  	q2m2 := model.Metric{
  3697  		"destination_canonical_revision": "v1",
  3698  		"destination_canonical_service":  "details",
  3699  		"destination_cluster":            "kukulcan",
  3700  		"destination_service":            "details.bookinfo.svc.cluster.local",
  3701  		"destination_service_name":       "details",
  3702  		"destination_service_namespace":  "bookinfo",
  3703  		"destination_workload":           "details-v1",
  3704  		"destination_workload_namespace": "bookinfo",
  3705  		"request_protocol":               "http",
  3706  		"response_code":                  "200",
  3707  		"response_flags":                 "-",
  3708  		"source_canonical_revision":      "v1",
  3709  		"source_canonical_service":       "productpage",
  3710  		"source_cluster":                 "kukulcan",
  3711  		"source_workload":                "productpage-v1",
  3712  		"source_workload_namespace":      "bookinfo",
  3713  	}
  3714  	q2m3 := model.Metric{
  3715  		"destination_canonical_revision": "v1",
  3716  		"destination_canonical_service":  "reviews",
  3717  		"destination_cluster":            "kukulcan",
  3718  		"destination_service":            "reviews.bookinfo.svc.cluster.local",
  3719  		"destination_service_name":       "reviews",
  3720  		"destination_service_namespace":  "bookinfo",
  3721  		"destination_workload":           "reviews-v1",
  3722  		"destination_workload_namespace": "bookinfo",
  3723  		"request_protocol":               "http",
  3724  		"response_code":                  "200",
  3725  		"response_flags":                 "-",
  3726  		"source_canonical_revision":      "v1",
  3727  		"source_canonical_service":       "productpage",
  3728  		"source_cluster":                 "kukulcan",
  3729  		"source_workload":                "productpage-v1",
  3730  		"source_workload_namespace":      "bookinfo",
  3731  	}
  3732  	q2m4 := model.Metric{
  3733  		"destination_canonical_revision": "v2",
  3734  		"destination_canonical_service":  "reviews",
  3735  		"destination_cluster":            "kukulcan",
  3736  		"destination_service":            "reviews.bookinfo.svc.cluster.local",
  3737  		"destination_service_name":       "reviews",
  3738  		"destination_service_namespace":  "bookinfo",
  3739  		"destination_workload":           "reviews-v2",
  3740  		"destination_workload_namespace": "bookinfo",
  3741  		"request_protocol":               "http",
  3742  		"response_code":                  "200",
  3743  		"response_flags":                 "-",
  3744  		"source_canonical_revision":      "v1",
  3745  		"source_canonical_service":       "productpage",
  3746  		"source_cluster":                 "kukulcan",
  3747  		"source_workload":                "productpage-v1",
  3748  		"source_workload_namespace":      "bookinfo",
  3749  	}
  3750  	q2m5 := model.Metric{
  3751  		"destination_canonical_revision": "v2",
  3752  		"destination_canonical_service":  "reviews",
  3753  		"destination_cluster":            "tzotz",
  3754  		"destination_service":            "reviews.bookinfo.svc.cluster.local",
  3755  		"destination_service_name":       "reviews",
  3756  		"destination_service_namespace":  "bookinfo",
  3757  		"destination_workload":           "reviews-v2",
  3758  		"destination_workload_namespace": "bookinfo",
  3759  		"request_protocol":               "http",
  3760  		"response_code":                  "200",
  3761  		"response_flags":                 "-",
  3762  		"source_canonical_revision":      "v1",
  3763  		"source_canonical_service":       "productpage",
  3764  		"source_cluster":                 "kukulcan",
  3765  		"source_workload":                "productpage-v1",
  3766  		"source_workload_namespace":      "bookinfo",
  3767  	}
  3768  	// This is an additional test for #4488, done here because, unlike the other tests, this test is injecting service nodes
  3769  	q2m6 := model.Metric{
  3770  		"destination_canonical_revision": "v1",
  3771  		"destination_canonical_service":  "kiali#4488-dest",
  3772  		"destination_cluster":            "tzotz",
  3773  		"destination_service":            "10.2.3.4:8080",
  3774  		"destination_service_name":       "PassthroughCluster",
  3775  		"destination_service_namespace":  "bookinfo",
  3776  		"destination_workload":           "kiali#4488-dest-v1",
  3777  		"destination_workload_namespace": "bookinfo",
  3778  		"request_protocol":               "http",
  3779  		"response_code":                  "200",
  3780  		"response_flags":                 "-",
  3781  		"source_canonical_revision":      "v1",
  3782  		"source_canonical_service":       "kiali#4488-source",
  3783  		"source_cluster":                 "tzotz",
  3784  		"source_workload":                "kiali#4488-source-v1",
  3785  		"source_workload_namespace":      "bookinfo",
  3786  	}
  3787  	v2 := model.Vector{
  3788  		&model.Sample{
  3789  			Metric: q2m0,
  3790  			Value:  100,
  3791  		},
  3792  		&model.Sample{
  3793  			Metric: q2m1,
  3794  			Value:  100,
  3795  		},
  3796  		&model.Sample{
  3797  			Metric: q2m2,
  3798  			Value:  100,
  3799  		},
  3800  		&model.Sample{
  3801  			Metric: q2m3,
  3802  			Value:  100,
  3803  		},
  3804  		&model.Sample{
  3805  			Metric: q2m4,
  3806  			Value:  100,
  3807  		},
  3808  		&model.Sample{
  3809  			Metric: q2m5,
  3810  			Value:  100,
  3811  		},
  3812  		&model.Sample{
  3813  			Metric: q2m6,
  3814  			Value:  100,
  3815  		},
  3816  	}
  3817  
  3818  	q3 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace!="bookinfo",destination_workload_namespace="unknown",destination_workload="unknown",destination_service=~"^.+\\.bookinfo\\..+$"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) ,0.001)`
  3819  	v3 := model.Vector{}
  3820  
  3821  	q4 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="destination",destination_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) ,0.001)`
  3822  	v4 := model.Vector{}
  3823  
  3824  	q5 := `round(sum(rate(istio_tcp_received_bytes_total{reporter="source",source_workload_namespace="bookinfo"} [600s])) by (source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision,response_flags) ,0.001)`
  3825  	v5 := model.Vector{}
  3826  
  3827  	clients := map[string]kubernetes.ClientInterface{
  3828  		"kukulcan": kubetest.NewFakeK8sClient(
  3829  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "bookinfo"}},
  3830  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
  3831  		),
  3832  		"tzotz": kubetest.NewFakeK8sClient(
  3833  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "bookinfo"}},
  3834  			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
  3835  		),
  3836  	}
  3837  	client, xapi, err := setupMockedWithIstioComponentNamespaces(t, "", clients)
  3838  	if err != nil {
  3839  		t.Error(err)
  3840  		return
  3841  	}
  3842  
  3843  	mockQuery(xapi, q0, &v0)
  3844  	mockQuery(xapi, q1, &v1)
  3845  	mockQuery(xapi, q2, &v2)
  3846  	mockQuery(xapi, q3, &v3)
  3847  	mockQuery(xapi, q4, &v4)
  3848  	mockQuery(xapi, q5, &v5)
  3849  
  3850  	var fut func(ctx context.Context, b *business.Layer, p *prometheus.Client, o graph.Options) (int, interface{})
  3851  
  3852  	mr := mux.NewRouter()
  3853  	mr.HandleFunc("/api/namespaces/graph", http.HandlerFunc(
  3854  		func(w http.ResponseWriter, r *http.Request) {
  3855  			context := authentication.SetAuthInfoContext(r.Context(), &api.AuthInfo{Token: "test"})
  3856  			code, config := fut(context, nil, client, graph.NewOptions(r.WithContext(context)))
  3857  			respond(w, code, config)
  3858  		}))
  3859  
  3860  	ts := httptest.NewServer(mr)
  3861  	defer ts.Close()
  3862  
  3863  	fut = graphNamespacesIstio
  3864  	url := ts.URL + "/api/namespaces/graph?graphType=versionedApp&injectServiceNodes=true&includeIdleEdges=true&appenders=&queryTime=1523364075&namespaces=bookinfo"
  3865  	resp, err := http.Get(url)
  3866  	if err != nil {
  3867  		t.Fatal(err)
  3868  	}
  3869  	actual, _ := io.ReadAll(resp.Body)
  3870  	expected, _ := os.ReadFile("testdata/test_mc_source_graph.expected")
  3871  	if runtime.GOOS == "windows" {
  3872  		expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
  3873  	}
  3874  	expected = expected[:len(expected)-1] // remove EOF byte
  3875  
  3876  	assertObjectsEqual(t, expected, actual)
  3877  	assert.Equal(t, 200, resp.StatusCode)
  3878  }