├── .gitignore ├── CHANGES.md ├── IDEAS.md ├── LICENSE.txt ├── Makefile ├── README.md ├── bucket_scope_tracker.go ├── cbauth.go ├── cfg.go ├── cfg_cb.go ├── cfg_mem.go ├── cfg_metakv.go ├── cfg_metakv_lean.go ├── cfg_metakv_test.go ├── cfg_metakv_util.go ├── cfg_registry.go ├── cfg_simple.go ├── cfg_test.go ├── cmd ├── cbgt-ctl │ ├── main.go │ ├── main_flags.go │ ├── prompt.go │ └── tmp │ │ └── .gitignore ├── dump.go ├── dump_test.go ├── dump_windows.go ├── main_cfg.go ├── main_cfg_test.go ├── main_flags.go ├── main_uuid.go ├── main_uuid_test.go ├── metakv-mock │ └── metakv.go ├── planner.go ├── planner_test.go └── tmp │ └── .gitignore ├── ctl ├── ctl.go └── manager.go ├── defs.go ├── defs_json.go ├── defs_test.go ├── dest.go ├── dest_forwarder.go ├── dest_test.go ├── feed.go ├── feed_cb.go ├── feed_dcp.go ├── feed_dcp_gocbcore.go ├── feed_dcp_gocbcore_observer.go ├── feed_dcp_gocouchbase.go ├── feed_files.go ├── feed_files_test.go ├── feed_nil.go ├── feed_primary.go ├── feed_tap.go ├── feed_test.go ├── go.mod ├── go.sum ├── gocbcore_agents.go ├── gocbcore_utils.go ├── gocouchbase_utils.go ├── hibernate ├── hibernate.go └── progress.go ├── httpclient.go ├── indexdefs_test.go ├── json.go ├── licenses ├── APL2.txt └── BSL-Couchbase.txt ├── manager.go ├── manager_api.go ├── manager_janitor.go ├── manager_planner.go ├── manager_test.go ├── misc.go ├── misc_test.go ├── msg_ring.go ├── msg_ring_test.go ├── pindex.go ├── pindex_consistency.go ├── pindex_impl.go ├── pindex_impl_blackhole.go ├── pindex_test.go ├── rebalance ├── progress.go ├── progress_table.go ├── rebalance.go ├── rebalance_test.go ├── run.go └── tmp │ └── .gitignore ├── rest ├── bindata_assetfs.go ├── monitor │ ├── cluster.go │ ├── cluster_test.go │ ├── monitor.go │ ├── nodes.go │ └── nodes_test.go ├── rest.go ├── rest_create_index.go ├── rest_delete_index.go ├── rest_diag.go ├── rest_index.go ├── rest_log.go ├── rest_manage.go ├── rest_meta.go ├── rest_noop.go ├── rest_source.go ├── rest_test.go ├── rest_util.go ├── static.go ├── static │ ├── css │ │ ├── app.css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── dashboard.css │ │ ├── prism.css │ │ └── rickshaw.min.css │ ├── favicon.ico │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── img │ │ └── cb.png │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── b64.js │ │ ├── controllers.js │ │ ├── directives.js │ │ ├── expvar.js │ │ ├── filters.js │ │ ├── index.js │ │ ├── logs.js │ │ ├── manage.js │ │ ├── monitor.js │ │ ├── node.js │ │ ├── query.js │ │ └── services.js │ ├── lib │ │ ├── angular-bootstrap │ │ │ ├── .bower.json │ │ │ ├── .gitignore │ │ │ ├── .npmignore │ │ │ ├── README.md │ │ │ ├── bower.json │ │ │ ├── index.js │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── ui-bootstrap-csp.css │ │ │ ├── ui-bootstrap-tpls.js │ │ │ ├── ui-bootstrap-tpls.min.js │ │ │ ├── ui-bootstrap.js │ │ │ └── ui-bootstrap.min.js │ │ ├── angular-route │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── angular-route.js │ │ │ ├── angular-route.min.js │ │ │ ├── angular-route.min.js.map │ │ │ ├── bower.json │ │ │ ├── index.js │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ ├── angular-ui-tree │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── dist │ │ │ │ ├── angular-ui-tree.js │ │ │ │ ├── angular-ui-tree.min.css │ │ │ │ └── angular-ui-tree.min.js │ │ ├── angular │ │ │ ├── .bower.json │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── angular-csp.css │ │ │ ├── angular.js │ │ │ ├── angular.min.js │ │ │ ├── angular.min.js.gzip │ │ │ ├── angular.min.js.map │ │ │ ├── bower.json │ │ │ ├── index.js │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ ├── bootstrap │ │ │ ├── bootstrap-lightbox.js │ │ │ ├── bootstrap-lightbox.min.js │ │ │ └── bootstrap.js │ │ ├── d3.v3.js │ │ ├── humanize.min.js │ │ ├── jquery │ │ │ ├── jquery-3.5.1.js │ │ │ ├── jquery-ui.js │ │ │ └── jquery-ui.min.js │ │ ├── jsonpointer.js │ │ ├── prism.js │ │ └── rickshaw.min.js │ ├── modal │ │ ├── backdrop.html │ │ └── window.html │ ├── partials │ │ ├── index │ │ │ ├── index.html │ │ │ ├── list.html │ │ │ ├── new.html │ │ │ ├── query-result-expl.html │ │ │ ├── query-results.html │ │ │ ├── start.html │ │ │ ├── tab-manage.html │ │ │ ├── tab-monitor.html │ │ │ ├── tab-query.html │ │ │ ├── tab-summary.html │ │ │ └── tabs.html │ │ ├── logs.html │ │ ├── manage.html │ │ ├── monitor.html │ │ └── node │ │ │ ├── list.html │ │ │ ├── node.html │ │ │ ├── tab-summary.html │ │ │ └── tabs.html │ └── tabs │ │ ├── tab.html │ │ └── tabset.html ├── static_test.go └── tmp │ └── .gitignore ├── system_event.go ├── task_scatter_gatherer.go ├── tmp └── .gitignore ├── version.go ├── version.md ├── version_test.go ├── work.go └── work_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *.out 3 | *.pprof 4 | *.sublime-* 5 | *.tmp 6 | *~ 7 | .#* 8 | .project 9 | .settings 10 | .DS_Store 11 | /cbgt-* 12 | /data 13 | /metakv-mock 14 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # v0.3.0 2 | 3 | - api: shorter API names to init static/rest router 4 | - api: optional pagesHandler param for InitStaticFileRouter 5 | - api: deprecated/renamed Manager.StartRegister to Register, issue: 22 6 | - api: use nodeUUID instead of nodeAddr/HostPort in web admin UI 7 | - api: fix VersionGTE comparison bug 8 | - api: added Manager.Stop() API 9 | - api: refactored out BlanceMap() API 10 | - api: refactored out BlancePartitionModel() API 11 | - api: exposed Manager.StartCfg() API 12 | - api: node and cluster monitoring API's 13 | - api: exposed RESTCfg struct 14 | - api: CfgRemoveNodeDef, issue 26 15 | - api: exposed CasePlanFrozen API 16 | - api: exposed PlannerGetPlan API 17 | - api: allow uuid to be optional in PlannerGetNodeDefs API 18 | - api: added FeedPartitionSeqsFunc to FeedType registration 19 | - api: issue 27: propagate DCP errors to Cfg subscribers 20 | - api: DEPRECATE: CfgCB.SetKeyPrefix; and NewCfgCBEx API 21 | - api: added StructChanges() helper API 22 | - api: cbgt.CopyPlanPIndexes func 23 | - api: cbgt.UnregisterNodes func 24 | - api: cmd.MainCfgClient helper func 25 | - api: cmd.MainCommon helper func 26 | - api: cmd.LogFlags helper func 27 | - api: BlancePlanPIndexes takes mode param for failover 28 | - api: exposed cbgt.Plan() API 29 | - api: removed unused FileService / FileLike code 30 | - api: exposed Rebalancer struct 31 | - api: CouchbasePartitionSeqs func to return current vbucket uuid/seq's 32 | - api: CouchbaseBucket helper func 33 | - auth: auth fixes to work with CB 3.x 34 | - auth: Merge pull request #15 from nimishzynga/cbauth1 35 | - build: added release instructions to Makefile 36 | - cfg: BREAKING Cfg storage format change for NodeDefs[uuid] 37 | - cfg: CfgCB parses url for user/pswd, issue 24 38 | - cfg: Merge pull request #23 from nimishzynga/master 39 | - cfg: issue 14 - propagate cfgKey during Refresh() 40 | - cfg: Adding cbauth and metakv in cbgt 41 | - diag: allow for nil assetDir for diag REST 42 | - diag: retrieves from staticx/dist 43 | - janitor: CalcFeedsDelta handles changed feeds correctly 44 | - monitor: fix: issue 42: more checks for stopCh closings 45 | - monitor: added ability to disable stats/diag monitoring 46 | - planner: break PROMOTE_CALC loop during failover promotion 47 | - planner: implement cbgt-planner FAILOVER 48 | - planner: cmd.PlannerSteps helper func 49 | - planner: cbgt-planner cmd-line tool 50 | - planner: added indexTypes cmd-line param to cbgt-planner/rebalance 51 | - planner: fix: pindex safety checks if no New/Open funcs (alias case) 52 | - planner: added -r/RemoveNodes cmd-line param 53 | - planner: add log verbose cmd-line flag 54 | - planner: cmd-line dry-run flag 55 | - rebalance: cbgt-rebalance cmd-line tool 56 | - rebalance: cbgt-rebalance skips uninstantiated indexTypes like aliases 57 | - rebalance: option to FavorMinNodes 58 | - rebalance: node removal has precendence over additions 59 | - rebalance: emit progress table only if changed 60 | - rebalance: emit graph markers for easy log searchability 61 | - rebalance: emit pindex promotion/demotions 62 | - rebalance: compute avg pct reached across sourcePartitions for a pindex/node 63 | - rebalance: tracks wantSeqs vs currSeqs 64 | - rebalance: faster rebalance via early seq check 65 | - rebalance: writes in right version when updating planPIndexes 66 | - rebalance: assignPIndex lock coverage increased 67 | - rebalance: log beg/end plans as json 68 | - rebalance: stops rebalance on node monitoring error 69 | - rebalance: added ErrorNotPausable/Resumable 70 | - rebalance: added Pause/ResumeNewAssignments to rebalancer 71 | - rebalance: indent logging 72 | - test: more test coverage 73 | - test: faster unit test with better fake IP address 74 | - test: issue 31: fix data race by avoiding HttpGet global 75 | - test: issue 32: fix some unit test data races via locking 76 | - test: metakv cmd test (disabled due to log spam) 77 | - web-UI: fix: issue 177, edit/clone index copies advanced params again 78 | - web-UI: display nodes list ordered by their hostPort 79 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Source code in this repository is licensed under various licenses. The 2 | Business Source License 1.1 (BSL) is one such license. Each file indicates in 3 | a section at the beginning of the file the name of the license that applies to 4 | it. All licenses used in this repository can be found in the top-level 5 | licenses directory. 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | build: gen-bindata 4 | go build ./... 5 | go build ./cmd/cbgt-ctl 6 | 7 | gen-bindata: 8 | (cd rest; \ 9 | go-bindata-assetfs -pkg=rest ./static/... -o bindata_assetfs.go; \ 10 | gofmt -s -w bindata_assetfs.go) 11 | 12 | coverage: 13 | go test -coverprofile=coverage.out -covermode=count 14 | go test -coverprofile=coverage-cmd.out -covermode=count ./cmd 15 | go test -coverprofile=coverage-rest.out -covermode=count ./rest 16 | go test -coverprofile=coverage-rest-monitor.out -covermode=count ./rest/monitor 17 | go test -coverprofile=coverage-rebalance.out -covermode=count ./rebalance 18 | cat coverage-cmd.out | grep -v "mode: count" >> coverage.out 19 | cat coverage-rest.out | grep -v "mode: count" >> coverage.out 20 | cat coverage-rest-monitor.out | grep -v "mode: count" >> coverage.out 21 | cat coverage-rebalance.out | grep -v "mode: count" >> coverage.out 22 | go tool cover -html=coverage.out 23 | 24 | # To release a new version of the cbgt library... 25 | # 26 | # git grep v0.2.0 # Look for old version strings. 27 | # git grep v0.2 # Look for old version strings. 28 | # # Edit/update files, if any. 29 | # # Ensure bindata_assetfs.go is up-to-date, by running... 30 | # make build 31 | # # Then, run tests... 32 | # go test ./... 33 | # # Then, run a diff against the previous version... 34 | # git log --pretty=format:%s v0.2.0... 35 | # # Then, update the CHANGES.md file based on diff. 36 | # git commit -m "v0.3.0" 37 | # git push 38 | # git tag -a "v0.3.0" -m "v0.3.0" 39 | # git push --tags 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cbgt 2 | ==== 3 | 4 | The cbgt project provides a golang library that helps manage 5 | distributed partitions (or data shards) across an elastic cluster of 6 | servers. 7 | 8 | [![GoDoc](https://godoc.org/github.com/couchbase/cbgt?status.svg)](https://godoc.org/github.com/couchbase/cbgt) [![Coverage Status](https://coveralls.io/repos/couchbase/cbgt/badge.svg?branch=master&service=github)](https://coveralls.io/github/couchbase/cbgt?branch=master) 9 | 10 | #### Documentation 11 | 12 | * [REST API Reference](http://labs.couchbase.com/cbft/api-ref/) - 13 | these REST API Reference docs come from cbft, which uses the cbgt 14 | library. 15 | 16 | NOTE: This library initializes math's random seed 17 | (rand.Seed(time.Now().UTC().UnixNano())) for unique id generation. 18 | -------------------------------------------------------------------------------- /cfg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import "time" 12 | 13 | // Cfg is the interface that configuration providers must implement. 14 | type Cfg interface { 15 | // Get retrieves an entry from the Cfg. A zero cas means don't do 16 | // a CAS match on Get(), and a non-zero cas value means the Get() 17 | // will succeed only if the CAS matches. 18 | Get(key string, cas uint64) (val []byte, casSuccess uint64, err error) 19 | 20 | // Set creates or updates an entry in the Cfg. A non-zero cas 21 | // that does not match will result in an error. A zero cas means 22 | // the Set() operation must be an entry creation, where a zero cas 23 | // Set() will error if the entry already exists. 24 | Set(key string, val []byte, cas uint64) (casSuccess uint64, err error) 25 | 26 | // Del removes an entry from the Cfg. A non-zero cas that does 27 | // not match will result in an error. A zero cas means a CAS 28 | // match will be skipped, so that clients can perform a 29 | // "don't-care, out-of-the-blue" deletion. 30 | Del(key string, cas uint64) error 31 | 32 | // Subscribe allows clients to receive events on changes to a key. 33 | // During a deletion event, the CfgEvent.CAS field will be 0. 34 | Subscribe(key string, ch chan CfgEvent) error 35 | 36 | // Refresh forces the Cfg implementation to reload from its 37 | // backend-specific data source, clearing any locally cached data. 38 | // Any subscribers will receive events on a Refresh, where it's up 39 | // to subscribers to detect if there were actual changes or not. 40 | Refresh() error 41 | } 42 | 43 | // The error used on mismatches of CAS (compare and set/swap) values. 44 | type CfgCASError struct{} 45 | 46 | func (e *CfgCASError) Error() string { return "CAS mismatch" } 47 | 48 | // See the Cfg.Subscribe() method. 49 | type CfgEvent struct { 50 | Time time.Time 51 | Key string 52 | CAS uint64 53 | Error error 54 | } 55 | -------------------------------------------------------------------------------- /cfg_mem.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "fmt" 13 | "math" 14 | "sync" 15 | ) 16 | 17 | const ( 18 | CFG_CAS_FORCE = math.MaxUint64 19 | ) 20 | 21 | // CfgMem is a local-only, memory-only implementation of Cfg 22 | // interface that's useful for development and testing. 23 | type CfgMem struct { 24 | m sync.Mutex 25 | CASNext uint64 26 | Entries map[string]*CfgMemEntry 27 | subscriptions map[string][]chan<- CfgEvent // Keyed by key. 28 | } 29 | 30 | // CfgMemEntry is a CAS-Val pairing tracked by CfgMem. 31 | type CfgMemEntry struct { 32 | CAS uint64 33 | Val []byte 34 | rev interface{} 35 | } 36 | 37 | // NewCfgMem returns an empty CfgMem instance. 38 | func NewCfgMem() *CfgMem { 39 | return &CfgMem{ 40 | CASNext: 1, 41 | Entries: make(map[string]*CfgMemEntry), 42 | subscriptions: make(map[string][]chan<- CfgEvent), 43 | } 44 | } 45 | 46 | func (c *CfgMem) Get(key string, cas uint64) ( 47 | []byte, uint64, error) { 48 | c.m.Lock() 49 | defer c.m.Unlock() 50 | 51 | entry, exists := c.Entries[key] 52 | if !exists { 53 | return nil, 0, nil 54 | } 55 | if cas != 0 && cas != entry.CAS { 56 | return nil, 0, &CfgCASError{} 57 | } 58 | 59 | val := make([]byte, len(entry.Val)) 60 | copy(val, entry.Val) 61 | return val, entry.CAS, nil 62 | } 63 | 64 | func (c *CfgMem) GetRev(key string, cas uint64) ( 65 | interface{}, error) { 66 | c.m.Lock() 67 | defer c.m.Unlock() 68 | 69 | entry, exists := c.Entries[key] 70 | if exists { 71 | if cas == 0 || cas == entry.CAS { 72 | return entry.rev, nil 73 | } 74 | return nil, &CfgCASError{} 75 | } 76 | return nil, nil 77 | } 78 | 79 | func (c *CfgMem) SetRev(key string, cas uint64, rev interface{}) error { 80 | c.m.Lock() 81 | defer c.m.Unlock() 82 | 83 | entry, exists := c.Entries[key] 84 | if cas == 0 || (exists && cas == entry.CAS) { 85 | entry.rev = rev 86 | c.Entries[key] = entry 87 | return nil 88 | } 89 | return &CfgCASError{} 90 | } 91 | 92 | func (c *CfgMem) Set(key string, val []byte, cas uint64) ( 93 | uint64, error) { 94 | c.m.Lock() 95 | defer c.m.Unlock() 96 | 97 | prevEntry, exists := c.Entries[key] 98 | switch { 99 | case cas == 0: 100 | if exists { 101 | return 0, fmt.Errorf("cfg_mem: entry already exists, key: %s", key) 102 | } 103 | case cas == CFG_CAS_FORCE: 104 | break 105 | default: 106 | if !exists || cas != prevEntry.CAS { 107 | return 0, &CfgCASError{} 108 | } 109 | } 110 | 111 | nextEntry := &CfgMemEntry{ 112 | CAS: c.CASNext, 113 | Val: make([]byte, len(val)), 114 | } 115 | copy(nextEntry.Val, val) 116 | c.Entries[key] = nextEntry 117 | c.CASNext += 1 118 | c.fireEvent(key, nextEntry.CAS, nil) 119 | return nextEntry.CAS, nil 120 | } 121 | 122 | func (c *CfgMem) Del(key string, cas uint64) error { 123 | c.m.Lock() 124 | defer c.m.Unlock() 125 | 126 | if cas != 0 { 127 | entry, exists := c.Entries[key] 128 | if !exists || cas != entry.CAS { 129 | return &CfgCASError{} 130 | } 131 | } 132 | delete(c.Entries, key) 133 | c.fireEvent(key, 0, nil) 134 | return nil 135 | } 136 | 137 | func (c *CfgMem) Subscribe(key string, ch chan CfgEvent) error { 138 | c.m.Lock() 139 | defer c.m.Unlock() 140 | 141 | a, exists := c.subscriptions[key] 142 | if !exists || a == nil { 143 | a = make([]chan<- CfgEvent, 0) 144 | } 145 | c.subscriptions[key] = append(a, ch) 146 | return nil 147 | } 148 | 149 | func (c *CfgMem) FireEvent(key string, cas uint64, err error) { 150 | c.m.Lock() 151 | c.fireEvent(key, cas, err) 152 | c.m.Unlock() 153 | } 154 | 155 | func (c *CfgMem) fireEvent(key string, cas uint64, err error) { 156 | for _, c := range c.subscriptions[key] { 157 | go func(c chan<- CfgEvent) { 158 | c <- CfgEvent{ 159 | Key: key, CAS: cas, Error: err, 160 | } 161 | }(c) 162 | } 163 | } 164 | 165 | func (c *CfgMem) Refresh() error { 166 | c.m.Lock() 167 | defer c.m.Unlock() 168 | 169 | for key := range c.subscriptions { 170 | entry, exists := c.Entries[key] 171 | if exists && entry != nil { 172 | c.fireEvent(key, entry.CAS, nil) 173 | } else { 174 | c.fireEvent(key, 0, nil) 175 | } 176 | } 177 | 178 | return nil 179 | } 180 | -------------------------------------------------------------------------------- /cfg_registry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | // Manager components that can subscribe to cfg changes 12 | const ( 13 | componentPlanner int = iota 14 | componentJanitor 15 | componentIndexDefsCache 16 | componentPlanPIndexesCache 17 | componentNodeDefsCache 18 | componentRebalanceStatus 19 | ) 20 | 21 | type cfgSubscription struct { 22 | // manager component -> subscription keys 23 | keys map[int][]string 24 | } 25 | 26 | // ----------------------------------------------------------------------------- 27 | 28 | // # cfg subscription Keys Registry 29 | // keyed by cfgName 30 | // cfgName: "" is reserved for default subscription keys for all components 31 | var cfgSubscriptions = map[string]*cfgSubscription{ 32 | // default subscriptions 33 | "": &cfgSubscription{ 34 | keys: map[int][]string{ 35 | componentPlanner: []string{ 36 | INDEX_DEFS_KEY, 37 | CfgNodeDefsKey(NODE_DEFS_WANTED), 38 | }, 39 | componentJanitor: []string{ 40 | PLAN_PINDEXES_KEY, 41 | CfgNodeDefsKey(NODE_DEFS_WANTED), 42 | }, 43 | componentIndexDefsCache: []string{ 44 | INDEX_DEFS_KEY, 45 | MANAGER_CLUSTER_OPTIONS_KEY, 46 | }, 47 | componentPlanPIndexesCache: []string{PLAN_PINDEXES_KEY}, 48 | componentNodeDefsCache: []string{ 49 | CfgNodeDefsKey(NODE_DEFS_KNOWN), 50 | CfgNodeDefsKey(NODE_DEFS_WANTED), 51 | }, 52 | componentRebalanceStatus: []string{ 53 | LAST_REBALANCE_STATUS_KEY, 54 | }, 55 | }, 56 | }, 57 | 58 | // metaKV overrides 59 | CFG_METAKV: &cfgSubscription{ 60 | keys: map[int][]string{ 61 | componentJanitor: []string{ 62 | PLAN_PINDEXES_DIRECTORY_STAMP, 63 | CfgNodeDefsKey(NODE_DEFS_WANTED), 64 | }, 65 | componentPlanPIndexesCache: []string{PLAN_PINDEXES_DIRECTORY_STAMP}, 66 | }, 67 | }, 68 | } 69 | 70 | // Method to get subscription keys for the given component from the registry. 71 | // If not found, try to get the default subscription keys for the component. 72 | func getCfgSubscriptionKeys(cfgName string, component int) []string { 73 | cfgNames := []string{cfgName, ""} // order matters 74 | for _, name := range cfgNames { 75 | if subscription, ok := cfgSubscriptions[name]; ok { 76 | if keys, ok := subscription.keys[component]; ok { 77 | return keys 78 | } 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /cfg_simple.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "os" 13 | "sync" 14 | ) 15 | 16 | // CfgSimple is a local-only, persisted (in a single file) 17 | // implementation of the Cfg interface that's useful for 18 | // non-clustered, single-node instances for developers. 19 | type CfgSimple struct { 20 | m sync.Mutex 21 | path string 22 | cfgMem *CfgMem 23 | } 24 | 25 | // NewCfgSimple returns a CfgSimple that reads and stores its single 26 | // configuration file in the provided file path. 27 | func NewCfgSimple(path string) *CfgSimple { 28 | return &CfgSimple{ 29 | path: path, 30 | cfgMem: NewCfgMem(), 31 | } 32 | } 33 | 34 | func (c *CfgSimple) Get(key string, cas uint64) ( 35 | []byte, uint64, error) { 36 | c.m.Lock() 37 | defer c.m.Unlock() 38 | 39 | return c.cfgMem.Get(key, cas) 40 | } 41 | 42 | func (c *CfgSimple) Set(key string, val []byte, cas uint64) ( 43 | uint64, error) { 44 | c.m.Lock() 45 | defer c.m.Unlock() 46 | 47 | cas, err := c.cfgMem.Set(key, val, cas) 48 | if err != nil { 49 | return 0, err 50 | } 51 | 52 | err = c.unlockedSave() 53 | if err != nil { 54 | return 0, err 55 | } 56 | return cas, err 57 | } 58 | 59 | func (c *CfgSimple) Del(key string, cas uint64) error { 60 | c.m.Lock() 61 | defer c.m.Unlock() 62 | 63 | err := c.cfgMem.Del(key, cas) 64 | if err != nil { 65 | return err 66 | } 67 | return c.unlockedSave() 68 | } 69 | 70 | func (c *CfgSimple) Load() error { 71 | c.m.Lock() 72 | defer c.m.Unlock() 73 | 74 | return c.unlockedLoad() 75 | } 76 | 77 | func (c *CfgSimple) unlockedLoad() error { 78 | buf, err := os.ReadFile(c.path) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | cfgMem := NewCfgMem() 84 | err = UnmarshalJSON(buf, cfgMem) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | c.cfgMem = cfgMem 90 | return nil 91 | } 92 | 93 | func (c *CfgSimple) unlockedSave() error { 94 | buf, err := MarshalJSON(c.cfgMem) 95 | if err != nil { 96 | return err 97 | } 98 | return os.WriteFile(c.path, []byte(string(buf)+"\n"), 0600) 99 | } 100 | 101 | func (c *CfgSimple) Subscribe(key string, ch chan CfgEvent) error { 102 | c.m.Lock() 103 | defer c.m.Unlock() 104 | 105 | return c.cfgMem.Subscribe(key, ch) 106 | } 107 | 108 | func (c *CfgSimple) Refresh() error { 109 | c.m.Lock() 110 | defer c.m.Unlock() 111 | 112 | c2 := NewCfgSimple(c.path) 113 | err := c2.Load() 114 | if err != nil { 115 | return err 116 | } 117 | 118 | c.cfgMem.CASNext = c2.cfgMem.CASNext 119 | c.cfgMem.Entries = c2.cfgMem.Entries 120 | 121 | return c.cfgMem.Refresh() 122 | } 123 | -------------------------------------------------------------------------------- /cmd/cbgt-ctl/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package main 10 | 11 | import ( 12 | "encoding/json" 13 | "flag" 14 | "fmt" 15 | "net/http" 16 | "os" 17 | "path" 18 | "strings" 19 | "time" 20 | 21 | "github.com/gorilla/mux" 22 | 23 | log "github.com/couchbase/clog" 24 | 25 | "github.com/couchbase/cbgt" 26 | "github.com/couchbase/cbgt/cmd" 27 | "github.com/couchbase/cbgt/ctl" 28 | "github.com/couchbase/cbgt/rebalance" 29 | ) 30 | 31 | func main() { 32 | flag.Parse() 33 | 34 | if flags.Help { 35 | flag.Usage() 36 | os.Exit(2) 37 | } 38 | 39 | if flags.Version { 40 | fmt.Printf("%s main: %s, data: %s\n", 41 | path.Base(os.Args[0]), cbgt.VERSION, cbgt.VERSION) 42 | os.Exit(0) 43 | } 44 | 45 | cmd.MainCommon(cbgt.VERSION, flagAliases) 46 | 47 | options := cmd.ParseOptions(flags.Options, "CBGTCTL_ENV_OPTIONS", nil) 48 | 49 | cfg, err := cmd.MainCfgClient(path.Base(os.Args[0]), flags.CfgConnect) 50 | if err != nil { 51 | log.Fatalf("%v", err) 52 | } 53 | 54 | if flags.IndexTypes != "" { 55 | cmd.RegisterIndexTypes(strings.Split(flags.IndexTypes, ",")) 56 | } 57 | 58 | nodesToRemove := []string(nil) 59 | if len(flags.RemoveNodes) > 0 { 60 | nodesToRemove = strings.Split(flags.RemoveNodes, ",") 61 | } 62 | 63 | steps := map[string]bool{} 64 | if flags.Steps != "" { 65 | steps = cbgt.StringsToMap(strings.Split(flags.Steps, ",")) 66 | } 67 | 68 | // ------------------------------------------------ 69 | 70 | if steps != nil && steps["rebalance"] { 71 | steps["rebalance_"] = true 72 | steps["unregister"] = true 73 | steps["planner"] = true 74 | } 75 | 76 | // ------------------------------------------------ 77 | 78 | if steps != nil && steps["rebalance_"] { 79 | log.Printf("main: step rebalance_") 80 | 81 | err = rebalance.RunRebalance(cfg, flags.Server, options, 82 | nodesToRemove, flags.FavorMinNodes, 83 | flags.DryRun, flags.Verbose, nil) 84 | if err != nil { 85 | log.Fatalf("main: RunRebalance, err: %v", err) 86 | } 87 | } 88 | 89 | // ------------------------------------------------ 90 | 91 | err = cmd.PlannerSteps(steps, cfg, cbgt.VERSION, 92 | flags.Server, options, nodesToRemove, flags.DryRun, nil, time.Time{}) 93 | if err != nil { 94 | log.Fatalf("main: PlannerSteps, err: %v", err) 95 | } 96 | 97 | // ------------------------------------------------ 98 | 99 | var c *ctl.Ctl 100 | 101 | if steps != nil && (steps["service"] || steps["rest"] || steps["prompt"]) { 102 | c, err = ctl.StartCtl(cfg, flags.Server, options, ctl.CtlOptions{ 103 | DryRun: flags.DryRun, 104 | Verbose: flags.Verbose, 105 | FavorMinNodes: flags.FavorMinNodes, 106 | WaitForMemberNodes: flags.WaitForMemberNodes, 107 | }) 108 | if err != nil { 109 | log.Fatalf("main: StartCtl, err: %v", err) 110 | } 111 | 112 | if steps["service"] { 113 | // TODO. 114 | } 115 | 116 | if steps["rest"] { 117 | bindHttp := flags.BindHttp 118 | if bindHttp[0] == ':' { 119 | bindHttp = "localhost" + bindHttp 120 | } 121 | if strings.HasPrefix(bindHttp, "0.0.0.0:") { 122 | bindHttp = "localhost" + bindHttp[len("0.0.0.0"):] 123 | } 124 | 125 | http.Handle("/", newRestRouter(c)) 126 | 127 | go func() { 128 | log.Printf("------------------------------------------------------------") 129 | log.Printf("REST API is available: http://%s", bindHttp) 130 | log.Printf("------------------------------------------------------------") 131 | 132 | err := http.ListenAndServe(bindHttp, nil) // Blocks. 133 | if err != nil { 134 | log.Fatalf("main: listen, err: %v\n"+ 135 | " Please check that your -bindHttp parameter (%q)\n"+ 136 | " is correct and available.", err, bindHttp) 137 | } 138 | }() 139 | } 140 | 141 | if steps["prompt"] { 142 | go runCtlPrompt(c) 143 | } 144 | 145 | <-make(chan struct{}) 146 | } 147 | 148 | // ------------------------------------------------ 149 | 150 | log.Printf("main: done") 151 | } 152 | 153 | // ------------------------------------------------ 154 | 155 | func newRestRouter(ctl *ctl.Ctl) *mux.Router { 156 | r := mux.NewRouter() 157 | 158 | r.HandleFunc("/api/getTopology", 159 | func(w http.ResponseWriter, r *http.Request) { 160 | topology := ctl.GetTopology() 161 | b, _ := json.Marshal(topology) 162 | w.Write(b) 163 | }).Methods("GET") 164 | 165 | // TODO: POST /api/changeTopology 166 | // TODO: POST /api/stopChangeTopology 167 | // TODO: POST /api/indexDefsChanged 168 | 169 | return r 170 | } 171 | -------------------------------------------------------------------------------- /cmd/cbgt-ctl/prompt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package main 10 | 11 | import ( 12 | "bufio" 13 | "encoding/json" 14 | "fmt" 15 | "os" 16 | "strings" 17 | "time" 18 | 19 | log "github.com/couchbase/clog" 20 | 21 | "github.com/couchbase/cbgt/ctl" 22 | ) 23 | 24 | func runCtlPrompt(ctlInst *ctl.Ctl) { 25 | reader := bufio.NewReader(os.Stdin) 26 | 27 | i := 0 28 | for { 29 | fmt.Printf("ctl [%d]> ", i) 30 | 31 | line, err := reader.ReadString('\n') 32 | if err != nil { 33 | log.Fatalf("exiting, err: %v", err) 34 | } 35 | 36 | line = strings.TrimSpace(line) 37 | if len(line) > 0 { 38 | lineParts := strings.Split(line, " ") 39 | if len(lineParts) > 0 { 40 | op := lineParts[0] 41 | if op == "?" || op == "h" || op == "help" { 42 | log.Printf("available commands:\n" + 43 | " getTopology\n" + 44 | " gt (alias for getTopology)\n" + 45 | " changeTopology $rev $mode $memberNodeUUIDsCSV\n" + 46 | " ct (alias for changeTopology)\n" + 47 | " stopChangeTopology $rev\n" + 48 | " sct (alias for stopChangeTopology)\n" + 49 | " indexDefsChanged\n" + 50 | " idc (alias for indexDefsChanged)\n" + 51 | " exit, quit, q") 52 | } else if op == "getTopology" || op == "gt" { 53 | topology := ctlInst.GetTopology() 54 | b, _ := json.Marshal(topology) 55 | log.Printf("topology: %s", string(b)) 56 | } else if op == "changeTopology" || op == "ct" { 57 | if len(lineParts) != 4 { 58 | log.Warnf("expected 3 arguments") 59 | } else { 60 | rev := lineParts[1] 61 | mode := lineParts[2] 62 | memberNodeUUIDs := strings.Split(lineParts[3], ",") 63 | 64 | log.Printf("changeTopology,"+ 65 | " rev: %s, mode: %s, memberNodeUUIDs: %#v", 66 | rev, mode, memberNodeUUIDs) 67 | var topology *ctl.CtlTopology 68 | topology, err = 69 | ctlInst.ChangeTopology(&ctl.CtlChangeTopology{ 70 | Rev: rev, 71 | Mode: mode, 72 | MemberNodeUUIDs: memberNodeUUIDs, 73 | }, nil) 74 | 75 | b, _ := json.Marshal(topology) 76 | log.Printf("topology: %s", string(b)) 77 | 78 | log.Printf("err: %v", err) 79 | } 80 | } else if op == "stopChangeTopology" || op == "sct" { 81 | if len(lineParts) != 2 { 82 | log.Warnf("expected 1 arguments") 83 | } else { 84 | rev := lineParts[1] 85 | 86 | log.Printf("stopChangeTopology, rev: %s", rev) 87 | 88 | ctlInst.StopChangeTopology(rev) 89 | } 90 | } else if op == "indexDefsChanged" || op == "idc" { 91 | err = ctlInst.IndexDefsChanged(time.Time{}) 92 | 93 | log.Printf("err: %v", err) 94 | } else if op == "quit" || op == "q" || op == "exit" { 95 | log.Printf("bye") 96 | os.Exit(0) 97 | } else { 98 | log.Warnf("unknown op: %s", op) 99 | } 100 | } 101 | } 102 | 103 | i++ 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cmd/cbgt-ctl/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /cmd/dump.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | //go:build !windows 10 | // +build !windows 11 | 12 | package cmd 13 | 14 | import ( 15 | "os" 16 | "os/signal" 17 | "runtime/pprof" 18 | "syscall" 19 | 20 | log "github.com/couchbase/clog" 21 | ) 22 | 23 | func DumpOnSignalForPlatform() { 24 | DumpOnSignal(syscall.SIGUSR2) 25 | } 26 | 27 | func DumpOnSignal(signals ...os.Signal) { 28 | c := make(chan os.Signal, 1) 29 | signal.Notify(c, signals...) 30 | for range c { 31 | log.Printf("dump: goroutine...") 32 | pprof.Lookup("goroutine").WriteTo(os.Stderr, 1) 33 | log.Printf("dump: heap...") 34 | pprof.Lookup("heap").WriteTo(os.Stderr, 1) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cmd/dump_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cmd 10 | 11 | import ( 12 | "testing" 13 | ) 14 | 15 | func TestDump(t *testing.T) { 16 | // I guess we just make sure this doesn't crash. 17 | go DumpOnSignalForPlatform() 18 | } 19 | -------------------------------------------------------------------------------- /cmd/dump_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | //go:build windows || !darwin || !freebsd || !linux || !openbsd || !netbsd 10 | // +build windows !darwin !freebsd !linux !openbsd !netbsd 11 | 12 | package cmd 13 | 14 | import ( 15 | log "github.com/couchbase/clog" 16 | ) 17 | 18 | func DumpOnSignalForPlatform() { 19 | // No-op for windows. 20 | } 21 | 22 | func init() { 23 | log.DisableColor() 24 | } 25 | -------------------------------------------------------------------------------- /cmd/main_cfg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cmd 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | "net/url" 15 | "os" 16 | "strings" 17 | 18 | "github.com/couchbase/cbgt" 19 | ) 20 | 21 | var ErrorBindHttp = errors.New("main_cfg:" + 22 | " couchbase cfg needs network/IP address for bindHttp,\n" + 23 | " Please specify a network/IP address for the -bindHttp parameter\n" + 24 | " (non-loopback, non-127.0.0.1/localhost, non-0.0.0.0)\n" + 25 | " so that this node can be clustered with other nodes.") 26 | 27 | // MainCfg connects to a Cfg provider as a server peer (e.g., as a 28 | // cbgt.Manager). 29 | func MainCfg(baseName, connect, bindHttp, 30 | register, dataDir string) (cbgt.Cfg, error) { 31 | return MainCfgEx(baseName, connect, bindHttp, register, dataDir, "", nil) 32 | } 33 | 34 | // MainCfgEx connects to a Cfg provider as a server peer (e.g., as a 35 | // cbgt.Manager), with more options. 36 | func MainCfgEx(baseName, connect, bindHttp, 37 | register, dataDir, uuid string, options map[string]string) (cbgt.Cfg, error) { 38 | // TODO: One day, the default cfg provider should not be simple. 39 | // TODO: One day, Cfg provider lookup should be table driven. 40 | var cfg cbgt.Cfg 41 | var err error 42 | switch { 43 | case connect == "", connect == "simple": 44 | cfg, err = MainCfgSimple(baseName, connect, bindHttp, register, dataDir) 45 | case strings.HasPrefix(connect, "couchbase:"): 46 | cfg, err = MainCfgCB(baseName, connect[len("couchbase:"):], 47 | bindHttp, register, dataDir) 48 | case strings.HasPrefix(connect, "metakv"): 49 | cfg, err = MainCfgMetaKv(baseName, connect[len("metakv"):], 50 | bindHttp, register, dataDir, uuid, options) 51 | default: 52 | err = fmt.Errorf("main_cfg1: unsupported cfg connect: %s", connect) 53 | } 54 | return cfg, err 55 | } 56 | 57 | // ------------------------------------------------ 58 | 59 | func MainCfgSimple(baseName, connect, bindHttp, register, dataDir string) ( 60 | cbgt.Cfg, error) { 61 | cfgPath := dataDir + string(os.PathSeparator) + baseName + ".cfg" 62 | cfgPathExists := false 63 | if _, err := os.Stat(cfgPath); err == nil { 64 | cfgPathExists = true 65 | } 66 | 67 | cfg := cbgt.NewCfgSimple(cfgPath) 68 | if cfgPathExists { 69 | err := cfg.Load() 70 | if err != nil { 71 | return nil, err 72 | } 73 | } 74 | 75 | return cfg, nil 76 | } 77 | 78 | func MainCfgCB(baseName, urlStr, bindHttp, register, dataDir string) ( 79 | cbgt.Cfg, error) { 80 | if (bindHttp[0] == ':' || 81 | strings.HasPrefix(bindHttp, "0.0.0.0:") || 82 | strings.HasPrefix(bindHttp, "127.0.0.1:") || 83 | strings.HasPrefix(bindHttp, "localhost:")) && 84 | register != "unwanted" && 85 | register != "unknown" { 86 | return nil, ErrorBindHttp 87 | } 88 | 89 | u, err := url.Parse(urlStr) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | bucket := "default" 95 | if u.User != nil && u.User.Username() != "" { 96 | bucket = u.User.Username() 97 | } 98 | 99 | cfg, err := cbgt.NewCfgCB(urlStr, bucket) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return cfg, nil 105 | } 106 | 107 | func MainCfgMetaKv(baseName, urlStr, bindHttp, register, dataDir, uuid string, 108 | options map[string]string) ( 109 | cbgt.Cfg, error) { 110 | cfg, err := cbgt.NewCfgMetaKv(uuid, options) 111 | if err == nil { 112 | // Useful for reseting internal testing. 113 | if urlStr == ":removeAllKeys" { 114 | cfg.RemoveAllKeys() 115 | } 116 | err = cfg.Load() 117 | } 118 | return cfg, err 119 | } 120 | 121 | // ------------------------------------------------ 122 | 123 | // MainCfgClient helper function connects to a Cfg provider as a 124 | // client (not as a known cbgt.Manager server or peer). This is 125 | // useful, for example, for tool developers as opposed to server 126 | // developers. 127 | func MainCfgClient(baseName, cfgConnect string) (cbgt.Cfg, error) { 128 | bindHttp := "" 129 | register := "unchanged" 130 | dataDir := "" 131 | 132 | cfg, err := MainCfg(baseName, cfgConnect, 133 | bindHttp, register, dataDir) 134 | if err == ErrorBindHttp { 135 | return nil, err 136 | } 137 | 138 | if err != nil { 139 | return nil, fmt.Errorf("main: could not start cfg,"+ 140 | " cfgConnect: %s, err: %v\n"+ 141 | " Please check that your -cfg/-cfgConnect parameter (%q)\n"+ 142 | " is correct and/or that your configuration provider\n"+ 143 | " is available.", 144 | cfgConnect, err, cfgConnect) 145 | } 146 | 147 | return cfg, err 148 | } 149 | -------------------------------------------------------------------------------- /cmd/main_cfg_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cmd 10 | 11 | import ( 12 | "os" 13 | "testing" 14 | ) 15 | 16 | func TestMainCfg(t *testing.T) { 17 | emptyDir, _ := os.MkdirTemp("./tmp", "test") 18 | defer os.RemoveAll(emptyDir) 19 | 20 | bindHttp := "10.1.1.20:8095" 21 | register := "wanted" 22 | 23 | cfg, err := MainCfg("cbgt", "an unknown cfg provider", 24 | bindHttp, register, emptyDir) 25 | if err == nil || cfg != nil { 26 | t.Errorf("expected MainCfg() to fail on unknown provider") 27 | } 28 | 29 | cfg, err = MainCfg("cbgt", "simple", bindHttp, register, emptyDir) 30 | if err != nil || cfg == nil { 31 | t.Errorf("expected MainCfg() to work on simple provider") 32 | } 33 | 34 | if _, err = cfg.Set("k", []byte("value"), 0); err != nil { 35 | t.Errorf("expected Set() to work") 36 | } 37 | 38 | cfg, err = MainCfg("cbgt", "simple", 39 | bindHttp, register, emptyDir) 40 | if err != nil || cfg == nil { 41 | t.Errorf("expected MainCfg() ok simple provider when reload") 42 | } 43 | 44 | cfg, err = MainCfg("cbgt", "couchbase:http://", 45 | bindHttp, register, emptyDir) 46 | if err == nil || cfg != nil { 47 | t.Errorf("expected err on bad url") 48 | } 49 | 50 | cfg, err = MainCfg("cbgt", "couchbase:http://user:pswd@127.0.0.1:666", 51 | bindHttp, register, emptyDir) 52 | if err == nil || cfg != nil { 53 | t.Errorf("expected err on bad server") 54 | } 55 | 56 | if false { // metakv skipped due to log spam. 57 | cfg, err = MainCfg("cbgt", "metakv", 58 | bindHttp, register, emptyDir) 59 | if err != nil || cfg == nil { 60 | t.Errorf("expected no err metakv cfg") 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/main_flags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cmd 10 | 11 | import ( 12 | "encoding/json" 13 | "flag" 14 | "fmt" 15 | "math/rand" 16 | "os" 17 | "runtime" 18 | "strings" 19 | "time" 20 | 21 | log "github.com/couchbase/clog" 22 | 23 | "github.com/couchbase/cbgt" 24 | ) 25 | 26 | func MainCommon(version string, flagAliases map[string][]string) { 27 | if os.Getenv("GOMAXPROCS") == "" { 28 | runtime.GOMAXPROCS(runtime.NumCPU()) 29 | } 30 | 31 | log.Printf("main: %s started (%s/%s)", 32 | os.Args[0], version, cbgt.VERSION) 33 | 34 | rand.Seed(time.Now().UTC().UnixNano()) 35 | 36 | go DumpOnSignalForPlatform() 37 | 38 | LogFlags(flagAliases) 39 | } 40 | 41 | func LogFlags(flagAliases map[string][]string) { 42 | flag.VisitAll(func(f *flag.Flag) { 43 | if flagAliases[f.Name] != nil { 44 | log.Printf(" -%s=%q\n", f.Name, f.Value) 45 | } 46 | }) 47 | log.Printf(" GOMAXPROCS=%d", runtime.GOMAXPROCS(-1)) 48 | } 49 | 50 | // -------------------------------------------------- 51 | 52 | // The user may have informed the cmd about application specific index 53 | // types, which we need to register (albeit with fake, "error-only" 54 | // implementations) because the cbgt's planner (cbgt.CalcPlan()) has 55 | // safety checks which skips any unknown, unregistered index types. 56 | func RegisterIndexTypes(indexTypes []string) { 57 | newErrorPIndexImpl := func(indexType, indexParams, 58 | path string, restart func()) (cbgt.PIndexImpl, cbgt.Dest, error) { 59 | return nil, nil, fmt.Errorf("ErrorPIndex-NEW") 60 | } 61 | 62 | openErrorPIndexImpl := func(indexType, path string, restart func()) ( 63 | cbgt.PIndexImpl, cbgt.Dest, error) { 64 | return nil, nil, fmt.Errorf("ErrorPIndex-OPEN") 65 | } 66 | 67 | for _, indexType := range indexTypes { 68 | if cbgt.PIndexImplTypes[indexType] == nil { 69 | cbgt.RegisterPIndexImplType(indexType, 70 | &cbgt.PIndexImplType{ 71 | New: newErrorPIndexImpl, 72 | Open: openErrorPIndexImpl, 73 | }) 74 | } 75 | } 76 | } 77 | 78 | // ParseOptions parses an options string and environment variable. 79 | // The optionKVs string is a comma-separated string formated as 80 | // "key=val,key=val,...". 81 | func ParseOptions(optionKVs string, envName string, 82 | options map[string]string) map[string]string { 83 | if options == nil { 84 | options = map[string]string{} 85 | } 86 | 87 | if optionKVs != "" { 88 | for _, kv := range strings.Split(optionKVs, ",") { 89 | a := strings.Split(kv, "=") 90 | if len(a) >= 2 { 91 | options[a[0]] = a[1] 92 | } 93 | } 94 | } 95 | 96 | optionsFile, exists := options["optionsFile"] 97 | if exists { 98 | log.Printf("main_flags: loading optionsFile: %q", optionsFile) 99 | 100 | b, err := os.ReadFile(optionsFile) 101 | if err != nil { 102 | log.Warnf("main_flags: reading options file: %s, err: %v", 103 | optionsFile, err) 104 | } else { 105 | err = json.Unmarshal(b, &options) 106 | if err != nil { 107 | log.Warnf("main_flags: JSON parse option file: %s, err: %v", 108 | optionsFile, err) 109 | } 110 | } 111 | } 112 | 113 | optionsEnv := os.Getenv(envName) 114 | if optionsEnv != "" { 115 | log.Printf("main_flags: ParseOptions, envName: %s", envName) 116 | for _, kv := range strings.Split(optionsEnv, ",") { 117 | a := strings.Split(kv, "=") 118 | if len(a) >= 2 { 119 | log.Printf("main_flags: ParseOptions, option: %s", a[0]) 120 | options[a[0]] = a[1] 121 | } 122 | } 123 | } 124 | 125 | return options 126 | } 127 | -------------------------------------------------------------------------------- /cmd/main_uuid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cmd 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | "strings" 15 | 16 | "github.com/couchbase/cbgt" 17 | log "github.com/couchbase/clog" 18 | ) 19 | 20 | // MainUUID is a helper function for cmd-line tool developers, that 21 | // reuses a previous "baseName.uuid" file from the dataDir if it 22 | // exists, or generates a brand new UUID (and persists it). 23 | func MainUUID(baseName, dataDir string) (string, error) { 24 | uuid := cbgt.NewUUID() 25 | uuidPath := dataDir + string(os.PathSeparator) + baseName + ".uuid" 26 | uuidBuf, err := os.ReadFile(uuidPath) 27 | if err == nil { 28 | uuid = strings.TrimSpace(string(uuidBuf)) 29 | if uuid == "" { 30 | return "", fmt.Errorf("error: could not parse uuidPath: %s", 31 | uuidPath) 32 | } 33 | log.Printf("main: manager uuid: %s", uuid) 34 | log.Printf("main: manager uuid was reloaded") 35 | } else { 36 | log.Printf("main: manager uuid: %s", uuid) 37 | log.Printf("main: manager uuid was generated") 38 | } 39 | err = os.WriteFile(uuidPath, []byte(uuid), 0600) 40 | if err != nil { 41 | return "", fmt.Errorf("error: could not write uuidPath: %s\n"+ 42 | " Please check that your -data/-dataDir parameter (%q)\n"+ 43 | " is to a writable directory where %s can store\n"+ 44 | " index data.", 45 | uuidPath, dataDir, baseName) 46 | } 47 | return uuid, nil 48 | } 49 | -------------------------------------------------------------------------------- /cmd/main_uuid_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cmd 10 | 11 | import ( 12 | "os" 13 | "testing" 14 | ) 15 | 16 | func TestMainUUID(t *testing.T) { 17 | emptyDir, _ := os.MkdirTemp("./tmp", "test") 18 | defer os.RemoveAll(emptyDir) 19 | 20 | uuid, err := MainUUID("cbgt", emptyDir) 21 | if err != nil || uuid == "" { 22 | t.Errorf("expected MainUUID() to work, err: %v", err) 23 | } 24 | 25 | uuid2, err := MainUUID("cbgt", emptyDir) 26 | if err != nil || uuid2 != uuid { 27 | t.Errorf("expected MainUUID() reload to give same uuid,"+ 28 | " uuid: %s vs %s, err: %v", uuid, uuid2, err) 29 | } 30 | 31 | path := emptyDir + string(os.PathSeparator) + "cbgt.uuid" 32 | os.Remove(path) 33 | os.WriteFile(path, []byte{}, 0600) 34 | 35 | uuid3, err := MainUUID("cbgt", emptyDir) 36 | if err == nil || uuid3 != "" { 37 | t.Errorf("expected MainUUID() to fail on empty file") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cmd/planner_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cmd 10 | 11 | import ( 12 | "testing" 13 | ) 14 | 15 | func TestParseOptionsBool(t *testing.T) { 16 | v := ParseOptionsBool(map[string]string{}, "x", "", false) 17 | exp := false 18 | if v != exp { 19 | t.Errorf("unexpected") 20 | } 21 | 22 | v = ParseOptionsBool(map[string]string{"x": "true"}, "x", "", false) 23 | exp = true 24 | if v != exp { 25 | t.Errorf("unexpected") 26 | } 27 | 28 | v = ParseOptionsBool(map[string]string{"x": ""}, "x", "", false) 29 | exp = false 30 | if v != exp { 31 | t.Errorf("unexpected") 32 | } 33 | 34 | v = ParseOptionsBool(map[string]string{"x": ""}, "x", "y", false) 35 | exp = false 36 | if v != exp { 37 | t.Errorf("unexpected") 38 | } 39 | 40 | v = ParseOptionsBool(map[string]string{"x-y": "true", "x": "false"}, 41 | "x", "y", false) 42 | exp = true 43 | if v != exp { 44 | t.Errorf("unexpected") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /feed_dcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // DCPFeedPrefix should be immutable after process init()'ialization. 16 | var DCPFeedPrefix string 17 | 18 | // DCPFeedBufferSizeBytes is representative of connection_buffer_size 19 | // for DCP to enable flow control, defaults at 20MB. 20 | var DCPFeedBufferSizeBytes = uint32(20 * 1024 * 1024) 21 | 22 | // DCPFeedBufferAckThreshold is representative of the percentage of 23 | // the connection_buffer_size when the consumer will ack back to 24 | // the producer. 25 | var DCPFeedBufferAckThreshold = float32(0.8) 26 | 27 | // DCPNoopTimeIntervalSecs is representative of set_noop_interval 28 | // for DCP to enable no-op messages, defaults at 2min. 29 | var DCPNoopTimeIntervalSecs = uint32(120) 30 | 31 | // DCPFeedParams are DCP data-source/feed specific connection 32 | // parameters that may be part of a sourceParams JSON and is a 33 | // superset of CBAuthParams. DCPFeedParams holds the information used 34 | // to populate a cbdatasource.BucketDataSourceOptions on calls to 35 | // cbdatasource.NewBucketDataSource(). DCPFeedParams also implements 36 | // the couchbase.AuthHandler interface. 37 | type DCPFeedParams struct { 38 | AuthUser string `json:"authUser,omitempty"` // May be "" for no auth. 39 | AuthPassword string `json:"authPassword,omitempty"` 40 | 41 | AuthSaslUser string `json:"authSaslUser,omitempty"` // May be "" for no auth. 42 | AuthSaslPassword string `json:"authSaslPassword,omitempty"` 43 | 44 | ClientCertPath string `json:"clientCertPath,omitempty"` 45 | ClientKeyPath string `json:"clientKeyPath,omitempty"` 46 | 47 | // Factor (like 1.5) to increase sleep time between retries 48 | // in connecting to a cluster manager node. 49 | ClusterManagerBackoffFactor float32 `json:"clusterManagerBackoffFactor,omitempty"` 50 | 51 | // Initial sleep time (millisecs) before first retry to cluster manager. 52 | ClusterManagerSleepInitMS int `json:"clusterManagerSleepInitMS,omitempty"` 53 | 54 | // Maximum sleep time (millisecs) between retries to cluster manager. 55 | ClusterManagerSleepMaxMS int `json:"clusterManagerSleepMaxMS,omitempty"` 56 | 57 | // Factor (like 1.5) to increase sleep time between retries 58 | // in connecting to a data manager node. 59 | DataManagerBackoffFactor float32 `json:"dataManagerBackoffFactor,omitempty"` 60 | 61 | // Initial sleep time (millisecs) before first retry to data manager. 62 | DataManagerSleepInitMS int `json:"dataManagerSleepInitMS,omitempty"` 63 | 64 | // Maximum sleep time (millisecs) between retries to data manager. 65 | DataManagerSleepMaxMS int `json:"dataManagerSleepMaxMS,omitempty"` 66 | 67 | // Buffer size in bytes provided for UPR flow control. 68 | FeedBufferSizeBytes uint32 `json:"feedBufferSizeBytes,omitempty"` 69 | 70 | // Used for UPR flow control and buffer-ack messages when this 71 | // percentage of FeedBufferSizeBytes is reached. 72 | FeedBufferAckThreshold float32 `json:"feedBufferAckThreshold,omitempty"` 73 | 74 | // Time interval in seconds of NO-OP messages for UPR flow control, 75 | // needs to be set to a non-zero value to enable no-ops. 76 | NoopTimeIntervalSecs uint32 `json:"noopTimeIntervalSecs,omitempty"` 77 | 78 | // Used to specify whether the applications are interested 79 | // in receiving the xattrs information in a dcp stream. 80 | IncludeXAttrs bool `json:"includeXAttrs,omitempty"` 81 | 82 | // Used to specify whether the applications are not interested 83 | // in receiving the value for mutations in a dcp stream. 84 | NoValue bool `json:"noValue,omitempty"` 85 | 86 | // Scope within the bucket to stream data from. 87 | Scope string `json:"scope,omitempty"` 88 | 89 | // Collections within the scope that the feed would cover. 90 | Collections []string `json:"collections,omitempty"` 91 | } 92 | 93 | // NewDCPFeedParams returns a DCPFeedParams initialized with default 94 | // values. 95 | func NewDCPFeedParams() *DCPFeedParams { 96 | return &DCPFeedParams{} 97 | } 98 | 99 | // ------------------------------------------------------- 100 | 101 | // The FeedEx interface will be used to represent extended functionality 102 | // for a DCP Feed. These functions will be invoked by the application's error 103 | // handlers to decide on the course of the feed. 104 | type FeedEx interface { 105 | VerifySourceNotExists() (bool, string, error) 106 | GetBucketDetails() (string, string) 107 | NotifyMgrOnClose() 108 | } 109 | 110 | // ------------------------------------------------------- 111 | 112 | type VBucketMetaData struct { 113 | FailOverLog [][]uint64 `json:"failOverLog"` 114 | } 115 | 116 | func ParseOpaqueToUUID(b []byte) string { 117 | vmd := &VBucketMetaData{} 118 | err := UnmarshalJSON(b, &vmd) 119 | if err != nil { 120 | return "" 121 | } 122 | 123 | flogLen := len(vmd.FailOverLog) 124 | if flogLen < 1 || len(vmd.FailOverLog[flogLen-1]) < 1 { 125 | return "" 126 | } 127 | 128 | return fmt.Sprintf("%d", vmd.FailOverLog[0][0]) 129 | } 130 | -------------------------------------------------------------------------------- /feed_nil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "io" 13 | ) 14 | 15 | func init() { 16 | RegisterFeedType("nil", &FeedType{ 17 | Start: func(mgr *Manager, feedName, indexName, indexUUID, 18 | sourceType, sourceName, sourceUUID, params string, 19 | dests map[string]Dest) error { 20 | return mgr.registerFeed(NewNILFeed(feedName, indexName, dests)) 21 | }, 22 | Partitions: func(sourceType, sourceName, sourceUUID, sourceParams, 23 | server string, options map[string]string) ([]string, error) { 24 | return nil, nil 25 | }, 26 | Public: true, 27 | Description: "advanced/nil" + 28 | " - a nil data source has no data;" + 29 | " used for index aliases and testing", 30 | }) 31 | } 32 | 33 | // A NILFeed implements the Feed interface and never feeds any data to 34 | // its Dest instances. It's useful for testing and for pindexes that are 35 | // actually primary data sources. 36 | // 37 | // See also the "blackhole" pindex type for the "opposite equivalent" 38 | // of a NILFeed. 39 | type NILFeed struct { 40 | name string 41 | indexName string 42 | dests map[string]Dest 43 | } 44 | 45 | // NewNILFeed creates a ready-to-be-started NILFeed instance. 46 | func NewNILFeed(name, indexName string, dests map[string]Dest) *NILFeed { 47 | return &NILFeed{name: name, indexName: indexName, dests: dests} 48 | } 49 | 50 | func (t *NILFeed) Name() string { 51 | return t.name 52 | } 53 | 54 | func (t *NILFeed) IndexName() string { 55 | return t.indexName 56 | } 57 | 58 | func (t *NILFeed) Start() error { 59 | return nil 60 | } 61 | 62 | func (t *NILFeed) Close() error { 63 | return nil 64 | } 65 | 66 | func (t *NILFeed) Dests() map[string]Dest { 67 | return t.dests 68 | } 69 | 70 | func (t *NILFeed) Stats(w io.Writer) error { 71 | _, err := w.Write([]byte("{}")) 72 | return err 73 | } 74 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/couchbase/cbgt 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/couchbase/blance v0.1.6 7 | github.com/couchbase/cbauth v0.1.13 8 | github.com/couchbase/clog v0.1.0 9 | github.com/couchbase/go-couchbase v0.1.1 10 | github.com/couchbase/gocbcore/v10 v10.5.4 11 | github.com/couchbase/gomemcached v0.2.1 12 | github.com/couchbase/goutils v0.1.2 13 | github.com/couchbase/tools-common/cloud/v5 v5.0.3 14 | github.com/couchbase/tools-common/fs v1.0.2 15 | github.com/elazarl/go-bindata-assetfs v1.0.0 16 | github.com/gorilla/mux v1.8.0 17 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 18 | golang.org/x/net v0.19.0 19 | ) 20 | 21 | require ( 22 | github.com/couchbase/tools-common/testing v1.0.1 // indirect 23 | github.com/couchbase/tools-common/types v1.1.4 // indirect 24 | github.com/davecgh/go-spew v1.1.1 // indirect 25 | github.com/golang/snappy v0.0.4 // indirect 26 | github.com/google/uuid v1.6.0 // indirect 27 | github.com/pkg/errors v0.9.1 // indirect 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/stretchr/objx v0.5.1 // indirect 30 | github.com/stretchr/testify v1.8.4 // indirect 31 | github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect 32 | golang.org/x/crypto v0.17.0 // indirect 33 | golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect 34 | golang.org/x/sys v0.15.0 // indirect 35 | golang.org/x/text v0.14.0 // indirect 36 | golang.org/x/time v0.5.0 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /hibernate/progress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package hibernate 10 | 11 | func (hm *Manager) ReportProgress( 12 | onProgress func(progressEntries map[string]float64, errs []error)) error { 13 | var firstError error 14 | for progress := range hm.progressCh { 15 | if progress.Error != nil { 16 | hm.Logf("progress: error, progress: %+v", progress) 17 | 18 | if firstError == nil { 19 | firstError = progress.Error 20 | } 21 | 22 | onProgress(progress.TransferProgress, []error{progress.Error}) 23 | hm.Stop() 24 | continue 25 | } 26 | 27 | onProgress(progress.TransferProgress, nil) 28 | 29 | // TransferProgress contains pindexes which belong to the list of indexes to be 30 | // hibernated. 31 | for _, transferProgress := range progress.TransferProgress { 32 | if transferProgress < 1 { 33 | // if any of the pindexes belonging to the indexes to be hibernated have 34 | // progress < 1, continue listening to the channel. 35 | break 36 | } 37 | } 38 | } 39 | 40 | return firstError 41 | } 42 | -------------------------------------------------------------------------------- /httpclient.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "crypto/tls" 13 | "crypto/x509" 14 | "io" 15 | "net" 16 | "net/http" 17 | "sync" 18 | "time" 19 | 20 | "golang.org/x/net/http2" 21 | ) 22 | 23 | var HttpTransportDialContextTimeout = 30 * time.Second // Go's default is 30 secs. 24 | var HttpTransportDialContextKeepAlive = 30 * time.Second // Go's default is 30 secs. 25 | var HttpTransportMaxIdleConns = 300 // Go's default is 100 (0 means no limit). 26 | var HttpTransportMaxIdleConnsPerHost = 100 // Go's default is 2. 27 | var HttpTransportIdleConnTimeout = 90 * time.Second // Go's default is 90 secs. 28 | var HttpTransportTLSHandshakeTimeout = 10 * time.Second // Go's default is 10 secs. 29 | var HttpTransportExpectContinueTimeout = 1 * time.Second // Go's default is 1 secs. 30 | 31 | var httpClientM sync.RWMutex 32 | 33 | type HTTPClient interface { 34 | Get(string) (*http.Response, error) 35 | Post(string, string, io.Reader) (*http.Response, error) 36 | Do(*http.Request) (*http.Response, error) 37 | } 38 | 39 | // A wrapper over the HTTP client. 40 | type WrapperHTTPClient struct { 41 | Client *http.Client 42 | } 43 | 44 | var httpClient = &WrapperHTTPClient{ 45 | Client: http.DefaultClient, 46 | } 47 | 48 | func (w *WrapperHTTPClient) Get(url string) (*http.Response, error) { 49 | req, err := http.NewRequest("GET", url, nil) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return w.Do(req) 55 | } 56 | 57 | func (w *WrapperHTTPClient) Post(url, contentType string, body io.Reader) (*http.Response, error) { 58 | req, err := http.NewRequest("POST", url, body) 59 | if err != nil { 60 | return nil, err 61 | } 62 | req.Header.Set("Content-Type", contentType) 63 | 64 | return w.Do(req) 65 | } 66 | 67 | var UserAgentStr = "CB-SearchService" 68 | 69 | func (w *WrapperHTTPClient) Do(req *http.Request) (*http.Response, error) { 70 | req.Header.Set("User-Agent", UserAgentStr) 71 | return w.Client.Do(req) 72 | } 73 | 74 | func RegisterHttpClient() { 75 | RegisterConfigRefreshCallback("cbgt/httpClient", updateHttpClient) 76 | updateHttpClient(AuthChange_certificates | AuthChange_clientCertificates) 77 | } 78 | 79 | func HttpClient() HTTPClient { 80 | httpClientM.RLock() 81 | client := httpClient 82 | httpClientM.RUnlock() 83 | return client 84 | } 85 | 86 | func updateHttpClient(status int) error { 87 | if status&AuthChange_certificates != 0 || 88 | status&AuthChange_clientCertificates != 0 { 89 | transport := &http.Transport{ 90 | DialContext: (&net.Dialer{ 91 | Timeout: HttpTransportDialContextTimeout, 92 | KeepAlive: HttpTransportDialContextKeepAlive, 93 | }).DialContext, 94 | MaxIdleConns: HttpTransportMaxIdleConns, 95 | MaxIdleConnsPerHost: HttpTransportMaxIdleConnsPerHost, 96 | IdleConnTimeout: HttpTransportIdleConnTimeout, 97 | TLSHandshakeTimeout: HttpTransportTLSHandshakeTimeout, 98 | ExpectContinueTimeout: HttpTransportExpectContinueTimeout, 99 | TLSClientConfig: &tls.Config{}, 100 | } 101 | 102 | ss := GetSecuritySetting() 103 | rootCAs := x509.NewCertPool() 104 | ok := rootCAs.AppendCertsFromPEM(ss.CACertInBytes) 105 | if ok { 106 | transport.TLSClientConfig.RootCAs = rootCAs 107 | if ss.EncryptionEnabled && ss.ShouldClientsUseClientCert { 108 | transport.TLSClientConfig.Certificates = []tls.Certificate{ss.ClientCertificate} 109 | } 110 | _ = http2.ConfigureTransport(transport) 111 | } else { 112 | transport.TLSClientConfig.InsecureSkipVerify = true 113 | } 114 | 115 | client := &http.Client{ 116 | Transport: transport, 117 | } 118 | 119 | httpClientM.Lock() 120 | httpClient.Client = client 121 | httpClientM.Unlock() 122 | } 123 | 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /indexdefs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | //go:build metakv_test 10 | // +build metakv_test 11 | 12 | package cbgt 13 | 14 | import ( 15 | "testing" 16 | ) 17 | 18 | // This test attempts to set index defs, first with an outdated CAS, 19 | // leading to a CAS mismatch error and then with the latest CAS. 20 | func TestCfgSetIndexDefs(t *testing.T) { 21 | options := make(map[string]string) 22 | options["nsServerUrl"] = "http://127.0.0.0:9000" 23 | 24 | cfg, err := NewCfgMetaKv(NewUUID(), options) 25 | if err != nil { 26 | t.Errorf("Error getting Cfg is %v", err) 27 | return 28 | } 29 | 30 | indexDefs := NewIndexDefs(CfgGetVersion(cfg)) 31 | indexDefs.IndexDefs["testIndex"] = &IndexDef{UUID: NewUUID(), Name: "testIndex"} 32 | 33 | // Forcibly setting it in metakv since the metakv mock has no index defs. 34 | cas, err := CfgSetIndexDefs(cfg, indexDefs, CFG_CAS_FORCE) 35 | if err != nil { 36 | t.Errorf("Error setting index defs: %v", err) 37 | return 38 | } 39 | t.Logf("CAS on setting is now %d", cas) 40 | 41 | indexDefs, cas1, err := CfgGetIndexDefs(cfg) 42 | if err != nil { 43 | t.Errorf("Error getting index defs: %v", err) 44 | return 45 | } 46 | t.Logf("CAS on getting index defs is %d", cas1) 47 | 48 | indexDefs, cas2, err := CfgGetIndexDefs(cfg) 49 | if err != nil { 50 | t.Errorf("Error getting index defs: %v", err) 51 | return 52 | } 53 | t.Logf("CAS on getting is %d", cas2) 54 | 55 | indexDefs.UUID = NewUUID() 56 | cas3, err := CfgSetIndexDefs(cfg, indexDefs, cas1) 57 | t.Logf("CAS on getting index defs again is %d", cas3) 58 | if err != nil { 59 | // expect a CAS mismatch error since setting it with cas1 60 | // (not the latest CAS value). 61 | if _, ok := err.(*CfgCASError); !ok { 62 | t.Errorf("Did not expect an error other than CAS mismatch: %v", err) 63 | return 64 | } 65 | } 66 | 67 | indexDefs, latestCAS, err := CfgGetIndexDefs(cfg) 68 | indexDefs.UUID = NewUUID() 69 | _, err = CfgSetIndexDefs(cfg, indexDefs, latestCAS) 70 | if err != nil { 71 | if _, ok := err.(*CfgCASError); ok { 72 | t.Errorf("Unexpected CAS mismatch error.") 73 | } else { 74 | t.Errorf("Error setting index defs with CAS %d: %v", latestCAS, err) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "encoding/json" 13 | ) 14 | 15 | // Should only be overwritten during process init()'ialization. 16 | var ( 17 | MarshalJSON = json.Marshal 18 | UnmarshalJSON = json.Unmarshal 19 | ) 20 | -------------------------------------------------------------------------------- /licenses/BSL-Couchbase.txt: -------------------------------------------------------------------------------- 1 | COUCHBASE BUSINESS SOURCE LICENSE AGREEMENT 2 | 3 | Business Source License 1.1 4 | Licensor: Couchbase, Inc. 5 | Licensed Work: Couchbase Server Version 7.0 6 | The Licensed Work is © 2021-Present Couchbase, Inc. 7 | 8 | Additional Use Grant: You may make production use of the Licensed Work, provided 9 | you comply with the following conditions: 10 | 11 | (i) You may not prepare a derivative work based upon the Licensed Work and 12 | distribute or otherwise offer such derivative work, whether on a standalone 13 | basis or in combination with other products, applications, or services 14 | (including in any "as-a-service" offering, such as, by way of example, a 15 | software-as-a-service, database-as-a-service, or infrastructure-as-a-service 16 | offering, or any other offering based on a cloud computing or other type of 17 | hosted distribution model (collectively, "Hosted Offerings")), for a fee or 18 | otherwise on a commercial or other for-profit basis. 19 | 20 | (ii) You may not link the Licensed Work to, or otherwise include the Licensed 21 | Work in or with, any product, application, or service (including in any Hosted 22 | Offering) that is distributed or otherwise offered, whether on a standalone 23 | basis or in combination with other products, applications, or services for a fee 24 | or otherwise on a commercial or other for-profit basis. Condition (ii) shall not 25 | limit the generality of condition (i) above. 26 | 27 | 28 | Change Date: July 1, 2025 29 | 30 | Change License: Apache License, Version 2.0 31 | 32 | 33 | Notice 34 | 35 | The Business Source License (this document, or the "License") is not an Open 36 | Source license. However, the Licensed Work will eventually be made available 37 | under an Open Source License, as stated in this License. License text copyright 38 | © 2017 MariaDB Corporation Ab, All Rights Reserved. "Business Source License" is 39 | a trademark of MariaDB Corporation Ab. 40 | 41 | Terms 42 | 43 | The Licensor hereby grants You the right to copy, modify, create derivative 44 | works, redistribute, and make non-production use of the Licensed Work. The 45 | Licensor may make an Additional Use Grant, above, permitting limited production 46 | use. 47 | 48 | Effective on the Change Date, or the fourth anniversary of the first publicly 49 | available distribution of a specific version of the Licensed Work under this 50 | License, whichever comes first, the Licensor hereby grants you rights under the 51 | terms of the Change License, and the rights granted in the paragraph above 52 | terminate. 53 | 54 | If your use of the Licensed Work does not comply with the requirements currently 55 | in effect as described in this License, you must purchase a commercial license 56 | from the Licensor, its affiliated entities, or authorized resellers, or you must 57 | refrain from using the Licensed Work. 58 | 59 | All copies of the original and modified Licensed Work, and derivative works of 60 | the Licensed Work, are subject to this License. This License applies separately 61 | for each version of the Licensed Work and the Change Date may vary for each 62 | version of the Licensed Work released by Licensor. 63 | 64 | You must conspicuously display this License on each original or modified copy of 65 | the Licensed Work. If you receive the Licensed Work in original or modified form 66 | from a third party, the terms and conditions set forth in this License apply to 67 | your use of that work. 68 | 69 | Any use of the Licensed Work in violation of this License will automatically 70 | terminate your rights under this License for the current and all other versions 71 | of the Licensed Work. 72 | 73 | This License does not grant you any right in any trademark or logo of Licensor 74 | or its affiliates (provided that you may use a trademark or logo of Licensor as 75 | expressly required by this License). 76 | 77 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN 78 | "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS 79 | OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, 80 | FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. 81 | 82 | MariaDB hereby grants you permission to use this License's text to license your 83 | works, and to refer to it using the trademark "Business Source License", as long 84 | as you comply with the Covenants of Licensor below. 85 | 86 | Covenants of Licensor 87 | 88 | In consideration of the right to use this License's text and the "Business 89 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 90 | other recipients of the licensed work to be provided by Licensor: 91 | 92 | 1. To specify as the Change License the GPL Version 2.0 or any later version, or 93 | a license that is compatible with GPL Version 2.0 or a later version, where 94 | "compatible" means that software provided under the Change License can be 95 | included in a program with software provided under GPL Version 2.0 or a later 96 | version. Licensor may specify additional Change Licenses without limitation. 97 | 98 | 2. To either: (a) specify an additional grant of rights to use that does not 99 | impose any additional restriction on the right granted in this License, as the 100 | Additional Use Grant; or (b) insert the text "None". 101 | 102 | 3. To specify a Change Date. 103 | 104 | 4. Not to modify this License in any other way. 105 | -------------------------------------------------------------------------------- /msg_ring.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "sync" 15 | ) 16 | 17 | // MsgRingMaxSmallBufSize is the cutoff point, in bytes, in which a 18 | // msg ring categorizes a buf as small versus large for reuse. 19 | var MsgRingMaxSmallBufSize = 1024 20 | 21 | // MsgRingMaxSmallBufSize is the max pool size for reused buf's. 22 | var MsgRingMaxBufPoolSize = 8 23 | 24 | // A MsgRing wraps an io.Writer, and remembers a ring of previous 25 | // writes to the io.Writer. It is concurrent safe and is useful, for 26 | // example, for remembering recent log messages. 27 | type MsgRing struct { 28 | m sync.Mutex 29 | inner io.Writer 30 | Next int `json:"next"` 31 | Msgs [][]byte `json:"msgs"` 32 | 33 | SmallBufs [][]byte // Pool of small buffers. 34 | LargeBufs [][]byte // Pool of large buffers. 35 | } 36 | 37 | // NewMsgRing returns a MsgRing of a given ringSize. 38 | func NewMsgRing(inner io.Writer, ringSize int) (*MsgRing, error) { 39 | if inner == nil { 40 | return nil, fmt.Errorf("msg_ring: nil inner io.Writer") 41 | } 42 | if ringSize <= 0 { 43 | return nil, fmt.Errorf("msg_ring: non-positive ring size") 44 | } 45 | return &MsgRing{ 46 | inner: inner, 47 | Next: 0, 48 | Msgs: make([][]byte, ringSize), 49 | }, nil 50 | } 51 | 52 | // Implements the io.Writer interface. 53 | func (m *MsgRing) Write(p []byte) (n int, err error) { 54 | m.m.Lock() 55 | 56 | // Recycle the oldMsg into the small-vs-large pools, as long as 57 | // there's enough pool space. 58 | oldMsg := m.Msgs[m.Next] 59 | if oldMsg != nil { 60 | if len(oldMsg) <= MsgRingMaxSmallBufSize { 61 | if len(m.SmallBufs) < MsgRingMaxBufPoolSize { 62 | m.SmallBufs = append(m.SmallBufs) 63 | } 64 | } else { 65 | if len(m.LargeBufs) < MsgRingMaxBufPoolSize { 66 | m.LargeBufs = append(m.LargeBufs) 67 | } 68 | } 69 | } 70 | 71 | // Allocate a new buf or recycled buf from the pools. 72 | var buf []byte 73 | 74 | if len(p) <= MsgRingMaxSmallBufSize { 75 | if len(m.SmallBufs) > 0 { 76 | buf = m.SmallBufs[len(m.SmallBufs)-1] 77 | m.SmallBufs = m.SmallBufs[0 : len(m.SmallBufs)-1] 78 | } 79 | } else { 80 | // Although we wastefully throw away any cached large bufs 81 | // that aren't large enough, this simple approach doesn't 82 | // "learn" the wrong large buf size. 83 | for len(m.LargeBufs) > 0 && buf == nil { 84 | largeBuf := m.LargeBufs[len(m.LargeBufs)-1] 85 | m.LargeBufs = m.LargeBufs[0 : len(m.LargeBufs)-1] 86 | if len(p) <= cap(largeBuf) { 87 | buf = largeBuf 88 | } 89 | } 90 | } 91 | 92 | if buf == nil { 93 | buf = make([]byte, len(p)) 94 | } 95 | 96 | copy(buf[0:len(p)], p) 97 | 98 | m.Msgs[m.Next] = buf 99 | m.Next += 1 100 | if m.Next >= len(m.Msgs) { 101 | m.Next = 0 102 | } 103 | 104 | m.m.Unlock() 105 | 106 | return m.inner.Write(p) 107 | } 108 | 109 | // Retrieves the recent writes to the MsgRing. 110 | func (m *MsgRing) Messages() [][]byte { 111 | rv := make([][]byte, 0, len(m.Msgs)) 112 | 113 | m.m.Lock() 114 | 115 | // Pre-alloc a buf to hold a copy of all msgs. 116 | bufSize := 0 117 | for _, msg := range m.Msgs { 118 | bufSize += len(msg) 119 | } 120 | 121 | buf := make([]byte, 0, bufSize) 122 | 123 | n := len(m.Msgs) 124 | i := 0 125 | idx := m.Next 126 | for i < n { 127 | if msg := m.Msgs[idx]; msg != nil { 128 | bufLen := len(buf) 129 | buf = append(buf, msg...) 130 | rv = append(rv, buf[bufLen:]) 131 | } 132 | idx += 1 133 | if idx >= n { 134 | idx = 0 135 | } 136 | i += 1 137 | } 138 | 139 | m.m.Unlock() 140 | 141 | return rv 142 | } 143 | -------------------------------------------------------------------------------- /msg_ring_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "io/ioutil" 13 | "os" 14 | "testing" 15 | ) 16 | 17 | func TestMsgRing(t *testing.T) { 18 | if m, err := NewMsgRing(nil, 0); err == nil || m != nil { 19 | t.Errorf("expected no inner io.Writer to fail") 20 | } 21 | 22 | if m, err := NewMsgRing(os.Stderr, 0); err == nil || m != nil { 23 | t.Errorf("expected 0 ring size io.Writer to fail") 24 | } 25 | 26 | if m, err := NewMsgRing(os.Stderr, -1); err == nil || m != nil { 27 | t.Errorf("expected 0 ring size io.Writer to fail") 28 | } 29 | 30 | // ------------------------------------------------ 31 | 32 | m, err := NewMsgRing(ioutil.Discard, 1) 33 | if err != nil || m == nil { 34 | t.Errorf("expected NewMsgRing to work") 35 | } 36 | msgs := m.Messages() 37 | if msgs == nil || len(msgs) != 0 { 38 | t.Errorf("expected messages to be empty") 39 | } 40 | 41 | n, err := m.Write([]byte("test0\n")) 42 | if err != nil || n != 6 { 43 | t.Errorf("expected write to work") 44 | } 45 | msgs = m.Messages() 46 | if len(msgs) != 1 { 47 | t.Errorf("expected messages to have 1 msg") 48 | } 49 | if string(msgs[0]) != "test0\n" { 50 | t.Errorf("expected messages[0] to equal test0") 51 | } 52 | 53 | n, err = m.Write([]byte("test1\n")) 54 | if err != nil || n != 6 { 55 | t.Errorf("expected write to work") 56 | } 57 | msgs = m.Messages() 58 | if len(msgs) != 1 { 59 | t.Errorf("expected messages to still have 1 msg") 60 | } 61 | if string(msgs[0]) != "test1\n" { 62 | t.Errorf("expected messages[0] to equal test1") 63 | } 64 | 65 | // ------------------------------------------------ 66 | 67 | m, err = NewMsgRing(ioutil.Discard, 2) 68 | if err != nil || m == nil { 69 | t.Errorf("expected NewMsgRing to work") 70 | } 71 | msgs = m.Messages() 72 | if msgs == nil || len(msgs) != 0 { 73 | t.Errorf("expected messages to be empty") 74 | } 75 | 76 | n, err = m.Write([]byte("test0\n")) 77 | if err != nil || n != 6 { 78 | t.Errorf("expected write to work") 79 | } 80 | msgs = m.Messages() 81 | if len(msgs) != 1 { 82 | t.Errorf("expected messages to have 1 msg") 83 | } 84 | if string(msgs[0]) != "test0\n" { 85 | t.Errorf("expected messages[0] to equal test0") 86 | } 87 | 88 | n, err = m.Write([]byte("test1\n")) 89 | if err != nil || n != 6 { 90 | t.Errorf("expected write to work") 91 | } 92 | msgs = m.Messages() 93 | if len(msgs) != 2 { 94 | t.Errorf("expected messages to still have 2 msgs") 95 | } 96 | if string(msgs[0]) != "test0\n" { 97 | t.Errorf("expected messages[0] to equal test0") 98 | } 99 | if string(msgs[1]) != "test1\n" { 100 | t.Errorf("expected messages[1] to equal test1") 101 | } 102 | 103 | n, err = m.Write([]byte("test2\n")) 104 | if err != nil || n != 6 { 105 | t.Errorf("expected write to work") 106 | } 107 | msgs = m.Messages() 108 | if len(msgs) != 2 { 109 | t.Errorf("expected messages to still have 2 msgs") 110 | } 111 | if string(msgs[0]) != "test1\n" { 112 | t.Errorf("expected messages[0] to equal test1") 113 | } 114 | if string(msgs[1]) != "test2\n" { 115 | t.Errorf("expected messages[1] to equal test2") 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /rebalance/progress_table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rebalance 10 | 11 | import ( 12 | "bytes" 13 | "fmt" 14 | ) 15 | 16 | // ProgressTableString implements the ProgressToString func signature 17 | // by generating a tabular representation of the progress. 18 | func ProgressTableString(maxNodeLen, maxPIndexLen int, 19 | seenNodes map[string]bool, 20 | seenNodesSorted []string, 21 | seenPIndexes map[string]bool, 22 | seenPIndexesSorted []string, 23 | progressEntries map[string]map[string]map[string]*ProgressEntry) string { 24 | var b bytes.Buffer 25 | 26 | WriteProgressTable(&b, maxNodeLen, maxPIndexLen, 27 | seenNodes, 28 | seenNodesSorted, 29 | seenPIndexes, 30 | seenPIndexesSorted, 31 | progressEntries) 32 | 33 | return b.String() 34 | } 35 | 36 | // WriteProgressTable writes progress entries in tabular format to a 37 | // bytes buffer. 38 | func WriteProgressTable(b *bytes.Buffer, 39 | maxNodeLen, maxPIndexLen int, 40 | seenNodes map[string]bool, 41 | seenNodesSorted []string, 42 | seenPIndexes map[string]bool, 43 | seenPIndexesSorted []string, 44 | progressEntries map[string]map[string]map[string]*ProgressEntry, 45 | ) { 46 | written, _ := b.Write([]byte("%%%")) 47 | for i := written; i < maxPIndexLen; i++ { 48 | b.WriteByte(' ') 49 | } 50 | b.WriteByte(' ') 51 | 52 | for i, seenNode := range seenNodesSorted { 53 | if i > 0 { 54 | b.WriteByte(' ') 55 | } 56 | 57 | // TODO: Emit node human readable ADDR:PORT. 58 | b.Write([]byte(seenNode)) 59 | } 60 | b.WriteByte('\n') 61 | 62 | for _, seenPIndex := range seenPIndexesSorted { 63 | b.Write([]byte(" % ")) 64 | b.Write([]byte(seenPIndex)) 65 | 66 | for _, seenNode := range seenNodesSorted { 67 | b.WriteByte(' ') 68 | 69 | sourcePartitions, exists := 70 | progressEntries[seenPIndex] 71 | if !exists || sourcePartitions == nil { 72 | WriteProgressCell(b, nil, nil, maxNodeLen) 73 | continue 74 | } 75 | 76 | nodes, exists := sourcePartitions[""] 77 | if !exists || nodes == nil { 78 | WriteProgressCell(b, nil, nil, maxNodeLen) 79 | continue 80 | } 81 | 82 | pe, exists := nodes[seenNode] 83 | if !exists || pe == nil { 84 | WriteProgressCell(b, nil, nil, maxNodeLen) 85 | continue 86 | } 87 | 88 | WriteProgressCell(b, pe, sourcePartitions, maxNodeLen) 89 | } 90 | 91 | b.WriteByte('\n') 92 | } 93 | } 94 | 95 | var opMap = map[string]string{ 96 | "": ".", 97 | "add": "+", 98 | "del": "-", 99 | "promote": "P", 100 | "demote": "D", 101 | } 102 | 103 | // WriteProgressCell writes a cell in a progress table to a buffer. 104 | func WriteProgressCell(b *bytes.Buffer, 105 | pe *ProgressEntry, 106 | sourcePartitions map[string]map[string]*ProgressEntry, 107 | maxNodeLen int) { 108 | var written int 109 | 110 | totPct := 0.0 // To compute average pct. 111 | numPct := 0 112 | 113 | if pe != nil { 114 | written, _ = fmt.Fprintf(b, "%d ", pe.Move) 115 | 116 | if sourcePartitions != nil { 117 | n, _ := b.Write([]byte(opMap[pe.StateOp.Op])) 118 | written = written + n 119 | 120 | for sourcePartition, nodes := range sourcePartitions { 121 | if sourcePartition == "" { 122 | continue 123 | } 124 | 125 | pex := nodes[pe.Node] 126 | if pex == nil || pex.WantUUIDSeq.UUID == "" { 127 | continue 128 | } 129 | 130 | if pex.WantUUIDSeq.Seq <= pex.CurrUUIDSeq.Seq { 131 | totPct = totPct + 1.0 132 | numPct = numPct + 1 133 | continue 134 | } 135 | 136 | n := pex.CurrUUIDSeq.Seq - pex.InitUUIDSeq.Seq 137 | d := pex.WantUUIDSeq.Seq - pex.InitUUIDSeq.Seq 138 | if d > 0 { 139 | pct := float64(n) / float64(d) 140 | totPct = totPct + pct 141 | numPct = numPct + 1 142 | } 143 | } 144 | } 145 | } else { 146 | b.Write([]byte(" .")) 147 | written = 3 148 | } 149 | 150 | if numPct > 0 { 151 | avgPct := totPct / float64(numPct) 152 | 153 | n, _ := fmt.Fprintf(b, " %.1f%%", avgPct*100.0) 154 | written = written + n 155 | } 156 | 157 | for i := written; i < maxNodeLen; i++ { 158 | b.WriteByte(' ') 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /rebalance/run.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rebalance 10 | 11 | import ( 12 | "fmt" 13 | 14 | "github.com/couchbase/cbgt" 15 | ) 16 | 17 | // RunRebalance synchronously runs a rebalance and reports progress 18 | // until the rebalance is done or has errored. 19 | func RunRebalance(cfg cbgt.Cfg, server string, options map[string]string, 20 | nodesToRemove []string, favorMinNodes bool, dryRun bool, verbose int, 21 | progressToString ProgressToString) error { 22 | r, err := StartRebalance(cbgt.VERSION, cfg, server, options, 23 | nodesToRemove, 24 | RebalanceOptions{ 25 | FavorMinNodes: favorMinNodes, 26 | DryRun: dryRun, 27 | Verbose: verbose, 28 | }) 29 | if err != nil { 30 | return fmt.Errorf("run: StartRebalance, err: %v", err) 31 | } 32 | 33 | if progressToString == nil { 34 | progressToString = ProgressTableString 35 | } 36 | 37 | err = ReportProgress(r, progressToString) 38 | if err != nil { 39 | return fmt.Errorf("run: reportProgress, err: %v", err) 40 | } 41 | 42 | r.Stop() 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /rebalance/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /rest/monitor/monitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package monitor 10 | 11 | import ( 12 | "time" 13 | ) 14 | 15 | const DEFAULT_STATS_SAMPLE_INTERVAL_SECS = 10 16 | const DEFAULT_DIAG_SAMPLE_INTERVAL_SECS = 60 17 | const DEFAULT_CFG_SAMPLE_INTERVAL_SECS = 60 18 | 19 | // MonitorSample represents the information collected during 20 | // monitoring and sampling a node. 21 | type MonitorSample struct { 22 | Kind string // Ex: "/api/cfg", "/api/stats", "/api/diag". 23 | Url string // Ex: "http://10.0.0.1:8095". 24 | UUID string 25 | Start time.Time // When we started to get this sample. 26 | Duration time.Duration // How long it took to get this sample. 27 | Error error 28 | Data []byte 29 | } 30 | 31 | // UrlUUID associates a URL with a UUID. 32 | type UrlUUID struct { 33 | Url string 34 | UUID string 35 | } 36 | -------------------------------------------------------------------------------- /rest/monitor/nodes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package monitor 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "net/http" 15 | "time" 16 | ) 17 | 18 | // A MonitorNodes struct holds all the tracking information for the 19 | // StartMonitorNodes operation. 20 | type MonitorNodes struct { 21 | urlUUIDs []UrlUUID // Array of base REST URL's to monitor. 22 | sampleCh chan MonitorSample 23 | options MonitorNodesOptions 24 | stopCh chan struct{} 25 | } 26 | 27 | type MonitorNodesOptions struct { 28 | StatsSampleInterval time.Duration // Ex: 1 * time.Second. 29 | StatsSampleDisable bool 30 | 31 | DiagSampleInterval time.Duration 32 | DiagSampleDisable bool 33 | 34 | // Optional, defaults to http.Get(); this is used, for example, 35 | // for unit testing. 36 | HttpGet func(url string) (resp *http.Response, err error) 37 | } 38 | 39 | // StartMonitorNodes begins REST stats and diag sampling from a fixed 40 | // set of cbgt nodes. Higher level parts (like StartMonitorCluster) 41 | // should handle situations of node membership changes by stopping and 42 | // restarting StartMonitorNodes() as needed. 43 | // 44 | // The cbgt REST URL endpoints that are monitored are [url]/api/stats 45 | // and [url]/api/diag. 46 | func StartMonitorNodes( 47 | urlUUIDs []UrlUUID, 48 | sampleCh chan MonitorSample, 49 | options MonitorNodesOptions, 50 | ) (*MonitorNodes, error) { 51 | m := &MonitorNodes{ 52 | urlUUIDs: urlUUIDs, 53 | sampleCh: sampleCh, 54 | options: options, 55 | stopCh: make(chan struct{}), 56 | } 57 | 58 | for _, urlUUID := range urlUUIDs { 59 | go m.runNode(urlUUID) 60 | } 61 | 62 | return m, nil 63 | } 64 | 65 | func (m *MonitorNodes) Stop() { 66 | close(m.stopCh) 67 | } 68 | 69 | func (m *MonitorNodes) runNode(urlUUID UrlUUID) { 70 | statsSampleInterval := m.options.StatsSampleInterval 71 | if statsSampleInterval <= 0 { 72 | statsSampleInterval = 73 | DEFAULT_STATS_SAMPLE_INTERVAL_SECS * time.Second 74 | } 75 | 76 | diagSampleInterval := m.options.StatsSampleInterval 77 | if diagSampleInterval <= 0 { 78 | diagSampleInterval = 79 | DEFAULT_DIAG_SAMPLE_INTERVAL_SECS * time.Second 80 | } 81 | 82 | statsTicker := time.NewTicker(statsSampleInterval) 83 | defer statsTicker.Stop() 84 | 85 | diagTicker := time.NewTicker(diagSampleInterval) 86 | defer diagTicker.Stop() 87 | 88 | if !m.options.StatsSampleDisable { 89 | m.sample(urlUUID, "/api/stats?partitions=true", time.Now()) 90 | } else { 91 | m.sample(urlUUID, "/api/stats?partitions=true&seqno=false", time.Now()) 92 | } 93 | 94 | if !m.options.DiagSampleDisable { 95 | m.sample(urlUUID, "/api/diag", time.Now()) 96 | } 97 | 98 | for { 99 | select { 100 | case <-m.stopCh: 101 | return 102 | 103 | case t, ok := <-statsTicker.C: 104 | if !ok { 105 | return 106 | } 107 | 108 | if !m.options.StatsSampleDisable { 109 | m.sample(urlUUID, "/api/stats?partitions=true", t) 110 | } else { 111 | m.sample(urlUUID, "/api/stats?partitions=true&seqno=false", t) 112 | } 113 | 114 | case t, ok := <-diagTicker.C: 115 | if !ok { 116 | return 117 | } 118 | 119 | if !m.options.DiagSampleDisable { 120 | m.sample(urlUUID, "/api/diag", t) 121 | } 122 | } 123 | } 124 | } 125 | 126 | func (m *MonitorNodes) sample( 127 | urlUUID UrlUUID, 128 | kind string, 129 | start time.Time) { 130 | httpGet := m.options.HttpGet 131 | if httpGet == nil { 132 | httpGet = http.Get 133 | } 134 | 135 | res, err := httpGet(urlUUID.Url + kind) 136 | 137 | duration := time.Now().Sub(start) 138 | 139 | data := []byte(nil) 140 | if err == nil && res != nil { 141 | if res.StatusCode == 200 { 142 | var dataErr error 143 | 144 | data, dataErr = io.ReadAll(res.Body) 145 | if err == nil && dataErr != nil { 146 | err = dataErr 147 | } 148 | } else { 149 | err = fmt.Errorf("nodes: sample res.StatusCode not 200,"+ 150 | " res: %#v, urlUUID: %#v, kind: %s, err: %v", 151 | res, urlUUID, kind, err) 152 | } 153 | 154 | res.Body.Close() 155 | } else { 156 | err = fmt.Errorf("nodes: sample,"+ 157 | " res: %#v, urlUUID: %#v, kind: %s, err: %v", 158 | res, urlUUID, kind, err) 159 | } 160 | 161 | monitorSample := MonitorSample{ 162 | Kind: kind, 163 | Url: urlUUID.Url, 164 | UUID: urlUUID.UUID, 165 | Start: start, 166 | Duration: duration, 167 | Error: err, 168 | Data: data, 169 | } 170 | 171 | select { 172 | case <-m.stopCh: 173 | case m.sampleCh <- monitorSample: 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /rest/rest_delete_index.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rest 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | "net/http" 15 | "sync/atomic" 16 | 17 | "github.com/couchbase/cbgt" 18 | log "github.com/couchbase/clog" 19 | ) 20 | 21 | var ( 22 | totalDeleteIndexReq uint64 23 | totalDeleteIndexBadReqErr uint64 24 | totalDeleteIndexIntSerErr uint64 25 | totalDeleteIndexReqOk uint64 26 | ) 27 | 28 | func GatherDeleteIndexStats() map[string]interface{} { 29 | rv := make(map[string]interface{}) 30 | rv["total_delete_index_request"] = 31 | atomic.LoadUint64(&totalDeleteIndexReq) 32 | rv["total_delete_index_bad_request_error"] = 33 | atomic.LoadUint64(&totalDeleteIndexBadReqErr) 34 | rv["total_delete_index_internal_server_error"] = 35 | atomic.LoadUint64(&totalDeleteIndexIntSerErr) 36 | rv["total_delete_index_request_ok"] = 37 | atomic.LoadUint64(&totalDeleteIndexReqOk) 38 | 39 | return rv 40 | } 41 | 42 | // DeleteIndexHandler is a REST handler that processes an index 43 | // deletion request. 44 | type DeleteIndexHandler struct { 45 | mgr *cbgt.Manager 46 | } 47 | 48 | func NewDeleteIndexHandler(mgr *cbgt.Manager) *DeleteIndexHandler { 49 | return &DeleteIndexHandler{mgr: mgr} 50 | } 51 | 52 | func (h *DeleteIndexHandler) RESTOpts(opts map[string]string) { 53 | opts["param: indexName"] = "required, string, URL path parameter\n\n" + 54 | "The name of the index definition to be deleted." 55 | } 56 | 57 | func (h *DeleteIndexHandler) ServeHTTP( 58 | w http.ResponseWriter, req *http.Request) { 59 | 60 | ca := req.Header.Get(CLUSTER_ACTION) 61 | if ca != "orchestrator-forwarded" { 62 | atomic.AddUint64(&totalDeleteIndexReq, 1) 63 | } 64 | 65 | indexName := IndexNameLookup(req) 66 | if indexName == "" { 67 | ShowError(w, req, "rest_delete_index: index name is required", 68 | http.StatusBadRequest) 69 | atomic.AddUint64(&totalDeleteIndexBadReqErr, 1) 70 | return 71 | } 72 | 73 | if ca != "orchestrator-forwarded" { 74 | // defer all error handling to the default flow. 75 | indexDefs, _, _ := cbgt.CfgGetIndexDefs(h.mgr.Cfg()) 76 | if indexDefs != nil { 77 | indexDef, _ := indexDefs.IndexDefs[indexName] 78 | if indexDef != nil && (indexDef.Type == "fulltext-index" || 79 | indexDef.Type == "fulltext-alias") { 80 | // if there was successful proxying of the request to 81 | // the rebalance orchestrator node, then return early. 82 | if proxyOrchestratorNodeDone(w, req, h.mgr) { 83 | return 84 | } 85 | } 86 | } 87 | } 88 | 89 | log.Printf("rest_delete_index: delete index request received for %v", indexName) 90 | indexUUID, err := h.mgr.DeleteIndexEx(indexName, "") 91 | if err != nil { 92 | var internalServerError *cbgt.InternalServerError 93 | if errors.As(err, &internalServerError) { 94 | ShowError(w, req, fmt.Sprintf("rest_delete_index:"+ 95 | " error deleting index, err: %v", err), http.StatusInternalServerError) 96 | atomic.AddUint64(&totalDeleteIndexIntSerErr, 1) 97 | } else { 98 | ShowError(w, req, fmt.Sprintf("rest_delete_index:"+ 99 | " error deleting index, err: %v", err), http.StatusBadRequest) 100 | atomic.AddUint64(&totalDeleteIndexBadReqErr, 1) 101 | } 102 | return 103 | } 104 | 105 | atomic.AddUint64(&totalDeleteIndexReqOk, 1) 106 | 107 | MustEncode(w, struct { 108 | Status string `json:"status"` 109 | UUID string `json:"uuid"` 110 | }{ 111 | Status: "ok", 112 | UUID: indexUUID, 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /rest/rest_log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rest 10 | 11 | import ( 12 | "net/http" 13 | 14 | "github.com/couchbase/cbgt" 15 | ) 16 | 17 | // TODO: Need to give the codebase a scrub of its log 18 | // messages and fmt.Errorf()'s. 19 | 20 | // LogGetHandler is a REST handler that retrieves recent log messages. 21 | type LogGetHandler struct { 22 | mgr *cbgt.Manager 23 | mr *cbgt.MsgRing 24 | } 25 | 26 | func NewLogGetHandler( 27 | mgr *cbgt.Manager, mr *cbgt.MsgRing) *LogGetHandler { 28 | return &LogGetHandler{mgr: mgr, mr: mr} 29 | } 30 | 31 | func (h *LogGetHandler) ServeHTTP( 32 | w http.ResponseWriter, req *http.Request) { 33 | w.Write([]byte(`{"messages":[`)) 34 | if h.mr != nil { 35 | for i, message := range h.mr.Messages() { 36 | buf, err := cbgt.MarshalJSON(string(message)) 37 | if err == nil { 38 | if i > 0 { 39 | w.Write(cbgt.JsonComma) 40 | } 41 | w.Write(buf) 42 | } 43 | } 44 | } 45 | w.Write([]byte(`],"events":[`)) 46 | if h.mgr != nil { 47 | first := true 48 | h.mgr.VisitEvents(func(event []byte) { 49 | if !first { 50 | w.Write(cbgt.JsonComma) 51 | } 52 | first = false 53 | w.Write(event) 54 | }) 55 | } 56 | w.Write([]byte(`]}`)) 57 | } 58 | -------------------------------------------------------------------------------- /rest/rest_meta.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rest 10 | 11 | import ( 12 | "net/http" 13 | 14 | "github.com/couchbase/cbgt" 15 | ) 16 | 17 | // ManagerMetaHandler is a REST handler that returns metadata about a 18 | // manager/node. 19 | type ManagerMetaHandler struct { 20 | mgr *cbgt.Manager 21 | meta map[string]RESTMeta 22 | } 23 | 24 | func NewManagerMetaHandler(mgr *cbgt.Manager, 25 | meta map[string]RESTMeta) *ManagerMetaHandler { 26 | return &ManagerMetaHandler{mgr: mgr, meta: meta} 27 | } 28 | 29 | // MetaDesc represents a part of the JSON of a ManagerMetaHandler REST 30 | // response. 31 | type MetaDesc struct { 32 | Description string `json:"description"` 33 | StartSample interface{} `json:"startSample"` 34 | StartSampleDocs map[string]string `json:"startSampleDocs"` 35 | } 36 | 37 | // MetaDescSource represents the source-type/feed-type parts of the 38 | // JSON of a ManagerMetaHandler REST response. 39 | type MetaDescSource MetaDesc 40 | 41 | // MetaDescSource represents the index-type parts of 42 | // the JSON of a ManagerMetaHandler REST response. 43 | type MetaDescIndex struct { 44 | MetaDesc 45 | 46 | CanCount bool `json:"canCount"` 47 | CanQuery bool `json:"canQuery"` 48 | 49 | QuerySamples interface{} `json:"querySamples"` 50 | QueryHelp string `json:"queryHelp"` 51 | 52 | UI map[string]string `json:"ui"` 53 | } 54 | 55 | func (h *ManagerMetaHandler) ServeHTTP( 56 | w http.ResponseWriter, req *http.Request) { 57 | ps := cbgt.IndexPartitionSettings(h.mgr) 58 | 59 | startSamples := map[string]interface{}{ 60 | "planParams": &cbgt.PlanParams{ 61 | MaxPartitionsPerPIndex: ps.MaxPartitionsPerPIndex, 62 | IndexPartitions: ps.IndexPartitions, 63 | }, 64 | } 65 | 66 | // Key is sourceType, value is description. 67 | sourceTypes := map[string]*MetaDescSource{} 68 | for sourceType, f := range cbgt.FeedTypes { 69 | if f.Public { 70 | sourceTypes[sourceType] = &MetaDescSource{ 71 | Description: f.Description, 72 | StartSample: f.StartSample, 73 | StartSampleDocs: f.StartSampleDocs, 74 | } 75 | } 76 | } 77 | 78 | // Key is indexType, value is description. 79 | indexTypes := map[string]*MetaDescIndex{} 80 | for indexType, t := range cbgt.PIndexImplTypes { 81 | mdi := &MetaDescIndex{ 82 | MetaDesc: MetaDesc{ 83 | Description: t.Description, 84 | StartSample: t.StartSample, 85 | }, 86 | CanCount: t.Count != nil, 87 | CanQuery: t.Query != nil, 88 | QueryHelp: t.QueryHelp, 89 | UI: t.UI, 90 | } 91 | 92 | if t.QuerySamples != nil { 93 | mdi.QuerySamples = t.QuerySamples() 94 | } 95 | 96 | indexTypes[indexType] = mdi 97 | } 98 | 99 | r := map[string]interface{}{ 100 | "status": "ok", 101 | "startSamples": startSamples, 102 | "sourceTypes": sourceTypes, 103 | "indexNameRE": cbgt.INDEX_NAME_REGEXP, 104 | "indexTypes": indexTypes, 105 | "refREST": h.meta, 106 | } 107 | 108 | for _, t := range cbgt.PIndexImplTypes { 109 | if t.MetaExtra != nil { 110 | t.MetaExtra(r) 111 | } 112 | } 113 | 114 | MustEncode(w, r) 115 | } 116 | -------------------------------------------------------------------------------- /rest/rest_noop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rest 10 | 11 | import ( 12 | "io" 13 | "io/ioutil" 14 | "net/http" 15 | ) 16 | 17 | // NoopHandler is a REST handler that returns nothing. 18 | type NoopHandler struct{} 19 | 20 | func (h *NoopHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 21 | io.Copy(ioutil.Discard, req.Body) 22 | } 23 | -------------------------------------------------------------------------------- /rest/rest_source.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rest 10 | 11 | import ( 12 | "net/http" 13 | 14 | "github.com/couchbase/cbgt" 15 | ) 16 | 17 | // --------------------------------------------------- 18 | 19 | // SourcePartitionSeqsHandler is a REST handler for retrieving the 20 | // partition seqs from the data source for an index. 21 | type SourcePartitionSeqsHandler struct { 22 | mgr *cbgt.Manager 23 | } 24 | 25 | func NewSourcePartitionSeqsHandler(mgr *cbgt.Manager) *SourcePartitionSeqsHandler { 26 | return &SourcePartitionSeqsHandler{mgr: mgr} 27 | } 28 | 29 | func (h *SourcePartitionSeqsHandler) RESTOpts(opts map[string]string) { 30 | opts["param: indexName"] = 31 | "required, string, URL path parameter\n\n" + 32 | "The name of the index whose partition seqs should be retrieved." 33 | } 34 | 35 | func (h *SourcePartitionSeqsHandler) ServeHTTP( 36 | w http.ResponseWriter, req *http.Request) { 37 | indexName := IndexNameLookup(req) 38 | if indexName == "" { 39 | ShowError(w, req, "index name is required", http.StatusBadRequest) 40 | return 41 | } 42 | 43 | _, indexDefsByName, err := h.mgr.GetIndexDefs(false) 44 | if err != nil { 45 | ShowError(w, req, "could not retrieve index defs", 46 | http.StatusInternalServerError) 47 | return 48 | } 49 | 50 | indexDef, exists := indexDefsByName[indexName] 51 | if !exists || indexDef == nil { 52 | ShowError(w, req, "index not found", http.StatusBadRequest) 53 | return 54 | } 55 | 56 | indexUUID := req.FormValue("indexUUID") 57 | if indexUUID != "" && indexUUID != indexDef.UUID { 58 | ShowError(w, req, "wrong index UUID", http.StatusBadRequest) 59 | return 60 | } 61 | 62 | if indexDef.SourceParams == "" { 63 | MustEncode(w, nil) 64 | return 65 | } 66 | 67 | feedType, exists := cbgt.FeedTypes[indexDef.SourceType] 68 | if !exists || feedType == nil { 69 | ShowError(w, req, "unknown source type", http.StatusInternalServerError) 70 | return 71 | } 72 | 73 | if feedType.PartitionSeqs == nil { 74 | MustEncode(w, nil) 75 | return 76 | } 77 | 78 | partitionSeqs, err := feedType.PartitionSeqs( 79 | indexDef.SourceType, indexDef.SourceName, indexDef.SourceUUID, 80 | indexDef.SourceParams, h.mgr.Server(), h.mgr.Options()) 81 | if err != nil { 82 | ShowError(w, req, "could not retrieve partition seqs", 83 | http.StatusInternalServerError) 84 | return 85 | } 86 | 87 | MustEncode(w, partitionSeqs) 88 | } 89 | 90 | // --------------------------------------------------- 91 | 92 | // SourceStatsHandler is a REST handler for retrieving the 93 | // partition seqs from the data source for an index. 94 | type SourceStatsHandler struct { 95 | mgr *cbgt.Manager 96 | } 97 | 98 | func NewSourceStatsHandler(mgr *cbgt.Manager) *SourceStatsHandler { 99 | return &SourceStatsHandler{mgr: mgr} 100 | } 101 | 102 | func (h *SourceStatsHandler) RESTOpts(opts map[string]string) { 103 | opts["param: indexName"] = 104 | "required, string, URL path parameter\n\n" + 105 | "The name of the index whose partition seqs should be retrieved." 106 | opts["param: statsKind"] = 107 | "optional, string\n\n" + 108 | "Optional source-specific string for kind of stats wanted." 109 | } 110 | 111 | func (h *SourceStatsHandler) ServeHTTP( 112 | w http.ResponseWriter, req *http.Request) { 113 | indexName := IndexNameLookup(req) 114 | if indexName == "" { 115 | ShowError(w, req, "index name is required", http.StatusBadRequest) 116 | return 117 | } 118 | 119 | _, indexDefsByName, err := h.mgr.GetIndexDefs(false) 120 | if err != nil { 121 | ShowError(w, req, "could not retrieve index defs", 122 | http.StatusInternalServerError) 123 | return 124 | } 125 | 126 | indexDef, exists := indexDefsByName[indexName] 127 | if !exists || indexDef == nil { 128 | ShowError(w, req, "index not found", http.StatusBadRequest) 129 | return 130 | } 131 | 132 | indexUUID := req.FormValue("indexUUID") 133 | if indexUUID != "" && indexUUID != indexDef.UUID { 134 | ShowError(w, req, "wrong index UUID", http.StatusBadRequest) 135 | return 136 | } 137 | 138 | feedType, exists := cbgt.FeedTypes[indexDef.SourceType] 139 | if !exists || feedType == nil { 140 | ShowError(w, req, "unknown source type", http.StatusInternalServerError) 141 | return 142 | } 143 | 144 | if feedType.Stats == nil { 145 | MustEncode(w, nil) 146 | return 147 | } 148 | 149 | stats, err := feedType.Stats( 150 | indexDef.SourceType, indexDef.SourceName, indexDef.SourceUUID, 151 | indexDef.SourceParams, h.mgr.Server(), h.mgr.Options(), 152 | req.FormValue("statsKind")) 153 | if err != nil { 154 | ShowError(w, req, "could not retrieve stats", http.StatusInternalServerError) 155 | return 156 | } 157 | 158 | MustEncode(w, stats) 159 | } 160 | -------------------------------------------------------------------------------- /rest/static.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rest 10 | 11 | import ( 12 | "net/http" 13 | "os" 14 | 15 | "github.com/elazarl/go-bindata-assetfs" 16 | 17 | "github.com/gorilla/mux" 18 | 19 | log "github.com/couchbase/clog" 20 | 21 | "github.com/couchbase/cbgt" 22 | ) 23 | 24 | // AssetFS returns the assetfs.AssetFS "filesystem" that holds static 25 | // HTTP resources (css/html/js/images, etc) for the web UI. 26 | // 27 | // Users might introduce their own static HTTP resources and override 28 | // resources from AssetFS() with their own resource lookup chaining. 29 | func AssetFS() *assetfs.AssetFS { 30 | return assetFS() 31 | } 32 | 33 | // InitStaticRouter adds static HTTP resource routes to a router. 34 | func InitStaticRouter(r *mux.Router, staticDir, staticETag string, 35 | pages []string, pagesHandler http.Handler) *mux.Router { 36 | return InitStaticRouterEx(r, staticDir, staticETag, 37 | pages, pagesHandler, nil) 38 | } 39 | 40 | // InitStaticRouterEx is like InitStaticRouter, but with optional 41 | // manager parameter for more options. 42 | func InitStaticRouterEx(r *mux.Router, staticDir, staticETag string, 43 | pages []string, pagesHandler http.Handler, 44 | mgr *cbgt.Manager) *mux.Router { 45 | prefix := "" 46 | if mgr != nil { 47 | prefix = mgr.GetOption("urlPrefix") 48 | } 49 | 50 | PIndexTypesInitRouter(r, "static.before", mgr) 51 | 52 | var s http.FileSystem 53 | if staticDir != "" { 54 | if _, err := os.Stat(staticDir); err == nil { 55 | log.Printf("http: serving assets from staticDir: %s", staticDir) 56 | s = http.Dir(staticDir) 57 | } 58 | } 59 | if s == nil { 60 | log.Printf("http: serving assets from embedded data") 61 | s = AssetFS() 62 | } 63 | 64 | staticRoutes := AssetNames() 65 | 66 | for _, route := range staticRoutes { 67 | // Add '/' prefix so the router doesn't flag paths that do 68 | // not begin with a slash. 69 | route = "/" + route 70 | r.Handle(prefix+route, http.StripPrefix(prefix+"/static/", 71 | ETagFileHandler{http.FileServer(s), staticETag})) 72 | } 73 | 74 | // Redirect any page the client asks for. 75 | for _, p := range pages { 76 | if pagesHandler != nil { 77 | r.Handle(p, pagesHandler) 78 | } else { 79 | r.Handle(p, RewriteURL("/", http.FileServer(s))) 80 | } 81 | } 82 | 83 | r.Handle(prefix+"/index.html", 84 | http.RedirectHandler(prefix+"/static/index.html", 302)) 85 | r.Handle(prefix+"/", 86 | http.RedirectHandler(prefix+"/static/index.html", 302)) 87 | 88 | PIndexTypesInitRouter(r, "static.after", mgr) 89 | 90 | return r 91 | } 92 | 93 | type ETagFileHandler struct { 94 | h http.Handler 95 | etag string 96 | } 97 | 98 | func (mfh ETagFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 99 | if mfh.etag != "" { 100 | w.Header().Set("Etag", mfh.etag) 101 | } 102 | mfh.h.ServeHTTP(w, r) 103 | } 104 | 105 | // RewriteURL is a helper function that returns a URL path rewriter 106 | // HandlerFunc, rewriting the URL path to a provided "to" string. 107 | func RewriteURL(to string, h http.Handler) http.Handler { 108 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 109 | r.URL.Path = to 110 | h.ServeHTTP(w, r) 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /rest/static/css/app.css: -------------------------------------------------------------------------------- 1 | .highlight { 2 | background: yellow; 3 | padding:2px 2px; 4 | } 5 | 6 | .logo { 7 | margin-top: -8px; 8 | vertical-align: top; 9 | width: 32px; 10 | height: 32px; 11 | } 12 | 13 | .debug { 14 | font-family: fixed; 15 | } 16 | 17 | .overide-brand { 18 | float: left; 19 | height: 50px; 20 | padding: 15px 15px; 21 | font-size: 18px; 22 | line-height: 20px; 23 | } 24 | .overide-brand:hover, 25 | .overide-brand:focus { 26 | text-decoration: none; 27 | } 28 | @media (min-width: 768px) { 29 | .navbar > .container .overide-brand, 30 | .navbar > .container-fluid .overide-brand { 31 | margin-left: -15px; 32 | } 33 | } 34 | 35 | .navbar ul.navbar-nav li a { 36 | color: #aaa; 37 | } 38 | .navbar-inverse .overide-brand { 39 | color: white; 40 | } 41 | 42 | .swatch { 43 | display: inline-block; 44 | width: 10px; 45 | height: 10px; 46 | margin: 0 8px 0 0; 47 | } 48 | 49 | .label { 50 | display: inline-block; 51 | } 52 | 53 | .line { 54 | display: inline-block; 55 | margin: 0 0 0 30px; 56 | } 57 | 58 | .legend .label { 59 | color: black; 60 | } 61 | #legend { 62 | text-align: center; 63 | } 64 | 65 | .rickshaw_graph .detail { 66 | background: none; 67 | } 68 | .rickshaw_graph .detail .x_label { display: none } 69 | 70 | ul.nav-tabs { 71 | margin-bottom: 2em; 72 | } 73 | 74 | .list-custom span.selected { 75 | background-color: lightgrey; 76 | font-weight: bold; 77 | } 78 | -------------------------------------------------------------------------------- /rest/static/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | /* 11 | * Global add-ons 12 | */ 13 | 14 | .sub-header { 15 | padding-bottom: 10px; 16 | border-bottom: 1px solid #eee; 17 | } 18 | 19 | /* 20 | * Top navigation 21 | * Hide default border to remove 1px line. 22 | */ 23 | .navbar-fixed-top { 24 | border: 0; 25 | } 26 | 27 | /* 28 | * Sidebar 29 | */ 30 | 31 | /* Hide for mobile, show later */ 32 | .sidebar { 33 | display: none; 34 | } 35 | @media (min-width: 768px) { 36 | .sidebar { 37 | position: fixed; 38 | top: 51px; 39 | bottom: 0; 40 | left: 0; 41 | z-index: 1000; 42 | display: block; 43 | padding: 20px; 44 | overflow-x: hidden; 45 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 46 | background-color: #f5f5f5; 47 | border-right: 1px solid #eee; 48 | } 49 | } 50 | 51 | /* Sidebar navigation */ 52 | .nav-sidebar { 53 | margin-right: -21px; /* 20px padding + 1px border */ 54 | margin-bottom: 20px; 55 | margin-left: -20px; 56 | } 57 | .nav-sidebar > li > a { 58 | padding-right: 20px; 59 | padding-left: 20px; 60 | } 61 | .nav-sidebar > .active > a, 62 | .nav-sidebar > .active > a:hover, 63 | .nav-sidebar > .active > a:focus { 64 | color: #fff; 65 | background-color: #428bca; 66 | } 67 | 68 | /* 69 | * Main content 70 | */ 71 | 72 | .main { 73 | padding: 20px; 74 | } 75 | @media (min-width: 768px) { 76 | .main { 77 | padding-right: 40px; 78 | padding-left: 40px; 79 | } 80 | } 81 | .main .page-header { 82 | margin-top: 0; 83 | } 84 | 85 | /* 86 | * Placeholder dashboard ideas 87 | */ 88 | 89 | .placeholders { 90 | margin-bottom: 30px; 91 | text-align: center; 92 | } 93 | .placeholders h4 { 94 | margin-bottom: 0; 95 | } 96 | .placeholder { 97 | margin-bottom: 20px; 98 | } 99 | .placeholder img { 100 | display: inline-block; 101 | border-radius: 50%; 102 | } 103 | -------------------------------------------------------------------------------- /rest/static/css/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */ 2 | /** 3 | * prism.js default theme for JavaScript, CSS and HTML 4 | * Based on dabblet (http://dabblet.com) 5 | * @author Lea Verou 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: black; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | } 29 | 30 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 31 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 32 | text-shadow: none; 33 | background: #b3d4fc; 34 | } 35 | 36 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 37 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 38 | text-shadow: none; 39 | background: #b3d4fc; 40 | } 41 | 42 | @media print { 43 | code[class*="language-"], 44 | pre[class*="language-"] { 45 | text-shadow: none; 46 | } 47 | } 48 | 49 | /* Code blocks */ 50 | pre[class*="language-"] { 51 | padding: 1em; 52 | margin: .5em 0; 53 | overflow: auto; 54 | } 55 | 56 | :not(pre) > code[class*="language-"], 57 | pre[class*="language-"] { 58 | background: #f5f2f0; 59 | } 60 | 61 | /* Inline code */ 62 | :not(pre) > code[class*="language-"] { 63 | padding: .1em; 64 | border-radius: .3em; 65 | } 66 | 67 | .token.comment, 68 | .token.prolog, 69 | .token.doctype, 70 | .token.cdata { 71 | color: slategray; 72 | } 73 | 74 | .token.punctuation { 75 | color: #999; 76 | } 77 | 78 | .namespace { 79 | opacity: .7; 80 | } 81 | 82 | .token.property, 83 | .token.tag, 84 | .token.boolean, 85 | .token.number, 86 | .token.constant, 87 | .token.symbol { 88 | color: #905; 89 | } 90 | 91 | .token.selector, 92 | .token.attr-name, 93 | .token.string, 94 | .token.builtin { 95 | color: #690; 96 | } 97 | 98 | .token.operator, 99 | .token.entity, 100 | .token.url, 101 | .language-css .token.string, 102 | .style .token.string, 103 | .token.variable { 104 | color: #a67f59; 105 | background: hsla(0,0%,100%,.5); 106 | } 107 | 108 | .token.atrule, 109 | .token.attr-value, 110 | .token.keyword { 111 | color: #07a; 112 | } 113 | 114 | 115 | .token.regex, 116 | .token.important { 117 | color: #e90; 118 | } 119 | 120 | .token.important { 121 | font-weight: bold; 122 | } 123 | 124 | .token.entity { 125 | cursor: help; 126 | } 127 | 128 | -------------------------------------------------------------------------------- /rest/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbase/cbgt/2404a65e9af2301ccb71d7122d0deb353feeca92/rest/static/favicon.ico -------------------------------------------------------------------------------- /rest/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbase/cbgt/2404a65e9af2301ccb71d7122d0deb353feeca92/rest/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /rest/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbase/cbgt/2404a65e9af2301ccb71d7122d0deb353feeca92/rest/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /rest/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbase/cbgt/2404a65e9af2301ccb71d7122d0deb353feeca92/rest/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /rest/static/img/cb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbase/cbgt/2404a65e9af2301ccb71d7122d0deb353feeca92/rest/static/img/cb.png -------------------------------------------------------------------------------- /rest/static/index.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 66 | 67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /rest/static/js/app.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | 'use strict'; 10 | 11 | // Declare app level module which depends on filters, and services 12 | var cbgtApp = angular.module('cbgtApp', [ 13 | 'ngRoute', 14 | 'cbgtApp.filters', 15 | 'cbgtApp.services', 16 | 'cbgtApp.directives', 17 | 'expvar', 18 | 'ui.bootstrap', 19 | 'ui.bootstrap.modal', 20 | 'ui.bootstrap.tabs', 21 | 'ui.tree' 22 | ]); 23 | 24 | cbgtApp.config(['$routeProvider', '$locationProvider', 25 | function($routeProvider, $locationProvider) { 26 | $routeProvider.when('/indexes/', 27 | {templateUrl: '/static/partials/index/list.html', 28 | controller: 'IndexesCtrl'}); 29 | $routeProvider.when('/indexes/_new', 30 | {templateUrl: '/static/partials/index/new.html', 31 | controller: 'IndexNewCtrl'}); 32 | $routeProvider.when('/indexes/:indexName', 33 | {templateUrl: '/static/partials/index/index.html', 34 | controller: 'IndexCtrl'}); 35 | $routeProvider.when('/indexes/:indexName/_edit', 36 | {templateUrl: '/static/partials/index/new.html', 37 | controller: 'IndexNewCtrl'}); 38 | $routeProvider.when('/indexes/:indexName/_clone', 39 | {templateUrl: '/static/partials/index/new.html', 40 | controller: 'IndexNewCtrl'}); 41 | $routeProvider.when('/indexes/:indexName/:tabName', 42 | {templateUrl: '/static/partials/index/index.html', 43 | controller: 'IndexCtrl'}); 44 | 45 | $routeProvider.when('/nodes/', 46 | {templateUrl: '/static/partials/node/list.html', 47 | controller: 'NodeCtrl'}); 48 | $routeProvider.when('/nodes/:nodeUUID', 49 | {templateUrl: '/static/partials/node/node.html', 50 | controller: 'NodeCtrl'}); 51 | $routeProvider.when('/nodes/:nodeUUID/:tabName', 52 | {templateUrl: '/static/partials/node/node.html', 53 | controller: 'NodeCtrl'}); 54 | 55 | $routeProvider.when('/monitor/', 56 | {templateUrl: '/static/partials/monitor.html', 57 | controller: 'MonitorCtrl'}); 58 | 59 | $routeProvider.when('/manage/', 60 | {templateUrl: '/static/partials/manage.html', 61 | controller: 'ManageCtrl'}); 62 | 63 | $routeProvider.when('/logs/', 64 | {templateUrl: '/static/partials/logs.html', 65 | controller: 'LogsCtrl'}); 66 | 67 | $routeProvider.otherwise({redirectTo: '/indexes'}); 68 | 69 | $locationProvider.html5Mode(true); 70 | }]); 71 | -------------------------------------------------------------------------------- /rest/static/js/controllers.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | 'use strict'; 10 | 11 | /* Controllers */ 12 | 13 | function errorMessage(errorMessageFull, code) { 14 | if (typeof errorMessageFull == "object") { 15 | if (code == 403) { 16 | var rv = errorMessageFull.message + ": "; 17 | for (var x in errorMessageFull.permissions) { 18 | rv += errorMessageFull.permissions[x]; 19 | } 20 | return rv; 21 | } else { 22 | errorMessageFull = errorMessageFull.error 23 | } 24 | } 25 | console.log("errorMessageFull", errorMessageFull, code); 26 | var a = (errorMessageFull || (code + "")).split("err: "); 27 | return a[a.length - 1]; 28 | } 29 | 30 | cbgtApp.controller({ 31 | 'IndexesCtrl': IndexesCtrl, 32 | 'IndexNewCtrl': IndexNewCtrl, 33 | 'IndexCtrl': IndexCtrl, 34 | 'QueryCtrl': QueryCtrl, 35 | 'NodeCtrl': NodeCtrl, 36 | 'MonitorCtrl': MonitorCtrl, 37 | 'ManageCtrl': ManageCtrl, 38 | 'LogsCtrl': LogsCtrl 39 | }); 40 | -------------------------------------------------------------------------------- /rest/static/js/directives.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | 'use strict'; 10 | 11 | /* Directives */ 12 | 13 | angular.module('cbgtApp.directives', []). 14 | directive('appVersion', ['version', function(version) { 15 | return function(scope, elm, attrs) { 16 | elm.text(version); 17 | }; 18 | }]). 19 | directive('nagPrism', ['$compile', function($compile) { 20 | return { 21 | restrict: 'A', 22 | transclude: true, 23 | scope: { 24 | source: '@' 25 | }, 26 | link: function(scope, element, attrs, controller, transclude) { 27 | scope.$watch('source', function(v) { 28 | element.find("code").html(v); 29 | 30 | Prism.highlightElement(element.find("code")[0]); 31 | }); 32 | 33 | transclude(function(clone) { 34 | if (clone.html() !== undefined) { 35 | element.find("code").html(clone.html()); 36 | $compile(element.contents())(scope.$parent); 37 | } 38 | }); 39 | }, 40 | template: "" 41 | }; 42 | }]); 43 | -------------------------------------------------------------------------------- /rest/static/js/expvar.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015-Present Couchbase, Inc. 3 | 4 | Use of this software is governed by the Business Source License included in 5 | the file licenses/BSL-Couchbase.txt. As of the Change Date specified in that 6 | file, in accordance with the Business Source License, use of this software will 7 | be governed by the Apache License, Version 2.0, included in the file 8 | licenses/APL2.txt. 9 | */ 10 | 11 | var module = angular.module('expvar', []); 12 | 13 | module.factory('expvar', function($http, $log) { 14 | 15 | var result = { 16 | numSamplesToKeep: 60*10, 17 | 18 | // An example of an individual metric would be 19 | // like "stats/indexes/SOME_PINDEX/index/index_time". 20 | metricPaths: {}, // name => jsonPointerPath. 21 | metricValues: {}, // name => [val0, val1, ..., val59]. 22 | 23 | // Used for server-driven dynamic data. For example, when 24 | // jsonPointerPath is "stats/indexes", then the 25 | // dynamicDataKeys will be all the PINDEX_NAME's. 26 | dynamicDataPaths: {}, // dynamicName => jsonPointerPath. 27 | dynamicDataKeys: {}, // dynamicName => [key0, key1, ...]. 28 | 29 | addMetric: function(name, path) { 30 | this.metricPaths[name] = path; 31 | }, 32 | 33 | removeMetric: function(name) { 34 | delete this.metricPaths[name]; 35 | }, 36 | 37 | addDynamicDataPath: function(name, path) { 38 | this.dynamicDataPaths[name] = path; 39 | }, 40 | 41 | getDynamicDataKeys: function(name) { 42 | return this.dynamicDataKeys[name]; 43 | }, 44 | 45 | getMetricValues: function(name) { 46 | return this.metricValues[name]; 47 | }, 48 | 49 | getMetricCurrentValue: function(name) { 50 | if (this.metricValues[name] !== undefined) { 51 | len = this.metricValues[name].length; 52 | if (len > 0) { 53 | values = this.metricValues[name]; 54 | return values[len-1]; 55 | } 56 | } 57 | return 0; 58 | }, 59 | 60 | getMetricCurrentRate: function(name) { 61 | if (this.metricValues[name] !== undefined) { 62 | len = this.metricValues[name].length; 63 | if (len > 1) { 64 | values = this.metricValues[name]; 65 | curr = values[len-1]; 66 | prev = values[len-2]; 67 | return curr - prev; 68 | } 69 | } 70 | return 0; 71 | }, 72 | 73 | pollExpvar : function() { 74 | numSamplesToKeep = this.numSamplesToKeep; 75 | metricPaths = this.metricPaths; 76 | metricValues = this.metricValues; 77 | dynamicDataPaths = this.dynamicDataPaths; 78 | dynamicDataKeys = this.dynamicDataKeys; 79 | 80 | $http.get("/debug/vars").then(function(response) { 81 | var data = response.data; 82 | 83 | // lookup dynamic keys 84 | for(var keyLookupName in dynamicDataPaths) { 85 | keyPath = this.dynamicDataPaths[keyLookupName]; 86 | keysContainer = jsonpointer.get(data, keyPath); 87 | keys = []; 88 | for(var key in keysContainer) { 89 | keys.push(key); 90 | } 91 | dynamicDataKeys[keyLookupName] = keys; 92 | } 93 | 94 | // lookup metrics 95 | for(var metricName in metricPaths) { 96 | metricPath = this.metricPaths[metricName]; 97 | metricValue = jsonpointer.get(data, metricPath); 98 | thisMetricValues = metricValues[metricName]; 99 | if (thisMetricValues === undefined) { 100 | thisMetricValues = []; 101 | } 102 | thisMetricValues.push(metricValue); 103 | while (thisMetricValues.length > numSamplesToKeep) { 104 | thisMetricValues.shift(); 105 | } 106 | metricValues[metricName] = thisMetricValues; 107 | } 108 | }, function(response) { 109 | $log.info("error polling expvar"); 110 | }); 111 | }, 112 | }; 113 | 114 | return result; 115 | }); 116 | -------------------------------------------------------------------------------- /rest/static/js/filters.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | 'use strict'; 10 | 11 | /* Filters */ 12 | 13 | angular.module('cbgtApp.filters', []). 14 | filter('interpolate', ['version', function(version) { 15 | return function(text) { 16 | return String(text).replace(/\%VERSION\%/mg, version); 17 | }; 18 | }]); 19 | -------------------------------------------------------------------------------- /rest/static/js/logs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | function LogsCtrl($scope, $http, $routeParams, $log, $sce, $location) { 10 | 11 | $scope.errorMessage = null; 12 | $scope.logMessages = ""; 13 | 14 | $scope.updateLogs = function() { 15 | $scope.clearErrorMessage(); 16 | $scope.clearLogMessages(); 17 | $http.get('/api/log').then(function(response) { 18 | var data = response.data; 19 | for(var i in data.messages) { 20 | $scope.logMessages += data.messages[i]; 21 | } 22 | $scope.events = data.events; 23 | }, function(response) { 24 | $scope.errorMessage = response.data; 25 | }); 26 | }; 27 | 28 | $scope.clearErrorMessage = function() { 29 | $scope.errorMessage = null; 30 | }; 31 | 32 | $scope.clearLogMessages = function() { 33 | $scope.logMessages = ""; 34 | }; 35 | 36 | $scope.updateLogs(); 37 | } 38 | -------------------------------------------------------------------------------- /rest/static/js/manage.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | function ManageCtrl($scope, $http, $routeParams, $log, $sce, $location) { 10 | 11 | $scope.resultCfg = null; 12 | $scope.resultCfgJSON = null; 13 | $scope.resultCfgRefresh = null; 14 | $scope.resultManagerKick = null; 15 | 16 | $scope.managerKick = function(managerKickMsg) { 17 | $scope.resultManagerKick = null; 18 | $http.post('/api/managerKick?msg=' + managerKickMsg).then(function(response) { 19 | $scope.resultManagerKick = response.data.status; 20 | }, function(response) { 21 | $scope.resultManagerKick = response.data; 22 | }); 23 | }; 24 | 25 | $scope.cfgGet = function() { 26 | $scope.resultCfg = null; 27 | $scope.resultCfgJSON = null; 28 | $http.get('/api/cfg').then(function(response) { 29 | var data = response.data; 30 | $scope.resultCfg = data; 31 | $scope.resultCfgJSON = JSON.stringify(data, undefined, 2); 32 | }, function(response) { 33 | var data = response.data; 34 | $scope.resultCfg = data; 35 | $scope.resultCfgJSON = JSON.stringify(data, undefined, 2); 36 | }); 37 | }; 38 | 39 | $scope.cfgRefresh = function(managerKickMsg) { 40 | $scope.resultCfgRefresh = null; 41 | $http.post('/api/cfgRefresh').then(function(response) { 42 | $scope.resultCfgRefresh = response.data.status; 43 | $scope.cfgGet() 44 | }, function(response) { 45 | $scope.resultCfgRefresh = response.data.status; 46 | }); 47 | }; 48 | 49 | $scope.cfgGet(); 50 | } 51 | -------------------------------------------------------------------------------- /rest/static/js/node.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | function NodeCtrl($scope, $http, $routeParams, $log, $sce, $location) { 10 | 11 | $scope.resultCfg = null; 12 | $scope.resultCfgJSON = null; 13 | $scope.nodeDefsKnownArrOrderByHostPort = null; 14 | 15 | $scope.nodeUUID = $routeParams.nodeUUID; 16 | $scope.tab = $routeParams.tabName; 17 | if($scope.tab === undefined || $scope.tab === "") { 18 | $scope.tab = "summary"; 19 | } 20 | $scope.tabPath = '/static/partials/node/tab-' + $scope.tab + '.html'; 21 | 22 | $scope.containerPartColor = function(s) { 23 | var r = 3.14159; 24 | for (var i = 0; i < s.length; i++) { 25 | r = r ^ (r * s.charCodeAt(i)); 26 | } 27 | v = Math.abs(r).toString(16); 28 | v1 = v.slice(0, 1); 29 | v2 = v.slice(1, 3); 30 | return ('#1' + v1 + v2 + v2); 31 | } 32 | 33 | $scope.cfgGet = function() { 34 | $scope.resultCfg = null; 35 | $scope.resultCfgJSON = null; 36 | $scope.nodeDefsKnownArrOrderByHostPort = []; 37 | $http.get('/api/cfg').then(function(response) { 38 | var data = response.data; 39 | for (var nodeUUID in data.nodeDefsKnown.nodeDefs) { 40 | var nodeDef = data.nodeDefsKnown.nodeDefs[nodeUUID]; 41 | nodeDef.containerArr = (nodeDef.container || "").split('/'); 42 | $scope.nodeDefsKnownArrOrderByHostPort.push(nodeDef); 43 | } 44 | $scope.nodeDefsKnownArrOrderByHostPort.sort(function(a, b) { 45 | if (a.hostPort < b.hostPort) { 46 | return -1; 47 | } 48 | if (a.hostPort > b.hostPort) { 49 | return 1; 50 | } 51 | return 0; 52 | }); 53 | $scope.resultCfg = data; 54 | $scope.resultCfgJSON = JSON.stringify(data, undefined, 2); 55 | }, function(response) { 56 | var data = response.data; 57 | $scope.resultCfg = data; 58 | $scope.resultCfgJSON = JSON.stringify(data, undefined, 2); 59 | }); 60 | }; 61 | 62 | $scope.cfgGet(); 63 | } 64 | -------------------------------------------------------------------------------- /rest/static/js/services.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | 'use strict'; 10 | 11 | /* Services */ 12 | 13 | // Demonstrate how to register services 14 | // In this case it is a simple value service. 15 | angular.module('cbgtApp.services', []). 16 | value('version', '0.1'); 17 | -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "https://github.com/angular-ui/bootstrap/graphs/contributors" 4 | }, 5 | "name": "angular-bootstrap", 6 | "keywords": [ 7 | "angular", 8 | "angular-ui", 9 | "bootstrap" 10 | ], 11 | "license": "MIT", 12 | "ignore": [], 13 | "description": "Native AngularJS (Angular) directives for Bootstrap.", 14 | "version": "0.14.3", 15 | "main": [ 16 | "./ui-bootstrap-tpls.js" 17 | ], 18 | "dependencies": { 19 | "angular": ">=1.3.0" 20 | }, 21 | "homepage": "https://github.com/angular-ui/bootstrap-bower", 22 | "_release": "0.14.3", 23 | "_resolution": { 24 | "type": "version", 25 | "tag": "0.14.3", 26 | "commit": "306d1a30b4a8e8144741bb9c0126331ac884126a" 27 | }, 28 | "_source": "git://github.com/angular-ui/bootstrap-bower.git", 29 | "_target": "~0.14.3", 30 | "_originalSource": "angular-bootstrap", 31 | "_direct": true 32 | } -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/.npmignore: -------------------------------------------------------------------------------- 1 | bower.json -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/README.md: -------------------------------------------------------------------------------- 1 | ### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com) 2 | 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Build Status](https://secure.travis-ci.org/angular-ui/bootstrap.svg)](http://travis-ci.org/angular-ui/bootstrap) 5 | [![devDependency Status](https://david-dm.org/angular-ui/bootstrap/dev-status.svg?branch=master)](https://david-dm.org/angular-ui/bootstrap#info=devDependencies) 6 | 7 | ### Quick links 8 | - [Demo](#demo) 9 | - [Installation](#installation) 10 | - [NPM](#install-with-npm) 11 | - [Bower](#install-with-bower) 12 | - [NuGet](#install-with-nuget) 13 | - [Custom](#custom-build) 14 | - [Manual](#manual-download) 15 | - [Support](#support) 16 | - [FAQ](#faq) 17 | - [Supported browsers](#supported-browsers) 18 | - [Need help?](#need-help) 19 | - [Found a bug?](#found-a-bug) 20 | - [Contributing to the project](#contributing-to-the-project) 21 | - [Development, meeting minutes, roadmap and more.](#development-meeting-minutes-roadmap-and-more) 22 | 23 | 24 | # Demo 25 | 26 | Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/! 27 | 28 | # Installation 29 | 30 | Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required. 31 | Note: Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation. 32 | 33 | #### Install with NPM 34 | 35 | ```sh 36 | $ npm install angular-ui-bootstrap 37 | ``` 38 | 39 | This will install AngularJS and Bootstrap NPM packages. 40 | 41 | #### Install with Bower 42 | ```sh 43 | $ bower install angular-bootstrap 44 | ``` 45 | 46 | Note: do not install 'angular-ui-bootstrap'. A separate repository - [bootstrap-bower](https://github.com/angular-ui/bootstrap-bower) - hosts the compiled javascript file and bower.json. 47 | 48 | #### Install with NuGet 49 | To install AngularJS UI Bootstrap, run the following command in the Package Manager Console 50 | 51 | ```sh 52 | PM> Install-Package Angular.UI.Bootstrap 53 | ``` 54 | 55 | #### Custom build 56 | 57 | Head over to http://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it. 58 | 59 | #### Manual download 60 | 61 | After downloading dependencies (or better yet, referencing them from your favorite CDN) you need to download build version of this project. All the files and their purposes are described here: 62 | https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files 63 | Don't worry, if you are not sure which file to take, opt for `ui-bootstrap-tpls-[version].min.js`. 64 | 65 | ### Adding dependency to your project 66 | 67 | When you are done downloading all the dependencies and project files the only remaining part is to add dependencies on the `ui.bootstrap` AngularJS module: 68 | 69 | ```js 70 | angular.module('myModule', ['ui.bootstrap']); 71 | ``` 72 | 73 | If you're a Browserify or Webpack user, you can do: 74 | 75 | ```js 76 | var uibs = require('angular-ui-bootstrap'); 77 | 78 | angular.module('myModule', [uibs]); 79 | ``` 80 | 81 | # Support 82 | 83 | ## FAQ 84 | 85 | https://github.com/angular-ui/bootstrap/wiki/FAQ 86 | 87 | ## Supported browsers 88 | 89 | Directives from this repository are automatically tested with the following browsers: 90 | * Chrome (stable and canary channel) 91 | * Firefox 92 | * IE 9 and 10 93 | * Opera 94 | * Safari 95 | 96 | Modern mobile browsers should work without problems. 97 | 98 | 99 | ## Need help? 100 | Need help using UI Bootstrap? 101 | 102 | * Live help in the IRC (`#angularjs` channel at the `freenode` network). Use this [webchat](https://webchat.freenode.net/) or your own IRC client. 103 | * Ask a question in [StackOverflow](http://stackoverflow.com/) under the [angular-ui-bootstrap](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) tag. 104 | 105 | **Please do not create new issues in this repository to ask questions about using UI Bootstrap** 106 | 107 | ## Found a bug? 108 | Please take a look at [CONTRIBUTING.md](CONTRIBUTING.md#you-think-youve-found-a-bug) and submit your issue [here](https://github.com/angular-ui/bootstrap/issues/new). 109 | 110 | 111 | ---- 112 | 113 | 114 | # Contributing to the project 115 | 116 | We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines. 117 | 118 | # Development, meeting minutes, roadmap and more. 119 | 120 | Head over to the [Wiki](https://github.com/angular-ui/bootstrap/wiki) for notes on development for UI Bootstrap, meeting minutes from the UI Bootstrap team, roadmap plans, project philosophy and more. 121 | -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "https://github.com/angular-ui/bootstrap/graphs/contributors" 4 | }, 5 | "name": "angular-bootstrap", 6 | "keywords": [ 7 | "angular", 8 | "angular-ui", 9 | "bootstrap" 10 | ], 11 | "license": "MIT", 12 | "ignore": [], 13 | "description": "Native AngularJS (Angular) directives for Bootstrap.", 14 | "version": "0.14.3", 15 | "main": ["./ui-bootstrap-tpls.js"], 16 | "dependencies": { 17 | "angular": ">=1.3.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/index.js: -------------------------------------------------------------------------------- 1 | require('./ui-bootstrap-tpls'); 2 | module.exports = 'ui.bootstrap'; 3 | -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ui-bootstrap", 3 | "version": "0.14.3", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ui-bootstrap", 3 | "version": "0.14.3", 4 | "description": "Bootstrap widgets for Angular", 5 | "main": "index.js", 6 | "homepage": "http://angular-ui.github.io/bootstrap/", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/angular-ui/bootstrap.git" 10 | }, 11 | "keywords": [ 12 | "angular", 13 | "bootstrap", 14 | "angular-ui", 15 | "components", 16 | "client-side" 17 | ], 18 | "author": "https://github.com/angular-ui/bootstrap/graphs/contributors", 19 | "peerDependencies": { 20 | "angular": "^1.3.x || >= 1.4.0-beta.0 || >= 1.5.0-beta.0" 21 | }, 22 | "license": "MIT" 23 | } 24 | -------------------------------------------------------------------------------- /rest/static/lib/angular-bootstrap/ui-bootstrap-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | .ng-animate.item:not(.left):not(.right) { 4 | -webkit-transition: 0s ease-in-out left; 5 | transition: 0s ease-in-out left 6 | } -------------------------------------------------------------------------------- /rest/static/lib/angular-route/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Angular 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rest/static/lib/angular-route/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular-route 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngRoute). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular-route 15 | ``` 16 | 17 | Then add `ngRoute` as a dependency for your app: 18 | 19 | ```javascript 20 | angular.module('myApp', [require('angular-route')]); 21 | ``` 22 | 23 | ### bower 24 | 25 | ```shell 26 | bower install angular-route 27 | ``` 28 | 29 | Add a ` 33 | ``` 34 | 35 | Then add `ngRoute` as a dependency for your app: 36 | 37 | ```javascript 38 | angular.module('myApp', ['ngRoute']); 39 | ``` 40 | 41 | ## Documentation 42 | 43 | Documentation is available on the 44 | [AngularJS docs site](http://docs.angularjs.org/api/ngRoute). 45 | 46 | ## License 47 | 48 | The MIT License 49 | 50 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | -------------------------------------------------------------------------------- /rest/static/lib/angular-route/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.8.0 3 | (c) 2010-2020 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,b){'use strict';function z(b,h){var d=[],c=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[?*])?/g,function(b,c,h,k){b="?"===k||"*?"===k;k="*"===k||"*?"===k;d.push({name:h,optional:b});c=c||"";return(b?"(?:"+c:c+"(?:")+(k?"(.+?)":"([^/]+)")+(b?"?)?":")")}).replace(/([/$*])/g,"\\$1");h.ignoreTrailingSlashes&&(c=c.replace(/\/+$/,"")+"/*");return{keys:d,regexp:new RegExp("^"+c+"(?:[?#]|$)",h.caseInsensitiveMatch?"i":"")}}function A(b){p&&b.get("$route")}function v(u,h,d){return{restrict:"ECA", 7 | terminal:!0,priority:400,transclude:"element",link:function(c,f,g,l,k){function q(){r&&(d.cancel(r),r=null);m&&(m.$destroy(),m=null);s&&(r=d.leave(s),r.done(function(b){!1!==b&&(r=null)}),s=null)}function C(){var g=u.current&&u.current.locals;if(b.isDefined(g&&g.$template)){var g=c.$new(),l=u.current;s=k(g,function(g){d.enter(g,null,s||f).done(function(d){!1===d||!b.isDefined(w)||w&&!c.$eval(w)||h()});q()});m=l.scope=g;m.$emit("$viewContentLoaded");m.$eval(p)}else q()}var m,s,r,w=g.autoscroll,p=g.onload|| 8 | "";c.$on("$routeChangeSuccess",C);C()}}}function x(b,h,d){return{restrict:"ECA",priority:-400,link:function(c,f){var g=d.current,l=g.locals;f.html(l.$template);var k=b(f.contents());if(g.controller){l.$scope=c;var q=h(g.controller,l);g.controllerAs&&(c[g.controllerAs]=q);f.data("$ngControllerController",q);f.children().data("$ngControllerController",q)}c[g.resolveAs||"$resolve"]=l;k(c)}}}var D,E,F,G,y=b.module("ngRoute",[]).info({angularVersion:"1.8.0"}).provider("$route",function(){function u(d, 9 | c){return b.extend(Object.create(d),c)}D=b.isArray;E=b.isObject;F=b.isDefined;G=b.noop;var h={};this.when=function(d,c){var f;f=void 0;if(D(c)){f=f||[];for(var g=0,l=c.length;g", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/angular/angular.js/issues" 24 | }, 25 | "homepage": "http://angularjs.org", 26 | "jspm": { 27 | "shim": { 28 | "angular-route": { 29 | "deps": [ 30 | "angular" 31 | ] 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rest/static/lib/angular-ui-tree/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.9.0 2 | 3 | * Updated Bower package name to `angular-ui-tree` [#568](https://github.com/angular-ui-tree/angular-ui-tree/pull/568) 4 | * Remove placeholder and cancel drop on drag out of bounds [#550](https://github.com/angular-ui-tree/angular-ui-tree/pull/550) 5 | * Fix position detection on touch devices when using jQuery [#554](https://github.com/angular-ui-tree/angular-ui-tree/pull/554) 6 | 7 | # 2.8.0 8 | 9 | * Rename `$uiTreeHelper` service to `UiTreeHelper` [#534](https://github.com/angular-ui-tree/angular-ui-tree/pull/534) 10 | * Nodrop, clone, and dirty-checking fixes [#525](https://github.com/angular-ui-tree/angular-ui-tree/pull/525) 11 | 12 | # 2.7.0 13 | 14 | * Fix edge case error when you have a single node with no parents and no children and and drop the node in the same place [#510](https://github.com/angular-ui-tree/angular-ui-tree/pull/510) 15 | * Fix error in the `apply` function [#512](https://github.com/angular-ui-tree/angular-ui-tree/pull/512) 16 | * Fix calculation of the placeholder width [#526](https://github.com/angular-ui-tree/angular-ui-tree/pull/526) [#470](https://github.com/angular-ui-tree/angular-ui-tree/pull/470) 17 | 18 | # 2.6.0 19 | 20 | * Use `data-nodrag` instead of `nodrag` attribute in the examples, as `$uiTreeHelper` service looks for the first one [#468](https://github.com/angular-ui-tree/angular-ui-tree/pull/468) 21 | * Drag-dropping a node in the same position and container no longer removes and re-adds it to its parent node array [#485](https://github.com/angular-ui-tree/angular-ui-tree/pull/485) 22 | * `bower.json` should reference only one copy of `angular-ui-tree.js` in main [#488](https://github.com/angular-ui-tree/angular-ui-tree/pull/488) 23 | 24 | # 2.5.0 25 | 26 | * Prevents child node scope with no children to be counted in depth [#388](https://github.com/angular-ui-tree/angular-ui-tree/pull/388) 27 | * Fix callback errors when we have intermediate isolated scopes [#423](https://github.com/angular-ui-tree/angular-ui-tree/pull/423) 28 | * Rename API attribute for toggling the empty placeholder [#450](https://github.com/angular-ui-tree/angular-ui-tree/pull/450) 29 | 30 | # 2.4.0 31 | 32 | * Added JSCS validation task [#441](https://github.com/angular-ui-tree/angular-ui-tree/pull/441) 33 | * Bugfix `data-drag-delay` to actually delay `dragStart` [#444](https://github.com/angular-ui-tree/angular-ui-tree/pull/444) 34 | 35 | # 2.3.0 36 | 37 | * Add `data-clone-enabled` option + fix `data-drop-enabled` option ([#411](https://github.com/angular-ui-tree/angular-ui-tree/pull/411)) 38 | * Replaced Grunt with Gulp for the build process ([#435](https://github.com/angular-ui-tree/angular-ui-tree/pull/435)) 39 | * Fixed memory leak [#421](https://github.com/angular-ui-tree/angular-ui-tree/pull/421) 40 | 41 | # 2.2.0 42 | 43 | * release has been [reverted](https://github.com/angular-ui-tree/angular-ui-tree/commit/800dd0a43ce105d6301cd42038c1a28dbe3cd21e) to v2.1.5 44 | 45 | # 2.1.5 (2014-07-31) 46 | 47 | * latest release without CHANGELOG 48 | -------------------------------------------------------------------------------- /rest/static/lib/angular-ui-tree/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /rest/static/lib/angular-ui-tree/dist/angular-ui-tree.min.css: -------------------------------------------------------------------------------- 1 | .angular-ui-tree-empty{border:1px dashed #bbb;min-height:100px;background-color:#e5e5e5;background-image:-webkit-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff),-webkit-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff);background-image:-moz-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff),-moz-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff);background-image:linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff),linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff);background-size:60px 60px;background-position:0 0,30px 30px}.angular-ui-tree-nodes{display:block;position:relative;margin:0;padding:0;list-style:none}.angular-ui-tree-nodes .angular-ui-tree-nodes{padding-left:20px}.angular-ui-tree-node,.angular-ui-tree-placeholder{display:block;position:relative;margin:0;padding:0;min-height:20px;line-height:20px}.angular-ui-tree-hidden{display:none}.angular-ui-tree-placeholder{margin:5px 0;padding:0;min-height:30px}.angular-ui-tree-handle{cursor:move;text-decoration:none;font-weight:700;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;min-height:20px;line-height:20px}.angular-ui-tree-drag{position:absolute;pointer-events:none;z-index:999;opacity:.8} -------------------------------------------------------------------------------- /rest/static/lib/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.8.0", 4 | "license": "MIT", 5 | "main": "./angular.js", 6 | "ignore": [], 7 | "dependencies": {}, 8 | "homepage": "https://github.com/angular/bower-angular", 9 | "_release": "1.8.0", 10 | "_resolution": { 11 | "type": "version", 12 | "tag": "v1.8.0", 13 | "commit": "ebe29b77d2dc4a7ab602daf9301ab910672b6b41" 14 | }, 15 | "_source": "https://github.com/angular/bower-angular.git", 16 | "_target": "1.8.0", 17 | "_originalSource": "angular" 18 | } -------------------------------------------------------------------------------- /rest/static/lib/angular/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Angular 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rest/static/lib/angular/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular 15 | ``` 16 | 17 | Then add a ` 21 | ``` 22 | 23 | Or `require('angular')` from your code. 24 | 25 | ### bower 26 | 27 | ```shell 28 | bower install angular 29 | ``` 30 | 31 | Then add a ` 35 | ``` 36 | 37 | ## Documentation 38 | 39 | Documentation is available on the 40 | [AngularJS docs site](http://docs.angularjs.org/). 41 | 42 | ## License 43 | 44 | The MIT License 45 | 46 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | -------------------------------------------------------------------------------- /rest/static/lib/angular/angular-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], 6 | [ng-cloak], 7 | [data-ng-cloak], 8 | [x-ng-cloak], 9 | .ng-cloak, 10 | .x-ng-cloak, 11 | .ng-hide:not(.ng-hide-animate) { 12 | display: none !important; 13 | } 14 | 15 | ng\:form { 16 | display: block; 17 | } 18 | 19 | .ng-animate-shim { 20 | visibility:hidden; 21 | } 22 | 23 | .ng-anchor { 24 | position:absolute; 25 | } 26 | -------------------------------------------------------------------------------- /rest/static/lib/angular/angular.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/couchbase/cbgt/2404a65e9af2301ccb71d7122d0deb353feeca92/rest/static/lib/angular/angular.min.js.gzip -------------------------------------------------------------------------------- /rest/static/lib/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.8.0", 4 | "license": "MIT", 5 | "main": "./angular.js", 6 | "ignore": [], 7 | "dependencies": { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /rest/static/lib/angular/index.js: -------------------------------------------------------------------------------- 1 | require('./angular'); 2 | module.exports = angular; 3 | -------------------------------------------------------------------------------- /rest/static/lib/angular/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.8.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.8.0", 9 | "license": "MIT" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /rest/static/lib/angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.8.0", 4 | "description": "HTML enhanced for web apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/angular/angular.js.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "framework", 16 | "browser", 17 | "client-side" 18 | ], 19 | "author": "Angular Core Team ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/angular/angular.js/issues" 23 | }, 24 | "homepage": "http://angularjs.org" 25 | } 26 | -------------------------------------------------------------------------------- /rest/static/lib/bootstrap/bootstrap-lightbox.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bootstrap-lightbox.js v0.6.1 3 | * Copyright 2013 Jason Butz 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="lightbox"]',"click.dismiss.lightbox",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".lightbox-body").load(this.options.remote)};t.prototype=e.extend({},e.fn.modal.Constructor.prototype),t.prototype.constructor=t,t.prototype.enforceFocus=function(){var t=this;e(document).on("focusin.lightbox",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},t.prototype.show=function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.preloadSize(function(){t.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.focus().trigger("shown")}):t.$element.focus().trigger("shown")})})},t.prototype.hide=function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,this.escape(),e(document).off("focusin.lightbox"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},t.prototype.escape=function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.lightbox",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.lightbox")},t.prototype.preloadSize=function(t){var n=e.Callbacks();t&&n.add(t);var r=this,i,s,o,u,a,f,l,c,h,p;i=e(window).height(),s=e(window).width(),o=parseInt(r.$element.find(".lightbox-content").css("padding-top"),10),u=parseInt(r.$element.find(".lightbox-content").css("padding-bottom"),10),a=parseInt(r.$element.find(".lightbox-content").css("padding-left"),10),f=parseInt(r.$element.find(".lightbox-content").css("padding-right"),10),l=r.$element.find(".lightbox-content").find("img:first"),c=new Image,c.onload=function(){c.width+a+f>=s&&(h=c.width,p=c.height,c.width=s-a-f,c.height=p/h*c.width),c.height+o+u>=i&&(h=c.width,p=c.height,c.height=i-o-u,c.width=h/p*c.height),r.$element.css({position:"fixed",width:c.width+a+f,height:c.height+o+u,top:i/2-(c.height+o+u)/2,left:"50%","margin-left":-1*(c.width+a+f)/2}),r.$element.find(".lightbox-content").css({width:c.width,height:c.height}),n.fire()},c.src=l.attr("src")};var n=e.fn.lightbox;e.fn.lightbox=function(n){return this.each(function(){var r=e(this),i=r.data("lightbox"),s=e.extend({},e.fn.lightbox.defaults,r.data(),typeof n=="object"&&n);i||r.data("lightbox",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.lightbox.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.lightbox.Constructor=t,e.fn.lightbox.noConflict=function(){return e.fn.lightbox=n,this},e(document).on("click.lightbox.data-api",'[data-toggle*="lightbox"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("lightbox")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.lightbox(s).one("hide",function(){n.focus()})})}(window.jQuery); -------------------------------------------------------------------------------- /rest/static/lib/humanize.min.js: -------------------------------------------------------------------------------- 1 | (function(){var e,t,n,r,i,s,o;i=new function(){},o=i.toString,r=function(e){return e!==e},n=function(e){return((typeof window!="undefined"&&window!==null?window.isFinite:void 0)||global.isFinite)(e)&&!r(parseFloat(e))},t=function(e){return o.call(e)==="[object Array]"},s=[{name:"second",value:1e3},{name:"minute",value:6e4},{name:"hour",value:36e5},{name:"day",value:864e5},{name:"week",value:6048e5}],e={},e.intword=function(t,n,r){return r==null&&(r=2),e.compactInteger(t,r)},e.compactInteger=function(e,t){var n,r,i,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b,w;t==null&&(t=0),t=Math.max(t,0),u=parseInt(e,10),h=u<0?"-":"",p=Math.abs(u),v=""+p,a=v.length,f=[13,10,7,4],n=["T","B","M","k"];if(p<1e3)return""+h+v;if(a>f[0]+3)return u.toExponential(t).replace("e+","x10^");for(y=0,b=f.length;y=w){o=w;break}}return r=a-o+1,d=v.split(""),g=d.slice(0,r),s=d.slice(r,r+t+1),m=g.join(""),i=s.join(""),i.length=1073741824?n=e.formatNumber(t/1073741824,2,"")+" GB":t>=1048576?n=e.formatNumber(t/1048576,2,"")+" MB":t>=1024?n=e.formatNumber(t/1024,0)+" KB":n=e.formatNumber(t,0)+e.pluralize(t," byte"),n},e.formatNumber=function(t,n,r,i){var s,o,u,a,f,l,c;return n==null&&(n=0),r==null&&(r=","),i==null&&(i="."),a=function(e,t,n){return n?e.substr(0,n)+t:""},o=function(e,t,n){return e.substr(n).replace(/(\d{3})(?=\d)/g,"$1"+t)},u=function(t,n,r){return r?n+e.toFixed(Math.abs(t),r).split(".")[1]:""},c=e.normalizePrecision(n),l=t<0&&"-"||"",s=parseInt(e.toFixed(Math.abs(t||0),c),10)+"",f=s.length>3?s.length%3:0,l+a(s,r,f)+o(s,r,f)+u(t,i,c)},e.toFixed=function(t,n){var r;return n==null&&(n=e.normalizePrecision(n,0)),r=Math.pow(10,n),(Math.round(t*r)/r).toFixed(n)},e.normalizePrecision=function(e,t){return e=Math.round(Math.abs(e)),r(e)?t:e},e.ordinal=function(e){var t,n,r,i;r=parseInt(e,10);if(r===0)return e;i=r%100;if(i===11||i===12||i===13)return""+r+"th";n=r%10;switch(n){case 1:t="st";break;case 2:t="nd";break;case 3:t="rd";break;default:t="th"}return""+r+t},e.times=function(e,t){var r,i,s;t==null&&(t={});if(n(e)&&e>=0)return r=parseFloat(e),i=["never","once","twice"],t[r]!=null?""+t[r]:""+(((s=i[r])!=null?s.toString():void 0)||r.toString()+" times")},e.pluralize=function(e,t,n){if(e==null||t==null)return;return n==null&&(n=t+"s"),parseInt(e,10)===1?t:n},e.truncate=function(e,t,n){return t==null&&(t=100),n==null&&(n="..."),e.length>t?e.substring(0,t-n.length)+n:e},e.truncatewords=e.truncateWords=function(e,t){var n,r,i;n=e.split(" "),i="",r=0;while(rt)return i+="..."},e.truncatenumber=e.boundedNumber=function(e,t,r){var i;return t==null&&(t=100),r==null&&(r="+"),i=null,n(e)&&n(t)&&e>t&&(i=t+r),(i||e).toString()},e.oxford=function(t,n,r){var i,s,o;return o=t.length,o<2?""+t:o===2?t.join(" and "):(n!=null&&o>n?(i=o-n,s=n,r==null&&(r=", and "+i+" "+e.pluralize(i,"other"))):(s=-1,r=", and "+t[o-1]),t.slice(0,s).join(", ")+r)},e.dictionary=function(e,t,n){var r,i,s,o;t==null&&(t=" is "),n==null&&(n=", "),s="";if(e!=null&&typeof e=="object"&&Object.prototype.toString.call(e)!=="[object Array]"){r=[];for(i in e)o=e[i],r.push(""+i+t+o);s=r.join(n)}return s},e.frequency=function(n,r){var i,s,o;if(!t(n))return;return i=n.length,o=e.times(i),i===0?s=""+o+" "+r:s=""+r+" "+o,s},e.pace=function(t,n,r){var i,o,u,a,f,l,c,h;r==null&&(r="time");if(t===0||n===0)return"No "+e.pluralize(r);o="Approximately",l=null,u=t/n;for(c=0,h=s.length;c1){l=i.name;break}}return l||(o="Less than",a=1,l=s[s.length-1].name),f=Math.round(a),r=e.pluralize(f,r),""+o+" "+f+" "+r+" per "+l},e.nl2br=function(e,t){return t==null&&(t="
"),e.replace(/\n/g,t)},e.br2nl=function(e,t){return t==null&&(t="\r\n"),e.replace(/\/g,t)},e.capitalize=function(e,t){return t==null&&(t=!1),""+e.charAt(0).toUpperCase()+(t?e.slice(1).toLowerCase():e.slice(1))},e.capitalizeAll=function(e){return e.replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},e.titlecase=e.titleCase=function(t){var n,r,i,s,o,u=this;return i=/\b(a|an|and|at|but|by|de|en|for|if|in|of|on|or|the|to|via|vs?\.?)\b/i,r=/\S+[A-Z]+\S*/,o=/\s+/,s=/-/,n=function(t,u,a){var f,l,c,h,p,d;u==null&&(u=!1),a==null&&(a=!0),c=[],l=t.split(u?s:o);for(f=p=0,d=l.length;p 10 | 11 | 15 | -------------------------------------------------------------------------------- /rest/static/modal/window.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /rest/static/partials/index/index.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 20 | 21 | 24 | 25 |
26 | 27 |
28 | -------------------------------------------------------------------------------- /rest/static/partials/index/list.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 43 | 44 |
Name
{{indexName}} 30 | 34 | 38 | 42 |
45 | 46 |
48 |
49 | 50 | 57 | -------------------------------------------------------------------------------- /rest/static/partials/index/query-result-expl.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | {{explanation.value | number : 3}} - {{explanation.message}} 12 |
    13 |
  • 15 |
