Skip to content

Commit 14fea86

Browse files
authored
Unified api and add session invalidation handling
* Move to backward compatible 3.x API * If you're using Quobyte 2.x refer to 2.x management API definitions, not supported fields by 2.x are ignored by API server. * Add session cookies to improve API performance * Currently, each request is authenticated with username and password. The authentication is expensive and could block requests if there are many concurrent requests. With session cookies solves this problem.
1 parent 582c5e6 commit 14fea86

20 files changed

Lines changed: 5469 additions & 9711 deletions

README.md

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,59 @@
1-
# Quobyte API
1+
# Quobyte API Clients
22

3-
* User documentation
4-
* [For 2.x API](./v2/README.md)
5-
* [For 3.x API](./v3/README.md)
6-
* Developer documentation (./docs/)
3+
Get the Quobyte api client
4+
5+
```bash
6+
go get github.com/quobyte/api
7+
```
8+
9+
## Usage
10+
11+
`Note:` Create below example in separate project outside of the api repository to avoid circular `go mod` dependencies.
12+
13+
```go
14+
package main
15+
16+
import (
17+
"log"
18+
quobyte_api "github.com/quobyte/api/quobyte"
19+
)
20+
21+
func main() {
22+
url := flag.String("url", "", "URL of Quobyte API")
23+
username := flag.String("username", "", "username")
24+
password := flag.String("password", "", "password")
25+
flag.Parse()
26+
27+
if *url == "" || *username == "" || *password == "" {
28+
flag.PrintDefaults()
29+
os.Exit(1)
30+
}
31+
32+
client := quobyte_api.NewQuobyteClient(*url, *username, *password)
33+
client.SetAPIRetryPolicy(quobyte_api.RetryInfinitely) // Default quobyte_api.RetryInteractive
34+
req := &quobyte_api.CreateVolumeRequest{
35+
Name: "MyVolume",
36+
TenantId: "32edb36d-badc-affe-b44a-4ab749af4d9a",
37+
RootUserId: "root",
38+
RootGroupId: "root",
39+
ConfigurationName: "BASE",
40+
Label: []*quobyte_api.Label{
41+
{Name: "label1", Value: "value1"},
42+
{Name: "label2", Value: "value2"},
43+
},
44+
}
45+
46+
response, err := client.CreateVolume(req)
47+
if err != nil {
48+
log.Fatalf("Error: %v", err)
49+
}
50+
51+
capactiy := int64(1024 * 1024 * 1024)
52+
err = client.SetVolumeQuota(response.VolumeUuid, capactiy)
53+
if err != nil {
54+
log.Fatalf("Error: %v", err)
55+
}
56+
57+
log.Printf("%s", response.VolumeUuid)
58+
}
59+
```

docs/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Developer notes
2+
3+
## Releasing new version
4+
5+
* `go.mod` files must be present at the root level of the project
6+
* Each major release beyond V1 (such =v2[+].a.b) must provide unique import path such as `github.com/quobyte/api/vX`
7+
* To get around this issue, we always use v1.x.x (**NEVER** make v2 release)
8+
* Further, each `*.go` file must have a `package XYZ` statement as the first line and must be placed into `XZY`
9+
directory. (It seems go mod ignores .go file that is not placed in declared package directory!!)
10+
* For local testing of edited module,
11+
* Create a standalone project with the `testing or main.go` and `go mod init` inside the project root.
12+
The `go mod init` fetches the depedencies required by code.
13+
* Replace Quobyte API with updated API `go mod edit -replace github.com/quobyte/api=</path/to/local/quobyte/api>`
14+
* Publishing change must always have highest minor version of all the published tags (even if the tag is deleted,
15+
the new version must have the higher version than deleted tag).
16+
17+
Note: go mod updates dependency to the highest minor version available. Even if the version is deleted from the tags,
18+
go-git gets it from the deleted references (Uhhh!!). The only way out is to create a new higher minor
19+
version with the changes.

docs/Release-instructions.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/quobyte/api
2+
3+
go 1.14

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

v3/quobyte.go renamed to quobyte/quobyte.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ package quobyte
33
import (
44
"log"
55
"net/http"
6-
"regexp"
76
"net/http/cookiejar"
7+
"net/url"
8+
"regexp"
89
)
910

1011
// retry policy codes
@@ -17,11 +18,15 @@ var UUIDValidator = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0
1718

1819
type QuobyteClient struct {
1920
client *http.Client
20-
url string
21+
url *url.URL
2122
username string
2223
password string
2324
apiRetryPolicy string
24-
hasCookies bool
25+
// hasCookies bool
26+
}
27+
28+
func (client *QuobyteClient) hasCookies() (bool, error) {
29+
return client.client.Jar != nil && len(client.client.Jar.Cookies(client.url)) > 0, nil
2530
}
2631

