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