-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
client: Merge client code with kube-upgrade
Merge the fleetlock client code with the one for kube-upgrade, as it depends on this one anyway. This way all the client code is in one easy to use package. Signed-off-by: Heathcliff <[email protected]>
- Loading branch information
1 parent
fad47e9
commit 8002ca7
Showing
4 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package client | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"sync" | ||
) | ||
|
||
type FleetlockClient struct { | ||
url string | ||
group string | ||
appID string | ||
|
||
mutex sync.RWMutex | ||
} | ||
|
||
// Create a new client for fleetlock | ||
func NewClient(url, group string) (*FleetlockClient, error) { | ||
c, err := NewEmptyClient() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
err = c.SetURL(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
err = c.SetGroup(group) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return c, nil | ||
} | ||
|
||
// Create a new fleetlock client without url or group set | ||
func NewEmptyClient() (*FleetlockClient, error) { | ||
appID, err := GetZincateAppID() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create zincati app id: %v", err) | ||
} | ||
|
||
return &FleetlockClient{ | ||
appID: appID, | ||
}, nil | ||
} | ||
|
||
// Aquire a lock for this machine | ||
func (c *FleetlockClient) Lock() error { | ||
ok, res, err := c.doRequest("/v1/pre-reboot") | ||
if err != nil { | ||
return err | ||
} else if ok { | ||
return nil | ||
} | ||
return fmt.Errorf("failed to aquire lock kind=\"%s\" reason=\"%s\"", res.Kind, res.Value) | ||
} | ||
|
||
// Release the hold lock | ||
func (c *FleetlockClient) Release() error { | ||
ok, res, err := c.doRequest("/v1/steady-state") | ||
if err != nil { | ||
return err | ||
} else if ok { | ||
return nil | ||
} | ||
return fmt.Errorf("failed to release lock kind=\"%s\" reason=\"%s\"", res.Kind, res.Value) | ||
} | ||
|
||
func (c *FleetlockClient) doRequest(path string) (bool, FleetLockResponse, error) { | ||
c.mutex.RLock() | ||
defer c.mutex.RUnlock() | ||
|
||
body, err := PrepareRequest(c.group, c.appID) | ||
if err != nil { | ||
return false, FleetLockResponse{}, fmt.Errorf("failed to prepare request body: %v", err) | ||
} | ||
req, err := http.NewRequest(http.MethodPost, c.url+path, body) | ||
if err != nil { | ||
return false, FleetLockResponse{}, fmt.Errorf("failed to create http post request: %v", err) | ||
} | ||
req.Header.Set("fleet-lock-protocol", "true") | ||
req.Header.Set("Content-Type", "application/json") | ||
|
||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return false, FleetLockResponse{}, fmt.Errorf("failed to send request to server: %v", err) | ||
} | ||
|
||
resBody, err := ParseResponse(res.Body) | ||
if err != nil { | ||
return false, FleetLockResponse{}, fmt.Errorf("failed to prepare response body: %v", err) | ||
} | ||
|
||
return res.StatusCode == http.StatusOK, resBody, nil | ||
} | ||
|
||
// Get the fleetlock server url | ||
func (c *FleetlockClient) GetURL() string { | ||
if c == nil { | ||
return "" | ||
} | ||
|
||
c.mutex.RLock() | ||
defer c.mutex.RUnlock() | ||
|
||
return c.url | ||
} | ||
|
||
// Change the fleetlock server url | ||
func (c *FleetlockClient) SetURL(url string) error { | ||
c.mutex.Lock() | ||
defer c.mutex.Unlock() | ||
|
||
if url == "" { | ||
return fmt.Errorf("the fleetlock server url can't be empty") | ||
} | ||
c.url = TrimTrailingSlash(url) | ||
return nil | ||
} | ||
|
||
// Get the fleetlock group | ||
func (c *FleetlockClient) GetGroup() string { | ||
if c == nil { | ||
return "" | ||
} | ||
|
||
c.mutex.RLock() | ||
defer c.mutex.RUnlock() | ||
|
||
return c.group | ||
} | ||
|
||
// Change the fleetlock group | ||
func (c *FleetlockClient) SetGroup(group string) error { | ||
c.mutex.Lock() | ||
defer c.mutex.Unlock() | ||
|
||
if group == "" { | ||
return fmt.Errorf("the fleetlock group can't be empty") | ||
} | ||
c.group = group | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package client | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewClient(t *testing.T) { | ||
tMatrix := []struct { | ||
Name string | ||
Url, Group string | ||
Success bool | ||
}{ | ||
{ | ||
Name: "MissingUrl", | ||
Group: "default", | ||
}, | ||
{ | ||
Name: "MissingGroup", | ||
Url: "https://fleetlock.example.com", | ||
}, | ||
{ | ||
Name: "Success", | ||
Group: "default", | ||
Url: "https://fleetlock.example.com", | ||
Success: true, | ||
}, | ||
} | ||
for _, tCase := range tMatrix { | ||
t.Run(tCase.Name, func(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
res, err := NewClient(tCase.Url, tCase.Group) | ||
|
||
if tCase.Success { | ||
if !assert.NoError(err, "Should succeed") || !assert.NotNil(res, "Should return a client") { | ||
t.FailNow() | ||
} | ||
assert.Equal(tCase.Url, res.url, "Client should have the url set") | ||
assert.Equal(tCase.Group, res.group, "Client should have the group set") | ||
assert.NotEmpty(res.appID, "Client should have the appID set") | ||
} else { | ||
assert.Error(err, "Client creation should not succeed") | ||
assert.Nil(res, "Should not return a client") | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestNewEmptyClient(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
res, err := NewEmptyClient() | ||
|
||
if !assert.NoError(err, "Should succeed") || !assert.NotNil(res, "Should return a client") { | ||
t.FailNow() | ||
} | ||
|
||
assert.Empty(res.url, "Client should not have the url set") | ||
assert.Empty(res.group, "Client should not have the group set") | ||
assert.NotEmpty(res.appID, "Client should have the appID set") | ||
} | ||
|
||
func TestLock(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
c, srv := NewFakeServer(t, http.StatusOK, "/v1/pre-reboot") | ||
defer srv.Close() | ||
|
||
err := c.Lock() | ||
assert.NoError(err, "Should succeed") | ||
|
||
c2, srv2 := NewFakeServer(t, http.StatusLocked, "/v1/pre-reboot") | ||
defer srv2.Close() | ||
|
||
err = c2.Lock() | ||
assert.Error(err, "Should not succeed") | ||
} | ||
|
||
func TestRelease(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
c, srv := NewFakeServer(t, http.StatusOK, "/v1/steady-state") | ||
defer srv.Close() | ||
|
||
err := c.Release() | ||
assert.NoError(err, "Should succeed") | ||
|
||
c2, srv2 := NewFakeServer(t, http.StatusLocked, "/v1/steady-state") | ||
defer srv2.Close() | ||
|
||
err = c2.Release() | ||
assert.Error(err, "Should not succeed") | ||
} | ||
|
||
func TestGetAndSet(t *testing.T) { | ||
t.Run("URL", func(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
var c *FleetlockClient | ||
assert.Empty(c.GetURL(), "Should not panic when reading URL from nil pointer") | ||
|
||
c = &FleetlockClient{} | ||
|
||
assert.NoError(c.SetURL("https://fleetlock.example.com"), "Should set URL without error") | ||
assert.Equal("https://fleetlock.example.com", c.GetURL(), "URL should match") | ||
|
||
assert.NoError(c.SetURL("https://fleetlock.example.com/"), "Should set URL without trailing slash") | ||
assert.Equal("https://fleetlock.example.com", c.GetURL(), "URL should not have trailing /") | ||
|
||
assert.Error(c.SetURL(""), "Should not accept empty URL") | ||
}) | ||
t.Run("Group", func(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
var c *FleetlockClient | ||
assert.Empty(c.GetGroup(), "Should not panic when reading group from nil pointer") | ||
|
||
c = &FleetlockClient{} | ||
|
||
assert.NoError(c.SetGroup("default"), "Should set group without error") | ||
assert.Equal("default", c.GetGroup(), "group should match") | ||
|
||
assert.Error(c.SetGroup(""), "Should not accept empty group") | ||
}) | ||
} | ||
|
||
func NewFakeServer(t *testing.T, statusCode int, path string) (*FleetlockClient, *httptest.Server) { | ||
assert := assert.New(t) | ||
|
||
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { | ||
assert.Equal(path, req.URL.String(), "Request use the correct request URL") | ||
assert.Equal(http.MethodPost, req.Method, "Should be POST request") | ||
assert.Equal("true", strings.ToLower(req.Header.Get("fleet-lock-protocol")), "fleet-lock-protocol header should be set") | ||
|
||
params, err := ParseRequest(req.Body) | ||
assert.NoError(err, "Request should have the correct format") | ||
assert.Equal("testGroup", params.Client.Group, "Should have Group set") | ||
assert.Equal("testID", params.Client.ID, "Should have ID set") | ||
|
||
rw.WriteHeader(statusCode) | ||
b, err := json.MarshalIndent(FleetLockResponse{ | ||
Kind: "ok", | ||
Value: "Success", | ||
}, "", " ") | ||
if !assert.NoError(err, "Error in fake server: failed to prepare response") { | ||
return | ||
} | ||
|
||
_, err = rw.Write(b) | ||
assert.NoError(err, "Error in fake server: failed to send response") | ||
})) | ||
c := &FleetlockClient{ | ||
url: srv.URL, | ||
group: "testGroup", | ||
appID: "testID", | ||
} | ||
return c, srv | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package client | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestGetZincateAppID(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
id, err := GetZincateAppID() | ||
assert.NoError(err, "Should succeed") | ||
assert.NotEmpty(id, "Should return id") | ||
} | ||
|
||
func TestTrimTrailingSlash(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
assert.Equal("https://fleetlock.example.com", TrimTrailingSlash("https://fleetlock.example.com"), "Should not change URL") | ||
assert.Equal("https://fleetlock.example.com", TrimTrailingSlash("https://fleetlock.example.com/"), "Should remove trailing /") | ||
} |