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

feat: cache proxmox version in Client #312

Merged
merged 1 commit into from
Feb 7, 2024
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
95 changes: 82 additions & 13 deletions proxmox/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
)

Expand All @@ -26,14 +27,20 @@ const exitStatusSuccess = "OK"

// Client - URL, user and password to specific Proxmox node
type Client struct {
session *Session
ApiUrl string
Username string
Password string
Otp string
TaskTimeout int
session *Session
ApiUrl string
Username string
Password string
Otp string
TaskTimeout int
version *Version
versionMutex *sync.Mutex
}

const (
Client_Error_Nil string = "client may not be nil"
)

const (
VmRef_Error_Nil string = "vm reference may not be nil"
)
Expand Down Expand Up @@ -105,7 +112,7 @@ func NewClient(apiUrl string, hclient *http.Client, http_headers string, tls *tl
return nil, err
}
if err_s == nil {
client = &Client{session: sess, ApiUrl: apiUrl, TaskTimeout: taskTimeout}
client = &Client{session: sess, ApiUrl: apiUrl, TaskTimeout: taskTimeout, versionMutex: &sync.Mutex{}}
}

return client, err_s
Expand All @@ -130,13 +137,19 @@ func (c *Client) Login(username string, password string, otp string) (err error)
return c.session.Login(username, password, otp)
}

func (c *Client) GetVersion() (data map[string]interface{}, err error) {
resp, err := c.session.Get("/version", nil, nil)
if err != nil {
return nil, err
// Updates the client's cached version information and returns it.
func (c *Client) GetVersion() (version Version, err error) {
params, err := c.GetItemConfigMapStringInterface("/version", "version", "data")
version = version.mapToSDK(params)
cachedVersion := Version{ // clones the struct
Major: version.Major,
Minor: version.Minor,
Patch: version.Patch,
}

return ResponseJSON(resp)
c.versionMutex.Lock()
c.version = &cachedVersion
c.versionMutex.Unlock()
return
}

func (c *Client) GetJsonRetryable(url string, data *map[string]interface{}, tries int) error {
Expand Down Expand Up @@ -2213,3 +2226,59 @@ func (c *Client) CheckTask(resp *http.Response) (exitStatus string, err error) {
}
return c.WaitForCompletion(taskResponse)
}

// Returns the Client's cached version if it exists, otherwise fetches the version from the API.
func (c *Client) Version() (Version, error) {
if c == nil {
return Version{}, errors.New(Client_Error_Nil)
}
if c.version == nil {
return c.GetVersion()
}
c.versionMutex.Lock()
defer c.versionMutex.Unlock()
return Version{
Major: c.version.Major,
Minor: c.version.Minor,
Patch: c.version.Patch,
}, nil
}

type Version struct {
Major uint8
Minor uint8
Patch uint8
}

// Greater returns true if the version is greater than the other version.
func (v Version) Greater(other Version) bool {
return uint32(v.Major)*256*256+uint32(v.Minor)*256+uint32(v.Patch) > uint32(other.Major)*256*256+uint32(other.Minor)*256+uint32(other.Patch)
Copy link
Collaborator Author

@Tinyblargon Tinyblargon Feb 7, 2024

Choose a reason for hiding this comment

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

This is instead of multiple consecutive if statements. It needed a unit test anyways so might as well implement the most performant solution.
Minor 1 > Patch 255
Major 1 > (Minor 255 + Patch 255)

}

func (Version) mapToSDK(params map[string]interface{}) (version Version) {
if itemValue, isSet := params["version"]; isSet {
rawVersion := strings.Split(itemValue.(string), ".")
if len(rawVersion) > 0 {
tmpMajor, _ := strconv.ParseUint(rawVersion[0], 10, 8)
version.Major = uint8(tmpMajor)
}
if len(rawVersion) > 1 {
tmpMinor, _ := strconv.ParseUint(rawVersion[1], 10, 8)
version.Minor = uint8(tmpMinor)
}
if len(rawVersion) > 2 {
tmpPatch, _ := strconv.ParseUint(rawVersion[2], 10, 8)
version.Patch = uint8(tmpPatch)
}
}
return
}

// Smaller returns true if the version is less than the other version.
func (v Version) Smaller(other Version) bool {
return uint32(v.Major)*256*256+uint32(v.Minor)*256+uint32(v.Patch) < uint32(other.Major)*256*256+uint32(other.Minor)*256+uint32(other.Patch)
}

func (v Version) String() string {
return strconv.FormatInt(int64(v.Major), 10) + "." + strconv.FormatInt(int64(v.Minor), 10) + "." + strconv.FormatInt(int64(v.Patch), 10)
}
76 changes: 76 additions & 0 deletions proxmox/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package proxmox

import (
"testing"

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

func Test_Version_Greater(t *testing.T) {
type input struct {
a Version
b Version
}
tests := []struct {
name string
input input
output bool
}{
{"a > b 0", input{Version{1, 0, 0}, Version{0, 0, 0}}, true},
{"a > b 1", input{Version{0, 1, 0}, Version{0, 0, 255}}, true},
{"a > b 2", input{Version{1, 0, 0}, Version{0, 255, 255}}, true},
{"a < b 0", input{Version{7, 4, 1}, Version{7, 4, 2}}, false},
{"a < b 1", input{Version{0, 0, 255}, Version{0, 1, 0}}, false},
{"a < b 2", input{Version{0, 255, 255}, Version{1, 0, 0}}, false},
{"a = b", input{Version{0, 0, 0}, Version{0, 0, 0}}, false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.output, test.input.a.Greater(test.input.b))
})
}
}

func Test_Version_mapToSDK(t *testing.T) {
tests := []struct {
name string
input map[string]interface{}
output Version
}{
{"empty", map[string]interface{}{}, Version{}},
{"full", map[string]interface{}{"version": "1.2.3"}, Version{1, 2, 3}},
{"invalid", map[string]interface{}{"version": ""}, Version{}},
{"major", map[string]interface{}{"version": "1"}, Version{1, 0, 0}},
{"partial", map[string]interface{}{"version": "1.2"}, Version{1, 2, 0}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.output, Version{}.mapToSDK(test.input))
})
}
}

func Test_Version_Smaller(t *testing.T) {
type input struct {
a Version
b Version
}
tests := []struct {
name string
input input
output bool
}{
{"a > b 0", input{Version{1, 0, 0}, Version{0, 0, 0}}, false},
{"a > b 1", input{Version{0, 1, 0}, Version{0, 0, 255}}, false},
{"a > b 2", input{Version{1, 0, 0}, Version{0, 255, 255}}, false},
{"a < b 0", input{Version{7, 4, 1}, Version{7, 4, 2}}, true},
{"a < b 1", input{Version{0, 0, 255}, Version{0, 1, 0}}, true},
{"a < b 2", input{Version{0, 255, 255}, Version{1, 0, 0}}, true},
{"a = b", input{Version{0, 0, 0}, Version{0, 0, 0}}, false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.output, test.input.a.Smaller(test.input.b))
})
}
}
Loading