Skip to content

Commit

Permalink
Merge pull request #101 from cloudfoundry/canary_deploy_baras
Browse files Browse the repository at this point in the history
Add Canary Deployment tests
  • Loading branch information
Samze authored Aug 15, 2024
2 parents 1ce692a + b61b8c9 commit c99a55c
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 18 deletions.
195 changes: 191 additions & 4 deletions baras/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package baras
import (
"encoding/json"
"fmt"
"strings"
"time"

. "github.com/cloudfoundry/capi-bara-tests/bara_suite_helpers"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/cloudfoundry/capi-bara-tests/helpers/random_name"
. "github.com/cloudfoundry/capi-bara-tests/helpers/v3_helpers"
"github.com/cloudfoundry/cf-test-helpers/v2/cf"
"github.com/cloudfoundry/cf-test-helpers/v2/helpers"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
Expand All @@ -27,6 +29,7 @@ var _ = Describe("deployments", func() {
spaceName string
dropletGuid string
newDropletGuid string
instances = 4
)

BeforeEach(func() {
Expand All @@ -52,7 +55,6 @@ var _ = Describe("deployments", func() {
AssignDropletToApp(appGUID, dropletGuid)

CreateAndMapRoute(appGUID, spaceGUID, domainGUID, appName)
instances := 4

ScaleApp(appGUID, instances)
StartApp(appGUID)
Expand All @@ -74,7 +76,7 @@ var _ = Describe("deployments", func() {
// TODO: delete me once we delete v2
Describe("Creating new processes on the same app", func() {
It("ignores older processes on the same app", func() {
deploymentGuid := CreateDeployment(appGUID)
deploymentGuid := CreateDeployment(appGUID, "rolling")
Expect(deploymentGuid).ToNot(BeEmpty())
v3_processes := GetProcesses(appGUID, appName)
numWebProcesses := 0
Expand Down Expand Up @@ -137,7 +139,7 @@ var _ = Describe("deployments", func() {
AssignDropletToApp(appGUID, newDropletGuid)

By("Create a new Deployment")
deploymentGuid := CreateDeploymentForDroplet(appGUID, newDropletGuid)
deploymentGuid := CreateDeploymentForDroplet(appGUID, newDropletGuid, "rolling")
Expect(deploymentGuid).ToNot(BeEmpty())

time.Sleep(60 * time.Second)
Expand Down Expand Up @@ -170,9 +172,194 @@ var _ = Describe("deployments", func() {
})

It("completes the deployment", func() {
deploymentGUID := CreateDeployment(appGUID)
deploymentGUID := CreateDeployment(appGUID, "rolling")
Expect(deploymentGUID).ToNot(BeEmpty())
WaitUntilDeploymentReachesStatus(deploymentGUID, "FINALIZED", "DEPLOYED")
})
})

Describe("Canary deployments", func() {
var secondDropletGuid string
BeforeEach(func() {
By("Creating a second droplet for the app")
secondDropletGuid = uploadDroplet(appGUID, assets.NewAssets().StaticfileZip, Config.GetStaticFileBuildpackName())
})

It("deploys an app, transitions to pause, is continued and then deploys successfully", func() {
By("Pushing a canary deployment")
Eventually(func() string {
return helpers.CurlAppRoot(Config, appName)
}).Should(ContainSubstring("Hi, I'm Dora"))

_, originalWorkerStartEvent := GetLastAppUseEventForProcess("worker", "STARTED", "")

deploymentGuid := CreateDeploymentForDroplet(appGUID, secondDropletGuid, "canary")
Expect(deploymentGuid).ToNot(BeEmpty())

Eventually(func() int { return len(GetProcessGuidsForType(appGUID, "web")) }, Config.CfPushTimeoutDuration()).
Should(BeNumerically(">", 1))

By("Waiting for the a canary deployment to be paused")
WaitUntilDeploymentReachesStatus(deploymentGuid, "ACTIVE", "PAUSED")

Eventually(func() string {
return helpers.CurlAppRoot(Config, appName)
}).Should(ContainSubstring("Hello from a staticfile"))

Eventually(func() string {
return helpers.CurlAppRoot(Config, appName)
}).Should(ContainSubstring("Hi, I'm Dora"))

processGuids := GetProcessGuidsForType(appGUID, "web")
canaryProcessGuid := processGuids[len(processGuids)-1]

Eventually(func() int {
return GetRunningInstancesStats(canaryProcessGuid)
}).Should(Equal(1))

By("Continuing the deployment")
ContinueDeployment(deploymentGuid)

By("Verfiying the canary process is rolled out successfully")
WaitUntilDeploymentReachesStatus(deploymentGuid, "FINALIZED", "DEPLOYED")

Eventually(func() int {
return GetRunningInstancesStats(canaryProcessGuid)
}).Should(Equal(instances))

counter := 0
Eventually(func() int {
if strings.Contains(helpers.CurlAppRoot(Config, appName), "Hello from a staticfile") {
counter++
} else {
counter = 0
}
return counter
}).Should(Equal(10))

Eventually(func() bool {
restartEventExists, _ := GetLastAppUseEventForProcess("worker", "STARTED", originalWorkerStartEvent.Guid)
return restartEventExists
}).Should(BeTrue(), "Did not find a start event indicating the 'worker' process restarted")
})

It("deploys an app, transitions to pause and can be cancelled", func() {
By("Pushing a canary deployment")
Eventually(func() string {
return helpers.CurlAppRoot(Config, appName)
}).Should(ContainSubstring("Hi, I'm Dora"))

deploymentGuid := CreateDeploymentForDroplet(appGUID, secondDropletGuid, "canary")
Expect(deploymentGuid).ToNot(BeEmpty())

Eventually(func() int { return len(GetProcessGuidsForType(appGUID, "web")) }, Config.CfPushTimeoutDuration()).
Should(BeNumerically(">", 1))

By("Waiting for the a canary deployment to be paused")
WaitUntilDeploymentReachesStatus(deploymentGuid, "ACTIVE", "PAUSED")

By("Checking that both the canary and original apps exist simultaneously")
Eventually(func() string {
return helpers.CurlAppRoot(Config, appName)
}).Should(ContainSubstring("Hello from a staticfile"))

Eventually(func() string {
return helpers.CurlAppRoot(Config, appName)
}).Should(ContainSubstring("Hi, I'm Dora"))

processGuids := GetProcessGuidsForType(appGUID, "web")
originalProcessGuid := processGuids[len(processGuids)-2]
canaryProcessGuid := processGuids[len(processGuids)-1]

Eventually(func() int {
return GetRunningInstancesStats(canaryProcessGuid)
}).Should(Equal(1))

By("Cancelling the deployment")
CancelDeployment(deploymentGuid)

By("Verifying the cancel succeeded and we rolled back to old process")
WaitUntilDeploymentReachesStatus(deploymentGuid, "FINALIZED", "CANCELED")

Eventually(func() int {
return GetRunningInstancesStats(originalProcessGuid)
}).Should(Equal(instances))

Consistently(func() string {
return helpers.CurlAppRoot(Config, appName)
}, Config.CcClockCycleDuration(), "2s").ShouldNot(ContainSubstring("Hello from a staticfile"))

counter := 0
Eventually(func() int {
if strings.Contains(helpers.CurlAppRoot(Config, appName), "Hi, I'm Dora") {
counter++
} else {
counter = 0
}
return counter
}).Should(Equal(10))
})

It("deploys an app, transitions to pause and can be superseded", func() {
By("Pushing a canary deployment")
Eventually(func() string {
return helpers.CurlAppRoot(Config, appName)
}).Should(ContainSubstring("Hi, I'm Dora"))

deploymentGuid := CreateDeploymentForDroplet(appGUID, secondDropletGuid, "canary")
Expect(deploymentGuid).ToNot(BeEmpty())

Eventually(func() int { return len(GetProcessGuidsForType(appGUID, "web")) }, Config.CfPushTimeoutDuration()).
Should(BeNumerically(">", 1))

By("Waiting for the a canary deployment to be paused")
WaitUntilDeploymentReachesStatus(deploymentGuid, "ACTIVE", "PAUSED")

processGuids := GetProcessGuidsForType(appGUID, "web")
canaryProcessGuid := processGuids[len(processGuids)-1]

Eventually(func() int {
return GetRunningInstancesStats(canaryProcessGuid)
}).Should(Equal(1))

By("Superseding the deployment with a new rolling deployment")
newDeploymentGuid := CreateDeploymentForDroplet(appGUID, dropletGuid, "rolling")

By("Verifying the new deployment is used")
WaitUntilDeploymentReachesStatus(deploymentGuid, "FINALIZED", "SUPERSEDED")
WaitUntilDeploymentReachesStatus(newDeploymentGuid, "FINALIZED", "DEPLOYED")

processGuids = GetProcessGuidsForType(appGUID, "web")
newProcessGuid := processGuids[len(processGuids)-1]
Eventually(func() int {
return GetRunningInstancesStats(newProcessGuid)
}).Should(Equal(instances))

Consistently(func() string {
return helpers.CurlAppRoot(Config, appName)
}, Config.CcClockCycleDuration(), "2s").ShouldNot(ContainSubstring("Hello from a staticfile"))

counter := 0
Eventually(func() int {
if strings.Contains(helpers.CurlAppRoot(Config, appName), "Hi, I'm Dora") {
counter++
} else {
counter = 0
}
return counter
}).Should(Equal(10))
})
})
})

func uploadDroplet(appGuid, zipFile, buildpackName string) string {
packageGuid := CreatePackage(appGuid)
url := fmt.Sprintf("%s%s/v3/packages/%s/upload", Config.Protocol(), Config.GetApiEndpoint(), packageGuid)

UploadPackage(url, zipFile)
WaitForPackageToBeReady(packageGuid)

buildGuid := StagePackage(packageGuid, "buildpack", buildpackName)
WaitForBuildToStage(buildGuid)
return GetDropletFromBuild(buildGuid)
}
2 changes: 1 addition & 1 deletion baras/revisions.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ func waitForAllInstancesToStart(appGUID string, instances int) {
}

func zdtRestartAndWait(appGUID string) {
deploymentGUID := CreateDeployment(appGUID)
deploymentGUID := CreateDeployment(appGUID, "rolling")
Expect(deploymentGUID).ToNot(BeEmpty())
WaitUntilDeploymentReachesStatus(deploymentGUID, "FINALIZED", "DEPLOYED")
}
15 changes: 11 additions & 4 deletions helpers/v3_helpers/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (
. "github.com/onsi/gomega/gexec"
)

func CreateDeployment(appGUID string) string {
func CreateDeployment(appGUID, strategy string) string {
deploymentPath := fmt.Sprintf("/v3/deployments")
deploymentRequestBody := fmt.Sprintf(`{"relationships": {"app": {"data": {"guid": "%s"}}}}`, appGUID)
deploymentRequestBody := fmt.Sprintf(`{"strategy": "%s", "relationships": {"app": {"data": {"guid": "%s"}}}}`, strategy, appGUID)
session := cf.Cf("curl", "-f", deploymentPath, "-X", "POST", "-d", deploymentRequestBody).Wait()
Expect(session).To(Exit(0))
var deployment struct {
Expand All @@ -29,9 +29,9 @@ func CreateDeployment(appGUID string) string {
return deployment.GUID
}

func CreateDeploymentForDroplet(appGUID, dropletGUID string) string {
func CreateDeploymentForDroplet(appGUID, dropletGUID, strategy string) string {
deploymentPath := fmt.Sprintf("/v3/deployments")
deploymentRequestBody := fmt.Sprintf(`{"droplet": {"guid": "%s"}, "relationships": {"app": {"data": {"guid": "%s"}}}}`, dropletGUID, appGUID)
deploymentRequestBody := fmt.Sprintf(`{"strategy": "%s", "droplet": {"guid": "%s"}, "relationships": {"app": {"data": {"guid": "%s"}}}}`, strategy, dropletGUID, appGUID)
session := cf.Cf("curl", "-f", deploymentPath, "-X", "POST", "-d", deploymentRequestBody).Wait()
Expect(session).To(Exit(0))
var deployment struct {
Expand Down Expand Up @@ -66,6 +66,13 @@ func CancelDeployment(deploymentGUID string) {
Expect(session).To(Exit(0))
}

func ContinueDeployment(deploymentGUID string) {
deploymentPath := fmt.Sprintf("/v3/deployments/%s/actions/continue", deploymentGUID)
session := cf.Cf("curl", "-f", deploymentPath, "-X", "POST", "-i").Wait()
Expect(session.Out.Contents()).To(ContainSubstring("200 OK"))
Expect(session).To(Exit(0))
}

func WaitUntilDeploymentReachesStatus(deploymentGUID, statusValue, statusReason string) {
deploymentPath := fmt.Sprintf("/v3/deployments/%s", deploymentGUID)

Expand Down
20 changes: 11 additions & 9 deletions helpers/v3_helpers/processes.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,15 @@ func ScaleProcess(appGUID, processType, memoryInMb string) {
}

type ProcessAppUsageEvent struct {
Metadata struct {
Guid string `json:"guid"`
} `json:"metadata"`
Entity struct {
ProcessType string `json:"process_type"`
State string `json:"state"`
} `json:"entity"`
Guid string `json:"guid"`
Process struct {
ProcessType string `json:"type"`
Guid string `json:"guid"`
} `json:"process"`
State struct {
Current string `json:"current"`
Previous string `json:"previous"`
} `json:"state"`
}

type ProcessAppUsageEvents struct {
Expand All @@ -153,12 +155,12 @@ func GetLastAppUseEventForProcess(processType string, state string, afterGUID st
if afterGUID != "" {
afterGUIDParam = fmt.Sprintf("&after_guid=%s", afterGUID)
}
usageEventsUrl := fmt.Sprintf("/v2/app_usage_events?order-direction=desc&page=1&results-per-page=150%s", afterGUIDParam)
usageEventsUrl := fmt.Sprintf("/v3/app_usage_events?order_by=-created_at&per_page=5000%s", afterGUIDParam)
workflowhelpers.ApiRequest("GET", usageEventsUrl, &response, Config.DefaultTimeoutDuration())
})

for _, event := range response.Resources {
if event.Entity.ProcessType == processType && event.Entity.State == state {
if event.Process.ProcessType == processType && event.State.Current == state {
return true, event
}
}
Expand Down

0 comments on commit c99a55c

Please sign in to comment.