New Upstream Release - golang-github-clusterhq-flocker-go
Ready changes
Summary
Merged new upstream version: 0.0~git20160920.2b8b725 (was: 0.0~git20160209.0.1c0a791).
Resulting package
Built on 2022-11-10T06:45 (took 5m48s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-clusterhq-flocker-go-dev
Lintian Result
- golang-github-clusterhq-flocker-go-dev_0.0~git20160920.2b8b725-1~jan+nur1_all.deb
- golang-github-clusterhq-flocker-go_0.0~git20160920.2b8b725-1~jan+nur1.dsc
- golang-github-clusterhq-flocker-go_0.0~git20160920.2b8b725-1~jan+nur1_amd64.buildinfo
- golang-github-clusterhq-flocker-go_0.0~git20160920.2b8b725-1~jan+nur1_amd64.changes
Diff
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000..81b56c8
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,16 @@
+machine:
+ timezone:
+ America/Los_Angeles
+
+# Output the test output to circle.
+test:
+ pre:
+ - go get -u github.com/jstemmer/go-junit-report
+ override:
+ - go test -coverprofile=coverage.out -v -race ./... > test.out
+ - cat test.out | go-junit-report > report.xml
+ - go tool cover -func=coverage.out
+ post:
+ - mv test.out $CIRCLE_ARTIFACTS/
+ - mv report.xml $CIRCLE_TEST_REPORTS/
+
diff --git a/client.go b/client.go
index d379a03..7cdd710 100644
--- a/client.go
+++ b/client.go
@@ -34,12 +34,15 @@ var (
// Clientable exposes the needed methods to implement your own Flocker Client.
type Clientable interface {
- CreateDataset(metaName string) (*DatasetState, error)
+ CreateDataset(options *CreateDatasetOptions) (*DatasetState, error)
+ DeleteDataset(datasetID string) error
GetDatasetState(datasetID string) (*DatasetState, error)
GetDatasetID(metaName string) (datasetID string, err error)
GetPrimaryUUID() (primaryUUID string, err error)
+ ListNodes() (nodes []NodeState, err error)
+
UpdatePrimaryForDataset(primaryUUID, datasetID string) (*DatasetState, error)
}
@@ -57,6 +60,8 @@ type Client struct {
maximumSize json.Number
}
+var _ Clientable = &Client{}
+
// NewClient creates a wrapper over http.Client to communicate with the flocker control service.
func NewClient(host string, port int, clientIP string, caCertPath, keyPath, certPath string) (*Client, error) {
client, err := newTLSClient(caCertPath, keyPath, certPath)
@@ -110,6 +115,11 @@ func (c Client) post(url string, payload interface{}) (*http.Response, error) {
return c.request("POST", url, payload)
}
+// delete performs a delete request with the indicated payload
+func (c Client) delete(url string, payload interface{}) (*http.Response, error) {
+ return c.request("DELETE", url, payload)
+}
+
// get performs a get request
func (c Client) get(url string) (*http.Response, error) {
return c.request("GET", url, nil)
@@ -128,6 +138,13 @@ type configurationPayload struct {
Metadata metadataPayload `json:"metadata,omitempty"`
}
+type CreateDatasetOptions struct {
+ Primary string `json:"primary"`
+ DatasetID string `json:"dataset_id,omitempty"`
+ MaximumSize int64 `json:"maximum_size,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
type metadataPayload struct {
Name string `json:"name,omitempty"`
}
@@ -143,7 +160,7 @@ type datasetStatePayload struct {
*DatasetState
}
-type nodeStatePayload struct {
+type NodeState struct {
UUID string `json:"uuid"`
Host string `json:"host"`
}
@@ -163,25 +180,54 @@ func (c Client) findIDInConfigurationsPayload(body io.ReadCloser, name string) (
return "", err
}
+// ListNodes returns a list of dataset agent nodes from Flocker Control Service
+func (c *Client) ListNodes() (nodes []NodeState, err error) {
+ resp, err := c.get(c.getURL("state/nodes"))
+ if err != nil {
+ return []NodeState{}, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode >= 300 {
+ return []NodeState{}, fmt.Errorf("Expected: {1,2}xx listing nodes, got: %d", resp.StatusCode)
+ }
+
+ err = json.NewDecoder(resp.Body).Decode(&nodes)
+ if err != nil {
+ return []NodeState{}, err
+ }
+ return nodes, err
+}
+
// GetPrimaryUUID returns the UUID of the primary Flocker Control Service for
// the given host.
func (c Client) GetPrimaryUUID() (uuid string, err error) {
- resp, err := c.get(c.getURL("state/nodes"))
+ states, err := c.ListNodes()
if err != nil {
return "", err
}
- defer resp.Body.Close()
- var states []nodeStatePayload
- if err = json.NewDecoder(resp.Body).Decode(&states); err == nil {
- for _, s := range states {
- if s.Host == c.clientIP {
- return s.UUID, nil
- }
+ for _, s := range states {
+ if s.Host == c.clientIP {
+ return s.UUID, nil
}
- return "", errStateNotFound
}
- return "", err
+ return "", fmt.Errorf("No node found with IP '%s', available nodes %+v", c.clientIP, states)
+}
+
+// DeleteDataset performs a delete request to the given datasetID
+func (c *Client) DeleteDataset(datasetID string) error {
+ url := c.getURL(fmt.Sprintf("configuration/datasets/%s", datasetID))
+ resp, err := c.delete(url, nil)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode >= 300 {
+ return fmt.Errorf("Expected: {1,2}xx deleting the dataset %s, got: %d", datasetID, resp.StatusCode)
+ }
+
+ return nil
}
// GetDatasetState performs a get request to get the state of the given datasetID, if
@@ -213,34 +259,30 @@ returns the dataset id.
This process is a little bit complex but follows this flow:
1. Find the Flocker Control Service UUID
-2. Try to create the dataset
-3. If it already exists an error is returned
-4. If it didn't previously exist, wait for it to be ready
+2. If it already exists an error is returned
+3. If it didn't previously exist, wait for it to be ready
*/
-func (c Client) CreateDataset(metaName string) (*DatasetState, error) {
+func (c *Client) CreateDataset(options *CreateDatasetOptions) (datasetState *DatasetState, err error) {
// 1) Find the primary Flocker UUID
// Note: it could be cached, but doing this query we health check it
- primary, err := c.GetPrimaryUUID()
- if err != nil {
- return nil, err
+ if options.Primary == "" {
+ options.Primary, err = c.GetPrimaryUUID()
+ if err != nil {
+ return nil, err
+ }
}
- // 2) Try to create the dataset in the given Primary
- payload := configurationPayload{
- Primary: primary,
- MaximumSize: json.Number(c.maximumSize),
- Metadata: metadataPayload{
- Name: metaName,
- },
+ if options.MaximumSize == 0 {
+ options.MaximumSize, _ = c.maximumSize.Int64()
}
- resp, err := c.post(c.getURL("configuration/datasets"), payload)
+ resp, err := c.post(c.getURL("configuration/datasets"), options)
if err != nil {
return nil, err
}
defer resp.Body.Close()
- // 3) Return if the dataset was previously created
+ // 2) Return if the dataset was previously created
if resp.StatusCode == http.StatusConflict {
return nil, errVolumeAlreadyExists
}
@@ -254,21 +296,31 @@ func (c Client) CreateDataset(metaName string) (*DatasetState, error) {
return nil, err
}
- // 4) Wait until the dataset is ready for usage. In case it never gets
+ // 3) Wait until the dataset is ready for usage. In case it never gets
// ready there is a timeoutChan that will return an error
timeoutChan := time.NewTimer(timeoutWaitingForVolume).C
tickChan := time.NewTicker(tickerWaitingForVolume).C
for {
- if s, err := c.GetDatasetState(p.DatasetID); err == nil {
+ var strErrDel string
+ s, err := c.GetDatasetState(p.DatasetID)
+ if err == nil {
return s, nil
} else if err != errStateNotFound {
- return nil, err
+ errDel := c.DeleteDataset(p.DatasetID)
+ if errDel != nil {
+ strErrDel = fmt.Sprintf(", deletion of dataset failed with %s", errDel)
+ }
+ return nil, fmt.Errorf("Flocker API error during dataset creation (datasetID %s): %s%s", p.DatasetID, err, strErrDel)
}
select {
case <-timeoutChan:
- return nil, err
+ errDel := c.DeleteDataset(p.DatasetID)
+ if errDel != nil {
+ strErrDel = fmt.Sprintf(", deletion of dataset failed with %s", errDel)
+ }
+ return nil, fmt.Errorf("Flocker API timeout during dataset creation (datasetID %s): %s%s", p.DatasetID, err, strErrDel)
case <-tickChan:
break
}
diff --git a/client_test.go b/client_test.go
index 4ce9516..9077ad6 100644
--- a/client_test.go
+++ b/client_test.go
@@ -102,26 +102,75 @@ func TestFindIDInConfigurationsPayload(t *testing.T) {
assert.Error(err)
}
-func TestFindPrimaryUUID(t *testing.T) {
- const expectedPrimary = "primary-uuid"
- assert := assert.New(t)
-
- var (
- mockedHost = "127.0.0.1"
- mockedPrimary = expectedPrimary
- )
+func mockGetStateNodes(assert *assert.Assertions, data []byte) *Client {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal("GET", r.Method)
assert.Equal("/v1/state/nodes", r.URL.Path)
- w.Write([]byte(fmt.Sprintf(`[{"host": "%s", "uuid": "%s"}]`, mockedHost, mockedPrimary)))
+ w.Write(data)
}))
host, port, err := getHostAndPortFromTestServer(ts)
assert.NoError(err)
c := newFlockerTestClient(host, port)
+
+ return c
+}
+
+func TestListNodes(t *testing.T) {
+ assert := assert.New(t)
+
+ var (
+ mockedHost1 = "127.0.0.1"
+ mockedPrimary1 = "uuid1"
+ mockedHost2 = "127.0.0.2"
+ mockedPrimary2 = "uuid2"
+ )
+
+ c := mockGetStateNodes(
+ assert,
+ []byte(fmt.Sprintf(
+ `[{"host": "%s", "uuid": "%s"},{"host": "%s", "uuid": "%s"}]`,
+ mockedHost1,
+ mockedPrimary1,
+ mockedHost2,
+ mockedPrimary2,
+ )),
+ )
+
+ nodes, err := c.ListNodes()
+ assert.NoError(err)
+ assert.Equal(2, len(nodes))
+ assert.Equal(mockedHost1, nodes[0].Host)
+ assert.Equal(mockedPrimary1, nodes[0].UUID)
+ assert.Equal(mockedHost2, nodes[1].Host)
+ assert.Equal(mockedPrimary2, nodes[1].UUID)
+
+ c = mockGetStateNodes(
+ assert,
+ []byte(`[]`),
+ )
+
+ nodes, err = c.ListNodes()
assert.NoError(err)
+ assert.Equal(0, len(nodes))
+
+}
+
+func TestFindPrimaryUUID(t *testing.T) {
+ const expectedPrimary = "primary-uuid"
+ assert := assert.New(t)
+
+ var (
+ mockedHost = "127.0.0.1"
+ mockedPrimary = expectedPrimary
+ )
+
+ c := mockGetStateNodes(
+ assert,
+ []byte(fmt.Sprintf(`[{"host": "%s", "uuid": "%s"}]`, mockedHost, mockedPrimary)),
+ )
mockedPrimary = expectedPrimary
primary, err := c.GetPrimaryUUID()
@@ -130,7 +179,9 @@ func TestFindPrimaryUUID(t *testing.T) {
c.clientIP = "not.found"
_, err = c.GetPrimaryUUID()
- assert.Equal(errStateNotFound, err)
+ assert.Error(err, "An error was expected")
+ assert.True(strings.Contains(err.Error(), "No node found"), "returns right error")
+ assert.True(strings.Contains(err.Error(), "not.found"), "returns used client IP")
}
func TestGetURL(t *testing.T) {
@@ -163,6 +214,51 @@ func getHostAndPortFromTestServer(ts *httptest.Server) (string, int, error) {
return hostSplits[0], port, nil
}
+func TestDeleteDatasetExisting(t *testing.T) {
+ assert := assert.New(t)
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal("DELETE", r.Method)
+ assert.Equal("/v1/configuration/datasets/uuid1", r.URL.Path)
+ w.Header().Set("Content-Type", "application/json")
+ b, err := json.Marshal(configurationPayload{
+ DatasetID: "uuid1",
+ Primary: "primary1",
+ Deleted: true,
+ })
+ assert.NoError(err)
+ w.Write(b)
+ },
+ ))
+
+ host, port, err := getHostAndPortFromTestServer(ts)
+ assert.NoError(err)
+
+ c := newFlockerTestClient(host, port)
+
+ err = c.DeleteDataset("uuid1")
+ assert.NoError(err)
+}
+
+func TestDeleteDatasetNotExisting(t *testing.T) {
+ assert := assert.New(t)
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal("DELETE", r.Method)
+ assert.Equal("/v1/configuration/datasets/uuid2", r.URL.Path)
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+ w.Write([]byte(`{"description": "Dataset not found."}`))
+ },
+ ))
+
+ host, port, err := getHostAndPortFromTestServer(ts)
+ assert.NoError(err)
+
+ c := newFlockerTestClient(host, port)
+
+ err = c.DeleteDataset("uuid2")
+ assert.Error(err)
+}
+
func TestHappyPathCreateDatasetFromNonExistent(t *testing.T) {
const (
expectedDatasetName = "dir"
@@ -211,11 +307,15 @@ func TestHappyPathCreateDatasetFromNonExistent(t *testing.T) {
assert.NoError(err)
c := newFlockerTestClient(host, port)
- assert.NoError(err)
tickerWaitingForVolume = 1 * time.Millisecond // TODO: this is overriding globally
- s, err := c.CreateDataset(expectedDatasetName)
+ s, err := c.CreateDataset(&CreateDatasetOptions{
+ Metadata: map[string]string{
+ "name": expectedDatasetName,
+ },
+ })
+
assert.NoError(err)
assert.Equal(expectedPath, s.Path)
}
@@ -247,12 +347,138 @@ func TestCreateDatasetThatAlreadyExists(t *testing.T) {
assert.NoError(err)
c := newFlockerTestClient(host, port)
- assert.NoError(err)
- _, err = c.CreateDataset(datasetName)
+ _, err = c.CreateDataset(&CreateDatasetOptions{
+ Metadata: map[string]string{
+ "name": datasetName,
+ },
+ })
assert.Equal(errVolumeAlreadyExists, err)
}
+func TestCreateDatasetThatTimesoutServerSide(t *testing.T) {
+ const (
+ datasetName = "dir"
+ expectedPrimary = "A-B-C-D"
+ expectedDatasetID = "uuid-1"
+ )
+
+ // reduce timeouts for testing
+ tickerWaitingForVolume = 1 * time.Microsecond
+ timeoutWaitingForVolume = 1 * time.Millisecond
+
+ assert := assert.New(t)
+ var numCalls int
+ var deleted bool
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ numCalls++
+ switch numCalls {
+ case 1:
+ assert.Equal("GET", r.Method)
+ assert.Equal("/v1/state/nodes", r.URL.Path)
+ w.Write([]byte(fmt.Sprintf(`[{"host": "127.0.0.1", "uuid": "%s"}]`, expectedPrimary)))
+ case 2:
+ assert.Equal("POST", r.Method)
+ assert.Equal("/v1/configuration/datasets", r.URL.Path)
+ w.Write([]byte(fmt.Sprintf(`{"dataset_id": "%s"}`, expectedDatasetID)))
+ default:
+ if r.Method == "GET" {
+ assert.Equal("/v1/state/datasets", r.URL.Path)
+ w.Write([]byte(`[]`))
+ } else if r.Method == "DELETE" {
+ assert.Equal("/v1/configuration/datasets/uuid-1", r.URL.Path)
+ w.Header().Set("Content-Type", "application/json")
+ b, err := json.Marshal(configurationPayload{
+ DatasetID: expectedDatasetID,
+ Primary: expectedPrimary,
+ Deleted: true,
+ })
+ assert.NoError(err)
+ w.Write(b)
+ deleted = true
+ } else {
+ t.Errorf("Received unexpected call '%s' to '%s'", r.Method, r.URL.Path)
+ }
+ }
+ }))
+
+ host, port, err := getHostAndPortFromTestServer(ts)
+ assert.NoError(err)
+
+ c := newFlockerTestClient(host, port)
+
+ _, err = c.CreateDataset(&CreateDatasetOptions{
+ Metadata: map[string]string{
+ "name": datasetName,
+ "primary": expectedPrimary,
+ },
+ })
+
+ assert.True(numCalls > 3, fmt.Sprintf("Not enough retries getting dataset state: %d", numCalls))
+ assert.True(deleted, "Failed dataset was not cleaned up afterwards")
+ assert.Equal("Flocker API timeout during dataset creation (datasetID uuid-1): State not found by Dataset ID", err.Error())
+}
+
+func TestCreateDatasetThatTimesoutServerSideFailedDelete(t *testing.T) {
+ const (
+ datasetName = "dir"
+ expectedPrimary = "A-B-C-D"
+ expectedDatasetID = "uuid-1"
+ )
+
+ // reduce timeouts for testing
+ tickerWaitingForVolume = 1 * time.Microsecond
+ timeoutWaitingForVolume = 1 * time.Millisecond
+
+ assert := assert.New(t)
+ var numCalls int
+ var deleted bool
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ numCalls++
+ switch numCalls {
+ case 1:
+ assert.Equal("GET", r.Method)
+ assert.Equal("/v1/state/nodes", r.URL.Path)
+ w.Write([]byte(fmt.Sprintf(`[{"host": "127.0.0.1", "uuid": "%s"}]`, expectedPrimary)))
+ case 2:
+ assert.Equal("POST", r.Method)
+ assert.Equal("/v1/configuration/datasets", r.URL.Path)
+ w.Write([]byte(fmt.Sprintf(`{"dataset_id": "%s"}`, expectedDatasetID)))
+ default:
+ if r.Method == "GET" {
+ assert.Equal("/v1/state/datasets", r.URL.Path)
+ w.Write([]byte(`[]`))
+ } else if r.Method == "DELETE" {
+ assert.Equal("/v1/configuration/datasets/uuid-1", r.URL.Path)
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(500)
+ w.Write([]byte(`unexpected error`))
+ deleted = true
+ } else {
+ t.Errorf("Received unexpected call '%s' to '%s'", r.Method, r.URL.Path)
+ }
+ }
+ }))
+
+ host, port, err := getHostAndPortFromTestServer(ts)
+ assert.NoError(err)
+
+ c := newFlockerTestClient(host, port)
+
+ _, err = c.CreateDataset(&CreateDatasetOptions{
+ Metadata: map[string]string{
+ "name": datasetName,
+ "primary": expectedPrimary,
+ },
+ })
+
+ assert.True(numCalls > 3, fmt.Sprintf("Not enough retries getting dataset state: %d", numCalls))
+ assert.True(deleted, "Failed dataset was not cleaned up afterwards")
+ assert.Equal("Flocker API timeout during dataset creation (datasetID uuid-1): State not found by Dataset ID, deletion of dataset failed with Expected: {1,2}xx deleting the dataset uuid-1, got: 500", err.Error())
+}
+
func TestUpdatePrimaryForDataset(t *testing.T) {
const (
dir = "dir"
@@ -280,7 +506,6 @@ func TestUpdatePrimaryForDataset(t *testing.T) {
assert.NoError(err)
c := newFlockerTestClient(host, port)
- assert.NoError(err)
s, err := c.UpdatePrimaryForDataset(expectedPrimary, expectedDatasetID)
assert.NoError(err)
@@ -288,10 +513,6 @@ func TestUpdatePrimaryForDataset(t *testing.T) {
assert.NotEqual("", s.Path)
}
-func TestInterfaceIsImplemented(t *testing.T) {
- assert.Implements(t, (*Clientable)(nil), Client{})
-}
-
func newFlockerTestClient(host string, port int) *Client {
return &Client{
Client: &http.Client{},
diff --git a/debian/changelog b/debian/changelog
index 28ee2e7..2071ee1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-clusterhq-flocker-go (0.0~git20160920.2b8b725-1) UNRELEASED; urgency=low
+
+ * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk> Thu, 10 Nov 2022 06:39:59 -0000
+
golang-github-clusterhq-flocker-go (0.0~git20160209.0.1c0a791-3) unstable; urgency=medium
* Team upload.
diff --git a/debian/patches/01-fix-test-32bit-arch.patch b/debian/patches/01-fix-test-32bit-arch.patch
index d9064a1..aa9d9cf 100644
--- a/debian/patches/01-fix-test-32bit-arch.patch
+++ b/debian/patches/01-fix-test-32bit-arch.patch
@@ -5,9 +5,11 @@ Forwarded: https://github.com/ClusterHQ/flocker-go/pull/14
Author: Aloïs Micard <creekorful@debian.org>
Last-Update: 2021-09-26
---- a/client_test.go
-+++ b/client_test.go
-@@ -19,9 +19,9 @@
+Index: golang-github-clusterhq-flocker-go.git/client_test.go
+===================================================================
+--- golang-github-clusterhq-flocker-go.git.orig/client_test.go
++++ golang-github-clusterhq-flocker-go.git/client_test.go
+@@ -19,9 +19,9 @@ import (
func TestMaximumSizeIs1024Multiple(t *testing.T) {
assert := assert.New(t)
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files