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