Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
5.9 KiB
288 lines
5.9 KiB
package couchbase |
|
|
|
import ( |
|
"bytes" |
|
"encoding/json" |
|
"fmt" |
|
"github.com/couchbase/goutils/logging" |
|
"io/ioutil" |
|
"net/http" |
|
) |
|
|
|
// ViewDefinition represents a single view within a design document. |
|
type ViewDefinition struct { |
|
Map string `json:"map"` |
|
Reduce string `json:"reduce,omitempty"` |
|
} |
|
|
|
// DDoc is the document body of a design document specifying a view. |
|
type DDoc struct { |
|
Language string `json:"language,omitempty"` |
|
Views map[string]ViewDefinition `json:"views"` |
|
} |
|
|
|
// DDocsResult represents the result from listing the design |
|
// documents. |
|
type DDocsResult struct { |
|
Rows []struct { |
|
DDoc struct { |
|
Meta map[string]interface{} |
|
JSON DDoc |
|
} `json:"doc"` |
|
} `json:"rows"` |
|
} |
|
|
|
// GetDDocs lists all design documents |
|
func (b *Bucket) GetDDocs() (DDocsResult, error) { |
|
var ddocsResult DDocsResult |
|
b.RLock() |
|
pool := b.pool |
|
uri := b.DDocs.URI |
|
b.RUnlock() |
|
|
|
// MB-23555 ephemeral buckets have no ddocs |
|
if uri == "" { |
|
return DDocsResult{}, nil |
|
} |
|
|
|
err := pool.client.parseURLResponse(uri, &ddocsResult) |
|
if err != nil { |
|
return DDocsResult{}, err |
|
} |
|
return ddocsResult, nil |
|
} |
|
|
|
func (b *Bucket) GetDDocWithRetry(docname string, into interface{}) error { |
|
ddocURI := fmt.Sprintf("/%s/_design/%s", b.GetName(), docname) |
|
err := b.parseAPIResponse(ddocURI, &into) |
|
if err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
func (b *Bucket) GetDDocsWithRetry() (DDocsResult, error) { |
|
var ddocsResult DDocsResult |
|
b.RLock() |
|
uri := b.DDocs.URI |
|
b.RUnlock() |
|
|
|
// MB-23555 ephemeral buckets have no ddocs |
|
if uri == "" { |
|
return DDocsResult{}, nil |
|
} |
|
|
|
err := b.parseURLResponse(uri, &ddocsResult) |
|
if err != nil { |
|
return DDocsResult{}, err |
|
} |
|
return ddocsResult, nil |
|
} |
|
|
|
func (b *Bucket) ddocURL(docname string) (string, error) { |
|
u, err := b.randomBaseURL() |
|
if err != nil { |
|
return "", err |
|
} |
|
u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname) |
|
return u.String(), nil |
|
} |
|
|
|
func (b *Bucket) ddocURLNext(nodeId int, docname string) (string, int, error) { |
|
u, selected, err := b.randomNextURL(nodeId) |
|
if err != nil { |
|
return "", -1, err |
|
} |
|
u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname) |
|
return u.String(), selected, nil |
|
} |
|
|
|
const ABS_MAX_RETRIES = 10 |
|
const ABS_MIN_RETRIES = 3 |
|
|
|
func (b *Bucket) getMaxRetries() (int, error) { |
|
|
|
maxRetries := len(b.Nodes()) |
|
|
|
if maxRetries == 0 { |
|
return 0, fmt.Errorf("No available Couch rest URLs") |
|
} |
|
|
|
if maxRetries > ABS_MAX_RETRIES { |
|
maxRetries = ABS_MAX_RETRIES |
|
} else if maxRetries < ABS_MIN_RETRIES { |
|
maxRetries = ABS_MIN_RETRIES |
|
} |
|
|
|
return maxRetries, nil |
|
} |
|
|
|
// PutDDoc installs a design document. |
|
func (b *Bucket) PutDDoc(docname string, value interface{}) error { |
|
|
|
var Err error |
|
|
|
maxRetries, err := b.getMaxRetries() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
lastNode := START_NODE_ID |
|
|
|
for retryCount := 0; retryCount < maxRetries; retryCount++ { |
|
|
|
Err = nil |
|
|
|
ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
lastNode = selectedNode |
|
|
|
logging.Infof(" Trying with selected node %d", selectedNode) |
|
j, err := json.Marshal(value) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
req, err := http.NewRequest("PUT", ddocU, bytes.NewReader(j)) |
|
if err != nil { |
|
return err |
|
} |
|
req.Header.Set("Content-Type", "application/json") |
|
err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */)) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
res, err := doHTTPRequest(req) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if res.StatusCode != 201 { |
|
body, _ := ioutil.ReadAll(res.Body) |
|
Err = fmt.Errorf("error installing view: %v / %s", |
|
res.Status, body) |
|
logging.Errorf(" Error in PutDDOC %v. Retrying...", Err) |
|
res.Body.Close() |
|
b.Refresh() |
|
continue |
|
} |
|
|
|
res.Body.Close() |
|
break |
|
} |
|
|
|
return Err |
|
} |
|
|
|
// GetDDoc retrieves a specific a design doc. |
|
func (b *Bucket) GetDDoc(docname string, into interface{}) error { |
|
var Err error |
|
var res *http.Response |
|
|
|
maxRetries, err := b.getMaxRetries() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
lastNode := START_NODE_ID |
|
for retryCount := 0; retryCount < maxRetries; retryCount++ { |
|
|
|
Err = nil |
|
ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
lastNode = selectedNode |
|
logging.Infof(" Trying with selected node %d", selectedNode) |
|
|
|
req, err := http.NewRequest("GET", ddocU, nil) |
|
if err != nil { |
|
return err |
|
} |
|
req.Header.Set("Content-Type", "application/json") |
|
err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */)) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
res, err = doHTTPRequest(req) |
|
if err != nil { |
|
return err |
|
} |
|
if res.StatusCode != 200 { |
|
body, _ := ioutil.ReadAll(res.Body) |
|
Err = fmt.Errorf("error reading view: %v / %s", |
|
res.Status, body) |
|
logging.Errorf(" Error in GetDDOC %v Retrying...", Err) |
|
b.Refresh() |
|
res.Body.Close() |
|
continue |
|
} |
|
defer res.Body.Close() |
|
break |
|
} |
|
|
|
if Err != nil { |
|
return Err |
|
} |
|
|
|
d := json.NewDecoder(res.Body) |
|
return d.Decode(into) |
|
} |
|
|
|
// DeleteDDoc removes a design document. |
|
func (b *Bucket) DeleteDDoc(docname string) error { |
|
|
|
var Err error |
|
|
|
maxRetries, err := b.getMaxRetries() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
lastNode := START_NODE_ID |
|
|
|
for retryCount := 0; retryCount < maxRetries; retryCount++ { |
|
|
|
Err = nil |
|
ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
lastNode = selectedNode |
|
logging.Infof(" Trying with selected node %d", selectedNode) |
|
|
|
req, err := http.NewRequest("DELETE", ddocU, nil) |
|
if err != nil { |
|
return err |
|
} |
|
req.Header.Set("Content-Type", "application/json") |
|
err = maybeAddAuth(req, b.authHandler(false /* bucket not already locked */)) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
res, err := doHTTPRequest(req) |
|
if err != nil { |
|
return err |
|
} |
|
if res.StatusCode != 200 { |
|
body, _ := ioutil.ReadAll(res.Body) |
|
Err = fmt.Errorf("error deleting view : %v / %s", res.Status, body) |
|
logging.Errorf(" Error in DeleteDDOC %v. Retrying ... ", Err) |
|
b.Refresh() |
|
res.Body.Close() |
|
continue |
|
} |
|
|
|
res.Body.Close() |
|
break |
|
} |
|
return Err |
|
}
|
|
|