Skip to content

Commit

Permalink
feat: memory validator
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinyblargon committed Jul 27, 2024
1 parent 4098fd3 commit b6d5422
Show file tree
Hide file tree
Showing 5 changed files with 429 additions and 2 deletions.
87 changes: 87 additions & 0 deletions proxmox/config_qemu_memory.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package proxmox

import (
"fmt"

"github.com/Telmate/proxmox-api-go/internal/parse"
)

Expand All @@ -10,6 +12,12 @@ type QemuMemory struct {
Shares *QemuMemoryShares `json:"shares,omitempty"` // 0 to clear, max 50000
}

const (
QemuMemory_Error_MinimumCapacityMiB_GreaterThan_CapacityMiB string = "minimum capacity MiB cannot be greater than capacity MiB"
QemuMemory_Error_NoMemoryCapacity string = "no memory capacity specified"
QemuMemory_Error_SharesHasNoEffectWithoutBallooning string = "shares has no effect when capacity equals minimum capacity"
)

func (config QemuMemory) mapToAPI(current *QemuMemory, params map[string]interface{}) string {
if current == nil { // create
if config.CapacityMiB != nil {
Expand Down Expand Up @@ -71,8 +79,87 @@ func (QemuMemory) mapToSDK(params map[string]interface{}) *QemuMemory {
return &config
}

func (config QemuMemory) Validate(current *QemuMemory) error {
var eventualCapacityMiB QemuMemoryCapacity
var eventualMinimumCapacityMiB QemuMemoryBalloonCapacity
if config.MinimumCapacityMiB != nil {
if err := config.MinimumCapacityMiB.Validate(); err != nil {
return err
}
if config.CapacityMiB != nil && QemuMemoryCapacity(*config.MinimumCapacityMiB) > *config.CapacityMiB {
return fmt.Errorf(QemuMemory_Error_MinimumCapacityMiB_GreaterThan_CapacityMiB)
}
eventualMinimumCapacityMiB = *config.MinimumCapacityMiB
eventualCapacityMiB = QemuMemoryCapacity(eventualMinimumCapacityMiB)
} else if current != nil && current.MinimumCapacityMiB != nil {
eventualMinimumCapacityMiB = *current.MinimumCapacityMiB
}
if config.CapacityMiB != nil {
if err := config.CapacityMiB.Validate(); err != nil {
return err
}
eventualCapacityMiB = *config.CapacityMiB
} else if current != nil && current.CapacityMiB != nil {
eventualCapacityMiB = *current.CapacityMiB
}
if eventualCapacityMiB == 0 {
return fmt.Errorf(QemuMemory_Error_NoMemoryCapacity)
}
if config.Shares != nil {
if err := config.Shares.Validate(); err != nil {
return err
}
if *config.Shares > 0 {
if eventualCapacityMiB == QemuMemoryCapacity(eventualMinimumCapacityMiB) {
return fmt.Errorf(QemuMemory_Error_SharesHasNoEffectWithoutBallooning)
}
}
}
return nil
}

type QemuMemoryBalloonCapacity uint32 // max 4178944

const (
QemuMemoryBalloonCapacity_Error_Invalid string = "memory balloon capacity has a maximum of 4178944"
qemuMemoryBalloonCapacity_Max QemuMemoryBalloonCapacity = 4178944
)

func (m QemuMemoryBalloonCapacity) Validate() error {
if m > qemuMemoryBalloonCapacity_Max {
return fmt.Errorf(QemuMemoryBalloonCapacity_Error_Invalid)
}
return nil
}

type QemuMemoryCapacity uint32 // max 4178944

const (
QemuMemoryCapacity_Error_Maximum string = "memory capacity has a maximum of 4178944"
QemuMemoryCapacity_Error_Minimum string = "memory capacity has a minimum of 1"
qemuMemoryCapacity_Max QemuMemoryCapacity = 4178944
)

func (m QemuMemoryCapacity) Validate() error {
if m == 0 {
return fmt.Errorf(QemuMemoryCapacity_Error_Minimum)
}
if m > qemuMemoryCapacity_Max {
return fmt.Errorf(QemuMemoryCapacity_Error_Maximum)
}
return nil
}

type QemuMemoryShares uint16 // max 50000

const (
QemuMemoryShares_Error_Invalid string = "memory shares has a maximum of 50000"
qemuMemoryShares_Max QemuMemoryShares = 50000
)

func (m QemuMemoryShares) Validate() error {
if m > qemuMemoryShares_Max {
return fmt.Errorf(QemuMemoryShares_Error_Invalid)
}
return nil
}
221 changes: 221 additions & 0 deletions proxmox/config_qemu_memory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package proxmox

import (
"errors"
"testing"

"github.com/Telmate/proxmox-api-go/internal/util"
"github.com/stretchr/testify/require"
)

func Test_QemuMemory_Validate(t *testing.T) {
type testInput struct {
new QemuMemory
current *QemuMemory
}
tests := []struct {
name string
input testInput
output error
}{
// there could still be some edge cases that are not covered
{name: `Valid Create new.CapacityMiB`,
input: testInput{new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(qemuMemoryCapacity_Max))}}},
{name: `Valid Update new.CapacityMiB smaller then current.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(1000))},
current: &QemuMemory{MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(2000))}}},
{name: `Valid Update new.CapacityMiB smaller then current.MinimumCapacityMiB and MinimumCapacityMiB lowered`,
input: testInput{
new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(1000)), MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000))},
current: &QemuMemory{MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(2000))}}},
{name: `Valid Create new.MinimumCapacityMiB`,
input: testInput{new: QemuMemory{MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000))}}},
{name: `Valid Update new.CapacityMiB == new.MinimumCapacityMiB && new.CapacityMiB > current.CapacityMiB`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1500)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1500))},
current: &QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(1000))}}},
{name: `Valid Update new.MinimumCapacityMiB > current.MinimumCapacityMiB && new.MinimumCapacityMiB < new.CapacityMiB`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(3000)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(2000))},
current: &QemuMemory{MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1500))}}},
{name: `Valid Create new.Shares(qemuMemoryShares_Max) new.CapacityMiB & new.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1001)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000)),
Shares: util.Pointer(QemuMemoryShares(qemuMemoryShares_Max))}}},
{name: `Valid Create new.Shares new.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000)),
Shares: util.Pointer(QemuMemoryShares(0))}}},
{name: `Valid Create new.Shares(0) new.CapacityMiB & new.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1001)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000)),
Shares: util.Pointer(QemuMemoryShares(0))}}},
{name: `Valid Update new.Shares(0) current.CapacityMiB == current.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{Shares: util.Pointer(QemuMemoryShares(0))},
current: &QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1000)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000))}}},
{name: `Invalid Create new.CapacityMiB(0)`,
input: testInput{new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(0))}},
output: errors.New(QemuMemoryCapacity_Error_Minimum)},
{name: `Invalid Update new.CapacityMiB(0)`,
input: testInput{
new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(0))},
current: &QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(1000))}},
output: errors.New(QemuMemoryCapacity_Error_Minimum)},
{name: `Invalid Create new.CapacityMiB > qemuMemoryCapacity_Max`,
input: testInput{new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(qemuMemoryCapacity_Max + 1))}},
output: errors.New(QemuMemoryCapacity_Error_Maximum)},
{name: `Invalid Update new.CapacityMiB > qemuMemoryCapacity_Max`,
input: testInput{
new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(qemuMemoryCapacity_Max + 1))},
current: &QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(qemuMemoryCapacity_Max))}},
output: errors.New(QemuMemoryCapacity_Error_Maximum)},
{name: `Invalid Update new.CapacityMiB(0)`,
input: testInput{
new: QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(0))},
current: &QemuMemory{CapacityMiB: util.Pointer(QemuMemoryCapacity(1000))}},
output: errors.New(QemuMemoryCapacity_Error_Minimum)},
{name: `Invalid Create new.MinimumCapacityMiB to big`,
input: testInput{new: QemuMemory{MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(qemuMemoryBalloonCapacity_Max + 1))}},
output: errors.New(QemuMemoryBalloonCapacity_Error_Invalid)},
{name: `Invalid Update new.MinimumCapacityMiB to big`,
input: testInput{
new: QemuMemory{MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(qemuMemoryBalloonCapacity_Max + 1))},
current: &QemuMemory{MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000))}},
output: errors.New(QemuMemoryBalloonCapacity_Error_Invalid)},
{name: `Invalid Create new.MinimumCapacityMiB > new.CapacityMiB`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1000)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(2000))}},
output: errors.New(QemuMemory_Error_MinimumCapacityMiB_GreaterThan_CapacityMiB)},
{name: `Invalid Create new.Shares(1)`,
input: testInput{new: QemuMemory{Shares: util.Pointer(QemuMemoryShares(1))}},
output: errors.New(QemuMemory_Error_NoMemoryCapacity)},
{name: `Invalid Create new.Shares() too big and new.CapacityMiB & new.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1001)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000)),
Shares: util.Pointer(QemuMemoryShares(qemuMemoryShares_Max + 1))}},
output: errors.New(QemuMemoryShares_Error_Invalid)},
{name: `Invalid Update new.Shares() too big and new.CapacityMiB & new.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1001)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000)),
Shares: util.Pointer(QemuMemoryShares(qemuMemoryShares_Max + 1))},
current: &QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(512)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(256)),
Shares: util.Pointer(QemuMemoryShares(1))}},
output: errors.New(QemuMemoryShares_Error_Invalid)},
{name: `Invalid Create new.Shares(1) when new.CapacityMiB == new.MinimumCapacityMiB`,
input: testInput{new: QemuMemory{Shares: util.Pointer(QemuMemoryShares(1))}},
output: errors.New(QemuMemory_Error_NoMemoryCapacity)},
{name: `Invalid Update new.Shares(1) when current.CapacityMiB == current.MinimumCapacityMiB`,
input: testInput{
new: QemuMemory{Shares: util.Pointer(QemuMemoryShares(1))},
current: &QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1000)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1000))}},
output: errors.New(QemuMemory_Error_SharesHasNoEffectWithoutBallooning)},
{name: `Invalid Update new.Shares(1) new.CapacityMiB == current.MinimumCapacityMiB & MinimumCapacityMiB not updated`,
input: testInput{
new: QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(1024)),
Shares: util.Pointer(QemuMemoryShares(1))},
current: &QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(2048)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1024))}},
output: errors.New(QemuMemory_Error_SharesHasNoEffectWithoutBallooning)},
{name: `Invalid Update new.Shares(1) new.MinimumCapacityMiB == current.CapacityMiB & CapacityMiB not updated`,
input: testInput{
new: QemuMemory{
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(2048)),
Shares: util.Pointer(QemuMemoryShares(1))},
current: &QemuMemory{
CapacityMiB: util.Pointer(QemuMemoryCapacity(2048)),
MinimumCapacityMiB: util.Pointer(QemuMemoryBalloonCapacity(1024))}},
output: errors.New(QemuMemory_Error_SharesHasNoEffectWithoutBallooning)},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.output, test.input.new.Validate(test.input.current))
})
}
}

func Test_QemuMemoryBalloonCapacity_Validate(t *testing.T) {
tests := []struct {
name string
input QemuMemoryBalloonCapacity
output error
}{
{name: `Valid`,
input: qemuMemoryBalloonCapacity_Max},
{name: `Invalid Max`,
input: qemuMemoryBalloonCapacity_Max + 1,
output: errors.New(QemuMemoryBalloonCapacity_Error_Invalid)},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.output, test.input.Validate())
})
}
}

func Test_QemuMemoryCapacity_Validate(t *testing.T) {
tests := []struct {
name string
input QemuMemoryCapacity
output error
}{
{name: `Valid`,
input: qemuMemoryCapacity_Max},
{name: `Invalid Max`,
input: qemuMemoryCapacity_Max + 1,
output: errors.New(QemuMemoryCapacity_Error_Maximum)},
{name: `Invalid Min`,
input: 0,
output: errors.New(QemuMemoryCapacity_Error_Minimum)},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.output, test.input.Validate())
})
}
}

func Test_QemuMemoryShares_Validate(t *testing.T) {
tests := []struct {
name string
input QemuMemoryShares
output error
}{
{name: `Valid`,
input: qemuMemoryShares_Max,
},
{name: `Invalid`,
input: qemuMemoryShares_Max + 1,
output: errors.New(QemuMemoryShares_Error_Invalid),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.output, test.input.Validate())
})
}
}
Loading

0 comments on commit b6d5422

Please sign in to comment.