From d7f5aba177b83ba1fb05244103ff93e22c8386af Mon Sep 17 00:00:00 2001 From: Sunjay Bhatia Date: Thu, 26 Jan 2017 14:20:06 -0500 Subject: [PATCH 1/2] Add turbulence_agent_windows job Signed-off-by: Charlie Vieth --- jobs/turbulence_agent_windows/monit | 9 ++ jobs/turbulence_agent_windows/spec | 20 +++++ .../templates/ca_cert.erb | 1 + .../templates/config.json.erb | 17 ++++ .../templates/pre-start.ps1.erb | 16 ++++ packages/golang-windows/packaging | 17 ++++ packages/golang-windows/spec | 7 ++ packages/stress-windows/packaging | 23 +++++ packages/stress-windows/spec | 5 ++ packages/turbulence-windows/packaging | 32 +++++++ packages/turbulence-windows/spec | 10 +++ .../turbulence/agentreqs/control_net_task.go | 2 + .../agentreqs/control_net_task_windows.go | 39 ++++++++ .../turbulence/agentreqs/fill_disk_task.go | 26 ------ .../agentreqs/fill_disk_task_unix.go | 30 +++++++ .../agentreqs/fill_disk_task_windows.go | 41 +++++++++ .../turbulence/agentreqs/firewall_task.go | 2 + .../agentreqs/firewall_task_windows.go | 42 +++++++++ .../turbulence/agentreqs/kill_process_task.go | 78 +++++++++++++++- .../turbulence/agentreqs/monit/client.go | 83 ----------------- .../turbulence/agentreqs/monit/client_unix.go | 89 +++++++++++++++++++ .../agentreqs/monit/client_windows.go | 41 +++++++++ .../turbulence/agentreqs/monit/provider.go | 5 ++ .../turbulence/agentreqs/shutdown_task.go | 59 +++++++++--- 24 files changed, 570 insertions(+), 124 deletions(-) create mode 100644 jobs/turbulence_agent_windows/monit create mode 100644 jobs/turbulence_agent_windows/spec create mode 100644 jobs/turbulence_agent_windows/templates/ca_cert.erb create mode 100644 jobs/turbulence_agent_windows/templates/config.json.erb create mode 100644 jobs/turbulence_agent_windows/templates/pre-start.ps1.erb create mode 100644 packages/golang-windows/packaging create mode 100644 packages/golang-windows/spec create mode 100644 packages/stress-windows/packaging create mode 100644 packages/stress-windows/spec create mode 100644 packages/turbulence-windows/packaging create mode 100644 packages/turbulence-windows/spec create mode 100644 src/github.com/cppforlife/turbulence/agentreqs/control_net_task_windows.go create mode 100644 src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_unix.go create mode 100644 src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_windows.go create mode 100644 src/github.com/cppforlife/turbulence/agentreqs/firewall_task_windows.go create mode 100644 src/github.com/cppforlife/turbulence/agentreqs/monit/client_unix.go create mode 100644 src/github.com/cppforlife/turbulence/agentreqs/monit/client_windows.go diff --git a/jobs/turbulence_agent_windows/monit b/jobs/turbulence_agent_windows/monit new file mode 100644 index 0000000..7cc69de --- /dev/null +++ b/jobs/turbulence_agent_windows/monit @@ -0,0 +1,9 @@ +{ + "processes": [ + { + "name": "turbulence-windows", + "executable": "/var/vcap/packages/turbulence-windows/bin/agent.exe", + "args": [ "-configPath", "/var/vcap/jobs/turbulence_agent_windows/config/config.json" ] + } + ] +} diff --git a/jobs/turbulence_agent_windows/spec b/jobs/turbulence_agent_windows/spec new file mode 100644 index 0000000..a685bff --- /dev/null +++ b/jobs/turbulence_agent_windows/spec @@ -0,0 +1,20 @@ +--- +name: turbulence_agent_windows + +templates: + ca_cert.erb: config/ca_cert + config.json.erb: config/config.json + pre-start.ps1.erb: bin/pre-start.ps1 + +packages: +- turbulence-windows +- stress-windows + +consumes: +- name: api + type: turbulence_api + +properties: + debug: + description: "Show debug logs" + default: true diff --git a/jobs/turbulence_agent_windows/templates/ca_cert.erb b/jobs/turbulence_agent_windows/templates/ca_cert.erb new file mode 100644 index 0000000..9ade4a1 --- /dev/null +++ b/jobs/turbulence_agent_windows/templates/ca_cert.erb @@ -0,0 +1 @@ +<%= link("api").p("ca_cert") %> diff --git a/jobs/turbulence_agent_windows/templates/config.json.erb b/jobs/turbulence_agent_windows/templates/config.json.erb new file mode 100644 index 0000000..73fd68d --- /dev/null +++ b/jobs/turbulence_agent_windows/templates/config.json.erb @@ -0,0 +1,17 @@ +<%= + +api = link("api") + +JSON.dump( + "AgentID" => "_agent_id_", + + "API" => { + "Host" => api.instances.first.address, + "Port" => api.p("listen_port"), + "CACert" => api.p("ca_cert"), + "Username" => api.p("username"), + "Password" => api.p("password"), + }, +) + +%> diff --git a/jobs/turbulence_agent_windows/templates/pre-start.ps1.erb b/jobs/turbulence_agent_windows/templates/pre-start.ps1.erb new file mode 100644 index 0000000..9b6d305 --- /dev/null +++ b/jobs/turbulence_agent_windows/templates/pre-start.ps1.erb @@ -0,0 +1,16 @@ +$SETTINGS_FILE = "C:\var\vcap\bosh\settings.json" +$CONFIG_FILE = "C:\var\vcap\jobs\turbulence_agent_windows\config\config.json" + +try { + $vmID = (Get-Content -Raw $SETTINGS_FILE | ConvertFrom-Json).agent_id + + $config = (Get-Content -Raw $CONFIG_FILE | ConvertFrom-Json) + $config.AgentID = $vmID + + $config | ConvertTo-Json | Set-Content $CONFIG_FILE +} catch { + Write-Error $_.Exception.Message + Exit 1 +} + +Exit 0 diff --git a/packages/golang-windows/packaging b/packages/golang-windows/packaging new file mode 100644 index 0000000..56f663e --- /dev/null +++ b/packages/golang-windows/packaging @@ -0,0 +1,17 @@ +$path="golang-windows/go1.7.3.windows-amd64.zip" + +try { + Add-Type -AssemblyName System.IO.Compression.FileSystem + function Unzip { + param([string]$zipfile, [string]$outpath) + + [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) + } + + Unzip "${path}" "${env:BOSH_INSTALL_TARGET}" +} catch { + Write-Error $_.Exception.Message + Exit 1 +} + +Exit 0 diff --git a/packages/golang-windows/spec b/packages/golang-windows/spec new file mode 100644 index 0000000..98e1a5d --- /dev/null +++ b/packages/golang-windows/spec @@ -0,0 +1,7 @@ +--- +name: golang-windows + +dependencies: [] + +files: +- golang-windows/go1.7.3.windows-amd64.zip diff --git a/packages/stress-windows/packaging b/packages/stress-windows/packaging new file mode 100644 index 0000000..217d3f0 --- /dev/null +++ b/packages/stress-windows/packaging @@ -0,0 +1,23 @@ +$STRESS_ZIP = "stress/stress-windows-1.0.4.zip" +try { + Add-Type -AssemblyName System.IO.Compression.FileSystem + function Unzip + { + param([string]$zipfile, [string]$outpath) + + [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) + } + + Unzip "${STRESS_ZIP}" "${env:BOSH_INSTALL_TARGET}" + + # Add to global path + $oldpath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).path + $newpath = "${oldpath};${env:BOSH_INSTALL_TARGET}" + Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newpath +} catch { + Write-Error "Error installing stress" + Write-Error $_.Exception.Message + Exit 1 +} +Exit 0 + diff --git a/packages/stress-windows/spec b/packages/stress-windows/spec new file mode 100644 index 0000000..f16ce9a --- /dev/null +++ b/packages/stress-windows/spec @@ -0,0 +1,5 @@ +--- +name: stress-windows + +files: +- stress/stress-windows-1.0.4.zip diff --git a/packages/turbulence-windows/packaging b/packages/turbulence-windows/packaging new file mode 100644 index 0000000..3c96c5a --- /dev/null +++ b/packages/turbulence-windows/packaging @@ -0,0 +1,32 @@ +try { + $BOSH_INSTALL_TARGET = Resolve-Path "${env:BOSH_INSTALL_TARGET}" + + $env:GOROOT="C:\var\vcap\packages\golang-windows\go" + $env:GOPATH="${PWD}" + $env:PATH="${env:GOROOT}\bin;${env:PATH}" + $env:GO15VENDOREXPERIMENT=1 + + New-Item -Type directory -Path "..\src" -Force + Copy-Item -Recurse -Path "*" -Destination "..\src" -Force + Move-Item -Path "..\src" -Destination ".\src" + + go.exe build -o "${BOSH_INSTALL_TARGET}/bin/api.exe" github.com/cppforlife/turbulence/main + if ($LASTEXITCODE -ne 0) { + Write-Error "Error: compiling: github.com/cppforlife/turbulence/main" + Exit $LASTEXITCODE + } + + go.exe build -o "${BOSH_INSTALL_TARGET}/bin/agent.exe" github.com/cppforlife/turbulence/agent + if ($LASTEXITCODE -ne 0) { + Write-Error "Error: compiling: github.com/cppforlife/turbulence/agent" + Exit $LASTEXITCODE + } + + Copy-Item -Recurse -Path "src/github.com/cppforlife/turbulence/public" -Destination "${BOSH_INSTALL_TARGET}" + Copy-Item -Recurse -Path "src/github.com/cppforlife/turbulence/templates" -Destination "${BOSH_INSTALL_TARGET}" +} catch { + Write-Error $_.Exception.Message + Exit 1 +} + +Exit 0 diff --git a/packages/turbulence-windows/spec b/packages/turbulence-windows/spec new file mode 100644 index 0000000..bd65ca6 --- /dev/null +++ b/packages/turbulence-windows/spec @@ -0,0 +1,10 @@ +--- +name: turbulence-windows + +dependencies: +- golang-windows + +files: +- "**/*.go" +- github.com/cppforlife/turbulence/public/**/* +- github.com/cppforlife/turbulence/templates/**/* diff --git a/src/github.com/cppforlife/turbulence/agentreqs/control_net_task.go b/src/github.com/cppforlife/turbulence/agentreqs/control_net_task.go index 0a909be..e4a46b4 100644 --- a/src/github.com/cppforlife/turbulence/agentreqs/control_net_task.go +++ b/src/github.com/cppforlife/turbulence/agentreqs/control_net_task.go @@ -1,3 +1,5 @@ +// +build !windows + package agentreqs import ( diff --git a/src/github.com/cppforlife/turbulence/agentreqs/control_net_task_windows.go b/src/github.com/cppforlife/turbulence/agentreqs/control_net_task_windows.go new file mode 100644 index 0000000..c679042 --- /dev/null +++ b/src/github.com/cppforlife/turbulence/agentreqs/control_net_task_windows.go @@ -0,0 +1,39 @@ +package agentreqs + +import ( + boshlog "github.com/cloudfoundry/bosh-utils/logger" + boshsys "github.com/cloudfoundry/bosh-utils/system" +) + +// TODO (CEV): Refactor so that we don't duplicate the unix type definition. +// +// See http://www.linuxfoundation.org/collaborate/workgroups/networking/netem +type ControlNetOptions struct { + Type string + Timeout string // Times may be suffixed with ms,s,m,h + + // slow: tc qdisc add dev eth0 root netem delay 50ms 10ms distribution normal + Delay string + DelayVariation string + + // flaky: tc qdisc add dev eth0 root netem loss 20% 75% + Loss string + LossCorrelation string + + // reset: tc qdisc del dev eth0 root +} + +type ControlNetTask struct { + cmdRunner boshsys.CmdRunner + opts ControlNetOptions + logger boshlog.Logger +} + +func NewControlNetTask(cmdRunner boshsys.CmdRunner, opts ControlNetOptions, logger boshlog.Logger) ControlNetTask { + return ControlNetTask{cmdRunner: cmdRunner, opts: opts, logger: logger} +} + +func (t ControlNetTask) Execute() error { + t.logger.Info("ControlNetTask", "Not implemented on Windows") + return nil +} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task.go b/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task.go index 43c63ce..138eec6 100644 --- a/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task.go +++ b/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task.go @@ -1,7 +1,6 @@ package agentreqs import ( - bosherr "github.com/cloudfoundry/bosh-utils/errors" boshlog "github.com/cloudfoundry/bosh-utils/logger" boshsys "github.com/cloudfoundry/bosh-utils/system" ) @@ -23,28 +22,3 @@ type FillDiskTask struct { func NewFillDiskTask(cmdRunner boshsys.CmdRunner, opts FillDiskOptions, _ boshlog.Logger) FillDiskTask { return FillDiskTask{cmdRunner, opts} } - -func (t FillDiskTask) Execute() error { - if t.opts.Persistent { - return t.fill("/var/vcap/store/.filler") - } - - if t.opts.Ephemeral { - return t.fill("/var/vcap/data/.filler") - } - - if t.opts.Temporary { - return t.fill("/tmp/.filler") - } - - return t.fill("/.filler") -} - -func (t FillDiskTask) fill(path string) error { - _, _, _, err := t.cmdRunner.RunCommand("dd", "if=/dev/zero", "of="+path, "bs=1M") - if err != nil { - return bosherr.WrapError(err, "Filling disk") - } - - return nil -} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_unix.go b/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_unix.go new file mode 100644 index 0000000..c36a24a --- /dev/null +++ b/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_unix.go @@ -0,0 +1,30 @@ +// +build !windows + +package agentreqs + +import bosherr "github.com/cloudfoundry/bosh-utils/errors" + +func (t FillDiskTask) Execute() error { + if t.opts.Persistent { + return t.fill("/var/vcap/store/.filler") + } + + if t.opts.Ephemeral { + return t.fill("/var/vcap/data/.filler") + } + + if t.opts.Temporary { + return t.fill("/tmp/.filler") + } + + return t.fill("/.filler") +} + +func (t FillDiskTask) fill(path string) error { + _, _, _, err := t.cmdRunner.RunCommand("dd", "if=/dev/zero", "of="+path, "bs=1M") + if err != nil { + return bosherr.WrapError(err, "Filling disk") + } + + return nil +} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_windows.go b/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_windows.go new file mode 100644 index 0000000..c82eb85 --- /dev/null +++ b/src/github.com/cppforlife/turbulence/agentreqs/fill_disk_task_windows.go @@ -0,0 +1,41 @@ +package agentreqs + +import ( + "os" + "path/filepath" + + bosherr "github.com/cloudfoundry/bosh-utils/errors" +) + +func (t FillDiskTask) Execute() error { + if t.opts.Persistent { + return t.fill("/var/vcap/store/.filler") + } + + if t.opts.Ephemeral { + return t.fill("/var/vcap/data/.filler") + } + + if t.opts.Temporary { + return t.fill(filepath.Join(os.TempDir(), ".filler")) + } + + return t.fill("/.filler") +} + +func (t FillDiskTask) fill(path string) error { + f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + return bosherr.WrapError(err, "Filling disk") + } + defer f.Close() + + zero := make([]byte, 1024*1024) + var werr error + for { + if _, werr = f.Write(zero); werr != nil { + break + } + } + return bosherr.WrapError(werr, "Filling disk") +} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/firewall_task.go b/src/github.com/cppforlife/turbulence/agentreqs/firewall_task.go index bd52cb0..93b55b4 100644 --- a/src/github.com/cppforlife/turbulence/agentreqs/firewall_task.go +++ b/src/github.com/cppforlife/turbulence/agentreqs/firewall_task.go @@ -1,3 +1,5 @@ +// +build !windows + package agentreqs import ( diff --git a/src/github.com/cppforlife/turbulence/agentreqs/firewall_task_windows.go b/src/github.com/cppforlife/turbulence/agentreqs/firewall_task_windows.go new file mode 100644 index 0000000..9a03c61 --- /dev/null +++ b/src/github.com/cppforlife/turbulence/agentreqs/firewall_task_windows.go @@ -0,0 +1,42 @@ +package agentreqs + +import ( + boshlog "github.com/cloudfoundry/bosh-utils/logger" + boshsys "github.com/cloudfoundry/bosh-utils/system" +) + +type FirewallOptions struct { + Type string + Timeout string // Times may be suffixed with ms,s,m,h + + BlockBOSHAgent bool +} + +type FirewallTask struct { + cmdRunner boshsys.CmdRunner + opts FirewallOptions + + allowedOutputDest []FirewallTaskDest + logger boshlog.Logger +} + +type FirewallTaskDest struct { + Host string + Port int + + IsBOSHMbus bool +} + +func NewFirewallTask( + cmdRunner boshsys.CmdRunner, + opts FirewallOptions, + allowedOutputDest []FirewallTaskDest, + logger boshlog.Logger, +) FirewallTask { + return FirewallTask{cmdRunner, opts, allowedOutputDest, logger} +} + +func (t FirewallTask) Execute() error { + t.logger.Info("FirewallTask", "not implemented on Windows") + return nil +} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/kill_process_task.go b/src/github.com/cppforlife/turbulence/agentreqs/kill_process_task.go index 92380ba..a49a147 100644 --- a/src/github.com/cppforlife/turbulence/agentreqs/kill_process_task.go +++ b/src/github.com/cppforlife/turbulence/agentreqs/kill_process_task.go @@ -2,8 +2,11 @@ package agentreqs import ( "math/rand" + "os" "path/filepath" + "runtime" "strconv" + "strings" bosherr "github.com/cloudfoundry/bosh-utils/errors" boshlog "github.com/cloudfoundry/bosh-utils/logger" @@ -57,12 +60,75 @@ func (t KillProcessTask) Execute() error { func (t KillProcessTask) killProcesses(name string) error { t.logger.Debug(t.logTag, "Killing processes matching '%s'", name) + if runtime.GOOS == "windows" { + return t.killProcessesWindows(name) + } + return t.killProcessesUnix(name) +} +func (t KillProcessTask) killProcessesUnix(name string) error { _, _, _, err := t.cmdRunner.RunCommand("pkill", "-9", name) if err != nil { return bosherr.WrapError(err, "Killing processes") } + return nil +} + +func (t KillProcessTask) listProcessesWindows() (map[string][]int, error) { + stdout, _, _, err := t.cmdRunner.RunCommand("-Command", "(Get-Process) | foreach {$_.Name, $_.Id}") + if err != nil { + return nil, bosherr.WrapError(err, "listing Windows processes") + } + procs := make(map[string][]int) + lines := strings.Split(stdout, "\n") + + for i := 0; i < len(lines)-1; i += 2 { + name := strings.TrimSpace(lines[i]) + pid, err := strconv.Atoi(strings.TrimSpace(lines[i+1])) + if name != "" && err == nil && pid > 0 { + procs[name] = append(procs[name], pid) + } + } + + return procs, err +} + +func (t KillProcessTask) killProcessesWindows(pattern string) error { + procs, err := t.listProcessesWindows() + if err != nil { + return bosherr.WrapError(err, "KillProcessTask: listing Windows processes") + } + t.logger.Info("KillProcessTask", "found %d processes", len(procs)) + pattern = strings.ToLower(pattern) + foundMatch := false + + for name, pids := range procs { + // Process names are case-insensitive on Windows + match, err := filepath.Match(pattern, strings.ToLower(name)) + if err != nil { + return bosherr.Errorf("Process name pattern '%s' is invalid: %s", name, err) + } + if !match { + continue + } + for _, pid := range pids { + if pid == 0 { + continue + } + if err := killProcess(pid); err != nil { + t.logger.Info("KillProcessTask", "error killing process: %s - %d", name, pid) + return bosherr.WrapError(err, "Killing process") + } + t.logger.Info("KillProcessTask", "killed process: %s - %d", name, pid) + foundMatch = true + } + + } + + if !foundMatch { + return bosherr.Errorf("Process '%s' must match at least one monitored process", pattern) + } return nil } @@ -125,10 +191,18 @@ func (t KillProcessTask) killService(service monit.Service) error { return bosherr.Errorf("Process '%s' PID was 1 which is not allowed to kill", service.Name) } - _, _, _, err := t.cmdRunner.RunCommand("kill", "-9", strconv.Itoa(service.PID)) - if err != nil { + if err := killProcess(service.PID); err != nil { return bosherr.WrapError(err, "Killing process") } return nil } + +func killProcess(pid int) error { + proc, err := os.FindProcess(pid) + if err != nil { + return err + } + defer proc.Release() + return proc.Kill() +} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/monit/client.go b/src/github.com/cppforlife/turbulence/agentreqs/monit/client.go index 3e55aa8..2e284a9 100644 --- a/src/github.com/cppforlife/turbulence/agentreqs/monit/client.go +++ b/src/github.com/cppforlife/turbulence/agentreqs/monit/client.go @@ -1,13 +1,6 @@ package monit import ( - "encoding/xml" - "io/ioutil" - "net/http" - gourl "net/url" - "strings" - - bosherr "github.com/cloudfoundry/bosh-utils/errors" boshhttp "github.com/cloudfoundry/bosh-utils/http" boshlog "github.com/cloudfoundry/bosh-utils/logger" ) @@ -31,79 +24,3 @@ func NewHTTPClient( logger: logger, } } - -func (c httpClient) Services() ([]Service, error) { - var services []Service - - status, err := c.status() - if err != nil { - return services, bosherr.WrapError(err, "Getting status from monit") - } - - for _, service := range status.Services.Services { - // skip system service which does not have a PID (not a process) - if service.PID != 0 { - services = append(services, Service{Name: service.Name, PID: service.PID}) - } - } - - return services, nil -} - -func (c httpClient) status() (status, error) { - statusURL := gourl.URL{ - Scheme: "http", - Host: c.host, - Path: "/_status2", - RawQuery: "format=xml", - } - - resp, err := c.makeGETRequest(statusURL) - if err != nil { - return status{}, bosherr.WrapError(err, "Sending status request to monit") - } - - defer resp.Body.Close() - - respBody, err := c.validateResponse(resp) - if err != nil { - return status{}, bosherr.WrapError(err, "Getting monit status") - } - - // todo cheat a bit instead of loading charset library - respBodyStr := strings.Replace(string(respBody), ` encoding="ISO-8859-1"`, "", 1) - - var st status - - err = xml.Unmarshal([]byte(respBodyStr), &st) - if err != nil { - return status{}, bosherr.WrapError(err, "Unmarshalling monit status") - } - - return st, nil -} - -func (c httpClient) validateResponse(resp *http.Response) ([]byte, error) { - if resp.StatusCode != http.StatusOK { - return nil, bosherr.Errorf("Request failed with status '%s'", resp.Status) - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, bosherr.WrapError(err, "Reading body of failed monit response") - } - - return body, nil -} - -func (c httpClient) makeGETRequest(target gourl.URL) (*http.Response, error) { - request, err := http.NewRequest("GET", target.String(), nil) - if err != nil { - return nil, err - } - - request.SetBasicAuth(c.username, c.password) - request.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - return c.client.Do(request) -} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/monit/client_unix.go b/src/github.com/cppforlife/turbulence/agentreqs/monit/client_unix.go new file mode 100644 index 0000000..79e1284 --- /dev/null +++ b/src/github.com/cppforlife/turbulence/agentreqs/monit/client_unix.go @@ -0,0 +1,89 @@ +// +build !windows + +package monit + +import ( + "encoding/xml" + "io/ioutil" + "net/http" + gourl "net/url" + "strings" + + bosherr "github.com/cloudfoundry/bosh-utils/errors" +) + +func (c httpClient) Services() ([]Service, error) { + var services []Service + + status, err := c.status() + if err != nil { + return services, bosherr.WrapError(err, "Getting status from monit") + } + + for _, service := range status.Services.Services { + // skip system service which does not have a PID (not a process) + if service.PID != 0 { + services = append(services, Service{Name: service.Name, PID: service.PID}) + } + } + + return services, nil +} + +func (c httpClient) status() (status, error) { + statusURL := gourl.URL{ + Scheme: "http", + Host: c.host, + Path: "/_status2", + RawQuery: "format=xml", + } + + resp, err := c.makeGETRequest(statusURL) + if err != nil { + return status{}, bosherr.WrapError(err, "Sending status request to monit") + } + + defer resp.Body.Close() + + respBody, err := c.validateResponse(resp) + if err != nil { + return status{}, bosherr.WrapError(err, "Getting monit status") + } + + // todo cheat a bit instead of loading charset library + respBodyStr := strings.Replace(string(respBody), ` encoding="ISO-8859-1"`, "", 1) + + var st status + + err = xml.Unmarshal([]byte(respBodyStr), &st) + if err != nil { + return status{}, bosherr.WrapError(err, "Unmarshalling monit status") + } + + return st, nil +} + +func (c httpClient) validateResponse(resp *http.Response) ([]byte, error) { + if resp.StatusCode != http.StatusOK { + return nil, bosherr.Errorf("Request failed with status '%s'", resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, bosherr.WrapError(err, "Reading body of failed monit response") + } + + return body, nil +} + +func (c httpClient) makeGETRequest(target gourl.URL) (*http.Response, error) { + request, err := http.NewRequest("GET", target.String(), nil) + if err != nil { + return nil, err + } + + request.SetBasicAuth(c.username, c.password) + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + return c.client.Do(request) +} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/monit/client_windows.go b/src/github.com/cppforlife/turbulence/agentreqs/monit/client_windows.go new file mode 100644 index 0000000..24530a5 --- /dev/null +++ b/src/github.com/cppforlife/turbulence/agentreqs/monit/client_windows.go @@ -0,0 +1,41 @@ +package monit + +import ( + "bytes" + "fmt" + "os/exec" + "strconv" + "strings" +) + +const ( + serviceDescription = "vcap" + listAllJobsScript = `(get-wmiobject win32_service -filter "description='` + serviceDescription + `'") | ForEach{ $_.Name, $_.ProcessId }` +) + +func (c httpClient) Services() ([]Service, error) { + c.logger.Info("monit.Windows.httpClient", "Services: preparing to list services") + + cmd := exec.Command("Powershell.exe", "-Command", listAllJobsScript) + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("monit.Services Error: %s Stderr: %s", err, stderr.String()) + } + + var svcs []Service + lines := strings.Split(stdout.String(), "\n") + + for i := 0; i < len(lines)-1; i += 2 { + name := strings.TrimSpace(lines[i]) + pid, err := strconv.Atoi(strings.TrimSpace(lines[i+1])) + if name != "" && err == nil && pid > 0 { + svcs = append(svcs, Service{Name: name, PID: pid}) + } + } + + c.logger.Info("monit.Windows.httpClient", "Services: found %d services: %+v", len(svcs), svcs) + return svcs, nil +} diff --git a/src/github.com/cppforlife/turbulence/agentreqs/monit/provider.go b/src/github.com/cppforlife/turbulence/agentreqs/monit/provider.go index 527f165..37adc43 100644 --- a/src/github.com/cppforlife/turbulence/agentreqs/monit/provider.go +++ b/src/github.com/cppforlife/turbulence/agentreqs/monit/provider.go @@ -2,6 +2,7 @@ package monit import ( "net/http" + "runtime" "strings" "time" @@ -26,6 +27,10 @@ func NewClientProvider(fs boshsys.FileSystem, logger boshlog.Logger) ClientProvi } func (p ClientProvider) Get() (Client, error) { + if runtime.GOOS == "windows" { + return NewHTTPClient("", "", "", nil, p.logger), nil + } + credsStr, err := p.fs.ReadFileString(monitCredsPath) if err != nil { return nil, bosherr.WrapError(err, "Getting monit credentials") diff --git a/src/github.com/cppforlife/turbulence/agentreqs/shutdown_task.go b/src/github.com/cppforlife/turbulence/agentreqs/shutdown_task.go index bb50911..59e166e 100644 --- a/src/github.com/cppforlife/turbulence/agentreqs/shutdown_task.go +++ b/src/github.com/cppforlife/turbulence/agentreqs/shutdown_task.go @@ -2,6 +2,7 @@ package agentreqs import ( "fmt" + "runtime" bosherr "github.com/cloudfoundry/bosh-utils/errors" boshlog "github.com/cloudfoundry/bosh-utils/logger" @@ -22,15 +23,16 @@ type ShutdownOptions struct { type ShutdownTask struct { cmdRunner boshsys.CmdRunner opts ShutdownOptions + logger boshlog.Logger } -func NewShutdownTask(cmdRunner boshsys.CmdRunner, opts ShutdownOptions, _ boshlog.Logger) ShutdownTask { - return ShutdownTask{cmdRunner, opts} +func NewShutdownTask(cmdRunner boshsys.CmdRunner, opts ShutdownOptions, logger boshlog.Logger) ShutdownTask { + return ShutdownTask{cmdRunner, opts, logger} } func (t ShutdownTask) Execute() error { if t.opts.Crash { - return t.sysrq("c") + return t.crash() } if len(t.opts.Sysrq) > 0 { @@ -44,7 +46,20 @@ func (t ShutdownTask) Execute() error { return t.halt(t.opts.Force) } +func (t ShutdownTask) crash() error { + if runtime.GOOS == "windows" { + t.logger.Info("ShutdownTask", "crash not implemented on Windows - stopping with force") + return t.halt(true) + } + return t.sysrq("c") +} + func (t ShutdownTask) sysrq(val string) error { + if runtime.GOOS == "windows" { + t.logger.Info("ShutdownTask", "syrq not implemented on Windows") + return nil + } + cmd := fmt.Sprintf("echo 1 > /proc/sys/kernel/sysrq && echo %s > /proc/sysrq-trigger", val) _, _, _, err := t.cmdRunner.RunCommand("bash", "-c", cmd) @@ -56,13 +71,22 @@ func (t ShutdownTask) sysrq(val string) error { } func (t ShutdownTask) halt(force bool) error { - var forceArg []string - - if force { - forceArg = append(forceArg, "--force") + var args []string + var command string + if runtime.GOOS == "windows" { + command = "-Command" + args = []string{"Stop-Computer"} + if force { + args = append(args, "-Force") + } + } else { + command = "halt" + if force { + args = append(args, "--force") + } } - _, _, _, err := t.cmdRunner.RunCommand("halt", forceArg...) + _, _, _, err := t.cmdRunner.RunCommand(command, args...) if err != nil { return bosherr.WrapError(err, "Halting") } @@ -71,13 +95,22 @@ func (t ShutdownTask) halt(force bool) error { } func (t ShutdownTask) reboot(force bool) error { - var forceArg []string - - if force { - forceArg = append(forceArg, "--force") + var args []string + var command string + if runtime.GOOS == "windows" { + command = "-Command" + args = []string{"Restart-Computer"} + if force { + args = append(args, "-Force") + } + } else { + command = "reboot" + if force { + args = append(args, "--force") + } } - _, _, _, err := t.cmdRunner.RunCommand("reboot", forceArg...) + _, _, _, err := t.cmdRunner.RunCommand(command, args...) if err != nil { return bosherr.WrapError(err, "Rebooting") } From 4e9dd4e0c22d5145f02d6d98bd423c23bd6bacbe Mon Sep 17 00:00:00 2001 From: Amin Jamali Date: Thu, 26 Jan 2017 14:10:05 -0500 Subject: [PATCH 2/2] Fix pre-start script for windows jobs - User `cert.ca` instead of `ca_cert` (updates for release v0.5) - stress/stress-windows-1.0.4.zip blob was compiled using cygwin - golang-windows/go1.7.4.windows-amd64.zip can be downloaded from https://storage.googleapis.com/golang/go1.7.4.windows-amd64.zip --- config/blobs.yml | 6 +++++ .../templates/ca_cert.erb | 2 +- .../templates/config.json.erb | 2 +- .../templates/pre-start.ps1.erb | 10 +++++++ packages/golang-windows/packaging | 2 +- packages/golang-windows/spec | 2 +- packages/stress-windows/packaging | 27 +++++++------------ 7 files changed, 30 insertions(+), 21 deletions(-) diff --git a/config/blobs.yml b/config/blobs.yml index d8b5f81..ccadd61 100644 --- a/config/blobs.yml +++ b/config/blobs.yml @@ -1,3 +1,6 @@ +golang-windows/go1.7.4.windows-amd64.zip: + size: 90409580 + sha: a0da2f5d67e5b800eba811a34ca4b2aa22cca678 golang/go1.6.2.linux-amd64.tar.gz: size: 84840658 object_id: f1833f76-ad8b-4bef-55cd-a9fab4441299 @@ -6,3 +9,6 @@ stress/stress-1.0.4.tar.gz: size: 191800 object_id: 8c4a0c3b-348e-4367-8e2b-8c2742c5db79 sha: e1533bc704928ba6e26a362452e6db8fd58b1f0b +stress/stress-windows-1.0.4.zip: + size: 1275268 + sha: 45117dada9dcf3982759e163632c4c6669fbdeb5 diff --git a/jobs/turbulence_agent_windows/templates/ca_cert.erb b/jobs/turbulence_agent_windows/templates/ca_cert.erb index 9ade4a1..bce2eab 100644 --- a/jobs/turbulence_agent_windows/templates/ca_cert.erb +++ b/jobs/turbulence_agent_windows/templates/ca_cert.erb @@ -1 +1 @@ -<%= link("api").p("ca_cert") %> +<%= link("api").p("cert.ca") %> diff --git a/jobs/turbulence_agent_windows/templates/config.json.erb b/jobs/turbulence_agent_windows/templates/config.json.erb index 73fd68d..ebe02ab 100644 --- a/jobs/turbulence_agent_windows/templates/config.json.erb +++ b/jobs/turbulence_agent_windows/templates/config.json.erb @@ -8,7 +8,7 @@ JSON.dump( "API" => { "Host" => api.instances.first.address, "Port" => api.p("listen_port"), - "CACert" => api.p("ca_cert"), + "CACert" => api.p("cert.ca"), "Username" => api.p("username"), "Password" => api.p("password"), }, diff --git a/jobs/turbulence_agent_windows/templates/pre-start.ps1.erb b/jobs/turbulence_agent_windows/templates/pre-start.ps1.erb index 9b6d305..d96e298 100644 --- a/jobs/turbulence_agent_windows/templates/pre-start.ps1.erb +++ b/jobs/turbulence_agent_windows/templates/pre-start.ps1.erb @@ -1,3 +1,6 @@ +$ErrorActionPreference = "Stop"; +trap { $host.SetShouldExit(1) } + $SETTINGS_FILE = "C:\var\vcap\bosh\settings.json" $CONFIG_FILE = "C:\var\vcap\jobs\turbulence_agent_windows\config\config.json" @@ -13,4 +16,11 @@ try { Exit 1 } +$REG_KEY='Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' +$oldpath = (Get-ItemProperty -Path $REG_KEY -Name PATH).path +$newpath = "${oldpath};C:\var\vcap\packages\stress-windows" +Set-ItemProperty -Path $REG_KEY -Name PATH -Value $newpath + +SETX /M PATH $newpath + Exit 0 diff --git a/packages/golang-windows/packaging b/packages/golang-windows/packaging index 56f663e..20d20a0 100644 --- a/packages/golang-windows/packaging +++ b/packages/golang-windows/packaging @@ -1,4 +1,4 @@ -$path="golang-windows/go1.7.3.windows-amd64.zip" +$path="golang-windows/go1.7.4.windows-amd64.zip" try { Add-Type -AssemblyName System.IO.Compression.FileSystem diff --git a/packages/golang-windows/spec b/packages/golang-windows/spec index 98e1a5d..236d99d 100644 --- a/packages/golang-windows/spec +++ b/packages/golang-windows/spec @@ -4,4 +4,4 @@ name: golang-windows dependencies: [] files: -- golang-windows/go1.7.3.windows-amd64.zip +- golang-windows/go1.7.4.windows-amd64.zip diff --git a/packages/stress-windows/packaging b/packages/stress-windows/packaging index 217d3f0..7799b8f 100644 --- a/packages/stress-windows/packaging +++ b/packages/stress-windows/packaging @@ -1,23 +1,16 @@ -$STRESS_ZIP = "stress/stress-windows-1.0.4.zip" -try { - Add-Type -AssemblyName System.IO.Compression.FileSystem - function Unzip - { - param([string]$zipfile, [string]$outpath) +$ErrorActionPreference = "Stop"; +trap { $host.SetShouldExit(1) } - [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) - } +$STRESS_ZIP = "stress/stress-windows-1.0.4.zip" +Add-Type -AssemblyName System.IO.Compression.FileSystem - Unzip "${STRESS_ZIP}" "${env:BOSH_INSTALL_TARGET}" +function Unzip +{ + param([string]$zipfile, [string]$outpath) - # Add to global path - $oldpath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).path - $newpath = "${oldpath};${env:BOSH_INSTALL_TARGET}" - Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newpath -} catch { - Write-Error "Error installing stress" - Write-Error $_.Exception.Message - Exit 1 + [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) } + +Unzip "${STRESS_ZIP}" "${env:BOSH_INSTALL_TARGET}" Exit 0