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

  1. package couchbase
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/couchbase/goutils/logging"
  7. "io/ioutil"
  8. "net/http"
  9. )
  10. // ViewDefinition represents a single view within a design document.
  11. type ViewDefinition struct {
  12. Map string `json:"map"`
  13. Reduce string `json:"reduce,omitempty"`
  14. }
  15. // DDoc is the document body of a design document specifying a view.
  16. type DDoc struct {
  17. Language string `json:"language,omitempty"`
  18. Views map[string]ViewDefinition `json:"views"`
  19. }
  20. // DDocsResult represents the result from listing the design
  21. // documents.
  22. type DDocsResult struct {
  23. Rows []struct {
  24. DDoc struct {
  25. Meta map[string]interface{}
  26. JSON DDoc
  27. } `json:"doc"`
  28. } `json:"rows"`
  29. }
  30. // GetDDocs lists all design documents
  31. func (b *Bucket) GetDDocs() (DDocsResult, error) {
  32. var ddocsResult DDocsResult
  33. b.RLock()
  34. pool := b.pool
  35. uri := b.DDocs.URI
  36. b.RUnlock()
  37. // MB-23555 ephemeral buckets have no ddocs
  38. if uri == "" {
  39. return DDocsResult{}, nil
  40. }
  41. err := pool.client.parseURLResponse(uri, &ddocsResult)
  42. if err != nil {
  43. return DDocsResult{}, err
  44. }
  45. return ddocsResult, nil
  46. }
  47. func (b *Bucket) GetDDocWithRetry(docname string, into interface{}) error {
  48. ddocURI := fmt.Sprintf("/%s/_design/%s", b.GetName(), docname)
  49. err := b.parseAPIResponse(ddocURI, &into)
  50. if err != nil {
  51. return err
  52. }
  53. return nil
  54. }
  55. func (b *Bucket) GetDDocsWithRetry() (DDocsResult, error) {
  56. var ddocsResult DDocsResult
  57. b.RLock()
  58. uri := b.DDocs.URI
  59. b.RUnlock()
  60. // MB-23555 ephemeral buckets have no ddocs
  61. if uri == "" {
  62. return DDocsResult{}, nil
  63. }
  64. err := b.parseURLResponse(uri, &ddocsResult)
  65. if err != nil {
  66. return DDocsResult{}, err
  67. }
  68. return ddocsResult, nil
  69. }
  70. func (b *Bucket) ddocURL(docname string) (string, error) {
  71. u, err := b.randomBaseURL()
  72. if err != nil {
  73. return "", err
  74. }
  75. u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname)
  76. return u.String(), nil
  77. }
  78. func (b *Bucket) ddocURLNext(nodeId int, docname string) (string, int, error) {
  79. u, selected, err := b.randomNextURL(nodeId)
  80. if err != nil {
  81. return "", -1, err
  82. }
  83. u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname)
  84. return u.String(), selected, nil
  85. }
  86. const ABS_MAX_RETRIES = 10
  87. const ABS_MIN_RETRIES = 3
  88. func (b *Bucket) getMaxRetries() (int, error) {
  89. maxRetries := len(b.Nodes())
  90. if maxRetries == 0 {
  91. return 0, fmt.Errorf("No available Couch rest URLs")
  92. }
  93. if maxRetries > ABS_MAX_RETRIES {
  94. maxRetries = ABS_MAX_RETRIES
  95. } else if maxRetries < ABS_MIN_RETRIES {
  96. maxRetries = ABS_MIN_RETRIES
  97. }
  98. return maxRetries, nil
  99. }
  100. // PutDDoc installs a design document.
  101. func (b *Bucket) PutDDoc(docname string, value interface{}) error {
  102. var Err error
  103. maxRetries, err := b.getMaxRetries()
  104. if err != nil {
  105. return err
  106. }
  107. lastNode := START_NODE_ID
  108. for retryCount := 0; retryCount < maxRetries; retryCount++ {
  109. Err = nil
  110. ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname)
  111. if err != nil {
  112. return err
  113. }
  114. lastNode = selectedNode
  115. logging.Infof(" Trying with selected node %d", selectedNode)
  116. j, err := json.Marshal(value)
  117. if err != nil {
  118. return err
  119. }
  120. req, err := http.NewRequest("PUT", ddocU, bytes.NewReader(j))
  121. if err != nil {
  122. return err
  123. }
  124. req.Header.Set("Content-Type", "application/json")
  125. err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */))
  126. if err != nil {
  127. return err
  128. }
  129. res, err := doHTTPRequest(req)
  130. if err != nil {
  131. return err
  132. }
  133. if res.StatusCode != 201 {
  134. body, _ := ioutil.ReadAll(res.Body)
  135. Err = fmt.Errorf("error installing view: %v / %s",
  136. res.Status, body)
  137. logging.Errorf(" Error in PutDDOC %v. Retrying...", Err)
  138. res.Body.Close()
  139. b.Refresh()
  140. continue
  141. }
  142. res.Body.Close()
  143. break
  144. }
  145. return Err
  146. }
  147. // GetDDoc retrieves a specific a design doc.
  148. func (b *Bucket) GetDDoc(docname string, into interface{}) error {
  149. var Err error
  150. var res *http.Response
  151. maxRetries, err := b.getMaxRetries()
  152. if err != nil {
  153. return err
  154. }
  155. lastNode := START_NODE_ID
  156. for retryCount := 0; retryCount < maxRetries; retryCount++ {
  157. Err = nil
  158. ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname)
  159. if err != nil {
  160. return err
  161. }
  162. lastNode = selectedNode
  163. logging.Infof(" Trying with selected node %d", selectedNode)
  164. req, err := http.NewRequest("GET", ddocU, nil)
  165. if err != nil {
  166. return err
  167. }
  168. req.Header.Set("Content-Type", "application/json")
  169. err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */))
  170. if err != nil {
  171. return err
  172. }
  173. res, err = doHTTPRequest(req)
  174. if err != nil {
  175. return err
  176. }
  177. if res.StatusCode != 200 {
  178. body, _ := ioutil.ReadAll(res.Body)
  179. Err = fmt.Errorf("error reading view: %v / %s",
  180. res.Status, body)
  181. logging.Errorf(" Error in GetDDOC %v Retrying...", Err)
  182. b.Refresh()
  183. res.Body.Close()
  184. continue
  185. }
  186. defer res.Body.Close()
  187. break
  188. }
  189. if Err != nil {
  190. return Err
  191. }
  192. d := json.NewDecoder(res.Body)
  193. return d.Decode(into)
  194. }
  195. // DeleteDDoc removes a design document.
  196. func (b *Bucket) DeleteDDoc(docname string) error {
  197. var Err error
  198. maxRetries, err := b.getMaxRetries()
  199. if err != nil {
  200. return err
  201. }
  202. lastNode := START_NODE_ID
  203. for retryCount := 0; retryCount < maxRetries; retryCount++ {
  204. Err = nil
  205. ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname)
  206. if err != nil {
  207. return err
  208. }
  209. lastNode = selectedNode
  210. logging.Infof(" Trying with selected node %d", selectedNode)
  211. req, err := http.NewRequest("DELETE", ddocU, nil)
  212. if err != nil {
  213. return err
  214. }
  215. req.Header.Set("Content-Type", "application/json")
  216. err = maybeAddAuth(req, b.authHandler(false /* bucket not already locked */))
  217. if err != nil {
  218. return err
  219. }
  220. res, err := doHTTPRequest(req)
  221. if err != nil {
  222. return err
  223. }
  224. if res.StatusCode != 200 {
  225. body, _ := ioutil.ReadAll(res.Body)
  226. Err = fmt.Errorf("error deleting view : %v / %s", res.Status, body)
  227. logging.Errorf(" Error in DeleteDDOC %v. Retrying ... ", Err)
  228. b.Refresh()
  229. res.Body.Close()
  230. continue
  231. }
  232. res.Body.Close()
  233. break
  234. }
  235. return Err
  236. }