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