github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/pkg/utils/o11y/controller.go (about) 1 package o11y 2 3 import ( 4 "bufio" 5 "context" 6 "encoding/json" 7 "fmt" 8 "os/exec" 9 "regexp" 10 "strconv" 11 "strings" 12 "time" 13 14 kubeCl "github.com/redhat-appstudio/e2e-tests/pkg/apis/kubernetes" 15 "github.com/redhat-appstudio/e2e-tests/pkg/constants" 16 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" 17 appsv1 "k8s.io/api/apps/v1" 18 corev1 "k8s.io/api/core/v1" 19 "k8s.io/apimachinery/pkg/api/resource" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/labels" 22 "k8s.io/apimachinery/pkg/selection" 23 "k8s.io/apimachinery/pkg/util/wait" 24 "k8s.io/utils/pointer" 25 "sigs.k8s.io/controller-runtime/pkg/client" 26 ) 27 28 type SuiteController struct { 29 *kubeCl.CustomClient 30 } 31 32 type MetricResult struct { 33 Metric map[string]string `json:"metric"` 34 Value []interface{} `json:"value"` 35 } 36 37 func NewSuiteController(kube *kubeCl.CustomClient) (*SuiteController, error) { 38 return &SuiteController{ 39 kube, 40 }, nil 41 } 42 43 func (h *SuiteController) convertBytesToMB(valueInBytes float64) int { 44 valueInMegabytes := valueInBytes / (1000 * 1000) 45 return int(valueInMegabytes) 46 } 47 48 // Fetch metrics for given query 49 func (h *SuiteController) GetMetrics(query string) ([]MetricResult, error) { 50 51 var result struct { 52 Data struct { 53 Result []MetricResult `json:"result"` 54 } `json:"data"` 55 } 56 57 // Temporary way to fetch the metrics, will be replaced by golang http client library 58 // curl -X GET -kG "https://$THANOS_QUERIER_HOST/api/v1/query?" --data-urlencode "query="+query -H "Authorization: Bearer $TOKEN" 59 curlCmd := exec.Command("curl", "-X", "GET", "-kG", "http://localhost:8080/api/v1/query", "--data-urlencode", "query="+query) 60 output, err := curlCmd.Output() 61 if err != nil { 62 return nil, err 63 } 64 65 err = json.Unmarshal(output, &result) 66 if err != nil { 67 return nil, err 68 } 69 70 return result.Data.Result, nil 71 } 72 73 func (h *SuiteController) GetRegexPodNameWithResult(podNameRegex string, results []MetricResult) (map[string]string, error) { 74 podNamesWithResult := make(map[string]string) 75 regex, err := regexp.Compile(podNameRegex) 76 if err != nil { 77 return podNamesWithResult, fmt.Errorf("error compiling regex pattern: %v", err) 78 } 79 80 for _, res := range results { 81 if podName, ok := res.Metric["pod"]; ok { 82 if regex.MatchString(podName) { 83 value := res.Value[1].(string) 84 podNamesWithResult[podName] = value 85 } 86 } 87 } 88 89 if len(podNamesWithResult) == 0 { 90 return nil, fmt.Errorf("no pods matching the regex pattern were found") 91 } 92 93 return podNamesWithResult, nil 94 } 95 96 func (h *SuiteController) ConvertValuesToMB(podNamesWithResult map[string]string) (map[string]int, error) { 97 podNameWithMB := make(map[string]int) 98 99 for podName, value := range podNamesWithResult { 100 valueStr := value 101 102 valueInBytes, err := strconv.ParseFloat(valueStr, 64) 103 if err != nil { 104 return nil, fmt.Errorf("error parsing value for %s: %s", podName, err) 105 } 106 107 valueInMegabytes := h.convertBytesToMB(valueInBytes) 108 podNameWithMB[podName] = int(valueInMegabytes) 109 } 110 111 return podNameWithMB, nil 112 } 113 114 func labelsToSelector(labelMap map[string]string) labels.Selector { 115 selector := labels.NewSelector() 116 for key, value := range labelMap { 117 req, _ := labels.NewRequirement(key, selection.Equals, []string{value}) 118 selector = selector.Add(*req) 119 } 120 return selector 121 } 122 123 func (s *SuiteController) WaitForScriptCompletion(deployment *appsv1.Deployment, successMessage string, timeout time.Duration) error { 124 namespace := deployment.Namespace 125 deploymentName := deployment.Name 126 127 // Get the pod associated with the deployment 128 podList := &corev1.PodList{} 129 labels := deployment.Spec.Selector.MatchLabels 130 labelSelector := labelsToSelector(labels) 131 err := s.KubeRest().List(context.Background(), podList, client.InNamespace(namespace), client.MatchingLabelsSelector{Selector: labelSelector}) 132 if err != nil { 133 return err 134 } 135 136 if len(podList.Items) == 0 { 137 return fmt.Errorf("no pods found for deployment %s", deploymentName) 138 } 139 140 pod := podList.Items[0] 141 142 // Wait for the success message in the pod's log output 143 podLogOpts := &corev1.PodLogOptions{} 144 req := s.KubeInterface().CoreV1().Pods(namespace).GetLogs(pod.Name, podLogOpts) 145 146 err = wait.PollImmediate(time.Second, timeout, func() (bool, error) { 147 readCloser, err := req.Stream(context.Background()) 148 if err != nil { 149 return false, err 150 } 151 defer readCloser.Close() 152 153 scanner := bufio.NewScanner(readCloser) 154 for scanner.Scan() { 155 if strings.Contains(scanner.Text(), successMessage) { 156 return true, nil 157 } 158 } 159 return false, nil 160 }) 161 162 return err 163 } 164 165 func (h *SuiteController) getImagePushScript(secret, quayOrg string) string { 166 return fmt.Sprintf(`#!/bin/sh 167 authFilePath="/tekton/creds-secrets/%s/.dockerconfigjson" 168 destImageRef="quay.io/%s/o11y-workloads" 169 # Set Permissions 170 sed -i 's/^\s*short-name-mode\s*=\s*.*/short-name-mode = "disabled"/' /etc/containers/registries.conf 171 echo 'root:1:4294967294' | tee -a /etc/subuid >> /etc/subgid 172 # Pull Image 173 echo -e "FROM quay.io/libpod/alpine:latest\nRUN dd if=/dev/urandom of=/100mbfile bs=1M count=100" > Dockerfile 174 unshare -Ufp --keep-caps -r --map-users 1,1,65536 --map-groups 1,1,65536 -- buildah bud --tls-verify=false --no-cache -f ./Dockerfile -t "$destImageRef" . 175 IMAGE_SHA_DIGEST=$(buildah images --digests | grep ${destImageRef} | awk '{print $4}') 176 TAGGED_IMAGE_NAME="${destImageRef}:${IMAGE_SHA_DIGEST}" 177 buildah tag ${destImageRef} ${TAGGED_IMAGE_NAME} 178 buildah images 179 buildah push --authfile "$authFilePath" --disable-compression --tls-verify=false ${TAGGED_IMAGE_NAME} 180 if [ $? -eq 0 ]; then 181 # Scraping Interval Period, Pod must stay alive 182 sleep 1m 183 echo "Image push completed" 184 else 185 echo "Image push failed" 186 exit 1 187 fi`, secret, quayOrg) 188 } 189 190 func (h *SuiteController) QuayImagePushPipelineRun(quayOrg, secret, namespace string) (*v1beta1.PipelineRun, error) { 191 pipelineRun := &v1beta1.PipelineRun{ 192 ObjectMeta: metav1.ObjectMeta{ 193 GenerateName: "pipelinerun-egress-", 194 Namespace: namespace, 195 Labels: map[string]string{ 196 "pipelines.appstudio.openshift.io/type": "test", 197 }, 198 }, 199 Spec: v1beta1.PipelineRunSpec{ 200 PipelineSpec: &v1beta1.PipelineSpec{ 201 Tasks: []v1beta1.PipelineTask{ 202 { 203 Name: "buildah-quay", 204 TaskSpec: &v1beta1.EmbeddedTask{ 205 TaskSpec: v1beta1.TaskSpec{ 206 Steps: []v1beta1.Step{ 207 { 208 Name: "pull-and-push-image", 209 Image: "quay.io/redhat-appstudio/buildah:v1.28", 210 Env: []corev1.EnvVar{ 211 {Name: "BUILDAH_FORMAT", Value: "oci"}, 212 {Name: "STORAGE_DRIVER", Value: "vfs"}, 213 }, 214 Script: h.getImagePushScript(secret, quayOrg), 215 SecurityContext: &corev1.SecurityContext{ 216 RunAsUser: pointer.Int64(0), 217 Capabilities: &corev1.Capabilities{ 218 Add: []corev1.Capability{ 219 "SETFCAP", 220 }, 221 }, 222 }, 223 }, 224 }, 225 }, 226 }, 227 }, 228 }, 229 }, 230 }, 231 } 232 233 if err := h.KubeRest().Create(context.Background(), pipelineRun); err != nil { 234 return nil, err 235 } 236 237 return pipelineRun, nil 238 } 239 240 func (h *SuiteController) VCPUMinutesPipelineRun(namespace string) (*v1beta1.PipelineRun, error) { 241 pipelineRun := &v1beta1.PipelineRun{ 242 ObjectMeta: metav1.ObjectMeta{ 243 GenerateName: "pipelinerun-vcpu-", 244 Namespace: namespace, 245 Labels: map[string]string{ 246 "pipelines.appstudio.openshift.io/type": "test", 247 }, 248 }, 249 Spec: v1beta1.PipelineRunSpec{ 250 PipelineSpec: &v1beta1.PipelineSpec{ 251 Tasks: []v1beta1.PipelineTask{ 252 { 253 Name: "vcpu-minutes", 254 TaskSpec: &v1beta1.EmbeddedTask{ 255 TaskSpec: v1beta1.TaskSpec{ 256 Steps: []v1beta1.Step{ 257 { 258 Name: "resource-constraint", 259 Image: "registry.access.redhat.com/ubi9/ubi-micro", 260 Script: "#!/usr/bin/env bash\nsleep 1m\necho 'vCPU Deployment Completed'\n", 261 Resources: corev1.ResourceRequirements{ 262 Requests: corev1.ResourceList{ 263 corev1.ResourceMemory: resource.MustParse("200Mi"), 264 corev1.ResourceCPU: resource.MustParse("200m"), 265 }, 266 Limits: corev1.ResourceList{ 267 corev1.ResourceMemory: resource.MustParse("200Mi"), 268 corev1.ResourceCPU: resource.MustParse("200m"), 269 }, 270 }, 271 }, 272 }, 273 }, 274 }, 275 }, 276 }, 277 }, 278 }, 279 } 280 281 if err := h.KubeRest().Create(context.Background(), pipelineRun); err != nil { 282 return nil, err 283 } 284 285 return pipelineRun, nil 286 } 287 288 func (h *SuiteController) QuayImagePushDeployment(quayOrg, secret, namespace string) (*appsv1.Deployment, error) { 289 Deployment := &appsv1.Deployment{ 290 ObjectMeta: metav1.ObjectMeta{ 291 Name: "deployment-egress", 292 Namespace: namespace, 293 }, 294 Spec: appsv1.DeploymentSpec{ 295 Replicas: pointer.Int32(1), 296 Selector: &metav1.LabelSelector{ 297 MatchLabels: map[string]string{ 298 "app": "deployment-egress", 299 }, 300 }, 301 Template: corev1.PodTemplateSpec{ 302 ObjectMeta: metav1.ObjectMeta{ 303 Labels: map[string]string{ 304 "app": "deployment-egress", 305 }, 306 }, 307 Spec: corev1.PodSpec{ 308 ImagePullSecrets: []corev1.LocalObjectReference{ 309 { 310 Name: secret, 311 }, 312 }, 313 ServiceAccountName: constants.DefaultPipelineServiceAccount, 314 Containers: []corev1.Container{ 315 { 316 Name: "quay-image-push-container", 317 Image: "quay.io/redhat-appstudio/buildah:v1.28", 318 VolumeMounts: []corev1.VolumeMount{ 319 { 320 Name: "docker-config", 321 MountPath: "/tekton/creds-secrets/o11y-tests-token/", 322 ReadOnly: true, 323 }, 324 }, 325 Env: []corev1.EnvVar{ 326 {Name: "BUILDAH_FORMAT", Value: "oci"}, 327 {Name: "STORAGE_DRIVER", Value: "vfs"}, 328 }, 329 Command: []string{"/bin/sh", "-c"}, 330 Args: []string{h.getImagePushScript(secret, quayOrg)}, 331 SecurityContext: &corev1.SecurityContext{ 332 RunAsUser: pointer.Int64(0), 333 Capabilities: &corev1.Capabilities{ 334 Add: []corev1.Capability{ 335 "SETFCAP", 336 }, 337 }, 338 }, 339 }, 340 }, 341 Volumes: []corev1.Volume{ 342 { 343 Name: "docker-config", 344 VolumeSource: corev1.VolumeSource{ 345 Secret: &corev1.SecretVolumeSource{ 346 SecretName: secret, 347 }, 348 }, 349 }, 350 }, 351 }, 352 }, 353 }, 354 } 355 356 if err := h.KubeRest().Create(context.Background(), Deployment); err != nil { 357 return &appsv1.Deployment{}, err 358 } 359 360 return Deployment, nil 361 } 362 363 func (h *SuiteController) VCPUMinutesDeployment(namespace string) (*appsv1.Deployment, error) { 364 Deployment := &appsv1.Deployment{ 365 ObjectMeta: metav1.ObjectMeta{ 366 Name: "deployment-vcpu", 367 Namespace: namespace, 368 }, 369 Spec: appsv1.DeploymentSpec{ 370 Replicas: pointer.Int32(1), 371 Selector: &metav1.LabelSelector{ 372 MatchLabels: map[string]string{ 373 "app": "deployment-vcpu", 374 }, 375 }, 376 Template: corev1.PodTemplateSpec{ 377 ObjectMeta: metav1.ObjectMeta{ 378 Labels: map[string]string{ 379 "app": "deployment-vcpu", 380 }, 381 }, 382 Spec: corev1.PodSpec{ 383 Containers: []corev1.Container{ 384 { 385 Name: "vcpu-minutes", 386 Image: "registry.access.redhat.com/ubi9/ubi-micro", 387 Command: []string{ 388 "/bin/bash", 389 "-c", 390 "sleep 1m ; echo 'vCPU Deployment Completed'", 391 }, 392 Resources: corev1.ResourceRequirements{ 393 Requests: corev1.ResourceList{ 394 corev1.ResourceMemory: resource.MustParse("200Mi"), 395 corev1.ResourceCPU: resource.MustParse("200m"), 396 }, 397 Limits: corev1.ResourceList{ 398 corev1.ResourceMemory: resource.MustParse("200Mi"), 399 corev1.ResourceCPU: resource.MustParse("200m"), 400 }, 401 }, 402 }, 403 }, 404 }, 405 }, 406 }, 407 } 408 409 if err := h.KubeRest().Create(context.Background(), Deployment); err != nil { 410 return nil, err 411 } 412 413 return Deployment, nil 414 }