xref: /4.6.0/couchbase-cli/node.py (revision dd217e00)
1"""
2  Implementation for rebalance, add, remove, stop rebalance.
3"""
4
5import platform
6import cluster_manager
7import getpass
8import random
9import subprocess
10import time
11import os
12import sys
13import util_cli as util
14import socket
15import re
16import urlparse
17import json
18
19from usage import command_error
20from restclient import *
21from listservers import *
22from _csv import reader
23
24try:
25    import pump_bfd2
26    IS_ENTERPRISE= True
27except ImportError:
28    IS_ENTERPRISE = False
29
30MAX_LEN_PASSWORD = 24
31
32# the rest commands and associated URIs for various node operations
33
34rest_cmds = {
35    'rebalance'         :'/controller/rebalance',
36    'rebalance-stop'    :'/controller/stopRebalance',
37    'rebalance-status'  :'/pools/default/rebalanceProgress',
38    'server-add'        :'/controller/addNode',
39    'server-readd'      :'/controller/reAddNode',
40    'failover'          :'/controller/failOver',
41    'recovery'          :'/controller/setRecoveryType',
42    'cluster-init'      :'/settings/web',
43    'cluster-edit'      :'/settings/web',
44    'node-init'         :'/nodes/self/controller/settings',
45    'setting-cluster'   :'/pools/default',
46    'setting-compaction'    :'/controller/setAutoCompaction',
47    'setting-notification'  :'/settings/stats',
48    'setting-autofailover'  :'/settings/autoFailover',
49    'setting-alert'         :'/settings/alerts',
50    'setting-audit'         :'/settings/audit',
51    'setting-ldap'          :'/settings/saslauthdAuth',
52    'user-manage'           :'/settings/readOnlyUser',
53    'setting-index'         :'/settings/indexes',
54    'group-manage'          :'/pools/default/serverGroups',
55    'ssl-manage'            :'/pools/default/certificate',
56    'collect-logs-start'  : '/controller/startLogsCollection',
57    'collect-logs-stop'   : '/controller/cancelLogsCollection',
58    'collect-logs-status' : '/pools/default/tasks',
59    'admin-role-manage'   : '/settings/rbac/users',
60    'master-password'     : '/node/controller/changeMasterPassword'
61}
62
63server_no_remove = [
64    'rebalance-stop',
65    'rebalance-status',
66    'server-add',
67    'server-readd',
68    'failover',
69    'recovery',
70]
71server_no_add = [
72    'rebalance-stop',
73    'rebalance-status',
74    'failover',
75    'recovery',
76]
77
78# Map of operations and the HTTP methods used against the REST interface
79
80methods = {
81    'rebalance'         :'POST',
82    'rebalance-stop'    :'POST',
83    'rebalance-status'  :'GET',
84    'eject-server'      :'POST',
85    'server-add'        :'POST',
86    'server-readd'      :'POST',
87    'failover'          :'POST',
88    'recovery'          :'POST',
89    'cluster-init'      :'POST',
90    'cluster-edit'      :'POST',
91    'node-init'         :'POST',
92    'setting-cluster'   :'POST',
93    'setting-compaction'    :'POST',
94    'setting-notification'  :'POST',
95    'setting-autofailover'  :'POST',
96    'setting-alert'         :'POST',
97    'setting-audit'         :'POST',
98    'setting-ldap'          :'POST',
99    'setting-index'         :'POST',
100    'user-manage'           :'POST',
101    'group-manage'          :'POST',
102    'ssl-manage'            :'GET',
103    'collect-logs-start'  : 'POST',
104    'collect-logs-stop'   : 'POST',
105    'collect-logs-status' : 'GET',
106    'admin-role-manage'   : 'PUT',
107    'master-password'     : 'POST',
108}
109
110bool_to_str = lambda value: str(bool(int(value))).lower()
111
112# Map of HTTP success code, success message and error message for
113# handling HTTP response properly
114
115class Node:
116    SEP = ","
117    def __init__(self):
118        self.rest_cmd = rest_cmds['rebalance-status']
119        self.method = 'GET'
120        self.debug = False
121        self.server = ''
122        self.port = ''
123        self.user = ''
124        self.password = ''
125        self.ssl = False
126
127        self.ro_username = ''
128        self.ro_password = ''
129        self.params = {}
130        self.output = 'standard'
131        self.password_new = None
132        self.username_new = None
133        self.sa_username = None
134        self.sa_password = None
135        self.port_new = None
136        self.per_node_quota = None
137        self.cluster_index_ramsize = None
138        self.cluster_fts_ramsize = None
139        self.index_storage_setting = None
140        self.cluster_name = None
141        self.data_path = None
142        self.index_path = None
143        self.hostname = None
144        self.enable_auto_failover = None
145        self.enable_notification = None
146        self.autofailover_timeout = None
147        self.enable_email_alert = None
148
149        #compaction related settings
150        self.compaction_db_percentage = None
151        self.compaction_db_size = None
152        self.compaction_view_percentage = None
153        self.compaction_view_size = None
154        self.compaction_period_from = None
155        self.compaction_period_to = None
156        self.enable_compaction_abort = None
157        self.enable_compaction_parallel = None
158        self.purge_interval = None
159        self.gsi_compact_mode = None
160        self.gsi_compact_perc = None
161        self.gsi_compact_interval = None
162        self.gsi_compact_period_from = None
163        self.gsi_compact_period_to = None
164        self.gsi_compact_abort = None
165
166        #alert settings
167        self.email_recipient = None
168        self.email_sender = None
169        self.email_user = None
170        self.email_password = None
171        self.email_host = None
172        self.email_port = None
173        self.email_enable_encrypt = None
174        self.autofailover_node = None
175        self.autofailover_max_reached = None
176        self.autofailover_node_down = None
177        self.autofailover_cluster_small = None
178        self.autofailover_disabled = None
179        self.alert_ip_changed = None
180        self.alert_disk_space = None
181        self.alert_meta_overhead = None
182        self.alert_meta_oom = None
183        self.alert_write_failed = None
184        self.alert_audit_dropped = None
185
186        #group management
187        self.group_name = None
188        self.server_list = []
189        self.from_group = None
190        self.to_group = None
191        self.group_rename = None
192
193        #SSL certificate management
194        self.certificate_file = None
195        self.extended = False
196        self.cmd = None
197
198        self.hard_failover = None
199        self.recovery_type = None
200        self.recovery_buckets = None
201
202        # Collect logs
203        self.nodes = None
204        self.all_nodes = None
205        self.upload = False
206        self.upload_host = None
207        self.customer = None
208        self.ticket = ""
209
210        #auditing
211        self.audit_enabled = None
212        self.audit_log_path = None
213        self.audit_log_rotate_interval = None
214
215        #ldap
216        self.ldap_enabled = None
217        self.ldap_admins = ''
218        self.ldap_roadmins = ''
219        self.ldap_default = "none"
220
221        #index
222        self.max_rollback_points = None
223        self.stable_snapshot_interval = None
224        self.memory_snapshot_interval = None
225        self.index_threads = None
226        self.services = None
227        self.log_level = None
228
229        #set-roles / delete-roles
230        self.roles = None
231        self.my_roles = False
232        self.get_roles = None
233        self.set_users = None
234        self.set_names = None
235        self.delete_users = None
236
237        # master password
238        self.new_master_password = False
239        self.rotate_data_key = False
240        self.send_password = False
241        self.config_path = None
242
243    def runCmd(self, cmd, server, port,
244               user, password, ssl, opts):
245        self.rest_cmd = rest_cmds[cmd]
246        self.method = methods[cmd]
247        self.server = server
248        self.port = int(port)
249        self.user = user
250        self.password = password
251        self.ssl = ssl
252
253        servers = self.processOpts(cmd, opts)
254        if self.debug:
255            print "INFO: servers %s" % servers
256
257        if cmd == 'server-add' and not servers['add']:
258            command_error("please list one or more --server-add=HOST[:PORT],"
259                  " or use -h for more help.")
260
261        if cmd == 'server-readd' and not servers['add']:
262            command_error("please list one or more --server-add=HOST[:PORT],"
263                  " or use -h for more help.")
264
265        if cmd in ('server-add', 'rebalance'):
266            if len(servers['add']) > 0:
267                if cmd == 'rebalance':
268                    print "DEPRECATED: Adding server from the rebalance command is " + \
269                          "deprecated and will be removed in future release, use " + \
270                          "the server-add command to add servers instead."
271                self.groupAddServers()
272            if cmd == 'rebalance':
273                self.rebalance(servers)
274
275        elif cmd == 'server-readd':
276            self.reAddServers(servers)
277
278        elif cmd == 'rebalance-status':
279            output_result = self.rebalanceStatus()
280            print output_result
281
282        elif cmd == 'rebalance-stop':
283            output_result = self.rebalanceStop()
284            print output_result
285
286        elif cmd == 'failover':
287            if len(servers['failover']) <= 0:
288                command_error("please list one or more --server-failover=HOST[:PORT];"
289                      " or use -h for more help.")
290            if len(servers['failover'].keys()[0].split(',')) > 1:
291                print "DEPRECATED: Failing over more than one server at a time is deprecated" + \
292                    "and will not be allowed in future release."
293
294            self.failover(servers)
295
296        elif cmd == 'recovery':
297            if len(servers['recovery']) <= 0:
298                command_error("please list one or more --server-recovery=HOST[:PORT];"
299                      " or use -h for more help.")
300            self.recovery(servers)
301
302        elif cmd in ('cluster-init', 'cluster-edit'):
303            self.clusterInit(cmd)
304
305        elif cmd == 'master-password':
306            self.masterPassword()
307
308        elif cmd == 'node-init':
309            self.nodeInit()
310
311        elif cmd == 'setting-cluster':
312            self.clusterSetting()
313
314        elif cmd == 'setting-compaction':
315            self.compaction()
316
317        elif cmd == 'setting-notification':
318            self.notification()
319
320        elif cmd == 'setting-alert':
321            self.alert()
322
323        elif cmd == 'setting-autofailover':
324            self.autofailover()
325
326        elif cmd == 'setting-audit':
327            self.audit()
328
329        elif cmd == 'setting-ldap':
330            self.ldap()
331
332        elif cmd == 'setting-index':
333            self.index()
334
335        elif cmd == 'user-manage':
336            self.userManage()
337
338        elif cmd == 'group-manage':
339            self.groupManage()
340
341        elif cmd == 'ssl-manage':
342            self.retrieveCert()
343
344        elif cmd == 'collect-logs-start':
345            self.collectLogsStart(servers)
346
347        elif cmd == 'collect-logs-stop':
348            self.collectLogsStop()
349
350        elif cmd == 'collect-logs-status':
351            self.collectLogsStatus()
352
353        elif cmd == 'admin-role-manage':
354            self.alterRoles()
355
356    def masterPassword(self):
357        cm = cluster_manager.ClusterManager(self.server, self.port, self.user,
358                                                self.password, self.ssl)
359        if self.new_master_password:
360            newPassword = getpass.getpass("\nEnter new master password:")
361            _, errors = cm.set_master_pwd(newPassword)
362            _exitIfErrors(errors)
363            print "SUCCESS: New master password set"
364        elif self.rotate_data_key:
365            _, errors = cm.rotate_master_pwd()
366            _exitIfErrors(errors)
367            print "SUCCESS: Data key rotated"
368        elif self.send_password:
369            mydir = os.path.join(os.path.dirname(sys.argv[0]), "..", "..", "bin")
370            path = [mydir, os.environ['PATH']]
371            if os.name == 'posix':
372                os.environ['PATH'] = ':'.join(path)
373            else:
374                os.environ['PATH'] = ';'.join(path)
375
376            if self.config_path == None:
377                self.config_path = os.path.abspath(os.path.join(mydir, "..", "var", "lib", "couchbase"))
378
379            cookiefile = self.find_master_pwd_path("couchbase-server.cookie", self.config_path)
380            if cookiefile == None:
381                _exitIfErrors(["File couchbase-server.cookie is not found under the specified root"])
382
383            cookie = _exitOnFileReadFailure(cookiefile).rstrip()
384
385            node = "babysitter_of_ns_1@127.0.0.1"
386            nodefile = self.find_master_pwd_path("couchbase-server.babysitter.node", self.config_path)
387            if nodefile != None:
388                node = _exitOnFileReadFailure(nodefile).rstrip()
389
390            self.prompt_for_master_pwd(node, cookie)
391        else :
392            _exitIfErrors(["ERROR: no parameters set"])
393
394    def run_process(self, name, args):
395        try:
396            if platform.system() == 'Windows':
397                name = name + ".exe"
398
399            args.insert(0, name)
400            p = subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
401            output = p.stdout.read()
402            error = p.stderr.read()
403            p.wait()
404            rc = p.returncode
405            return output, error
406        except OSError:
407            _exitIfErrors(["Could not locate the %s executable" % name])
408
409    def find_master_pwd_path(self, filename, user_path):
410        variants = [os.path.abspath(os.path.join(user_path, filename)),
411                    "/opt/couchbase/var/lib/couchbase/" + filename,
412                    os.path.expanduser("~/Library/Application Support/Couchbase/var/lib/couchbase/" + filename)]
413        for path in variants:
414            if os.path.isfile(path):
415                return path
416
417        return None
418
419    def prompt_for_master_pwd(self, node, cookie):
420        password = getpass.getpass("\nEnter master password:")
421        password = "\"" + password.replace("\\", "\\\\").replace("\"", "\\\"") + "\""
422
423        randChars = ''.join(random.choice(string.ascii_letters) for i in xrange(20))
424        name = 'cb-%s@127.0.0.1' % randChars
425
426        instr = "Res = rpc:call('" + node + "', encryption_service, set_password, [" \
427                + password + "]), io:format(\"~p~n\", [Res])."
428        args = ["-noinput", "-name", name, "-setcookie", cookie, "-eval", \
429                instr, "-run", "init", "stop"]
430
431        res, error = self.run_process("erl", args)
432        res = res.strip(' \t\n\r')
433
434        if res == "ok":
435            print "SUCCESS: Password accepted. Node started booting."
436        elif res == "retry":
437            _exitIfErrors(["Incorrect password."])
438            prompt_for_password(node, cookie)
439        elif res == "{badrpc,nodedown}":
440            _exitIfErrors(["Either the node is down or password was already supplied"])
441        else:
442            _exitIfErrors(["Incorrect password. Node shuts down."])
443
444    def clusterInit(self, cmd):
445        # We need to ensure that creating the REST username/password is the
446        # last REST API that is called because once that API succeeds the
447        # cluster is initialized and cluster-init cannot be run again.
448
449        cm = cluster_manager.ClusterManager(self.server, self.port, self.user,
450                                            self.password, self.ssl)
451
452        if cmd == 'cluster-init':
453            data, errors = cm.pools()
454            _exitIfErrors(errors)
455            if data['pools'] and len(data['pools']) > 0:
456                print "Error: cluster is already initialized, use cluster-edit to change settings"
457                return
458
459        err, services = self.process_services(False)
460        if err:
461            print err
462            return
463
464        allowDefault = 'index' in services.split(',')
465        param = self.index_storage_to_param(self.index_storage_setting, allowDefault)
466
467        #set memory quota
468        if cmd == 'cluster-init':
469            if 'kv' in services.split(',') and not self.per_node_quota:
470                print "ERROR: option cluster-ramsize is not specified"
471                return
472            elif 'index' in services.split(','):
473                if not self.cluster_index_ramsize:
474                    print "ERROR: option cluster-index-ramsize is not specified"
475                    return
476                if param is None:
477                    print "ERROR: invalid index storage setting `%s`. Must be [default, memopt]" \
478                        % self.index_storage_setting
479                    return
480            elif 'fts' in services.split(',') and not self.cluster_fts_ramsize:
481                print "ERROR: option fts-index-ramsize is not specified"
482                return
483
484        if param is not None:
485            _, errors = cm.set_index_settings(param)
486            _exitIfErrors(errors)
487
488        opts = {
489            "error_msg": "unable to set memory quota",
490            "success_msg": "set memory quota successfully"
491        }
492        rest = util.restclient_factory(self.server,
493                                       self.port,
494                                       {'debug':self.debug},
495                                       self.ssl)
496        if self.per_node_quota:
497            rest.setParam('memoryQuota', self.per_node_quota)
498        if self.cluster_index_ramsize:
499            rest.setParam('indexMemoryQuota', self.cluster_index_ramsize)
500        if self.cluster_fts_ramsize:
501            rest.setParam('ftsMemoryQuota', self.cluster_fts_ramsize)
502        if rest.params:
503            output_result = rest.restCmd(self.method,
504                                         '/pools/default',
505                                         self.user,
506                                         self.password,
507                                         opts)
508
509        #setup services
510        if cmd == "cluster-init":
511            opts = {
512                "error_msg": "unable to setup services",
513                "success_msg": "setup service successfully"
514            }
515            rest = util.restclient_factory(self.server,
516                                           self.port,
517                                           {'debug':self.debug},
518                                           self.ssl)
519            rest.setParam('services', services)
520            output_result = rest.restCmd(self.method,
521                                         '/node/controller/setupServices',
522                                         self.user,
523                                         self.password,
524                                         opts)
525
526        # setup REST credentials/REST port
527        if cmd == 'cluster-init' or self.username_new or self.password_new or self.port_new:
528            self.enable_notification = "true"
529            self.notification(False)
530            rest = util.restclient_factory(self.server,
531                                         self.port,
532                                         {'debug':self.debug},
533                                         self.ssl)
534            if self.port_new:
535                rest.setParam('port', self.port_new)
536            else:
537                rest.setParam('port', 'SAME')
538            rest.setParam('initStatus', 'done')
539            if self.username_new:
540                rest.setParam('username', self.username_new)
541            else:
542                rest.setParam('username', self.user)
543            if self.password_new:
544                rest.setParam('password', self.password_new)
545            else:
546                rest.setParam('password', self.password)
547
548            if not (rest.getParam('username') and rest.getParam('password')):
549                print "ERROR: Both username and password are required."
550                return
551
552            if len(rest.getParam('password')) > MAX_LEN_PASSWORD:
553                print "ERROR: Password length %s exceeds maximum number of characters allowed, which is %s" \
554                      % (len(rest.getParam('password')), MAX_LEN_PASSWORD)
555                return
556
557            opts = {
558                "error_msg": "unable to init/modify %s" % self.server,
559                "success_msg": "init/edit %s" % self.server
560            }
561
562            output_result = rest.restCmd(self.method,
563                                         self.rest_cmd,
564                                         self.user,
565                                         self.password,
566                                         opts)
567        print output_result
568
569    def index_storage_to_param(self, value, allowDefault):
570        if (not value and allowDefault) or value == "default":
571            return "forestdb"
572        if value == "memopt":
573            return "memory_optimized"
574        return None
575
576    def process_services(self, data_required):
577        if not self.services:
578            self.services = "data"
579        sep = Node.SEP
580        if self.services.find(sep) < 0:
581            #backward compatible when using ";" as separator
582            sep = ";"
583        svc_list = list(set([w.strip() for w in self.services.split(sep)]))
584        svc_candidate = ["data", "index", "query", "fts"]
585        for svc in svc_list:
586            if svc not in svc_candidate:
587                return "ERROR: invalid service: %s" % svc, None
588        if data_required and "data" not in svc_list:
589            svc_list.append("data")
590        if not IS_ENTERPRISE:
591            if len(svc_list) != len(svc_candidate):
592                if len(svc_list) != 1 or "data" not in svc_list:
593                    return "ERROR: Community Edition requires that all nodes provision all services or data service only", None
594
595        services = ",".join(svc_list)
596        for old, new in [[";", ","], ["data", "kv"], ["query", "n1ql"]]:
597            services = services.replace(old, new)
598        return None, services
599
600    def nodeInit(self):
601        rest = util.restclient_factory(self.server,
602                                     self.port,
603                                     {'debug':self.debug},
604                                     self.ssl)
605        if self.data_path:
606            rest.setParam('path', self.data_path)
607
608        if self.index_path:
609            rest.setParam('index_path', self.index_path)
610
611        opts = {
612            "error_msg": "unable to init %s" % self.server,
613            "success_msg": "init %s" % self.server
614        }
615
616        output_result = rest.restCmd(self.method,
617                                     self.rest_cmd,
618                                     self.user,
619                                     self.password,
620                                     opts)
621        if self.hostname:
622            rest = util.restclient_factory(self.server,
623                                         self.port,
624                                         {'debug':self.debug},
625                                         self.ssl)
626            if self.hostname:
627                rest.setParam('hostname', self.hostname)
628
629            opts = {
630                "error_msg": "unable to set hostname for %s" % self.server,
631                "success_msg": "set hostname for %s" % self.server
632            }
633
634            output_result = rest.restCmd('POST',
635                                         '/node/controller/rename',
636                                         self.user,
637                                         self.password,
638                                         opts)
639        print output_result
640
641    def compaction(self):
642        rest = util.restclient_factory(self.server,
643                                     self.port,
644                                     {'debug':self.debug},
645                                     self.ssl)
646
647        if self.compaction_db_percentage:
648            rest.setParam('databaseFragmentationThreshold[percentage]', self.compaction_db_percentage)
649        if self.compaction_db_size:
650            self.compaction_db_size = int(self.compaction_db_size) * 1024**2
651            rest.setParam('databaseFragmentationThreshold[size]', self.compaction_db_size)
652        if self.compaction_view_percentage:
653            rest.setParam('viewFragmentationThreshold[percentage]', self.compaction_view_percentage)
654        if self.compaction_view_size:
655            self.compaction_view_size = int(self.compaction_view_size) * 1024**2
656            rest.setParam('viewFragmentationThreshold[size]', self.compaction_view_size)
657        if self.compaction_period_from:
658            hour, minute = self.compaction_period_from.split(':')
659            if (int(hour) not in range(24)) or (int(minute) not in range(60)):
660                print "ERROR: invalid hour or minute value for compaction period"
661                return
662            else:
663                rest.setParam('allowedTimePeriod[fromHour]', int(hour))
664                rest.setParam('allowedTimePeriod[fromMinute]', int(minute))
665        if self.compaction_period_to:
666            hour, minute = self.compaction_period_to.split(':')
667            if (int(hour) not in range(24)) or (int(minute) not in range(60)):
668                print "ERROR: invalid hour or minute value for compaction"
669                return
670            else:
671                rest.setParam('allowedTimePeriod[toHour]', hour)
672                rest.setParam('allowedTimePeriod[toMinute]', minute)
673        if self.enable_compaction_abort:
674            rest.setParam('allowedTimePeriod[abortOutside]', self.enable_compaction_abort)
675        if self.enable_compaction_parallel:
676            rest.setParam('parallelDBAndViewCompaction', self.enable_compaction_parallel)
677        else:
678            self.enable_compaction_parallel = bool_to_str(0)
679            rest.setParam('parallelDBAndViewCompaction', self.enable_compaction_parallel)
680
681        if self.compaction_period_from or self.compaction_period_to or self.enable_compaction_abort:
682            if not (self.compaction_period_from and self.compaction_period_to and \
683                    self.enable_compaction_abort):
684                print "ERROR: compaction-period-from, compaction-period-to and enable-compaction-abort have to be specified at the same time"
685                return
686        if self.purge_interval:
687            rest.setParam('purgeInterval', self.purge_interval)
688
689        if self.gsi_compact_mode is not None and self.gsi_compact_mode not in ["append", "circular"]:
690            _exitIfErrors(["ERROR: --gsi-compaction-mode must be \"append\" or \"circular\""])
691
692        if self.gsi_compact_mode == "append":
693            rest.setParam('indexCompactionMode', "full")
694            if self.gsi_compact_perc is None:
695                _exitIfErrors(["ERROR: --compaction-gsi-percentage must be specified when --gsi-compaction-mode is append"])
696
697            if self.gsi_compact_perc is not None:
698                rest.setParam('indexFragmentationThreshold[percentage]', self.gsi_compact_perc)
699        elif self.gsi_compact_mode == "circular":
700            rest.setParam('indexCompactionMode', "circular")
701
702            if self.gsi_compact_interval is None:
703                self.gsi_compact_interval = ""
704            rest.setParam('indexCircularCompaction[daysOfWeek]', self.gsi_compact_interval)
705
706            if self.gsi_compact_period_from is not None:
707                hour, minute = self.gsi_compact_period_from.split(':')
708                if (int(hour) not in range(24)) or (int(minute) not in range(60)):
709                    _exitIfErrors(["ERROR: invalid hour or minute value for gsi compaction from period"])
710                else:
711                    rest.setParam('indexCircularCompaction[interval][fromHour]', int(hour))
712                    rest.setParam('indexCircularCompaction[interval][fromMinute]', int(minute))
713            if self.gsi_compact_period_to is not None:
714                hour, minute = self.gsi_compact_period_to.split(':')
715                if (int(hour) not in range(24)) or (int(minute) not in range(60)):
716                    _exitIfErrors(["ERROR: invalid hour or minute value for gsi compaction to period"])
717                else:
718                    rest.setParam('indexCircularCompaction[interval][toHour]', hour)
719                    rest.setParam('indexCircularCompaction[interval][toMinute]', minute)
720
721            if self.enable_compaction_abort is not None:
722                rest.setParam('indexCircularCompaction[interval][abortOutside]', self.gsi_compact_abort)
723            else:
724                rest.setParam('indexCircularCompaction[interval][abortOutside]', "false")
725
726        opts = {
727            "error_msg": "unable to set compaction settings",
728            "success_msg": "set compaction settings"
729        }
730        output_result = rest.restCmd(self.method,
731                                     self.rest_cmd,
732                                     self.user,
733                                     self.password,
734                                     opts)
735        print output_result
736
737    def clusterSetting(self):
738        rest = util.restclient_factory(self.server,
739                                     self.port,
740                                     {'debug':self.debug},
741                                     self.ssl)
742        if self.per_node_quota:
743            rest.setParam('memoryQuota', self.per_node_quota)
744        if self.cluster_name is not None:
745            rest.setParam('clusterName', self.cluster_name)
746        if self.cluster_index_ramsize:
747            rest.setParam('indexMemoryQuota', self.cluster_index_ramsize)
748        if self.cluster_fts_ramsize:
749            rest.setParam('ftsMemoryQuota', self.cluster_fts_ramsize)
750        opts = {
751            "error_msg": "unable to set cluster configurations",
752            "success_msg": "set cluster settings"
753        }
754        if rest.params:
755            output_result = rest.restCmd(self.method,
756                                     self.rest_cmd,
757                                     self.user,
758                                     self.password,
759                                     opts)
760            print output_result
761        else:
762            print "Error: No parameters specified"
763
764    def notification(self, print_status=True):
765        rest = util.restclient_factory(self.server,
766                                     self.port,
767                                     {'debug':self.debug},
768                                     self.ssl)
769        if self.enable_notification:
770            rest.setParam('sendStats', self.enable_notification)
771
772        opts = {
773            "error_msg": "unable to set notification settings",
774            "success_msg": "set notification settings"
775        }
776        output_result = rest.restCmd(self.method,
777                                     '/settings/stats',
778                                     self.user,
779                                     self.password,
780                                     opts)
781
782        if print_status:
783            print output_result
784
785    def alert(self):
786        rest = util.restclient_factory(self.server,
787                                     self.port,
788                                     {'debug':self.debug},
789                                     self.ssl)
790        alert_opts = ''
791        if self.enable_email_alert:
792            rest.setParam('enabled', self.enable_email_alert)
793        if self.email_recipient:
794            rest.setParam('recipients', self.email_recipient)
795        if self.email_sender:
796            rest.setParam('sender', self.email_sender)
797        if self.email_user:
798            rest.setParam('emailUser', self.email_user)
799        if self.email_password:
800            rest.setParam('emailPass', self.email_password)
801        if self.email_host:
802            rest.setParam('emailHost', self.email_host)
803        if self.email_port:
804            rest.setParam('emailPort', self.email_port)
805        if self.email_enable_encrypt:
806            rest.setParam('emailEncrypt', self.email_enable_encrypt)
807        if self.autofailover_node:
808            alert_opts = alert_opts + 'auto_failover_node,'
809        if self.autofailover_max_reached:
810            alert_opts = alert_opts + 'auto_failover_maximum_reached,'
811        if self.autofailover_node_down:
812            alert_opts = alert_opts + 'auto_failover_other_nodes_down,'
813        if self.autofailover_cluster_small:
814            alert_opts = alert_opts + 'auto_failover_cluster_too_small,'
815        if self.autofailover_disabled:
816            alert_opts = alert_opts + 'auto_failover_disabled,'
817        if self.alert_ip_changed:
818            alert_opts = alert_opts + 'ip,'
819        if self.alert_disk_space:
820            alert_opts = alert_opts + 'disk,'
821        if self.alert_meta_overhead:
822            alert_opts = alert_opts + 'overhead,'
823        if self.alert_meta_oom:
824            alert_opts = alert_opts + 'ep_oom_errors,'
825        if self.alert_write_failed:
826            alert_opts = alert_opts + 'ep_item_commit_failed,'
827        if self.alert_audit_dropped:
828            alert_opts = alert_opts + 'audit_dropped_events,'
829
830        if alert_opts:
831            # remove last separator
832            alert_opts = alert_opts[:-1]
833            rest.setParam('alerts', alert_opts)
834
835        opts = {
836            "error_msg": "unable to set alert settings",
837            "success_msg": "set alert settings"
838        }
839        output_result = rest.restCmd(self.method,
840                                     self.rest_cmd,
841                                     self.user,
842                                     self.password,
843                                     opts)
844        print output_result
845
846    def autofailover(self):
847        rest = util.restclient_factory(self.server,
848                                     self.port,
849                                     {'debug':self.debug},
850                                     self.ssl)
851        if self.autofailover_timeout:
852            if int(self.autofailover_timeout) < 30:
853                print "ERROR: Timeout value must be larger than 30 second."
854                return
855            else:
856                rest.setParam('timeout', self.autofailover_timeout)
857
858        if self.enable_auto_failover:
859            rest.setParam('enabled', self.enable_auto_failover)
860
861        opts = {
862            "error_msg": "unable to set auto failover settings",
863            "success_msg": "set auto failover settings"
864        }
865        output_result = rest.restCmd(self.method,
866                                     self.rest_cmd,
867                                     self.user,
868                                     self.password,
869                                     opts)
870        print output_result
871
872    def audit(self):
873        rest = util.restclient_factory(self.server,
874                                     self.port,
875                                     {'debug':self.debug},
876                                     self.ssl)
877        if self.audit_enabled:
878            rest.setParam('auditdEnabled', self.audit_enabled)
879
880        if self.audit_log_path:
881            rest.setParam('logPath', self.audit_log_path)
882        elif self.audit_enabled == "true":
883             rest.setParam('logPath', "/opt/couchbase/var/lib/couchbase/logs")
884        if self.audit_log_rotate_interval:
885            rest.setParam('rotateInterval', int(self.audit_log_rotate_interval)*60)
886        elif self.audit_enabled == "true":
887            rest.setParam('rotateInterval', 86400)
888
889        opts = {
890            "error_msg": "unable to set audit settings",
891            "success_msg": "set audit settings"
892        }
893        output_result = rest.restCmd(self.method,
894                                     self.rest_cmd,
895                                     self.user,
896                                     self.password,
897                                     opts)
898        print output_result
899
900    def ldap(self):
901        print "DEPRECATED: The settings ldap command is deprecated and will be " + \
902              "removed in a future release. Please use admin-role-manage instead."
903        rest = util.restclient_factory(self.server,
904                                     self.port,
905                                     {'debug':self.debug},
906                                     self.ssl)
907        if self.ldap_enabled == 'true':
908            rest.setParam('enabled', 'true')
909            if self.ldap_default == 'admins':
910                rest.setParam('roAdmins', self.ldap_roadmins.replace(Node.SEP, "\n"))
911            elif self.ldap_default == 'roadmins':
912                rest.setParam('admins', self.ldap_admins.replace(Node.SEP, "\n"))
913            else:
914                rest.setParam('admins', self.ldap_admins.replace(Node.SEP,"\n"))
915                rest.setParam('roAdmins', self.ldap_roadmins.replace(Node.SEP, "\n"))
916        else:
917            rest.setParam('enabled', 'false')
918
919        opts = {
920            "error_msg": "unable to set LDAP auth settings",
921            "success_msg": "set LDAP auth settings"
922        }
923        output_result = rest.restCmd(self.method,
924                                     self.rest_cmd,
925                                     self.user,
926                                     self.password,
927                                     opts)
928        print output_result
929
930    # Role-Based Access Control
931    def alterRoles(self):
932        cm = cluster_manager.ClusterManager(self.server, self.port, self.user,
933                                            self.password, self.ssl)
934
935        # need to check arguments
936        if (self.my_roles == None and self.get_roles == None and \
937            self.set_users == None and self.delete_users == None):
938            print "ERROR: You must specify either '--my-roles', '--get-roles', " \
939                "'--set-users', or '--delete-users'"
940            return
941
942        if self.my_roles and (self.get_roles or self.set_users or self.roles or self.delete_users):
943            print "ERROR: The 'my-roles' option may not be used with any other option."
944            return
945
946        if self.get_roles and (self.my_roles or self.set_users or self.roles or self.delete_users):
947            print "ERROR: The 'get-roles' option may not be used with any other option."
948            return
949
950        if (self.set_users and self.roles == None) or (self.set_users == None and self.roles):
951            print "ERROR: You must specify lists of both users and roles for those users.\n  --set-users=[comma delimited user list] --roles=[comma-delimited list of one or more from admin, ro_admin, cluster_admin, replication_admin, bucket_admin[bucket name or '*'], views_admin[bucket name or '*']"
952            return
953
954        # my_roles
955        if self.my_roles:
956            data, errors = cm.myRoles()
957            if errors == None:
958                print "SUCCESS: my roles:"
959
960        # get_roles
961        elif self.get_roles:
962            data, errors = cm.getRoles()
963            if errors == None:
964                print "SUCCESS: user/role list:"
965
966        # set_users
967        elif self.set_users:
968            data, errors = cm.setRoles(self.set_users,self.roles,self.set_names)
969            if errors == None:
970                print "SUCCESS: set roles for ",self.set_users,". New user/role list:"
971
972        # delete_users
973        else:
974            data, errors = cm.deleteRoles(self.delete_users)
975            if errors == None:
976                print "SUCCESS: removed users ", self.delete_users, ". New user/role list:"
977
978        _exitIfErrors(errors)
979        print json.dumps(data,indent=2)
980
981    def index(self):
982        rest = util.restclient_factory(self.server,
983                                     self.port,
984                                     {'debug':self.debug},
985                                     self.ssl)
986        if self.max_rollback_points:
987            rest.setParam("maxRollbackPoints", self.max_rollback_points)
988        if self.stable_snapshot_interval:
989            rest.setParam("stableSnapshotInterval", self.stable_snapshot_interval)
990        if self.memory_snapshot_interval:
991            rest.setParam("memorySnapshotInterval", self.memory_snapshot_interval)
992        if self.index_threads:
993            rest.setParam("indexerThreads", self.index_threads)
994        if self.log_level:
995            rest.setParam("logLevel", self.log_level)
996
997        opts = {
998            "error_msg": "unable to set index settings",
999            "success_msg": "set index settings"
1000        }
1001        output_result = rest.restCmd(self.method,
1002                                     self.rest_cmd,
1003                                     self.user,
1004                                     self.password,
1005                                     opts)
1006        print output_result
1007
1008    def processOpts(self, cmd, opts):
1009        """ Set standard opts.
1010            note: use of a server key keeps optional
1011            args aligned with server.
1012            """
1013        servers = {
1014            'add': {},
1015            'remove': {},
1016            'failover': {},
1017            'recovery': {},
1018            'log': {},
1019        }
1020
1021        # don't allow options that don't correspond to given commands
1022
1023        for o, a in opts:
1024            command_error_msg = "option '%s' is not used with command '%s'" % (o, cmd)
1025
1026            if o in ( "-r", "--server-remove"):
1027                if cmd in server_no_remove:
1028                    command_error(command_error_msg)
1029            elif o in ( "-a", "--server-add",
1030                        "--server-add-username",
1031                        "--server-add-password"):
1032                if cmd in server_no_add:
1033                    command_error(command_error_msg)
1034
1035        server = None
1036        for o, a in opts:
1037            if o in ("-a", "--server-add"):
1038                if a == "self":
1039                    a = socket.gethostbyname(socket.getfqdn())
1040                server = "%s:%d" % util.hostport(a)
1041                servers['add'][server] = { 'user':'', 'password':''}
1042                self.server_list.append(server)
1043            elif o == "--server-add-username":
1044                if server:
1045                    servers['add'][server]['user'] = a
1046                self.sa_username = a
1047            elif o == "--server-add-password":
1048                if server:
1049                    servers['add'][server]['password'] = a
1050                self.sa_password = a
1051            elif o in ( "-r", "--server-remove"):
1052                server = "%s:%d" % util.hostport(a)
1053                servers['remove'][server] = True
1054                server = None
1055            elif o in ( "--server-failover"):
1056                server = "%s:%d" % util.hostport(a)
1057                servers['failover'][server] = True
1058                server = None
1059            elif o in ( "--server-recovery"):
1060                server = "%s:%d" % util.hostport(a)
1061                servers['recovery'][server] = True
1062                server = None
1063            elif o == "--nodes":
1064                for server in self.normalize_servers(a):
1065                    servers['log'][server] = True
1066            elif o in ('-o', '--output'):
1067                if a == 'json':
1068                    self.output = a
1069                server = None
1070            elif o in ('-d', '--debug'):
1071                self.debug = True
1072                server = None
1073            elif o in ('--cluster-init-password', '--cluster-password'):
1074                self.password_new = a
1075            elif o in ('--cluster-init-username', '--cluster-username'):
1076                self.username_new = a
1077            elif o in ('--cluster-init-port', '--cluster-port'):
1078                self.port_new = a
1079            elif o in ('--cluster-init-ramsize', '--cluster-ramsize'):
1080                self.per_node_quota = a
1081            elif o == '--cluster-index-ramsize':
1082                self.cluster_index_ramsize = a
1083            elif o == '--cluster-fts-ramsize':
1084                self.cluster_fts_ramsize = a
1085            elif o == '--index-storage-setting':
1086                self.index_storage_setting = a
1087            elif o == '--cluster-name':
1088                self.cluster_name = a
1089            elif o == '--enable-auto-failover':
1090                self.enable_auto_failover = bool_to_str(a)
1091            elif o == '--enable-notification':
1092                self.enable_notification = bool_to_str(a)
1093            elif o == '--auto-failover-timeout':
1094                self.autofailover_timeout = a
1095            elif o == '--compaction-db-percentage':
1096                self.compaction_db_percentage = a
1097            elif o == '--compaction-db-size':
1098                self.compaction_db_size = a
1099            elif o == '--compaction-view-percentage':
1100                self.compaction_view_percentage = a
1101            elif o == '--compaction-view-size':
1102                self.compaction_view_size = a
1103            elif o == '--compaction-period-from':
1104                self.compaction_period_from = a
1105            elif o == '--compaction-period-to':
1106                self.compaction_period_to = a
1107            elif o == '--enable-compaction-abort':
1108                self.enable_compaction_abort = bool_to_str(a)
1109            elif o == '--enable-compaction-parallel':
1110                self.enable_compaction_parallel = bool_to_str(a)
1111            elif o == '--gsi-compaction-mode':
1112                self.gsi_compact_mode = a
1113            elif o == '--compaction-gsi-percentage':
1114                self.gsi_compact_perc = a
1115            elif o == '--compaction-gsi-interval':
1116                self.gsi_compact_interval = a
1117            elif o == '--compaction-gsi-period-from':
1118                self.gsi_compact_period_from = a
1119            elif o == '--compaction-gsi-period-to':
1120                self.gsi_compact_period_to = a
1121            elif o == '--enable-gsi-compaction-abort':
1122                self.gsi_compact_abort = bool_to_str(a)
1123            elif o == '--enable-email-alert':
1124                self.enable_email_alert = bool_to_str(a)
1125            elif o == '--new-password':
1126                self.new_master_password = True
1127            elif o == '--rotate-data-key':
1128                self.rotate_data_key = True
1129            elif o == '--send-password':
1130                self.send_password = True
1131            elif o == '--config-path':
1132                self.config_path = a
1133            elif o == '--node-init-data-path':
1134                self.data_path = a
1135            elif o == '--node-init-index-path':
1136                self.index_path = a
1137            elif o == '--node-init-hostname':
1138                self.hostname = a
1139            elif o == '--email-recipients':
1140                self.email_recipient = a
1141            elif o == '--email-sender':
1142                self.email_sender = a
1143            elif o == '--email-user':
1144                self.email_user = a
1145            elif o == '--email-password':
1146                self.email_password = a
1147            elif o == '--email-host':
1148                self.email_host = a
1149            elif o == '--email-port':
1150                self.email_port = a
1151            elif o == '--enable-email-encrypt':
1152                self.email_enable_encrypt = bool_to_str(a)
1153            elif o == '--alert-auto-failover-node':
1154                self.autofailover_node = True
1155            elif o == '--alert-auto-failover-max-reached':
1156                self.autofailover_max_reached = True
1157            elif o == '--alert-auto-failover-node-down':
1158                self.autofailover_node_down = True
1159            elif o == '--alert-auto-failover-cluster-small':
1160                self.autofailover_cluster_small = True
1161            elif o == '--alert-auto-failover-disabled':
1162                self.autofailover_disabled = True
1163            elif o == '--alert-ip-changed':
1164                self.alert_ip_changed = True
1165            elif o == '--alert-disk-space':
1166                self.alert_disk_space = True
1167            elif o == '--alert-meta-overhead':
1168                self.alert_meta_overhead = True
1169            elif o == '--alert-meta-oom':
1170                self.alert_meta_oom = True
1171            elif o == '--alert-write-failed':
1172                self.alert_write_failed = True
1173            elif o == '--alert-audit-msg-dropped':
1174                self.alert_audit_dropped = True
1175            elif o == '--create':
1176                self.cmd = 'create'
1177            elif o == '--list':
1178                self.cmd = 'list'
1179            elif o == '--delete':
1180                self.cmd = 'delete'
1181            elif o == '--set':
1182                self.cmd = 'set'
1183            elif o == '--ro-username':
1184                self.ro_username = a
1185            elif o == '--ro-password':
1186                self.ro_password = a
1187            elif o == '--metadata-purge-interval':
1188                self.purge_interval = a
1189            elif o == '--group-name':
1190                self.group_name = a
1191            elif o == '--add-servers':
1192                self.server_list = self.normalize_servers(a)
1193                self.cmd = 'add-servers'
1194            elif o == '--remove-servers':
1195                self.server_list = self.normalize_servers(a)
1196                self.cmd = 'remove-servers'
1197            elif o == '--move-servers':
1198                self.server_list = self.normalize_servers(a)
1199                self.cmd = 'move-servers'
1200            elif o == '--from-group':
1201                self.from_group = a
1202            elif o == '--to-group':
1203                self.to_group = a
1204            elif o == '--rename':
1205                self.group_rename = a
1206                self.cmd = 'rename'
1207            elif o == '--retrieve-cert':
1208                self.cmd = 'retrieve'
1209                self.certificate_file = a
1210            elif o == '--regenerate-cert':
1211                self.cmd = 'regenerate'
1212                self.certificate_file = a
1213            elif o == '--cluster-cert-info':
1214                self.cmd = 'cluster-cert-info'
1215            elif o == '--extended':
1216                self.extended = True
1217            elif o == '--node-cert-info':
1218                self.cmd = 'node-cert-info'
1219            elif o == '--upload-cluster-ca':
1220                self.cmd = 'upload-cluster-ca'
1221                self.certificate_file = a
1222            elif o == '--set-node-certificate':
1223                self.cmd = 'set-node-certificate'
1224            elif o == '--force':
1225                self.hard_failover = True
1226            elif o == '--recovery-type':
1227                self.recovery_type = a
1228            elif o == '--recovery-buckets':
1229                self.recovery_buckets = a
1230            elif o == '--nodes':
1231                self.nodes = a
1232            elif o == '--all-nodes':
1233                self.all_nodes = True
1234            elif o == '--upload':
1235                self.upload = True
1236            elif o == '--upload-host':
1237                self.upload_host = a
1238            elif o == '--customer':
1239                self.customer = a
1240            elif o == '--ticket':
1241                self.ticket = a
1242            elif o == '--services':
1243                self.services = a
1244            elif o == '--audit-log-rotate-interval':
1245                self.audit_log_rotate_interval = a
1246            elif o == '--audit-log-path':
1247                self.audit_log_path = a
1248            elif o == '--audit-enabled':
1249                self.audit_enabled = bool_to_str(a)
1250            elif o == '--ldap-enabled':
1251                self.ldap_enabled = bool_to_str(a)
1252            elif o == '--ldap-admins':
1253                self.ldap_admins = a
1254            elif o == '--ldap-roadmins':
1255                self.ldap_roadmins = a
1256            elif o == '--ldap-default':
1257                self.ldap_default = a
1258            elif o == '--index-max-rollback-points':
1259                self.max_rollback_points = a
1260            elif o == '--index-stable-snapshot-interval':
1261                self.stable_snapshot_interval = a
1262            elif o == '--index-memory-snapshot-interval':
1263                self.memory_snapshot_interval = a
1264            elif o == '--index-threads':
1265                self.index_threads = a
1266            elif o == '--index-log-level':
1267                self.log_level = a
1268
1269            elif o == '--roles':
1270                self.roles = a
1271            elif o == '--my-roles':
1272                self.my_roles = True
1273            elif o == '--get-roles':
1274                self.get_roles = True
1275            elif o == '--set-users':
1276                self.set_users = a
1277            elif o == '--set-names':
1278                self.set_names = a
1279            elif o == '--delete-users':
1280                self.delete_users = a
1281
1282        return servers
1283
1284    def normalize_servers(self, server_list):
1285        slist = []
1286        sep = Node.SEP
1287        if server_list.find(sep) < 0:
1288            #backward compatible with ";" as separator
1289            sep = ";"
1290        for server in server_list.split(sep):
1291            hostport = "%s:%d" % util.hostport(server)
1292            slist.append(hostport)
1293        return slist
1294
1295    def reAddServers(self, servers):
1296        print "DEPRECATED: The server-readd command is deprecated and will be " + \
1297              "removed in a future release. Please use recovery instead."
1298        known_otps, eject_otps, failover_otps, readd_otps, _ = \
1299            self.getNodeOtps(to_readd=servers['add'])
1300
1301        for readd_otp in readd_otps:
1302            rest = util.restclient_factory(self.server,
1303                                         self.port,
1304                                         {'debug':self.debug},
1305                                         self.ssl)
1306            rest.setParam('otpNode', readd_otp)
1307
1308            opts = {
1309                'error_msg': "unable to re-add %s" % readd_otp,
1310                'success_msg': "re-add %s" % readd_otp
1311            }
1312            output_result = rest.restCmd('POST',
1313                                         rest_cmds['server-readd'],
1314                                         self.user,
1315                                         self.password,
1316                                         opts)
1317            print output_result
1318
1319    def getNodeOtps(self, to_eject=[], to_failover=[], to_readd=[]):
1320        """ Convert known nodes into otp node id's.
1321            """
1322        listservers = ListServers()
1323        known_nodes_list = listservers.getNodes(
1324                                listservers.getData(self.server,
1325                                                    self.port,
1326                                                    self.user,
1327                                                    self.password))
1328        known_otps = []
1329        eject_otps = []
1330        failover_otps = []
1331        readd_otps = []
1332        hostnames = []
1333
1334        for node in known_nodes_list:
1335            if node.get('otpNode') is None:
1336                raise Exception("could not access node")
1337            known_otps.append(node['otpNode'])
1338            hostnames.append(node['hostname'])
1339            if node['hostname'] in to_eject:
1340                eject_otps.append(node['otpNode'])
1341            if node['hostname'] in to_failover:
1342                if node['clusterMembership'] != 'active':
1343                    raise Exception('node %s is not active' % node['hostname'])
1344                else:
1345                    failover_otps.append((node['otpNode'], node['status']))
1346            _, host = node['otpNode'].split('@')
1347            hostport = "%s:%d" % util.hostport(host)
1348            if node['hostname'] in to_readd or hostport in to_readd:
1349                readd_otps.append(node['otpNode'])
1350
1351        return (known_otps, eject_otps, failover_otps, readd_otps, hostnames)
1352
1353    def recovery(self, servers):
1354        known_otps, eject_otps, failover_otps, readd_otps, _ = \
1355            self.getNodeOtps(to_readd=servers['recovery'])
1356        for readd_otp in readd_otps:
1357            rest = util.restclient_factory(self.server,
1358                                         self.port,
1359                                         {'debug':self.debug},
1360                                         self.ssl)
1361            opts = {
1362                'error_msg': "unable to setRecoveryType for node %s" % readd_otp,
1363                'success_msg': "setRecoveryType for node %s" % readd_otp
1364            }
1365            rest.setParam('otpNode', readd_otp)
1366            if self.recovery_type:
1367                rest.setParam('recoveryType', self.recovery_type)
1368            else:
1369                rest.setParam('recoveryType', 'delta')
1370            output_result = rest.restCmd('POST',
1371                                         '/controller/setRecoveryType',
1372                                         self.user,
1373                                         self.password,
1374                                         opts)
1375            print output_result
1376
1377    def rebalance(self, servers):
1378        known_otps, eject_otps, failover_otps, readd_otps, _ = \
1379            self.getNodeOtps(to_eject=servers['remove'])
1380        rest = util.restclient_factory(self.server,
1381                                     self.port,
1382                                     {'debug':self.debug},
1383                                     self.ssl)
1384        rest.setParam('knownNodes', ','.join(known_otps))
1385        rest.setParam('ejectedNodes', ','.join(eject_otps))
1386        if self.recovery_buckets:
1387            rest.setParam('requireDeltaRecoveryBuckets', self.recovery_buckets)
1388        opts = {
1389            'success_msg': 'rebalanced cluster',
1390            'error_msg': 'unable to rebalance cluster'
1391        }
1392        output_result = rest.restCmd('POST',
1393                                     rest_cmds['rebalance'],
1394                                     self.user,
1395                                     self.password,
1396                                     opts)
1397        if self.debug:
1398            print "INFO: rebalance started: %s" % output_result
1399
1400        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
1401
1402        print "INFO: rebalancing",
1403
1404        status, error = self.rebalanceStatus(prefix='\n')
1405        while status in['running', 'unknown']:
1406            print ".",
1407            time.sleep(0.5)
1408            try:
1409                status, error = self.rebalanceStatus(prefix='\n')
1410            except socket.error:
1411                time.sleep(2)
1412                status, error = self.rebalanceStatus(prefix='\n')
1413
1414        if error:
1415            print '\n' + error
1416            sys.exit(1)
1417        else:
1418            print '\n' + output_result
1419
1420    def rebalanceStatus(self, prefix=''):
1421        rest = util.restclient_factory(self.server,
1422                                     self.port,
1423                                     {'debug':self.debug},
1424                                     self.ssl)
1425
1426        opts = {
1427            'error_msg': "unable to obtain rebalance status",
1428            'success_msg': "retrieve replication status successfully"
1429        }
1430        output_result = rest.restCmd('GET',
1431                                     '/pools/default/tasks',
1432                                     self.user,
1433                                     self.password,
1434                                     opts)
1435        tasks = rest.getJson(output_result)
1436        for task in tasks:
1437            error_message = None
1438            if "errorMessage" in task:
1439                error_message = task['errorMessage']
1440
1441            if task["type"] == "rebalance":
1442                if task["status"] == "running":
1443                    return task["status"], error_message
1444                if task["status"] == "notRunning":
1445                    if task.has_key("statusIsStale"):
1446                        if task["statusIsStale"] or task["statusIsStale"] == "true":
1447                            return "unknown", error_message
1448
1449                return task["status"], error_message
1450
1451        return "unknown", error_message
1452
1453    def rebalanceStop(self):
1454        rest = util.restclient_factory(self.server,
1455                                     self.port,
1456                                     {'debug':self.debug},
1457                                     self.ssl)
1458
1459        opts = {
1460            'success_msg': 'rebalance cluster stopped',
1461            'error_msg': 'unable to stop rebalance'
1462        }
1463        output_result = rest.restCmd('POST',
1464                                     rest_cmds['rebalance-stop'],
1465                                     self.user,
1466                                     self.password,
1467                                     opts)
1468        return output_result
1469
1470
1471    def failover(self, servers):
1472        known_otps, eject_otps, failover_otps, readd_otps, _ = \
1473            self.getNodeOtps(to_failover=servers['failover'])
1474
1475        if len(failover_otps) <= 0:
1476            command_error("specified servers are not part of the cluster: %s" %
1477                  servers['failover'].keys())
1478
1479        for failover_otp, node_status in failover_otps:
1480            rest = util.restclient_factory(self.server,
1481                                         self.port,
1482                                         {'debug':self.debug},
1483                                         self.ssl)
1484            opts = {
1485                'error_msg': "unable to failover %s" % failover_otp,
1486                'success_msg': "failover %s" % failover_otp
1487            }
1488            rest.setParam('otpNode', failover_otp)
1489            if self.hard_failover or node_status != 'healthy':
1490                output_result = rest.restCmd('POST',
1491                                             rest_cmds['failover'],
1492                                             self.user,
1493                                             self.password,
1494                                             opts)
1495                print output_result
1496            else:
1497                output_result = rest.restCmd('POST',
1498                                             '/controller/startGracefulFailover',
1499                                             self.user,
1500                                             self.password,
1501                                             opts)
1502                if self.debug:
1503                    print "INFO: rebalance started: %s" % output_result
1504
1505                sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
1506
1507                print "INFO: graceful failover",
1508
1509                status, error = self.rebalanceStatus(prefix='\n')
1510                while status == 'running':
1511                    print ".",
1512                    time.sleep(0.5)
1513                    try:
1514                        status, error = self.rebalanceStatus(prefix='\n')
1515                    except socket.error:
1516                        time.sleep(2)
1517                        status, error = self.rebalanceStatus(prefix='\n')
1518
1519                if error:
1520                    print '\n' + error
1521                else:
1522                    print '\n' + output_result
1523
1524    def userManage(self):
1525        if self.cmd == 'list':
1526            self.roUserList()
1527        elif self.cmd == 'delete':
1528            self.roUserDelete()
1529        elif self.cmd == 'set':
1530            self.roUserSet()
1531
1532    def roUserList(self):
1533        rest = util.restclient_factory(self.server,
1534                                     self.port,
1535                                     {'debug':self.debug},
1536                                     self.ssl)
1537        opts = { 'error_msg':'not any read only user defined'}
1538        try:
1539            output_result = rest.restCmd('GET',
1540                                         '/settings/readOnlyAdminName',
1541                                         self.user,
1542                                         self.password,
1543                                         opts)
1544            json = rest.getJson(output_result)
1545            print json
1546        except:
1547            pass
1548
1549    def roUserDelete(self):
1550        rest = util.restclient_factory(self.server,
1551                                     self.port,
1552                                     {'debug':self.debug},
1553                                     self.ssl)
1554
1555        opts = {
1556            'success_msg': 'readOnly user deleted',
1557            'error_msg': 'unable to delete readOnly user'
1558        }
1559        output_result = rest.restCmd('DELETE',
1560                                     "/settings/readOnlyUser",
1561                                     self.user,
1562                                     self.password,
1563                                     opts)
1564        print output_result
1565
1566    def roUserSet(self):
1567        rest = util.restclient_factory(self.server,
1568                                     self.port,
1569                                     {'debug':self.debug},
1570                                     self.ssl)
1571        try:
1572            output_result = rest.restCmd('GET',
1573                                         '/settings/readOnlyAdminName',
1574                                         self.user,
1575                                         self.password)
1576            json = rest.getJson(output_result)
1577            print "ERROR: readonly user %s exist already. Delete it before creating a new one" % json
1578            return
1579        except:
1580            pass
1581
1582        rest = util.restclient_factory(self.server,
1583                                     self.port,
1584                                     {'debug':self.debug},
1585                                     self.ssl)
1586        if self.ro_username:
1587            rest.setParam('username', self.ro_username)
1588        if self.ro_password:
1589            rest.setParam('password', self.ro_password)
1590        opts = {
1591            'success_msg': 'readOnly user created',
1592            'error_msg': 'fail to create readOnly user'
1593        }
1594        output_result = rest.restCmd('POST',
1595                                     "/settings/readOnlyUser",
1596                                     self.user,
1597                                     self.password,
1598                                     opts)
1599        print output_result
1600
1601    def groupManage(self):
1602        if self.cmd == 'move-servers':
1603            self.groupMoveServer()
1604        elif self.cmd == 'list':
1605             self.groupList()
1606        else:
1607            if self.group_name is None:
1608                command_error("please specify --group-name for the operation")
1609            elif self.cmd == 'delete':
1610                self.groupDelete()
1611            elif self.cmd == 'create':
1612                self.groupCreate()
1613            elif self.cmd == 'add-servers':
1614                print "DEPRECATED: Adding server from group-manage is deprecated, use server-add instead"
1615                self.groupAddServers()
1616            elif self.cmd == 'rename':
1617                self.groupRename()
1618            else:
1619                print "Unknown group command:%s" % self.cmd
1620
1621    def getGroupUri(self, groupName):
1622        rest = util.restclient_factory(self.server,
1623                                     self.port,
1624                                     {'debug':self.debug},
1625                                     self.ssl)
1626        output_result = rest.restCmd('GET',
1627                                     '/pools/default/serverGroups',
1628                                     self.user,
1629                                     self.password)
1630        groups = rest.getJson(output_result)
1631        for group in groups["groups"]:
1632            if groupName == group["name"]:
1633                return group["uri"]
1634        return None
1635
1636    def getServerGroups(self):
1637        rest = util.restclient_factory(self.server,
1638                                     self.port,
1639                                     {'debug':self.debug},
1640                                     self.ssl)
1641        output_result = rest.restCmd('GET',
1642                                     '/pools/default/serverGroups',
1643                                     self.user,
1644                                     self.password)
1645        return rest.getJson(output_result)
1646
1647    def groupList(self):
1648        rest = util.restclient_factory(self.server,
1649                                     self.port,
1650                                     {'debug':self.debug},
1651                                     self.ssl)
1652        output_result = rest.restCmd('GET',
1653                                     '/pools/default/serverGroups',
1654                                     self.user,
1655                                     self.password)
1656        groups = rest.getJson(output_result)
1657        found = False
1658        for group in groups["groups"]:
1659            if self.group_name is None or self.group_name == group['name']:
1660                found = True
1661                print '%s' % group['name']
1662                for node in group['nodes']:
1663                    print ' server: %s' % node["hostname"]
1664        if not found and self.group_name:
1665            print "Invalid group name: %s" % self.group_name
1666
1667    def groupCreate(self):
1668        rest = util.restclient_factory(self.server,
1669                                     self.port,
1670                                     {'debug':self.debug},
1671                                     self.ssl)
1672        rest.setParam('name', self.group_name)
1673        opts = {
1674            'error_msg': "unable to create group %s" % self.group_name,
1675            'success_msg': "group created %s" % self.group_name
1676        }
1677        output_result = rest.restCmd('POST',
1678                                     '/pools/default/serverGroups',
1679                                     self.user,
1680                                     self.password,
1681                                     opts)
1682        print output_result
1683
1684    def groupRename(self):
1685        uri = self.getGroupUri(self.group_name)
1686        if uri is None:
1687            command_error("invalid group name:%s" % self.group_name)
1688        if self.group_rename is None:
1689            command_error("invalid group name:%s" % self.group_name)
1690
1691        rest = util.restclient_factory(self.server,
1692                                     self.port,
1693                                     {'debug':self.debug},
1694                                     self.ssl)
1695        rest.setParam('name', self.group_rename)
1696        opts = {
1697            'error_msg': "unable to rename group %s" % self.group_name,
1698            'success_msg': "group renamed %s" % self.group_name
1699        }
1700        output_result = rest.restCmd('PUT',
1701                                     uri,
1702                                     self.user,
1703                                     self.password,
1704                                     opts)
1705        print output_result
1706
1707    def groupDelete(self):
1708        uri = self.getGroupUri(self.group_name)
1709        if uri is None:
1710            command_error("invalid group name:%s" % self.group_name)
1711
1712        rest = util.restclient_factory(self.server,
1713                                     self.port,
1714                                     {'debug':self.debug},
1715                                     self.ssl)
1716        rest.setParam('name', self.group_name)
1717        opts = {
1718            'error_msg': "unable to delete group %s" % self.group_name,
1719            'success_msg': "group deleted %s" % self.group_name
1720        }
1721        output_result = rest.restCmd('DELETE',
1722                                     uri,
1723                                     self.user,
1724                                     self.password,
1725                                     opts)
1726        print output_result
1727
1728    def groupAddServers(self):
1729        # If this is the first index node added then we need to make sure to
1730        # set the index storage setting.
1731        indexStorageParam = self.index_storage_to_param(self.index_storage_setting, True)
1732        if not indexStorageParam:
1733            print "ERROR: invalid index storage setting `%s`. Must be [default, memopt]" \
1734                % self.index_storage_setting
1735            return
1736
1737        cm = cluster_manager.ClusterManager(self.server, self.port, self.user,
1738                                            self.password, self.ssl)
1739
1740        settings, errors = cm.index_settings()
1741        _exitIfErrors(errors)
1742
1743        if not settings:
1744            print "Error: unable to infer the current index storage mode"
1745            return
1746
1747        if settings['storageMode'] == "":
1748            _, errors = cm.set_index_settings(indexStorageParam)
1749            if errors:
1750                _exitIfErrors(errors)
1751        elif settings['storageMode'] != self.index_storage_setting and \
1752             self.index_storage_setting:
1753            print "Error: Cannot change index storage mode from `%s` to `%s`" % \
1754                (settings['storageMode'], self.index_storage_setting)
1755            return
1756
1757        err, services = self.process_services(False)
1758        if err:
1759            print err
1760            return
1761        for server in self.server_list:
1762            _, errors = cm.add_server(server, self.group_name, self.sa_username,
1763                                           self.sa_password, services)
1764            if errors:
1765                _exitIfErrors(errors, "Error: Failed to add server %s: " % server)
1766
1767            if self.group_name:
1768                print "Server %s added to group %s" % (server, self.group_name)
1769            else:
1770                print "Server %s added" % server
1771
1772    def groupMoveServer(self):
1773        groups = self.getServerGroups()
1774        node_info = {}
1775        for group in groups["groups"]:
1776            if self.from_group == group['name']:
1777                for server in self.server_list:
1778                    for node in group["nodes"]:
1779                        if server == node["hostname"]:
1780                            node_info[server] = node
1781                            group["nodes"].remove(node)
1782        if not node_info:
1783            print "No servers removed from group '%s'" % self.from_group
1784            return
1785
1786        for group in groups["groups"]:
1787            if self.to_group == group['name']:
1788                for server in self.server_list:
1789                    found = False
1790                    for node in group["nodes"]:
1791                        if server == node["hostname"]:
1792                            found = True
1793                            break
1794                    if not found:
1795                        group["nodes"].append(node_info[server])
1796
1797        payload = json.dumps(groups)
1798        rest = util.restclient_factory(self.server,
1799                                     self.port,
1800                                     {'debug':self.debug},
1801                                     self.ssl)
1802        rest.setPayload(payload)
1803
1804        opts = {
1805            'error_msg': "unable to move servers from group '%s' to group '%s'" % (self.from_group, self.to_group),
1806            'success_msg': "move servers from group '%s' to group '%s'" % (self.from_group, self.to_group)
1807        }
1808        output_result = rest.restCmd('PUT',
1809                                     groups["uri"],
1810                                     self.user,
1811                                     self.password,
1812                                     opts)
1813        print output_result
1814
1815    def retrieveCert(self):
1816        if self.cmd in ['retrieve', 'regenerate', 'upload-cluster-ca'] and self.certificate_file is None:
1817            command_error("please specify certificate file name for the operation")
1818
1819        cm = cluster_manager.ClusterManager(self.server, self.port, self.user, self.password, self.ssl)
1820
1821        if self.cmd == 'retrieve':
1822            print "Warning --retrieve-cert is deprecated, use --cluster-cert-info"
1823            certificate, errors = cm.retrieve_cluster_certificate()
1824            _exitIfErrors(errors)
1825            _exitOnFileWriteFailure(self.certificate_file, certificate)
1826            print "SUCCESS: %s certificate to '%s'" % (self.cmd, self.certificate_file)
1827        elif self.cmd  == 'regenerate':
1828            certificate, errors = cm.regenerate_cluster_certificate()
1829            _exitIfErrors(errors)
1830            _exitOnFileWriteFailure(self.certificate_file, certificate)
1831            print "SUCCESS: %s certificate to '%s'" % (self.cmd, self.certificate_file)
1832        elif self.cmd == 'cluster-cert-info':
1833            certificate, errors = cm.retrieve_cluster_certificate(self.extended)
1834            _exitIfErrors(errors)
1835            if isinstance(certificate, dict):
1836                print json.dumps(certificate, sort_keys=True, indent=2)
1837            else:
1838                print certificate
1839        elif self.cmd == 'node-cert-info':
1840            certificate, errors = cm.retrieve_node_certificate('%s:%d' % (self.server, self.port))
1841            _exitIfErrors(errors)
1842            print json.dumps(certificate, sort_keys=True, indent=2)
1843        elif self.cmd == 'upload-cluster-ca':
1844            certificate = _exitOnFileReadFailure(self.certificate_file)
1845            _, errors = cm.upload_cluster_certificate(certificate)
1846            _exitIfErrors(errors)
1847            print "SUCCESS: uploaded cluster certificate to %s:%d" % (self.server, self.port)
1848        elif self.cmd == 'set-node-certificate':
1849            _, errors = cm.set_node_certificate()
1850            _exitIfErrors(errors)
1851            print "SUCCESS: node certificate set"
1852        else:
1853            print "ERROR: unknown request:", self.cmd
1854
1855    def collectLogsStart(self, servers):
1856        """Starts a cluster-wide log collection task"""
1857        if (servers['log'] is None) and (self.all_nodes is not True):
1858            command_error("please specify a list of nodes to collect logs from, " +
1859                  " or 'all-nodes'")
1860
1861        rest = util.restclient_factory(self.server, self.port,
1862                                     {'debug': self.debug}, self.ssl)
1863        if self.all_nodes:
1864            rest.setParam("nodes", "*")
1865        else:
1866            known_otps, eject_otps, failover_otps, readd_otps, hostnames = \
1867                self.getNodeOtps(to_readd=servers['log'])
1868            if not len(readd_otps):
1869                msg = ",".join(hostnames)
1870                command_error("invalid node name specified for collecting logs, available nodes are:\n"+msg)
1871
1872            nodelist = ",".join(readd_otps)
1873            rest.setParam("nodes", nodelist)
1874            print "NODES:", nodelist
1875
1876        if self.upload:
1877            if self.upload_host is None:
1878                command_error("please specify an upload-host when using --upload")
1879
1880            rest.setParam("uploadHost", self.upload_host)
1881
1882            if not self.customer:
1883                command_error("please specify a value for --customer when using" +
1884                      " --upload")
1885
1886            rest.setParam("customer", self.customer)
1887            rest.setParam("ticket", self.ticket)
1888
1889        opts = {
1890            'error_msg': "unable to start log collection:",
1891            'success_msg': "Log collection started"
1892        }
1893
1894        output_result = rest.restCmd(self.method, self.rest_cmd, self.user,
1895                                     self.password, opts)
1896        print output_result
1897
1898    def collectLogsStop(self):
1899        """Stops a cluster-wide log collection task"""
1900        rest = util.restclient_factory(self.server, self.port,
1901                                     {'debug': self.debug}, self.ssl)
1902
1903        opts = {
1904            'success_msg': 'collect logs successfully stopped',
1905            'error_msg': 'unable to stop collect logs'
1906        }
1907        output_result = rest.restCmd(self.method, self.rest_cmd, self.user,
1908                                     self.password, opts)
1909        print output_result
1910
1911    def collectLogsStatus(self):
1912        """Shows the current status of log collection task"""
1913        rest = util.restclient_factory(self.server, self.port,
1914                                     {'debug': self.debug}, self.ssl)
1915
1916        opts = {
1917            'error_msg': 'unable to obtain collect logs status'
1918        }
1919        output_result = rest.restCmd(self.method, self.rest_cmd, self.user,
1920                                     self.password, opts)
1921
1922        output_json = rest.getJson(output_result)
1923
1924        for e in output_json:
1925            if ((type(e) == type(dict()) and ('type' in e) and
1926                (e['type'] == 'clusterLogsCollection'))):
1927                print "Status:   %s" % e['status']
1928                if 'perNode' in e:
1929                    print "Details:"
1930                    for node, ns in e["perNode"].iteritems():
1931                        print '\tNode:', node
1932                        print '\tStatus:', ns['status']
1933                        for f in ["path", "statusCode", "url", "uploadStatusCode", "uploadOutput"]:
1934                            if f in ns:
1935                                print '\t', f, ":", ns[f]
1936                        print
1937                return
1938
1939    def getCommandSummary(self, cmd):
1940        """Return one-line summary info for each supported command"""
1941        command_summary = {
1942            "server-list" :"list all servers in a cluster",
1943            "server-info" :"show details on one server",
1944            "server-add" :"add one or more servers to the cluster",
1945            "server-readd" :"readd a server that was failed over",
1946            "group-manage" :"manage server groups",
1947            "rebalance" :"start a cluster rebalancing",
1948            "rebalance-stop" :"stop current cluster rebalancing",
1949            "rebalance-status" :"show status of current cluster rebalancing",
1950            "failover" :"failover one or more servers",
1951            "recovery" :"recover one or more servers",
1952            "setting-cluster" : "set cluster settings",
1953            "setting-compaction" : "set auto compaction settings",
1954            "setting-notification" : "set notification settings",
1955            "setting-alert" : "set email alert settings",
1956            "setting-autofailover" : "set auto failover settings",
1957            "collect-logs-start" : "start a cluster-wide log collection",
1958            "collect-logs-stop" : "stop a cluster-wide log collection",
1959            "collect-logs-status" : "show the status of cluster-wide log collection",
1960            "cluster-init" : "set the username,password and port of the cluster",
1961            "cluster-edit" : "modify cluster settings",
1962            "node-init" : "set node specific parameters",
1963            "ssl-manage" : "manage cluster certificate",
1964            "user-manage" : "manage read only user",
1965            "setting-index" : "set index settings",
1966            "setting-ldap" : "set ldap settings",
1967            "setting-audit" : "set audit settings",
1968            "admin-role-manage" : "set access-control roles for users"
1969        }
1970        if cmd in command_summary:
1971            return command_summary[cmd]
1972        else:
1973            return None
1974
1975    def getCommandHelp(self, cmd):
1976        """ Obtain detailed parameter help for Node commands
1977        Returns a list of pairs (arg1, arg1-information) or None if there's
1978        no help or cmd is unknown.
1979        """
1980
1981        # Some common flags for server- commands
1982        server_common = [("--server-add=HOST[:PORT]", "server to be added,"),
1983                         ("--server-add-username=USERNAME",
1984                          "admin username for the server to be added"),
1985                         ("--server-add-password=PASSWORD",
1986                          "admin password for the server to be added"),
1987                         ("--group-name=GROUPNAME", "group that server belongs")]
1988
1989        services = [("--services=data,index,query,fts",
1990                     "services that server runs")]
1991
1992        if cmd == "server-add" or cmd == "rebalance":
1993            return [("--index-storage-setting=SETTING", "index storage type [default, memopt]")] \
1994            + server_common + services
1995        elif cmd == "server-readd":
1996            return server_common
1997        elif cmd == "group-manage":
1998            return [
1999            ("--group-name=GROUPNAME", "group name"),
2000            ("--create", "create a new group"),
2001            ("--delete", "delete an empty group"),
2002            ("--list", "show group/server relationship map"),
2003            ("--rename=NEWGROUPNAME", "rename group to new name"),
2004            ("--add-servers=HOST[:PORT],HOST[:PORT]",
2005             "add a list of servers to group"),
2006            ("--move-servers=HOST[:PORT],HOST[:PORT]",
2007             "move a list of servers from group"),
2008            ("--from-group=GROUPNAME", "group name to move servers from"),
2009            ("--to-group=GROUPNAME", "group name to move servers into"),
2010            ("--index-storage-setting=SETTING", "index storage type [default, memopt]")] + services
2011        elif cmd == "cluster-init" or cmd == "cluster-edit":
2012            return [
2013            ("--cluster-username=USER", "new admin username"),
2014            ("--cluster-password=PASSWORD", "new admin password"),
2015            ("--cluster-port=PORT", "new cluster REST/http port"),
2016            ("--cluster-ramsize=RAMSIZEMB", "per node data service ram quota in MB"),
2017            ("--cluster-index-ramsize=RAMSIZEMB", "per node index service ram quota in MB"),
2018            ("--cluster-fts-ramsize=RAMSIZEMB", "per node fts service ram quota in MB"),
2019            ("--index-storage-setting=SETTING", "index storage type [default, memopt]")] + services
2020        elif cmd == "node-init":
2021            return [
2022            ("--node-init-data-path=PATH", "data path for database files"),
2023            ("--node-init-index-path=PATH", "index path for view data")]
2024        elif cmd == "failover":
2025            return [
2026            ("--server-failover=HOST[:PORT]", "server to failover"),
2027            ("--force", "failover node from cluster right away")]
2028        elif cmd == "recovery":
2029            return [
2030            ("--server-recovery=HOST[:PORT]", "server to recover"),
2031            ("--recovery-type=TYPE[delta|full]",
2032             "type of recovery to be performed for a node")]
2033        elif cmd == "user-manage":
2034            return [
2035            ("--set", "create/modify a read only user"),
2036            ("--list", "list any read only user"),
2037            ("--delete", "delete read only user"),
2038            ("--ro-username=USERNAME", "readonly user name"),
2039            ("--ro-password=PASSWORD", "readonly user password")]
2040        elif cmd == "setting-compaction":
2041            return [
2042            ("", ""),
2043            ("Data/View compaction settings:", ""),
2044            ("  --compaction-db-percentage=PERC",
2045            "Starts compaction once data file fragmentation has reached this percentage"),
2046            ("  --compaction-db-size=SIZE",
2047             "Starts compaction once data file fragmentation has reached this size"),
2048            ("  --compaction-view-percentage=PERC",
2049            "Starts compaction once view file fragmentation has reached this percentage"),
2050            ("  --compaction-view-size=SIZE",
2051             "Starts compaction once view file fragmentation has reached this size"),
2052            ("  --compaction-period-from=HH:MM", "Allow compaction to run after this time"),
2053            ("  --compaction-period-to=HH:MM", "Allow compaction to run before this time"),
2054            ("  --enable-compaction-abort=[0|1]",
2055            "Abort compaction if when run outside of the accepted interval"),
2056            ("  --enable-compaction-parallel=[0|1]", "Allow view/data file compaction at the same time"),
2057            ("", ""),
2058            ("GSI index compaction settings:", ""),
2059            ("  --gsi-compaction-mode", "Sets the gsi compaction mode [append|circular]"),
2060            ("  --compaction-gsi-percentage=PERC",
2061            "Starts compaction once gsi file fragmentation has reached this percentage (Append mode only)"),
2062            ("  --compaction-gsi-interval",
2063            "A comma separated list of days compaction can run (Circular mode only)"),
2064            ("  --compaction-gsi-period-from=HH:MM",
2065            "Allow gsi compaction to run after this time (Circular mode only)"),
2066            ("  --compaction-gsi-period-to=HH:MM",
2067            "Allow gsi compaction to run before this time (Circular mode only)"),
2068            ("  --enable-gsi-compaction-abort=[0|1]",
2069            "Abort gsi compaction if when run outside of the accepted interaval (Circular mode only)")]
2070        elif cmd == "setting-alert":
2071            return [
2072            ("--enable-email-alert=[0|1]", "allow email alert"),
2073            ("--email-recipients=RECIPIENT",
2074             "email recipients, separate addresses with , or ;"),
2075            ("--email-sender=SENDER", "sender email address"),
2076            ("--email-user=USER", "email server username"),
2077            ("--email-password=PWD", "email server password"),
2078            ("--email-host=HOST", "email server host"),
2079            ("--email-port=PORT", "email server port"),
2080            ("--enable-email-encrypt=[0|1]", "email encrypt"),
2081            ("--alert-auto-failover-node", "node was auto failover"),
2082            ("--alert-auto-failover-max-reached",
2083             "maximum number of auto failover nodes was reached"),
2084            ("--alert-auto-failover-node-down",
2085             "node wasn't auto failover as other nodes are down at the same time"),
2086            ("--alert-auto-failover-cluster-small",
2087             "node wasn't auto fail over as cluster was too small"),
2088            ("--alert-auto-failover-disabled",
2089             "node was not auto-failed-over as auto-failover for one or more services running on the node is disabled"),
2090            ("--alert-ip-changed", "node ip address has changed unexpectedly"),
2091            ("--alert-disk-space",
2092             "disk space used for persistent storgage has reached at least 90% capacity"),
2093            ("--alert-meta-overhead",
2094             "metadata overhead is more than 50%"),
2095            ("--alert-meta-oom",
2096             "bucket memory on a node is entirely used for metadata"),
2097            ("--alert-write-failed",
2098             "writing data to disk for a specific bucket has failed"),
2099            ("--alert-audit-msg-dropped", "writing event to audit log has failed")]
2100        elif cmd == "setting-cluster":
2101            return [("--cluster-name=[CLUSTERNAME]", "cluster name"),
2102                    ("--cluster-ramsize=[RAMSIZEMB]", "per node data service ram quota in MB"),
2103                    ("--cluster-index-ramsize=[RAMSIZEMB]","per node index service ram quota in MB"),
2104                    ("--cluster-fts-ramsize=RAMSIZEMB", "per node fts service ram quota in MB")]
2105        elif cmd == "setting-notification":
2106            return [("--enable-notification=[0|1]", "allow notification")]
2107        elif cmd == "setting-autofailover":
2108            return [("--enable-auto-failover=[0|1]", "allow auto failover"),
2109                    ("--auto-failover-timeout=TIMEOUT (>=30)",
2110                     "specify timeout that expires to trigger auto failover")]
2111        elif cmd == "ssl-manage":
2112            return [("--cluster-cert-info", "prints cluster certificate info"),
2113                    ("--node-cert-info", "prints node certificate info"),
2114                    ("--retrieve-cert=CERTIFICATE",
2115                     "retrieve cluster certificate AND save to a pem file"),
2116                    ("--regenerate-cert=CERTIFICATE",
2117                     "regenerate cluster certificate AND save to a pem file"),
2118                    ("--set-node-certificate", "sets the node certificate"),
2119                    ("--upload-cluster-ca", "uploads a new cluster certificate")]
2120        elif cmd == "setting-audit":
2121            return [
2122            ("--audit-log-rotate-interval=[MINUTES]", "log rotation interval"),
2123            ("--audit-log-path=[PATH]", "target log directory"),
2124            ("--audit-enabled=[0|1]", "enable auditing or not")]
2125        elif cmd == "setting-ldap":
2126            return [
2127            ("--ldap-admins=", "full admins, separated by comma"),
2128            ("--ldap-roadmins=", "read only admins, separated by comma"),
2129            ("--ldap-enabled=[0|1]", "using LDAP protocol for authentication"),
2130            ("--ldap-default=[admins|roadmins|none]", "set default ldap accounts")]
2131        elif cmd == "setting-index":
2132            return [
2133            ("--index-max-rollback-points=[5]", "max rollback points"),
2134            ("--index-stable-snapshot-interval=SECONDS", "stable snapshot interval"),
2135            ("--index-memory-snapshot-interval=SECONDS", "in memory snapshot interval"),
2136            ("--index-threads=[4]", "indexer threads"),
2137            ("--index-log-level=[debug|silent|fatal|error|warn|info|verbose|timing|trace]", "indexer log level")]
2138        elif cmd == "collect-logs-start":
2139            return [
2140            ("--all-nodes", "Collect logs from all accessible cluster nodes"),
2141            ("--nodes=HOST[:PORT],HOST[:PORT]",
2142             "Collect logs from the specified subset of cluster nodes"),
2143            ("--upload", "Upload collects logs to specified host"),
2144            ("--upload-host=HOST",
2145             "Host to upload logs to (Manditory when --upload specified)"),
2146            ("--customer=CUSTOMER",
2147             "Customer name to use when uploading logs (Mandatory when --upload specified)"),
2148            ("--ticket=TICKET_NUMBER",
2149             "Ticket number to associate the uploaded logs with")]
2150        elif cmd == "admin-role-manage":
2151            return [
2152            ("--my-roles", "Return a list of roles for the current user."),
2153            ("--get-roles", "Return list of users and roles."),
2154            ("--set-users", "A comma-delimited list of user ids to set acess-control roles for"),
2155            ("--set-names", "A optional quoted, comma-delimited list names, one for each specified user id"),
2156            ("--roles", "A comma-delimited list of roles to set for users, one or more from admin, ro_admin, cluster_admin, replication_admin, bucket_admin[bucket name or '*'], views_admin[bucket name or '*']"),
2157            ("--delete-users", "A comma-delimited list of users to remove from access control")
2158            ]
2159        elif cmd == "master-password":
2160            return [
2161            ("--new-password", "Prompts user for a new master password on this node."),
2162            ("--rotate-data-key", "Rotates the master password data key."),
2163            ("--send-password", "Prompts for the master password to start the server."),
2164            ]
2165        else:
2166            return None
2167
2168    def getCommandExampleHelp(self, cmd):
2169        """ Obtain detailed example help for command
2170        Returns a list of command examples to illustrate how to use command
2171        or None if there's no example help or cmd is unknown.
2172        """
2173
2174        if cmd == "cluster-init":
2175            return [("Set data service ram quota and index ram quota",
2176"""
2177    couchbase-cli cluster-init -c 192.168.0.1:8091 \\
2178       --cluster-username=Administrator \\
2179       --cluster-password=password \\
2180       --cluster-port=8080 \\
2181       --services=data,index \\
2182       --cluster-ramsize=300 \\
2183       --cluster-index-ramsize=256\\
2184       --index-storage-setting=memopt""")]
2185        elif cmd == "cluster-edit":
2186            return [("Change the cluster username, password, port and data service ram quota",
2187"""
2188    couchbase-cli cluster-edit -c 192.168.0.1:8091 \\
2189       --cluster-username=Administrator1 \\
2190       --cluster-password=password1 \\
2191       --cluster-port=8080 \\
2192       --cluster-ramsize=300 \\
2193       -u Administrator -p password""")]
2194        elif cmd == "node-init":
2195            return [("Set data path and hostname for an unprovisioned cluster",
2196"""
2197    couchbse-cli node-init -c 192.168.0.1:8091 \\
2198       --node-init-data-path=/tmp/data \\
2199       --node-init-index-path=/tmp/index \\
2200       --node-init-hostname=myhostname \\
2201       -u Administrator -p password"""),
2202                    ("Change the data path",
2203"""
2204     couchbase-cli node-init -c 192.168.0.1:8091 \\
2205       --node-init-data-path=/tmp \\
2206       -u Administrator -p password""")]
2207        elif cmd == "server-add":
2208            return [("Add a node to a cluster, but do not rebalance",
2209"""
2210    couchbase-cli server-add -c 192.168.0.1:8091 \\
2211       --server-add=192.168.0.2:8091 \\
2212       --server-add-username=Administrator1 \\
2213       --server-add-password=password1 \\
2214
2215       --group-name=group1 \\
2216       --index-storage-setting=memopt \\
2217       -u Administrator -p password"""),
2218                    ("Add a node to a cluster, but do not rebalance",
2219"""
2220    couchbase-cli server-add -c 192.168.0.1:8091 \\
2221       --server-add=192.168.0.2:8091 \\
2222       --server-add-username=Administrator1 \\
2223       --server-add-password=password1 \\
2224       --group-name=group1 \\
2225       -u Administrator -p password""")]
2226        elif cmd == "rebalance":
2227            return [("Add a node to a cluster and rebalance",
2228"""
2229    couchbase-cli rebalance -c 192.168.0.1:8091 \\
2230       --server-add=192.168.0.2:8091 \\
2231       --server-add-username=Administrator1 \\
2232       --server-add-password=password1 \\
2233       --group-name=group1 \\
2234       -u Administrator -p password"""),
2235                    ("Add a node to a cluster and rebalance",
2236"""
2237    couchbase-cli rebalance -c 192.168.0.1:8091 \\
2238       --server-add=192.168.0.2:8091 \\
2239       --server-add-username=Administrator1 \\
2240       --server-add-password=password1 \\
2241       --group-name=group1 \\
2242       -u Administrator -p password"""),
2243                    ("Remove a node from a cluster and rebalance",
2244"""
2245    couchbase-cli rebalance -c 192.168.0.1:8091 \\
2246       --server-remove=192.168.0.2:8091 \\
2247       -u Administrator -p password"""),
2248                    ("Remove and add nodes from/to a cluster and rebalance",
2249"""
2250    couchbase-cli rebalance -c 192.168.0.1:8091 \\
2251      --server-remove=192.168.0.2 \\
2252      --server-add=192.168.0.4 \\
2253      --server-add-username=Administrator1 \\
2254      --server-add-password=password1 \\
2255      --group-name=group1 \\
2256      -u Administrator -p password""")
2257       ]
2258        elif cmd == "rebalance-stop":
2259            return [("Stop the current rebalancing",
2260"""
2261    couchbase-cli rebalance-stop -c 192.168.0.1:8091 \\
2262       -u Administrator -p password""")]
2263        elif cmd == "recovery":
2264            return [("Set recovery type to a server",
2265"""
2266    couchbase-cli recovery -c 192.168.0.1:8091 \\
2267       --server-recovery=192.168.0.2 \\
2268       --recovery-type=full \\
2269       -u Administrator -p password""")]
2270        elif cmd == "failover":
2271            return [("Set a failover, readd, recovery and rebalance sequence operations",
2272"""
2273    couchbase-cli failover -c 192.168.0.1:8091 \\
2274       --server-failover=192.168.0.2 \\
2275       -u Administrator -p password
2276
2277    couchbase-cli server-readd -c 192.168.0.1:8091 \\
2278       --server-add=192.168.0.2 \\
2279       -u Administrator -p password
2280
2281    couchbase-cli recovery -c 192.168.0.1:8091 \\
2282       --server-recovery=192.168.0.2 \\
2283       --recovery-type=delta \\
2284       -u Administrator -p password
2285
2286    couchbase-cli rebalance -c 192.168.0.1:8091 \\
2287       --recovery-buckets="default,bucket1" \\
2288       -u Administrator -p password""")]
2289        elif cmd == "user-manage":
2290            return [("List read only user in a cluster",
2291"""
2292    couchbase-cli user-manage --list -c 192.168.0.1:8091 \\
2293           -u Administrator -p password"""),
2294                ("Delete a read only user in a cluster",
2295"""
2296    couchbase-cli user-manage -c 192.168.0.1:8091 \\
2297        --delete --ro-username=readonlyuser \\
2298        -u Administrator -p password"""),
2299                ("Create/modify a read only user in a cluster",
2300"""
2301    couchbase-cli user-manage -c 192.168.0.1:8091 \\
2302        --set --ro-username=readonlyuser --ro-password=readonlypassword \\
2303        -u Administrator -p password""")]
2304        elif cmd == "group-manage":
2305            return [("Create a new group",
2306"""
2307    couchbase-cli group-manage -c 192.168.0.1:8091 \\
2308        --create --group-name=group1 -u Administrator -p password"""),
2309                ("Delete an empty group",
2310"""
2311    couchbase-cli group-manage -c 192.168.0.1:8091 \\
2312        --delete --group-name=group1 -u Administrator -p password"""),
2313                ("Rename an existed group",
2314"""
2315    couchbase-cli group-manage -c 192.168.0.1:8091 \\
2316        --rename=newgroup --group-name=group1 -u Administrator -p password"""),
2317                ("Show group/server map",
2318"""
2319    couchbase-cli group-manage -c 192.168.0.1:8091 \\
2320        --list -u Administrator -p password"""),
2321                ("Add a server to a group",
2322"""
2323    couchbase-cli group-manage -c 192.168.0.1:8091 \\
2324        --add-servers=10.1.1.1:8091,10.1.1.2:8091 \\
2325        --group-name=group1 \\
2326        --server-add-username=Administrator1 \\
2327        --server-add-password=password1 \\
2328        --services=data,index,query,fts \\
2329        -u Administrator -p password"""),
2330                ("Move list of servers from group1 to group2",
2331"""
2332    couchbase-cli group-manage -c 192.168.0.1:8091 \\
2333        --move-servers=10.1.1.1:8091,10.1.1.2:8091 \\
2334        --from-group=group1 \\
2335        --to-group=group2 \\
2336        -u Administrator -p password""")]
2337        elif cmd == "ssl-manage":
2338            return [("Download a cluster certificate",
2339"""
2340    couchbase-cli ssl-manage -c 192.168.0.1:8091 \\
2341        --retrieve-cert=/tmp/test.pem \\
2342        -u Administrator -p password
2343
2344    couchbase-cli ssl-manage -c 192.168.0.1:8091 \\
2345        --cluster-cert-info \\
2346        -u Administrator -p password"""),
2347                ("Regenerate AND download a cluster certificate",
2348"""
2349    couchbase-cli ssl-manage -c 192.168.0.1:8091 \\
2350        --regenerate-cert=/tmp/test.pem \\
2351        -u Administrator -p password"""),
2352                ("Download the extended cluster certificate",
2353"""
2354    couchbase-cli ssl-manage -c 192.168.0.1:8091 \\
2355        --cluster-cert-info --extended \\
2356        -u Administrator -p password"""),
2357                ("Download the current node certificate",
2358"""
2359    couchbase-cli ssl-manage -c 192.168.0.1:8091 \\
2360        --node-cert-info \\
2361        -u Administrator -p password"""),
2362                ("Upload a new cluster certificate",
2363"""
2364    couchbase-cli ssl-manage -c 192.168.0.1:8091 \\
2365        --upload-cluster-ca=/tmp/test.pem \\
2366        -u Administrator -p password"""),
2367                ("Set the new node certificate",
2368"""
2369    couchbase-cli ssl-manage -c 192.168.0.1:8091 \\
2370        --set-node-certificate \\
2371        -u Administrator -p password""")]
2372        elif cmd == "collect-logs-start":
2373            return [("Start cluster-wide log collection for whole cluster",
2374"""
2375    couchbase-cli collect-logs-start -c 192.168.0.1:8091 \\
2376        -u Administrator -p password \\
2377        --all-nodes --upload --upload-host=host.upload.com \\
2378        --customer="example inc" --ticket=12345"""),
2379                ("Start cluster-wide log collection for selected nodes",
2380"""
2381    couchbase-cli collect-logs-start -c 192.168.0.1:8091 \\
2382        -u Administrator -p password \\
2383        --nodes=10.1.2.3:8091,10.1.2.4 --upload --upload-host=host.upload.com \\
2384        --customer="example inc" --ticket=12345""")]
2385        elif cmd == "collect-logs-stop":
2386            return [("Stop cluster-wide log collection",
2387"""
2388    couchbase-cli collect-logs-stop -c 192.168.0.1:8091 \\
2389        -u Administrator -p password""")]
2390        elif cmd == "collect-logs-status":
2391            return [("Show status of cluster-wide log collection",
2392"""
2393    couchbase-cli collect-logs-status -c 192.168.0.1:8091 \\
2394        -u Administrator -p password""")]
2395        elif cmd == "setting-ldap":
2396            return [("Enable LDAP with None default",
2397"""
2398    couchbase-cli setting-ldap -c 192.168.0.1:8091 \\
2399        --ldap-enabled=1 --ldap-admins=u1,u2 --ldap-roadmins=u3,u3,u5 \\
2400        -u Administrator -p password"""),
2401                ("Enable LDAP with full admin default",
2402"""
2403    couchbase-cli setting-ldap -c 192.168.0.1:8091 \\
2404        --ldap-enabled=1 --ldap-default=admins --ldap-roadmins=u3,u3,u5 \\
2405        -u Administrator -p password"""),
2406                ("Enable LDAP with read only default",
2407"""
2408    couchbase-cli setting-ldap -c 192.168.0.1:8091 \\
2409        --ldap-enabled=1 --ldap-default=roadmins --ldap-admins=u1,u2 \\
2410        -u Administrator -p password"""),
2411                ("Disable LDAP",
2412"""
2413    couchbase-cli setting-ldap -c 192.168.0.1:8091 \\
2414        --ldap-enabled=0  -u Administrator -p password""")]
2415        elif cmd == "setting-audit":
2416            return [("Enable audit",
2417"""
2418    couchbase-cli setting-audit -c 192.168.0.1:8091 \\
2419        --audit-enabled=1 --audit-log-rotate-interval=900 \\
2420        --audit-log-path="/opt/couchbase/var/lib/couchbase/logs"
2421        -u Administrator -p password"""),
2422                ("Disable audit",
2423"""
2424    couchbase-cli setting-audit -c 192.168.0.1:8091 \\
2425        --audit-enabled=0 -u Administrator -p password""")]
2426        elif cmd == "setting-index":
2427            return [("Indexer setting",
2428"""
2429    couchbase-cli setting-index  -c 192.168.0.1:8091 \\
2430        --index-max-rollback-points=5 \\
2431        --index-stable-snapshot-interval=5000 \\
2432        --index-memory-snapshot-interval=200 \\
2433        --index-threads=5 \\
2434        --index-log-level=debug \\
2435        -u Administrator -p password""")]
2436
2437        elif cmd == "admin-role-manage":
2438            return [("Show the current users roles.",
2439"""
2440    couchbase-cli admin-role-manage -c 192.168.0.1:8091 --my-roles
2441            """),
2442            ("Get a list of all users and roles",
2443"""
2444    couchbase-cli admin-role-manage -c 192.168.0.1:8091 --get-roles
2445            """),
2446                    ("Make bob and mary cluster_admins, and bucket admins for the default bucket",
2447"""
2448    couchbase-cli admin-role-manage -c 192.168.0.1:8091 \\
2449        --set-users=bob,mary --set-names="Bob Smith,Mary Jones" --roles=cluster_admin,bucket_admin[default]
2450            """),
2451                    ("Make jen bucket admins for all buckets",
2452"""
2453    couchbase-cli admin-role-manage -c 192.168.0.1:8091 \\
2454        --set-users=jen --roles=bucket_admin[*]
2455            """),
2456            ("Remove all roles for bob",
2457"""
2458    couchbase-cli admin-role-manage -c 192.168.0.1:8091 --delete-users=bob
2459            """)
2460            ]
2461        elif cmd == "master-password":
2462            return [("Change the master password",
2463"""
2464    couchbase-cli master-password -c 192.168.0.1:8091 -u Administrator \\
2465        -p password --new-password
2466            """),
2467            ("Rotate the master password data key",
2468"""
2469    couchbase-cli master-password -c 192.168.0.1:8091 -u Administrator \\
2470        -p password --rotate-data-key
2471            """),
2472            ("Send the master password to the server",
2473"""
2474    couchbase-cli master-password -c 192.168.0.1:8091 --send-password
2475            """),
2476            ]
2477
2478        else:
2479            return None
2480
2481def _exitIfErrors(errors, prefix=""):
2482    if errors:
2483        for error in errors:
2484            print prefix + error
2485        sys.exit(1)
2486
2487def _exitOnFileWriteFailure(fname, bytes):
2488    try:
2489        fp = open(fname, 'w')
2490        fp.write(bytes)
2491        fp.close()
2492    except IOError, error:
2493        print "ERROR:", error
2494        sys.exit(1)
2495
2496def _exitOnFileReadFailure(fname):
2497    try:
2498        fp = open(fname, 'r')
2499        bytes = fp.read()
2500        fp.close()
2501        return bytes
2502    except IOError, error:
2503        print "ERROR:", error
2504        sys.exit(1)
2505