2732
func (client *QuobyteClient) SetAPIRetryPolicy(retry string) {
@@ -37,20 +42,22 @@ func (client *QuobyteClient) SetTransport(t http.RoundTripper) {
3742
}
3843

3944
// NewQuobyteClient creates a new Quobyte API client
40-
func NewQuobyteClient(url string, username string, password string) *QuobyteClient {
45+
func NewQuobyteClient(urlStr string, username string, password string) *QuobyteClient {
46+
url, err := url.Parse(urlStr)
47+
if err != nil {
48+
log.Fatalf("could not parse url due to %s", err.Error())
49+
}
4150
cookieJar, err := cookiejar.New(nil)
42-
if err == nil {
43-
return &QuobyteClient{
44-
client: &http.Client{Jar: cookieJar},
45-
url: url,
46-
username: username,
47-
password: password,
48-
apiRetryPolicy: RetryInteractive,
49-
hasCookies: false,
50-
}
51-
} else {
51+
if err != nil {
5252
log.Fatalf("could not initialize cookie jar due to %s", err.Error())
5353
}
54+
return &QuobyteClient{
55+
client: &http.Client{Jar: cookieJar},
56+
url: url,
57+
username: username,
58+
password: password,
59+
apiRetryPolicy: RetryInteractive,
60+
}
5461
return nil
5562
}
5663

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -103,39 +103,60 @@ func (client QuobyteClient) sendRequest(method string, request interface{}, resp
103103
if err != nil {
104104
return err
105105
}
106-
req, err := http.NewRequest("POST", client.url, bytes.NewBuffer(message))
106+
req, err := http.NewRequest("POST", client.url.String(), bytes.NewBuffer(message))
107107
if err != nil {
108108
return err
109109
}
110110
req.Header.Set("Content-Type", "application/json")
111111
// If no cookies, serialize requests such that first successful request sets the cookies
112-
if !client.hasCookies {
112+
for {
113113
mux.Lock()
114-
defer mux.Unlock()
115-
req.SetBasicAuth(client.username, client.password)
116-
}
117-
resp, err := client.client.Do(req)
118-
if err != nil {
119-
return err
120-
}
121-
// log.Printf("response %v", resp)
122-
defer resp.Body.Close()
123-
124-
if resp.StatusCode < 200 || resp.StatusCode > 299 {
125-
if resp.StatusCode == 401 {
126-
return errors.New("Unable to authenticate with Quobyte API service")
114+
hasCookies, err := client.hasCookies()
115+
if err != nil {
116+
return err
117+
}
118+
if !hasCookies {
119+
req.SetBasicAuth(client.username, client.password)
120+
// no cookies available, must hold lock until request is completed and
121+
// new cookies are created by server
122+
defer mux.Unlock()
123+
} else {
124+
// let every thread/routine send request using the cookie
125+
mux.Unlock()
127126
}
128-
body, err := ioutil.ReadAll(resp.Body)
127+
resp, err := client.client.Do(req)
129128
if err != nil {
130-
return (err)
129+
return err
131130
}
132-
return fmt.Errorf("JsonRPC failed with error (error code: %d) %s",
133-
resp.StatusCode, string(body))
134-
}
135-
if len(resp.Cookies()) > 0 {
136-
client.hasCookies = true
137-
} else {
138-
client.hasCookies = false
131+
defer resp.Body.Close()
132+
133+
if resp.StatusCode < 200 || resp.StatusCode > 299 {
134+
if resp.StatusCode == 401 {
135+
_, ok := req.Header["Authorization"]
136+
if ok {
137+
return errors.New("Unable to authenticate with Quobyte API service")
138+
}
139+
// Session is not valid anymore (service restart, sesssion invalidated etc)!!
140+
// resend basic auth and get new cookies
141+
// invalidate session cookies
142+
cookieJar := client.client.Jar
143+
if cookieJar != nil {
144+
cookies := cookieJar.Cookies(client.url)
145+
for _, cookie := range cookies {
146+
cookie.MaxAge = -1
147+
}
148+
cookieJar.SetCookies(client.url, cookies)
149+
}
150+
// retry request with authorization header
151+
continue
152+
}
153+
body, err := ioutil.ReadAll(resp.Body)
154+
if err != nil {
155+
return (err)
156+
}
157+
return fmt.Errorf("JsonRPC failed with error (error code: %d) %s",
158+
resp.StatusCode, string(body))
159+
}
160+
return decodeResponse(resp.Body, &response)
139161
}
140-
return decodeResponse(resp.Body, &response)
141162
}

0 commit comments

Comments
 (0)