16 | -------------------------------------------------------------------------------- /rest/static/partials/index/query-results.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 |
15 | [partial ({{resultsSuccessPct}}%)] 17 | Page {{page}} of 18 | {{results.total_hits}} results 19 | ({{results.roundTook}} server-side) 20 |
21 |
22 | 25 | 26 |
27 | 28 |
    29 |
  1. 30 |
    31 | 32 | {{hit.id}} 33 | 34 | 35 | {{hit.id}} 36 | 37 |
    [{{hit.score}}] 39 |
    40 |
    41 | {{hit.score | number : 3}} 42 |
    43 | Scoring 44 |
      45 |
    • 48 |
    49 |
    50 |
    51 |
    52 |
    {{fieldName}}
    53 |
      54 |
    • 55 |
    56 |
    57 |
    58 |
  2. 59 |
60 |
61 | 62 |
63 |
64 | 90 | 91 |
92 | 93 | 101 | -------------------------------------------------------------------------------- /rest/static/partials/index/start.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |

Welcome

13 |
Please define your first index with the New Index button.
14 |
15 | 16 | 21 | -------------------------------------------------------------------------------- /rest/static/partials/index/tab-manage.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 30 | 42 | 43 | 44 | 45 | 49 | 61 | 62 | 63 | 64 | 68 | 80 | 81 |
Index Ingest 27 | enabled 28 | disabled 29 | 31 | 36 | 41 |
Index Queries 46 | enabled 47 | disabled 48 | 50 | 55 | 60 |
Index Partition Reassignments 65 | disabled 66 | enabled 67 | 69 | 74 | 79 |
82 | 83 | 84 | 85 | 86 | 87 | 90 | 91 | 92 | 93 | 94 | 100 | 101 |
Index PartitionSource Partitions 88 | {{nodeAddr}} 89 |
{{planPIndex.name}}{{planPIndex.sourcePartitionsStr}} 95 |
97 | {{(planPIndex.nodes[nodeDefsByAddr[nodeAddr].uuid].canRead && 'r') || ' '}}{{(planPIndex.nodes[nodeDefsByAddr[nodeAddr].uuid].canWrite && 'w') || ' '}} 98 |
99 |
102 | 103 | 146 | -------------------------------------------------------------------------------- /rest/static/partials/index/tab-query.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 16 | 17 | 47 | 48 |
49 |
50 |
51 | 52 | 55 |
56 |
57 | 58 | 61 |
62 |
63 | 64 | 67 |
68 |
69 | 75 |
{{jsonQuery}}
76 |
curl -XPOST -H "Content-Type: application/json" \
 77 |  http://{{hostPort}}/api/index/{{indexName}}/query \
 78 |  -d '{{jsonQuery}}'
