e2e_tests/utils/utils.go (414 lines of code) (raw):

// Copyright 2019 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package utils contains helper utils for osconfig_tests. package utils import ( "fmt" "math/rand" "path" "strings" "time" daisyCompute "github.com/GoogleCloudPlatform/compute-image-tools/daisy/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/config" api "google.golang.org/api/compute/v1" "google.golang.org/grpc/status" ) var ( testingPkgsProjectName = "gce-pkg-osconfig-testing" yumInstallAgent = ` sed -i 's/repo_gpgcheck=1/repo_gpgcheck=0/g' /etc/yum.repos.d/google-cloud.repo sleep 10 systemctl stop google-osconfig-agent while ! yum install -y google-osconfig-agent; do if [[ n -gt 3 ]]; then exit 1 fi n=$[$n+1] sleep 5 done systemctl start google-osconfig-agent` + CurlPost zypperInstallAgent = ` sleep 10 systemctl stop google-osconfig-agent zypper -n remove google-osconfig-agent while ! zypper -n -i --no-gpg-checks install google-osconfig-agent; do if [[ n -gt 2 ]]; then # Zypper repos are flaky, we retry 3 times then just continue, the agent may be installed fine. zypper --no-refresh -n -i --no-gpg-checks install google-osconfig-agent break fi n=$[$n+1] sleep 5 done systemctl start google-osconfig-agent` + CurlPost // CosSetup sets up serial logging on COS. CosSetup = ` sleep 10 sed -i 's/^#ForwardToConsole=no/ForwardToConsole=yes/' /etc/systemd/journald.conf sed -i 's/^#MaxLevelConsole=info/MaxLevelConsole=debug/' /etc/systemd/journald.conf MaxLevelConsole=debug systemctl force-reload systemd-journald systemctl restart google-osconfig-agent` + CurlPost // CurlPost indicates agent is installed. CurlPost = ` uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/guestInventory/LastUpdated curl -X DELETE $uri -H "Metadata-Flavor: Google" uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/install_done curl -X PUT --data "1" $uri -H "Metadata-Flavor: Google" ` windowsPost = ` $uri = 'http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/guestInventory/LastUpdated' Invoke-RestMethod -Method DELETE -Uri $uri -Headers @{"Metadata-Flavor" = "Google"} Start-Sleep 10 $uri = 'http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/install_done' Invoke-RestMethod -Method PUT -Uri $uri -Headers @{"Metadata-Flavor" = "Google"} -Body 1 ` ) // getRegionFromZone extracts the region from a zone name. // Example: If zone is "us-central1-a", this would return "us-central1" func getRegionFromZone(zone string) string { parts := strings.Split(zone, "-") return strings.Join(parts[:len(parts)-1], "-") } // pickTestRegionForArtifactRegistry selects a random zone from the configured zones to pull osconfig-agent package from AR & selected-region func pickTestRegionForArtifactRegistry() string { zones := config.Zones() if len(zones) == 0 { // default region for tests return "us-central1" } zoneKeys := make([]string, 0, len(zones)) for k := range zones { zoneKeys = append(zoneKeys, k) } randomIndex := rand.Intn(len(zoneKeys)) randomZone := zoneKeys[randomIndex] return getRegionFromZone(randomZone) } // getRepoLineForApt returns the repo line that should be added to apt sources.list file func getRepoLineForApt(osName string) string { repo := config.AgentRepo() if repo == "testing" { testRegion := pickTestRegionForArtifactRegistry() return fmt.Sprintf("deb ar+https://%s-apt.pkg.dev/projects/%s google-osconfig-agent-%s-testing main", testRegion, testingPkgsProjectName, osName) } return fmt.Sprintf("deb http://packages.cloud.google.com/apt google-osconfig-agent-%s-%s main", osName, repo) } // InstallOSConfigDeb installs the osconfig agent on deb based systems. func InstallOSConfigDeb(image string) string { if config.AgentRepo() == "" { return CurlPost } osName := GetDebOsName(image) return fmt.Sprintf(` sleep 10 systemctl stop google-osconfig-agent # install gnupg2 if not exist apt-get update apt-get install -y gnupg2 # install apt-transport-artifact-registry apt-get install -y apt-transport-artifact-registry echo '%s' >> /etc/apt/sources.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - apt-get update apt-get install -y google-osconfig-agent systemctl start google-osconfig-agent`+CurlPost, getRepoLineForApt(osName)) } // InstallOSConfigGooGet installs the osconfig agent on Windows systems. func InstallOSConfigGooGet() string { if config.AgentRepo() == "" { return windowsPost } removeAgentCmd := `c:\programdata\googet\googet.exe -noconfirm remove google-osconfig-agent` if config.AgentRepo() == "stable" { return fmt.Sprintf(` %s c:\programdata\googet\googet.exe -noconfirm install google-osconfig-agent`, removeAgentCmd) + windowsPost } else if config.AgentRepo() == "testing" { testRegion := pickTestRegionForArtifactRegistry() agentRepo := config.AgentRepo() return fmt.Sprintf(` %s c:\programdata\googet\googet.exe addrepo google-osconfig-agent-googet-testing https://%s-googet.pkg.dev/projects/%s/repos/google-osconfig-agent-googet-%s # set useoauth to true in repo new file $filePath = 'C:\ProgramData\GooGet\repos\google-osconfig-agent-googet-testing.repo' (Get-Content $filePath) -replace 'useoauth: false', 'useoauth: true' | Set-Content $filePath c:\programdata\googet\googet.exe -noconfirm install google-osconfig-agent`+windowsPost, removeAgentCmd, testRegion, testingPkgsProjectName, agentRepo) } return fmt.Sprintf(` %s c:\programdata\googet\googet.exe -noconfirm install -sources https://packages.cloud.google.com/yuck/repos/google-osconfig-agent-%s google-osconfig-agent `+windowsPost, removeAgentCmd, config.AgentRepo()) } // getYumRepoBaseURL returns the repo baseUrl that should be added to repo file google-osconfig-agent.repo func getYumRepoBaseURL(osType string) string { agentRepo := config.AgentRepo() if agentRepo == "testing" { testRegion := pickTestRegionForArtifactRegistry() return fmt.Sprintf("https://%s-yum.pkg.dev/projects/%s/google-osconfig-agent-%s-testing", testRegion, testingPkgsProjectName, osType) } return fmt.Sprintf("https://packages.cloud.google.com/yum/repos/google-osconfig-agent-%s-%s", osType, agentRepo) } func getYumRepoSetup(osType string) string { gpgcheck := 1 // According to doc, pkg name differ according to ELv version // doc: https://cloud.google.com/artifact-registry/docs/os-packages/rpm/configure#prepare-yum format := "dnf" if osType == "el7" { format = "yum" } repoConfig := fmt.Sprintf(` # install yum-plugin-artifact-registry yum makecache yum install -y %s-plugin-artifact-registry cat > /etc/yum.repos.d/google-osconfig-agent.repo <<EOM [google-osconfig-agent] name=Google OSConfig Agent Repository baseurl=%s enabled=1 gpgcheck=%d gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg EOM`, format, getYumRepoBaseURL(osType), gpgcheck) return repoConfig } // InstallOSConfigEL9 installs the osconfig agent on el9 based systems. (RHEL) func InstallOSConfigEL9() string { if config.AgentRepo() == "" { return CurlPost } if config.AgentRepo() == "stable" { return yumInstallAgent } if config.AgentRepo() == "staging" { return getYumRepoSetup("el9") + yumInstallAgent } return getYumRepoSetup("el9") + yumInstallAgent } // InstallOSConfigEL8 installs the osconfig agent on el8 based systems. (RHEL) func InstallOSConfigEL8() string { if config.AgentRepo() == "" { return CurlPost } if config.AgentRepo() == "stable" { return yumInstallAgent } if config.AgentRepo() == "staging" { return getYumRepoSetup("el8") + yumInstallAgent } return getYumRepoSetup("el8") + yumInstallAgent } // InstallOSConfigEL7 installs the osconfig agent on el7 based systems. func InstallOSConfigEL7() string { if config.AgentRepo() == "" { return CurlPost } if config.AgentRepo() == "stable" { return yumInstallAgent } if config.AgentRepo() == "staging" { return getYumRepoSetup("el7") + yumInstallAgent } return getYumRepoSetup("el7") + yumInstallAgent } // containsAnyOf checks if a string contains any substring from a given list. func containsAnyOf(str string, substrings []string) bool { for _, substring := range substrings { if strings.Contains(str, substring) { return true } } return false } // InstallOSConfigEL installs the osconfig agent on el based systems. func InstallOSConfigEL(image string) string { imageName := path.Base(image) switch { case image == "9" || containsAnyOf(imageName, []string{"rhel-9", "rhel-sap-9", "centos-stream-9", "rocky-linux-9"}): return InstallOSConfigEL9() case image == "8" || containsAnyOf(imageName, []string{"rhel-8", "rhel-sap-8", "centos-stream-8", "rocky-linux-8"}): return InstallOSConfigEL8() case image == "7" || containsAnyOf(imageName, []string{"rhel-7", "rhel-sap-7", "centos-7"}): return InstallOSConfigEL7() } return "" } func getZypperRepoSetup(osType string) string { gpgcheck := 1 // TODO: Allow SUSE tests to pull packages from test project. repoConfig := fmt.Sprintf(` cat > /etc/zypp/repos.d/google-osconfig-agent.repo <<EOM [google-osconfig-agent] name=Google OSConfig Agent Repository baseurl=%s enabled=1 gpgcheck=%d gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg EOM`, getYumRepoBaseURL(osType), gpgcheck) return repoConfig } // InstallOSConfigSUSE installs the osconfig agent on suse systems. func InstallOSConfigSUSE() string { if config.AgentRepo() == "" { return "" } if config.AgentRepo() == "staging" || config.AgentRepo() == "stable" { return getZypperRepoSetup("el8") + zypperInstallAgent } return getZypperRepoSetup("el8") + zypperInstallAgent } // GetDebOsName returns the equivalent os_name for deb version (e.g. debian-11 --> bullseye) func GetDebOsName(image string) string { imageName := path.Base(image) switch { case image == "10" || containsAnyOf(imageName, []string{"debian-10", "buster"}): return "buster" case image == "11" || containsAnyOf(imageName, []string{"debian-11", "bullseye"}): return "bullseye" case image == "12" || containsAnyOf(imageName, []string{"debian-12", "bookworm"}): return "bookworm" } return "" } // DowngradeBullseyeAptImages is a single image that are used for testing downgrade case with apt-get var DowngradeBullseyeAptImages = map[string]string{ "debian-cloud/debian-11": "projects/debian-cloud/global/images/debian-11-bullseye-v20231010", } // HeadBusterAptImages empty for now as debian-10 wil reach EOL and some of its repos are not reachable anymore. var HeadBusterAptImages = map[string]string{ // Debian images. } // HeadBullseyeAptImages is a map of names to image paths for public debian-11 images var HeadBullseyeAptImages = map[string]string{ // Debian images. "debian-cloud/debian-11": "projects/debian-cloud/global/images/family/debian-11", } // HeadBookwormAptImages is a map of names to image paths for public debian-12 images var HeadBookwormAptImages = map[string]string{ // Debian images. "debian-cloud/debian-12": "projects/debian-cloud/global/images/family/debian-12", } // HeadSUSEImages is a map of names to image paths for public SUSE images. var HeadSUSEImages = func() map[string]string { imgsMap := make(map[string]string) // TODO: enable SUSE tests to use testing pkgs after Artifact Registry supports zypper installation from private repos if config.AgentRepo() != "testing" { imgsMap = map[string]string{ "suse-cloud/sles-12-sp5": "projects/suse-cloud/global/images/family/sles-12", "suse-cloud/sles-15-sp5": "projects/suse-cloud/global/images/family/sles-15", "suse-sap-cloud/sles-12-sp5-sap": "projects/suse-sap-cloud/global/images/family/sles-12-sp5-sap", "suse-sap-cloud/sles-15-sp5-sap": "projects/suse-sap-cloud/global/images/family/sles-15-sp5-sap", "suse-sap-cloud/sles-15-sp6-sap": "projects/suse-sap-cloud/global/images/family/sles-15-sp6-sap", "suse-sap-cloud/sles-15-sp5-hardened-sap": "projects/suse-sap-cloud/global/images/family/sles-sap-15-sp5-hardened", "opensuse-cloud/opensuse-leap-15": "projects/opensuse-cloud/global/images/family/opensuse-leap", } } return imgsMap }() // OldSUSEImages is a map of names to image paths for public SUSE images. var OldSUSEImages = func() map[string]string { imgsMap := make(map[string]string) // TODO: enable SUSE tests to use testing pkgs after Artifact Registry supports zypper installation from private repos if config.AgentRepo() != "testing" { imgsMap = map[string]string{ "old/sles-15-sp3-sap": "projects/suse-sap-cloud/global/images/sles-15-sp3-sap-v20240808-x86-64", "old/sles-15-sp4-sap": "projects/suse-sap-cloud/global/images/sles-15-sp4-sap-v20240208-x86-64", } } return imgsMap }() // HeadEL8Images is a map of names to image paths for public EL8 image families. (RHEL, CentOS, Rocky) var HeadEL8Images = map[string]string{ "rhel-cloud/rhel-8": "projects/rhel-cloud/global/images/family/rhel-8", "rhel-sap-cloud/rhel-8-4-sap": "projects/rhel-sap-cloud/global/images/family/rhel-8-4-sap-ha", "rhel-sap-cloud/rhel-8-6-sap": "projects/rhel-sap-cloud/global/images/family/rhel-8-6-sap-ha", "rhel-sap-cloud/rhel-8-8-sap": "projects/rhel-sap-cloud/global/images/family/rhel-8-8-sap-ha", "rocky-linux-cloud/rocky-linux-8": "projects/rocky-linux-cloud/global/images/family/rocky-linux-8", "rocky-linux-cloud/rocky-linux-8-optimized-gcp": "projects/rocky-linux-cloud/global/images/family/rocky-linux-8-optimized-gcp", } // OldEL8Images is a map of names to image paths for old EL8 images. (RHEL, CentOS, Rocky) var OldEL8Images = map[string]string{ // Currently empty } // HeadEL9Images is a map of names to image paths for public EL9 image families. (RHEL, CentOS, Rocky) var HeadEL9Images = map[string]string{ "centos-cloud/centos-stream-9": "projects/centos-cloud/global/images/family/centos-stream-9", "rhel-cloud/rhel-9": "projects/rhel-cloud/global/images/family/rhel-9", "rhel-sap-cloud/rhel-9-0-sap": "projects/rhel-sap-cloud/global/images/family/rhel-9-0-sap-ha", "rhel-sap-cloud/rhel-9-2-sap": "projects/rhel-sap-cloud/global/images/family/rhel-9-2-sap-ha", "rocky-linux-cloud/rocky-linux-9": "projects/rocky-linux-cloud/global/images/family/rocky-linux-9", "rocky-linux-cloud/rocky-linux-9-optimized-gcp": "projects/rocky-linux-cloud/global/images/family/rocky-linux-9-optimized-gcp", } // OldEL9Images is a map of names to image paths for old EL9 images. (RHEL, CentOS, Rocky) var OldEL9Images = map[string]string{ // Currently empty } // HeadELImages is a map of names to image paths for public EL image families. (RHEL, CentOS, Rocky) var HeadELImages = func() (newMap map[string]string) { newMap = make(map[string]string) for k, v := range HeadEL8Images { newMap[k] = v } for k, v := range HeadEL9Images { newMap[k] = v } return }() // HeadAptImages is a map of names to image paths for public EL image families. (RHEL, CentOS, Rocky) var HeadAptImages = func() (newMap map[string]string) { newMap = make(map[string]string) for k, v := range HeadBusterAptImages { newMap[k] = v } for k, v := range HeadBullseyeAptImages { newMap[k] = v } for k, v := range HeadBookwormAptImages { newMap[k] = v } return }() // HeadWindowsImages is a map of names to image paths for public Windows image families. var HeadWindowsImages = map[string]string{ "windows-cloud/windows-2016": "projects/windows-cloud/global/images/family/windows-2016", "windows-cloud/windows-2016-core": "projects/windows-cloud/global/images/family/windows-2016-core", "windows-cloud/windows-2019": "projects/windows-cloud/global/images/family/windows-2019", "windows-cloud/windows-2019-core": "projects/windows-cloud/global/images/family/windows-2019-core", // Testing of win-2022-dc disabled because of https://techcommunity.microsoft.com/t5/windows-server-for-it-pro/faulty-patches-on-server-2022/m-p/4028125 /* "windows-cloud/windows-2022": "projects/windows-cloud/global/images/family/windows-2022", "windows-cloud/windows-2022-core": "projects/windows-cloud/global/images/family/windows-2022-core", */ } // OldWindowsImages is a map of names to image paths for old Windows images. var OldWindowsImages = map[string]string{ // Currently empty } // HeadCOSImages is a map of names to image paths for public COS image families. var HeadCOSImages = map[string]string{ "cos-cloud/cos-stable": "projects/cos-cloud/global/images/family/cos-stable", "cos-cloud/cos-beta": "projects/cos-cloud/global/images/family/cos-beta", "cos-cloud/cos-dev": "projects/cos-cloud/global/images/family/cos-dev", } // RandString generates a random string of n length. func RandString(n int) string { gen := rand.New(rand.NewSource(time.Now().UnixNano())) letters := "bdghjlmnpqrstvwxyz0123456789" b := make([]byte, n) for i := range b { b[i] = letters[gen.Int63()%int64(len(letters))] } return string(b) } // GetStatusFromError return a string that contains all information // about the error that is created from a status func GetStatusFromError(err error) string { if s, ok := status.FromError(err); ok { return fmt.Sprintf("code: %q, message: %q, details: %q", s.Code(), s.Message(), s.Details()) } return fmt.Sprintf("%v", err) } // This pool is just used for CreateComputeInstance so that we limit our calls to the API during the heavy create process. var pool = make(chan struct{}, 10) // CreateComputeInstance is an utility function to create gce instance func CreateComputeInstance(metadataitems []*api.MetadataItems, client daisyCompute.Client, machineType, image, name, projectID, zone, serviceAccountEmail string, serviceAccountScopes []string) (*compute.Instance, error) { pool <- struct{}{} defer func() { <-pool }() var items []*api.MetadataItems // enable debug logging and guest-attributes for all test instances items = append(items, compute.BuildInstanceMetadataItem("enable-os-config-debug", "true")) items = append(items, compute.BuildInstanceMetadataItem("enable-guest-attributes", "true")) if config.AgentSvcEndpoint() != "" { items = append(items, compute.BuildInstanceMetadataItem("os-config-endpoint", config.AgentSvcEndpoint())) } for _, item := range metadataitems { items = append(items, item) } i := &api.Instance{ Name: name, MachineType: fmt.Sprintf("projects/%s/zones/%s/machineTypes/%s", projectID, zone, machineType), NetworkInterfaces: []*api.NetworkInterface{ { Subnetwork: fmt.Sprintf("projects/%s/regions/%s/subnetworks/default", projectID, zone[:len(zone)-2]), AccessConfigs: []*api.AccessConfig{ { Type: "ONE_TO_ONE_NAT", }, }, }, }, Metadata: &api.Metadata{ Items: items, }, Disks: []*api.AttachedDisk{ { AutoDelete: true, Boot: true, InitializeParams: &api.AttachedDiskInitializeParams{ SourceImage: image, DiskType: fmt.Sprintf("projects/%s/zones/%s/diskTypes/pd-balanced", projectID, zone), }, }, }, ServiceAccounts: []*api.ServiceAccount{ { Email: serviceAccountEmail, Scopes: serviceAccountScopes, }, }, Labels: map[string]string{"name": name}, } inst, err := compute.CreateInstance(client, projectID, zone, i) if err != nil { return nil, err } return inst, nil }