Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setup for Dynamic ASG performance testing #132

Merged
merged 1 commit into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
324 changes: 324 additions & 0 deletions src/code.cloudfoundry.org/cf-pusher/cmd/multispace-pusher/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"code.cloudfoundry.org/cf-pusher/cf_cli_adapter"
"code.cloudfoundry.org/cf-pusher/cf_command"
"code.cloudfoundry.org/cf-pusher/config"
"code.cloudfoundry.org/cf-pusher/manifest_generator"
"code.cloudfoundry.org/cf-pusher/models"
"code.cloudfoundry.org/lib/testsupport"
)

type Config struct {
config.Config
GlobalAGGs int `json:"global_asgs"`
TotalSpaces int `json:"total_spaces"`
SpacesWithOneASG int `json:"spaces_with_one_asg"`
HowManyASGsIsMany int `json:"how_many_asgs_is_many"`
AppsPerSpace int `json:"apps_per_space"`
}

type ConcurrentSpaceSetup struct {
Adapter *cf_cli_adapter.Adapter
ApiConnector cf_command.ApiConnector
OrgSpaceCreator cf_command.OrgSpaceCreator
AppPusher cf_command.AppPusher
}

func main() {
config := parseConfig()

globalAdapter := generateAdapterWithHome(config.Prefix)
globalApiConnector := &cf_command.ApiConnector{
Api: config.Api,
AdminUser: config.AdminUser,
AdminPassword: config.AdminPassword,
SkipSSLValidation: config.SkipSSLValidation,
Adapter: globalAdapter,
}
if err := globalApiConnector.Connect(); err != nil {
log.Fatalf("connecting to api: %s", err)
}

// Create global asgs
createGlobalASGs(config)
// Create a bunch of bindable ASGs
manyASGs := createManyASGs(config.HowManyASGsIsMany, config.ASGSize, config.Prefix, globalAdapter)
// Compile the proxy app
compileBinary()

// Iterate over each space and create/bind asgs and push apps as needed
sem := make(chan bool, config.Concurrency)
for i := 0; i < config.TotalSpaces; i++ {
setup := generateConcurrentSpaceSetup(i, config)
sem <- true
go func(s *ConcurrentSpaceSetup, c Config, index int) {
defer func() { <-sem }()

// Connect to the api with this adapter
if err := s.ApiConnector.Connect(); err != nil {
log.Fatalf("connecting to api: %s", err)
}

// Create and target the space
if err := s.OrgSpaceCreator.Create(); err != nil {
log.Fatalf("creating org and space: %s", err)
}

if index < c.SpacesWithOneASG {
// Create and bind a single ASG to this space
createAndBindOneASGToThisSpace(fmt.Sprintf("%s-asg", s.OrgSpaceCreator.Space), c.ASGSize, s.OrgSpaceCreator, s.Adapter)
} else {
// Bind many asgs to this space
bindManyASGsToThisSpace(manyASGs, s.OrgSpaceCreator.Org, s.OrgSpaceCreator.Space, s.Adapter)
}

// Push apps for this space
if err := s.AppPusher.Push(); err != nil {
log.Printf("Got an error while pushing proxy apps: %s", err)
}

}(setup, config, i)
}

for i := 0; i < cap(sem); i++ {
sem <- true
}
}

