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

Allow to specify total memory on agent configuration #4052

Merged
merged 4 commits into from
Mar 28, 2018
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
4 changes: 4 additions & 0 deletions client/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ type Config struct {
// dynamically. It should be given as Cores * MHz (2 Cores * 2 Ghz = 4000)
CpuCompute int

// MemoryMB is the default node total memory in megabytes if it cannot be
// determined dynamically.
MemoryMB int

// MaxKillTimeout allows capping the user-specifiable KillTimeout. If the
// task's KillTimeout is greater than the MaxKillTimeout, MaxKillTimeout is
// used.
Expand Down
26 changes: 18 additions & 8 deletions client/fingerprint/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/shirou/gopsutil/mem"
)

const bytesInMB = 1024 * 1024

// MemoryFingerprint is used to fingerprint the available memory on the node
type MemoryFingerprint struct {
StaticFingerprinter
Expand All @@ -24,17 +26,25 @@ func NewMemoryFingerprint(logger *log.Logger) Fingerprint {
}

func (f *MemoryFingerprint) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error {
memInfo, err := mem.VirtualMemory()
if err != nil {
f.logger.Printf("[WARN] Error reading memory information: %s", err)
return err
var totalMemory int
cfg := req.Config
if cfg.MemoryMB != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would writing a unit test for this make sense?

totalMemory = cfg.MemoryMB * bytesInMB
} else {
memInfo, err := mem.VirtualMemory()
if err != nil {
f.logger.Printf("[WARN] Error reading memory information: %s", err)
return err
}
if memInfo.Total > 0 {
totalMemory = int(memInfo.Total)
}
}

if memInfo.Total > 0 {
resp.AddAttribute("memory.totalbytes", fmt.Sprintf("%d", memInfo.Total))

if totalMemory > 0 {
resp.AddAttribute("memory.totalbytes", fmt.Sprintf("%d", totalMemory))
resp.Resources = &structs.Resources{
MemoryMB: int(memInfo.Total / 1024 / 1024),
MemoryMB: totalMemory / bytesInMB,
}
}

Expand Down
22 changes: 22 additions & 0 deletions client/fingerprint/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/hashicorp/nomad/client/config"
cstructs "github.com/hashicorp/nomad/client/structs"
"github.com/hashicorp/nomad/nomad/structs"

"github.com/stretchr/testify/require"
)

func TestMemoryFingerprint(t *testing.T) {
Expand All @@ -30,3 +32,23 @@ func TestMemoryFingerprint(t *testing.T) {
t.Fatalf("Expected node.Resources.MemoryMB to be non-zero")
}
}

func TestMemoryFingerprint_Override(t *testing.T) {
f := NewMemoryFingerprint(testLogger())
node := &structs.Node{
Attributes: make(map[string]string),
}

memoryMB := 15000
request := &cstructs.FingerprintRequest{Config: &config.Config{MemoryMB: memoryMB}, Node: node}
var response cstructs.FingerprintResponse
err := f.Fingerprint(request, &response)
if err != nil {
t.Fatalf("err: %v", err)
}

assertNodeAttributeContains(t, response.Attributes, "memory.totalbytes")
require := require.New(t)
require.NotNil(response.Resources)
require.Equal(response.Resources.MemoryMB, memoryMB)
}
3 changes: 3 additions & 0 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ func (a *Agent) clientConfig() (*clientconfig.Config, error) {
if a.config.Client.CpuCompute != 0 {
conf.CpuCompute = a.config.Client.CpuCompute
}
if a.config.Client.MemoryMB != 0 {
conf.MemoryMB = a.config.Client.MemoryMB
}
if a.config.Client.MaxKillTimeout != "" {
dur, err := time.ParseDuration(a.config.Client.MaxKillTimeout)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions command/agent/config-test-fixtures/non-optional.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
client {
memory_total_mb = 5555
}
6 changes: 6 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ type ClientConfig struct {
// CpuCompute is used to override any detected or default total CPU compute.
CpuCompute int `mapstructure:"cpu_total_compute"`

// MemoryMB is used to override any detected or default total memory.
MemoryMB int `mapstructure:"memory_total_mb"`

// MaxKillTimeout allows capping the user-specifiable KillTimeout.
MaxKillTimeout string `mapstructure:"max_kill_timeout"`

Expand Down Expand Up @@ -1094,6 +1097,9 @@ func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig {
if b.CpuCompute != 0 {
result.CpuCompute = b.CpuCompute
}
if b.MemoryMB != 0 {
result.MemoryMB = b.MemoryMB
}
if b.MaxKillTimeout != "" {
result.MaxKillTimeout = b.MaxKillTimeout
}
Expand Down
1 change: 1 addition & 0 deletions command/agent/config_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ func parseClient(result **ClientConfig, list *ast.ObjectList) error {
"chroot_env",
"network_interface",
"network_speed",
"memory_total_mb",
"cpu_total_compute",
"max_kill_timeout",
"client_max_port",
Expand Down
65 changes: 58 additions & 7 deletions command/agent/config_parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package agent

import (
"path/filepath"
"reflect"
"strings"
"testing"
"time"

"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/structs/config"
"github.com/kr/pretty"
"github.com/stretchr/testify/require"
)

func TestConfig_Parse(t *testing.T) {
Expand Down Expand Up @@ -64,6 +62,7 @@ func TestConfig_Parse(t *testing.T) {
NetworkInterface: "eth0",
NetworkSpeed: 100,
CpuCompute: 4444,
MemoryMB: 0,
MaxKillTimeout: "10s",
ClientMinPort: 1000,
ClientMaxPort: 2000,
Expand Down Expand Up @@ -206,9 +205,64 @@ func TestConfig_Parse(t *testing.T) {
},
false,
},
{
"non-optional.hcl",
&Config{
Region: "",
Datacenter: "",
NodeName: "",
DataDir: "",
LogLevel: "",
BindAddr: "",
EnableDebug: false,
Ports: nil,
Addresses: nil,
AdvertiseAddrs: nil,
Client: &ClientConfig{
Enabled: false,
StateDir: "",
AllocDir: "",
Servers: nil,
NodeClass: "",
Meta: nil,
Options: nil,
ChrootEnv: nil,
NetworkInterface: "",
NetworkSpeed: 0,
CpuCompute: 0,
MemoryMB: 5555,
MaxKillTimeout: "",
ClientMinPort: 0,
ClientMaxPort: 0,
Reserved: nil,
GCInterval: 0,
GCParallelDestroys: 0,
GCDiskUsageThreshold: 0,
GCInodeUsageThreshold: 0,
GCMaxAllocs: 0,
NoHostUUID: nil,
},
Server: nil,
ACL: nil,
Telemetry: nil,
LeaveOnInt: false,
LeaveOnTerm: false,
EnableSyslog: false,
SyslogFacility: "",
DisableUpdateCheck: nil,
DisableAnonymousSignature: false,
Consul: nil,
Vault: nil,
TLSConfig: nil,
HTTPAPIResponseHeaders: nil,
Sentinel: nil,
},
false,
},
}

for _, tc := range cases {
require := require.New(t)
t.Run(tc.File, func(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./config-test-fixtures", tc.File))
if err != nil {
Expand All @@ -219,10 +273,7 @@ func TestConfig_Parse(t *testing.T) {
if (err != nil) != tc.Err {
t.Fatalf("file: %s\n\n%s", tc.File, err)
}

if !reflect.DeepEqual(actual, tc.Result) {
t.Errorf("file: %s diff: (actual vs expected)\n\n%s", tc.File, strings.Join(pretty.Diff(actual, tc.Result), "\n"))
}
require.EqualValues(actual, tc.Result)
})
}
}
2 changes: 2 additions & 0 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func TestConfig_Merge(t *testing.T) {
},
NetworkSpeed: 100,
CpuCompute: 100,
MemoryMB: 100,
MaxKillTimeout: "20s",
ClientMaxPort: 19996,
Reserved: &Resources{
Expand Down Expand Up @@ -232,6 +233,7 @@ func TestConfig_Merge(t *testing.T) {
ClientMinPort: 22000,
NetworkSpeed: 105,
CpuCompute: 105,
MemoryMB: 105,
MaxKillTimeout: "50s",
Reserved: &Resources{
CPU: 15,
Expand Down
3 changes: 3 additions & 0 deletions website/source/docs/agent/configuration/client.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ client {
clients can determine their total CPU compute automatically, and thus in most
cases this should be left unset.

- `memory_total_mb` `(int:0)` - Specifies an override for the total memory. If set,
this value overrides any detected memory.

- `node_class` `(string: "")` - Specifies an arbitrary string used to logically
group client nodes by user-defined class. This can be used during job
placement as a filter.
Expand Down