xref: /4.6.0/couchbase-cli/buckets.py (revision 51c42ae4)
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4from usage import command_error
5
6import restclient
7from timeout import timed_out
8import time
9import sys
10import urllib
11import util_cli as util
12
13rest_cmds = {
14    'bucket-list': '/pools/default/buckets',
15    'bucket-flush': '/pools/default/buckets/',
16    'bucket-delete': '/pools/default/buckets/',
17    'bucket-create': '/pools/default/buckets/',
18    'bucket-edit': '/pools/default/buckets/',
19    'bucket-get': '/pools/default/buckets',
20    'bucket-stats': '/pools/default/buckets/%s/stats?zoom=hour',
21    'bucket-node-stats': '/pools/default/buckets/%s/stats/%s?zoom=%s',
22    'bucket-info': '/pools/default/buckets/%s',
23    'bucket-compact': '/pools/default/buckets/',
24    'bucket-ddocs': '/pools/default/buckets/%s/ddocs',
25    }
26methods = {
27    'bucket-list': 'GET',
28    'bucket-delete': 'DELETE',
29    'bucket-create': 'POST',
30    'bucket-edit': 'POST',
31    'bucket-flush': 'POST',
32    'bucket-get': 'GET',
33    'bucket-stats': 'GET',
34    'bucket-node-stats': 'GET',
35    'bucket-compact': 'POST',
36    'bucket-ddocs': 'GET',
37    }
38
39priority = {
40    'low': 3,
41    'high': 8,
42    }
43
44class Buckets:
45    def __init__(self):
46        self.debug = False
47        self.rest_cmd = rest_cmds['bucket-list']
48        self.method = 'GET'
49        self.ssl = False
50
51    @timed_out(60)
52    def runCmd(self, cmd, server, port,
53               user, password, ssl, opts):
54        self.user = user
55        self.password = password
56        self.ssl = ssl
57
58        bucketname = ''
59        buckettype = ''
60        authtype = 'sasl'
61        bucketport = '11211'
62        bucketpassword = ''
63        bucketramsize = ''
64        bucketreplication = ''
65        bucketpriority = None
66        eviction_policy = None
67        output = 'default'
68        wait_for_bucket_ready = False
69        enable_flush = None
70        enable_replica_index = None
71        conflict_resolution = 'sequence'
72        force = False
73        compact_data_only = False
74        compact_view_only = False
75
76        for (o, a) in opts:
77            if o in ('-b', '--bucket'):
78                bucketname = a
79            elif o == '--bucket-type':
80                buckettype = a
81            elif o == '--bucket-port':
82                bucketport = a
83            elif o == '--bucket-password':
84                bucketpassword = a
85            elif o == '--bucket-ramsize':
86                bucketramsize = a
87            elif o == '--bucket-replica':
88                bucketreplication = a
89            elif o == '--bucket-eviction-policy':
90                eviction_policy = a
91            elif o == '--bucket-priority':
92                bucketpriority = a
93            elif o == '-d' or o == '--debug':
94                self.debug = True
95            elif o in ('-o', '--output'):
96                output = a
97            elif o == '--enable-flush':
98                enable_flush = a
99            elif o == '--enable-index-replica':
100                enable_replica_index = a
101            elif o == '--conflict-resolution':
102                conflict_resolution = a
103            elif o == '--wait':
104                wait_for_bucket_ready = True
105            elif o == '--force':
106                force = True
107            elif o == '--data-only':
108                compact_data_only = True
109            elif o == '--view-only':
110                compact_view_only = True
111
112        self.rest_cmd = rest_cmds[cmd]
113        rest = util.restclient_factory(server, port, {'debug':self.debug}, self.ssl)
114        #rest = restclient.RestClient(server, port, {'debug':self.debug})
115        # get the parameters straight
116        opts = {}
117        opts['error_msg'] = "unable to %s;" % cmd
118        opts['success_msg'] = "%s" % cmd
119
120        if cmd in ('bucket-create', 'bucket-edit'):
121            if bucketname:
122                rest.setParam('name', bucketname)
123                if bucketname == "default":
124                    if bucketport and bucketport != "11211":
125                        command_error("default bucket must be on port 11211.")
126                    if bucketpassword:
127                        command_error("default bucket should only have empty password.")
128                    authtype = 'sasl'
129                else:
130                    if bucketport == "11211":
131                        authtype = 'sasl'
132                    else:
133                        authtype = 'none'
134                        if bucketpassword:
135                            command_error("a sasl bucket is supported only on port 11211.")
136            if buckettype:
137                rest.setParam('bucketType', buckettype)
138            if authtype:
139                rest.setParam('authType', authtype)
140            if bucketport:
141                rest.setParam('proxyPort', bucketport)
142            if bucketpassword:
143                rest.setParam('saslPassword', bucketpassword)
144            if bucketramsize:
145                rest.setParam('ramQuotaMB', bucketramsize)
146            if bucketreplication:
147                rest.setParam('replicaNumber', bucketreplication)
148            if enable_flush:
149                rest.setParam('flushEnabled', enable_flush)
150            if eviction_policy:
151                if eviction_policy in ['valueOnly', 'fullEviction']:
152                    rest.setParam('evictionPolicy', eviction_policy)
153                else:
154                    command_error("eviction policy value should be either 'valueOnly' or 'fullEviction'.")
155            if enable_replica_index and cmd == 'bucket-create':
156                rest.setParam('replicaIndex', enable_replica_index)
157            if cmd == 'bucket-create':
158                if conflict_resolution == 'timestamp':
159                    rest.setParam('conflictResolutionType', 'lww')
160                elif conflict_resolution == 'sequence':
161                    rest.setParam('conflictResolutionType', 'seqno')
162                else:
163                    command_error("--conflict-resolution must be timestamp or sequence")
164
165            if bucketpriority:
166                if bucketpriority in priority:
167                    rest.setParam('threadsNumber', priority[bucketpriority])
168                else:
169                    command_error("bucket priority must be either low or high.")
170
171        if cmd in ('bucket-delete', 'bucket-flush', 'bucket-edit'):
172            self.rest_cmd = self.rest_cmd + bucketname
173        if cmd == 'bucket-flush':
174            self.rest_cmd = self.rest_cmd + '/controller/doFlush'
175            if not force:
176                question = "Running this command will totally PURGE database data from disk. " + \
177                           "Do you really want to do it? (Yes/No)"
178                confirm = raw_input(question)
179                if confirm in ('Y', 'Yes'):
180                    print "\nDatabase data will be purged from disk ..."
181                else:
182                    print "\nDatabase data will not be purged. Done."
183                    return False
184            else:
185                print "Database data will be purged from disk ..."
186            opts['error_msg'] = "unable to %s; please check if the bucket exists or not;" % cmd
187        elif cmd == 'bucket-compact':
188            if compact_data_only and compact_view_only:
189                print "You cannot compact data only and view only at the same time."
190                return False
191            elif compact_data_only:
192                self.rest_cmd = self.rest_cmd + bucketname + '/controller/compactDatabases'
193            elif compact_view_only:
194                self.compact_view(rest, server, port, bucketname)
195                return True
196            else:
197                self.rest_cmd = self.rest_cmd + bucketname + '/controller/compactBucket'
198        elif cmd == 'bucket-ddocs':
199            self.rest_cmd = self.rest_cmd % bucketname
200
201        data = rest.restCmd(methods[cmd], urllib.quote(self.rest_cmd),
202                            self.user, self.password, opts)
203
204        if cmd in ("bucket-get", "bucket-stats", "bucket-node-stats", "bucket-ddocs"):
205            return rest.getJson(data)
206        elif cmd == "bucket-list":
207            if output == 'json':
208                print data
209            else:
210                json = rest.getJson(data)
211                for bucket in json:
212                    print '%s' % bucket['name']
213                    print ' bucketType: %s' % bucket['bucketType']
214                    print ' authType: %s' % bucket['authType']
215                    if bucket['authType'] == "sasl":
216                        print ' saslPassword: %s' % bucket['saslPassword']
217                    else:
218                        print ' proxyPort: %s' % bucket['proxyPort']
219                    print ' numReplicas: %s' % bucket['replicaNumber']
220                    print ' ramQuota: %s' % bucket['quota']['ram']
221                    print ' ramUsed: %s' % bucket['basicStats']['memUsed']
222        elif cmd == "bucket-create" and wait_for_bucket_ready:
223            rest_query = util.restclient_factory(server, port, {'debug':self.debug}, self.ssl)
224            timeout_in_seconds = 120
225            start = time.time()
226            # Make sure the bucket exists before querying its status
227            bucket_exist = False
228            while (time.time() - start) <= timeout_in_seconds and not bucket_exist:
229                buckets = rest_query.restCmd('GET', rest_cmds['bucket-list'],
230                                             self.user, self.password, opts)
231                for bucket in rest_query.getJson(buckets):
232                    if bucket["name"] == bucketname:
233                        bucket_exist = True
234                        break
235                if not bucket_exist:
236                    sys.stderr.write(".")
237                    time.sleep(2)
238
239            if not bucket_exist:
240                print "\nFail to create bucket '%s' within %s seconds" %\
241                      (bucketname, timeout_in_seconds)
242                return False
243
244            #Query status for all bucket nodes
245            while (time.time() - start) <= timeout_in_seconds:
246                bucket_info = rest_query.restCmd('GET', urllib.quote(rest_cmds['bucket-info'] % bucketname),
247                                                 self.user, self.password, opts)
248                json = rest_query.getJson(bucket_info)
249                all_node_ready = True
250                for node in json["nodes"]:
251                    if node["status"] != "healthy":
252                        all_node_ready = False
253                        break
254                if all_node_ready:
255                    if output == 'json':
256                        print rest.jsonMessage(data)
257                    else:
258                        print data
259                    return True
260                else:
261                    sys.stderr.write(".")
262                    time.sleep(2)
263
264            print "\nBucket '%s' is created but not ready to use within %s seconds" %\
265                 (bucketname, timeout_in_seconds)
266            return False
267        else:
268            if output == 'json':
269                print rest.jsonMessage(data)
270            else:
271                print data
272
273    def compact_view(self, rest, server, port, bucket_name):
274        opts = {}
275        rest_query = util.restclient_factory(server, port, {'debug':self.debug}, self.ssl)
276        rest_cmd = '/pools/default/buckets/%s/ddocs' % bucket_name
277        ddoc_info = rest_query.restCmd('GET', urllib.quote(rest_cmd),
278                                       self.user, self.password, opts)
279        json = rest_query.getJson(ddoc_info)
280        for row in json["rows"]:
281            cmd = row["controllers"]["compact"]
282            opts['error_msg'] = "fail to run task:%s" % cmd
283            opts['success_msg'] = "run task:%s" % cmd
284            data = rest.restCmd('POST', cmd,
285                                self.user, self.password, opts)
286            print data
287
288    def getCommandSummary(self, cmd):
289        """Return one-line summary info for each supported command"""
290        command_summary = {
291            "bucket-list" : "list all buckets in a cluster",
292            "bucket-create" : "add a new bucket to the cluster",
293            "bucket-edit" : "modify an existing bucket",
294            "bucket-delete" : "delete an existing bucket",
295            "bucket-flush" : "flush all data from disk for a given bucket",
296            "bucket-compact" : "compact database and index data"}
297        if cmd in command_summary:
298            return command_summary[cmd]
299        else:
300            return None
301
302    def getCommandHelp(self, cmd):
303        """ Obtain detailed parameter help for Bucket commands
304        Returns a list of pairs (arg1, arg1-information) or None if there's
305        no help or cmd is unknown.
306        """
307        bucket_name = [("--bucket=BUCKETNAME", "bucket to act on")]
308        bucket_replica = [("--bucket-replica=COUNT", "replication count")]
309        bucket_ramsize = [("--bucket-ramsize=RAMSIZEMB", "ram quota in MB")]
310        bucket_type = [("--bucket-type=TYPE","memcached or couchbase")]
311        bucket_priority = [("--bucket-priority=[low|high]",
312                            "priority when compared to other buckets")]
313        bucket_port = [("--bucket-port=PORT",
314                        "supports ASCII protocol and is auth-less")]
315        bucket_password = [("--bucket-password=PASSWORD",
316                            "standard port, exclusive with bucket-port")]
317        eviction_policy = [("--bucket-eviction-policy=[valueOnly|fullEviction]",
318                            "policy how to retain meta in memory")]
319        enable_flush = [("--enable-flush=[0|1]", "enable/disable flush")]
320        enable_replica_idx = [("--enable-index-replica=[0|1]",
321                               "enable/disable index replicas")]
322        conflict_resolution = [("--conflict-resolution",
323                                "Specifies the XDCR conflict resolution type (timestamp or sequence)")]
324
325        force = [("--force",
326                  "force to execute command without asking for confirmation")]
327        wait = [("--wait",
328                 "wait for bucket create to be complete before returning")]
329        compact_data = [("--data-only", "compact database data only")]
330        compact_view = [("--view-only", "compact view data only")]
331
332        create_edit = (bucket_name + bucket_ramsize + bucket_replica +
333                      bucket_type + bucket_priority + bucket_password +
334                      eviction_policy + enable_flush)
335
336        if cmd == "bucket-create":
337            return create_edit + enable_replica_idx + conflict_resolution + wait
338        elif cmd == "bucket-edit":
339            return create_edit
340        elif cmd == "bucket-delete":
341            return bucket_name
342        elif cmd == "bucket-flush":
343            return bucket_name + force
344        elif cmd == "bucket-compact":
345            return bucket_name + compact_data + compact_view
346
347    def getCommandExampleHelp(self, cmd):
348        """ Obtain detailed example help for command
349        Returns a list of command examples to illustrate how to use command
350        or None if there's no example help or cmd is unknown.
351        """
352
353        if cmd == "bucket-list":
354            return [("List buckets in a cluster",
355"""
356    couchbase-cli bucket-list -c 192.168.0.1:8091""")]
357        elif cmd == "bucket-create":
358            return [("Create a new dedicated port couchbase bucket",
359"""
360    couchbase-cli bucket-create -c 192.168.0.1:8091 \\
361       --bucket=test_bucket \\
362       --bucket-type=couchbase \\
363       --bucket-port=11222 \\
364       --bucket-ramsize=200 \\
365       --bucket-replica=1 \\
366       --bucket-priority=high \\
367       --bucket-eviction-policy=valueOnly \\
368       --conflict-resolution=timestamp \\
369       -u Administrator -p password"""),
370                ("Create a couchbase bucket and wait for bucket ready",
371"""
372    couchbase-cli bucket-create -c 192.168.0.1:8091 \\
373       --bucket=test_bucket \\
374       --bucket-type=couchbase \\
375       --bucket-port=11222 \\
376       --bucket-ramsize=200 \\
377       --bucket-replica=1 \\
378       --bucket-priority=low \\
379       --wait \\
380       -u Administrator -p password"""),
381                ("Create a new sasl memcached bucket",
382"""
383    couchbase-cli bucket-create -c 192.168.0.1:8091 \\
384       --bucket=test_bucket \\
385       --bucket-type=memcached \\
386       --bucket-password=password \\
387       --bucket-ramsize=200 \\
388       --bucket-eviction-policy=valueOnly \\
389       --enable-flush=1 \\
390       -u Administrator -p password""")]
391        elif cmd == "bucket-edit":
392            return [("Modify a dedicated port bucket",
393"""
394    couchbase-cli bucket-edit -c 192.168.0.1:8091 \\
395       --bucket=test_bucket \\
396       --bucket-port=11222 \\
397       --bucket-ramsize=400 \\
398       --bucket-eviction-policy=fullEviction \\
399       --enable-flush=1 \\
400       --bucket-priority=high \\
401       -u Administrator -p password""")]
402        elif cmd == "bucket-delete":
403            return [("Delete a bucket",
404"""
405    couchbase-cli bucket-delete -c 192.168.0.1:8091 \\
406       --bucket=test_bucket""")]
407        elif cmd == "bucket-compact":
408            return [("Compact a bucket for both data and view",
409"""
410    couchbase-cli bucket-compact -c 192.168.0.1:8091 \\
411        --bucket=test_bucket \\
412        -u Administrator -p password"""),
413                    ("Compact a bucket for data only",
414"""
415    couchbase-cli bucket-compact -c 192.168.0.1:8091 \\
416        --bucket=test_bucket \\
417        --data-only \\
418        -u Administrator -p password"""),
419                    ("Compact a bucket for view only",
420"""
421    couchbase-cli bucket-compact -c 192.168.0.1:8091 \\
422        --bucket=test_bucket \\
423        --view-only \\
424        -u Administrator -p password""")]
425        elif cmd == "bucket-flush":
426            return [("Flush a bucket",
427"""
428    couchbase-cli bucket-flush -c 192.168.0.1:8091 \\
429       --force \\
430       -u Administrator -p password""")]
431        else:
432            return None
433
434class BucketStats:
435    def __init__(self, bucket_name):
436        self.debug = False
437        self.rest_cmd = rest_cmds['bucket-stats'] % bucket_name
438        self.method = 'GET'
439
440    def runCmd(self, cmd, server, port,
441               user, password, ssl, opts):
442        opts = {}
443        opts['error_msg'] = "unable to %s" % cmd
444        opts['success_msg'] = "%s" % cmd
445
446        #print server, port, cmd, self.rest_cmd
447        rest = util.restclient_factory(server, port, {'debug':self.debug}, ssl)
448        data = rest.restCmd(methods[cmd], self.rest_cmd,
449                            user, password, opts)
450        return rest.getJson(data)
451
452class BucketNodeStats:
453    def __init__(self, bucket_name, stat_name, scale):
454        self.debug = False
455        self.rest_cmd = rest_cmds['bucket-node-stats'] % (bucket_name, stat_name, scale)
456        self.method = 'GET'
457        #print self.rest_cmd
458
459    def runCmd(self, cmd, server, port,
460               user, password, ssl, opts):
461        opts = {}
462        opts['error_msg'] = "unable to %s" % cmd
463        opts['success_msg'] = "%s" % cmd
464
465        #print server, port, cmd, self.rest_cmd
466        rest = util.restclient_factory(server, port, {'debug':self.debug}, ssl)
467        data = rest.restCmd(methods[cmd], self.rest_cmd,
468                            user, password, opts)
469        return rest.getJson(data)
470