func generateConcurrentSpaceSetup(spaceNumber int, config Config) *ConcurrentSpaceSetup {
appsDir := os.Getenv("APPS_DIR")
if appsDir == "" {
log.Fatal("APPS_DIR not set")
}
orgName := fmt.Sprintf("%s-org", config.Prefix)
adapter := generateAdapterWithHome(config.Prefix)
var apps []cf_command.Application
for i := 0; i < config.AppsPerSpace; i++ {
apps = append(apps, cf_command.Application{Name: fmt.Sprintf("%s-%s-%d-%d", config.Prefix, "app", spaceNumber, i)})
}

return &ConcurrentSpaceSetup{
Adapter: adapter,
ApiConnector: cf_command.ApiConnector{
Api: config.Api,
AdminUser: config.AdminUser,
AdminPassword: config.AdminPassword,
SkipSSLValidation: config.SkipSSLValidation,
Adapter: adapter,
},
OrgSpaceCreator: cf_command.OrgSpaceCreator{
Org: orgName,
Space: fmt.Sprintf("%s-%s-%d", config.Prefix, "space", spaceNumber),
Quota: cf_command.Quota{
Name: config.Prefix + "-quota",
Memory: "1000G",
InstanceMemory: -1,
Routes: 20000,
ServiceInstances: 100,
AppInstances: -1,
RoutePorts: -1,
},
Adapter: adapter,
},
AppPusher: cf_command.AppPusher{
Applications: apps,
Adapter: adapter,
Concurrency: config.Concurrency,
ManifestPath: generateAppManifest(appsDir),
Directory: filepath.Join(appsDir, "proxy"),
SkipIfPresent: true,
DesiredRunningInstances: 1,
},
}
}

func generateAdapterWithHome(prefix string) *cf_cli_adapter.Adapter {
dir, err := os.MkdirTemp("", prefix)
if err != nil {
log.Fatalf("Failed to create a cf home dir")
}

return cf_cli_adapter.NewAdapterWithHome(dir)
}

func compileBinary() {
appsDir := os.Getenv("APPS_DIR")
if appsDir == "" {
log.Fatal("APPS_DIR not set")
}

buildCmd := exec.Command("go", "build", "-o", "proxy")
buildCmd.Dir = filepath.Join(appsDir, "proxy")
buildCmd.Env = append(os.Environ(),
"GOOS=linux",
"GOARCH=amd64",
)

output, err := buildCmd.CombinedOutput()
if err != nil {
log.Fatalf("compiling app binary:\nOutput: %s\nError: %s\n", output, err)
}
}

func generateAppManifest(appsDir string) string {
manifestGenerator := &manifest_generator.ManifestGenerator{}
appManifest := models.Manifest{
Applications: []models.Application{{
Name: "proxy",
Memory: "32M",
DiskQuota: "32M",
BuildPack: "binary_buildpack",
Instances: 1,
Command: "./proxy",
}},
}
manifestPath, err := manifestGenerator.Generate(appManifest)
if err != nil {
log.Fatalf("generate manifest: %s", err)
}

return manifestPath
}

func createGlobalASGs(config Config) {
asgContent := testsupport.BuildASG(config.ASGSize)
asgFile, err := testsupport.CreateTempFile(asgContent)
if err != nil {
log.Fatalf("creating asg file: %s", err)
}

sem := make(chan bool, config.Concurrency)
for index := 0; index < config.GlobalAGGs; index++ {
sem <- true
go func(p string, i int) {
defer func() { <-sem }()
adapter := generateAdapterWithHome(p)
asgName := fmt.Sprintf("%s-global-%d-asg", p, i)

// check ASG and install if not OK
apiConnector := &cf_command.ApiConnector{
Api: config.Api,
AdminUser: config.AdminUser,
AdminPassword: config.AdminPassword,
SkipSSLValidation: config.SkipSSLValidation,
Adapter: adapter,
}
if err := apiConnector.Connect(); err != nil {
log.Fatalf("connecting to api: %s", err)
}
asgChecker := cf_command.ASGChecker{Adapter: adapter}
asgErr := asgChecker.CheckASG(asgName, asgContent)
if asgErr != nil {
// install ASG
asgInstaller := cf_command.ASGInstaller{Adapter: adapter}
if err = asgInstaller.InstallGlobalASG(asgName, asgFile); err != nil {
log.Fatalf("install asg: %s", err)
}
}
}(config.Prefix, index)
}

for i := 0; i < cap(sem); i++ {
sem <- true
}
}

func bindManyASGsToThisSpace(asgNames []string, orgName, spaceName string, adapter *cf_cli_adapter.Adapter) {
for _, asg := range asgNames {
if err := adapter.BindSecurityGroup(asg, orgName, spaceName); err != nil {
log.Fatalf("binding asg %s to org %s, space %s: %s", asg, orgName, spaceName, err)
}
}
}

