github.com/netdata/go.d.plugin@v0.58.1/agent/discovery/sd/kubernetes/pod_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package kubernetes
     4  
     5  import (
     6  	"context"
     7  	"net"
     8  	"strconv"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/netdata/go.d.plugin/agent/discovery/sd/model"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	corev1 "k8s.io/api/core/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"k8s.io/client-go/kubernetes"
    20  	"k8s.io/client-go/tools/cache"
    21  )
    22  
    23  func TestPodTargetGroup_Provider(t *testing.T) {
    24  	var p podTargetGroup
    25  	assert.NotEmpty(t, p.Provider())
    26  }
    27  
    28  func TestPodTargetGroup_Source(t *testing.T) {
    29  	tests := map[string]struct {
    30  		createSim   func() discoverySim
    31  		wantSources []string
    32  	}{
    33  		"pods with multiple ports": {
    34  			createSim: func() discoverySim {
    35  				httpd, nginx := newHTTPDPod(), newNGINXPod()
    36  				disc, _ := prepareAllNsPodDiscoverer(httpd, nginx)
    37  
    38  				return discoverySim{
    39  					td: disc,
    40  					wantTargetGroups: []model.TargetGroup{
    41  						preparePodTargetGroup(httpd),
    42  						preparePodTargetGroup(nginx),
    43  					},
    44  				}
    45  			},
    46  			wantSources: []string{
    47  				"sd:k8s:pod(default/httpd-dd95c4d68-5bkwl)",
    48  				"sd:k8s:pod(default/nginx-7cfd77469b-q6kxj)",
    49  			},
    50  		},
    51  	}
    52  
    53  	for name, test := range tests {
    54  		t.Run(name, func(t *testing.T) {
    55  			sim := test.createSim()
    56  
    57  			var sources []string
    58  			for _, tgg := range sim.run(t) {
    59  				sources = append(sources, tgg.Source())
    60  			}
    61  
    62  			assert.Equal(t, test.wantSources, sources)
    63  		})
    64  	}
    65  }
    66  
    67  func TestPodTargetGroup_Targets(t *testing.T) {
    68  	tests := map[string]struct {
    69  		createSim   func() discoverySim
    70  		wantTargets int
    71  	}{
    72  		"pods with multiple ports": {
    73  			createSim: func() discoverySim {
    74  				httpd, nginx := newHTTPDPod(), newNGINXPod()
    75  				discovery, _ := prepareAllNsPodDiscoverer(httpd, nginx)
    76  
    77  				return discoverySim{
    78  					td: discovery,
    79  					wantTargetGroups: []model.TargetGroup{
    80  						preparePodTargetGroup(httpd),
    81  						preparePodTargetGroup(nginx),
    82  					},
    83  				}
    84  			},
    85  			wantTargets: 4,
    86  		},
    87  	}
    88  
    89  	for name, test := range tests {
    90  		t.Run(name, func(t *testing.T) {
    91  			sim := test.createSim()
    92  
    93  			var targets int
    94  			for _, tgg := range sim.run(t) {
    95  				targets += len(tgg.Targets())
    96  			}
    97  
    98  			assert.Equal(t, test.wantTargets, targets)
    99  		})
   100  	}
   101  }
   102  
   103  func TestPodTarget_Hash(t *testing.T) {
   104  	tests := map[string]struct {
   105  		createSim  func() discoverySim
   106  		wantHashes []uint64
   107  	}{
   108  		"pods with multiple ports": {
   109  			createSim: func() discoverySim {
   110  				httpd, nginx := newHTTPDPod(), newNGINXPod()
   111  				discovery, _ := prepareAllNsPodDiscoverer(httpd, nginx)
   112  
   113  				return discoverySim{
   114  					td: discovery,
   115  					wantTargetGroups: []model.TargetGroup{
   116  						preparePodTargetGroup(httpd),
   117  						preparePodTargetGroup(nginx),
   118  					},
   119  				}
   120  			},
   121  			wantHashes: []uint64{
   122  				12703169414253998055,
   123  				13351713096133918928,
   124  				8241692333761256175,
   125  				11562466355572729519,
   126  			},
   127  		},
   128  	}
   129  
   130  	for name, test := range tests {
   131  		t.Run(name, func(t *testing.T) {
   132  			sim := test.createSim()
   133  
   134  			var hashes []uint64
   135  			for _, tgg := range sim.run(t) {
   136  				for _, tg := range tgg.Targets() {
   137  					hashes = append(hashes, tg.Hash())
   138  				}
   139  			}
   140  
   141  			assert.Equal(t, test.wantHashes, hashes)
   142  		})
   143  	}
   144  }
   145  
   146  func TestPodTarget_TUID(t *testing.T) {
   147  	tests := map[string]struct {
   148  		createSim func() discoverySim
   149  		wantTUID  []string
   150  	}{
   151  		"pods with multiple ports": {
   152  			createSim: func() discoverySim {
   153  				httpd, nginx := newHTTPDPod(), newNGINXPod()
   154  				discovery, _ := prepareAllNsPodDiscoverer(httpd, nginx)
   155  
   156  				return discoverySim{
   157  					td: discovery,
   158  					wantTargetGroups: []model.TargetGroup{
   159  						preparePodTargetGroup(httpd),
   160  						preparePodTargetGroup(nginx),
   161  					},
   162  				}
   163  			},
   164  			wantTUID: []string{
   165  				"default_httpd-dd95c4d68-5bkwl_httpd_tcp_80",
   166  				"default_httpd-dd95c4d68-5bkwl_httpd_tcp_443",
   167  				"default_nginx-7cfd77469b-q6kxj_nginx_tcp_80",
   168  				"default_nginx-7cfd77469b-q6kxj_nginx_tcp_443",
   169  			},
   170  		},
   171  	}
   172  
   173  	for name, test := range tests {
   174  		t.Run(name, func(t *testing.T) {
   175  			sim := test.createSim()
   176  
   177  			var tuid []string
   178  			for _, tgg := range sim.run(t) {
   179  				for _, tg := range tgg.Targets() {
   180  					tuid = append(tuid, tg.TUID())
   181  				}
   182  			}
   183  
   184  			assert.Equal(t, test.wantTUID, tuid)
   185  		})
   186  	}
   187  }
   188  
   189  func TestNewPodDiscoverer(t *testing.T) {
   190  	tests := map[string]struct {
   191  		podInf    cache.SharedInformer
   192  		cmapInf   cache.SharedInformer
   193  		secretInf cache.SharedInformer
   194  		wantPanic bool
   195  	}{
   196  		"valid informers": {
   197  			wantPanic: false,
   198  			podInf:    cache.NewSharedInformer(nil, &corev1.Pod{}, resyncPeriod),
   199  			cmapInf:   cache.NewSharedInformer(nil, &corev1.ConfigMap{}, resyncPeriod),
   200  			secretInf: cache.NewSharedInformer(nil, &corev1.Secret{}, resyncPeriod),
   201  		},
   202  		"nil informers": {
   203  			wantPanic: true,
   204  		},
   205  	}
   206  
   207  	for name, test := range tests {
   208  		t.Run(name, func(t *testing.T) {
   209  			f := func() { newPodDiscoverer(test.podInf, test.cmapInf, test.secretInf) }
   210  
   211  			if test.wantPanic {
   212  				assert.Panics(t, f)
   213  			} else {
   214  				assert.NotPanics(t, f)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func TestPodDiscoverer_String(t *testing.T) {
   221  	var p podDiscoverer
   222  	assert.NotEmpty(t, p.String())
   223  }
   224  
   225  func TestPodDiscoverer_Discover(t *testing.T) {
   226  	tests := map[string]func() discoverySim{
   227  		"ADD: pods exist before run": func() discoverySim {
   228  			httpd, nginx := newHTTPDPod(), newNGINXPod()
   229  			td, _ := prepareAllNsPodDiscoverer(httpd, nginx)
   230  
   231  			return discoverySim{
   232  				td: td,
   233  				wantTargetGroups: []model.TargetGroup{
   234  					preparePodTargetGroup(httpd),
   235  					preparePodTargetGroup(nginx),
   236  				},
   237  			}
   238  		},
   239  		"ADD: pods exist before run and add after sync": func() discoverySim {
   240  			httpd, nginx := newHTTPDPod(), newNGINXPod()
   241  			disc, client := prepareAllNsPodDiscoverer(httpd)
   242  			podClient := client.CoreV1().Pods("default")
   243  
   244  			return discoverySim{
   245  				td: disc,
   246  				runAfterSync: func(ctx context.Context) {
   247  					_, _ = podClient.Create(ctx, nginx, metav1.CreateOptions{})
   248  				},
   249  				wantTargetGroups: []model.TargetGroup{
   250  					preparePodTargetGroup(httpd),
   251  					preparePodTargetGroup(nginx),
   252  				},
   253  			}
   254  		},
   255  		"DELETE: remove pods after sync": func() discoverySim {
   256  			httpd, nginx := newHTTPDPod(), newNGINXPod()
   257  			disc, client := prepareAllNsPodDiscoverer(httpd, nginx)
   258  			podClient := client.CoreV1().Pods("default")
   259  
   260  			return discoverySim{
   261  				td: disc,
   262  				runAfterSync: func(ctx context.Context) {
   263  					time.Sleep(time.Millisecond * 50)
   264  					_ = podClient.Delete(ctx, httpd.Name, metav1.DeleteOptions{})
   265  					_ = podClient.Delete(ctx, nginx.Name, metav1.DeleteOptions{})
   266  				},
   267  				wantTargetGroups: []model.TargetGroup{
   268  					preparePodTargetGroup(httpd),
   269  					preparePodTargetGroup(nginx),
   270  					prepareEmptyPodTargetGroup(httpd),
   271  					prepareEmptyPodTargetGroup(nginx),
   272  				},
   273  			}
   274  		},
   275  		"DELETE,ADD: remove and add pods after sync": func() discoverySim {
   276  			httpd, nginx := newHTTPDPod(), newNGINXPod()
   277  			disc, client := prepareAllNsPodDiscoverer(httpd)
   278  			podClient := client.CoreV1().Pods("default")
   279  
   280  			return discoverySim{
   281  				td: disc,
   282  				runAfterSync: func(ctx context.Context) {
   283  					time.Sleep(time.Millisecond * 50)
   284  					_ = podClient.Delete(ctx, httpd.Name, metav1.DeleteOptions{})
   285  					_, _ = podClient.Create(ctx, nginx, metav1.CreateOptions{})
   286  				},
   287  				wantTargetGroups: []model.TargetGroup{
   288  					preparePodTargetGroup(httpd),
   289  					prepareEmptyPodTargetGroup(httpd),
   290  					preparePodTargetGroup(nginx),
   291  				},
   292  			}
   293  		},
   294  		"ADD: pods with empty PodIP": func() discoverySim {
   295  			httpd, nginx := newHTTPDPod(), newNGINXPod()
   296  			httpd.Status.PodIP = ""
   297  			nginx.Status.PodIP = ""
   298  			disc, _ := prepareAllNsPodDiscoverer(httpd, nginx)
   299  
   300  			return discoverySim{
   301  				td: disc,
   302  				wantTargetGroups: []model.TargetGroup{
   303  					prepareEmptyPodTargetGroup(httpd),
   304  					prepareEmptyPodTargetGroup(nginx),
   305  				},
   306  			}
   307  		},
   308  		"UPDATE: set pods PodIP after sync": func() discoverySim {
   309  			httpd, nginx := newHTTPDPod(), newNGINXPod()
   310  			httpd.Status.PodIP = ""
   311  			nginx.Status.PodIP = ""
   312  			disc, client := prepareAllNsPodDiscoverer(httpd, nginx)
   313  			podClient := client.CoreV1().Pods("default")
   314  
   315  			return discoverySim{
   316  				td: disc,
   317  				runAfterSync: func(ctx context.Context) {
   318  					time.Sleep(time.Millisecond * 50)
   319  					_, _ = podClient.Update(ctx, newHTTPDPod(), metav1.UpdateOptions{})
   320  					_, _ = podClient.Update(ctx, newNGINXPod(), metav1.UpdateOptions{})
   321  				},
   322  				wantTargetGroups: []model.TargetGroup{
   323  					prepareEmptyPodTargetGroup(httpd),
   324  					prepareEmptyPodTargetGroup(nginx),
   325  					preparePodTargetGroup(newHTTPDPod()),
   326  					preparePodTargetGroup(newNGINXPod()),
   327  				},
   328  			}
   329  		},
   330  		"ADD: pods without containers": func() discoverySim {
   331  			httpd, nginx := newHTTPDPod(), newNGINXPod()
   332  			httpd.Spec.Containers = httpd.Spec.Containers[:0]
   333  			nginx.Spec.Containers = httpd.Spec.Containers[:0]
   334  			disc, _ := prepareAllNsPodDiscoverer(httpd, nginx)
   335  
   336  			return discoverySim{
   337  				td: disc,
   338  				wantTargetGroups: []model.TargetGroup{
   339  					prepareEmptyPodTargetGroup(httpd),
   340  					prepareEmptyPodTargetGroup(nginx),
   341  				},
   342  			}
   343  		},
   344  		"Env: from value": func() discoverySim {
   345  			httpd := newHTTPDPod()
   346  			mangle := func(c *corev1.Container) {
   347  				c.Env = []corev1.EnvVar{
   348  					{Name: "key1", Value: "value1"},
   349  				}
   350  			}
   351  			mangleContainers(httpd.Spec.Containers, mangle)
   352  			data := map[string]string{"key1": "value1"}
   353  
   354  			disc, _ := prepareAllNsPodDiscoverer(httpd)
   355  
   356  			return discoverySim{
   357  				td: disc,
   358  				wantTargetGroups: []model.TargetGroup{
   359  					preparePodTargetGroupWithEnv(httpd, data),
   360  				},
   361  			}
   362  		},
   363  		"Env: from Secret": func() discoverySim {
   364  			httpd := newHTTPDPod()
   365  			mangle := func(c *corev1.Container) {
   366  				c.Env = []corev1.EnvVar{
   367  					{
   368  						Name: "key1",
   369  						ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
   370  							LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
   371  							Key:                  "key1",
   372  						}},
   373  					},
   374  				}
   375  			}
   376  			mangleContainers(httpd.Spec.Containers, mangle)
   377  			data := map[string]string{"key1": "value1"}
   378  			secret := prepareSecret("my-secret", data)
   379  
   380  			disc, _ := prepareAllNsPodDiscoverer(httpd, secret)
   381  
   382  			return discoverySim{
   383  				td: disc,
   384  				wantTargetGroups: []model.TargetGroup{
   385  					preparePodTargetGroupWithEnv(httpd, data),
   386  				},
   387  			}
   388  		},
   389  		"Env: from ConfigMap": func() discoverySim {
   390  			httpd := newHTTPDPod()
   391  			mangle := func(c *corev1.Container) {
   392  				c.Env = []corev1.EnvVar{
   393  					{
   394  						Name: "key1",
   395  						ValueFrom: &corev1.EnvVarSource{ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
   396  							LocalObjectReference: corev1.LocalObjectReference{Name: "my-cmap"},
   397  							Key:                  "key1",
   398  						}},
   399  					},
   400  				}
   401  			}
   402  			mangleContainers(httpd.Spec.Containers, mangle)
   403  			data := map[string]string{"key1": "value1"}
   404  			cmap := prepareConfigMap("my-cmap", data)
   405  
   406  			disc, _ := prepareAllNsPodDiscoverer(httpd, cmap)
   407  
   408  			return discoverySim{
   409  				td: disc,
   410  				wantTargetGroups: []model.TargetGroup{
   411  					preparePodTargetGroupWithEnv(httpd, data),
   412  				},
   413  			}
   414  		},
   415  		"EnvFrom: from ConfigMap": func() discoverySim {
   416  			httpd := newHTTPDPod()
   417  			mangle := func(c *corev1.Container) {
   418  				c.EnvFrom = []corev1.EnvFromSource{
   419  					{
   420  						ConfigMapRef: &corev1.ConfigMapEnvSource{
   421  							LocalObjectReference: corev1.LocalObjectReference{Name: "my-cmap"}},
   422  					},
   423  				}
   424  			}
   425  			mangleContainers(httpd.Spec.Containers, mangle)
   426  			data := map[string]string{"key1": "value1", "key2": "value2"}
   427  			cmap := prepareConfigMap("my-cmap", data)
   428  
   429  			disc, _ := prepareAllNsPodDiscoverer(httpd, cmap)
   430  
   431  			return discoverySim{
   432  				td: disc,
   433  				wantTargetGroups: []model.TargetGroup{
   434  					preparePodTargetGroupWithEnv(httpd, data),
   435  				},
   436  			}
   437  		},
   438  		"EnvFrom: from Secret": func() discoverySim {
   439  			httpd := newHTTPDPod()
   440  			mangle := func(c *corev1.Container) {
   441  				c.EnvFrom = []corev1.EnvFromSource{
   442  					{
   443  						SecretRef: &corev1.SecretEnvSource{
   444  							LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"}},
   445  					},
   446  				}
   447  			}
   448  			mangleContainers(httpd.Spec.Containers, mangle)
   449  			data := map[string]string{"key1": "value1", "key2": "value2"}
   450  			secret := prepareSecret("my-secret", data)
   451  
   452  			disc, _ := prepareAllNsPodDiscoverer(httpd, secret)
   453  
   454  			return discoverySim{
   455  				td: disc,
   456  				wantTargetGroups: []model.TargetGroup{
   457  					preparePodTargetGroupWithEnv(httpd, data),
   458  				},
   459  			}
   460  		},
   461  	}
   462  
   463  	for name, createSim := range tests {
   464  		t.Run(name, func(t *testing.T) {
   465  			sim := createSim()
   466  			sim.run(t)
   467  		})
   468  	}
   469  }
   470  
   471  func prepareAllNsPodDiscoverer(objects ...runtime.Object) (*KubeDiscoverer, kubernetes.Interface) {
   472  	return prepareDiscoverer("pod", []string{corev1.NamespaceAll}, objects...)
   473  }
   474  
   475  func preparePodDiscoverer(namespaces []string, objects ...runtime.Object) (*KubeDiscoverer, kubernetes.Interface) {
   476  	return prepareDiscoverer("pod", namespaces, objects...)
   477  }
   478  
   479  func mangleContainers(containers []corev1.Container, mange func(container *corev1.Container)) {
   480  	for i := range containers {
   481  		mange(&containers[i])
   482  	}
   483  }
   484  
   485  var controllerTrue = true
   486  
   487  func newHTTPDPod() *corev1.Pod {
   488  	return &corev1.Pod{
   489  		ObjectMeta: metav1.ObjectMeta{
   490  			Name:        "httpd-dd95c4d68-5bkwl",
   491  			Namespace:   "default",
   492  			UID:         "1cebb6eb-0c1e-495b-8131-8fa3e6668dc8",
   493  			Annotations: map[string]string{"phase": "prod"},
   494  			Labels:      map[string]string{"app": "httpd", "tier": "frontend"},
   495  			OwnerReferences: []metav1.OwnerReference{
   496  				{Name: "netdata-test", Kind: "DaemonSet", Controller: &controllerTrue},
   497  			},
   498  		},
   499  		Spec: corev1.PodSpec{
   500  			NodeName: "m01",
   501  			Containers: []corev1.Container{
   502  				{
   503  					Name:  "httpd",
   504  					Image: "httpd",
   505  					Ports: []corev1.ContainerPort{
   506  						{Name: "http", Protocol: corev1.ProtocolTCP, ContainerPort: 80},
   507  						{Name: "https", Protocol: corev1.ProtocolTCP, ContainerPort: 443},
   508  					},
   509  				},
   510  			},
   511  		},
   512  		Status: corev1.PodStatus{
   513  			PodIP: "172.17.0.1",
   514  		},
   515  	}
   516  }
   517  
   518  func newNGINXPod() *corev1.Pod {
   519  	return &corev1.Pod{
   520  		ObjectMeta: metav1.ObjectMeta{
   521  			Name:        "nginx-7cfd77469b-q6kxj",
   522  			Namespace:   "default",
   523  			UID:         "09e883f2-d740-4c5f-970d-02cf02876522",
   524  			Annotations: map[string]string{"phase": "prod"},
   525  			Labels:      map[string]string{"app": "nginx", "tier": "frontend"},
   526  			OwnerReferences: []metav1.OwnerReference{
   527  				{Name: "netdata-test", Kind: "DaemonSet", Controller: &controllerTrue},
   528  			},
   529  		},
   530  		Spec: corev1.PodSpec{
   531  			NodeName: "m01",
   532  			Containers: []corev1.Container{
   533  				{
   534  					Name:  "nginx",
   535  					Image: "nginx",
   536  					Ports: []corev1.ContainerPort{
   537  						{Name: "http", Protocol: corev1.ProtocolTCP, ContainerPort: 80},
   538  						{Name: "https", Protocol: corev1.ProtocolTCP, ContainerPort: 443},
   539  					},
   540  				},
   541  			},
   542  		},
   543  		Status: corev1.PodStatus{
   544  			PodIP: "172.17.0.2",
   545  		},
   546  	}
   547  }
   548  
   549  func prepareConfigMap(name string, data map[string]string) *corev1.ConfigMap {
   550  	return &corev1.ConfigMap{
   551  		ObjectMeta: metav1.ObjectMeta{
   552  			Name:      name,
   553  			Namespace: "default",
   554  			UID:       types.UID("a03b8dc6-dc40-46dc-b571-5030e69d8167" + name),
   555  		},
   556  		Data: data,
   557  	}
   558  }
   559  
   560  func prepareSecret(name string, data map[string]string) *corev1.Secret {
   561  	secretData := make(map[string][]byte, len(data))
   562  	for k, v := range data {
   563  		secretData[k] = []byte(v)
   564  	}
   565  	return &corev1.Secret{
   566  		ObjectMeta: metav1.ObjectMeta{
   567  			Name:      name,
   568  			Namespace: "default",
   569  			UID:       types.UID("a03b8dc6-dc40-46dc-b571-5030e69d8161" + name),
   570  		},
   571  		Data: secretData,
   572  	}
   573  }
   574  
   575  func prepareEmptyPodTargetGroup(pod *corev1.Pod) *podTargetGroup {
   576  	return &podTargetGroup{source: podSource(pod)}
   577  }
   578  
   579  func preparePodTargetGroup(pod *corev1.Pod) *podTargetGroup {
   580  	tgg := prepareEmptyPodTargetGroup(pod)
   581  
   582  	for _, container := range pod.Spec.Containers {
   583  		for _, port := range container.Ports {
   584  			portNum := strconv.FormatUint(uint64(port.ContainerPort), 10)
   585  			tgt := &PodTarget{
   586  				tuid:           podTUIDWithPort(pod, container, port),
   587  				Address:        net.JoinHostPort(pod.Status.PodIP, portNum),
   588  				Namespace:      pod.Namespace,
   589  				Name:           pod.Name,
   590  				Annotations:    mapAny(pod.Annotations),
   591  				Labels:         mapAny(pod.Labels),
   592  				NodeName:       pod.Spec.NodeName,
   593  				PodIP:          pod.Status.PodIP,
   594  				ControllerName: "netdata-test",
   595  				ControllerKind: "DaemonSet",
   596  				ContName:       container.Name,
   597  				Image:          container.Image,
   598  				Env:            nil,
   599  				Port:           portNum,
   600  				PortName:       port.Name,
   601  				PortProtocol:   string(port.Protocol),
   602  			}
   603  			tgt.hash = mustCalcHash(tgt)
   604  			tgt.Tags().Merge(discoveryTags)
   605  
   606  			tgg.targets = append(tgg.targets, tgt)
   607  		}
   608  	}
   609  
   610  	return tgg
   611  }
   612  
   613  func preparePodTargetGroupWithEnv(pod *corev1.Pod, env map[string]string) *podTargetGroup {
   614  	tgg := preparePodTargetGroup(pod)
   615  
   616  	for _, tgt := range tgg.Targets() {
   617  		tgt.(*PodTarget).Env = mapAny(env)
   618  		tgt.(*PodTarget).hash = mustCalcHash(tgt)
   619  	}
   620  
   621  	return tgg
   622  }