79 |
80 |
81 |
82 | 83 |
85 |
no results for your query
86 |
87 | 88 |
92 |
93 | 94 |
95 |
    96 |
  • 97 |
    98 | {{result}} 99 |
    100 |
  • 101 |
102 |
103 | 104 |
105 | 106 | 123 | -------------------------------------------------------------------------------- /rest/static/partials/index/tab-summary.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |
13 |
Document Count:
14 |
15 | 16 | 17 | 22 |
23 | 24 |
25 |
26 | 27 |
Index Name:
28 |
{{indexDef.name}}
29 |
Index UUID:
30 |
{{indexDef.uuid}}
31 |
Index Type:
32 |
{{indexDef.type}}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Source Type:
42 |
{{indexDef.sourceType}}
43 |
Source Name:
44 |
{{indexDef.sourceName}}
45 | 46 |
47 | 48 |
Index Definition:
49 |
50 | 51 | Show index definition JSON 52 | 53 |
54 | 55 | Show curl command to modify this index definition 56 | 57 | 58 |
curl -XPUT -H "Content-Type: application/json" \
59 |  http://{{hostPort}}{{api_base}}/api/index/{{indexName}} \
60 |  -d '{{indexDefStr}}'
61 | 62 |
64 |         {{indexDefStr}}
65 |       
66 |
67 |
68 |
69 | 70 | 75 | -------------------------------------------------------------------------------- /rest/static/partials/index/tabs.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /rest/static/partials/logs.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |

Events

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
EventNameTime
{{e.event}}{{e.name}}{{e.time}}
26 | 27 |
28 | no events 29 |
30 | 31 | 34 |
35 | 36 |
37 |

Logs

38 | 39 | 42 | 43 |
{{logMessages}}
44 | 45 | 48 |
49 | 50 | 67 | -------------------------------------------------------------------------------- /rest/static/partials/manage.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |

Manage

12 | 13 |

Kick

14 | 15 | Re-run the planner and janitor... 16 | 17 |
18 |
19 | 20 |
21 | 23 |
24 |
25 | 26 | 30 |
31 | 32 |
33 |
34 |
{{resultManagerKick}}
35 |
36 | 37 |

Cfg

38 | 39 | 40 | Show Cfg contents as JSON 41 | 42 | 43 |
44 | 48 | 49 |
50 |
51 |
{{resultCfgRefresh}}
52 |
53 | 54 |
55 | 56 |
57 |

58 |   
59 | 60 | 64 | 65 |
66 |
67 |
{{resultCfgRefresh}}
68 |
69 |
70 | -------------------------------------------------------------------------------- /rest/static/partials/monitor.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |

Monitor

12 | 13 |

Process Stats

14 | 15 |
16 | 17 |

Process Index Stats

18 | 19 | 22 | 23 |
24 | 25 | 33 | -------------------------------------------------------------------------------- /rest/static/partials/node/list.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 34 | 44 | 49 | 50 |
Node
27 | 28 | 30 | 32 | {{nodeDef.hostPort}} ({{nodeDef.uuid}}) 33 | 35 |
    36 |
  • 38 |
    39 | {{nodeDefContainerPart}} 40 |
    41 |
  • 42 |
