diff --git a/changelog/v0.5.1/cloudbuild.yaml b/changelog/v0.5.1/cloudbuild.yaml new file mode 100644 index 0000000..ad2aec3 --- /dev/null +++ b/changelog/v0.5.1/cloudbuild.yaml @@ -0,0 +1,3 @@ +changelog: +- type: NON_USER_FACING + description: "basic e2e test" diff --git a/cloudbuild.yaml b/cloudbuild.yaml index fdb676a..4a7f1ea 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -53,6 +53,19 @@ steps: waitFor: ['get-creds'] id: 'check-code-and-docs-gen' +- name: 'gcr.io/$PROJECT_ID/e2e-ginkgo' + env: + - 'PROJECT_ROOT=github.com/solo-io/squash' + - 'GOPATH=/workspace/gopath' + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=test-cluster' + - 'RUN_KUBE_TESTS=1' + - 'DOCKER_CONFIG=/workspace/.docker/' + dir: './gopath/src/github.com/solo-io/squash' + args: ['-r', '-failFast', '-p'] + waitFor: ['get-creds', 'check-code-and-docs-gen'] + id: 'test' + - name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: ['-c', 'docker login quay.io --username "solo-io+solobot" --password $$QUAY_IO_PASSWORD'] @@ -60,7 +73,7 @@ steps: id: 'docker-login' - name: 'gcr.io/$PROJECT_ID/go-make' - args: ['docker'] + args: ['-j', 'docker'] env: - 'PROJECT_ROOT=github.com/solo-io/squash' - 'GOPATH=/workspace/gopath' @@ -68,7 +81,7 @@ steps: - 'BUILD_ID=$BUILD_ID' - 'GCLOUD_PROJECT_ID=$PROJECT_ID' dir: './gopath/src/github.com/solo-io/squash' - waitFor: ['docker-login', 'check-code-and-docs-gen'] + waitFor: ['test', 'docker-login', 'check-code-and-docs-gen'] id: 'compile' - name: 'gcr.io/$PROJECT_ID/go-make' @@ -104,5 +117,3 @@ secrets: VSCODE_TOKEN: CiQABlzmSepRzBG6r2UapqKVaJfx5X3PQgWpuKtIinDWI4IpZsASXQCCPGSGtYjgB1ARs6VcRy3J23Mlbo7zeqPamti48qk71axnOBu4pSomCTKj+4iB81E/dgJEmo9aXOIfPoSv7jEs1ijN7J326jA+AOS1M4eUQwfAWovUtmjecP0p+Q== timeout: 3000s -options: - machineType: 'N1_HIGHCPU_8' diff --git a/editor/vscode/src/extension.ts b/editor/vscode/src/extension.ts index b5f9981..5229bc5 100644 --- a/editor/vscode/src/extension.ts +++ b/editor/vscode/src/extension.ts @@ -64,12 +64,11 @@ async function getremote(extPath: string): Promise { // exit this early until release is smoothed out - return ""; if (fs.existsSync(execpath)) { let exechash = await hash(execpath); // make sure its the one we expect: // this can happen on version updates. - if (exechash !== ks.checksum) { + if (!hashesMatch(ks.checksum, exechash)) { // remove the bad binary. fs.unlinkSync(execpath); } @@ -87,9 +86,7 @@ async function getremote(extPath: string): Promise { // test after the download let exechash = await hash(execpath); // make sure its the one we expect: - // first split because the github hash includes the filename - let hashParts = ks.checksum.split(" "); - if (hashParts.length != 2 || exechash !== hashParts[0]) { + if (!hashesMatch(ks.checksum, exechash)) { // remove the bad binary. fs.unlinkSync(execpath); throw new Error("bad checksum for binary; download may be corrupted - please try again."); @@ -114,6 +111,16 @@ function hash(f: string): Promise { }); } +// solo is the hash that was created from the squashctl binary when the binary was compiled +// gen is the hash that was generated locally from the squashctl file that the extension is trying to use +function hashesMatch(solo: string, gen: string): boolean { + let hashParts = solo.split(" "); + if (hashParts.length !== 2 || gen !== hashParts[0]) { + return false; + } + return true; +} + function download2file(what: string, to: string): Promise { return new Promise((resolve, reject) => { @@ -170,11 +177,11 @@ class SquashExtension { async debug() { let squashpath: string = get_conf_or("path", null); - console.log("using squashctl from:"); - console.log(squashpath); if (!squashpath) { squashpath = await getremote(this.context.extensionPath); } + console.log("using squashctl from:"); + console.log(squashpath); if (!vscode.workspace.workspaceFolders) { throw new Error("no workspace folders"); @@ -469,7 +476,7 @@ interface SquashctlBinary { function createSquashctlBinary(os: string, checksum: string): SquashctlBinary { let link = "https://github.com/solo-io/squash/releases/download/" + getSquashInfo().version + "/" + getSquashInfo().baseName + "-" + os; - console.log("trying to dl from: " + link) + console.log("trying to dl from: " + link); return { link: "https://github.com/solo-io/squash/releases/download/" + getSquashInfo().version + "/" + getSquashInfo().baseName + "-" + os, checksum: checksum diff --git a/pkg/config/squash.go b/pkg/config/squash.go index 98cf11c..b7b7df2 100644 --- a/pkg/config/squash.go +++ b/pkg/config/squash.go @@ -80,12 +80,9 @@ func StartDebugContainer(s Squash, dbt DebugTarget) (*v1.Pod, error) { } // wait for running state - // ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.TimeoutSeconds)*time.Second) - ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) err = <-s.waitForPod(ctx, createdPod) cancel() - // ctx, cancel = context.WithTimeout(context.Background(), time.Duration(s.TimeoutSeconds)*time.Second) - // err <-s.waitForDebugAttachment(ctx) if err != nil { // s.printError(createdPodName) return nil, fmt.Errorf("Waiting for pod: %v", err) @@ -283,9 +280,9 @@ func (s *Squash) waitForPod(ctx context.Context, createdPod *v1.Pod) <-chan erro if createdPod.Status.Phase != v1.PodPending { // err := s.printError(createdPod) if err != nil { - errchan <- errors.Wrap(err, "pod is not running and not pending") + errchan <- errors.Wrapf(err, "pod is not running and not pending, status: %v", createdPod.Status.Phase) } else { - errchan <- errors.New("pod is not running and not pending") + errchan <- errors.New(fmt.Sprintf("pod is not running and not pending, status: %v", createdPod.Status.Phase)) } return } diff --git a/pkg/debuggers/local/utils.go b/pkg/debuggers/local/utils.go index 9045873..1bdca29 100644 --- a/pkg/debuggers/local/utils.go +++ b/pkg/debuggers/local/utils.go @@ -83,7 +83,7 @@ func waitForDebugServerAddress(daName, daNamespace string) (*v1.DebugAttachment, } // TODO - make timeout configurable - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + ctx, cancel := context.WithTimeout(ctx, 300*time.Second) defer cancel() for { select { diff --git a/pkg/squashctl/util.go b/pkg/squashctl/util.go index a8b9c0c..4fcfa97 100644 --- a/pkg/squashctl/util.go +++ b/pkg/squashctl/util.go @@ -18,6 +18,7 @@ import ( "gopkg.in/AlecAivazis/survey.v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) @@ -206,10 +207,11 @@ func (o *Options) createPlankPermissions() error { // create namespace. ignore errors as it most likely exists and will error cs.CoreV1().Namespaces().Create(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) - _, err = cs.CoreV1().ServiceAccounts(namespace).Get(sqOpts.PlankServiceAccountName, metav1.GetOptions{}) - if err == nil { - // service account already exists, no need to create it - return nil + if _, err := cs.CoreV1().ServiceAccounts(namespace).Get(sqOpts.PlankServiceAccountName, metav1.GetOptions{}); err != nil { + if !errors.IsAlreadyExists(err) { + // service account already exists, no need to create it + return err + } } sa := corev1.ServiceAccount{ @@ -218,9 +220,11 @@ func (o *Options) createPlankPermissions() error { }, } - fmt.Printf("Creating service account %v in namespace %v\n", sqOpts.PlankServiceAccountName, namespace) + o.info(fmt.Sprintf("Creating service account %v in namespace %v\n", sqOpts.PlankServiceAccountName, namespace)) if _, err := cs.CoreV1().ServiceAccounts(namespace).Create(&sa); err != nil { - fmt.Println(err) + if !errors.IsAlreadyExists(err) { + return err + } } cr := &rbacv1.ClusterRole{ @@ -251,8 +255,11 @@ func (o *Options) createPlankPermissions() error { }, }, } + o.info(fmt.Sprintf("Creating cluster role %v \n", sqOpts.PlankClusterRoleName)) if _, err := cs.Rbac().ClusterRoles().Create(cr); err != nil { - fmt.Println(err) + if !errors.IsAlreadyExists(err) { + return err + } } crb := &rbacv1.ClusterRoleBinding{ @@ -272,9 +279,14 @@ func (o *Options) createPlankPermissions() error { Kind: "ClusterRole", }, } + + o.info(fmt.Sprintf("Creating cluster role binding %v \n", sqOpts.PlankClusterRoleBindingName)) if _, err := cs.Rbac().ClusterRoleBindings().Create(crb); err != nil { - fmt.Println(err) + if !errors.IsAlreadyExists(err) { + return err + } } + o.info(fmt.Sprintf("All squashctl permission resources created.\n")) return nil } @@ -285,3 +297,11 @@ func getClientSet() (kubernetes.Interface, error) { } return kubernetes.NewForConfig(restCfg) } + +// only print info if squashctl is being used by a human +// machine mode currently expects an exact output +func (o *Options) info(msg string) { + if !o.Squash.Machine { + fmt.Println(msg) + } +} diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 6d7ffea..6f94d48 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -1,8 +1,13 @@ package e2e_test import ( + "fmt" + "math/rand" + "time" + . "github.com/onsi/ginkgo" "github.com/solo-io/solo-kit/test/helpers" + "github.com/solo-io/squash/test/testutils" "testing" ) @@ -12,5 +17,13 @@ func TestE2e(t *testing.T) { helpers.RegisterCommonFailHandlers() helpers.SetupLog() - RunSpecs(t, "E2e Suite") + RunSpecs(t, "E2e Squash Suite") } + +var _ = BeforeSuite(func() { + testutils.DeclareTestConditions() + + seed := time.Now().UnixNano() + fmt.Printf("rand seed: %v\n", seed) + rand.Seed(seed) +}) diff --git a/test/e2e/session_test.go b/test/e2e/session_test.go index 12729ca..d7e9b17 100644 --- a/test/e2e/session_test.go +++ b/test/e2e/session_test.go @@ -1,203 +1,206 @@ package e2e_test import ( + "encoding/json" "fmt" "math/rand" - "os" + "os/exec" + "regexp" + "strings" "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - v1 "github.com/solo-io/squash/pkg/api/v1" + gokubeutils "github.com/solo-io/go-utils/kubeutils" + "github.com/solo-io/squash/pkg/config" sqOpts "github.com/solo-io/squash/pkg/options" - squashcli "github.com/solo-io/squash/pkg/squashctl" + "github.com/solo-io/squash/pkg/utils" "github.com/solo-io/squash/test/testutils" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) -//const KubeEndpoint = "http://localhost:8001/api" - -func Must(err error) { - Expect(err).NotTo(HaveOccurred()) +func check(err error) { + ExpectWithOffset(1, err).NotTo(HaveOccurred()) } -var ( - daName = "debug-attachment-1" - daName2 = "debug-attachment-2" - // testNamespace = "squash-debugger-test2" - testNamespace = "stest" - testNSRoot = "stest" - testsStarted = 0 -) - var _ = Describe("Single debug mode", func() { - seed := time.Now().UnixNano() - fmt.Printf("rand seed: %v\n", seed) - rand.Seed(seed) - - var ( - params testutils.E2eParams - ) - - // Deploy the services that you will debug - BeforeEach(func() { - testsStarted++ - // Use unique namespaces so we can start tests before namespace is deleted - // Use predictable namespaces so that we can establish watches - // (solo-kit does not have a "watch all namespaces" feature yet) - if os.Getenv("SERIALIZE_NAMESPACES") != "1" { - testNamespace = fmt.Sprintf("%v-%v", testNSRoot, rand.Int31n(100000)) - } else { - testNamespace = fmt.Sprintf("%v-%v", testNSRoot, testsStarted) - } - params = testutils.NewE2eParams(testNamespace, daName, GinkgoWriter) - params.SetupE2e() + It("Should create a debug session", func() { + plankNamespace := sqOpts.SquashNamespace + cs := &kubernetes.Clientset{} + By("should get a kube client") + restCfg, err := gokubeutils.GetConfig("", "") + check(err) + cs, err = kubernetes.NewForConfig(restCfg) + check(err) + + By("should list no resources after delete") + // Run delete before testing to ensure there are no lingering artifacts + err = testutils.Squashctl("utils delete-attachments") + check(err) + str, err := testutils.SquashctlOut("utils list-attachments") + check(err) + validateUtilsListDebugAttachments(str, 0) + + // create namespace + testNamespace := fmt.Sprintf("testsquash-%v", rand.Intn(1000)) + By("should create a demo namespace") + _, err = cs.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}) + check(err) + + By("should deploy a demo app") + err = testutils.Squashctl(fmt.Sprintf("deploy demo --demo-id %v --demo-namespace1 %v --demo-namespace2 %v", "go-java", testNamespace, testNamespace)) + check(err) + + By("should find the demo deployment") + podName, err := waitForPod(cs, testNamespace, "example-service1") + check(err) + + By("should attach a debugger") + dbgStr, err := testutils.SquashctlOut(testutils.MachineDebugArgs("dlv", testNamespace, podName)) + check(err) + validateMachineDebugOutput(dbgStr) + fmt.Println(dbgStr) + + By("should have created the required permissions") + check(ensurePlankPermissionsWereCreated(cs, plankNamespace)) + + By("should speak with dlv") + ensureDLVServerIsLive(dbgStr) + + By("should list expected resources after debug session initiated") + attachmentList, err := testutils.SquashctlOut("utils list-attachments") + check(err) + validateUtilsListDebugAttachments(attachmentList, 1) + + // cleanup + By("should cleanup") + check(cs.CoreV1().Namespaces().Delete(testNamespace, &metav1.DeleteOptions{})) }) +}) - // Delete the resources you created - AfterEach(params.CleanupE2e) - - Describe("Single Container mode", func() { - It("should get a debug server endpoint", func() { - container := params.CurrentMicroservicePod.Spec.Containers[0] - - time.Sleep(3 * time.Second) - dbgattachment, err := params.UserController.Attach(daName, params.Namespace, container.Image, params.CurrentMicroservicePod.ObjectMeta.Name, container.Name, "", "dlv") - Expect(err).NotTo(HaveOccurred()) - - time.Sleep(9 * time.Second) - - updatedattachment, err := squashcli.WaitCmd(testNamespace, dbgattachment.Metadata.Name, 1.0) - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment.DebugServerAddress).ToNot(BeEmpty()) - - // TODO(mitchdraft) put selector spec in a shared package - nsPods, err := params.KubeClient.CoreV1().Pods(params.Namespace).List(metav1.ListOptions{LabelSelector: sqOpts.SquashLabelSelectorString}) - Expect(err).NotTo(HaveOccurred()) - Expect(len(nsPods.Items)).To(Equal(1)) - - }) - - It("should get a debug server endpoint, specific process", func() { - container := params.CurrentMicroservicePod.Spec.Containers[0] - - dbgattachment, err := params.UserController.Attach(daName, params.Namespace, container.Image, params.CurrentMicroservicePod.ObjectMeta.Name, container.Name, "service1", "dlv") - Expect(err).NotTo(HaveOccurred()) - time.Sleep(5 * time.Second) - - updatedattachment, err := squashcli.WaitCmd(testNamespace, dbgattachment.Metadata.Name, 1.0) - - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment.DebugServerAddress).ToNot(BeEmpty()) - }) - - It("should get a debug server endpoint, specific process that doesn't exist", func() { - container := params.CurrentMicroservicePod.Spec.Containers[0] - - dbgattachment, err := params.UserController.Attach(daName, params.Namespace, container.Image, params.CurrentMicroservicePod.ObjectMeta.Name, container.Name, "processNameDoesntExist", "dlv") - Expect(err).NotTo(HaveOccurred()) - - time.Sleep(time.Second) - - updatedattachment, err := squashcli.WaitCmd(testNamespace, dbgattachment.Metadata.Name, 1.0) - - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment.Status.State).NotTo(Equal(core.Status_Accepted)) - }) - It("should attach to two micro services", func() { - container1 := params.CurrentMicroservicePod.Spec.Containers[0] - dbgattachment1, err := params.UserController.Attach(daName, - params.Namespace, - container1.Image, - params.CurrentMicroservicePod.ObjectMeta.Name, - container1.Name, - "", - "dlv") - time.Sleep(5 * time.Second) - Expect(err).NotTo(HaveOccurred()) - updatedattachment1, err := squashcli.WaitCmd(testNamespace, dbgattachment1.Metadata.Name, 1.0) - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment1.State).To(Equal(v1.DebugAttachment_Attached)) - - container2 := params.Current2MicroservicePod.Spec.Containers[0] - dbgattachment2, err := params.UserController.Attach(daName2, - params.Namespace, - container2.Image, - params.Current2MicroservicePod.ObjectMeta.Name, - container2.Name, - "", - "dlv") - time.Sleep(5 * time.Second) - Expect(err).NotTo(HaveOccurred()) - updatedattachment2, err := squashcli.WaitCmd(testNamespace, dbgattachment2.Metadata.Name, 1.0) - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment2.State).To(Equal(v1.DebugAttachment_Attached)) - }) - - It("should attach and detatch", func() { - container := params.CurrentMicroservicePod.Spec.Containers[0] - - dbgattachment, err := params.UserController.Attach(daName, params.Namespace, container.Image, params.CurrentMicroservicePod.ObjectMeta.Name, container.Name, "", "dlv") - Expect(err).NotTo(HaveOccurred()) - testutils.ExpectCounts(params, daName). - SumPreAttachments(1). - Attachments(0). - SumPostAttachments(0) - - time.Sleep(5 * time.Second) - - updatedattachment, err := squashcli.WaitCmd(testNamespace, dbgattachment.Metadata.Name, 1.0) - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment.State).To(Equal(v1.DebugAttachment_Attached)) - - testutils.ExpectCounts(params, daName). - SumPreAttachments(0). - Attachments(1). - SumPostAttachments(0) - - dbgattachment, err = params.UserController.RequestDelete(params.Namespace, daName) - Expect(err).NotTo(HaveOccurred()) - - testutils.ExpectCounts(params, daName). - SumPreAttachments(0). - Attachments(0). - SumPostAttachments(1) - - time.Sleep(5 * time.Second) - testutils.ExpectCounts(params, daName). - Total(0) - }) - - It("Be able to re-attach once session exited", func() { - container := params.CurrentMicroservicePod.Spec.Containers[0] - - dbgattachment, err := params.UserController.Attach(daName, params.Namespace, container.Image, params.CurrentMicroservicePod.ObjectMeta.Name, container.Name, "", "dlv") - time.Sleep(5 * time.Second) - Expect(err).NotTo(HaveOccurred()) - updatedattachment, err := squashcli.WaitCmd(testNamespace, dbgattachment.Metadata.Name, 1.0) - - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment.DebugServerAddress).ToNot(BeEmpty()) - - // Ok; now delete the attachment - dbgattachment, err = params.UserController.RequestDelete(dbgattachment.Metadata.Namespace, dbgattachment.Metadata.Name) - Expect(err).NotTo(HaveOccurred()) - - time.Sleep(5 * time.Second) - - // try again! - dbgattachment, err = params.UserController.Attach(daName, params.Namespace, container.Image, params.CurrentMicroservicePod.ObjectMeta.Name, container.Name, "", "dlv") - Expect(err).NotTo(HaveOccurred()) - - time.Sleep(5 * time.Second) - updatedattachment, err = squashcli.WaitCmd(testNamespace, dbgattachment.Metadata.Name, 1.0) - - Expect(err).NotTo(HaveOccurred()) - Expect(updatedattachment.DebugServerAddress).ToNot(BeEmpty()) - }) - }) +func waitForPod(cs *kubernetes.Clientset, testNamespace, deploymentName string) (string, error) { + // this can be slow, pulls image for the first time - should store demo images in cache if possible + timeLimit := 100 + timeStepSleepDuration := time.Second + for i := 0; i < timeLimit; i++ { + pods, err := cs.CoreV1().Pods(testNamespace).List(metav1.ListOptions{}) + if err != nil { + return "", err + } + if podName, found := findPod(pods, deploymentName); found { + return podName, nil + } + time.Sleep(timeStepSleepDuration) + } + return "", fmt.Errorf("Pod for deployment %v not found", deploymentName) +} -}) +func findPod(pods *v1.PodList, deploymentName string) (string, bool) { + for _, pod := range pods.Items { + if pod.Spec.Containers[0].Name == deploymentName && podReady(pod) { + return pod.Name, true + } + } + return "", false +} + +func podReady(pod v1.Pod) bool { + switch pod.Status.Phase { + case v1.PodRunning: + return true + case v1.PodSucceeded: + return true + default: + return false + } +} + +/* sample of expected output (case of 4 debug attachments across two namespaces) +Existing debug attachments: +dd, ea8f2f3omi +dd, hm52rfvkbt +default, cq13qxkxa2 +default, lmgv6h2g7o +*/ +func validateUtilsListDebugAttachments(output string, expectedDaCount int) { + lines := strings.Split(output, "\n") + // should return one line per da + a header line + expectedLength := 1 + expectedDaCount + expectedHeader := "Existing debug attachments:" + if expectedDaCount == 0 { + expectedHeader = "Found no debug attachments" + } + Expect(lines[0]).To(Equal(expectedHeader)) + Expect(len(lines)).To(Equal(expectedLength)) + for i := 1; i < expectedLength; i++ { + validateUtilsListDebugAttachmentsLine(lines[i]) + } +} + +func validateUtilsListDebugAttachmentsLine(line string) { + cols := strings.Split(line, ", ") + Expect(len(cols)).To(Equal(2)) +} + +/* sample of expected output: +{"PortForwardCmd":"kubectl port-forward plankhxpq4 :33303 -n squash-debugger"} +*/ +func validateMachineDebugOutput(output string) { + re := regexp.MustCompile(`{"PortForwardCmd":"kubectl port-forward.*}`) + Expect(re.MatchString(output)).To(BeTrue()) +} + +// using the kubectl port-forward command spec provided by the Plank pod, +// port forward, curl, and inspect the curl error message +// expect to see the error associated with a rejection, rather than a failure to connect +func ensureDLVServerIsLive(dbgJson string) { + ed := config.EditorData{} + check(json.Unmarshal([]byte(dbgJson), &ed)) + cmdParts := strings.Split(ed.PortForwardCmd, " ") + // 0: kubectl + // 1: port-forward + // 2: plankhxpq4 + // 3: :33303 + // 4: -n + // 5: squash-debugger + ports := strings.Split(cmdParts[3], ":") + remotePort := ports[1] + var localPort int + check(utils.FindAnyFreePort(&localPort)) + cmdParts[3] = fmt.Sprintf("%v:%v", localPort, remotePort) + // the portforward spec includes "kubectl ..." but exec.Command requires the binary be called explicitly + pfCmd := exec.Command("kubectl", cmdParts[1:]...) + go func() { + out, _ := pfCmd.CombinedOutput() + fmt.Println(string(out)) + }() + time.Sleep(2 * time.Second) + dlvAddr := fmt.Sprintf("localhost:%v", localPort) + curlOut, _ := testutils.Curl(dlvAddr) + // valid response signature: curl: (52) Empty reply from server + // invalid response signature: curl: (7) Failed to connect to localhost port 58239: Connection refused + re := regexp.MustCompile(`curl: \(52\) Empty reply from server`) + match := re.Match(curlOut) + Expect(match).To(BeTrue()) + // dlvClient := rpc1.NewClient(dlvAddr) + // err, dlvState := dlvClient.GetState() + // check(err) + // fmt.Print +} + +func ensurePlankPermissionsWereCreated(cs *kubernetes.Clientset, plankNs string) error { + if _, err := cs.CoreV1().ServiceAccounts(plankNs).Get(sqOpts.PlankServiceAccountName, metav1.GetOptions{}); err != nil { + return err + } + if _, err := cs.Rbac().ClusterRoles().Get(sqOpts.PlankClusterRoleName, metav1.GetOptions{}); err != nil { + return err + } + if _, err := cs.Rbac().ClusterRoleBindings().Get(sqOpts.PlankClusterRoleBindingName, metav1.GetOptions{}); err != nil { + return err + } + return nil +} diff --git a/test/testutils/utils.go b/test/testutils/utils.go new file mode 100644 index 0000000..57f74ac --- /dev/null +++ b/test/testutils/utils.go @@ -0,0 +1,75 @@ +package testutils + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/solo-io/squash/pkg/squashctl" +) + +const ( + plankTestVersion = "0.4.7" + plankTestRepo = "quay.io/solo-io" +) + +func DeclareTestConditions() { + fmt.Printf(`Squash tests are running under the following conditions: +plank repo: %v +plank tag: %v + +If Plank has changed, you should update these values. +`, plankTestRepo, plankTestVersion) +} + +func Squashctl(args string) error { + app, err := squashctl.App("test") + if err != nil { + return err + } + app.SetArgs(strings.Split(args, " ")) + return app.Execute() +} +func SquashctlOut(args string) (string, error) { + stdOut := os.Stdout + r, w, err := os.Pipe() + if err != nil { + return "", err + } + os.Stdout = w + + app, err := squashctl.App("test") + if err != nil { + return "", err + } + app.SetArgs(strings.Split(args, " ")) + err = app.Execute() + + outC := make(chan string) + + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + // back to normal state + w.Close() + os.Stdout = stdOut // restoring the real stdout + out := <-outC + + return strings.TrimSuffix(out, "\n"), nil +} + +func Curl(args string) ([]byte, error) { + curl := exec.Command("curl", strings.Split(args, " ")...) + return curl.CombinedOutput() +} + +func MachineDebugArgs(debugger, ns, podName string) string { + return fmt.Sprintf(`--debugger %v --machine --namespace %v --pod %v --container-version %v --container-repo %v`, debugger, ns, podName, plankTestVersion, plankTestRepo) +}