From 23a23160e4684f339a4bef5f11838f6447cab076 Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Wed, 17 Oct 2018 12:05:10 -0700 Subject: [PATCH] Add volume services tests Tests that volume service provided by volume service broker can be bound to the app and app can use the volumes provided by that service. [#161267650](https://www.pivotaltracker.com/story/show/161267650) --- README.md | 7 + assets/pora/Dockerfile | 3 + assets/pora/Procfile | 1 + assets/pora/manifest.yml | 6 + assets/pora/server.go | 318 +++++++++++++++++++++++ cats_suite_helpers/cats_suite_helpers.go | 13 +- cats_suite_test.go | 1 + example-cats-config.json | 3 +- helpers/assets/assets.go | 42 +-- helpers/config/config.go | 6 + helpers/config/config_struct.go | 57 ++++ helpers/skip_messages/skip_messages.go | 2 + volume_services/volume_services.go | 72 +++++ 13 files changed, 509 insertions(+), 22 deletions(-) create mode 100644 assets/pora/Dockerfile create mode 100644 assets/pora/Procfile create mode 100644 assets/pora/manifest.yml create mode 100644 assets/pora/server.go create mode 100644 volume_services/volume_services.go diff --git a/README.md b/README.md index b8b2dbfe2..25389a297 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,12 @@ include_capi_no_bridge * `include_service_discovery`: Flag to include test for the service discovery. These tests use `apps.internal` domain, which is the default in `cf-networking-release`. The internal domain is currently not configurable. +* `include_volume_services`: Flag to include the tests for volume services. Diego must be deployed for these tests to pass and volume service broker should be registered in platform. +* `volume_service_name`: The name of the volume service provided by the volume service broker. +* `volume_service_plan_name`: The name of the plan of the service provided by the volume service broker. +* `volume_service_create_config`: The JSON configuration that is used when volume service is created. +* `volume_service_bind_config`: The JSON configuration that is used when volume service is bound to the test application. + #### Buildpack Names Many tests specify a buildpack when pushing an app, so that on diego the app staging process completes in less time. The default names for the buildpacks are as follows; if you have buildpacks with different names, you can override them by setting different names: @@ -343,6 +349,7 @@ Test Group Name| Description `tasks`| Tests Cloud Foundry's [Tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html) feature. `tcp_routing`| Tests TCP Routing Feature of Cloud Foundry. You need to make sure you've set up a TCP domain `tcp.` as described [here](https://docs.cloudfoundry.org/adminguide/enabling-tcp-routing.html). If you are using `bbl` (BOSH Bootloader), TCP domain is set up for you automatically. `v3`| This test group contains tests for the next-generation v3 Cloud Controller API. +`volume_services` | Tests the [Volume Services](https://docs.cloudfoundry.org/devguide/services/using-vol-services.html) feature of Cloud Foundry. ## Contributing diff --git a/assets/pora/Dockerfile b/assets/pora/Dockerfile new file mode 100644 index 000000000..6a3e3f83e --- /dev/null +++ b/assets/pora/Dockerfile @@ -0,0 +1,3 @@ +FROM golang:onbuild +ENV PORT 80 +EXPOSE 80 \ No newline at end of file diff --git a/assets/pora/Procfile b/assets/pora/Procfile new file mode 100644 index 000000000..af8eb67ac --- /dev/null +++ b/assets/pora/Procfile @@ -0,0 +1 @@ +web: pora diff --git a/assets/pora/manifest.yml b/assets/pora/manifest.yml new file mode 100644 index 000000000..f06af95ea --- /dev/null +++ b/assets/pora/manifest.yml @@ -0,0 +1,6 @@ +--- +applications: +- name: pora + buildpack: go_buildpack + env: + GOPACKAGENAME: pora diff --git a/assets/pora/server.go b/assets/pora/server.go new file mode 100644 index 000000000..6e10691e5 --- /dev/null +++ b/assets/pora/server.go @@ -0,0 +1,318 @@ +package main + +import ( + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" +) + +const backgroundLoadDirName = "pora-background-load" + +func main() { + http.HandleFunc("/", hello) + http.HandleFunc("/env", env) + http.HandleFunc("/write", write) + http.HandleFunc("/create", createFile) + http.HandleFunc("/loadtest", dataLoad) + http.HandleFunc("/loadtestcleanup", dataLoadCleanup) + http.HandleFunc("/read/", readFile) + http.HandleFunc("/chmod/", chmodFile) + http.HandleFunc("/delete/", deleteFile) + http.HandleFunc("/mkdir-for-background-load", mkdirForBackgroundLoad) + fmt.Println("listening...") + + ports := os.Getenv("PORT") + portArray := strings.Split(ports, " ") + + errCh := make(chan error) + + for _, port := range portArray { + println(port) + go func(port string) { + errCh <- http.ListenAndServe(":"+port, nil) + }(port) + } + + if runBackgroundLoad := os.Getenv("RUN_BACKGROUND_LOAD_THREAD"); runBackgroundLoad != "" { + fmt.Println("starting background load thread...") + go backgroundLoad() + } + + err := <-errCh + if err != nil { + panic(err) + } +} + +type VCAPApplication struct { + InstanceIndex int `json:"instance_index"` +} + +func hello(res http.ResponseWriter, req *http.Request) { + fmt.Fprintf(res, "instance index: %s", os.Getenv("INSTANCE_INDEX")) +} + +func getPath() string { + r, err := regexp.Compile(`"container_dir":\s*"([^"]+)"`) + if err != nil { + panic(err) + } + + vcapEnv := os.Getenv("VCAP_SERVICES") + match := r.FindStringSubmatch(vcapEnv) + if len(match) < 2 { + fmt.Fprintf(os.Stderr, "VCAP_SERVICES is %s", vcapEnv) + panic("failed to find container_dir in environment json") + } + + return match[1] +} + +func write(res http.ResponseWriter, req *http.Request) { + mountPointPath := getPath() + "/poratest-" + randomString(10) + + d1 := []byte("Hello Persistent World!\n") + err := ioutil.WriteFile(mountPointPath, d1, 0644) + if err != nil { + writeError(res, "Writing \n", err) + return + } + + body, err := ioutil.ReadFile(mountPointPath) + if err != nil { + writeError(res, "Reading \n", err) + return + } + + err = os.Remove(mountPointPath) + if err != nil { + writeError(res, "Deleting \n", err) + return + } + + res.WriteHeader(http.StatusOK) + res.Write(body) + return +} + +func dataLoad(res http.ResponseWriter, req *http.Request) { + // this method will read and write data to a single file for 4 seconds, then clean up. + mountPointPath := getPath() + "/poraload-" + randomString(10) + + d1 := []byte("Hello Persistent World!\n") + err := ioutil.WriteFile(mountPointPath, d1, 0644) + if err != nil { + writeError(res, "Writing \n", err) + return + } + + var totalIO int + for startTime := time.Now(); time.Since(startTime) < 4*time.Second; { + d2 := []byte(randomString(1048576)) + err := ioutil.WriteFile(mountPointPath, d2, 0644) + if err != nil { + writeError(res, "Writing Load\n", err) + return + } + body, err := ioutil.ReadFile(mountPointPath) + if err != nil { + writeError(res, "Reading Load\n", err) + return + } + if string(body) != string(d2) { + writeError(res, "Data Mismatch\n", err) + return + } + totalIO = totalIO + 1 + } + + err = os.Remove(mountPointPath) + if err != nil { + writeError(res, "Deleting\n", err) + return + } + + res.WriteHeader(http.StatusOK) + body := fmt.Sprintf("%d MiB written\n", totalIO) + res.Write([]byte(body)) + return +} + +func backgroundLoad() { + // this method will run forever reading and writing and cleaning up data files + dirPath := filepath.Join(getPath(), backgroundLoadDirName) + + for true { + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + fmt.Println("background load directory doesn't exist... waiting") + time.Sleep(10 * time.Second) + continue + } + + filePath := filepath.Join(dirPath, "poraload-"+os.Getenv("INSTANCE_INDEX")) + + d2 := []byte(randomString(1048576)) + err := ioutil.WriteFile(filePath, d2, 0644) + if err != nil { + fmt.Println(err) + return + } + + body, err := ioutil.ReadFile(filePath) + if err != nil { + fmt.Println(err) + return + } + + if string(body) != string(d2) { + fmt.Println("Data Mismatch!") + return + } + + os.Remove(filePath) + } +} + +func dataLoadCleanup(res http.ResponseWriter, req *http.Request) { + // this method will clean up any files that couldn't be deleted during load testing due to interruptions. + mountPointPath := getPath() + "/poraload-*" + + files, err := filepath.Glob(mountPointPath) + if err != nil { + writeError(res, "Unable to find files \n", err) + return + } + for _, f := range files { + if err := os.Remove(f); err != nil { + writeError(res, "Unable to remove "+f+" \n", err) + return + } + } + + res.WriteHeader(http.StatusOK) + body := fmt.Sprintf("%d Files Removed\n", len(files)) + res.Write([]byte(body)) + return +} + +func createFile(res http.ResponseWriter, _ *http.Request) { + fileName := "pora" + randomString(10) + mountPointPath := filepath.Join(getPath(), fileName) + + d1 := []byte("Hello Persistent World!\n") + err := ioutil.WriteFile(mountPointPath, d1, 0644) + if err != nil { + writeError(res, "Writing \n", err) + return + } + + res.WriteHeader(http.StatusOK) + res.Write([]byte(fileName)) + return +} + +func mkdirForBackgroundLoad(res http.ResponseWriter, _ *http.Request) { + dirPath := filepath.Join(getPath(), backgroundLoadDirName) + + err := os.MkdirAll(dirPath, 0777) + if err != nil { + writeError(res, "Error creating directory", err) + return + } + + res.WriteHeader(http.StatusOK) + res.Write([]byte(dirPath)) +} + +func readFile(res http.ResponseWriter, req *http.Request) { + parts := strings.Split(req.URL.Path, "/") + fileName := parts[len(parts)-1] + mountPointPath := filepath.Join(getPath(), fileName) + + body, err := ioutil.ReadFile(mountPointPath) + if err != nil { + res.WriteHeader(http.StatusNotFound) + res.Write([]byte(err.Error())) + return + } + + res.WriteHeader(http.StatusOK) + res.Write(body) + res.Write([]byte("instance index: " + os.Getenv("INSTANCE_INDEX"))) + return +} + +func chmodFile(res http.ResponseWriter, req *http.Request) { + parts := strings.Split(req.URL.Path, "/") + fileName := parts[len(parts)-2] + mountPointPath := filepath.Join(getPath(), fileName) + mode := parts[len(parts)-1] + parsedMode, err := strconv.ParseUint(mode, 8, 32) + if err != nil { + res.WriteHeader(http.StatusBadRequest) + res.Write([]byte(err.Error())) + } + err = os.Chmod(mountPointPath, os.FileMode(uint(parsedMode))) + if err != nil { + res.WriteHeader(http.StatusForbidden) + res.Write([]byte(err.Error())) + return + } + + res.WriteHeader(http.StatusOK) + res.Write([]byte(fileName + "->" + mode)) + res.Write([]byte("instance index: " + os.Getenv("INSTANCE_INDEX"))) + return +} + +func deleteFile(res http.ResponseWriter, req *http.Request) { + parts := strings.Split(req.URL.Path, "/") + fileName := parts[len(parts)-1] + mountPointPath := filepath.Join(getPath(), fileName) + + err := os.Remove(mountPointPath) + if err != nil { + res.WriteHeader(http.StatusForbidden) + res.Write([]byte(err.Error())) + return + } + + res.WriteHeader(http.StatusOK) + res.Write([]byte("deleted " + fileName)) + return +} + +func env(res http.ResponseWriter, req *http.Request) { + for _, e := range os.Environ() { + fmt.Fprintf(res, "%s\n", e) + } +} + +var isSeeded = false + +func randomString(n int) string { + if !isSeeded { + rand.Seed(time.Now().UnixNano()) + isSeeded = true + } + runes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + + b := make([]rune, n) + for i := range b { + b[i] = runes[rand.Intn(len(runes))] + } + return string(b) +} + +func writeError(res http.ResponseWriter, msg string, err error) { + res.WriteHeader(http.StatusInternalServerError) + res.Write([]byte(msg)) + res.Write([]byte(err.Error())) +} diff --git a/cats_suite_helpers/cats_suite_helpers.go b/cats_suite_helpers/cats_suite_helpers.go index b3698d9d0..39e9f555b 100644 --- a/cats_suite_helpers/cats_suite_helpers.go +++ b/cats_suite_helpers/cats_suite_helpers.go @@ -16,7 +16,7 @@ import ( const ( CF_JAVA_TIMEOUT = 10 * time.Minute - V3_PROCESS_TIMEOUT = 45 * time.Second + V3_PROCESS_TIMEOUT = 45 * time.Second DEFAULT_MEMORY_LIMIT = "256M" ) @@ -328,3 +328,14 @@ func WindowsDescribe(description string, callback func()) bool { Describe(description, callback) }) } + +func VolumeServicesDescribe(description string, callback func()) bool { + return Describe("[volume_services]", func() { + BeforeEach(func() { + if !Config.GetIncludeVolumeServices() { + Skip(skip_messages.SkipVolumeServicesMessage) + } + }) + Describe(description, callback) + }) +} diff --git a/cats_suite_test.go b/cats_suite_test.go index a01d0b066..4ef132551 100644 --- a/cats_suite_test.go +++ b/cats_suite_test.go @@ -30,6 +30,7 @@ import ( _ "github.com/cloudfoundry/cf-acceptance-tests/tasks" _ "github.com/cloudfoundry/cf-acceptance-tests/tcp_routing" _ "github.com/cloudfoundry/cf-acceptance-tests/v3" + _ "github.com/cloudfoundry/cf-acceptance-tests/volume_services" _ "github.com/cloudfoundry/cf-acceptance-tests/windows" "github.com/cloudfoundry-incubator/cf-test-helpers/cf" diff --git a/example-cats-config.json b/example-cats-config.json index 396262c83..ca13dc2ef 100644 --- a/example-cats-config.json +++ b/example-cats-config.json @@ -32,5 +32,6 @@ IN DEVELOPMENT "include_zipkin": true, "include_assisted_credhub": true, "include_credhub": true, - "include_tcp_routing": true + "include_tcp_routing": true, + "include_volume_services": false } diff --git a/helpers/assets/assets.go b/helpers/assets/assets.go index e5c639cb2..060a7e2b7 100644 --- a/helpers/assets/assets.go +++ b/helpers/assets/assets.go @@ -24,6 +24,7 @@ type Assets struct { Node string NodeWithProcfile string Nora string + Pora string Php string Proxy string RubySimple string @@ -63,25 +64,26 @@ func NewAssets() Assets { JavaUnwriteableZip: "assets/java-unwriteable-dir/java-unwriteable-dir.jar", LoggregatorLoadGenerator: "assets/loggregator-load-generator", LoggregatorLoadGeneratorGo: "assets/loggregator-load-generator-go", - Node: "assets/node", - NodeWithProcfile: "assets/node-with-procfile", - Nora: "assets/nora/NoraPublished", - Php: "assets/php", - Proxy: "vendor/code.cloudfoundry.org/cf-networking-release/src/example-apps/proxy", - Python: "assets/python", - RubySimple: "assets/ruby_simple", - SecurityGroupBuildpack: "assets/security_group_buildpack.zip", - ServiceBroker: "assets/service_broker", - Staticfile: "assets/staticfile", - SyslogDrainListener: "assets/syslog-drain-listener", - Binary: "assets/binary", - LoggingRouteService: "assets/logging-route-service", - TCPListener: "assets/tcp-listener", - Wcf: "assets/wcf/Hello.Service.IIS", - WindowsWebapp: "assets/webapp", - WorkerApp: "assets/worker-app", - WindowsWorker: "assets/worker", - MultiPortApp: "assets/multi-port-app", - SpringSleuthZip: "assets/spring-sleuth/spring-sleuth.jar", + Node: "assets/node", + NodeWithProcfile: "assets/node-with-procfile", + Nora: "assets/nora/NoraPublished", + Pora: "assets/pora", + Php: "assets/php", + Proxy: "vendor/code.cloudfoundry.org/cf-networking-release/src/example-apps/proxy", + Python: "assets/python", + RubySimple: "assets/ruby_simple", + SecurityGroupBuildpack: "assets/security_group_buildpack.zip", + ServiceBroker: "assets/service_broker", + Staticfile: "assets/staticfile", + SyslogDrainListener: "assets/syslog-drain-listener", + Binary: "assets/binary", + LoggingRouteService: "assets/logging-route-service", + TCPListener: "assets/tcp-listener", + Wcf: "assets/wcf/Hello.Service.IIS", + WindowsWebapp: "assets/webapp", + WorkerApp: "assets/worker-app", + WindowsWorker: "assets/worker", + MultiPortApp: "assets/multi-port-app", + SpringSleuthZip: "assets/spring-sleuth/spring-sleuth.jar", } } diff --git a/helpers/config/config.go b/helpers/config/config.go index 4d313b060..948befc36 100644 --- a/helpers/config/config.go +++ b/helpers/config/config.go @@ -31,6 +31,7 @@ type CatsConfig interface { GetIncludeServiceInstanceSharing() bool GetIncludeTCPRouting() bool GetIncludeWindows() bool + GetIncludeVolumeServices() bool GetUseLogCache() bool GetShouldKeepUser() bool GetSkipSSLValidation() bool @@ -72,6 +73,11 @@ type CatsConfig interface { GetUseWindowsContextPath() bool GetWindowsStack() string + GetVolumeServiceName() string + GetVolumeServicePlanName() string + GetVolumeServiceCreateConfig() string + GetVolumeServiceBindConfig() string + GetReporterConfig() reporterConfig AsyncServiceOperationTimeoutDuration() time.Duration diff --git a/helpers/config/config_struct.go b/helpers/config/config_struct.go index 3a7be9a57..0e6d95852 100644 --- a/helpers/config/config_struct.go +++ b/helpers/config/config_struct.go @@ -62,6 +62,11 @@ type config struct { RubyBuildpackName *string `json:"ruby_buildpack_name"` StaticFileBuildpackName *string `json:"staticfile_buildpack_name"` + VolumeServiceName *string `json:"volume_service_name"` + VolumeServicePlanName *string `json:"volume_service_plan_name"` + VolumeServiceCreateConfig *string `json:"volume_service_create_config"` + VolumeServiceBindConfig *string `json:"volume_service_bind_config"` + IncludeApps *bool `json:"include_apps"` IncludeBackendCompatiblity *bool `json:"include_backend_compatibility"` IncludeCapiExperimental *bool `json:"include_capi_experimental"` @@ -85,6 +90,7 @@ type config struct { IncludeZipkin *bool `json:"include_zipkin"` IncludeIsolationSegments *bool `json:"include_isolation_segments"` IncludeRoutingIsolationSegments *bool `json:"include_routing_isolation_segments"` + IncludeVolumeServices *bool `json:"include_volume_services"` UseLogCache *bool `json:"use_log_cache"` @@ -177,6 +183,7 @@ func getDefaults() config { defaults.IncludeZipkin = ptrToBool(false) defaults.IncludeServiceInstanceSharing = ptrToBool(false) defaults.IncludeTCPRouting = ptrToBool(false) + defaults.IncludeVolumeServices = ptrToBool(false) defaults.UseLogCache = ptrToBool(true) @@ -185,6 +192,10 @@ func getDefaults() config { defaults.WindowsStack = ptrToString("windows2012R2") defaults.UseWindowsTestTask = ptrToBool(false) + defaults.VolumeServiceName = ptrToString("") + defaults.VolumeServicePlanName = ptrToString("") + defaults.VolumeServiceCreateConfig = ptrToString("") + defaults.VolumeServiceBindConfig = ptrToString("") defaults.ReporterConfig = &reporterConfig{} @@ -285,6 +296,11 @@ func validateConfig(config *config) Errors { errs.Add(err) } + err = validateVolumeServices(config) + if err != nil { + errs.Add(err) + } + if config.UseHttp == nil { errs.Add(fmt.Errorf("* 'use_http' must not be null")) } @@ -617,6 +633,27 @@ func validateCredHubSettings(config *config) error { return nil } +func validateVolumeServices(config *config) error { + if !config.GetIncludeVolumeServices() { + return nil + } + + if config.GetVolumeServiceName() == "" { + return fmt.Errorf("* Invalid configuration: 'volume_service_name' must be provided if 'include_volume_services' is true") + } + if config.GetVolumeServicePlanName() == "" { + return fmt.Errorf("* Invalid configuration: 'volume_service_plan_name' must be provided if 'include_volume_services' is true") + } + if config.GetVolumeServiceCreateConfig() == "" { + return fmt.Errorf("* Invalid configuration: 'volume_service_create_config' must be provided if 'include_volume_services' is true") + } + if config.GetVolumeServiceBindConfig() == "" { + return fmt.Errorf("* Invalid configuration: 'volume_service_bind_config' must be provided if 'include_volume_services' is true") + } + + return nil +} + func validateWindows(config *config) error { if config.IncludeWindows == nil { return fmt.Errorf("* 'include_windows' must not be null") @@ -894,6 +931,10 @@ func (c *config) GetIncludeServiceDiscovery() bool { return *c.IncludeServiceDiscovery } +func (c *config) GetIncludeVolumeServices() bool { + return *c.IncludeVolumeServices +} + func (c *config) GetUseLogCache() bool { return *c.UseLogCache } @@ -962,6 +1003,22 @@ func (c *config) GetWindowsStack() string { return *c.WindowsStack } +func (c *config) GetVolumeServiceName() string { + return *c.VolumeServiceName +} + +func (c *config) GetVolumeServicePlanName() string { + return *c.VolumeServicePlanName +} + +func (c *config) GetVolumeServiceCreateConfig() string { + return *c.VolumeServiceCreateConfig +} + +func (c *config) GetVolumeServiceBindConfig() string { + return *c.VolumeServiceBindConfig +} + func (c *config) GetReporterConfig() reporterConfig { reporterConfigFromConfig := c.ReporterConfig diff --git a/helpers/skip_messages/skip_messages.go b/helpers/skip_messages/skip_messages.go index 6711d4ce3..fe071329b 100644 --- a/helpers/skip_messages/skip_messages.go +++ b/helpers/skip_messages/skip_messages.go @@ -45,3 +45,5 @@ const SkipCapiExperimentalMessage = `Skipping this test because Config.IncludeCa const SkipCapiNoBridgeMessage = `Skipping this test because Config.IncludeCapiNoBridge is set to 'false'.` const SkipSSHOnWindows2012R2Message = `cf ssh does not work on windows2012R2` const SkipWindowsTasksMessage = `Skipping Windows tasks tests (requires diego-release v1.20.0 and above)` +const SkipVolumeServicesMessage = `Skipping this test because config.IncludeVolumeServices is set to 'false'. +NOTE: Ensure that volume services are enabled on your platform and volume service broker is registered before running this test.` diff --git a/volume_services/volume_services.go b/volume_services/volume_services.go new file mode 100644 index 000000000..3aabd0596 --- /dev/null +++ b/volume_services/volume_services.go @@ -0,0 +1,72 @@ +package volume_services + +import ( + "path/filepath" + + . "github.com/cloudfoundry/cf-acceptance-tests/cats_suite_helpers" + "github.com/cloudfoundry/cf-acceptance-tests/helpers/assets" + "github.com/cloudfoundry/cf-acceptance-tests/helpers/random_name" + + "github.com/cloudfoundry-incubator/cf-test-helpers/cf" + "github.com/cloudfoundry-incubator/cf-test-helpers/helpers" + "github.com/cloudfoundry-incubator/cf-test-helpers/workflowhelpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = VolumeServicesDescribe("Volume Services", func() { + var ( + serviceName string + serviceInstanceName string + appName string + poraAsset = assets.NewAssets().Pora + ) + + BeforeEach(func() { + serviceName = Config.GetVolumeServiceName() + serviceInstanceName = random_name.CATSRandomName("SVIN") + appName = random_name.CATSRandomName("APP") + + workflowhelpers.AsUser(TestSetup.AdminUserContext(), TestSetup.ShortTimeout(), func() { + session := cf.Cf("enable-service-access", serviceName, "-o", TestSetup.RegularUserContext().Org).Wait() + Expect(session).To(Exit(0)) + }) + + By("pushing an app") + Expect(cf.Cf("push", + appName, + "-b", Config.GetGoBuildpackName(), + "-m", DEFAULT_MEMORY_LIMIT, + "-p", poraAsset, + "-f", filepath.Join(poraAsset, "manifest.yml"), + "-d", Config.GetAppsDomain(), + "--no-start", + ).Wait(Config.CfPushTimeoutDuration())).To(Exit(0)) + + By("creating a service") + createServiceSession := cf.Cf("create-service", serviceName, Config.GetVolumeServicePlanName(), serviceInstanceName, "-c", Config.GetVolumeServiceCreateConfig()) + Expect(createServiceSession.Wait(TestSetup.ShortTimeout())).To(Exit(0)) + + By("binding the service") + bindSession := cf.Cf("bind-service", appName, serviceInstanceName, "-c", Config.GetVolumeServiceBindConfig()) + Expect(bindSession.Wait(TestSetup.ShortTimeout())).To(Exit(0)) + + By("starting the app") + Expect(cf.Cf("start", appName).Wait(Config.CfPushTimeoutDuration())).To(Exit(0)) + }) + + AfterEach(func() { + Eventually(cf.Cf("delete", appName, "-f")).Should(Exit(0)) + Eventually(cf.Cf("delete-service", serviceInstanceName, "-f")).Should(Exit(0)) + + workflowhelpers.AsUser(TestSetup.AdminUserContext(), TestSetup.ShortTimeout(), func() { + session := cf.Cf("disable-service-access", serviceName, "-o", TestSetup.RegularUserContext().Org).Wait() + Expect(session).To(Exit(0)) + }) + }) + + It("should be able to write to the volume", func() { + Expect(helpers.CurlApp(Config, appName, "/write")).To(ContainSubstring("Hello Persistent World")) + }) +})