43 |
45 | 46 |  http://{{nodeDef.hostPort}}{{static_base}} 47 | 48 |
51 | 52 | 67 | 68 | -------------------------------------------------------------------------------- /rest/static/partials/node/node.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 20 | 21 | 24 | 25 |
26 | 27 |
28 | -------------------------------------------------------------------------------- /rest/static/partials/node/tab-summary.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |
Node:
13 |
14 | 15 | 17 | 19 | {{nodeUUID}} 20 |    /    21 | 22 |  http://{{resultCfg.nodeDefsKnown.nodeDefs[nodeUUID].hostPort}}{{static_base}} 23 | 24 |
25 |
26 | 27 |
Version:
28 |
{{resultCfg.nodeDefsKnown.nodeDefs[nodeUUID].implVersion}}
29 |
UUID:
30 |
{{resultCfg.nodeDefsKnown.nodeDefs[nodeUUID].uuid}}
31 |
Host / Port:
32 |
{{resultCfg.nodeDefsKnown.nodeDefs[nodeUUID].hostPort}}
33 |
Tags:
34 |
{{resultCfg.nodeDefsKnown.nodeDefs[nodeUUID].tags}}
35 |
Container:
36 |
37 |
    39 |
  • 41 |
    42 | {{nodeDefContainerPart}} 43 |
    44 |
  • 45 |