func createManyASGs(howMany, asgSize int, prefix string, adapter *cf_cli_adapter.Adapter) []string {
var asgNames []string
for i := 0; i < howMany; i++ {
asgName := fmt.Sprintf("%s-many-%d-asg", prefix, i)
asgNames = append(asgNames, asgName)
asgContent := testsupport.BuildASG(asgSize)
asgFile, err := testsupport.CreateTempFile(asgContent)
if err != nil {
log.Fatalf("creating asg file: %s", err)
}

// check ASG and create if not OK
asgChecker := cf_command.ASGChecker{Adapter: adapter}
asgErr := asgChecker.CheckASG(asgName, asgContent)
if asgErr != nil {
// install ASG
if err := adapter.DeleteSecurityGroup(asgName); err != nil {
log.Fatalf("deleting security group: %s", err)
}
if err := adapter.CreateSecurityGroup(asgName, asgFile); err != nil {
log.Fatalf("creating security group: %s", err)
}
}
}

return asgNames
}

func createAndBindOneASGToThisSpace(asgName string, asgSize int, osc cf_command.OrgSpaceCreator, adapter *cf_cli_adapter.Adapter) {
asgContent := testsupport.BuildASG(asgSize)
asgFile, err := testsupport.CreateTempFile(asgContent)
if err != nil {
log.Fatalf("creating asg file: %s", err)
}

// check ASG and install if not OK
asgChecker := cf_command.ASGChecker{Adapter: adapter}
asgErr := asgChecker.CheckASG(asgName, asgContent)
if asgErr != nil {
// install ASG
asgInstaller := cf_command.ASGInstaller{Adapter: adapter}
if err = asgInstaller.InstallASG(asgName, asgFile, osc.Org, osc.Space); err != nil {
log.Fatalf("install asg: %s", err)
}
}
}

func parseConfig() Config {
configPath := flag.String("config", "", "path to the config file")
flag.Parse()

if *configPath == "" {
log.Fatal("must include config file with --config")
}

configBytes, err := ioutil.ReadFile(*configPath)
if err != nil {
log.Fatalf("error reading config: %s", err)
}

var config Config
if err := json.Unmarshal(configBytes, &config); err != nil {
log.Fatalf("error unmarshaling config: %s", err)
}

if config.Prefix == "" {
config.Prefix = "scale-asg"
}
config.Prefix = strings.TrimSuffix(config.Prefix, "-")

if config.SpacesWithOneASG > config.TotalSpaces {
log.Fatalf("total_spaces must be greater than or equal to spaces_with_one_asg")
}

if config.Concurrency < 1 {
config.Concurrency = 1
}

return config
}
1 change: 1 addition & 0 deletions src/code.cloudfoundry.org/cf-pusher/models/tick.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Manifest struct {

type Application struct {
Name string `yaml:"name,omitempty"`
Command string `yaml:"command,omitempty"`
Memory string `yaml:"memory,omitempty"`
DiskQuota string `yaml:"disk_quota,omitempty"`
BuildPack string `yaml:"buildpack,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
To use:

1. Clone the [prometheus-boshrelease](https://github.com/bosh-prometheus/prometheus-boshrelease) to ~/workspace
1. Deploy your CF with the opsfile [add-prometheus-uaa-clients.yml](https://github.com/bosh-prometheus/prometheus-boshrelease/blob/master/manifests/operators/cf/add-prometheus-uaa-clients.yml)
1. Look up the values in credhub for the created clients:
1. `credhub find -n uaa_clients_cf_exporter_secret`
1. `credhub get -n <path from above>`
1. repeat for `uaa_clients_firehose_exporter_secret`
1. Fill empty values in `./change-me-deployment-vars.yml` (Note that bosh client/secret doesn't work for username/password)
1. Run `./deploy.sh` to deploy prometheus and grafana that scrapes from your cf/bosh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
system_domain:
cf_deployment_name:
skip_ssl_verify: false
uaa_clients_cf_exporter_secret:
uaa_clients_firehose_exporter_secret:
metron_deployment_name:
traffic_controller_external_port: 443
metrics_environment:
bosh_username:
bosh_password:
default_stemcell_os:
default_vm_type:
default_network:
default_az:
Loading