46 |
47 |
Weight:
48 |
{{resultCfg.nodeDefsKnown.nodeDefs[nodeUUID].weight}}
49 |
50 | 51 | 63 | -------------------------------------------------------------------------------- /rest/static/partials/node/tabs.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /rest/static/tabs/tab.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
  • 12 | {{heading}} 13 |
  • -------------------------------------------------------------------------------- /rest/static/tabs/tabset.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
    12 | 13 |
    14 |
    18 |
    19 |
    20 |
    -------------------------------------------------------------------------------- /rest/static_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package rest 10 | 11 | import ( 12 | "os" 13 | "testing" 14 | ) 15 | 16 | func TestAssetFS(t *testing.T) { 17 | // Get code coverage for the assets embedded into 18 | // bindata_assetfs.go via the 19 | // github.com/elazarl/go-bindata-assetfs tool. 20 | if AssetFS() == nil { 21 | t.Errorf("expected an assetFS") 22 | } 23 | 24 | d, _ := os.MkdirTemp("./tmp", "test") 25 | defer os.RemoveAll(d) 26 | 27 | err := RestoreAssets(d, "static") 28 | if err != nil { 29 | t.Errorf("expected RestoreAssets to work, err: %v", err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rest/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /task_scatter_gatherer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "fmt" 13 | "sync" 14 | ) 15 | 16 | // TaskRequestHandler represents the interface that 17 | // need to implemented by the partitions for using 18 | // the task scatter gatherer. 19 | type TaskRequestHandler interface { 20 | HandleTask([]byte) (*TaskRequestStatus, error) 21 | Name() string 22 | } 23 | 24 | // TaskRequest represent a generic task request like 25 | // "compact" or "encrypt" for partitions 26 | type TaskRequest struct { 27 | Op string `json:"op"` 28 | UUID string `json:"uuid"` 29 | Contents map[string]interface{} `json:"contents,omitempty"` 30 | PartitionNames []string `json:"partitionNames,omitempty"` 31 | } 32 | 33 | // PartitionErrMap tracks errors with the name 34 | // of the partition where it occurred 35 | type PartitionErrMap map[string]error 36 | 37 | // TaskPartitionStatusMap tracks the current state 38 | // of a task across the partitions 39 | type TaskPartitionStatusMap map[string]string 40 | 41 | // MarshalJSON seralizes the error into a string for JSON consumption 42 | func (pem PartitionErrMap) MarshalJSON() ([]byte, error) { 43 | tmp := make(map[string]string, len(pem)) 44 | for k, v := range pem { 45 | tmp[k] = v.Error() 46 | } 47 | return MarshalJSON(tmp) 48 | } 49 | 50 | func (pem PartitionErrMap) UnmarshalJSON(data []byte) error { 51 | var tmp map[string]string 52 | err := UnmarshalJSON(data, &tmp) 53 | if err != nil { 54 | return err 55 | } 56 | for k, v := range tmp { 57 | pem[k] = fmt.Errorf("%s", v) 58 | } 59 | return nil 60 | } 61 | 62 | type TaskRequestStatus struct { 63 | Request *TaskRequest `json:"request"` 64 | Total int `json:"total"` 65 | Failed int `json:"failed"` 66 | Successful int `json:"successful"` 67 | Errors PartitionErrMap `json:"errors,omitempty"` 68 | Status TaskPartitionStatusMap `json:"status,omitempty"` 69 | } 70 | 71 | func (trs *TaskRequestStatus) Merge(other *TaskRequestStatus) { 72 | trs.Total += other.Total 73 | trs.Successful += other.Successful 74 | trs.Failed += other.Failed 75 | if len(other.Errors) > 0 { 76 | if trs.Errors == nil { 77 | trs.Errors = make(map[string]error) 78 | } 79 | for oPartition, oErr := range other.Errors { 80 | trs.Errors[oPartition] = oErr 81 | } 82 | } 83 | 84 | if len(other.Status) > 0 { 85 | if trs.Status == nil { 86 | trs.Status = make(map[string]string) 87 | } 88 | for pname, state := range other.Status { 89 | trs.Status[pname] = state 90 | } 91 | } 92 | 93 | if trs.Request == nil { 94 | trs.Request = other.Request 95 | } 96 | } 97 | 98 | type partialResultStatus struct { 99 | name string 100 | err error 101 | reqStatus *TaskRequestStatus 102 | } 103 | 104 | func ScatterTaskRequest(req []byte, 105 | partitions []TaskRequestHandler) (*TaskRequestStatus, error) { 106 | var waitGroup sync.WaitGroup 107 | asyncResults := make(chan *partialResultStatus, len(partitions)) 108 | 109 | var scatterRequest = func(in TaskRequestHandler, childReq []byte) { 110 | rv := partialResultStatus{name: in.Name()} 111 | rv.reqStatus, rv.err = in.HandleTask(childReq) 112 | asyncResults <- &rv 113 | waitGroup.Done() 114 | } 115 | 116 | waitGroup.Add(len(partitions)) 117 | for _, p := range partitions { 118 | go scatterRequest(p, req) 119 | } 120 | 121 | // on another go routine, close after finished 122 | go func() { 123 | waitGroup.Wait() 124 | close(asyncResults) 125 | }() 126 | 127 | sr := &TaskRequestStatus{} 128 | partitionErrs := make(map[string]error) 129 | 130 | for asr := range asyncResults { 131 | if asr.err == nil { 132 | if sr == nil { 133 | sr = asr.reqStatus 134 | } else { 135 | sr.Merge(asr.reqStatus) 136 | } 137 | } else { 138 | partitionErrs[asr.name] = asr.err 139 | } 140 | } 141 | 142 | if len(partitionErrs) > 0 { 143 | if sr.Errors == nil { 144 | sr.Errors = make(map[string]error) 145 | } 146 | for indexName, indexErr := range partitionErrs { 147 | sr.Errors[indexName] = indexErr 148 | sr.Total++ 149 | sr.Failed++ 150 | } 151 | } 152 | 153 | return sr, nil 154 | } 155 | -------------------------------------------------------------------------------- /tmp/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /version.md: -------------------------------------------------------------------------------- 1 | Version 2 | ======= 3 | 4 | ### Documentation 5 | 6 | The cbgt.VERSION tracks persistence versioning of the schema/format of 7 | persisted data and configuration. You *must* update the cbgt.VERSION if you 8 | change what's stored in the Cfg such as the JSON/struct definitions or the planning 9 | algorithms. If the persisted data/config format was unchanged, then the cbgt.VERSION 10 | number should remain unchanged. 11 | 12 | These version number checks prevent the older planners from operating/modifying on 13 | the configs laid out by the newer version planners, as the config formats would 14 | have got changed over the version increments. 15 | 16 | ### Few rules of thumb for version management 17 | 18 | In a multi node cluster, the version bump happens only when all the nodes in the 19 | cluster reaches at a homogeneous CBGT version. This effective version consistency 20 | check is performed over a compatibility version check rest call with ns-server 21 | along with a version check derived from nodeDefiniions as a fallback mechanism, 22 | in case the former approach fails. 23 | 24 | - Ctl (rebalance), Planner are the two routines responsible for bumping the version 25 | value in a cluster. 26 | - All routines are supposed to use the CfgGetVersion api to retrieve the effective 27 | version in a cluster before attempting any version sensitive config operations. 28 | - The planner should respect the input version number parameterised to it. 29 | - The ImplVersion value embedded inside the index definitions, node Definitions and 30 | plan pindexes are also supposed to get incremented only after the effective 31 | compatible version in a cluster gets incremented. 32 | -------------------------------------------------------------------------------- /work.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import "runtime" 12 | 13 | const WORK_NOOP = "" 14 | const WORK_KICK = "kick" 15 | 16 | // A workReq represents an asynchronous request for work or a task, 17 | // where results can be awaited upon via the resCh. 18 | type workReq struct { 19 | op string // The operation. 20 | msg string // Some simple msg as part of the request. 21 | obj interface{} // Any extra params for the request. 22 | resCh chan error // Response/result channel. 23 | } 24 | 25 | // syncWorkReq makes a workReq request and synchronously awaits for a 26 | // resCh response. 27 | func syncWorkReq(ch chan *workReq, op, msg string, obj interface{}) error { 28 | resCh := make(chan error) 29 | ch <- &workReq{op: op, msg: msg, obj: obj, resCh: resCh} 30 | return <-resCh 31 | } 32 | 33 | func getWorkerCount(itemCount int) int { 34 | ncpu := runtime.NumCPU() 35 | if itemCount < ncpu { 36 | return itemCount 37 | } 38 | return ncpu 39 | } 40 | -------------------------------------------------------------------------------- /work_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-Present Couchbase, Inc. 2 | // 3 | // Use of this software is governed by the Business Source License included 4 | // in the file licenses/BSL-Couchbase.txt. As of the Change Date specified 5 | // in that file, in accordance with the Business Source License, use of this 6 | // software will be governed by the Apache License, Version 2.0, included in 7 | // the file licenses/APL2.txt. 8 | 9 | package cbgt 10 | 11 | import ( 12 | "testing" 13 | ) 14 | 15 | func TestSyncWorkReq(t *testing.T) { 16 | ch := make(chan *workReq) 17 | go func() { 18 | w, ok := <-ch 19 | if !ok || w == nil { 20 | t.Errorf("expected ok and w") 21 | } 22 | if w.op != "op" || w.msg != "msg" { 23 | t.Errorf("expected op and msg") 24 | } 25 | close(w.resCh) 26 | w, ok = <-ch 27 | if ok || w != nil { 28 | t.Errorf("expected done") 29 | } 30 | }() 31 | 32 | err := syncWorkReq(ch, "op", "msg", nil) 33 | if err != nil { 34 | t.Errorf("expect nil err") 35 | } 36 | close(ch) 37 | } 38 | --------------------------------------------------------------------------------